Compare commits

...

52 Commits

Author SHA1 Message Date
maxswa
4872d04301 v2.16.2 2023-11-26 19:53:20 -05:00
Max Swartwout
55312ba0fd Merge pull request #85 from seditionist/main
Add Custom Errors
2023-11-26 19:52:07 -05:00
maxswa
331a448503 Disabled eslint rule. 2023-11-26 19:43:35 -05:00
maxswa
84c704a846 Add test for excluded gamemodes. 2023-11-26 19:42:46 -05:00
seditionist
561495632c Fix gamemode checks 2023-11-26 18:55:26 -05:00
seditionist
bdfdf1eb14 Merge branch 'maxswa-main' 2023-11-26 18:23:31 -05:00
seditionist
e349776060 Merge branch 'main' of github.com:maxswa/osrs-json-hiscores into maxswa-main
# Conflicts:
#	src/hiscores.ts
2023-11-26 18:20:03 -05:00
seditionist
546bc5acf7 Remove unnecessary type cast 2023-11-26 17:43:38 -05:00
seditionist
66528cd9d9 Restore error message constants 2023-11-26 17:42:55 -05:00
maxswa
99ea3fb722 v2.16.1 2023-11-25 22:07:55 -05:00
Max Swartwout
7f16d26e3c Merge pull request #89 from maxswa/bump-axios
Bump `axios`
2023-11-25 22:06:07 -05:00
maxswa
ed9fcf47b3 Bump axios 2023-11-25 22:04:48 -05:00
Max Swartwout
bb935bb6c9 Merge pull request #86 from maxswa/dependabot/npm_and_yarn/babel/traverse-7.23.2
Bump @babel/traverse from 7.22.8 to 7.23.2
2023-11-25 21:36:52 -05:00
dependabot[bot]
d046fffa9e Bump @babel/traverse from 7.22.8 to 7.23.2
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.22.8 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-19 11:00:07 +00:00
seditionist
5483e54d78 cleanup 2023-08-17 00:58:37 -04:00
seditionist
97532b0c03 fix tests 2023-08-17 00:35:22 -04:00
seditionist
f9a56e2b3a update hiscores to throw new custom errors 2023-08-17 00:32:23 -04:00
seditionist
9f43f0c39f update validRSN to use InvalidRSNError 2023-08-17 00:12:27 -04:00
seditionist
d67ffa3e20 created custom errors 2023-08-17 00:11:18 -04:00
maxswa
7bdde9f5d1 v2.16.0 2023-08-15 21:26:53 -04:00
Max Swartwout
b4a50e69c8 Merge pull request #83 from maxswa/json-endpoint
Use Official JSON Endpoint
2023-08-15 21:25:57 -04:00
maxswa
7935c5a523 Update name matching to use lowercase values. 2023-08-12 11:19:33 -04:00
maxswa
f1e2155428 Fix tests. 2023-08-12 11:01:06 -04:00
maxswa
dd90fe1260 Update README with new formatted names. 2023-08-11 15:11:38 -04:00
maxswa
12c4e530a2 Update get stats functions to use JSON endpoint. 2023-08-11 14:59:49 -04:00
maxswa
5a2b0d9ad8 Add functions to support JSON endpoint. 2023-08-11 14:52:02 -04:00
maxswa
c43858eac1 v2.15.0 2023-07-26 18:50:54 -04:00
Max Swartwout
0d0081f4f6 Merge pull request #81 from wpdough/main
Add new DT2 bosses
2023-07-26 18:49:03 -04:00
wpdough
3570f0f07d Update README and revert yarn.lock 2023-07-26 14:32:13 -04:00
wpdough
3ddda54e02 Merge branch 'main' of https://github.com/wpdough/osrs-json-hiscores into main 2023-07-26 14:08:46 -04:00
wpdough
388f01522a Add new DT2 bosses 2023-07-26 14:08:38 -04:00
wpdough
24b3de2a29 Add new DT2 bosses 2023-07-26 12:00:23 -04:00
maxswa
fc7897d4c7 v2.14.2 2023-07-15 20:48:08 -04:00
Max Swartwout
72ea31490f Merge pull request #80 from maxswa/update-test-lint-format-libs
Update testing, linting and formatting libraries
2023-07-13 16:40:08 -04:00
maxswa
66825955c1 Update linting and formatting libraries. 2023-07-13 16:36:37 -04:00
maxswa
4b4bf8c702 Update jest, jsdom, typescript 2023-07-13 16:16:07 -04:00
Max Swartwout
53981e7468 Merge pull request #78 from maxswa/dependabot/npm_and_yarn/semver-5.7.2
Bump semver from 5.7.1 to 5.7.2
2023-07-13 16:04:52 -04:00
dependabot[bot]
a90e5b98dd Bump semver from 5.7.1 to 5.7.2
Bumps [semver](https://github.com/npm/node-semver) from 5.7.1 to 5.7.2.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/v5.7.2/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v5.7.1...v5.7.2)

---
updated-dependencies:
- dependency-name: semver
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-11 11:51:33 +00:00
maxswa
16a975f10b v2.14.1 2023-06-16 15:36:44 -04:00
Max Swartwout
c800501188 Merge pull request #77 from maxswa/axios-config
Add optional axios config argument
2023-06-16 15:36:14 -04:00
maxswa
8a320df015 Update necessary jsdoc comments. 2023-05-28 13:47:21 -04:00
maxswa
169462c446 Add optional config arg to all fns using httpGet 2023-05-28 13:42:37 -04:00
maxswa
317a972314 Add optional axios configs to GetStatsOptions 2023-05-28 13:41:53 -04:00
maxswa
c558f8402c Add optional axios config arg to httpGet 2023-05-28 13:41:02 -04:00
maxswa
41c6da4d5c v2.14.0 2023-05-26 09:40:51 -04:00
Max Swartwout
99ad5eb662 Merge pull request #76 from dmeredith96/fix-invalid-format-err
Add support for the Legacy Bounty Hunter minigame
2023-05-26 09:38:31 -04:00
David Meredith
a8cf1713af Change approach to tracking the new Bounty Hunter stats 2023-05-25 09:37:30 -05:00
David Meredith
11391c90f2 Add support for the Legacy Bounty Hunter minigame 2023-05-24 08:48:46 -05:00
maxswa
e4d2b10488 v2.13.1 2023-04-12 21:51:49 -04:00
Max Swartwout
0cfd0e2471 Merge pull request #73 from davidvorona/main
Fix spelling of Calvar'ion
2023-04-12 21:50:54 -04:00
David Vorona
9c284ad4b0 Fix spelling of Calvar'ion 2023-04-12 10:26:06 -07:00
Max Swartwout
ec60a72c08 Merge pull request #71 from maxswa/fix-gh-workflow
Update GH action versions.
2023-04-12 13:04:45 -04:00
15 changed files with 3536 additions and 3443 deletions

3
.gitignore vendored
View File

@@ -1,3 +1,4 @@
.idea/ .idea/
node_modules node_modules
/lib /lib
.vscode

View File

@@ -104,14 +104,16 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses
### Minigames ### Minigames
| Minigame | Param | | Minigame | Param |
| ---------------------- | :---------------: | | ------------------------------- | :---------------: |
| Bounty Hunter (Rogue) | `rogueBH` | | Bounty Hunter (Legacy - Rogue) | `rogueBH` |
| Bounty Hunter (Hunter) | `hunterBH` | | Bounty Hunter (Legacy - Hunter) | `hunterBH` |
| Last Man Standing | `lastManStanding` | | Bounty Hunter (Rogue) | `rogueBHV2` |
| PvP Arena | `pvpArena` | | Bounty Hunter (Hunter) | `hunterBHV2` |
| Soul Wars Zeal | `soulWarsZeal` | | LMS - Rank | `lastManStanding` |
| Rifts Closed | `riftsClosed` | | PvP Arena - Rank | `pvpArena` |
| Soul Wars Zeal | `soulWarsZeal` |
| Rifts closed | `riftsClosed` |
### Leagues ### Leagues
@@ -129,7 +131,7 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses
| Barrows Chests | `barrows` | | Barrows Chests | `barrows` |
| Bryophyta | `bryophyta` | | Bryophyta | `bryophyta` |
| Callisto | `callisto` | | Callisto | `callisto` |
| Cal'varion | `calvarion` | | Calvar'ion | `calvarion` |
| Cerberus | `cerberus` | | Cerberus | `cerberus` |
| Chambers Of Xeric | `chambersOfXeric` | | Chambers Of Xeric | `chambersOfXeric` |
| Chambers Of Xeric: Challenge Mode | `chambersOfXericChallengeMode` | | Chambers Of Xeric: Challenge Mode | `chambersOfXericChallengeMode` |
@@ -142,6 +144,7 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses
| Dagannoth Rex | `dagannothRex` | | Dagannoth Rex | `dagannothRex` |
| Dagannoth Supreme | `dagannothSupreme` | | Dagannoth Supreme | `dagannothSupreme` |
| Deranged Archaeologist | `derangedArchaeologist` | | Deranged Archaeologist | `derangedArchaeologist` |
| Duke Sucellus | `dukeSucellus` |
| General Graardor | `generalGraardor` | | General Graardor | `generalGraardor` |
| Giant Mole | `giantMole` | | Giant Mole | `giantMole` |
| Grotesque Guardians | `grotesqueGuardians` | | Grotesque Guardians | `grotesqueGuardians` |
@@ -153,7 +156,7 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses
| K'ril Tsutsaroth | `krilTsutsaroth` | | K'ril Tsutsaroth | `krilTsutsaroth` |
| Mimic | `mimic` | | Mimic | `mimic` |
| Nex | `nex` | | Nex | `nex` |
| The Nightmare of Ashihama | `nightmare` | | Nightmare | `nightmare` |
| Phosani's Nightmare | `phosanisNightmare` | | Phosani's Nightmare | `phosanisNightmare` |
| Obor | `obor` | | Obor | `obor` |
| Phantom Muspah | `phantomMuspah` | | Phantom Muspah | `phantomMuspah` |
@@ -164,6 +167,8 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses
| Tempoross | `tempoross` | | Tempoross | `tempoross` |
| The Gauntlet | `gauntlet` | | The Gauntlet | `gauntlet` |
| The Corrupted Gauntlet | `corruptedGauntlet` | | The Corrupted Gauntlet | `corruptedGauntlet` |
| The Leviathan | `leviathan` |
| The Whisperer | `whisperer` |
| Theatre Of Blood | `theatreOfBlood` | | Theatre Of Blood | `theatreOfBlood` |
| Theatre Of Blood: Hard Mode | `theatreOfBloodHardMode` | | Theatre Of Blood: Hard Mode | `theatreOfBloodHardMode` |
| Thermonuclear Smoke Devil | `thermonuclearSmokeDevil` | | Thermonuclear Smoke Devil | `thermonuclearSmokeDevil` |
@@ -171,6 +176,7 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses
| Tombs of Amascut: Expert Mode | `tombsOfAmascutExpertMode` | | Tombs of Amascut: Expert Mode | `tombsOfAmascutExpertMode` |
| TzKal-Zuk | `tzKalZuk` | | TzKal-Zuk | `tzKalZuk` |
| TzTok-Jad | `tzTokJad` | | TzTok-Jad | `tzTokJad` |
| Vardorvis | `vardorvis` |
| Venenatis | `venenatis` | | Venenatis | `venenatis` |
| Vetion | `vetion` | | Vetion | `vetion` |
| Vorkath | `vorkath` | | Vorkath | `vorkath` |

File diff suppressed because it is too large Load Diff

98
__tests__/b0atyStats.csv Normal file
View File

@@ -0,0 +1,98 @@
3923,2277,792976020
23024,99,29963318
12244,99,28197154
2261,99,134139762
4074,99,112054504
7166,99,101083246
3747,99,15110682
4516,99,50824811
126288,99,13353110
24800,99,16366466
49293,99,13453226
20784,99,16654289
16376,99,19114676
42069,99,14055874
9305,99,16527135
25135,99,14657869
35831,99,13233130
6197,99,15739327
48215,99,13963265
4786,99,33830972
3402,99,62531484
4314,99,22733194
14826,99,17399368
1009,99,17989158
-1,-1
22270,10
10481,3
149217,8
103672,7
253,9508
713,825
71,6000
1943,1600
42364,287
467,436
1634,360
12937,2521
-1,-1
5222,14780
3007,1060
2578,2192
84703,594
19500,143
3335,1830
191697,5
27457,293
43607,58
17384,1905
171607,33
2074,501
78908,50
135830,26
48781,287
2112,2000
138748,41
31297,707
40908,698
31273,714
76034,25
2391,688
17409,1228
60841,474
1895,2140
19948,159
1855,2046
46423,579
107879,1908
9791,920
2050,1444
848,33
24797,620
4157,744
4154,500
880,217
15860,251
60424,119
100429,26
358191,9
1131,1569
33908,273
165651,7
64278,201
1533,583
748,700
21700,250
13200,7
6622,3066
87093,35
2449,502
14541,2
-1,-1
1048,1432
138,7096
58203,51
13949,2780
827440,62
153191,25
216690,274
Can't render this file because it has a wrong number of fields in line 25.

194
__tests__/b0atyStats.json Normal file
View File

@@ -0,0 +1,194 @@
{
"skills": [
{
"id": 0,
"name": "Overall",
"rank": 3923,
"level": 2277,
"xp": 792976020
},
{ "id": 1, "name": "Attack", "rank": 23024, "level": 99, "xp": 29963318 },
{ "id": 2, "name": "Defence", "rank": 12244, "level": 99, "xp": 28197154 },
{ "id": 3, "name": "Strength", "rank": 2261, "level": 99, "xp": 134139762 },
{
"id": 4,
"name": "Hitpoints",
"rank": 4074,
"level": 99,
"xp": 112054504
},
{ "id": 5, "name": "Ranged", "rank": 7166, "level": 99, "xp": 101083246 },
{ "id": 6, "name": "Prayer", "rank": 3747, "level": 99, "xp": 15110682 },
{ "id": 7, "name": "Magic", "rank": 4516, "level": 99, "xp": 50824811 },
{ "id": 8, "name": "Cooking", "rank": 126288, "level": 99, "xp": 13353110 },
{
"id": 9,
"name": "Woodcutting",
"rank": 24800,
"level": 99,
"xp": 16366466
},
{
"id": 10,
"name": "Fletching",
"rank": 49293,
"level": 99,
"xp": 13453226
},
{ "id": 11, "name": "Fishing", "rank": 20784, "level": 99, "xp": 16654289 },
{
"id": 12,
"name": "Firemaking",
"rank": 16376,
"level": 99,
"xp": 19114676
},
{
"id": 13,
"name": "Crafting",
"rank": 42069,
"level": 99,
"xp": 14055874
},
{ "id": 14, "name": "Smithing", "rank": 9305, "level": 99, "xp": 16527135 },
{ "id": 15, "name": "Mining", "rank": 25135, "level": 99, "xp": 14657869 },
{
"id": 16,
"name": "Herblore",
"rank": 35831,
"level": 99,
"xp": 13233130
},
{ "id": 17, "name": "Agility", "rank": 6197, "level": 99, "xp": 15739327 },
{
"id": 18,
"name": "Thieving",
"rank": 48215,
"level": 99,
"xp": 13963265
},
{ "id": 19, "name": "Slayer", "rank": 4786, "level": 99, "xp": 33830972 },
{ "id": 20, "name": "Farming", "rank": 3402, "level": 99, "xp": 62531484 },
{
"id": 21,
"name": "Runecraft",
"rank": 4314,
"level": 99,
"xp": 22733194
},
{ "id": 22, "name": "Hunter", "rank": 14826, "level": 99, "xp": 17399368 },
{
"id": 23,
"name": "Construction",
"rank": 1009,
"level": 99,
"xp": 17989158
}
],
"activities": [
{ "id": 0, "name": "League Points", "rank": -1, "score": -1 },
{ "id": 1, "name": "Bounty Hunter - Hunter", "rank": 22270, "score": 10 },
{ "id": 2, "name": "Bounty Hunter - Rogue", "rank": 10481, "score": 3 },
{
"id": 3,
"name": "Bounty Hunter (Legacy) - Hunter",
"rank": 149217,
"score": 8
},
{
"id": 4,
"name": "Bounty Hunter (Legacy) - Rogue",
"rank": 103672,
"score": 7
},
{ "id": 5, "name": "Clue Scrolls (all)", "rank": 253, "score": 9508 },
{ "id": 6, "name": "Clue Scrolls (beginner)", "rank": 713, "score": 825 },
{ "id": 7, "name": "Clue Scrolls (easy)", "rank": 71, "score": 6000 },
{ "id": 8, "name": "Clue Scrolls (medium)", "rank": 1943, "score": 1600 },
{ "id": 9, "name": "Clue Scrolls (hard)", "rank": 42364, "score": 287 },
{ "id": 10, "name": "Clue Scrolls (elite)", "rank": 467, "score": 436 },
{ "id": 11, "name": "Clue Scrolls (master)", "rank": 1634, "score": 360 },
{ "id": 12, "name": "LMS - Rank", "rank": 12937, "score": 2521 },
{ "id": 13, "name": "PvP Arena - Rank", "rank": -1, "score": -1 },
{ "id": 14, "name": "Soul Wars Zeal", "rank": 5222, "score": 14780 },
{ "id": 15, "name": "Rifts closed", "rank": 3007, "score": 1060 },
{ "id": 16, "name": "Abyssal Sire", "rank": 2578, "score": 2192 },
{ "id": 17, "name": "Alchemical Hydra", "rank": 84703, "score": 594 },
{ "id": 18, "name": "Artio", "rank": 19500, "score": 143 },
{ "id": 19, "name": "Barrows Chests", "rank": 3335, "score": 1830 },
{ "id": 20, "name": "Bryophyta", "rank": 191697, "score": 5 },
{ "id": 21, "name": "Callisto", "rank": 27457, "score": 293 },
{ "id": 22, "name": "Calvar'ion", "rank": 43607, "score": 58 },
{ "id": 23, "name": "Cerberus", "rank": 17384, "score": 1905 },
{ "id": 24, "name": "Chambers of Xeric", "rank": 171607, "score": 33 },
{
"id": 25,
"name": "Chambers of Xeric: Challenge Mode",
"rank": 2074,
"score": 501
},
{ "id": 26, "name": "Chaos Elemental", "rank": 78908, "score": 50 },
{ "id": 27, "name": "Chaos Fanatic", "rank": 135830, "score": 26 },
{ "id": 28, "name": "Commander Zilyana", "rank": 48781, "score": 287 },
{ "id": 29, "name": "Corporeal Beast", "rank": 2112, "score": 2000 },
{ "id": 30, "name": "Crazy Archaeologist", "rank": 138748, "score": 41 },
{ "id": 31, "name": "Dagannoth Prime", "rank": 31297, "score": 707 },
{ "id": 32, "name": "Dagannoth Rex", "rank": 40908, "score": 698 },
{ "id": 33, "name": "Dagannoth Supreme", "rank": 31273, "score": 714 },
{ "id": 34, "name": "Deranged Archaeologist", "rank": 76034, "score": 25 },
{ "id": 35, "name": "Duke Sucellus", "rank": 2391, "score": 688 },
{ "id": 36, "name": "General Graardor", "rank": 17409, "score": 1228 },
{ "id": 37, "name": "Giant Mole", "rank": 60841, "score": 474 },
{ "id": 38, "name": "Grotesque Guardians", "rank": 1895, "score": 2140 },
{ "id": 39, "name": "Hespori", "rank": 19948, "score": 159 },
{ "id": 40, "name": "Kalphite Queen", "rank": 1855, "score": 2046 },
{ "id": 41, "name": "King Black Dragon", "rank": 46423, "score": 579 },
{ "id": 42, "name": "Kraken", "rank": 107879, "score": 1908 },
{ "id": 43, "name": "Kree'Arra", "rank": 9791, "score": 920 },
{ "id": 44, "name": "K'ril Tsutsaroth", "rank": 2050, "score": 1444 },
{ "id": 45, "name": "Mimic", "rank": 848, "score": 33 },
{ "id": 46, "name": "Nex", "rank": 24797, "score": 620 },
{ "id": 47, "name": "Nightmare", "rank": 4157, "score": 744 },
{ "id": 48, "name": "Phosani's Nightmare", "rank": 4154, "score": 500 },
{ "id": 49, "name": "Obor", "rank": 880, "score": 217 },
{ "id": 50, "name": "Phantom Muspah", "rank": 15860, "score": 251 },
{ "id": 51, "name": "Sarachnis", "rank": 60424, "score": 119 },
{ "id": 52, "name": "Scorpia", "rank": 100429, "score": 26 },
{ "id": 53, "name": "Skotizo", "rank": 358191, "score": 9 },
{ "id": 54, "name": "Spindel", "rank": 1131, "score": 1569 },
{ "id": 55, "name": "Tempoross", "rank": 33908, "score": 273 },
{ "id": 56, "name": "The Gauntlet", "rank": 165651, "score": 7 },
{ "id": 57, "name": "The Corrupted Gauntlet", "rank": 64278, "score": 201 },
{ "id": 58, "name": "The Leviathan", "rank": 1533, "score": 583 },
{ "id": 59, "name": "The Whisperer", "rank": 748, "score": 700 },
{ "id": 60, "name": "Theatre of Blood", "rank": 21700, "score": 250 },
{
"id": 61,
"name": "Theatre of Blood: Hard Mode",
"rank": 13200,
"score": 7
},
{
"id": 62,
"name": "Thermonuclear Smoke Devil",
"rank": 6622,
"score": 3066
},
{ "id": 63, "name": "Tombs of Amascut", "rank": 87093, "score": 35 },
{
"id": 64,
"name": "Tombs of Amascut: Expert Mode",
"rank": 2449,
"score": 502
},
{ "id": 65, "name": "TzKal-Zuk", "rank": 14541, "score": 2 },
{ "id": 66, "name": "TzTok-Jad", "rank": -1, "score": -1 },
{ "id": 67, "name": "Vardorvis", "rank": 1048, "score": 1432 },
{ "id": 68, "name": "Venenatis", "rank": 138, "score": 7096 },
{ "id": 69, "name": "Vet'ion", "rank": 58203, "score": 51 },
{ "id": 70, "name": "Vorkath", "rank": 13949, "score": 2780 },
{ "id": 71, "name": "Wintertodt", "rank": 827440, "score": 62 },
{ "id": 72, "name": "Zalcano", "rank": 153191, "score": 25 },
{ "id": 73, "name": "Zulrah", "rank": 216690, "score": 274 }
]
}

View File

@@ -12,7 +12,10 @@ import {
getSkillPageURL, getSkillPageURL,
getStatsURL, getStatsURL,
BOSSES, BOSSES,
INVALID_FORMAT_ERROR InvalidFormatError,
BH_MODES,
parseJsonStats,
HiscoresResponse
} from '../src/index'; } from '../src/index';
const B0ATY_NAME = 'B0ATY'; const B0ATY_NAME = 'B0ATY';
@@ -24,7 +27,13 @@ const LYNX_TITAN_FORMATTED_NAME = 'Lynx Titan';
const attackTopPage = readFileSync(`${__dirname}/attackTopPage.html`, 'utf8'); const attackTopPage = readFileSync(`${__dirname}/attackTopPage.html`, 'utf8');
const b0atyNamePage = readFileSync(`${__dirname}/b0atyNamePage.html`, 'utf8'); const b0atyNamePage = readFileSync(`${__dirname}/b0atyNamePage.html`, 'utf8');
const lynxTitanStats = readFileSync(`${__dirname}/lynxTitanStats.csv`, 'utf8'); const b0atyStatsCsv = readFileSync(`${__dirname}/b0atyStats.csv`, 'utf8');
const b0atyStatsJson: HiscoresResponse = JSON.parse(
readFileSync(`${__dirname}/b0atyStats.json`, 'utf8')
);
const lynxTitanStats = JSON.parse(
readFileSync(`${__dirname}/lynxTitanStats.json`, 'utf8')
);
const lynxTitanNamePage = readFileSync( const lynxTitanNamePage = readFileSync(
`${__dirname}/lynxTitanNamePage.html`, `${__dirname}/lynxTitanNamePage.html`,
'utf8' 'utf8'
@@ -45,7 +54,7 @@ jest.spyOn(axios, 'get').mockImplementation((url) => {
if (getSkillPageURL('main', 'attack', 1) === url) { if (getSkillPageURL('main', 'attack', 1) === url) {
return Promise.resolve({ data: attackTopPage }); return Promise.resolve({ data: attackTopPage });
} }
if (getStatsURL('main', LYNX_TITAN_FORMATTED_NAME) === url) { if (getStatsURL('main', LYNX_TITAN_FORMATTED_NAME, true) === url) {
return Promise.resolve({ status: 200, data: lynxTitanStats }); return Promise.resolve({ status: 200, data: lynxTitanStats });
} }
throw new Error(`No mock response for URL: ${url}`); throw new Error(`No mock response for URL: ${url}`);
@@ -77,6 +86,8 @@ test('Parse CSV to json', () => {
810,99,37688883 810,99,37688883
92,99,32005622 92,99,32005622
23423,478 23423,478
89914,35
99834,25
99831,23 99831,23
89912,37 89912,37
32,12148 32,12148
@@ -109,6 +120,7 @@ test('Parse CSV to json', () => {
4342,1655 4342,1655
966,2951 966,2951
10151,1 10151,1
1289,2477
1288,2407 1288,2407
377,4669 377,4669
545,1567 545,1567
@@ -131,6 +143,8 @@ test('Parse CSV to json', () => {
18823,23 18823,23
63,375 63,375
2870,6 2870,6
2871,7
2872,8
6984,138 6984,138
23,923141 23,923141
4043,2000 4043,2000
@@ -138,6 +152,7 @@ test('Parse CSV to json', () => {
289,13070 289,13070
489,8 489,8
967,47 967,47
968,48
11155,223 11155,223
1940,272 1940,272
8623,1340 8623,1340
@@ -174,6 +189,8 @@ test('Parse CSV to json', () => {
}, },
leaguePoints: { rank: 23423, score: 478 }, leaguePoints: { rank: 23423, score: 478 },
bountyHunter: { bountyHunter: {
hunterV2: { rank: 89914, score: 35 },
rogueV2: { rank: 99834, score: 25 },
hunter: { rank: 99831, score: 23 }, hunter: { rank: 99831, score: 23 },
rogue: { rank: 89912, score: 37 } rogue: { rank: 89912, score: 37 }
}, },
@@ -210,6 +227,7 @@ test('Parse CSV to json', () => {
dagannothRex: { rank: 4342, score: 1655 }, dagannothRex: { rank: 4342, score: 1655 },
dagannothSupreme: { rank: 966, score: 2951 }, dagannothSupreme: { rank: 966, score: 2951 },
derangedArchaeologist: { rank: 10151, score: 1 }, derangedArchaeologist: { rank: 10151, score: 1 },
dukeSucellus: { rank: 1289, score: 2477 },
generalGraardor: { rank: 1288, score: 2407 }, generalGraardor: { rank: 1288, score: 2407 },
giantMole: { rank: 377, score: 4669 }, giantMole: { rank: 377, score: 4669 },
grotesqueGuardians: { rank: 545, score: 1567 }, grotesqueGuardians: { rank: 545, score: 1567 },
@@ -232,6 +250,8 @@ test('Parse CSV to json', () => {
tempoross: { rank: 18823, score: 23 }, tempoross: { rank: 18823, score: 23 },
gauntlet: { rank: 63, score: 375 }, gauntlet: { rank: 63, score: 375 },
corruptedGauntlet: { rank: 2870, score: 6 }, corruptedGauntlet: { rank: 2870, score: 6 },
leviathan: { rank: 2871, score: 7 },
whisperer: { rank: 2872, score: 8 },
theatreOfBlood: { rank: 6984, score: 138 }, theatreOfBlood: { rank: 6984, score: 138 },
theatreOfBloodHardMode: { rank: 23, score: 923141 }, theatreOfBloodHardMode: { rank: 23, score: 923141 },
thermonuclearSmokeDevil: { rank: 4043, score: 2000 }, thermonuclearSmokeDevil: { rank: 4043, score: 2000 },
@@ -239,6 +259,7 @@ test('Parse CSV to json', () => {
tombsOfAmascutExpertMode: { rank: 289, score: 13070 }, tombsOfAmascutExpertMode: { rank: 289, score: 13070 },
tzKalZuk: { rank: 489, score: 8 }, tzKalZuk: { rank: 489, score: 8 },
tzTokJad: { rank: 967, score: 47 }, tzTokJad: { rank: 967, score: 47 },
vardorvis: { rank: 968, score: 48 },
venenatis: { rank: 11155, score: 223 }, venenatis: { rank: 11155, score: 223 },
vetion: { rank: 1940, score: 272 }, vetion: { rank: 1940, score: 272 },
vorkath: { rank: 8623, score: 1340 }, vorkath: { rank: 8623, score: 1340 },
@@ -255,12 +276,12 @@ test('Parse CSV with unknown activity', () => {
const statsWithUnknownActivity = `${lynxTitanStats} const statsWithUnknownActivity = `${lynxTitanStats}
-1,-1`; -1,-1`;
expect(() => parseStats(statsWithUnknownActivity)).toThrow( expect(() => parseStats(statsWithUnknownActivity)).toThrow(
INVALID_FORMAT_ERROR InvalidFormatError
); );
}); });
test('Parse invalid CSV', () => { test('Parse invalid CSV', () => {
expect(() => parseStats('invalid')).toThrow(INVALID_FORMAT_ERROR); expect(() => parseStats('invalid')).toThrow(InvalidFormatError);
}); });
describe('Get name format', () => { describe('Get name format', () => {
@@ -475,7 +496,7 @@ test('Get non-existent player', async () => {
}); });
test('Get stats by gamemode', async () => { test('Get stats by gamemode', async () => {
const { skills, bosses } = await getStatsByGamemode( const { skills, bosses, bountyHunter } = await getStatsByGamemode(
LYNX_TITAN_FORMATTED_NAME LYNX_TITAN_FORMATTED_NAME
); );
@@ -508,8 +529,10 @@ test('Get stats by gamemode', async () => {
const bossKeys = Object.keys(bosses); const bossKeys = Object.keys(bosses);
expect(bossKeys).toStrictEqual(BOSSES); expect(bossKeys).toStrictEqual(BOSSES);
const bountyHunterKeys = Object.keys(bountyHunter);
expect(bountyHunterKeys).toStrictEqual(BH_MODES);
expect.assertions(2); expect.assertions(3);
}); });
describe('Get stats options', () => { describe('Get stats options', () => {
@@ -518,13 +541,13 @@ describe('Get stats options', () => {
beforeEach(() => { beforeEach(() => {
axios.get = jest.fn( axios.get = jest.fn(
(url) => (url) =>
new Promise<any>((resolve) => new Promise<any>((resolve) => {
resolve( resolve(
url === getPlayerTableURL('main', rsn) url === getPlayerTableURL('main', rsn)
? { data: lynxTitanNamePage } ? { data: lynxTitanNamePage }
: { status: 200, data: lynxTitanStats } : { status: 200, data: lynxTitanStats }
) );
) })
); );
axiosMock = axios.get as any; axiosMock = axios.get as any;
axiosMock.mockClear(); axiosMock.mockClear();
@@ -532,11 +555,11 @@ describe('Get stats options', () => {
it('fetches all gamemodes and formatted RSN when no options provided', async () => { it('fetches all gamemodes and formatted RSN when no options provided', async () => {
await getStats(rsn); await getStats(rsn);
expect(axiosMock.mock.calls.map((val) => val[0])).toEqual([ expect(axiosMock.mock.calls.map((val) => val[0])).toEqual([
getStatsURL('main', rsn), getStatsURL('main', rsn, true),
getPlayerTableURL('main', rsn), getPlayerTableURL('main', rsn),
getStatsURL('ironman', rsn), getStatsURL('ironman', rsn, true),
getStatsURL('hardcore', rsn), getStatsURL('hardcore', rsn, true),
getStatsURL('ultimate', rsn) getStatsURL('ultimate', rsn, true)
]); ]);
}); });
it('skips fetching formatted RSN when option is provided', async () => { it('skips fetching formatted RSN when option is provided', async () => {
@@ -557,4 +580,16 @@ describe('Get stats options', () => {
) )
).toBeFalsy(); ).toBeFalsy();
}); });
it('omits excluded gamemodes', async () => {
const response = await getStats(rsn, {
otherGamemodes: ['ironman', 'ultimate']
});
expect(response.hardcore).toBeUndefined();
});
});
test('CSV and JSON parsing outputs identical object', async () => {
const csvOutput = parseStats(b0atyStatsCsv);
const jsonOutput = parseJsonStats(b0atyStatsJson);
expect(csvOutput).toEqual(jsonOutput);
}); });

View File

@@ -9,7 +9,7 @@
<!--<![endif]--> <!--<![endif]-->
<head> <head>
<link <link
href="https://www.runescape.com/css/rsp777_basic-104.css" href="https://www.runescape.com/a=135/css/rsp777_basic-104.css"
rel="stylesheet" rel="stylesheet"
type="text/css" type="text/css"
media="all" media="all"
@@ -51,7 +51,7 @@
data-blockingmode="auto" data-blockingmode="auto"
></script> ></script>
<script <script
src="https://www.runescape.com/js/c/responsive/vendor-151.js" src="https://www.runescape.com/a=135/js/c/responsive/vendor-151.js"
data-cookieconsent="ignore" data-cookieconsent="ignore"
></script> ></script>
<title>Old School Hiscores</title> <title>Old School Hiscores</title>
@@ -86,7 +86,7 @@
property="og:image" property="og:image"
content="https://www.runescape.com/img/rsp777/social-share-fb.jpg?1" content="https://www.runescape.com/img/rsp777/social-share-fb.jpg?1"
/> />
<meta property="og:url" content="https://www.runescape.com" /> <meta property="og:url" content="https://www.runescape.com/a=135" />
<meta <meta
property="og:description" property="og:description"
content="If you're a RuneScape veteran hungry for nostalgia, get stuck right in to Old School RuneScape. Sign up for membership and re-live the adventure." content="If you're a RuneScape veteran hungry for nostalgia, get stuck right in to Old School RuneScape. Sign up for membership and re-live the adventure."
@@ -102,7 +102,7 @@
/> />
<meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:domain" content="Oldschool.RuneScape.com" /> <meta name="twitter:domain" content="Oldschool.RuneScape.com" />
<meta name="twitter:url" content="https://www.runescape.com" /> <meta name="twitter:url" content="https://www.runescape.com/a=135" />
<meta name="twitter:title" content="Old School Hiscores" /> <meta name="twitter:title" content="Old School Hiscores" />
<meta <meta
name="twitter:description" name="twitter:description"
@@ -118,7 +118,7 @@
content="pUwl5KYHKSZ6tbbgsR2wYNv1kbVmsYral3iFmijHTWM" content="pUwl5KYHKSZ6tbbgsR2wYNv1kbVmsYral3iFmijHTWM"
/> />
<link <link
href="https://www.runescape.com/css/rsp777_hiscore-108.css" href="https://www.runescape.com/a=135/css/rsp777_hiscore-108.css"
rel="stylesheet" rel="stylesheet"
type="text/css" type="text/css"
media="all" media="all"
@@ -136,6 +136,14 @@
content: url('https://www.runescape.com/img/rsp777/game_icon_bountyhunterrogue.png?2'); content: url('https://www.runescape.com/img/rsp777/game_icon_bountyhunterrogue.png?2');
} }
.bountyhunterlegacyhunter:before {
content: url('https://www.runescape.com/img/rsp777/game_icon_bountyhunterlegacyhunter.png?2');
}
.bountyhunterlegacyrogue:before {
content: url('https://www.runescape.com/img/rsp777/game_icon_bountyhunterlegacyrogue.png?2');
}
.cluescrollsall:before { .cluescrollsall:before {
content: url('https://www.runescape.com/img/rsp777/game_icon_cluescrollsall.png?2'); content: url('https://www.runescape.com/img/rsp777/game_icon_cluescrollsall.png?2');
} }
@@ -188,6 +196,10 @@
content: url('https://www.runescape.com/img/rsp777/game_icon_alchemicalhydra.png?2'); content: url('https://www.runescape.com/img/rsp777/game_icon_alchemicalhydra.png?2');
} }
.artio:before {
content: url('https://www.runescape.com/img/rsp777/game_icon_artio.png?2');
}
.barrowschests:before { .barrowschests:before {
content: url('https://www.runescape.com/img/rsp777/game_icon_barrowschests.png?2'); content: url('https://www.runescape.com/img/rsp777/game_icon_barrowschests.png?2');
} }
@@ -200,6 +212,10 @@
content: url('https://www.runescape.com/img/rsp777/game_icon_callisto.png?2'); content: url('https://www.runescape.com/img/rsp777/game_icon_callisto.png?2');
} }
.calvarion:before {
content: url('https://www.runescape.com/img/rsp777/game_icon_calvarion.png?2');
}
.cerberus:before { .cerberus:before {
content: url('https://www.runescape.com/img/rsp777/game_icon_cerberus.png?2'); content: url('https://www.runescape.com/img/rsp777/game_icon_cerberus.png?2');
} }
@@ -304,6 +320,10 @@
content: url('https://www.runescape.com/img/rsp777/game_icon_obor.png?2'); content: url('https://www.runescape.com/img/rsp777/game_icon_obor.png?2');
} }
.phantommuspah:before {
content: url('https://www.runescape.com/img/rsp777/game_icon_phantommuspah.png?2');
}
.sarachnis:before { .sarachnis:before {
content: url('https://www.runescape.com/img/rsp777/game_icon_sarachnis.png?2'); content: url('https://www.runescape.com/img/rsp777/game_icon_sarachnis.png?2');
} }
@@ -316,6 +336,10 @@
content: url('https://www.runescape.com/img/rsp777/game_icon_skotizo.png?2'); content: url('https://www.runescape.com/img/rsp777/game_icon_skotizo.png?2');
} }
.spindel:before {
content: url('https://www.runescape.com/img/rsp777/game_icon_spindel.png?2');
}
.tempoross:before { .tempoross:before {
content: url('https://www.runescape.com/img/rsp777/game_icon_tempoross.png?2'); content: url('https://www.runescape.com/img/rsp777/game_icon_tempoross.png?2');
} }
@@ -387,7 +411,7 @@
<span style="float: right" <span style="float: right"
><a ><a
id="loginLink" id="loginLink"
href="https://secure.runescape.com/m=weblogin/loginform?theme=oldschool&mod=hiscore_oldschool&amp;ssl=1&amp;dest=overall.ws?table=0&amp;user=lYnX%20tiTAn" href="https://secure.runescape.com/m=weblogin/a=135/loginform?theme=oldschool&mod=hiscore_oldschool&amp;ssl=1&amp;dest=overall.ws?table=0&amp;user=lynx%20titan"
>Log in</a >Log in</a
></span ></span
> >
@@ -396,7 +420,7 @@
<div class="hiscoretitlebground"> <div class="hiscoretitlebground">
<div class="hiscoretitleframe"> <div class="hiscoretitleframe">
<strong>Old School Hiscores</strong><br /> <strong>Old School Hiscores</strong><br />
<a href="https://oldschool.runescape.com/">Home</a> <a href="https://oldschool.runescape.com/a=135/">Home</a>
</div> </div>
</div> </div>
<br /> <br />
@@ -412,17 +436,17 @@
<div class="ironman-nav__submenu"> <div class="ironman-nav__submenu">
<a <a
class="ironman-nav__option" class="ironman-nav__option"
href="https://secure.runescape.com/m=hiscore_oldschool_ironman/" href="https://secure.runescape.com/m=hiscore_oldschool_ironman/a=135/"
>Ironman</a >Ironman</a
> >
<a <a
class="ironman-nav__option" class="ironman-nav__option"
href="https://secure.runescape.com/m=hiscore_oldschool_ultimate/" href="https://secure.runescape.com/m=hiscore_oldschool_ultimate/a=135/"
>Ultimate Ironman</a >Ultimate Ironman</a
> >
<a <a
class="ironman-nav__option" class="ironman-nav__option"
href="https://secure.runescape.com/m=hiscore_oldschool_hardcore_ironman/" href="https://secure.runescape.com/m=hiscore_oldschool_hardcore_ironman/a=135/"
>Hardcore Ironman</a >Hardcore Ironman</a
> >
</div> </div>
@@ -434,17 +458,17 @@
<div class="ironman-nav__submenu"> <div class="ironman-nav__submenu">
<a <a
class="ironman-nav__option" class="ironman-nav__option"
href="https://secure.runescape.com/m=hiscore_oldschool_deadman/" href="https://secure.runescape.com/m=hiscore_oldschool_deadman/a=135/"
>Deadman Mode</a >Deadman Mode</a
> >
<a <a
class="ironman-nav__option" class="ironman-nav__option"
href="https://secure.runescape.com/m=hiscore_oldschool_seasonal/" href="https://secure.runescape.com/m=hiscore_oldschool_seasonal/a=135/"
>Leagues</a >Leagues</a
> >
<a <a
class="ironman-nav__option" class="ironman-nav__option"
href="https://secure.runescape.com/m=hiscore_oldschool_tournament/" href="https://secure.runescape.com/m=hiscore_oldschool_tournament/a=135/"
>Tournament</a >Tournament</a
> >
</div> </div>
@@ -456,12 +480,12 @@
<div class="ironman-nav__submenu"> <div class="ironman-nav__submenu">
<a <a
class="ironman-nav__option" class="ironman-nav__option"
href="https://secure.runescape.com/m=hiscore_oldschool_ironman/group-ironman/" href="https://secure.runescape.com/m=hiscore_oldschool_ironman/a=135/group-ironman/"
>Group Ironman</a >Group Ironman</a
> >
<a <a
class="ironman-nav__option" class="ironman-nav__option"
href="https://secure.runescape.com/m=hiscore_oldschool_hardcore_ironman/group-ironman/" href="https://secure.runescape.com/m=hiscore_oldschool_hardcore_ironman/a=135/group-ironman/"
>Hardcore Group Ironman</a >Hardcore Group Ironman</a
> >
</div> </div>
@@ -469,11 +493,28 @@
<div class="ironman-nav__group"> <div class="ironman-nav__group">
<a <a
class="ironman-nav__option" class="ironman-nav__option"
href="https://secure.runescape.com/m=hiscore_oldschool_fresh_start/" href="https://secure.runescape.com/m=hiscore_oldschool_fresh_start/a=135/"
> >
Fresh Start Worlds Fresh Start Worlds
</a> </a>
</div> </div>
<div class="ironman-nav__group">
<a class="ironman-nav__option ironman-nav__option--parent">
Account Builds
</a>
<div class="ironman-nav__submenu">
<a
class="ironman-nav__option"
href="https://secure.runescape.com/m=hiscore_oldschool_skiller/a=135/"
>Skillers</a
>
<a
class="ironman-nav__option"
href="https://secure.runescape.com/m=hiscore_oldschool_skiller_defence/a=135/"
>1 Defence</a
>
</div>
</div>
</div> </div>
<div class="personal-hiscores"> <div class="personal-hiscores">
<nav class="personal-hiscores__side-banner"> <nav class="personal-hiscores__side-banner">
@@ -486,393 +527,423 @@
>Up</a >Up</a
> >
<div id="contentCategory"> <div id="contentCategory">
<a href="overall?table=0&user=lYnX tiTAn" class="Overall"> <a href="overall?table=0&user=lynx titan" class="Overall">
Overall Overall
</a> </a>
<a href="overall?table=1&user=lYnX tiTAn" class="Attack"> <a href="overall?table=1&user=lynx titan" class="Attack">
Attack Attack
</a> </a>
<a href="overall?table=2&user=lYnX tiTAn" class="Defence"> <a href="overall?table=2&user=lynx titan" class="Defence">
Defence Defence
</a> </a>
<a href="overall?table=3&user=lYnX tiTAn" class="Strength"> <a href="overall?table=3&user=lynx titan" class="Strength">
Strength Strength
</a> </a>
<a href="overall?table=4&user=lYnX tiTAn" class="Hitpoints"> <a href="overall?table=4&user=lynx titan" class="Hitpoints">
Hitpoints Hitpoints
</a> </a>
<a href="overall?table=5&user=lYnX tiTAn" class="Ranged"> <a href="overall?table=5&user=lynx titan" class="Ranged">
Ranged Ranged
</a> </a>
<a href="overall?table=6&user=lYnX tiTAn" class="Prayer"> <a href="overall?table=6&user=lynx titan" class="Prayer">
Prayer Prayer
</a> </a>
<a href="overall?table=7&user=lYnX tiTAn" class="Magic"> <a href="overall?table=7&user=lynx titan" class="Magic">
Magic Magic
</a> </a>
<a href="overall?table=8&user=lYnX tiTAn" class="Cooking"> <a href="overall?table=8&user=lynx titan" class="Cooking">
Cooking Cooking
</a> </a>
<a href="overall?table=9&user=lYnX tiTAn" class="Woodcutting"> <a href="overall?table=9&user=lynx titan" class="Woodcutting">
Woodcutting Woodcutting
</a> </a>
<a href="overall?table=10&user=lYnX tiTAn" class="Fletching"> <a href="overall?table=10&user=lynx titan" class="Fletching">
Fletching Fletching
</a> </a>
<a href="overall?table=11&user=lYnX tiTAn" class="Fishing"> <a href="overall?table=11&user=lynx titan" class="Fishing">
Fishing Fishing
</a> </a>
<a href="overall?table=12&user=lYnX tiTAn" class="Firemaking"> <a href="overall?table=12&user=lynx titan" class="Firemaking">
Firemaking Firemaking
</a> </a>
<a href="overall?table=13&user=lYnX tiTAn" class="Crafting"> <a href="overall?table=13&user=lynx titan" class="Crafting">
Crafting Crafting
</a> </a>
<a href="overall?table=14&user=lYnX tiTAn" class="Smithing"> <a href="overall?table=14&user=lynx titan" class="Smithing">
Smithing Smithing
</a> </a>
<a href="overall?table=15&user=lYnX tiTAn" class="Mining"> <a href="overall?table=15&user=lynx titan" class="Mining">
Mining Mining
</a> </a>
<a href="overall?table=16&user=lYnX tiTAn" class="Herblore"> <a href="overall?table=16&user=lynx titan" class="Herblore">
Herblore Herblore
</a> </a>
<a href="overall?table=17&user=lYnX tiTAn" class="Agility"> <a href="overall?table=17&user=lynx titan" class="Agility">
Agility Agility
</a> </a>
<a href="overall?table=18&user=lYnX tiTAn" class="Thieving"> <a href="overall?table=18&user=lynx titan" class="Thieving">
Thieving Thieving
</a> </a>
<a href="overall?table=19&user=lYnX tiTAn" class="Slayer"> <a href="overall?table=19&user=lynx titan" class="Slayer">
Slayer Slayer
</a> </a>
<a href="overall?table=20&user=lYnX tiTAn" class="Farming"> <a href="overall?table=20&user=lynx titan" class="Farming">
Farming Farming
</a> </a>
<a href="overall?table=21&user=lYnX tiTAn" class="Runecraft"> <a href="overall?table=21&user=lynx titan" class="Runecraft">
Runecraft Runecraft
</a> </a>
<a href="overall?table=22&user=lYnX tiTAn" class="Hunter"> <a href="overall?table=22&user=lynx titan" class="Hunter">
Hunter Hunter
</a> </a>
<a href="overall?table=23&user=lYnX tiTAn" class="Construction"> <a href="overall?table=23&user=lynx titan" class="Construction">
Construction Construction
</a> </a>
<span style="color: #d9c27e; display: block; text-align: center" <span style="color: #d9c27e; display: block; text-align: center"
>----</span >----</span
> >
<a <a
href="overall?category_type=1&table=1&user=lYnX tiTAn" href="overall?category_type=1&table=1&user=lynx titan"
class="activity-link bountyhunterhunter" class="activity-link bountyhunterhunter"
>Bounty Hunter - Hunter</a >Bounty Hunter - Hunter</a
> >
<a <a
href="overall?category_type=1&table=2&user=lYnX tiTAn" href="overall?category_type=1&table=2&user=lynx titan"
class="activity-link bountyhunterrogue" class="activity-link bountyhunterrogue"
>Bounty Hunter - Rogue</a >Bounty Hunter - Rogue</a
> >
<a <a
href="overall?category_type=1&table=3&user=lYnX tiTAn" href="overall?category_type=1&table=3&user=lynx titan"
class="activity-link bountyhunterlegacyhunter"
>Bounty Hunter (Legacy) - Hunter</a
>
<a
href="overall?category_type=1&table=4&user=lynx titan"
class="activity-link bountyhunterlegacyrogue"
>Bounty Hunter (Legacy) - Rogue</a
>
<a
href="overall?category_type=1&table=5&user=lynx titan"
class="activity-link cluescrollsall" class="activity-link cluescrollsall"
>Clue Scrolls (all)</a >Clue Scrolls (all)</a
> >
<a <a
href="overall?category_type=1&table=4&user=lYnX tiTAn" href="overall?category_type=1&table=6&user=lynx titan"
class="activity-link cluescrollsbeginner" class="activity-link cluescrollsbeginner"
>Clue Scrolls (beginner)</a >Clue Scrolls (beginner)</a
> >
<a <a
href="overall?category_type=1&table=5&user=lYnX tiTAn" href="overall?category_type=1&table=7&user=lynx titan"
class="activity-link cluescrollseasy" class="activity-link cluescrollseasy"
>Clue Scrolls (easy)</a >Clue Scrolls (easy)</a
> >
<a <a
href="overall?category_type=1&table=6&user=lYnX tiTAn" href="overall?category_type=1&table=8&user=lynx titan"
class="activity-link cluescrollsmedium" class="activity-link cluescrollsmedium"
>Clue Scrolls (medium)</a >Clue Scrolls (medium)</a
> >
<a <a
href="overall?category_type=1&table=7&user=lYnX tiTAn" href="overall?category_type=1&table=9&user=lynx titan"
class="activity-link cluescrollshard" class="activity-link cluescrollshard"
>Clue Scrolls (hard)</a >Clue Scrolls (hard)</a
> >
<a <a
href="overall?category_type=1&table=8&user=lYnX tiTAn" href="overall?category_type=1&table=10&user=lynx titan"
class="activity-link cluescrollselite" class="activity-link cluescrollselite"
>Clue Scrolls (elite)</a >Clue Scrolls (elite)</a
> >
<a <a
href="overall?category_type=1&table=9&user=lYnX tiTAn" href="overall?category_type=1&table=11&user=lynx titan"
class="activity-link cluescrollsmaster" class="activity-link cluescrollsmaster"
>Clue Scrolls (master)</a >Clue Scrolls (master)</a
> >
<a <a
href="overall?category_type=1&table=10&user=lYnX tiTAn" href="overall?category_type=1&table=12&user=lynx titan"
class="activity-link lmsrank" class="activity-link lmsrank"
>LMS - Rank</a >LMS - Rank</a
> >
<a <a
href="overall?category_type=1&table=11&user=lYnX tiTAn" href="overall?category_type=1&table=13&user=lynx titan"
class="activity-link pvparenarank" class="activity-link pvparenarank"
>PvP Arena - Rank</a >PvP Arena - Rank</a
> >
<a <a
href="overall?category_type=1&table=12&user=lYnX tiTAn" href="overall?category_type=1&table=14&user=lynx titan"
class="activity-link soulwarszeal" class="activity-link soulwarszeal"
>Soul Wars Zeal</a >Soul Wars Zeal</a
> >
<a <a
href="overall?category_type=1&table=13&user=lYnX tiTAn" href="overall?category_type=1&table=15&user=lynx titan"
class="activity-link riftsclosed" class="activity-link riftsclosed"
>Rifts closed</a >Rifts closed</a
> >
<a <a
href="overall?category_type=1&table=14&user=lYnX tiTAn" href="overall?category_type=1&table=16&user=lynx titan"
class="activity-link abyssalsire" class="activity-link abyssalsire"
>Abyssal Sire</a >Abyssal Sire</a
> >
<a <a
href="overall?category_type=1&table=15&user=lYnX tiTAn" href="overall?category_type=1&table=17&user=lynx titan"
class="activity-link alchemicalhydra" class="activity-link alchemicalhydra"
>Alchemical Hydra</a >Alchemical Hydra</a
> >
<a <a
href="overall?category_type=1&table=16&user=lYnX tiTAn" href="overall?category_type=1&table=18&user=lynx titan"
class="activity-link artio"
>Artio</a
>
<a
href="overall?category_type=1&table=19&user=lynx titan"
class="activity-link barrowschests" class="activity-link barrowschests"
>Barrows Chests</a >Barrows Chests</a
> >
<a <a
href="overall?category_type=1&table=17&user=lYnX tiTAn" href="overall?category_type=1&table=20&user=lynx titan"
class="activity-link bryophyta" class="activity-link bryophyta"
>Bryophyta</a >Bryophyta</a
> >
<a <a
href="overall?category_type=1&table=18&user=lYnX tiTAn" href="overall?category_type=1&table=21&user=lynx titan"
class="activity-link callisto" class="activity-link callisto"
>Callisto</a >Callisto</a
> >
<a <a
href="overall?category_type=1&table=19&user=lYnX tiTAn" href="overall?category_type=1&table=22&user=lynx titan"
class="activity-link calvarion"
>Calvar'ion</a
>
<a
href="overall?category_type=1&table=23&user=lynx titan"
class="activity-link cerberus" class="activity-link cerberus"
>Cerberus</a >Cerberus</a
> >
<a <a
href="overall?category_type=1&table=20&user=lYnX tiTAn" href="overall?category_type=1&table=24&user=lynx titan"
class="activity-link chambersofxeric" class="activity-link chambersofxeric"
>Chambers of Xeric</a >Chambers of Xeric</a
> >
<a <a
href="overall?category_type=1&table=21&user=lYnX tiTAn" href="overall?category_type=1&table=25&user=lynx titan"
class="activity-link chambersofxericchallengemode" class="activity-link chambersofxericchallengemode"
>Chambers of Xeric: Challenge Mode</a >Chambers of Xeric: Challenge Mode</a
> >
<a <a
href="overall?category_type=1&table=22&user=lYnX tiTAn" href="overall?category_type=1&table=26&user=lynx titan"
class="activity-link chaoselemental" class="activity-link chaoselemental"
>Chaos Elemental</a >Chaos Elemental</a
> >
<a <a
href="overall?category_type=1&table=23&user=lYnX tiTAn" href="overall?category_type=1&table=27&user=lynx titan"
class="activity-link chaosfanatic" class="activity-link chaosfanatic"
>Chaos Fanatic</a >Chaos Fanatic</a
> >
<a <a
href="overall?category_type=1&table=24&user=lYnX tiTAn" href="overall?category_type=1&table=28&user=lynx titan"
class="activity-link commanderzilyana" class="activity-link commanderzilyana"
>Commander Zilyana</a >Commander Zilyana</a
> >
<a <a
href="overall?category_type=1&table=25&user=lYnX tiTAn" href="overall?category_type=1&table=29&user=lynx titan"
class="activity-link corporealbeast" class="activity-link corporealbeast"
>Corporeal Beast</a >Corporeal Beast</a
> >
<a <a
href="overall?category_type=1&table=26&user=lYnX tiTAn" href="overall?category_type=1&table=30&user=lynx titan"
class="activity-link crazyarchaeologist" class="activity-link crazyarchaeologist"
>Crazy Archaeologist</a >Crazy Archaeologist</a
> >
<a <a
href="overall?category_type=1&table=27&user=lYnX tiTAn" href="overall?category_type=1&table=31&user=lynx titan"
class="activity-link dagannothprime" class="activity-link dagannothprime"
>Dagannoth Prime</a >Dagannoth Prime</a
> >
<a <a
href="overall?category_type=1&table=28&user=lYnX tiTAn" href="overall?category_type=1&table=32&user=lynx titan"
class="activity-link dagannothrex" class="activity-link dagannothrex"
>Dagannoth Rex</a >Dagannoth Rex</a
> >
<a <a
href="overall?category_type=1&table=29&user=lYnX tiTAn" href="overall?category_type=1&table=33&user=lynx titan"
class="activity-link dagannothsupreme" class="activity-link dagannothsupreme"
>Dagannoth Supreme</a >Dagannoth Supreme</a
> >
<a <a
href="overall?category_type=1&table=30&user=lYnX tiTAn" href="overall?category_type=1&table=34&user=lynx titan"
class="activity-link derangedarchaeologist" class="activity-link derangedarchaeologist"
>Deranged Archaeologist</a >Deranged Archaeologist</a
> >
<a <a
href="overall?category_type=1&table=31&user=lYnX tiTAn" href="overall?category_type=1&table=35&user=lynx titan"
class="activity-link generalgraardor" class="activity-link generalgraardor"
>General Graardor</a >General Graardor</a
> >
<a <a
href="overall?category_type=1&table=32&user=lYnX tiTAn" href="overall?category_type=1&table=36&user=lynx titan"
class="activity-link giantmole" class="activity-link giantmole"
>Giant Mole</a >Giant Mole</a
> >
<a <a
href="overall?category_type=1&table=33&user=lYnX tiTAn" href="overall?category_type=1&table=37&user=lynx titan"
class="activity-link grotesqueguardians" class="activity-link grotesqueguardians"
>Grotesque Guardians</a >Grotesque Guardians</a
> >
<a <a
href="overall?category_type=1&table=34&user=lYnX tiTAn" href="overall?category_type=1&table=38&user=lynx titan"
class="activity-link hespori" class="activity-link hespori"
>Hespori</a >Hespori</a
> >
<a <a
href="overall?category_type=1&table=35&user=lYnX tiTAn" href="overall?category_type=1&table=39&user=lynx titan"
class="activity-link kalphitequeen" class="activity-link kalphitequeen"
>Kalphite Queen</a >Kalphite Queen</a
> >
<a <a
href="overall?category_type=1&table=36&user=lYnX tiTAn" href="overall?category_type=1&table=40&user=lynx titan"
class="activity-link kingblackdragon" class="activity-link kingblackdragon"
>King Black Dragon</a >King Black Dragon</a
> >
<a <a
href="overall?category_type=1&table=37&user=lYnX tiTAn" href="overall?category_type=1&table=41&user=lynx titan"
class="activity-link kraken" class="activity-link kraken"
>Kraken</a >Kraken</a
> >
<a <a
href="overall?category_type=1&table=38&user=lYnX tiTAn" href="overall?category_type=1&table=42&user=lynx titan"
class="activity-link kreearra" class="activity-link kreearra"
>Kree'Arra</a >Kree'Arra</a
> >
<a <a
href="overall?category_type=1&table=39&user=lYnX tiTAn" href="overall?category_type=1&table=43&user=lynx titan"
class="activity-link kriltsutsaroth" class="activity-link kriltsutsaroth"
>K'ril Tsutsaroth</a >K'ril Tsutsaroth</a
> >
<a <a
href="overall?category_type=1&table=40&user=lYnX tiTAn" href="overall?category_type=1&table=44&user=lynx titan"
class="activity-link mimic" class="activity-link mimic"
>Mimic</a >Mimic</a
> >
<a <a
href="overall?category_type=1&table=41&user=lYnX tiTAn" href="overall?category_type=1&table=45&user=lynx titan"
class="activity-link nex" class="activity-link nex"
>Nex</a >Nex</a
> >
<a <a
href="overall?category_type=1&table=42&user=lYnX tiTAn" href="overall?category_type=1&table=46&user=lynx titan"
class="activity-link nightmare" class="activity-link nightmare"
>Nightmare</a >Nightmare</a
> >
<a <a
href="overall?category_type=1&table=43&user=lYnX tiTAn" href="overall?category_type=1&table=47&user=lynx titan"
class="activity-link phosanisnightmare" class="activity-link phosanisnightmare"
>Phosani's Nightmare</a >Phosani's Nightmare</a
> >
<a <a
href="overall?category_type=1&table=44&user=lYnX tiTAn" href="overall?category_type=1&table=48&user=lynx titan"
class="activity-link obor" class="activity-link obor"
>Obor</a >Obor</a
> >
<a <a
href="overall?category_type=1&table=45&user=lYnX tiTAn" href="overall?category_type=1&table=49&user=lynx titan"
class="activity-link phantommuspah"
>Phantom Muspah</a
>
<a
href="overall?category_type=1&table=50&user=lynx titan"
class="activity-link sarachnis" class="activity-link sarachnis"
>Sarachnis</a >Sarachnis</a
> >
<a <a
href="overall?category_type=1&table=46&user=lYnX tiTAn" href="overall?category_type=1&table=51&user=lynx titan"
class="activity-link scorpia" class="activity-link scorpia"
>Scorpia</a >Scorpia</a
> >
<a <a
href="overall?category_type=1&table=47&user=lYnX tiTAn" href="overall?category_type=1&table=52&user=lynx titan"
class="activity-link skotizo" class="activity-link skotizo"
>Skotizo</a >Skotizo</a
> >
<a <a
href="overall?category_type=1&table=48&user=lYnX tiTAn" href="overall?category_type=1&table=53&user=lynx titan"
class="activity-link spindel"
>Spindel</a
>
<a
href="overall?category_type=1&table=54&user=lynx titan"
class="activity-link tempoross" class="activity-link tempoross"
>Tempoross</a >Tempoross</a
> >
<a <a
href="overall?category_type=1&table=49&user=lYnX tiTAn" href="overall?category_type=1&table=55&user=lynx titan"
class="activity-link thegauntlet" class="activity-link thegauntlet"
>The Gauntlet</a >The Gauntlet</a
> >
<a <a
href="overall?category_type=1&table=50&user=lYnX tiTAn" href="overall?category_type=1&table=56&user=lynx titan"
class="activity-link thecorruptedgauntlet" class="activity-link thecorruptedgauntlet"
>The Corrupted Gauntlet</a >The Corrupted Gauntlet</a
> >
<a <a
href="overall?category_type=1&table=51&user=lYnX tiTAn" href="overall?category_type=1&table=57&user=lynx titan"
class="activity-link theatreofblood" class="activity-link theatreofblood"
>Theatre of Blood</a >Theatre of Blood</a
> >
<a <a
href="overall?category_type=1&table=52&user=lYnX tiTAn" href="overall?category_type=1&table=58&user=lynx titan"
class="activity-link theatreofbloodhardmode" class="activity-link theatreofbloodhardmode"
>Theatre of Blood: Hard Mode</a >Theatre of Blood: Hard Mode</a
> >
<a <a
href="overall?category_type=1&table=53&user=lYnX tiTAn" href="overall?category_type=1&table=59&user=lynx titan"
class="activity-link thermonuclearsmokedevil" class="activity-link thermonuclearsmokedevil"
>Thermonuclear Smoke Devil</a >Thermonuclear Smoke Devil</a
> >
<a <a
href="overall?category_type=1&table=54&user=lYnX tiTAn" href="overall?category_type=1&table=60&user=lynx titan"
class="activity-link tombsofamascut" class="activity-link tombsofamascut"
>Tombs of Amascut</a >Tombs of Amascut</a
> >
<a <a
href="overall?category_type=1&table=55&user=lYnX tiTAn" href="overall?category_type=1&table=61&user=lynx titan"
class="activity-link tombsofamascutexpertmode" class="activity-link tombsofamascutexpertmode"
>Tombs of Amascut: Expert Mode</a >Tombs of Amascut: Expert Mode</a
> >
<a <a
href="overall?category_type=1&table=56&user=lYnX tiTAn" href="overall?category_type=1&table=62&user=lynx titan"
class="activity-link tzkalzuk" class="activity-link tzkalzuk"
>TzKal-Zuk</a >TzKal-Zuk</a
> >
<a <a
href="overall?category_type=1&table=57&user=lYnX tiTAn" href="overall?category_type=1&table=63&user=lynx titan"
class="activity-link tztokjad" class="activity-link tztokjad"
>TzTok-Jad</a >TzTok-Jad</a
> >
<a <a
href="overall?category_type=1&table=58&user=lYnX tiTAn" href="overall?category_type=1&table=64&user=lynx titan"
class="activity-link venenatis" class="activity-link venenatis"
>Venenatis</a >Venenatis</a
> >
<a <a
href="overall?category_type=1&table=59&user=lYnX tiTAn" href="overall?category_type=1&table=65&user=lynx titan"
class="activity-link vetion" class="activity-link vetion"
>Vet'ion</a >Vet'ion</a
> >
<a <a
href="overall?category_type=1&table=60&user=lYnX tiTAn" href="overall?category_type=1&table=66&user=lynx titan"
class="activity-link vorkath" class="activity-link vorkath"
>Vorkath</a >Vorkath</a
> >
<a <a
href="overall?category_type=1&table=61&user=lYnX tiTAn" href="overall?category_type=1&table=67&user=lynx titan"
class="activity-link wintertodt" class="activity-link wintertodt"
>Wintertodt</a >Wintertodt</a
> >
<a <a
href="overall?category_type=1&table=62&user=lYnX tiTAn" href="overall?category_type=1&table=68&user=lynx titan"
class="activity-link zalcano" class="activity-link zalcano"
>Zalcano</a >Zalcano</a
> >
<a <a
href="overall?category_type=1&table=63&user=lYnX tiTAn" href="overall?category_type=1&table=69&user=lynx titan"
class="activity-link zulrah" class="activity-link zulrah"
>Zulrah</a >Zulrah</a
> >
@@ -1190,7 +1261,7 @@
</div> </div>
</form> </form>
<form <form
action="https://secure.runescape.com/m=hiscore_oldschool/compare" action="https://secure.runescape.com/m=hiscore_oldschool/a=135/compare"
method="post" method="post"
class="compareForm" class="compareForm"
> >
@@ -1229,7 +1300,7 @@
yourself to your friends<br /> yourself to your friends<br />
</div> </div>
<a <a
href="https://secure.runescape.com/m=weblogin/oldschool_login?mod=hiscore_oldschool&amp;ssl=0&amp;dest=hiscorefriends_all" href="https://secure.runescape.com/m=weblogin/a=135/oldschool_login?mod=hiscore_oldschool&amp;ssl=0&amp;dest=hiscorefriends_all"
class="linkToFriends" class="linkToFriends"
>Click here</a >Click here</a
> >
@@ -1246,7 +1317,7 @@
alt="Jagex Software" alt="Jagex Software"
/></a> /></a>
<p class="tandc__copy"> <p class="tandc__copy">
This website and its contents are copyright &copy; 1999 - 2022 Jagex This website and its contents are copyright &copy; 1999 - 2023 Jagex
Ltd, 220 Science Park, Cambridge, CB4 0WA, United Kingdom.<br />Use of Ltd, 220 Science Park, Cambridge, CB4 0WA, United Kingdom.<br />Use of
this website is subject to our this website is subject to our
<a href="https://www.jagex.com/en-GB/terms/terms.ws" target="_blank" <a href="https://www.jagex.com/en-GB/terms/terms.ws" target="_blank"
@@ -1269,16 +1340,16 @@
<a <a
class="osrsRSS" class="osrsRSS"
target="_BLANK" target="_BLANK"
href="https://secure.runescape.com/m=news/latest_news.rss?oldschool=true" href="https://secure.runescape.com/m=news/a=135/latest_news.rss?oldschool=true"
><img ><img
src="https://www.runescape.com/img/rsp777/rss.png" src="https://www.runescape.com/img/rsp777/rss.png"
title="OSRS RSS Feed" title="OSRS RSS Feed"
alt="OSRS RSS Feed" alt="OSRS RSS Feed"
/></a> /></a>
</div> </div>
<script src="https://www.runescape.com/js/c/rs3/modernizr_3_0_0_min-100.js"></script> <script src="https://www.runescape.com/a=135/js/c/rs3/modernizr_3_0_0_min-100.js"></script>
<script src="https://www.runescape.com/js/rsp777_basic-105.js"></script> <script src="https://www.runescape.com/a=135/js/rsp777_basic-105.js"></script>
<script src="https://www.runescape.com/js/rsp777_twitter-100.js"></script> <script src="https://www.runescape.com/a=135/js/rsp777_twitter-100.js"></script>
<script type="text/javascript"> <script type="text/javascript">
if ($('#fb-root').length == 0) { if ($('#fb-root').length == 0) {
@@ -1313,7 +1384,7 @@
_target _target
) { ) {
$.getJSON( $.getJSON(
'https://secure.runescape.com/m=sn-integration/checkLogin.ws?json=?', 'https://secure.runescape.com/m=sn-integration/a=135/checkLogin.ws?json=?',
{ {
tps: 0, tps: 0,
token: FB.getAuthResponse().accessToken, token: FB.getAuthResponse().accessToken,
@@ -1369,7 +1440,7 @@
</script> </script>
<script <script
type="text/javascript" type="text/javascript"
src="https://www.runescape.com/js/rs3/plugins-100.js" src="https://www.runescape.com/a=135/js/rs3/plugins-100.js"
data-cookieconsent="ignore" data-cookieconsent="ignore"
></script> ></script>
<script> <script>
@@ -1383,7 +1454,7 @@
</script> </script>
<script <script
type="text/javascript" type="text/javascript"
src="https://www.runescape.com/js/osrs/gtm-100.js" src="https://www.runescape.com/a=135/js/osrs/gtm-100.js"
data-cookieconsent="ignore" data-cookieconsent="ignore"
></script> ></script>
</div> </div>

View File

@@ -1,92 +0,0 @@
1,2277,4600000000
15,99,200000000
28,99,200000000
18,99,200000000
7,99,200000000
8,99,200000000
11,99,200000000
32,99,200000000
159,99,200000000
15,99,200000000
12,99,200000000
9,99,200000000
48,99,200000000
4,99,200000000
3,99,200000000
25,99,200000000
5,99,200000000
23,99,200000000
12,99,200000000
2,99,200000000
19,99,200000000
7,99,200000000
4,99,200000000
4,99,200000000
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
347584,22
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
-1,-1
Can't render this file because it has a wrong number of fields in line 25.

View File

@@ -0,0 +1,147 @@
{
"skills": [
{ "id": 0, "name": "Overall", "rank": 1, "level": 2277, "xp": 4600000000 },
{ "id": 1, "name": "Attack", "rank": 15, "level": 99, "xp": 200000000 },
{ "id": 2, "name": "Defence", "rank": 28, "level": 99, "xp": 200000000 },
{ "id": 3, "name": "Strength", "rank": 18, "level": 99, "xp": 200000000 },
{ "id": 4, "name": "Hitpoints", "rank": 7, "level": 99, "xp": 200000000 },
{ "id": 5, "name": "Ranged", "rank": 8, "level": 99, "xp": 200000000 },
{ "id": 6, "name": "Prayer", "rank": 11, "level": 99, "xp": 200000000 },
{ "id": 7, "name": "Magic", "rank": 32, "level": 99, "xp": 200000000 },
{ "id": 8, "name": "Cooking", "rank": 157, "level": 99, "xp": 200000000 },
{
"id": 9,
"name": "Woodcutting",
"rank": 15,
"level": 99,
"xp": 200000000
},
{ "id": 10, "name": "Fletching", "rank": 12, "level": 99, "xp": 200000000 },
{ "id": 11, "name": "Fishing", "rank": 9, "level": 99, "xp": 200000000 },
{
"id": 12,
"name": "Firemaking",
"rank": 48,
"level": 99,
"xp": 200000000
},
{ "id": 13, "name": "Crafting", "rank": 4, "level": 99, "xp": 200000000 },
{ "id": 14, "name": "Smithing", "rank": 3, "level": 99, "xp": 200000000 },
{ "id": 15, "name": "Mining", "rank": 25, "level": 99, "xp": 200000000 },
{ "id": 16, "name": "Herblore", "rank": 5, "level": 99, "xp": 200000000 },
{ "id": 17, "name": "Agility", "rank": 24, "level": 99, "xp": 200000000 },
{ "id": 18, "name": "Thieving", "rank": 12, "level": 99, "xp": 200000000 },
{ "id": 19, "name": "Slayer", "rank": 2, "level": 99, "xp": 200000000 },
{ "id": 20, "name": "Farming", "rank": 19, "level": 99, "xp": 200000000 },
{ "id": 21, "name": "Runecraft", "rank": 7, "level": 99, "xp": 200000000 },
{ "id": 22, "name": "Hunter", "rank": 4, "level": 99, "xp": 200000000 },
{
"id": 23,
"name": "Construction",
"rank": 4,
"level": 99,
"xp": 200000000
}
],
"activities": [
{ "id": 0, "name": "League Points", "rank": -1, "score": -1 },
{ "id": 1, "name": "Bounty Hunter - Hunter", "rank": -1, "score": -1 },
{ "id": 2, "name": "Bounty Hunter - Rogue", "rank": -1, "score": -1 },
{
"id": 3,
"name": "Bounty Hunter (Legacy) - Hunter",
"rank": -1,
"score": -1
},
{
"id": 4,
"name": "Bounty Hunter (Legacy) - Rogue",
"rank": -1,
"score": -1
},
{ "id": 5, "name": "Clue Scrolls (all)", "rank": 769527, "score": 22 },
{ "id": 6, "name": "Clue Scrolls (beginner)", "rank": -1, "score": -1 },
{ "id": 7, "name": "Clue Scrolls (easy)", "rank": -1, "score": -1 },
{ "id": 8, "name": "Clue Scrolls (medium)", "rank": -1, "score": -1 },
{ "id": 9, "name": "Clue Scrolls (hard)", "rank": 483333, "score": 22 },
{ "id": 10, "name": "Clue Scrolls (elite)", "rank": -1, "score": -1 },
{ "id": 11, "name": "Clue Scrolls (master)", "rank": -1, "score": -1 },
{ "id": 12, "name": "LMS - Rank", "rank": -1, "score": -1 },
{ "id": 13, "name": "PvP Arena - Rank", "rank": -1, "score": -1 },
{ "id": 14, "name": "Soul Wars Zeal", "rank": -1, "score": -1 },
{ "id": 15, "name": "Rifts closed", "rank": -1, "score": -1 },
{ "id": 16, "name": "Abyssal Sire", "rank": -1, "score": -1 },
{ "id": 17, "name": "Alchemical Hydra", "rank": -1, "score": -1 },
{ "id": 18, "name": "Artio", "rank": -1, "score": -1 },
{ "id": 19, "name": "Barrows Chests", "rank": -1, "score": -1 },
{ "id": 20, "name": "Bryophyta", "rank": -1, "score": -1 },
{ "id": 21, "name": "Callisto", "rank": -1, "score": -1 },
{ "id": 22, "name": "Calvar'ion", "rank": -1, "score": -1 },
{ "id": 23, "name": "Cerberus", "rank": -1, "score": -1 },
{ "id": 24, "name": "Chambers of Xeric", "rank": -1, "score": -1 },
{
"id": 25,
"name": "Chambers of Xeric: Challenge Mode",
"rank": -1,
"score": -1
},
{ "id": 26, "name": "Chaos Elemental", "rank": -1, "score": -1 },
{ "id": 27, "name": "Chaos Fanatic", "rank": -1, "score": -1 },
{ "id": 28, "name": "Commander Zilyana", "rank": -1, "score": -1 },
{ "id": 29, "name": "Corporeal Beast", "rank": -1, "score": -1 },
{ "id": 30, "name": "Crazy Archaeologist", "rank": -1, "score": -1 },
{ "id": 31, "name": "Dagannoth Prime", "rank": -1, "score": -1 },
{ "id": 32, "name": "Dagannoth Rex", "rank": -1, "score": -1 },
{ "id": 33, "name": "Dagannoth Supreme", "rank": -1, "score": -1 },
{ "id": 34, "name": "Deranged Archaeologist", "rank": -1, "score": -1 },
{ "id": 35, "name": "Duke Sucellus", "rank": -1, "score": -1 },
{ "id": 36, "name": "General Graardor", "rank": -1, "score": -1 },
{ "id": 37, "name": "Giant Mole", "rank": -1, "score": -1 },
{ "id": 38, "name": "Grotesque Guardians", "rank": -1, "score": -1 },
{ "id": 39, "name": "Hespori", "rank": -1, "score": -1 },
{ "id": 40, "name": "Kalphite Queen", "rank": -1, "score": -1 },
{ "id": 41, "name": "King Black Dragon", "rank": -1, "score": -1 },
{ "id": 42, "name": "Kraken", "rank": -1, "score": -1 },
{ "id": 43, "name": "Kree'Arra", "rank": -1, "score": -1 },
{ "id": 44, "name": "K'ril Tsutsaroth", "rank": -1, "score": -1 },
{ "id": 45, "name": "Mimic", "rank": -1, "score": -1 },
{ "id": 46, "name": "Nex", "rank": -1, "score": -1 },
{ "id": 47, "name": "Nightmare", "rank": -1, "score": -1 },
{ "id": 48, "name": "Phosani's Nightmare", "rank": -1, "score": -1 },
{ "id": 49, "name": "Obor", "rank": -1, "score": -1 },
{ "id": 50, "name": "Phantom Muspah", "rank": -1, "score": -1 },
{ "id": 51, "name": "Sarachnis", "rank": -1, "score": -1 },
{ "id": 52, "name": "Scorpia", "rank": -1, "score": -1 },
{ "id": 53, "name": "Skotizo", "rank": -1, "score": -1 },
{ "id": 54, "name": "Spindel", "rank": -1, "score": -1 },
{ "id": 55, "name": "Tempoross", "rank": -1, "score": -1 },
{ "id": 56, "name": "The Gauntlet", "rank": -1, "score": -1 },
{ "id": 57, "name": "The Corrupted Gauntlet", "rank": -1, "score": -1 },
{ "id": 58, "name": "The Leviathan", "rank": -1, "score": -1 },
{ "id": 59, "name": "The Whisperer", "rank": -1, "score": -1 },
{ "id": 60, "name": "Theatre of Blood", "rank": -1, "score": -1 },
{
"id": 61,
"name": "Theatre of Blood: Hard Mode",
"rank": -1,
"score": -1
},
{ "id": 62, "name": "Thermonuclear Smoke Devil", "rank": -1, "score": -1 },
{ "id": 63, "name": "Tombs of Amascut", "rank": -1, "score": -1 },
{
"id": 64,
"name": "Tombs of Amascut: Expert Mode",
"rank": -1,
"score": -1
},
{ "id": 65, "name": "TzKal-Zuk", "rank": -1, "score": -1 },
{ "id": 66, "name": "TzTok-Jad", "rank": 198, "score": 186 },
{ "id": 67, "name": "Vardorvis", "rank": -1, "score": -1 },
{ "id": 68, "name": "Venenatis", "rank": -1, "score": -1 },
{ "id": 69, "name": "Vet'ion", "rank": -1, "score": -1 },
{ "id": 70, "name": "Vorkath", "rank": -1, "score": -1 },
{ "id": 71, "name": "Wintertodt", "rank": -1, "score": -1 },
{ "id": 72, "name": "Zalcano", "rank": -1, "score": -1 },
{ "id": 73, "name": "Zulrah", "rank": -1, "score": -1 }
]
}

View File

@@ -1,6 +1,6 @@
{ {
"name": "osrs-json-hiscores", "name": "osrs-json-hiscores",
"version": "2.13.0", "version": "2.16.2",
"description": "The Old School Runescape API wrapper that does more!", "description": "The Old School Runescape API wrapper that does more!",
"main": "lib/index.js", "main": "lib/index.js",
"types": "lib/index.d.ts", "types": "lib/index.d.ts",
@@ -61,18 +61,23 @@
"@typescript-eslint" "@typescript-eslint"
], ],
"extends": [ "extends": [
"eslint-config-airbnb-typescript", "airbnb-base",
"airbnb-typescript/base",
"prettier" "prettier"
], ],
"ignorePatterns": [ "ignorePatterns": [
"**/@types/*" "**/@types/*"
] ],
"rules": {
"max-classes-per-file": "off"
}
}, },
"prettier": { "prettier": {
"trailingComma": "none", "trailingComma": "none",
"tabWidth": 2, "tabWidth": 2,
"semi": true, "semi": true,
"singleQuote": true "singleQuote": true,
"endOfLine": "auto"
}, },
"jest": { "jest": {
"transform": { "transform": {
@@ -90,28 +95,25 @@
"testEnvironment": "node" "testEnvironment": "node"
}, },
"dependencies": { "dependencies": {
"axios": "^0.21.1", "axios": "^1.6.2",
"jsdom": "^16.3.0", "jsdom": "^22.1.0",
"useragent-generator": "^1.1.0" "useragent-generator": "^1.1.0"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^26.0.21", "@types/jest": "^29.5.3",
"@types/jsdom": "^16.2.3", "@types/jsdom": "^21.1.1",
"@typescript-eslint/eslint-plugin": "^4.19.0", "@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^4.19.0", "@typescript-eslint/parser": "^6.0.0",
"eslint": "^7.22.0", "eslint": "^8.44.0",
"eslint-config-airbnb-typescript": "^12.3.1", "eslint-config-airbnb-typescript": "^17.1.0",
"eslint-config-prettier": "^8.1.0", "eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.22.1", "eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.23.1",
"eslint-plugin-react-hooks": "^4.2.0",
"husky": "^5.2.0", "husky": "^5.2.0",
"jest": "^26.6.3", "jest": "^29.6.1",
"lint-staged": "^10.5.4", "lint-staged": "^13.2.3",
"np": "^7.7.0", "np": "^7.7.0",
"prettier": "^2.2.1", "prettier": "^3.0.0",
"ts-jest": "^26.5.4", "ts-jest": "^29.1.1",
"typescript": "^4.2.3" "typescript": "^5.1.6"
} }
} }

View File

@@ -1,4 +1,4 @@
import { AxiosResponse } from 'axios'; import axios, { AxiosRequestConfig } from 'axios';
import { BinaryData, JSDOM } from 'jsdom'; import { BinaryData, JSDOM } from 'jsdom';
import { import {
Player, Player,
@@ -14,7 +14,8 @@ import {
ActivityName, ActivityName,
PlayerActivityRow, PlayerActivityRow,
Bosses, Bosses,
GetStatsOptions GetStatsOptions,
HiscoresResponse
} from './types'; } from './types';
import { import {
getStatsURL, getStatsURL,
@@ -30,23 +31,67 @@ import {
getActivityPageURL, getActivityPageURL,
httpGet, httpGet,
BOSSES, BOSSES,
INVALID_FORMAT_ERROR, InvalidFormatError,
validateRSN PlayerNotFoundError,
HiScoresError,
validateRSN,
FORMATTED_SKILL_NAMES,
FORMATTED_BH_NAMES,
FORMATTED_CLUE_NAMES,
FORMATTED_BOSS_NAMES,
FORMATTED_LEAGUE_POINTS,
FORMATTED_LMS,
FORMATTED_PVP_ARENA,
FORMATTED_SOUL_WARS,
FORMATTED_RIFTS_CLOSED
} from './utils'; } from './utils';
/**
* Gets a player's stats from the official OSRS JSON endpoint.
*
* @param rsn Username of the player.
* @param mode Gamemode to fetch ranks for.
* @param config Optional axios request config object.
* @returns Official JSON stats object.
*/
export async function getOfficialStats(
rsn: string,
mode: Gamemode = 'main',
config?: AxiosRequestConfig
): Promise<HiscoresResponse> {
validateRSN(rsn);
const url = getStatsURL(mode, rsn, true);
try {
const response = await httpGet<HiscoresResponse>(url, config);
return response.data;
} catch (err) {
if (!axios.isAxiosError(err)) throw err;
if (err.response?.status === 404) throw new PlayerNotFoundError();
throw new HiScoresError();
}
}
/** /**
* Screen scrapes the hiscores to get the formatted rsn of a player. * Screen scrapes the hiscores to get the formatted rsn of a player.
* *
* @param rsn Username of the player. * @param rsn Username of the player.
* @param config Optional axios request config object.
* @returns Formatted version of the rsn. * @returns Formatted version of the rsn.
*/ */
export async function getRSNFormat(rsn: string): Promise<string> { export async function getRSNFormat(
rsn: string,
config?: AxiosRequestConfig
): Promise<string> {
validateRSN(rsn); validateRSN(rsn);
const url = getPlayerTableURL('main', rsn); const url = getPlayerTableURL('main', rsn);
try { try {
const response = await httpGet<string | Buffer | BinaryData | undefined>( const response = await httpGet<string | Buffer | BinaryData | undefined>(
url url,
config
); );
const dom = new JSDOM(response.data); const dom = new JSDOM(response.data);
const anchor = dom.window.document.querySelector( const anchor = dom.window.document.querySelector(
@@ -55,12 +100,82 @@ export async function getRSNFormat(rsn: string): Promise<string> {
if (anchor) { if (anchor) {
return rsnFromElement(anchor); return rsnFromElement(anchor);
} }
throw Error('Player not found'); throw new PlayerNotFoundError();
} catch { } catch {
throw Error('Player not found'); throw new HiScoresError();
} }
} }
/**
* Parses official JSON object of raw stats and returns a stats object.
*
* @param csv Raw JSON from the official OSRS API.
* @returns Parsed stats object.
*/
export function parseJsonStats(json: HiscoresResponse): Stats {
const getActivity = (formattedName: string): Activity => {
const hiscoresActivity = json.activities.find(
// We must match on name here since id is not guaranteed to be the same between updates
({ name }) => name.toLowerCase() === formattedName.toLowerCase()
);
return {
rank: hiscoresActivity?.rank ?? -1,
score: hiscoresActivity?.score ?? -1
};
};
const reduceActivity = <Key extends string, Reduced = Record<Key, Activity>>(
keys: Key[],
formattedNames: Record<Key, string>
): Reduced =>
keys.reduce<Reduced>(
(reducer, key) => ({
...reducer,
[key]: getActivity(formattedNames[key])
}),
{} as Reduced
);
const skills = SKILLS.reduce<Skills>((skillsObject, skillName) => {
const hiscoresSkill = json.skills.find(
// We must match on name here since id is not guaranteed to be the same between updates
({ name }) =>
name.toLowerCase() === FORMATTED_SKILL_NAMES[skillName].toLowerCase()
);
return {
...skillsObject,
[skillName]: {
rank: hiscoresSkill?.rank ?? -1,
level: hiscoresSkill?.level ?? -1,
xp: hiscoresSkill?.xp ?? -1
}
};
}, {} as Skills);
const bountyHunter = reduceActivity(BH_MODES, FORMATTED_BH_NAMES);
const clues = reduceActivity(CLUES, FORMATTED_CLUE_NAMES);
const bosses = reduceActivity(BOSSES, FORMATTED_BOSS_NAMES);
const leaguePoints = getActivity(FORMATTED_LEAGUE_POINTS);
const lastManStanding = getActivity(FORMATTED_LMS);
const pvpArena = getActivity(FORMATTED_PVP_ARENA);
const soulWarsZeal = getActivity(FORMATTED_SOUL_WARS);
const riftsClosed = getActivity(FORMATTED_RIFTS_CLOSED);
const stats: Stats = {
skills,
leaguePoints,
bountyHunter,
lastManStanding,
pvpArena,
soulWarsZeal,
riftsClosed,
clues,
bosses
};
return stats;
}
/** /**
* Parses CSV string of raw stats and returns a stats object. * Parses CSV string of raw stats and returns a stats object.
* *
@@ -77,7 +192,7 @@ export function parseStats(csv: string): Stats {
splitCSV.length !== splitCSV.length !==
SKILLS.length + BH_MODES.length + CLUES.length + BOSSES.length + 5 SKILLS.length + BH_MODES.length + CLUES.length + BOSSES.length + 5
) { ) {
throw Error(INVALID_FORMAT_ERROR); throw new InvalidFormatError();
} }
const skillObjects: Skill[] = splitCSV const skillObjects: Skill[] = splitCSV
@@ -106,12 +221,8 @@ export function parseStats(csv: string): Stats {
const [leaguePoints] = activityObjects.splice(0, 1); const [leaguePoints] = activityObjects.splice(0, 1);
const bhObjects = activityObjects.splice(0, BH_MODES.length); const bhObjects = activityObjects.splice(0, BH_MODES.length);
const clueObjects = activityObjects.splice(0, CLUES.length); const clueObjects = activityObjects.splice(0, CLUES.length);
const [ const [lastManStanding, pvpArena, soulWarsZeal, riftsClosed] =
lastManStanding, activityObjects.splice(0, 4);
pvpArena,
soulWarsZeal,
riftsClosed
] = activityObjects.splice(0, 4);
const bossObjects = activityObjects.splice(0, BOSSES.length); const bossObjects = activityObjects.splice(0, BOSSES.length);
const skills: Skills = skillObjects.reduce<Skills>((prev, curr, index) => { const skills: Skills = skillObjects.reduce<Skills>((prev, curr, index) => {
@@ -174,83 +285,77 @@ export async function getStats(
]; ];
const shouldGetFormattedRsn = options?.shouldGetFormattedRsn ?? true; const shouldGetFormattedRsn = options?.shouldGetFormattedRsn ?? true;
const mainRes = await httpGet<string>(getStatsURL('main', rsn)); const main = await getOfficialStats(rsn, 'main', options?.axiosConfigs?.main);
if (mainRes.status === 200) {
const emptyResponse: AxiosResponse<string> = { const getModeStats = async (
status: 404, mode: Extract<Gamemode, 'ironman' | 'hardcore' | 'ultimate'>
data: '', ): Promise<HiscoresResponse | undefined> =>
statusText: '', otherGamemodes.includes(mode)
headers: {}, ? getOfficialStats(rsn, mode, options?.axiosConfigs?.[mode])
config: {} .catch(() => undefined)
};
const getModeStats = async (
mode: Extract<Gamemode, 'ironman' | 'hardcore' | 'ultimate'>
): Promise<AxiosResponse<string>> =>
otherGamemodes.includes(mode)
? httpGet<string>(getStatsURL(mode, rsn)).catch((err) => err)
: emptyResponse;
const formattedName = shouldGetFormattedRsn
? await getRSNFormat(rsn).catch(() => undefined)
: undefined; : undefined;
const formattedName = shouldGetFormattedRsn
? await getRSNFormat(rsn, options?.axiosConfigs?.rsn).catch(
() => undefined
)
: undefined;
const player: Player = { const player: Player = {
name: formattedName ?? rsn, name: formattedName ?? rsn,
mode: 'main', mode: 'main',
dead: false, dead: false,
deulted: false, deulted: false,
deironed: false deironed: false
}; };
player.main = parseStats(mainRes.data); player.main = parseJsonStats(main);
const ironRes = await getModeStats('ironman'); const iron = await getModeStats('ironman');
if (ironRes.status === 200) { if (iron) {
player.ironman = parseStats(ironRes.data); player.ironman = parseJsonStats(iron);
const hcRes = await getModeStats('hardcore'); const hc = await getModeStats('hardcore');
const ultRes = await getModeStats('ultimate'); const ult = await getModeStats('ultimate');
if (hcRes.status === 200) { if (hc) {
player.mode = 'hardcore'; player.mode = 'hardcore';
player.hardcore = parseStats(hcRes.data); player.hardcore = parseJsonStats(hc);
if ( if (
player.ironman.skills.overall.xp !== player.hardcore.skills.overall.xp player.ironman.skills.overall.xp !== player.hardcore.skills.overall.xp
) { ) {
player.dead = true; player.dead = true;
player.mode = 'ironman';
}
if (
player.main.skills.overall.xp !== player.ironman.skills.overall.xp
) {
player.deironed = true;
player.mode = 'main';
}
} else if (ultRes.status === 200) {
player.mode = 'ultimate';
player.ultimate = parseStats(ultRes.data);
if (
player.ironman.skills.overall.xp !== player.ultimate.skills.overall.xp
) {
player.deulted = true;
player.mode = 'ironman';
}
if (
player.main.skills.overall.xp !== player.ironman.skills.overall.xp
) {
player.deironed = true;
player.mode = 'main';
}
} else {
player.mode = 'ironman'; player.mode = 'ironman';
if ( }
if (
player.main.skills.overall.xp !== player.ironman.skills.overall.xp
) {
player.deironed = true;
player.mode = 'main';
}
} else if (ult) {
player.mode = 'ultimate';
player.ultimate = parseJsonStats(ult);
if (
player.ironman.skills.overall.xp !== player.ultimate.skills.overall.xp
) {
player.deulted = true;
player.mode = 'ironman';
}
if (
player.main.skills.overall.xp !== player.ironman.skills.overall.xp
) {
player.deironed = true;
player.mode = 'main';
}
} else {
player.mode = 'ironman';
if (
player.main.skills.overall.xp !== player.ironman.skills.overall.xp player.main.skills.overall.xp !== player.ironman.skills.overall.xp
) { ) {
player.deironed = true; player.deironed = true;
player.mode = 'main'; player.mode = 'main';
}
} }
} }
return player;
} }
throw Error('Player not found');
return player;
} }
/** /**
@@ -258,21 +363,20 @@ export async function getStats(
* *
* @param rsn Username of the player. * @param rsn Username of the player.
* @param mode Gamemode to fetch ranks for. * @param mode Gamemode to fetch ranks for.
* @param config Optional axios request config object.
* @returns Stats object. * @returns Stats object.
*/ */
export async function getStatsByGamemode( export async function getStatsByGamemode(
rsn: string, rsn: string,
mode: Gamemode = 'main' mode: Gamemode = 'main',
config?: AxiosRequestConfig
): Promise<Stats> { ): Promise<Stats> {
validateRSN(rsn); validateRSN(rsn);
if (!GAMEMODES.includes(mode)) { if (!GAMEMODES.includes(mode)) {
throw Error('Invalid game mode'); throw Error('Invalid game mode');
} }
const response = await httpGet<string>(getStatsURL(mode, rsn)); const response = await getOfficialStats(rsn, mode, config);
if (response.status !== 200) { const stats = parseJsonStats(response);
throw Error('Player not found');
}
const stats = parseStats(response.data);
return stats; return stats;
} }
@@ -280,7 +384,8 @@ export async function getStatsByGamemode(
export async function getSkillPage( export async function getSkillPage(
skill: SkillName, skill: SkillName,
mode: Gamemode = 'main', mode: Gamemode = 'main',
page: number = 1 page: number = 1,
config?: AxiosRequestConfig
): Promise<PlayerSkillRow[]> { ): Promise<PlayerSkillRow[]> {
if (!GAMEMODES.includes(mode)) { if (!GAMEMODES.includes(mode)) {
throw Error('Invalid game mode'); throw Error('Invalid game mode');
@@ -291,7 +396,10 @@ export async function getSkillPage(
} }
const url = getSkillPageURL(mode, skill, page); const url = getSkillPageURL(mode, skill, page);
const response = await httpGet<string | Buffer | BinaryData | undefined>(url); const response = await httpGet<string | Buffer | BinaryData | undefined>(
url,
config
);
const dom = new JSDOM(response.data); const dom = new JSDOM(response.data);
const playersHTML = dom.window.document.querySelectorAll( const playersHTML = dom.window.document.querySelectorAll(
'.personal-hiscores__row' '.personal-hiscores__row'
@@ -323,12 +431,14 @@ export async function getSkillPage(
* @param activity Name of the activity or boss to fetch hiscores for. * @param activity Name of the activity or boss to fetch hiscores for.
* @param mode Gamemode to fetch ranks for. * @param mode Gamemode to fetch ranks for.
* @param page Page number. * @param page Page number.
* @param config Optional axios request config object.
* @returns Array of `PlayerActivityRow` objects. * @returns Array of `PlayerActivityRow` objects.
*/ */
export async function getActivityPage( export async function getActivityPage(
activity: ActivityName, activity: ActivityName,
mode: Gamemode = 'main', mode: Gamemode = 'main',
page: number = 1 page: number = 1,
config?: AxiosRequestConfig
): Promise<PlayerActivityRow[]> { ): Promise<PlayerActivityRow[]> {
if (!GAMEMODES.includes(mode)) { if (!GAMEMODES.includes(mode)) {
throw Error('Invalid game mode'); throw Error('Invalid game mode');
@@ -339,7 +449,10 @@ export async function getActivityPage(
} }
const url = getActivityPageURL(mode, activity, page); const url = getActivityPageURL(mode, activity, page);
const response = await httpGet<string | Buffer | BinaryData | undefined>(url); const response = await httpGet<string | Buffer | BinaryData | undefined>(
url,
config
);
const dom = new JSDOM(response.data); const dom = new JSDOM(response.data);
const playersHTML = dom.window.document.querySelectorAll( const playersHTML = dom.window.document.querySelectorAll(
'.personal-hiscores__row' '.personal-hiscores__row'

View File

@@ -1,3 +1,5 @@
import { AxiosRequestConfig } from 'axios';
export type Gamemode = export type Gamemode =
| 'main' | 'main'
| 'ironman' | 'ironman'
@@ -60,7 +62,7 @@ export type ClueType =
export type Clues = { [Type in ClueType]: Activity }; export type Clues = { [Type in ClueType]: Activity };
export type BHType = 'rogue' | 'hunter'; export type BHType = 'rogue' | 'hunter' | 'rogueV2' | 'hunterV2';
export type BH = { [Type in BHType]: Activity }; export type BH = { [Type in BHType]: Activity };
@@ -84,6 +86,7 @@ export type Boss =
| 'dagannothRex' | 'dagannothRex'
| 'dagannothSupreme' | 'dagannothSupreme'
| 'derangedArchaeologist' | 'derangedArchaeologist'
| 'dukeSucellus'
| 'generalGraardor' | 'generalGraardor'
| 'giantMole' | 'giantMole'
| 'grotesqueGuardians' | 'grotesqueGuardians'
@@ -106,6 +109,8 @@ export type Boss =
| 'tempoross' | 'tempoross'
| 'gauntlet' | 'gauntlet'
| 'corruptedGauntlet' | 'corruptedGauntlet'
| 'leviathan'
| 'whisperer'
| 'theatreOfBlood' | 'theatreOfBlood'
| 'theatreOfBloodHardMode' | 'theatreOfBloodHardMode'
| 'thermonuclearSmokeDevil' | 'thermonuclearSmokeDevil'
@@ -113,6 +118,7 @@ export type Boss =
| 'tombsOfAmascutExpertMode' | 'tombsOfAmascutExpertMode'
| 'tzKalZuk' | 'tzKalZuk'
| 'tzTokJad' | 'tzTokJad'
| 'vardorvis'
| 'venenatis' | 'venenatis'
| 'vetion' | 'vetion'
| 'vorkath' | 'vorkath'
@@ -124,6 +130,8 @@ export type Bosses = { [Type in Boss]: Activity };
export type ActivityName = export type ActivityName =
| 'leaguePoints' | 'leaguePoints'
| 'hunterBHV2'
| 'rogueBHV2'
| 'hunterBH' | 'hunterBH'
| 'rogueBH' | 'rogueBH'
| 'lastManStanding' | 'lastManStanding'
@@ -181,4 +189,51 @@ export interface GetStatsOptions {
* @defaultvalue `true` * @defaultvalue `true`
*/ */
shouldGetFormattedRsn?: boolean; shouldGetFormattedRsn?: boolean;
/**
* Map of configs for each requests that can take place in the `getStats` function.
*/
axiosConfigs?: Partial<Record<Gamemode, AxiosRequestConfig>> & {
/**
* The axios request config object to use for the RSN format request.
*/
rsn?: AxiosRequestConfig;
};
}
export interface HiscoresCommon {
/**
* This field behaves more like an index than a true unique ID.
*/
id: number;
/**
* The display name of this skill / activity.
*/
name: string;
/**
* The player's official hiscores rank in this skill / activity.
*/
rank: number;
}
export interface HiscoresSkill extends HiscoresCommon {
/**
* The player's current level in this skill.
*/
level: number;
/**
* The player's current experience in this skill.
*/
xp: number;
}
export interface HiscoresActivity extends HiscoresCommon {
/**
* The player's current score in this activity.
*/
score: number;
}
export interface HiscoresResponse {
skills: HiscoresSkill[];
activities: HiscoresActivity[];
} }

View File

@@ -9,6 +9,7 @@ import {
export const BASE_URL = 'https://secure.runescape.com/m=hiscore_oldschool'; export const BASE_URL = 'https://secure.runescape.com/m=hiscore_oldschool';
export const STATS_URL = 'index_lite.ws?player='; export const STATS_URL = 'index_lite.ws?player=';
export const JSON_STATS_URL = 'index_lite.json?player=';
export const SCORES_URL = 'overall.ws?'; export const SCORES_URL = 'overall.ws?';
export type GamemodeUrl = { export type GamemodeUrl = {
@@ -62,7 +63,7 @@ export const CLUES: ClueType[] = [
'elite', 'elite',
'master' 'master'
]; ];
export const BH_MODES: BHType[] = ['hunter', 'rogue']; export const BH_MODES: BHType[] = ['hunterV2', 'rogueV2', 'hunter', 'rogue'];
export const GAMEMODES: Gamemode[] = [ export const GAMEMODES: Gamemode[] = [
'main', 'main',
'ironman', 'ironman',
@@ -92,6 +93,7 @@ export const BOSSES: Boss[] = [
'dagannothRex', 'dagannothRex',
'dagannothSupreme', 'dagannothSupreme',
'derangedArchaeologist', 'derangedArchaeologist',
'dukeSucellus',
'generalGraardor', 'generalGraardor',
'giantMole', 'giantMole',
'grotesqueGuardians', 'grotesqueGuardians',
@@ -114,6 +116,8 @@ export const BOSSES: Boss[] = [
'tempoross', 'tempoross',
'gauntlet', 'gauntlet',
'corruptedGauntlet', 'corruptedGauntlet',
'leviathan',
'whisperer',
'theatreOfBlood', 'theatreOfBlood',
'theatreOfBloodHardMode', 'theatreOfBloodHardMode',
'thermonuclearSmokeDevil', 'thermonuclearSmokeDevil',
@@ -121,6 +125,7 @@ export const BOSSES: Boss[] = [
'tombsOfAmascutExpertMode', 'tombsOfAmascutExpertMode',
'tzKalZuk', 'tzKalZuk',
'tzTokJad', 'tzTokJad',
'vardorvis',
'venenatis', 'venenatis',
'vetion', 'vetion',
'vorkath', 'vorkath',
@@ -130,6 +135,8 @@ export const BOSSES: Boss[] = [
]; ];
export const ACTIVITIES: ActivityName[] = [ export const ACTIVITIES: ActivityName[] = [
'leaguePoints', 'leaguePoints',
'hunterBHV2',
'rogueBHV2',
'hunterBH', 'hunterBH',
'rogueBH', 'rogueBH',
'allClues', 'allClues',
@@ -157,7 +164,7 @@ export const FORMATTED_BOSS_NAMES: FormattedBossNames = {
barrows: 'Barrows Chests', barrows: 'Barrows Chests',
bryophyta: 'Bryophyta', bryophyta: 'Bryophyta',
callisto: 'Callisto', callisto: 'Callisto',
calvarion: "Cal'varion", calvarion: "Calvar'ion",
cerberus: 'Cerberus', cerberus: 'Cerberus',
chambersOfXeric: 'Chambers of Xeric', chambersOfXeric: 'Chambers of Xeric',
chambersOfXericChallengeMode: 'Chambers of Xeric: Challenge Mode', chambersOfXericChallengeMode: 'Chambers of Xeric: Challenge Mode',
@@ -170,6 +177,7 @@ export const FORMATTED_BOSS_NAMES: FormattedBossNames = {
dagannothRex: 'Dagannoth Rex', dagannothRex: 'Dagannoth Rex',
dagannothSupreme: 'Dagannoth Supreme', dagannothSupreme: 'Dagannoth Supreme',
derangedArchaeologist: 'Deranged Archaeologist', derangedArchaeologist: 'Deranged Archaeologist',
dukeSucellus: 'Duke Sucellus',
generalGraardor: 'General Graardor', generalGraardor: 'General Graardor',
giantMole: 'Giant Mole', giantMole: 'Giant Mole',
grotesqueGuardians: 'Grotesque Guardians', grotesqueGuardians: 'Grotesque Guardians',
@@ -181,7 +189,7 @@ export const FORMATTED_BOSS_NAMES: FormattedBossNames = {
krilTsutsaroth: "K'ril Tsutsaroth", krilTsutsaroth: "K'ril Tsutsaroth",
mimic: 'Mimic', mimic: 'Mimic',
nex: 'Nex', nex: 'Nex',
nightmare: 'The Nightmare of Ashihama', nightmare: 'Nightmare',
phosanisNightmare: "Phosani's Nightmare", phosanisNightmare: "Phosani's Nightmare",
obor: 'Obor', obor: 'Obor',
phantomMuspah: 'Phantom Muspah', phantomMuspah: 'Phantom Muspah',
@@ -192,6 +200,8 @@ export const FORMATTED_BOSS_NAMES: FormattedBossNames = {
tempoross: 'Tempoross', tempoross: 'Tempoross',
gauntlet: 'The Gauntlet', gauntlet: 'The Gauntlet',
corruptedGauntlet: 'The Corrupted Gauntlet', corruptedGauntlet: 'The Corrupted Gauntlet',
leviathan: 'The Leviathan',
whisperer: 'The Whisperer',
theatreOfBlood: 'Theatre of Blood', theatreOfBlood: 'Theatre of Blood',
theatreOfBloodHardMode: 'Theatre of Blood: Hard Mode', theatreOfBloodHardMode: 'Theatre of Blood: Hard Mode',
thermonuclearSmokeDevil: 'Thermonuclear Smoke Devil', thermonuclearSmokeDevil: 'Thermonuclear Smoke Devil',
@@ -199,6 +209,7 @@ export const FORMATTED_BOSS_NAMES: FormattedBossNames = {
tombsOfAmascutExpertMode: 'Tombs of Amascut: Expert Mode', tombsOfAmascutExpertMode: 'Tombs of Amascut: Expert Mode',
tzKalZuk: 'TzKal-Zuk', tzKalZuk: 'TzKal-Zuk',
tzTokJad: 'TzTok-Jad', tzTokJad: 'TzTok-Jad',
vardorvis: 'Vardorvis',
venenatis: 'Venenatis', venenatis: 'Venenatis',
vetion: "Vet'ion", vetion: "Vet'ion",
vorkath: 'Vorkath', vorkath: 'Vorkath',
@@ -257,14 +268,54 @@ export type FormattedBHNames = {
}; };
export const FORMATTED_BH_NAMES: FormattedBHNames = { export const FORMATTED_BH_NAMES: FormattedBHNames = {
rogue: 'Bounty Hunter - Rogue', rogue: 'Bounty Hunter (Legacy) - Rogue',
hunter: 'Bounty Hunter - Hunter' hunter: 'Bounty Hunter (Legacy) - Hunter',
rogueV2: 'Bounty Hunter - Rogue',
hunterV2: 'Bounty Hunter - Hunter'
}; };
export const FORMATTED_LMS = 'Last Man Standing'; export const FORMATTED_LMS = 'LMS - Rank';
export const FORMATTED_PVP_ARENA = 'PvP Arena'; export const FORMATTED_PVP_ARENA = 'PvP Arena - Rank';
export const FORMATTED_SOUL_WARS = 'Soul Wars Zeal'; export const FORMATTED_SOUL_WARS = 'Soul Wars Zeal';
export const FORMATTED_LEAGUE_POINTS = 'League Points'; export const FORMATTED_LEAGUE_POINTS = 'League Points';
export const FORMATTED_RIFTS_CLOSED = 'Rifts Closed'; export const FORMATTED_RIFTS_CLOSED = 'Rifts closed';
export const INVALID_FORMAT_ERROR = 'Invalid hiscores format'; export const INVALID_FORMAT_ERROR = 'Invalid hiscores format';
export const PLAYER_NOT_FOUND_ERROR = 'Player not found';
export const HISCORES_ERROR = 'HiScores not responding';
export class InvalidFormatError extends Error {
__proto__ = Error;
constructor() {
super(INVALID_FORMAT_ERROR);
Object.setPrototypeOf(this, InvalidFormatError.prototype);
}
}
export class InvalidRSNError extends Error {
__proto__ = Error;
constructor(message: string) {
super(message);
Object.setPrototypeOf(this, InvalidRSNError.prototype);
}
}
export class PlayerNotFoundError extends Error {
__proto__ = Error;
constructor() {
super(PLAYER_NOT_FOUND_ERROR);
Object.setPrototypeOf(this, PlayerNotFoundError.prototype);
}
}
export class HiScoresError extends Error {
__proto__ = Error;
constructor() {
super(HISCORES_ERROR);
Object.setPrototypeOf(this, HiScoresError.prototype);
}
}

View File

@@ -1,4 +1,4 @@
import axios from 'axios'; import axios, { AxiosRequestConfig } from 'axios';
import * as ua from 'useragent-generator'; import * as ua from 'useragent-generator';
import { Gamemode, SkillName, ActivityName } from '../types'; import { Gamemode, SkillName, ActivityName } from '../types';
import { import {
@@ -6,7 +6,9 @@ import {
STATS_URL, STATS_URL,
SCORES_URL, SCORES_URL,
SKILLS, SKILLS,
ACTIVITIES ACTIVITIES,
JSON_STATS_URL,
InvalidRSNError
} from './constants'; } from './constants';
/** /**
@@ -14,10 +16,13 @@ import {
* *
* @param gamemode Gamemode to fetch ranks for. * @param gamemode Gamemode to fetch ranks for.
* @param rsn Username of the player. * @param rsn Username of the player.
* @param json If the JSON endpoint is desired instead of CSV.
* @returns Encoded stats URL. * @returns Encoded stats URL.
*/ */
export const getStatsURL = (gamemode: Gamemode, rsn: string) => export const getStatsURL = (gamemode: Gamemode, rsn: string, json = false) =>
`${GAMEMODE_URL[gamemode]}${STATS_URL}${encodeURIComponent(rsn)}`; `${GAMEMODE_URL[gamemode]}${
json ? JSON_STATS_URL : STATS_URL
}${encodeURIComponent(rsn)}`;
/** /**
* Will generate a player table URL for the official OSRS hiscores website. * Will generate a player table URL for the official OSRS hiscores website.
@@ -96,12 +101,16 @@ export const rsnFromElement = (el: Element | null) => {
* @param url URL to run a `GET` request against. * @param url URL to run a `GET` request against.
* @returns Axios response. * @returns Axios response.
*/ */
export const httpGet = <Response>(url: string) => export const httpGet = <Response>(
url: string,
config: AxiosRequestConfig = {}
) =>
axios.get<Response>(url, { axios.get<Response>(url, {
headers: { headers: {
// without User-Agent header requests may be rejected by DDoS protection mechanism // without User-Agent header requests may be rejected by DDoS protection mechanism
'User-Agent': ua.firefox(80) 'User-Agent': ua.firefox(80)
} },
...config
}); });
/** /**
@@ -111,10 +120,10 @@ export const httpGet = <Response>(url: string) =>
*/ */
export const validateRSN = (rsn: string) => { export const validateRSN = (rsn: string) => {
if (typeof rsn !== 'string') { if (typeof rsn !== 'string') {
throw Error('RSN must be a string'); throw new InvalidRSNError('RSN must be a string');
} else if (!/^[a-zA-Z0-9 _-]+$/.test(rsn)) { } else if (!/^[a-zA-Z0-9 _-]+$/.test(rsn)) {
throw Error('RSN contains invalid character'); throw new InvalidRSNError('RSN contains invalid character');
} else if (rsn.length > 12 || rsn.length < 1) { } else if (rsn.length > 12 || rsn.length < 1) {
throw Error('RSN must be between 1 and 12 characters'); throw new InvalidRSNError('RSN must be between 1 and 12 characters');
} }
}; };

4952
yarn.lock

File diff suppressed because it is too large Load Diff