Compare commits

..

25 Commits

Author SHA1 Message Date
maxswa
7de2d9a95a v2.2.0 2020-07-23 18:22:38 -04:00
Max Swartwout
a143306519 Merge pull request #15 from maxswa/release/2.2.0
Release/2.2.0
2020-07-23 18:20:49 -04:00
maxswa
86b81abfd8 Upgrade dependencies. 2020-07-23 18:18:58 -04:00
maxswa
8d065742d0 Replace cheerio with jsdom. 2020-07-23 18:13:51 -04:00
Max
eaa3d4a299 v2.1.0 2020-04-01 00:30:08 -04:00
Max Swartwout
a92fa7fffe Merge pull request #10 from maxswa/release/1.2.2
Release/1.2.2
2020-04-01 00:27:54 -04:00
Max
90f2939761 Upgrade dependencies, fix tests. 2020-04-01 00:23:56 -04:00
Max
12046246ef Add nightmare to boss list. 2020-04-01 00:23:37 -04:00
Max Swartwout
adf73e59c4 Merge pull request #9 from maxswa/dependabot/npm_and_yarn/acorn-5.7.4
Bump acorn from 5.7.3 to 5.7.4
2020-03-31 23:53:56 -04:00
dependabot[bot]
f2fcbad1c4 Bump acorn from 5.7.3 to 5.7.4
Bumps [acorn](https://github.com/acornjs/acorn) from 5.7.3 to 5.7.4.
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/5.7.3...5.7.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-04-01 03:53:20 +00:00
Max Swartwout
0f47310814 Merge pull request #8 from maxswa/bug/rsn-format-error
Add catch for getRSNFormat error.
2020-03-31 23:52:30 -04:00
maxswa
30530fde01 Add catch for getRSNFormat error. 2020-01-29 12:24:59 -05:00
maxswa
2c0268939d v2.0.2 2020-01-06 21:54:21 -05:00
maxswa
0142783d43 Remove temporary bosses fix. 2020-01-06 21:53:50 -05:00
maxswa
468b43f66f v2.0.1 2020-01-05 20:43:45 -05:00
maxswa
d5bbe2a169 Update getActivityPage to accept bosses. 2020-01-05 20:43:15 -05:00
maxswa
26d06da24b v2.0.0 2020-01-05 18:10:22 -05:00
Max Swartwout
582a5c01fd Merge pull request #6 from maxswa/boss-hiscores
Add Boss Hiscores
2020-01-05 18:02:47 -05:00
maxswa
0138dcd5fd Add trailing slash to gamemode urls. 2020-01-05 17:57:22 -05:00
maxswa
f1730d44ec Rename properties for clarity, add formatted names 2020-01-05 17:47:13 -05:00
maxswa
ae73fef5ed Add bosses and support for leagues. 2020-01-04 16:59:07 -05:00
Max Swartwout
8bf1f6cdbc Merge pull request #5 from maxswa/dependabot/npm_and_yarn/mixin-deep-1.3.2
Bump mixin-deep from 1.3.1 to 1.3.2
2019-12-31 13:37:35 -05:00
dependabot[bot]
50d8365e87 Bump mixin-deep from 1.3.1 to 1.3.2
Bumps [mixin-deep](https://github.com/jonschlinkert/mixin-deep) from 1.3.1 to 1.3.2.
- [Release notes](https://github.com/jonschlinkert/mixin-deep/releases)
- [Commits](https://github.com/jonschlinkert/mixin-deep/compare/1.3.1...1.3.2)

Signed-off-by: dependabot[bot] <support@github.com>
2019-12-31 18:36:46 +00:00
Max Swartwout
8f9c9777e5 Merge pull request #4 from maxswa/dependabot/npm_and_yarn/handlebars-4.5.3
Bump handlebars from 4.1.2 to 4.5.3
2019-12-31 13:36:18 -05:00
dependabot[bot]
470d788327 Bump handlebars from 4.1.2 to 4.5.3
Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.1.2 to 4.5.3.
- [Release notes](https://github.com/wycats/handlebars.js/releases)
- [Changelog](https://github.com/wycats/handlebars.js/blob/master/release-notes.md)
- [Commits](https://github.com/wycats/handlebars.js/compare/v4.1.2...v4.5.3)

Signed-off-by: dependabot[bot] <support@github.com>
2019-12-28 15:44:13 +00:00
10 changed files with 2534 additions and 2086 deletions

120
README.md
View File

@@ -68,17 +68,17 @@ const topPage = await getSkillPage('overall');
`getStats` will return a full player object with gamemode.
`getStatsByGameMode` will return a stats object and accepts a gamemode parameter:
| Game mode | Param |
| ---------------- | :----: |
| Regular | `main` |
| Ironman | `iron` |
| Hardcore Ironman | `hc` |
| Ultimate Ironman | `ult` |
| Deadman Mode | `dmm` |
| Seasonal Deadman | `sdmm` |
| DMM Tournament | `dmmt` |
| Game mode | Param |
| ---------------- | :----------: |
| Regular | `main` |
| Ironman | `ironman` |
| Hardcore Ironman | `hardcore` |
| Ultimate Ironman | `ultimate` |
| Deadman Mode | `deadman` |
| Tournament | `tournament` |
| Leagues | `seasonal` |
`getSkillPage` and `getActivityPage` require a skill/activity and optionally a gamemode and page:
`getSkillPage` and `getActivityPage` require a skill / activity and optionally a gamemode and page:
```javascript
hiscores
@@ -87,27 +87,82 @@ hiscores
.catch(err => console.error(err));
```
Activities consist of all levels of clue scrolls as well as minigames:
Activities consist of all levels of clue scrolls as well as minigames and bosses:
### Clue Scrolls
| Type | Param |
| -------- | :-------------: |
| All | `allclues` |
| Beginner | `beginnerclues` |
| Easy | `easyclues` |
| Medium | `mediumclues` |
| Hard | `hardclues` |
| Elite | `eliteclues` |
| Master | `masterclues` |
| All | `allClues` |
| Beginner | `beginnerClues` |
| Easy | `easyClues` |
| Medium | `mediumClues` |
| Hard | `hardClues` |
| Elite | `eliteClues` |
| Master | `masterClues` |
### Minigames
| Minigame | Param |
| ---------------------- | :--------: |
| Bounty Hunter (Rogue) | `roguebh` |
| Bounty Hunter (Hunter) | `hunterbh` |
| Last Man Standing | `lms` |
| Minigame | Param |
| ---------------------- | :---------------: |
| Bounty Hunter (Rogue) | `rogueBH` |
| Bounty Hunter (Hunter) | `hunterBH` |
| Last Man Standing | `lastManStanding` |
### Leagues
| Activity | Param |
| ------------- | :------------: |
| League Points | `leaguePoints` |
### Bosses
| Boss Name | Param |
| -------------------------------- | :----------------------------: |
| Abyssal Sire | `abyssalSire` |
| Alchemical Hydra | `alchemicalHydra` |
| Barrows Chests | `barrows` |
| Bryophyta | `bryophyta` |
| Callisto | `callisto` |
| Cerberus | `cerberus` |
| Chambers Of Xeric | `chambersOfXeric` |
| Chambers Of Xeric Challenge Mode | `chambersOfXericChallengeMode` |
| Chaos Elemental | `chaosElemental` |
| Chaos Fanatic | `chaosFanatic` |
| Commander Zilyana | `commanderZilyana` |
| Corporeal Beast | `corporealBeast` |
| Crazy Archaeologist | `crazyArchaeologist` |
| Dagannoth Prime | `dagannothPrime` |
| Dagannoth Rex | `dagannothRex` |
| Dagannoth Supreme | `dagannothSupreme` |
| Deranged Archaeologist | `derangedArchaeologist` |
| General Graardor | `generalGraardor` |
| Giant Mole | `giantMole` |
| Grotesque Guardians | `grotesqueGuardians` |
| Hespori | `hespori` |
| Kalphite Queen | `kalphiteQueen` |
| King Black Dragon | `kingBlackDragon` |
| Kraken | `kraken` |
| Kreearra | `kreeArra` |
| K'ril Tsutsaroth | `krilTsutsaroth` |
| Mimic | `mimic` |
| The Nightmare of Ashihama | `nightmare` |
| Obor | `obor` |
| Sarachnis | `sarachnis` |
| Scorpia | `scorpia` |
| Skotizo | `skotizo` |
| Gauntlet | `gauntlet` |
| Corrupted Gauntlet | `corruptedGauntlet` |
| Theatre Of Blood | `theatreOfBlood` |
| Thermonuclear Smoke Devil | `thermonuclearSmokeDevil` |
| TzKal-Zuk | `tzKalZuk` |
| TzTok-Jad | `tzTokJad` |
| Venenatis | `venenatis` |
| Vetion | `vetion` |
| Vorkath | `vorkath` |
| Wintertodt | `wintertodt` |
| Zalcano | `zalcano` |
| Zulrah | `zulrah` |
## What you'll get
@@ -115,7 +170,7 @@ Activities consist of all levels of clue scrolls as well as minigames:
```javascript
{
rsn: 'Lynx Titan',
name: 'Lynx Titan',
mode: 'main',
dead: false,
deulted: false,
@@ -128,8 +183,10 @@ Activities consist of all levels of clue scrolls as well as minigames:
// ...
},
clues: {},
bh: {},
lms: {}
leaguePoints: {},
bountyHunter: {},
lastManStanding: {},
bosses: {}
}
}
```
@@ -138,9 +195,18 @@ Activities consist of all levels of clue scrolls as well as minigames:
```javascript
[
{ rank: 1, rsn: 'Lynx Titan', level: 2277, xp: 4600000000, dead: false },
{ rank: 1, name: 'Lynx Titan', level: 2277, xp: 4600000000, dead: false },
{},
{},
// ...
];
```
## Helpful Extras
Get the properly formatted name of any skill, boss, clue or other activity:
```javascript
// kril === "K'ril Tsutsaroth"
const kril = FORMATTED_BOSS_NAMES['krilTsutsaroth'];
```

View File

@@ -1,322 +1,404 @@
import {
parseStats,
getRSNFormat,
getSkillPage,
getStats,
getStatsByGamemode,
getRSNFormat,
Stats,
} from '../src/index';
import { PlayerSkillRow, Player, Stats } from '../src/types';
import axios, { AxiosError } from 'axios';
test('Parse CSV to json', () => {
const csv = `40258,2063,218035714
20554, 99, 21102621
39059, 99, 15364425
14245, 99, 26556827
19819, 99, 33511407
27857, 99, 25774115
44278, 91, 6081159
40110, 99, 15128024
178948, 90, 5347474
175463, 81, 2355494
138677, 90, 5356303
77587, 91, 5904710
158478, 85, 3570485
93958, 83, 2684426
39179, 88, 4425107
138406, 77, 1591377
33399, 90, 5866307
1794, 99, 15057674
45551, 91, 6363261
121165, 90, 5748493
89460, 88, 4624078
53099, 80, 2008229
169127, 73, 1067670
115543, 82, 2546048
-1, -1
-1, -1
32875, 500
24817, 476
212728, 1
94827, 20
59099, 74
24642, 231
5206, 99
6293, 51`;
const csv = `246,2277,1338203419
615,99,77438259
428,99,69176307
425,99,115218641
138,99,181425111
160,99,169725807
97,99,50666171
144,99,93155913
2108,99,53198880
5750,99,19589494
295,99,76386488
1304,99,32252994
847,99,54325931
534,99,26379932
7213,99,13246799
2475,99,18161285
1837,99,14746134
668,99,23961670
3841,99,17970837
265,99,56230978
821,99,62123353
169,99,43127930
810,99,37688883
92,99,32005622
-1,-1
-1,-1
-1,-1
32,12148
3105,76
1997,505
127,4259
361,915
392,250
1,6143
4814,898
382,2780
944,3000
1981,1452
4981,23
888,1046
613,4856
102,4038
156,334
6240,133
4569,250
6893,603
1,17798
9320,125
1030,2802
4342,1655
966,2951
10151,1
1288,2407
377,4669
545,1567
6083,94
264,2897
4052,1277
41643,1477
625,2391
120,2981
1,109
3,22666
26,323
201,1101
82,3404
5085,61
63,375
2870,6
6984,138
4043,2000
489,8
967,47
11155,223
1940,272
8623,1340
605,1694
-1,-1
3867,4583`;
expect(parseStats(csv)).toStrictEqual({
const expectedOutput: Stats = {
skills: {
overall: { rank: 40258, level: 2063, xp: 218035714 },
attack: { rank: 20554, level: 99, xp: 21102621 },
defence: { rank: 39059, level: 99, xp: 15364425 },
strength: { rank: 14245, level: 99, xp: 26556827 },
hitpoints: { rank: 19819, level: 99, xp: 33511407 },
ranged: { rank: 27857, level: 99, xp: 25774115 },
prayer: { rank: 44278, level: 91, xp: 6081159 },
magic: { rank: 40110, level: 99, xp: 15128024 },
cooking: { rank: 178948, level: 90, xp: 5347474 },
woodcutting: { rank: 175463, level: 81, xp: 2355494 },
fletching: { rank: 138677, level: 90, xp: 5356303 },
fishing: { rank: 77587, level: 91, xp: 5904710 },
firemaking: { rank: 158478, level: 85, xp: 3570485 },
crafting: { rank: 93958, level: 83, xp: 2684426 },
smithing: { rank: 39179, level: 88, xp: 4425107 },
mining: { rank: 138406, level: 77, xp: 1591377 },
herblore: { rank: 33399, level: 90, xp: 5866307 },
agility: { rank: 1794, level: 99, xp: 15057674 },
thieving: { rank: 45551, level: 91, xp: 6363261 },
slayer: { rank: 121165, level: 90, xp: 5748493 },
farming: { rank: 89460, level: 88, xp: 4624078 },
runecraft: { rank: 53099, level: 80, xp: 2008229 },
hunter: { rank: 169127, level: 73, xp: 1067670 },
construction: { rank: 115543, level: 82, xp: 2546048 },
overall: { rank: 246, level: 2277, xp: 1338203419 },
attack: { rank: 615, level: 99, xp: 77438259 },
defence: { rank: 428, level: 99, xp: 69176307 },
strength: { rank: 425, level: 99, xp: 115218641 },
hitpoints: { rank: 138, level: 99, xp: 181425111 },
ranged: { rank: 160, level: 99, xp: 169725807 },
prayer: { rank: 97, level: 99, xp: 50666171 },
magic: { rank: 144, level: 99, xp: 93155913 },
cooking: { rank: 2108, level: 99, xp: 53198880 },
woodcutting: { rank: 5750, level: 99, xp: 19589494 },
fletching: { rank: 295, level: 99, xp: 76386488 },
fishing: { rank: 1304, level: 99, xp: 32252994 },
firemaking: { rank: 847, level: 99, xp: 54325931 },
crafting: { rank: 534, level: 99, xp: 26379932 },
smithing: { rank: 7213, level: 99, xp: 13246799 },
mining: { rank: 2475, level: 99, xp: 18161285 },
herblore: { rank: 1837, level: 99, xp: 14746134 },
agility: { rank: 668, level: 99, xp: 23961670 },
thieving: { rank: 3841, level: 99, xp: 17970837 },
slayer: { rank: 265, level: 99, xp: 56230978 },
farming: { rank: 821, level: 99, xp: 62123353 },
runecraft: { rank: 169, level: 99, xp: 43127930 },
hunter: { rank: 810, level: 99, xp: 37688883 },
construction: { rank: 92, level: 99, xp: 32005622 },
},
bh: {
leaguePoints: { rank: -1, score: -1 },
bountyHunter: {
rogue: { rank: -1, score: -1 },
hunter: { rank: -1, score: -1 },
},
lms: { rank: 32875, score: 500 },
lastManStanding: { rank: 4814, score: 898 },
clues: {
all: { rank: 24817, score: 476 },
beginner: { rank: 212728, score: 1 },
easy: { rank: 94827, score: 20 },
medium: { rank: 59099, score: 74 },
hard: { rank: 24642, score: 231 },
elite: { rank: 5206, score: 99 },
master: { rank: 6293, score: 51 },
all: { rank: 32, score: 12148 },
beginner: { rank: 3105, score: 76 },
easy: { rank: 1997, score: 505 },
medium: { rank: 127, score: 4259 },
hard: { rank: 361, score: 915 },
elite: { rank: 392, score: 250 },
master: { rank: 1, score: 6143 },
},
bosses: {
abyssalSire: { rank: 382, score: 2780 },
alchemicalHydra: { rank: 944, score: 3000 },
barrows: { rank: 1981, score: 1452 },
bryophyta: { rank: 4981, score: 23 },
callisto: { rank: 888, score: 1046 },
cerberus: { rank: 613, score: 4856 },
chambersOfXeric: { rank: 102, score: 4038 },
chambersOfXericChallengeMode: { rank: 156, score: 334 },
chaosElemental: { rank: 6240, score: 133 },
chaosFanatic: { rank: 4569, score: 250 },
commanderZilyana: { rank: 6893, score: 603 },
corporealBeast: { rank: 1, score: 17798 },
crazyArchaeologist: { rank: 9320, score: 125 },
dagannothPrime: { rank: 1030, score: 2802 },
dagannothRex: { rank: 4342, score: 1655 },
dagannothSupreme: { rank: 966, score: 2951 },
derangedArchaeologist: { rank: 10151, score: 1 },
generalGraardor: { rank: 1288, score: 2407 },
giantMole: { rank: 377, score: 4669 },
grotesqueGuardians: { rank: 545, score: 1567 },
hespori: { rank: 6083, score: 94 },
kalphiteQueen: { rank: 264, score: 2897 },
kingBlackDragon: { rank: 4052, score: 1277 },
kraken: { rank: 41643, score: 1477 },
kreeArra: { rank: 625, score: 2391 },
krilTsutsaroth: { rank: 120, score: 2981 },
mimic: { rank: 1, score: 109 },
nightmare: { rank: 3, score: 22666 },
obor: { rank: 26, score: 323 },
sarachnis: { rank: 201, score: 1101 },
scorpia: { rank: 82, score: 3404 },
skotizo: { rank: 5085, score: 61 },
gauntlet: { rank: 63, score: 375 },
corruptedGauntlet: { rank: 2870, score: 6 },
theatreOfBlood: { rank: 6984, score: 138 },
thermonuclearSmokeDevil: { rank: 4043, score: 2000 },
tzKalZuk: { rank: 489, score: 8 },
tzTokJad: { rank: 967, score: 47 },
venenatis: { rank: 11155, score: 223 },
vetion: { rank: 1940, score: 272 },
vorkath: { rank: 8623, score: 1340 },
wintertodt: { rank: 605, score: 1694 },
zalcano: { rank: -1, score: -1 },
zulrah: { rank: 3867, score: 4583 },
},
});
});
test('Get rsn format', async done => {
const callback = (data: string) => {
expect(data).toBe('Lynx Titan');
done();
};
getRSNFormat('lYnX tiTaN').then(callback);
expect(parseStats(csv)).toStrictEqual(expectedOutput);
});
test('Get attack top page', async done => {
const callback = (data: PlayerSkillRow[]) => {
expect(data).toMatchObject([
{
rsn: expect.any(String),
rank: 1,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 2,
level: 99,
xp: 200000000,
dead: false,
},
{ rsn: 'Drakon', rank: 3, level: 99, xp: 200000000, dead: false },
{
rsn: expect.any(String),
rank: 4,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 5,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 6,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 7,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 8,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 9,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 10,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 11,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 12,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 13,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 14,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 15,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 16,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 17,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 18,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 19,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 20,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 21,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 22,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 23,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 24,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 25,
level: 99,
xp: 200000000,
dead: false,
},
]);
done();
};
getSkillPage('attack').then(callback);
test('Get name format', async () => {
jest.setTimeout(30000);
const data = await getRSNFormat('lYnX tiTaN');
expect(data).toBe('Lynx Titan');
});
test('Get non-existant player', async done => {
const callback = (err: AxiosError) => {
test('Get attack top page', async () => {
jest.setTimeout(30000);
const data = await getSkillPage('attack');
expect(data).toMatchObject([
{
name: expect.any(String),
rank: 1,
level: 99,
xp: 200000000,
dead: false,
},
{
name: expect.any(String),
rank: 2,
level: 99,
xp: 200000000,
dead: false,
},
{ name: 'Drakon', rank: 3, level: 99, xp: 200000000, dead: false },
{
name: expect.any(String),
rank: 4,
level: 99,
xp: 200000000,
dead: false,
},
{
name: expect.any(String),
rank: 5,
level: 99,
xp: 200000000,
dead: false,
},
{
name: expect.any(String),
rank: 6,
level: 99,
xp: 200000000,
dead: false,
},
{
name: expect.any(String),
rank: 7,
level: 99,
xp: 200000000,
dead: false,
},
{
name: expect.any(String),
rank: 8,
level: 99,
xp: 200000000,
dead: false,
},
{
name: expect.any(String),
rank: 9,
level: 99,
xp: 200000000,
dead: false,
},
{
name: expect.any(String),
rank: 10,
level: 99,
xp: 200000000,
dead: false,
},
{
name: expect.any(String),
rank: 11,
level: 99,
xp: 200000000,
dead: false,
},
{
name: expect.any(String),
rank: 12,
level: 99,
xp: 200000000,
dead: false,
},
{
name: expect.any(String),
rank: 13,
level: 99,
xp: 200000000,
dead: false,
},
{
name: expect.any(String),
rank: 14,
level: 99,
xp: 200000000,
dead: false,
},
{
name: expect.any(String),
rank: 15,
level: 99,
xp: 200000000,
dead: false,
},
{
name: expect.any(String),
rank: 16,
level: 99,
xp: 200000000,
dead: false,
},
{
name: expect.any(String),
rank: 17,
level: 99,
xp: 200000000,
dead: false,
},
{
name: expect.any(String),
rank: 18,
level: 99,
xp: 200000000,
dead: false,
},
{
name: expect.any(String),
rank: 19,
level: 99,
xp: 200000000,
dead: false,
},
{
name: expect.any(String),
rank: 20,
level: 99,
xp: 200000000,
dead: false,
},
{
name: expect.any(String),
rank: 21,
level: 99,
xp: 200000000,
dead: false,
},
{
name: expect.any(String),
rank: 22,
level: 99,
xp: 200000000,
dead: false,
},
{
name: expect.any(String),
rank: 23,
level: 99,
xp: 200000000,
dead: false,
},
{
name: expect.any(String),
rank: 24,
level: 99,
xp: 200000000,
dead: false,
},
{
name: expect.any(String),
rank: 25,
level: 99,
xp: 200000000,
dead: false,
},
]);
});
test('Get non-existant player', async () => {
jest.setTimeout(30000);
getStats('fishy').catch(err => {
if (err.response) {
expect(err.response.status).toBe(404);
}
done();
};
getStats('fishy').catch(callback);
});
});
test('Get stats by gamemode', async done => {
const callback = (stats: Stats) => {
expect(stats.skills).toStrictEqual({
overall: { rank: 1, level: 2277, xp: 4600000000 },
attack: { rank: 15, level: 99, xp: 200000000 },
defence: { rank: 27, level: 99, xp: 200000000 },
strength: { rank: 18, level: 99, xp: 200000000 },
hitpoints: { rank: 7, level: 99, xp: 200000000 },
ranged: { rank: 7, level: 99, xp: 200000000 },
prayer: { rank: 11, level: 99, xp: 200000000 },
magic: { rank: 32, level: 99, xp: 200000000 },
cooking: { rank: 158, level: 99, xp: 200000000 },
woodcutting: { rank: 15, level: 99, xp: 200000000 },
fletching: { rank: 12, level: 99, xp: 200000000 },
fishing: { rank: 9, level: 99, xp: 200000000 },
firemaking: { rank: 49, level: 99, xp: 200000000 },
crafting: { rank: 4, level: 99, xp: 200000000 },
smithing: { rank: 3, level: 99, xp: 200000000 },
mining: { rank: 25, level: 99, xp: 200000000 },
herblore: { rank: 5, level: 99, xp: 200000000 },
agility: { rank: 24, level: 99, xp: 200000000 },
thieving: { rank: 12, level: 99, xp: 200000000 },
slayer: { rank: 2, level: 99, xp: 200000000 },
farming: { rank: 19, level: 99, xp: 200000000 },
runecraft: { rank: 7, level: 99, xp: 200000000 },
hunter: { rank: 4, level: 99, xp: 200000000 },
construction: { rank: 4, level: 99, xp: 200000000 },
});
done();
};
getStatsByGamemode('Lynx Titan').then(callback);
test('Get stats by gamemode', async () => {
jest.setTimeout(30000);
const { skills } = await getStatsByGamemode('Lynx Titan');
expect(skills).toMatchObject({
overall: { rank: expect.any(Number), level: 2277, xp: 4600000000 },
attack: { rank: expect.any(Number), level: 99, xp: 200000000 },
defence: { rank: expect.any(Number), level: 99, xp: 200000000 },
strength: { rank: expect.any(Number), level: 99, xp: 200000000 },
hitpoints: { rank: expect.any(Number), level: 99, xp: 200000000 },
ranged: { rank: expect.any(Number), level: 99, xp: 200000000 },
prayer: { rank: expect.any(Number), level: 99, xp: 200000000 },
magic: { rank: expect.any(Number), level: 99, xp: 200000000 },
cooking: { rank: expect.any(Number), level: 99, xp: 200000000 },
woodcutting: { rank: expect.any(Number), level: 99, xp: 200000000 },
fletching: { rank: expect.any(Number), level: 99, xp: 200000000 },
fishing: { rank: expect.any(Number), level: 99, xp: 200000000 },
firemaking: { rank: expect.any(Number), level: 99, xp: 200000000 },
crafting: { rank: expect.any(Number), level: 99, xp: 200000000 },
smithing: { rank: expect.any(Number), level: 99, xp: 200000000 },
mining: { rank: expect.any(Number), level: 99, xp: 200000000 },
herblore: { rank: expect.any(Number), level: 99, xp: 200000000 },
agility: { rank: expect.any(Number), level: 99, xp: 200000000 },
thieving: { rank: expect.any(Number), level: 99, xp: 200000000 },
slayer: { rank: expect.any(Number), level: 99, xp: 200000000 },
farming: { rank: expect.any(Number), level: 99, xp: 200000000 },
runecraft: { rank: expect.any(Number), level: 99, xp: 200000000 },
hunter: { rank: expect.any(Number), level: 99, xp: 200000000 },
construction: { rank: expect.any(Number), level: 99, xp: 200000000 },
});
});

View File

@@ -3,5 +3,6 @@
"^.+\\.(t|j)sx?$": "ts-jest"
},
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"]
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"],
"testEnvironment": "node"
}

View File

@@ -1,6 +1,6 @@
{
"name": "osrs-json-hiscores",
"version": "1.2.2",
"version": "2.2.0",
"description": "The Oldschool Runescape API wrapper that does more!",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@@ -10,6 +10,7 @@
"scripts": {
"dev": "watch 'yarn run build' src",
"build": "tsc",
"format": "prettier --write \"src/**/*.ts\"",
"test": "jest --config jestconfig.json",
"prepublish": "yarn run build",
"release": "np"
@@ -36,13 +37,14 @@
"homepage": "https://github.com/maxswa/osrs-json-hiscores#readme",
"dependencies": {
"axios": "^0.19.0",
"cheerio": "^1.0.0-rc.3"
"jsdom": "^16.3.0"
},
"devDependencies": {
"@types/cheerio": "^0.22.11",
"@types/jest": "^24.0.14",
"@types/jsdom": "^16.2.3",
"jest": "^24.8.0",
"np": "^5.0.3",
"prettier": "^1.19.1",
"ts-jest": "^24.0.2",
"tslint": "^5.17.0",
"tslint-config-airbnb": "^5.11.1",

View File

@@ -1,5 +1,4 @@
import axios from 'axios';
import * as cheerio from 'cheerio';
import {
Player,
Activity,
@@ -13,6 +12,7 @@ import {
PlayerSkillRow,
ActivityName,
PlayerActivityRow,
Bosses,
} from './types';
import {
getStatsURL,
@@ -26,7 +26,9 @@ import {
numberFromElement,
rsnFromElement,
getActivityPageURL,
BOSSES,
} from './utils';
import { JSDOM } from 'jsdom';
export async function getStats(rsn: string): Promise<Player> {
if (typeof rsn !== 'string') {
@@ -40,16 +42,16 @@ export async function getStats(rsn: string): Promise<Player> {
const mainRes = await axios(getStatsURL('main', rsn));
if (mainRes.status === 200) {
const otherResponses = await Promise.all([
axios(getStatsURL('iron', rsn)).catch(err => err),
axios(getStatsURL('hc', rsn)).catch(err => err),
axios(getStatsURL('ult', rsn)).catch(err => err),
getRSNFormat(rsn),
axios(getStatsURL('ironman', rsn)).catch(err => err),
axios(getStatsURL('hardcore', rsn)).catch(err => err),
axios(getStatsURL('ultimate', rsn)).catch(err => err),
getRSNFormat(rsn).catch(() => undefined),
]);
const [ironRes, hcRes, ultRes, formattedName] = otherResponses;
const player: Player = {
rsn: formattedName,
name: formattedName || rsn,
mode: 'main',
dead: false,
deulted: false,
@@ -58,32 +60,42 @@ export async function getStats(rsn: string): Promise<Player> {
player.main = parseStats(mainRes.data);
if (ironRes.status === 200) {
player.iron = parseStats(ironRes.data);
player.ironman = parseStats(ironRes.data);
if (hcRes.status === 200) {
player.mode = 'hc';
player.hc = parseStats(hcRes.data);
if (player.iron.skills.overall.xp !== player.hc.skills.overall.xp) {
player.mode = 'hardcore';
player.hardcore = parseStats(hcRes.data);
if (
player.ironman.skills.overall.xp !== player.hardcore.skills.overall.xp
) {
player.dead = true;
player.mode = 'iron';
player.mode = 'ironman';
}
if (player.main.skills.overall.xp !== player.iron.skills.overall.xp) {
if (
player.main.skills.overall.xp !== player.ironman.skills.overall.xp
) {
player.deironed = true;
player.mode = 'main';
}
} else if (ultRes.status === 200) {
player.mode = 'ult';
player.ult = parseStats(ultRes.data);
if (player.iron.skills.overall.xp !== player.ult.skills.overall.xp) {
player.mode = 'ultimate';
player.ultimate = parseStats(ultRes.data);
if (
player.ironman.skills.overall.xp !== player.ultimate.skills.overall.xp
) {
player.deulted = true;
player.mode = 'iron';
player.mode = 'ironman';
}
if (player.main.skills.overall.xp !== player.iron.skills.overall.xp) {
if (
player.main.skills.overall.xp !== player.ironman.skills.overall.xp
) {
player.deironed = true;
player.mode = 'main';
}
} else {
player.mode = 'iron';
if (player.main.skills.overall.xp !== player.iron.skills.overall.xp) {
player.mode = 'ironman';
if (
player.main.skills.overall.xp !== player.ironman.skills.overall.xp
) {
player.deironed = true;
player.mode = 'main';
}
@@ -112,7 +124,7 @@ export async function getStatsByGamemode(
if (response.status !== 200) {
throw Error('Player not found');
}
const stats: Stats = parseStats(response.data);
const stats = parseStats(response.data);
return stats;
}
@@ -132,21 +144,26 @@ export async function getSkillPage(
const url = getSkillPageURL(mode, skill, page);
const response = await axios(url);
const $ = cheerio.load(response.data);
const playersHTML = $('.personal-hiscores__row').toArray();
const dom = new JSDOM(response.data);
const playersHTML = dom.window.document.querySelectorAll(
'.personal-hiscores__row'
);
const players: PlayerSkillRow[] = playersHTML.map(row => {
const cells = row.children.filter(el => el.name === 'td');
const [rankEl, nameCell, levelEl, xpEl] = cells;
const [nameEl] = nameCell.children.filter(el => el.name === 'a');
const players: PlayerSkillRow[] = [];
playersHTML.forEach(row => {
const rankEl = row.querySelector('td');
const nameEl = row.querySelector('td a');
const levelEl = row.querySelector('td.left + td');
const xpEl = row.querySelector('td.left + td + td');
const isDead = !!row.querySelector('td img');
return {
rsn: rsnFromElement(nameEl),
players.push({
name: rsnFromElement(nameEl),
rank: numberFromElement(rankEl),
level: numberFromElement(levelEl),
xp: numberFromElement(xpEl),
dead: nameCell.children.length === 4,
};
dead: isDead,
});
});
return players;
@@ -167,20 +184,24 @@ export async function getActivityPage(
const url = getActivityPageURL(mode, activity, page);
const response = await axios(url);
const $ = cheerio.load(response.data);
const playersHTML = $('.personal-hiscores__row').toArray();
const dom = new JSDOM(response.data);
const playersHTML = dom.window.document.querySelectorAll(
'.personal-hiscores__row'
);
const players: PlayerActivityRow[] = playersHTML.map(row => {
const cells = row.children.filter(el => el.name === 'td');
const [rankEl, nameCell, scoreEl] = cells;
const [nameEl] = nameCell.children.filter(el => el.name === 'a');
const players: PlayerActivityRow[] = [];
playersHTML.forEach(row => {
const rankEl = row.querySelector('td');
const nameEl = row.querySelector('td a');
const scoreEl = row.querySelector('td.left + td');
const isDead = !!row.querySelector('td img');
return {
rsn: rsnFromElement(nameEl),
players.push({
name: rsnFromElement(nameEl),
rank: numberFromElement(rankEl),
score: numberFromElement(scoreEl),
dead: nameCell.children.length === 4,
};
dead: isDead,
});
});
return players;
@@ -198,10 +219,13 @@ export async function getRSNFormat(rsn: string): Promise<string> {
const url = getPlayerTableURL('main', rsn);
try {
const response = await axios(url);
const $ = cheerio.load(response.data);
const rawName = $('[style="color:#AA0022;"]')[1].children[0].data;
if (rawName) {
return rawName.replace(/\uFFFD/g, ' ');
const dom = new JSDOM(response.data);
const spans = dom.window.document.querySelectorAll(
'span[style="color:#AA0022;"]'
);
if (spans.length >= 2) {
const nameSpan = spans[1];
return rsnFromElement(nameSpan);
}
throw Error('Player not found');
} catch {
@@ -238,42 +262,43 @@ export function parseStats(csv: string): Stats {
return activity;
});
const [leaguePoints] = activityObjects.splice(0, 1);
const bhObjects = activityObjects.splice(0, BH_MODES.length);
const [lms] = activityObjects.splice(0, 1);
const clueObjects = activityObjects.splice(0, CLUES.length);
const [lastManStanding] = activityObjects.splice(0, 1);
const bossObjects = activityObjects.splice(0, BOSSES.length);
const skills: Skills = skillObjects.reduce<Skills>(
(prev, curr, index) => {
const newSkills = { ...prev };
newSkills[SKILLS[index]] = curr;
return newSkills;
},
{} as Skills
);
const skills: Skills = skillObjects.reduce<Skills>((prev, curr, index) => {
const newSkills = { ...prev };
newSkills[SKILLS[index]] = curr;
return newSkills;
}, {} as Skills);
const bh: BH = bhObjects.reduce<BH>(
(prev, curr, index) => {
const newBH = { ...prev };
newBH[BH_MODES[index]] = curr;
return newBH;
},
{} as BH
);
const bountyHunter: BH = bhObjects.reduce<BH>((prev, curr, index) => {
const newBH = { ...prev };
newBH[BH_MODES[index]] = curr;
return newBH;
}, {} as BH);
const clues: Clues = clueObjects.reduce<Clues>(
(prev, curr, index) => {
const newClues = { ...prev };
newClues[CLUES[index]] = curr;
return newClues;
},
{} as Clues
);
const clues: Clues = clueObjects.reduce<Clues>((prev, curr, index) => {
const newClues = { ...prev };
newClues[CLUES[index]] = curr;
return newClues;
}, {} as Clues);
const bosses: Bosses = bossObjects.reduce<Bosses>((prev, curr, index) => {
const newBosses = { ...prev };
newBosses[BOSSES[index]] = curr;
return newBosses;
}, {} as Bosses);
const stats: Stats = {
skills,
bh,
lms,
leaguePoints,
bountyHunter,
lastManStanding,
clues,
bosses,
};
return stats;

View File

@@ -2,5 +2,6 @@ import * as hiscores from './hiscores';
export * from './hiscores';
export * from './types';
export * from './utils';
export default hiscores;

View File

@@ -1,4 +1,11 @@
export type Gamemode = 'main' | 'iron' | 'hc' | 'ult' | 'dmm' | 'sdmm' | 'dmmt';
export type Gamemode =
| 'main'
| 'ironman'
| 'ultimate'
| 'hardcore'
| 'deadman'
| 'seasonal'
| 'tournament';
export interface Skill {
rank: number;
@@ -54,29 +61,80 @@ export type BHType = 'rogue' | 'hunter';
export type BH = { [Type in BHType]: Activity };
export type Boss =
| 'abyssalSire'
| 'alchemicalHydra'
| 'barrows'
| 'bryophyta'
| 'callisto'
| 'cerberus'
| 'chambersOfXeric'
| 'chambersOfXericChallengeMode'
| 'chaosElemental'
| 'chaosFanatic'
| 'commanderZilyana'
| 'corporealBeast'
| 'crazyArchaeologist'
| 'dagannothPrime'
| 'dagannothRex'
| 'dagannothSupreme'
| 'derangedArchaeologist'
| 'generalGraardor'
| 'giantMole'
| 'grotesqueGuardians'
| 'hespori'
| 'kalphiteQueen'
| 'kingBlackDragon'
| 'kraken'
| 'kreeArra'
| 'krilTsutsaroth'
| 'mimic'
| 'nightmare'
| 'obor'
| 'sarachnis'
| 'scorpia'
| 'skotizo'
| 'gauntlet'
| 'corruptedGauntlet'
| 'theatreOfBlood'
| 'thermonuclearSmokeDevil'
| 'tzKalZuk'
| 'tzTokJad'
| 'venenatis'
| 'vetion'
| 'vorkath'
| 'wintertodt'
| 'zalcano'
| 'zulrah';
export type Bosses = { [Type in Boss]: Activity };
export type ActivityName =
| 'hunterbh'
| 'roguebh'
| 'lms'
| 'allclues'
| 'beginnerclues'
| 'easyclues'
| 'mediumclues'
| 'hardclues'
| 'eliteclues'
| 'masterclues';
| 'leaguePoints'
| 'hunterBH'
| 'rogueBH'
| 'lastManStanding'
| 'allClues'
| 'beginnerClues'
| 'easyClues'
| 'mediumClues'
| 'hardClues'
| 'eliteClues'
| 'masterClues'
| Boss;
export interface Stats {
skills: Skills;
clues: Clues;
bh: BH;
lms: Activity;
leaguePoints: Activity;
bountyHunter: BH;
lastManStanding: Activity;
bosses: Bosses;
}
export type Modes = { [M in Gamemode]?: Stats };
export interface Player extends Modes {
rsn: string;
name: string;
mode: Gamemode;
dead: boolean;
deulted: boolean;
@@ -84,11 +142,11 @@ export interface Player extends Modes {
}
export interface PlayerSkillRow extends Skill {
rsn: string;
name: string;
dead: boolean;
}
export interface PlayerActivityRow extends Activity {
rsn: string;
name: string;
dead: boolean;
}

View File

@@ -1,16 +1,28 @@
import { SkillName, ClueType, BHType, Gamemode } from '../types';
import {
BHType,
Boss,
ClueType,
Gamemode,
SkillName,
ActivityName,
} from '../types';
export const BASE_URL = 'http://services.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 SCORES_URL = 'overall.ws?';
export const GAMEMODE_URL = {
dmm: '_deadman/',
dmmt: '_tournament/',
hc: '_hardcore_ironman/',
iron: '_ironman/',
main: '/',
sdmm: '_seasonal/',
ult: '_ultimate/',
export type GamemodeUrl = {
[key in Gamemode]: string;
};
export const GAMEMODE_URL: GamemodeUrl = {
main: `${BASE_URL}/`,
ironman: `${BASE_URL}_ironman/`,
hardcore: `${BASE_URL}_hardcore_ironman/`,
ultimate: `${BASE_URL}_ultimate/`,
deadman: `${BASE_URL}_deadman/`,
seasonal: `${BASE_URL}_seasonal/`,
tournament: `${BASE_URL}_tournament/`,
};
export const SKILLS: SkillName[] = [
'overall',
@@ -48,24 +60,180 @@ export const CLUES: ClueType[] = [
'master',
];
export const BH_MODES: BHType[] = ['rogue', 'hunter'];
export const ACTIVITIES = [
'hunterbh',
'roguebh',
'lms',
'allclues',
'beginnerclues',
'easyclues',
'mediumclues',
'hardclues',
'eliteclues',
'masterclues',
];
export const GAMEMODES: Gamemode[] = [
'main',
'iron',
'hc',
'ult',
'dmm',
'sdmm',
'dmmt',
'ironman',
'hardcore',
'ultimate',
'deadman',
'seasonal',
'tournament',
];
export const BOSSES: Boss[] = [
'abyssalSire',
'alchemicalHydra',
'barrows',
'bryophyta',
'callisto',
'cerberus',
'chambersOfXeric',
'chambersOfXericChallengeMode',
'chaosElemental',
'chaosFanatic',
'commanderZilyana',
'corporealBeast',
'crazyArchaeologist',
'dagannothPrime',
'dagannothRex',
'dagannothSupreme',
'derangedArchaeologist',
'generalGraardor',
'giantMole',
'grotesqueGuardians',
'hespori',
'kalphiteQueen',
'kingBlackDragon',
'kraken',
'kreeArra',
'krilTsutsaroth',
'mimic',
'nightmare',
'obor',
'sarachnis',
'scorpia',
'skotizo',
'gauntlet',
'corruptedGauntlet',
'theatreOfBlood',
'thermonuclearSmokeDevil',
'tzKalZuk',
'tzTokJad',
'venenatis',
'vetion',
'vorkath',
'wintertodt',
'zalcano',
'zulrah',
];
export const ACTIVITIES: ActivityName[] = [
'leaguePoints',
'hunterBH',
'rogueBH',
'allClues',
'beginnerClues',
'easyClues',
'mediumClues',
'hardClues',
'eliteClues',
'masterClues',
'lastManStanding',
...BOSSES,
];
export type FormattedBossNames = {
[key in Boss]: string;
};
export const FORMATTED_BOSS_NAMES: FormattedBossNames = {
abyssalSire: 'Abyssal Sire',
alchemicalHydra: 'Alchemical Hydra',
barrows: 'Barrows Chests',
bryophyta: 'Bryophyta',
callisto: 'Callisto',
cerberus: 'Cerberus',
chambersOfXeric: 'Chambers of Xeric',
chambersOfXericChallengeMode: 'Chambers of Xeric: Challenge Mode',
chaosElemental: 'Chaos Elemental',
chaosFanatic: 'Chaos Fanatic',
commanderZilyana: 'Commander Zilyana',
corporealBeast: 'Corporeal Beast',
crazyArchaeologist: 'Crazy Archaeologist',
dagannothPrime: 'Dagannoth Prime',
dagannothRex: 'Dagannoth Rex',
dagannothSupreme: 'Dagannoth Supreme',
derangedArchaeologist: 'Deranged Archaeologist',
generalGraardor: 'General Graardor',
giantMole: 'Giant Mole',
grotesqueGuardians: 'Grotesque Guardians',
hespori: 'Hespori',
kalphiteQueen: 'Kalphite Queen',
kingBlackDragon: 'King Black Dragon',
kraken: 'Kraken',
kreeArra: "Kree'Arra",
krilTsutsaroth: "K'ril Tsutsaroth",
mimic: 'Mimic',
nightmare: 'The Nightmare of Ashihama',
obor: 'Obor',
sarachnis: 'Sarachnis',
scorpia: 'Scorpia',
skotizo: 'Skotizo',
gauntlet: 'The Gauntlet',
corruptedGauntlet: 'The Corrupted Gauntlet',
theatreOfBlood: 'Theatre of Blood',
thermonuclearSmokeDevil: 'Thermonuclear Smoke Devil',
tzKalZuk: 'TzKal-Zuk',
tzTokJad: 'TzTok-Jad',
venenatis: 'Venenatis',
vetion: "Vet'ion",
vorkath: 'Vorkath',
wintertodt: 'Wintertodt',
zalcano: 'Zalcano',
zulrah: 'Zulrah',
};
export type FormattedSkillNames = {
[key in SkillName]: string;
};
export const FORMATTED_SKILL_NAMES: FormattedSkillNames = {
overall: 'Overall',
attack: 'Attack',
defence: 'Defence',
strength: 'Strength',
hitpoints: 'Hitpoints',
ranged: 'Ranged',
prayer: 'Prayer',
magic: 'Magic',
cooking: 'Cooking',
woodcutting: 'Woodcutting',
fletching: 'Fletching',
fishing: 'Fishing',
firemaking: 'Firemaking',
crafting: 'Crafting',
smithing: 'Smithing',
mining: 'Mining',
herblore: 'Herblore',
agility: 'Agility',
thieving: 'Thieving',
slayer: 'Slayer',
farming: 'Farming',
runecraft: 'Runecraft',
hunter: 'Hunter',
construction: 'Construction',
};
export type FormattedClueNames = {
[key in ClueType]: string;
};
export const FORMATTED_CLUE_NAMES: FormattedClueNames = {
all: 'Clue Scrolls (all)',
beginner: 'Clue Scrolls (beginner)',
easy: 'Clue Scrolls (easy)',
medium: 'Clue Scrolls (medium)',
hard: 'Clue Scrolls (hard)',
elite: 'Clue Scrolls (elite)',
master: 'Clue Scrolls (master)',
};
export type FormattedBHNames = {
[key in BHType]: string;
};
export const FORMATTED_BH_NAMES: FormattedBHNames = {
rogue: 'Bounty Hunter - Rogue',
hunter: 'Bounty Hunter - Hunter',
};
export const FORMATTED_LMS = 'Last Man Standing';
export const FORMATTED_LEAGUE_POINTS = 'League Points';

View File

@@ -1,6 +1,5 @@
import { Gamemode, SkillName, ActivityName } from '../types';
import {
BASE_URL,
GAMEMODE_URL,
STATS_URL,
SCORES_URL,
@@ -9,19 +8,19 @@ import {
} from './constants';
export const getStatsURL = (gamemode: Gamemode, rsn: string) =>
`${BASE_URL}${GAMEMODE_URL[gamemode]}${STATS_URL}${encodeURIComponent(rsn)}`;
`${GAMEMODE_URL[gamemode]}${STATS_URL}${encodeURIComponent(rsn)}`;
export const getPlayerTableURL = (gamemode: Gamemode, rsn: string) =>
`${BASE_URL}${
GAMEMODE_URL[gamemode]
}${SCORES_URL}table=0&user=${encodeURIComponent(rsn)}`;
`${GAMEMODE_URL[gamemode]}${SCORES_URL}table=0&user=${encodeURIComponent(
rsn
)}`;
export const getSkillPageURL = (
gamemode: Gamemode,
skill: SkillName,
page: number
) =>
`${BASE_URL}${GAMEMODE_URL[gamemode]}${SCORES_URL}table=${SKILLS.indexOf(
`${GAMEMODE_URL[gamemode]}${SCORES_URL}table=${SKILLS.indexOf(
skill
)}&page=${page}`;
@@ -30,19 +29,19 @@ export const getActivityPageURL = (
activity: ActivityName,
page: number
) =>
`${BASE_URL}${
`${
GAMEMODE_URL[gamemode]
}${SCORES_URL}category_type=1&table=${ACTIVITIES.indexOf(
activity
)}&page=${page}`;
export const numberFromElement = (el: CheerioElement) => {
const innerText = el.firstChild.data;
const number = innerText ? innerText.replace(/[\n|,]/g, '') : '-1';
export const numberFromElement = (el: Element | null) => {
const { innerHTML } = el || {};
const number = innerHTML?.replace(/[\n|,]/g, '') ?? '-1';
return parseInt(number, 10);
};
export const rsnFromElement = (el: CheerioElement) => {
const innerText = el.firstChild.data;
return innerText ? innerText.replace(/\uFFFD/g, ' ') : '';
export const rsnFromElement = (el: Element | null) => {
const { innerHTML } = el || {};
return innerHTML?.replace(/\uFFFD/g, ' ') || '';
};

3310
yarn.lock

File diff suppressed because it is too large Load Diff