Compare commits

..

56 Commits

Author SHA1 Message Date
maxswa
d097567a3c v2.3.2 2021-02-13 11:22:26 -05:00
maxswa
be5c093cf7 Downgrade np. 2021-02-13 11:13:52 -05:00
maxswa
db72f0c57d Bump np. 2021-02-13 11:08:57 -05:00
Max Swartwout
bd2ea21980 Merge pull request #26 from maxswa/bh-order-fix
Fixes #25 Flipped BH modes.
2021-02-13 11:07:27 -05:00
maxswa
d67837c161 Fix flipped BH modes. 2021-02-13 10:50:58 -05:00
Max Swartwout
4aa6ad2752 Add github action. 2021-02-13 10:48:31 -05:00
maxswa
2f788fabf6 v2.3.1 2021-01-12 19:56:17 -05:00
maxswa
0f5aea795c Upgrade dependencies. 2021-01-12 19:55:36 -05:00
Max Swartwout
7acb84e310 Merge pull request #21 from maxswa/dependabot/npm_and_yarn/dot-prop-4.2.1
Bump dot-prop from 4.2.0 to 4.2.1
2021-01-12 19:48:41 -05:00
Max Swartwout
9619bd2a2b Merge pull request #23 from molo-pl/master
Fixes #22 adding User-Agent request header to bypass Incapsula
2021-01-12 19:48:29 -05:00
molo-pl
55abd9f800 Fixes #22 adding User-Agent request header - post review changes 2021-01-12 23:29:25 +01:00
molo-pl
cf8a4cc26e Fixes #22 adding User-Agent request header to bypass Incapsula protection of OSRS hiscore pages 2021-01-12 16:05:47 +01:00
maxswa
0f68c1995c v2.3.0 2021-01-10 16:20:54 -05:00
dependabot[bot]
ae47df31f1 Bump dot-prop from 4.2.0 to 4.2.1
Bumps [dot-prop](https://github.com/sindresorhus/dot-prop) from 4.2.0 to 4.2.1.
- [Release notes](https://github.com/sindresorhus/dot-prop/releases)
- [Commits](https://github.com/sindresorhus/dot-prop/compare/v4.2.0...v4.2.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-10 21:00:31 +00:00
Max Swartwout
7a3f3a0800 Merge pull request #17 from maxswa/dependabot/npm_and_yarn/ini-1.3.7
Bump ini from 1.3.5 to 1.3.7
2021-01-10 16:00:05 -05:00
Max Swartwout
b16e8ace6e Merge pull request #18 from maxswa/dependabot/npm_and_yarn/axios-0.21.1
Bump axios from 0.19.2 to 0.21.1
2021-01-10 15:59:55 -05:00
Max Swartwout
09f7805fd4 Merge pull request #20 from molo-pl/master
Fixes #19 adding support for Soul Wars Zeal
2021-01-10 15:59:43 -05:00
molo-pl
939f4d2721 Fixes #19 adding support for Soul Wars Zeal 2021-01-06 17:21:46 +01:00
dependabot[bot]
a4577ffb2c Bump axios from 0.19.2 to 0.21.1
Bumps [axios](https://github.com/axios/axios) from 0.19.2 to 0.21.1.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v0.21.1/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.19.2...v0.21.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-05 09:06:49 +00:00
dependabot[bot]
19ba7e2916 Bump ini from 1.3.5 to 1.3.7
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.7.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.7)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-11 09:18:50 +00:00
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
maxswa
58e8eaad59 v1.2.2 2019-07-15 09:48:13 -04:00
Max Swartwout
6b6d561a1f Merge pull request #2 from maxswa/dependabot/npm_and_yarn/lodash-4.17.14
Bump lodash from 4.17.11 to 4.17.14
2019-07-15 09:44:30 -04:00
dependabot[bot]
26aefe95a0 Bump lodash from 4.17.11 to 4.17.14
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.11 to 4.17.14.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.11...4.17.14)

Signed-off-by: dependabot[bot] <support@github.com>
2019-07-15 13:23:37 +00:00
maxswa
880679b9e6 v1.2.1 2019-06-26 09:53:55 -04:00
maxswa
f679e2bde8 Remove rsn string literals from test 2019-06-26 09:44:59 -04:00
maxswa
06a176873b Add CORS disclaimer 2019-06-26 09:37:28 -04:00
maxswa
553f4f5c36 Add test for getStatsByGamemode 2019-06-18 10:03:26 -04:00
maxswa
e95efb5cdf Remove renamed import 2019-06-17 16:42:25 -04:00
maxswa
68eb807657 Separate getStats into getStatsByGamemode 2019-06-17 16:32:51 -04:00
maxswa
93d6961a4c Remove Modes in favor of Gamemodes 2019-06-17 16:30:44 -04:00
Max
fbce22fd07 Fix link to types in readme 2019-06-15 15:47:31 -04:00
12 changed files with 3534 additions and 2921 deletions

18
.github/workflows/main.yml vendored Normal file
View File

@@ -0,0 +1,18 @@
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: borales/actions-yarn@v2.0.0
with:
cmd: install
- uses: borales/actions-yarn@v2.0.0
with:
cmd: build
- uses: borales/actions-yarn@v2.0.0
with:
cmd: test

137
README.md
View File

@@ -2,7 +2,7 @@
[![npm](https://img.shields.io/npm/v/osrs-json-hiscores.svg?style=flat-square)](https://www.npmjs.com/package/osrs-json-hiscores) [![npm](https://img.shields.io/npm/v/osrs-json-hiscores.svg?style=flat-square)](https://www.npmjs.com/package/osrs-json-hiscores)
[![downloads](https://img.shields.io/npm/dm/osrs-json-hiscores.svg?style=flat-square)](https://npm-stat.com/charts.html?package=osrs-json-hiscores) [![downloads](https://img.shields.io/npm/dm/osrs-json-hiscores.svg?style=flat-square)](https://npm-stat.com/charts.html?package=osrs-json-hiscores)
[![types](https://img.shields.io/npm/types/osrs-json-hiscores.svg?style=flat-square)](https://github.com/maxswa/osrs-json-hiscores/src/types.ts) [![types](https://img.shields.io/npm/types/osrs-json-hiscores.svg?style=flat-square)](https://github.com/maxswa/osrs-json-hiscores/blob/master/src/types.ts)
**The Oldschool Runescape API wrapper that does more!** **The Oldschool Runescape API wrapper that does more!**
@@ -15,6 +15,14 @@ Additional functions are provided that screen-scrape the OSRS leaderboards and r
`osrs-json-hiscores` has TypeScript support, with full definitions for all functions and custom data types. `osrs-json-hiscores` has TypeScript support, with full definitions for all functions and custom data types.
---
### Disclaimer
Jagex does not provide `Access-Control-Allow-Origin` headers in their responses. This means that [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) will block all browser requests to their hiscores API. In order to get around this, osrs-json-hiscores should be installed on the server side and exposed to the front end via a simple API. Here is an example of this in use: [codesandbox.io/s/osrs-json-hiscores-demo](https://codesandbox.io/s/osrs-json-hiscores-demo-qz656)
---
## Installation ## Installation
With npm: With npm:
@@ -41,7 +49,7 @@ Once you import it you can call the functions asynchronously:
```javascript ```javascript
hiscores hiscores
.getStats('Lynx Titan', 'full') .getStats('Lynx Titan')
.then(res => console.log(res)) .then(res => console.log(res))
.catch(err => console.error(err)); .catch(err => console.error(err));
``` ```
@@ -57,19 +65,20 @@ const stats = await hiscores.getStats('Lynx Titan');
const topPage = await getSkillPage('overall'); const topPage = await getSkillPage('overall');
``` ```
`getStats` will return a `full` player object with game mode by default, but it will also accept any of the following game modes: `getStats` will return a full player object with gamemode.
`getStatsByGameMode` will return a stats object and accepts a gamemode parameter:
| Game mode | Param | | Game mode | Param |
| ---------------- | :----: | | ---------------- | :----------: |
| Regular | `main` | | Regular | `main` |
| Ironman | `iron` | | Ironman | `ironman` |
| Hardcore Ironman | `hc` | | Hardcore Ironman | `hardcore` |
| Ultimate Ironman | `ult` | | Ultimate Ironman | `ultimate` |
| Deadman Mode | `dmm` | | Deadman Mode | `deadman` |
| Seasonal Deadman | `sdmm` | | Tournament | `tournament` |
| DMM Tournament | `dmmt` | | 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 ```javascript
hiscores hiscores
@@ -78,27 +87,83 @@ hiscores
.catch(err => console.error(err)); .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 ### Clue Scrolls
| Type | Param | | Type | Param |
| -------- | :-------------: | | -------- | :-------------: |
| All | `allclues` | | All | `allClues` |
| Beginner | `beginnerclues` | | Beginner | `beginnerClues` |
| Easy | `easyclues` | | Easy | `easyClues` |
| Medium | `mediumclues` | | Medium | `mediumClues` |
| Hard | `hardclues` | | Hard | `hardClues` |
| Elite | `eliteclues` | | Elite | `eliteClues` |
| Master | `masterclues` | | Master | `masterClues` |
### Minigames ### Minigames
| Minigame | Param | | Minigame | Param |
| ---------------------- | :--------: | | ---------------------- | :---------------: |
| Bounty Hunter (Rogue) | `roguebh` | | Bounty Hunter (Rogue) | `rogueBH` |
| Bounty Hunter (Hunter) | `hunterbh` | | Bounty Hunter (Hunter) | `hunterBH` |
| Last Man Standing | `lms` | | Last Man Standing | `lastManStanding` |
| Soul Wars Zeal | `soulWarsZeal` |
### 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 ## What you'll get
@@ -106,7 +171,7 @@ Activities consist of all levels of clue scrolls as well as minigames:
```javascript ```javascript
{ {
rsn: 'Lynx Titan', name: 'Lynx Titan',
mode: 'main', mode: 'main',
dead: false, dead: false,
deulted: false, deulted: false,
@@ -119,8 +184,11 @@ Activities consist of all levels of clue scrolls as well as minigames:
// ... // ...
}, },
clues: {}, clues: {},
bh: {}, leaguePoints: {},
lms: {} bountyHunter: {},
lastManStanding: {},
soulWarsZeal: {},
bosses: {}
} }
} }
``` ```
@@ -129,9 +197,18 @@ Activities consist of all levels of clue scrolls as well as minigames:
```javascript ```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,222 +1,406 @@
import { parseStats, getRSNFormat, getSkillPage, getStats } from '../src/index'; import {
import { PlayerSkillRow, Player } from '../src/types'; parseStats,
import axios, { AxiosError } from 'axios'; getSkillPage,
getStats,
getStatsByGamemode,
getRSNFormat,
Stats,
} from '../src/index';
test('Parse CSV to json', () => { test('Parse CSV to json', () => {
const csv = `40258,2063,218035714 const csv = `246,2277,1338203419
20554, 99, 21102621 615,99,77438259
39059, 99, 15364425 428,99,69176307
14245, 99, 26556827 425,99,115218641
19819, 99, 33511407 138,99,181425111
27857, 99, 25774115 160,99,169725807
44278, 91, 6081159 97,99,50666171
40110, 99, 15128024 144,99,93155913
178948, 90, 5347474 2108,99,53198880
175463, 81, 2355494 5750,99,19589494
138677, 90, 5356303 295,99,76386488
77587, 91, 5904710 1304,99,32252994
158478, 85, 3570485 847,99,54325931
93958, 83, 2684426 534,99,26379932
39179, 88, 4425107 7213,99,13246799
138406, 77, 1591377 2475,99,18161285
33399, 90, 5866307 1837,99,14746134
1794, 99, 15057674 668,99,23961670
45551, 91, 6363261 3841,99,17970837
121165, 90, 5748493 265,99,56230978
89460, 88, 4624078 821,99,62123353
53099, 80, 2008229 169,99,43127930
169127, 73, 1067670 810,99,37688883
115543, 82, 2546048 92,99,32005622
-1, -1 23423,478
-1, -1 99831,23
32875, 500 89912,37
24817, 476 32,12148
212728, 1 3105,76
94827, 20 1997,505
59099, 74 127,4259
24642, 231 361,915
5206, 99 392,250
6293, 51`; 1,6143
4814,898
37,225
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: { skills: {
overall: { rank: 40258, level: 2063, xp: 218035714 }, overall: { rank: 246, level: 2277, xp: 1338203419 },
attack: { rank: 20554, level: 99, xp: 21102621 }, attack: { rank: 615, level: 99, xp: 77438259 },
defence: { rank: 39059, level: 99, xp: 15364425 }, defence: { rank: 428, level: 99, xp: 69176307 },
strength: { rank: 14245, level: 99, xp: 26556827 }, strength: { rank: 425, level: 99, xp: 115218641 },
hitpoints: { rank: 19819, level: 99, xp: 33511407 }, hitpoints: { rank: 138, level: 99, xp: 181425111 },
ranged: { rank: 27857, level: 99, xp: 25774115 }, ranged: { rank: 160, level: 99, xp: 169725807 },
prayer: { rank: 44278, level: 91, xp: 6081159 }, prayer: { rank: 97, level: 99, xp: 50666171 },
magic: { rank: 40110, level: 99, xp: 15128024 }, magic: { rank: 144, level: 99, xp: 93155913 },
cooking: { rank: 178948, level: 90, xp: 5347474 }, cooking: { rank: 2108, level: 99, xp: 53198880 },
woodcutting: { rank: 175463, level: 81, xp: 2355494 }, woodcutting: { rank: 5750, level: 99, xp: 19589494 },
fletching: { rank: 138677, level: 90, xp: 5356303 }, fletching: { rank: 295, level: 99, xp: 76386488 },
fishing: { rank: 77587, level: 91, xp: 5904710 }, fishing: { rank: 1304, level: 99, xp: 32252994 },
firemaking: { rank: 158478, level: 85, xp: 3570485 }, firemaking: { rank: 847, level: 99, xp: 54325931 },
crafting: { rank: 93958, level: 83, xp: 2684426 }, crafting: { rank: 534, level: 99, xp: 26379932 },
smithing: { rank: 39179, level: 88, xp: 4425107 }, smithing: { rank: 7213, level: 99, xp: 13246799 },
mining: { rank: 138406, level: 77, xp: 1591377 }, mining: { rank: 2475, level: 99, xp: 18161285 },
herblore: { rank: 33399, level: 90, xp: 5866307 }, herblore: { rank: 1837, level: 99, xp: 14746134 },
agility: { rank: 1794, level: 99, xp: 15057674 }, agility: { rank: 668, level: 99, xp: 23961670 },
thieving: { rank: 45551, level: 91, xp: 6363261 }, thieving: { rank: 3841, level: 99, xp: 17970837 },
slayer: { rank: 121165, level: 90, xp: 5748493 }, slayer: { rank: 265, level: 99, xp: 56230978 },
farming: { rank: 89460, level: 88, xp: 4624078 }, farming: { rank: 821, level: 99, xp: 62123353 },
runecraft: { rank: 53099, level: 80, xp: 2008229 }, runecraft: { rank: 169, level: 99, xp: 43127930 },
hunter: { rank: 169127, level: 73, xp: 1067670 }, hunter: { rank: 810, level: 99, xp: 37688883 },
construction: { rank: 115543, level: 82, xp: 2546048 }, construction: { rank: 92, level: 99, xp: 32005622 },
}, },
bh: { leaguePoints: { rank: 23423, score: 478 },
rogue: { rank: -1, score: -1 }, bountyHunter: {
hunter: { rank: -1, score: -1 }, hunter: { rank: 99831, score: 23 },
rogue: { rank: 89912, score: 37 },
}, },
lms: { rank: 32875, score: 500 }, lastManStanding: { rank: 4814, score: 898 },
soulWarsZeal: { rank: 37, score: 225 },
clues: { clues: {
all: { rank: 24817, score: 476 }, all: { rank: 32, score: 12148 },
beginner: { rank: 212728, score: 1 }, beginner: { rank: 3105, score: 76 },
easy: { rank: 94827, score: 20 }, easy: { rank: 1997, score: 505 },
medium: { rank: 59099, score: 74 }, medium: { rank: 127, score: 4259 },
hard: { rank: 24642, score: 231 }, hard: { rank: 361, score: 915 },
elite: { rank: 5206, score: 99 }, elite: { rank: 392, score: 250 },
master: { rank: 6293, score: 51 }, 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 => { test('Get name format', async () => {
const callback = (data: PlayerSkillRow[]) => { jest.setTimeout(30000);
expect(data).toStrictEqual([ const data = await getRSNFormat('lYnX tiTaN');
{ rsn: 'Heur', rank: 1, level: 99, xp: 200000000, dead: false }, expect(data).toBe('Lynx Titan');
{
rsn: 'Unohdettu2',
rank: 2,
level: 99,
xp: 200000000,
dead: false,
},
{ rsn: 'Drakon', rank: 3, level: 99, xp: 200000000, dead: false },
{
rsn: 'Ame Umehara',
rank: 4,
level: 99,
xp: 200000000,
dead: false,
},
{ rsn: 'Jakee', rank: 5, level: 99, xp: 200000000, dead: false },
{ rsn: 'Hitsuji', rank: 6, level: 99, xp: 200000000, dead: false },
{ rsn: 'Howson', rank: 7, level: 99, xp: 200000000, dead: false },
{ rsn: 'Dr PFAFF', rank: 8, level: 99, xp: 200000000, dead: false },
{
rsn: 'Malt Lickeys',
rank: 9,
level: 99,
xp: 200000000,
dead: false,
},
{ rsn: 'Burned', rank: 10, level: 99, xp: 200000000, dead: false },
{
rsn: 'Blue Limes',
rank: 11,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: 'Mini Finbarr',
rank: 12,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: 'Unohdettu3',
rank: 13,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: 'Eslihero',
rank: 14,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: 'Lynx Titan',
rank: 15,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: 'AndrewWigins',
rank: 16,
level: 99,
xp: 200000000,
dead: false,
},
{ rsn: 'iMelee', rank: 17, level: 99, xp: 200000000, dead: false },
{
rsn: 'Portuguese',
rank: 18,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: 'MarkoOSRS',
rank: 19,
level: 99,
xp: 200000000,
dead: false,
},
{ rsn: 'Cairo', rank: 20, level: 99, xp: 200000000, dead: false },
{
rsn: 'Hey Jase',
rank: 21,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: 'Sleighur',
rank: 22,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: 'KMSat200mALL',
rank: 23,
level: 99,
xp: 200000000,
dead: false,
},
{ rsn: 'Yumemi', rank: 24, level: 99, xp: 200000000, dead: false },
{ rsn: 'Fiiggy', rank: 25, level: 99, xp: 200000000, dead: false },
]);
done();
};
getSkillPage('attack').then(callback);
}); });
test('Get non-existant player', async done => { test('Get attack top page', async () => {
const callback = (err: AxiosError) => { 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) { if (err.response) {
expect(err.response.status).toBe(404); expect(err.response.status).toBe(404);
} }
done(); });
}; });
getStats('fishy').catch(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" "^.+\\.(t|j)sx?$": "ts-jest"
}, },
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", "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", "name": "osrs-json-hiscores",
"version": "1.2.0", "version": "2.3.2",
"description": "The Oldschool Runescape API wrapper that does more!", "description": "The Oldschool Runescape API wrapper that does more!",
"main": "lib/index.js", "main": "lib/index.js",
"types": "lib/index.d.ts", "types": "lib/index.d.ts",
@@ -10,6 +10,7 @@
"scripts": { "scripts": {
"dev": "watch 'yarn run build' src", "dev": "watch 'yarn run build' src",
"build": "tsc", "build": "tsc",
"format": "prettier --write \"src/**/*.ts\"",
"test": "jest --config jestconfig.json", "test": "jest --config jestconfig.json",
"prepublish": "yarn run build", "prepublish": "yarn run build",
"release": "np" "release": "np"
@@ -35,15 +36,17 @@
}, },
"homepage": "https://github.com/maxswa/osrs-json-hiscores#readme", "homepage": "https://github.com/maxswa/osrs-json-hiscores#readme",
"dependencies": { "dependencies": {
"axios": "^0.19.0", "axios": "^0.21.1",
"cheerio": "^1.0.0-rc.3" "jsdom": "^16.3.0",
"useragent-generator": "^1.1.0"
}, },
"devDependencies": { "devDependencies": {
"@types/cheerio": "^0.22.11", "@types/jest": "^26.0.20",
"@types/jest": "^24.0.14", "@types/jsdom": "^16.2.3",
"jest": "^24.8.0", "jest": "^26.6.3",
"np": "^5.0.3", "np": "6.5.0",
"ts-jest": "^24.0.2", "prettier": "^1.19.1",
"ts-jest": "^26.4.4",
"tslint": "^5.17.0", "tslint": "^5.17.0",
"tslint-config-airbnb": "^5.11.1", "tslint-config-airbnb": "^5.11.1",
"tslint-config-prettier": "^1.18.0", "tslint-config-prettier": "^1.18.0",

51
src/@types/useragent-generator.d.ts vendored Normal file
View File

@@ -0,0 +1,51 @@
declare module 'useragent-generator' {
/********************
* Google Chrome *
/*******************/
export function chrome(opt: number | string | { version: string, os?: string }): string;
export namespace chrome {
function androidPhone(opt: number | string | { version: string, androidVersion?: string, device?: string }): string;
function androidTablet(opt: number | string | { version: string, androidVersion?: string, device?: string })
: string;
function androidWebview(opt: number | string | { androidVersion: string, chromeVersion?: string, device?: string })
: string;
function chromecast(opt: number | string | { version: string }): string;
function iOS(opt: number | string | { iOSVersion: string, chromeVersion?: string, device?: string }): string;
}
export function chromium(opt: number | string | { version: string, os?: string }): string;
/***************
* Firefox *
/*************/
export function firefox(opt: number | string | { version: string, os?: string }): string;
export namespace firefox {
function androidPhone(opt: number | string | { version: string, androidVersion?: string, device?: string }): string;
function androidTablet(opt: number | string | { version: string, androidVersion?: string, device?: string })
: string;
function iOS(opt: number | string | { iOSVersion: string, device?: string }): string;
}
/**************
* Safari *
/************/
export function safari(opt: number | string | { version: string, os?: string }): string;
export namespace safari {
function iOS(opt: number | string | { iOSVersion: string, safariVersion?: string, device?: string }): string;
function iOSWebview(opt: number | string | { iOSVersion: string, safariVersion?: string, device?: string }): string;
}
/***********************
* Internet Explorer *
/*********************/
export function ie(opt: number | string | { version: string, os?: string }): string;
export namespace ie {
function windowsPhone(opt: number | string | { version: string, device?: string }): string;
}
/**********************
* Microsoft Edge *
/********************/
export function edge(opt: number | string | { version: string, chromeVersion?: string, os?: string }): string;
/************************
* Search Engine Bots *
/**********************/
export function googleBot(opt?: number | string | { version?: string }): string;
export function bingBot(opt?: number | string | { version?: string }): string;
export function yahooBot(): string;
}

View File

@@ -1,26 +1,23 @@
import axios from 'axios';
import * as cheerio from 'cheerio';
import { import {
Player, Player,
Mode,
Activity, Activity,
Skill, Skill,
Stats, Stats,
Skills, Skills,
BH as BHStats, BH,
Clues, Clues,
Gamemode, Gamemode,
SkillName, SkillName,
PlayerSkillRow, PlayerSkillRow,
ActivityName, ActivityName,
PlayerActivityRow, PlayerActivityRow,
Bosses,
} from './types'; } from './types';
import { import {
getStatsURL, getStatsURL,
SKILLS, SKILLS,
BH_MODES, BH_MODES,
CLUES, CLUES,
MODES,
getPlayerTableURL, getPlayerTableURL,
getSkillPageURL, getSkillPageURL,
GAMEMODES, GAMEMODES,
@@ -28,94 +25,108 @@ import {
numberFromElement, numberFromElement,
rsnFromElement, rsnFromElement,
getActivityPageURL, getActivityPageURL,
httpGet,
BOSSES,
} from './utils'; } from './utils';
import { JSDOM } from 'jsdom';
export async function getStats( export async function getStats(rsn: string): Promise<Player> {
rsn: string,
mode: Mode = 'full'
): Promise<Player> {
if (typeof rsn !== 'string') { if (typeof rsn !== 'string') {
throw Error('RSN must be a string'); throw Error('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 Error('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 Error('RSN must be between 1 and 12 characters');
} else if (!MODES.includes(mode)) {
throw Error('Invalid game mode');
} }
if (mode === 'full') { const mainRes = await httpGet(getStatsURL('main', rsn));
const mainRes = await axios(getStatsURL('main', rsn)); if (mainRes.status === 200) {
if (mainRes.status === 200) { const otherResponses = await Promise.all([
const otherResponses = await Promise.all([ httpGet(getStatsURL('ironman', rsn)).catch(err => err),
axios(getStatsURL('iron', rsn)).catch(err => err), httpGet(getStatsURL('hardcore', rsn)).catch(err => err),
axios(getStatsURL('hc', rsn)).catch(err => err), httpGet(getStatsURL('ultimate', rsn)).catch(err => err),
axios(getStatsURL('ult', rsn)).catch(err => err), getRSNFormat(rsn).catch(() => undefined),
getRSNFormat(rsn), ]);
]);
const [ironRes, hcRes, ultRes, formattedName] = otherResponses; const [ironRes, hcRes, ultRes, formattedName] = otherResponses;
const player: Player = {
rsn: formattedName,
mode: 'main',
dead: false,
deulted: false,
deironed: false,
};
player.main = parseStats(mainRes.data);
if (ironRes.status === 200) {
player.iron = 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.dead = true;
player.mode = 'iron';
}
if (player.main.skills.overall.xp !== player.iron.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.deulted = true;
player.mode = 'iron';
}
if (player.main.skills.overall.xp !== player.iron.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.deironed = true;
player.mode = 'main';
}
}
}
return player;
}
throw Error('Player not found');
} else {
const response = await axios(getStatsURL(mode, rsn));
if (response.status !== 200) {
throw Error('Player not found');
}
const player: Player = { const player: Player = {
rsn, name: formattedName || rsn,
mode, mode: 'main',
dead: false, dead: false,
deulted: false, deulted: false,
deironed: false, deironed: false,
[mode]: parseStats(response.data),
}; };
player.main = parseStats(mainRes.data);
if (ironRes.status === 200) {
player.ironman = parseStats(ironRes.data);
if (hcRes.status === 200) {
player.mode = 'hardcore';
player.hardcore = parseStats(hcRes.data);
if (
player.ironman.skills.overall.xp !== player.hardcore.skills.overall.xp
) {
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';
if (
player.main.skills.overall.xp !== player.ironman.skills.overall.xp
) {
player.deironed = true;
player.mode = 'main';
}
}
}
return player; return player;
} }
throw Error('Player not found');
}
export async function getStatsByGamemode(
rsn: string,
mode: Gamemode = 'main'
): Promise<Stats> {
if (typeof rsn !== 'string') {
throw Error('RSN must be a string');
} else if (!/^[a-zA-Z0-9 _]+$/.test(rsn)) {
throw Error('RSN contains invalid character');
} else if (rsn.length > 12 || rsn.length < 1) {
throw Error('RSN must be between 1 and 12 characters');
} else if (!GAMEMODES.includes(mode)) {
throw Error('Invalid game mode');
}
const response = await httpGet(getStatsURL(mode, rsn));
if (response.status !== 200) {
throw Error('Player not found');
}
const stats = parseStats(response.data);
return stats;
} }
export async function getSkillPage( export async function getSkillPage(
@@ -132,22 +143,27 @@ export async function getSkillPage(
} }
const url = getSkillPageURL(mode, skill, page); const url = getSkillPageURL(mode, skill, page);
const response = await axios(url); const response = await httpGet(url);
const $ = cheerio.load(response.data); const dom = new JSDOM(response.data);
const playersHTML = $('.personal-hiscores__row').toArray(); const playersHTML = dom.window.document.querySelectorAll(
'.personal-hiscores__row'
);
const players: PlayerSkillRow[] = playersHTML.map(row => { const players: PlayerSkillRow[] = [];
const cells = row.children.filter(el => el.name === 'td'); playersHTML.forEach(row => {
const [rankEl, nameCell, levelEl, xpEl] = cells; const rankEl = row.querySelector('td');
const [nameEl] = nameCell.children.filter(el => el.name === 'a'); 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 { players.push({
rsn: rsnFromElement(nameEl), name: rsnFromElement(nameEl),
rank: numberFromElement(rankEl), rank: numberFromElement(rankEl),
level: numberFromElement(levelEl), level: numberFromElement(levelEl),
xp: numberFromElement(xpEl), xp: numberFromElement(xpEl),
dead: nameCell.children.length === 4, dead: isDead,
}; });
}); });
return players; return players;
@@ -167,21 +183,25 @@ export async function getActivityPage(
} }
const url = getActivityPageURL(mode, activity, page); const url = getActivityPageURL(mode, activity, page);
const response = await axios(url); const response = await httpGet(url);
const $ = cheerio.load(response.data); const dom = new JSDOM(response.data);
const playersHTML = $('.personal-hiscores__row').toArray(); const playersHTML = dom.window.document.querySelectorAll(
'.personal-hiscores__row'
);
const players: PlayerActivityRow[] = playersHTML.map(row => { const players: PlayerActivityRow[] = [];
const cells = row.children.filter(el => el.name === 'td'); playersHTML.forEach(row => {
const [rankEl, nameCell, scoreEl] = cells; const rankEl = row.querySelector('td');
const [nameEl] = nameCell.children.filter(el => el.name === 'a'); const nameEl = row.querySelector('td a');
const scoreEl = row.querySelector('td.left + td');
const isDead = !!row.querySelector('td img');
return { players.push({
rsn: rsnFromElement(nameEl), name: rsnFromElement(nameEl),
rank: numberFromElement(rankEl), rank: numberFromElement(rankEl),
score: numberFromElement(scoreEl), score: numberFromElement(scoreEl),
dead: nameCell.children.length === 4, dead: isDead,
}; });
}); });
return players; return players;
@@ -198,11 +218,14 @@ export async function getRSNFormat(rsn: string): Promise<string> {
const url = getPlayerTableURL('main', rsn); const url = getPlayerTableURL('main', rsn);
try { try {
const response = await axios(url); const response = await httpGet(url);
const $ = cheerio.load(response.data); const dom = new JSDOM(response.data);
const rawName = $('[style="color:#AA0022;"]')[1].children[0].data; const spans = dom.window.document.querySelectorAll(
if (rawName) { 'span[style="color:#AA0022;"]'
return rawName.replace(/\uFFFD/g, ' '); );
if (spans.length >= 2) {
const nameSpan = spans[1];
return rsnFromElement(nameSpan);
} }
throw Error('Player not found'); throw Error('Player not found');
} catch { } catch {
@@ -219,10 +242,11 @@ export function parseStats(csv: string): Stats {
const skillObjects: Skill[] = splitCSV const skillObjects: Skill[] = splitCSV
.filter(stat => stat.length === 3) .filter(stat => stat.length === 3)
.map(stat => { .map(stat => {
const [rank, level, xp] = stat;
const skill: Skill = { const skill: Skill = {
rank: parseInt(stat[0], 10), rank: parseInt(rank, 10),
level: parseInt(stat[1], 10), level: parseInt(level, 10),
xp: parseInt(stat[2], 10), xp: parseInt(xp, 10),
}; };
return skill; return skill;
}); });
@@ -230,49 +254,52 @@ export function parseStats(csv: string): Stats {
const activityObjects: Activity[] = splitCSV const activityObjects: Activity[] = splitCSV
.filter(stat => stat.length === 2) .filter(stat => stat.length === 2)
.map(stat => { .map(stat => {
const [rank, score] = stat;
const activity: Activity = { const activity: Activity = {
rank: parseInt(stat[0], 10), rank: parseInt(rank, 10),
score: parseInt(stat[1], 10), score: parseInt(score, 10),
}; };
return activity; return activity;
}); });
const [leaguePoints] = activityObjects.splice(0, 1);
const bhObjects = activityObjects.splice(0, BH_MODES.length); const bhObjects = activityObjects.splice(0, BH_MODES.length);
const [lms] = activityObjects.splice(0, 1);
const clueObjects = activityObjects.splice(0, CLUES.length); const clueObjects = activityObjects.splice(0, CLUES.length);
const [lastManStanding, soulWarsZeal] = activityObjects.splice(0, 2);
const bossObjects = activityObjects.splice(0, BOSSES.length);
const skills: Skills = skillObjects.reduce<Skills>( const skills: Skills = skillObjects.reduce<Skills>((prev, curr, index) => {
(prev, curr, index) => { const newSkills = { ...prev };
const newSkills = { ...prev }; newSkills[SKILLS[index]] = curr;
newSkills[SKILLS[index]] = curr; return newSkills;
return newSkills; }, {} as Skills);
},
{} as Skills
);
const bh: BHStats = bhObjects.reduce<BHStats>( const bountyHunter: BH = bhObjects.reduce<BH>((prev, curr, index) => {
(prev, curr, index) => { const newBH = { ...prev };
const newBH = { ...prev }; newBH[BH_MODES[index]] = curr;
newBH[BH_MODES[index]] = curr; return newBH;
return newBH; }, {} as BH);
},
{} as BHStats
);
const clues: Clues = clueObjects.reduce<Clues>( const clues: Clues = clueObjects.reduce<Clues>((prev, curr, index) => {
(prev, curr, index) => { const newClues = { ...prev };
const newClues = { ...prev }; newClues[CLUES[index]] = curr;
newClues[CLUES[index]] = curr; return newClues;
return newClues; }, {} as Clues);
},
{} 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 = { const stats: Stats = {
skills, skills,
bh, leaguePoints,
lms, bountyHunter,
lastManStanding,
soulWarsZeal,
clues, clues,
bosses,
}; };
return stats; return stats;

View File

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

View File

@@ -1,6 +1,11 @@
export type Gamemode = 'main' | 'iron' | 'hc' | 'ult' | 'dmm' | 'sdmm' | 'dmmt'; export type Gamemode =
| 'main'
export type Mode = Gamemode | 'full'; | 'ironman'
| 'ultimate'
| 'hardcore'
| 'deadman'
| 'seasonal'
| 'tournament';
export interface Skill { export interface Skill {
rank: number; rank: number;
@@ -56,29 +61,82 @@ export type BHType = 'rogue' | 'hunter';
export type BH = { [Type in BHType]: Activity }; 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 = export type ActivityName =
| 'hunterbh' | 'leaguePoints'
| 'roguebh' | 'hunterBH'
| 'lms' | 'rogueBH'
| 'allclues' | 'lastManStanding'
| 'beginnerclues' | 'soulWarsZeal'
| 'easyclues' | 'allClues'
| 'mediumclues' | 'beginnerClues'
| 'hardclues' | 'easyClues'
| 'eliteclues' | 'mediumClues'
| 'masterclues'; | 'hardClues'
| 'eliteClues'
| 'masterClues'
| Boss;
export interface Stats { export interface Stats {
skills: Skills; skills: Skills;
clues: Clues; clues: Clues;
bh: BH; leaguePoints: Activity;
lms: Activity; bountyHunter: BH;
lastManStanding: Activity;
soulWarsZeal: Activity;
bosses: Bosses;
} }
export type Modes = { [M in Gamemode]?: Stats }; export type Modes = { [M in Gamemode]?: Stats };
export interface Player extends Modes { export interface Player extends Modes {
rsn: string; name: string;
mode: Gamemode; mode: Gamemode;
dead: boolean; dead: boolean;
deulted: boolean; deulted: boolean;
@@ -86,11 +144,11 @@ export interface Player extends Modes {
} }
export interface PlayerSkillRow extends Skill { export interface PlayerSkillRow extends Skill {
rsn: string; name: string;
dead: boolean; dead: boolean;
} }
export interface PlayerActivityRow extends Activity { export interface PlayerActivityRow extends Activity {
rsn: string; name: string;
dead: boolean; dead: boolean;
} }

View File

@@ -1,16 +1,28 @@
import { SkillName, ClueType, BHType, Gamemode, Mode } 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 STATS_URL = 'index_lite.ws?player=';
export const SCORES_URL = 'overall.ws?'; export const SCORES_URL = 'overall.ws?';
export const GAMEMODE_URL = {
dmm: '_deadman/', export type GamemodeUrl = {
dmmt: '_tournament/', [key in Gamemode]: string;
hc: '_hardcore_ironman/', };
iron: '_ironman/',
main: '/', export const GAMEMODE_URL: GamemodeUrl = {
sdmm: '_seasonal/', main: `${BASE_URL}/`,
ult: '_ultimate/', 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[] = [ export const SKILLS: SkillName[] = [
'overall', 'overall',
@@ -47,26 +59,183 @@ export const CLUES: ClueType[] = [
'elite', 'elite',
'master', 'master',
]; ];
export const BH_MODES: BHType[] = ['rogue', 'hunter']; export const BH_MODES: BHType[] = ['hunter', 'rogue'];
export const ACTIVITIES = [
'hunterbh',
'roguebh',
'lms',
'allclues',
'beginnerclues',
'easyclues',
'mediumclues',
'hardclues',
'eliteclues',
'masterclues',
];
export const GAMEMODES: Gamemode[] = [ export const GAMEMODES: Gamemode[] = [
'main', 'main',
'iron', 'ironman',
'hc', 'hardcore',
'ult', 'ultimate',
'dmm', 'deadman',
'sdmm', 'seasonal',
'dmmt', 'tournament',
]; ];
export const MODES: Mode[] = [...GAMEMODES, 'full']; 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',
'soulWarsZeal',
...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_SOUL_WARS = 'Soul Wars Zeal';
export const FORMATTED_LEAGUE_POINTS = 'League Points';

View File

@@ -1,6 +1,7 @@
import axios from 'axios';
import * as ua from 'useragent-generator';
import { Gamemode, SkillName, ActivityName } from '../types'; import { Gamemode, SkillName, ActivityName } from '../types';
import { import {
BASE_URL,
GAMEMODE_URL, GAMEMODE_URL,
STATS_URL, STATS_URL,
SCORES_URL, SCORES_URL,
@@ -9,19 +10,19 @@ import {
} from './constants'; } from './constants';
export const getStatsURL = (gamemode: Gamemode, rsn: string) => 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) => export const getPlayerTableURL = (gamemode: Gamemode, rsn: string) =>
`${BASE_URL}${ `${GAMEMODE_URL[gamemode]}${SCORES_URL}table=0&user=${encodeURIComponent(
GAMEMODE_URL[gamemode] rsn
}${SCORES_URL}table=0&user=${encodeURIComponent(rsn)}`; )}`;
export const getSkillPageURL = ( export const getSkillPageURL = (
gamemode: Gamemode, gamemode: Gamemode,
skill: SkillName, skill: SkillName,
page: number page: number
) => ) =>
`${BASE_URL}${GAMEMODE_URL[gamemode]}${SCORES_URL}table=${SKILLS.indexOf( `${GAMEMODE_URL[gamemode]}${SCORES_URL}table=${SKILLS.indexOf(
skill skill
)}&page=${page}`; )}&page=${page}`;
@@ -30,19 +31,28 @@ export const getActivityPageURL = (
activity: ActivityName, activity: ActivityName,
page: number page: number
) => ) =>
`${BASE_URL}${ `${
GAMEMODE_URL[gamemode] GAMEMODE_URL[gamemode]
}${SCORES_URL}category_type=1&table=${ACTIVITIES.indexOf( }${SCORES_URL}category_type=1&table=${ACTIVITIES.indexOf(
activity activity
)}&page=${page}`; )}&page=${page}`;
export const numberFromElement = (el: CheerioElement) => { export const numberFromElement = (el: Element | null) => {
const innerText = el.firstChild.data; const { innerHTML } = el || {};
const number = innerText ? innerText.replace(/[\n|,]/g, '') : '-1'; const number = innerHTML?.replace(/[\n|,]/g, '') ?? '-1';
return parseInt(number, 10); return parseInt(number, 10);
}; };
export const rsnFromElement = (el: CheerioElement) => { export const rsnFromElement = (el: Element | null) => {
const innerText = el.firstChild.data; const { innerHTML } = el || {};
return innerText ? innerText.replace(/\uFFFD/g, ' ') : ''; return innerHTML?.replace(/\uFFFD/g, ' ') || '';
};
export const httpGet = (url: string) => {
return axios.get(url, {
headers: {
// without User-Agent header requests may be rejected by DDoS protection mechanism
'User-Agent': ua.firefox(80)
}
});
}; };

4980
yarn.lock

File diff suppressed because it is too large Load Diff