mirror of
https://github.com/maxswa/osrs-json-hiscores.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
237 Commits
v1.2.2
...
varlamore-
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d0f5285470 | ||
![]() |
9c56e44344 | ||
![]() |
e5b3075ebd | ||
![]() |
8bb50b73b2 | ||
![]() |
cb482ad720 | ||
![]() |
0fbd85aa07 | ||
![]() |
e634b107b1 | ||
![]() |
29db21493c | ||
![]() |
b117cfcf41 | ||
![]() |
d895326d9f | ||
![]() |
1f7a25d421 | ||
![]() |
057812681b | ||
![]() |
ee510ec69a | ||
![]() |
6adcb02545 | ||
![]() |
dacbd90189 | ||
![]() |
3b502cb7cf | ||
![]() |
b3f81e66eb | ||
![]() |
01150b6234 | ||
![]() |
ca4d75f2c7 | ||
![]() |
03b2ef42d2 | ||
![]() |
7e2603b235 | ||
![]() |
92452e240c | ||
![]() |
d01b36422e | ||
![]() |
3bc1352838 | ||
![]() |
91066b5366 | ||
![]() |
4872d04301 | ||
![]() |
55312ba0fd | ||
![]() |
331a448503 | ||
![]() |
84c704a846 | ||
![]() |
561495632c | ||
![]() |
bdfdf1eb14 | ||
![]() |
e349776060 | ||
![]() |
546bc5acf7 | ||
![]() |
66528cd9d9 | ||
![]() |
99ea3fb722 | ||
![]() |
7f16d26e3c | ||
![]() |
ed9fcf47b3 | ||
![]() |
bb935bb6c9 | ||
![]() |
d046fffa9e | ||
![]() |
5483e54d78 | ||
![]() |
97532b0c03 | ||
![]() |
f9a56e2b3a | ||
![]() |
9f43f0c39f | ||
![]() |
d67ffa3e20 | ||
![]() |
7bdde9f5d1 | ||
![]() |
b4a50e69c8 | ||
![]() |
7935c5a523 | ||
![]() |
f1e2155428 | ||
![]() |
dd90fe1260 | ||
![]() |
12c4e530a2 | ||
![]() |
5a2b0d9ad8 | ||
![]() |
c43858eac1 | ||
![]() |
0d0081f4f6 | ||
![]() |
3570f0f07d | ||
![]() |
3ddda54e02 | ||
![]() |
388f01522a | ||
![]() |
24b3de2a29 | ||
![]() |
fc7897d4c7 | ||
![]() |
72ea31490f | ||
![]() |
66825955c1 | ||
![]() |
4b4bf8c702 | ||
![]() |
53981e7468 | ||
![]() |
a90e5b98dd | ||
![]() |
16a975f10b | ||
![]() |
c800501188 | ||
![]() |
8a320df015 | ||
![]() |
169462c446 | ||
![]() |
317a972314 | ||
![]() |
c558f8402c | ||
![]() |
41c6da4d5c | ||
![]() |
99ad5eb662 | ||
![]() |
a8cf1713af | ||
![]() |
11391c90f2 | ||
![]() |
e4d2b10488 | ||
![]() |
0cfd0e2471 | ||
![]() |
9c284ad4b0 | ||
![]() |
ec60a72c08 | ||
![]() |
d45e2efcfd | ||
![]() |
f80b417121 | ||
![]() |
415fe2f02b | ||
![]() |
968799176f | ||
![]() |
f45ae67813 | ||
![]() |
7724636b40 | ||
![]() |
3954b04acc | ||
![]() |
ee7389455c | ||
![]() |
54c11635e0 | ||
![]() |
dc799cf363 | ||
![]() |
d4c7aaff2a | ||
![]() |
03c089ebff | ||
![]() |
96641d875a | ||
![]() |
b2bdd3d2ce | ||
![]() |
a4ddfaf199 | ||
![]() |
efbcdfc709 | ||
![]() |
fe913aba1f | ||
![]() |
1497653bab | ||
![]() |
f76de38eb1 | ||
![]() |
2f1d10b309 | ||
![]() |
f08424cb81 | ||
![]() |
69088db3b6 | ||
![]() |
9cc89735cd | ||
![]() |
a126ae4fc7 | ||
![]() |
52f3df1838 | ||
![]() |
2891bbddc4 | ||
![]() |
e2ea2fe33d | ||
![]() |
b7ae812f45 | ||
![]() |
240628d9cc | ||
![]() |
b754663f6d | ||
![]() |
0678f7c979 | ||
![]() |
2a60283a93 | ||
![]() |
b912731cf9 | ||
![]() |
cf44c57ec6 | ||
![]() |
3f34c9ab61 | ||
![]() |
1ef73b6b0c | ||
![]() |
08ecf68689 | ||
![]() |
ab3ee70237 | ||
![]() |
cad20d41cb | ||
![]() |
2877b9c8a1 | ||
![]() |
afbb22749a | ||
![]() |
eebf76a34c | ||
![]() |
e60442a7ce | ||
![]() |
1132a085e1 | ||
![]() |
a2a34a46e3 | ||
![]() |
71a76a12fd | ||
![]() |
3a72315498 | ||
![]() |
c1c9833ad0 | ||
![]() |
9fe03746e1 | ||
![]() |
66a4fb47a6 | ||
![]() |
83c44148a7 | ||
![]() |
44dea6b35c | ||
![]() |
0bf491638c | ||
![]() |
febca534b0 | ||
![]() |
8daee5c39b | ||
![]() |
f98cf8aaaa | ||
![]() |
5d9f6a6bac | ||
![]() |
8737b50a82 | ||
![]() |
1255e7e3d2 | ||
![]() |
f4135d0b0f | ||
![]() |
6135ff459c | ||
![]() |
9fb7e4cebc | ||
![]() |
11350b2444 | ||
![]() |
e0d6c5b20f | ||
![]() |
c8a4440ac3 | ||
![]() |
f3781cf142 | ||
![]() |
5fbc04217b | ||
![]() |
eb5d0ba49f | ||
![]() |
c68e3bebea | ||
![]() |
aa6fd6f39a | ||
![]() |
cc9aae8ced | ||
![]() |
12882b4e34 | ||
![]() |
b17a8b9299 | ||
![]() |
50f2c19257 | ||
![]() |
d892f8e67b | ||
![]() |
af5664ae6e | ||
![]() |
20c5c2f730 | ||
![]() |
04e59cff04 | ||
![]() |
ad2249ba76 | ||
![]() |
5c52bfd883 | ||
![]() |
947d994cdf | ||
![]() |
130446ab74 | ||
![]() |
f96e7e3e2d | ||
![]() |
90d66b6b2c | ||
![]() |
14cedb5877 | ||
![]() |
4703812b52 | ||
![]() |
a11ac96f68 | ||
![]() |
f97caa6a15 | ||
![]() |
e7e54741d2 | ||
![]() |
062e5e02b2 | ||
![]() |
ed7036f9c0 | ||
![]() |
74aba3e818 | ||
![]() |
09fe7ed539 | ||
![]() |
7a78e1ea0a | ||
![]() |
7a4163aed2 | ||
![]() |
85464d02fe | ||
![]() |
592d82f86b | ||
![]() |
a28a9e253e | ||
![]() |
0bd15f2402 | ||
![]() |
ed8ad51b4d | ||
![]() |
311ea88c61 | ||
![]() |
e056d052a5 | ||
![]() |
b230560442 | ||
![]() |
b2c6efecbd | ||
![]() |
e9f47b9f08 | ||
![]() |
3a6671d3d1 | ||
![]() |
a4065022d6 | ||
![]() |
163f4dd184 | ||
![]() |
e200a8d566 | ||
![]() |
89944ca8cc | ||
![]() |
29c76b4afb | ||
![]() |
e3e904a23f | ||
![]() |
0b96530a8e | ||
![]() |
175f945ab8 | ||
![]() |
6b31e8b08c | ||
![]() |
d097567a3c | ||
![]() |
be5c093cf7 | ||
![]() |
db72f0c57d | ||
![]() |
bd2ea21980 | ||
![]() |
d67837c161 | ||
![]() |
4aa6ad2752 | ||
![]() |
2f788fabf6 | ||
![]() |
0f5aea795c | ||
![]() |
7acb84e310 | ||
![]() |
9619bd2a2b | ||
![]() |
55abd9f800 | ||
![]() |
cf8a4cc26e | ||
![]() |
0f68c1995c | ||
![]() |
ae47df31f1 | ||
![]() |
7a3f3a0800 | ||
![]() |
b16e8ace6e | ||
![]() |
09f7805fd4 | ||
![]() |
939f4d2721 | ||
![]() |
a4577ffb2c | ||
![]() |
19ba7e2916 | ||
![]() |
7de2d9a95a | ||
![]() |
a143306519 | ||
![]() |
86b81abfd8 | ||
![]() |
8d065742d0 | ||
![]() |
eaa3d4a299 | ||
![]() |
a92fa7fffe | ||
![]() |
90f2939761 | ||
![]() |
12046246ef | ||
![]() |
adf73e59c4 | ||
![]() |
f2fcbad1c4 | ||
![]() |
0f47310814 | ||
![]() |
30530fde01 | ||
![]() |
2c0268939d | ||
![]() |
0142783d43 | ||
![]() |
468b43f66f | ||
![]() |
d5bbe2a169 | ||
![]() |
26d06da24b | ||
![]() |
582a5c01fd | ||
![]() |
0138dcd5fd | ||
![]() |
f1730d44ec | ||
![]() |
ae73fef5ed | ||
![]() |
8bf1f6cdbc | ||
![]() |
50d8365e87 | ||
![]() |
8f9c9777e5 | ||
![]() |
470d788327 |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.html linguist-vendored
|
28
.github/workflows/main.yml
vendored
Normal file
28
.github/workflows/main.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.3.0
|
||||
- uses: borales/actions-yarn@v4.2.0
|
||||
with:
|
||||
cmd: install
|
||||
- uses: borales/actions-yarn@v4.2.0
|
||||
with:
|
||||
cmd: lint
|
||||
- uses: borales/actions-yarn@v4.2.0
|
||||
with:
|
||||
cmd: format
|
||||
- uses: borales/actions-yarn@v4.2.0
|
||||
with:
|
||||
cmd: build
|
||||
- uses: borales/actions-yarn@v4.2.0
|
||||
with:
|
||||
cmd: test
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
.idea/
|
||||
node_modules
|
||||
/lib
|
||||
.vscode
|
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true
|
||||
}
|
5
LICENSE
Normal file
5
LICENSE
Normal file
@@ -0,0 +1,5 @@
|
||||
Copyright 2021 maxswa
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
195
README.md
195
README.md
@@ -3,23 +3,26 @@
|
||||
[](https://www.npmjs.com/package/osrs-json-hiscores)
|
||||
[](https://npm-stat.com/charts.html?package=osrs-json-hiscores)
|
||||
[](https://github.com/maxswa/osrs-json-hiscores/blob/master/src/types.ts)
|
||||
[](https://github.com/maxswa/osrs-json-hiscores/actions/workflows/main.yml?query=branch%3Amain)
|
||||
|
||||
**The Oldschool Runescape API wrapper that does more!**
|
||||
**The Old School RuneScape API wrapper that does more!**
|
||||
|
||||
## What it does
|
||||
|
||||
The official hiscores API for Oldschool Runescape (OSRS) returns CSV.
|
||||
This wrapper converts it to json and provides extra information about the given player. By comparing player info it infers the player's game mode, as well as any previous modes (de-ultimated, de-ironed and/or died as a hardcore ironman).
|
||||
The official hiscores API for Old School RuneScape (OSRS) can return CSV or a simple JSON array.
|
||||
This wrapper converts the hiscores data into a more usable JSON object and provides extra information about the given player. By comparing player info it infers the player's game mode, as well as any previous modes (de-ultimated, de-ironed and/or died as a hardcore ironman).
|
||||
|
||||
Additional functions are provided that screen-scrape the OSRS leaderboards and return a list of players as json.
|
||||
Additional functions are provided that screen-scrape the OSRS leaderboards and return a list of players as json. Also simple utility functions are provided to fetch the raw responses from Jagex's APIs, if desired.
|
||||
|
||||
`osrs-json-hiscores` has TypeScript support, with full definitions for all functions and custom data types.
|
||||
|
||||
---
|
||||
|
||||
### Disclaimer
|
||||
### ⚠ 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)
|
||||
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 needs to 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)
|
||||
|
||||
TLDR: You cannot use this library directly in your client side app e.g. React or Vue, you must set up a server which uses this lib internally and have your client fetch data from your server.
|
||||
|
||||
---
|
||||
|
||||
@@ -41,27 +44,14 @@ $ yarn add osrs-json-hiscores
|
||||
|
||||
Install the package and then import it into your project:
|
||||
|
||||
```javascript
|
||||
const hiscores = require('osrs-json-hiscores');
|
||||
```typescript
|
||||
import { getStatsByGamemode, getSkillPage } from 'osrs-json-hiscores';
|
||||
```
|
||||
|
||||
Once you import it you can call the functions asynchronously:
|
||||
|
||||
```javascript
|
||||
hiscores
|
||||
.getStats('Lynx Titan')
|
||||
.then(res => console.log(res))
|
||||
.catch(err => console.error(err));
|
||||
```
|
||||
|
||||
If you are using TypeScript or transpiling your JS you can use ES6 syntax:
|
||||
|
||||
```javascript
|
||||
import hiscores, { getSkillPage } from 'osrs-json-hiscores';
|
||||
|
||||
// ...
|
||||
|
||||
const stats = await hiscores.getStats('Lynx Titan');
|
||||
```typescript
|
||||
const stats = await getStatsByGamemode('Lynx Titan');
|
||||
const topPage = await getSkillPage('overall');
|
||||
```
|
||||
|
||||
@@ -69,53 +59,134 @@ const topPage = await getSkillPage('overall');
|
||||
`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` |
|
||||
| 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
|
||||
.getSkillPage('attack', 'main', 1)
|
||||
.then(res => console.log(res))
|
||||
.catch(err => console.error(err));
|
||||
```typescript
|
||||
getSkillPage('attack', 'main', 1)
|
||||
.then((res) => console.log(res))
|
||||
.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` |
|
||||
| ------------------------------- | :---------------: |
|
||||
| Bounty Hunter (Legacy - Rogue) | `rogueBH` |
|
||||
| Bounty Hunter (Legacy - Hunter) | `hunterBH` |
|
||||
| Bounty Hunter (Rogue) | `rogueBHV2` |
|
||||
| Bounty Hunter (Hunter) | `hunterBHV2` |
|
||||
| LMS - Rank | `lastManStanding` |
|
||||
| PvP Arena - Rank | `pvpArena` |
|
||||
| Soul Wars Zeal | `soulWarsZeal` |
|
||||
| Rifts closed | `riftsClosed` |
|
||||
| Colosseum Glory | `colosseumGlory` |
|
||||
|
||||
### Points
|
||||
|
||||
| Activity | Param |
|
||||
| -------------- | :-------------: |
|
||||
| League Points | `leaguePoints` |
|
||||
| Deadman Points | `deadmanPoints` |
|
||||
|
||||
### Bosses
|
||||
|
||||
| Boss Name | Param |
|
||||
| --------------------------------- | :----------------------------: |
|
||||
| Abyssal Sire | `abyssalSire` |
|
||||
| Alchemical Hydra | `alchemicalHydra` |
|
||||
| Amoxliatl | `amoxliatl` |
|
||||
| Araxxor | `araxxor` |
|
||||
| Artio | `artio` |
|
||||
| Barrows Chests | `barrows` |
|
||||
| Bryophyta | `bryophyta` |
|
||||
| Callisto | `callisto` |
|
||||
| Calvar'ion | `calvarion` |
|
||||
| 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` |
|
||||
| Duke Sucellus | `dukeSucellus` |
|
||||
| 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` |
|
||||
| Lunar Chests | `lunarChests` |
|
||||
| Mimic | `mimic` |
|
||||
| Nex | `nex` |
|
||||
| Nightmare | `nightmare` |
|
||||
| Phosani's Nightmare | `phosanisNightmare` |
|
||||
| Obor | `obor` |
|
||||
| Phantom Muspah | `phantomMuspah` |
|
||||
| Sarachnis | `sarachnis` |
|
||||
| Scorpia | `scorpia` |
|
||||
| Scurrius | `scurrius` |
|
||||
| Skotizo | `skotizo` |
|
||||
| Sol Heredit | `solHeredit` |
|
||||
| Spindel | `spindel` |
|
||||
| Tempoross | `tempoross` |
|
||||
| The Gauntlet | `gauntlet` |
|
||||
| The Corrupted Gauntlet | `corruptedGauntlet` |
|
||||
| The Hueycoatl | `hueycoatl` |
|
||||
| The Leviathan | `leviathan` |
|
||||
| The Whisperer | `whisperer` |
|
||||
| Theatre Of Blood | `theatreOfBlood` |
|
||||
| Theatre Of Blood: Hard Mode | `theatreOfBloodHardMode` |
|
||||
| Thermonuclear Smoke Devil | `thermonuclearSmokeDevil` |
|
||||
| Tombs of Amascut | `tombsOfAmascut` |
|
||||
| Tombs of Amascut: Expert Mode | `tombsOfAmascutExpertMode` |
|
||||
| TzKal-Zuk | `tzKalZuk` |
|
||||
| TzTok-Jad | `tzTokJad` |
|
||||
| Vardorvis | `vardorvis` |
|
||||
| Venenatis | `venenatis` |
|
||||
| Vetion | `vetion` |
|
||||
| Vorkath | `vorkath` |
|
||||
| Wintertodt | `wintertodt` |
|
||||
| Zalcano | `zalcano` |
|
||||
| Zulrah | `zulrah` |
|
||||
|
||||
## What you'll get
|
||||
|
||||
`getStats` returns a player object that looks like this:
|
||||
|
||||
```javascript
|
||||
```typescript
|
||||
{
|
||||
rsn: 'Lynx Titan',
|
||||
name: 'Lynx Titan',
|
||||
mode: 'main',
|
||||
dead: false,
|
||||
deulted: false,
|
||||
@@ -128,19 +199,33 @@ Activities consist of all levels of clue scrolls as well as minigames:
|
||||
// ...
|
||||
},
|
||||
clues: {},
|
||||
bh: {},
|
||||
lms: {}
|
||||
leaguePoints: {},
|
||||
bountyHunter: {},
|
||||
lastManStanding: {},
|
||||
pvpArena: {},
|
||||
soulWarsZeal: {},
|
||||
riftsClosed: {},
|
||||
bosses: {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`getSkillPage` returns and array of 25 players (This represents a page on the hiscores):
|
||||
|
||||
```javascript
|
||||
```typescript
|
||||
[
|
||||
{ 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:
|
||||
|
||||
```typescript
|
||||
// kril === "K'ril Tsutsaroth"
|
||||
const kril = FORMATTED_BOSS_NAMES['krilTsutsaroth'];
|
||||
```
|
||||
|
1192
__tests__/attackTopPage.html
vendored
Normal file
1192
__tests__/attackTopPage.html
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1188
__tests__/b0atyNamePage.html
vendored
Normal file
1188
__tests__/b0atyNamePage.html
vendored
Normal file
File diff suppressed because it is too large
Load Diff
106
__tests__/b0atyStats.csv
Normal file
106
__tests__/b0atyStats.csv
Normal file
@@ -0,0 +1,106 @@
|
||||
2130,2277,1102428578
|
||||
22624,99,32956104
|
||||
3574,99,47147957
|
||||
1226,99,200000000
|
||||
1454,99,180249986
|
||||
3112,99,155438899
|
||||
2533,99,17520811
|
||||
2078,99,84629930
|
||||
150276,99,13378420
|
||||
23833,99,19182079
|
||||
61715,99,13456926
|
||||
24604,99,16672148
|
||||
19900,99,19161337
|
||||
53450,99,14082613
|
||||
12063,99,16553015
|
||||
32871,99,14985851
|
||||
39747,99,13346314
|
||||
8390,99,15771711
|
||||
55848,99,14322777
|
||||
1723,99,52832158
|
||||
2107,99,101923913
|
||||
5301,99,22739376
|
||||
20087,99,18044758
|
||||
1301,99,18031495
|
||||
-1,-1
|
||||
-1,-1
|
||||
32847,10
|
||||
16567,3
|
||||
147262,8
|
||||
101823,7
|
||||
382,9739
|
||||
1265,825
|
||||
131,6000
|
||||
2401,1606
|
||||
43386,311
|
||||
799,460
|
||||
945,537
|
||||
5531,5516
|
||||
-1,-1
|
||||
7538,14780
|
||||
6781,1060
|
||||
6880,43487
|
||||
3193,2192
|
||||
2932,4796
|
||||
123,6789
|
||||
37,111
|
||||
44605,143
|
||||
4023,1830
|
||||
238149,5
|
||||
47139,293
|
||||
98811,58
|
||||
10766,2657
|
||||
200557,34
|
||||
1735,692
|
||||
23548,215
|
||||
43337,116
|
||||
61439,287
|
||||
2859,2000
|
||||
53841,106
|
||||
18420,1078
|
||||
23824,1062
|
||||
18418,1088
|
||||
85908,25
|
||||
6077,1320
|
||||
21980,1228
|
||||
76561,477
|
||||
774,3642
|
||||
7917,281
|
||||
2313,2046
|
||||
52832,580
|
||||
64296,2899
|
||||
4205,1740
|
||||
2681,1444
|
||||
3424,324
|
||||
523,48
|
||||
35420,626
|
||||
4162,749
|
||||
5046,500
|
||||
1218,217
|
||||
21243,379
|
||||
40733,249
|
||||
53931,91
|
||||
4870,1245
|
||||
174877,29
|
||||
2122,49
|
||||
3471,1569
|
||||
50947,273
|
||||
236110,7
|
||||
102423,201
|
||||
2344,21039
|
||||
10174,666
|
||||
665,2072
|
||||
23177,310
|
||||
1967,419
|
||||
8495,3066
|
||||
122123,37
|
||||
240,1537
|
||||
7756,7
|
||||
140561,8
|
||||
4736,2113
|
||||
249,7096
|
||||
89194,51
|
||||
15067,2780
|
||||
975929,62
|
||||
196393,25
|
||||
227062,340
|
Can't render this file because it has a wrong number of fields in line 25.
|
666
__tests__/b0atyStats.json
Normal file
666
__tests__/b0atyStats.json
Normal file
@@ -0,0 +1,666 @@
|
||||
{
|
||||
"skills": [
|
||||
{
|
||||
"id": 0,
|
||||
"name": "Overall",
|
||||
"rank": 2130,
|
||||
"level": 2277,
|
||||
"xp": 1102428578
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Attack",
|
||||
"rank": 22624,
|
||||
"level": 99,
|
||||
"xp": 32956104
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Defence",
|
||||
"rank": 3574,
|
||||
"level": 99,
|
||||
"xp": 47147957
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Strength",
|
||||
"rank": 1226,
|
||||
"level": 99,
|
||||
"xp": 200000000
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Hitpoints",
|
||||
"rank": 1454,
|
||||
"level": 99,
|
||||
"xp": 180249986
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "Ranged",
|
||||
"rank": 3112,
|
||||
"level": 99,
|
||||
"xp": 155438899
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"name": "Prayer",
|
||||
"rank": 2533,
|
||||
"level": 99,
|
||||
"xp": 17520811
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"name": "Magic",
|
||||
"rank": 2078,
|
||||
"level": 99,
|
||||
"xp": 84629930
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"name": "Cooking",
|
||||
"rank": 150276,
|
||||
"level": 99,
|
||||
"xp": 13378420
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"name": "Woodcutting",
|
||||
"rank": 23833,
|
||||
"level": 99,
|
||||
"xp": 19182079
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"name": "Fletching",
|
||||
"rank": 61715,
|
||||
"level": 99,
|
||||
"xp": 13456926
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"name": "Fishing",
|
||||
"rank": 24604,
|
||||
"level": 99,
|
||||
"xp": 16672148
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"name": "Firemaking",
|
||||
"rank": 19900,
|
||||
"level": 99,
|
||||
"xp": 19161337
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"name": "Crafting",
|
||||
"rank": 53450,
|
||||
"level": 99,
|
||||
"xp": 14082613
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"name": "Smithing",
|
||||
"rank": 12063,
|
||||
"level": 99,
|
||||
"xp": 16553015
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"name": "Mining",
|
||||
"rank": 32871,
|
||||
"level": 99,
|
||||
"xp": 14985851
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"name": "Herblore",
|
||||
"rank": 39747,
|
||||
"level": 99,
|
||||
"xp": 13346314
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"name": "Agility",
|
||||
"rank": 8390,
|
||||
"level": 99,
|
||||
"xp": 15771711
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"name": "Thieving",
|
||||
"rank": 55848,
|
||||
"level": 99,
|
||||
"xp": 14322777
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"name": "Slayer",
|
||||
"rank": 1723,
|
||||
"level": 99,
|
||||
"xp": 52832158
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"name": "Farming",
|
||||
"rank": 2107,
|
||||
"level": 99,
|
||||
"xp": 101923913
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"name": "Runecraft",
|
||||
"rank": 5301,
|
||||
"level": 99,
|
||||
"xp": 22739376
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"name": "Hunter",
|
||||
"rank": 20087,
|
||||
"level": 99,
|
||||
"xp": 18044758
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"name": "Construction",
|
||||
"rank": 1301,
|
||||
"level": 99,
|
||||
"xp": 18031495
|
||||
}
|
||||
],
|
||||
"activities": [
|
||||
{
|
||||
"id": 0,
|
||||
"name": "League Points",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Deadman Points",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Bounty Hunter - Hunter",
|
||||
"rank": 32847,
|
||||
"score": 10
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Bounty Hunter - Rogue",
|
||||
"rank": 16567,
|
||||
"score": 3
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Bounty Hunter (Legacy) - Hunter",
|
||||
"rank": 147262,
|
||||
"score": 8
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "Bounty Hunter (Legacy) - Rogue",
|
||||
"rank": 101823,
|
||||
"score": 7
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"name": "Clue Scrolls (all)",
|
||||
"rank": 382,
|
||||
"score": 9739
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"name": "Clue Scrolls (beginner)",
|
||||
"rank": 1265,
|
||||
"score": 825
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"name": "Clue Scrolls (easy)",
|
||||
"rank": 131,
|
||||
"score": 6000
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"name": "Clue Scrolls (medium)",
|
||||
"rank": 2401,
|
||||
"score": 1606
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"name": "Clue Scrolls (hard)",
|
||||
"rank": 43386,
|
||||
"score": 311
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"name": "Clue Scrolls (elite)",
|
||||
"rank": 799,
|
||||
"score": 460
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"name": "Clue Scrolls (master)",
|
||||
"rank": 945,
|
||||
"score": 537
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"name": "LMS - Rank",
|
||||
"rank": 5531,
|
||||
"score": 5516
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"name": "PvP Arena - Rank",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"name": "Soul Wars Zeal",
|
||||
"rank": 7538,
|
||||
"score": 14780
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"name": "Rifts closed",
|
||||
"rank": 6781,
|
||||
"score": 1060
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"name": "Colosseum Glory",
|
||||
"rank": 6880,
|
||||
"score": 43487
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"name": "Abyssal Sire",
|
||||
"rank": 3193,
|
||||
"score": 2192
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"name": "Alchemical Hydra",
|
||||
"rank": 2932,
|
||||
"score": 4796
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"name": "Amoxliatl",
|
||||
"rank": 123,
|
||||
"score": 6789
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"name": "Araxxor",
|
||||
"rank": 37,
|
||||
"score": 111
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"name": "Artio",
|
||||
"rank": 44605,
|
||||
"score": 143
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"name": "Barrows Chests",
|
||||
"rank": 4023,
|
||||
"score": 1830
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"name": "Bryophyta",
|
||||
"rank": 238149,
|
||||
"score": 5
|
||||
},
|
||||
{
|
||||
"id": 25,
|
||||
"name": "Callisto",
|
||||
"rank": 47139,
|
||||
"score": 293
|
||||
},
|
||||
{
|
||||
"id": 26,
|
||||
"name": "Calvar'ion",
|
||||
"rank": 98811,
|
||||
"score": 58
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"name": "Cerberus",
|
||||
"rank": 10766,
|
||||
"score": 2657
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"name": "Chambers of Xeric",
|
||||
"rank": 200557,
|
||||
"score": 34
|
||||
},
|
||||
{
|
||||
"id": 29,
|
||||
"name": "Chambers of Xeric: Challenge Mode",
|
||||
"rank": 1735,
|
||||
"score": 692
|
||||
},
|
||||
{
|
||||
"id": 30,
|
||||
"name": "Chaos Elemental",
|
||||
"rank": 23548,
|
||||
"score": 215
|
||||
},
|
||||
{
|
||||
"id": 31,
|
||||
"name": "Chaos Fanatic",
|
||||
"rank": 43337,
|
||||
"score": 116
|
||||
},
|
||||
{
|
||||
"id": 32,
|
||||
"name": "Commander Zilyana",
|
||||
"rank": 61439,
|
||||
"score": 287
|
||||
},
|
||||
{
|
||||
"id": 33,
|
||||
"name": "Corporeal Beast",
|
||||
"rank": 2859,
|
||||
"score": 2000
|
||||
},
|
||||
{
|
||||
"id": 34,
|
||||
"name": "Crazy Archaeologist",
|
||||
"rank": 53841,
|
||||
"score": 106
|
||||
},
|
||||
{
|
||||
"id": 35,
|
||||
"name": "Dagannoth Prime",
|
||||
"rank": 18420,
|
||||
"score": 1078
|
||||
},
|
||||
{
|
||||
"id": 36,
|
||||
"name": "Dagannoth Rex",
|
||||
"rank": 23824,
|
||||
"score": 1062
|
||||
},
|
||||
{
|
||||
"id": 37,
|
||||
"name": "Dagannoth Supreme",
|
||||
"rank": 18418,
|
||||
"score": 1088
|
||||
},
|
||||
{
|
||||
"id": 38,
|
||||
"name": "Deranged Archaeologist",
|
||||
"rank": 85908,
|
||||
"score": 25
|
||||
},
|
||||
{
|
||||
"id": 39,
|
||||
"name": "Duke Sucellus",
|
||||
"rank": 6077,
|
||||
"score": 1320
|
||||
},
|
||||
{
|
||||
"id": 40,
|
||||
"name": "General Graardor",
|
||||
"rank": 21980,
|
||||
"score": 1228
|
||||
},
|
||||
{
|
||||
"id": 41,
|
||||
"name": "Giant Mole",
|
||||
"rank": 76561,
|
||||
"score": 477
|
||||
},
|
||||
{
|
||||
"id": 42,
|
||||
"name": "Grotesque Guardians",
|
||||
"rank": 774,
|
||||
"score": 3642
|
||||
},
|
||||
{
|
||||
"id": 43,
|
||||
"name": "Hespori",
|
||||
"rank": 7917,
|
||||
"score": 281
|
||||
},
|
||||
{
|
||||
"id": 44,
|
||||
"name": "Kalphite Queen",
|
||||
"rank": 2313,
|
||||
"score": 2046
|
||||
},
|
||||
{
|
||||
"id": 45,
|
||||
"name": "King Black Dragon",
|
||||
"rank": 52832,
|
||||
"score": 580
|
||||
},
|
||||
{
|
||||
"id": 46,
|
||||
"name": "Kraken",
|
||||
"rank": 64296,
|
||||
"score": 2899
|
||||
},
|
||||
{
|
||||
"id": 47,
|
||||
"name": "Kree'Arra",
|
||||
"rank": 4205,
|
||||
"score": 1740
|
||||
},
|
||||
{
|
||||
"id": 48,
|
||||
"name": "K'ril Tsutsaroth",
|
||||
"rank": 2681,
|
||||
"score": 1444
|
||||
},
|
||||
{
|
||||
"id": 49,
|
||||
"name": "Lunar Chests",
|
||||
"rank": 3424,
|
||||
"score": 324
|
||||
},
|
||||
{
|
||||
"id": 50,
|
||||
"name": "Mimic",
|
||||
"rank": 523,
|
||||
"score": 48
|
||||
},
|
||||
{
|
||||
"id": 51,
|
||||
"name": "Nex",
|
||||
"rank": 35420,
|
||||
"score": 626
|
||||
},
|
||||
{
|
||||
"id": 52,
|
||||
"name": "Nightmare",
|
||||
"rank": 4162,
|
||||
"score": 749
|
||||
},
|
||||
{
|
||||
"id": 53,
|
||||
"name": "Phosani's Nightmare",
|
||||
"rank": 5046,
|
||||
"score": 500
|
||||
},
|
||||
{
|
||||
"id": 54,
|
||||
"name": "Obor",
|
||||
"rank": 1218,
|
||||
"score": 217
|
||||
},
|
||||
{
|
||||
"id": 55,
|
||||
"name": "Phantom Muspah",
|
||||
"rank": 21243,
|
||||
"score": 379
|
||||
},
|
||||
{
|
||||
"id": 56,
|
||||
"name": "Sarachnis",
|
||||
"rank": 40733,
|
||||
"score": 249
|
||||
},
|
||||
{
|
||||
"id": 57,
|
||||
"name": "Scorpia",
|
||||
"rank": 53931,
|
||||
"score": 91
|
||||
},
|
||||
{
|
||||
"id": 58,
|
||||
"name": "Scurrius",
|
||||
"rank": 4870,
|
||||
"score": 1245
|
||||
},
|
||||
{
|
||||
"id": 59,
|
||||
"name": "Skotizo",
|
||||
"rank": 174877,
|
||||
"score": 29
|
||||
},
|
||||
{
|
||||
"id": 60,
|
||||
"name": "Sol Heredit",
|
||||
"rank": 2122,
|
||||
"score": 49
|
||||
},
|
||||
{
|
||||
"id": 61,
|
||||
"name": "Spindel",
|
||||
"rank": 3471,
|
||||
"score": 1569
|
||||
},
|
||||
{
|
||||
"id": 62,
|
||||
"name": "Tempoross",
|
||||
"rank": 50947,
|
||||
"score": 273
|
||||
},
|
||||
{
|
||||
"id": 63,
|
||||
"name": "The Gauntlet",
|
||||
"rank": 236110,
|
||||
"score": 7
|
||||
},
|
||||
{
|
||||
"id": 64,
|
||||
"name": "The Corrupted Gauntlet",
|
||||
"rank": 102423,
|
||||
"score": 201
|
||||
},
|
||||
{
|
||||
"id": 65,
|
||||
"name": "The Hueycoatl",
|
||||
"rank": 2344,
|
||||
"score": 21039
|
||||
},
|
||||
{
|
||||
"id": 66,
|
||||
"name": "The Leviathan",
|
||||
"rank": 10174,
|
||||
"score": 666
|
||||
},
|
||||
{
|
||||
"id": 67,
|
||||
"name": "The Whisperer",
|
||||
"rank": 665,
|
||||
"score": 2072
|
||||
},
|
||||
{
|
||||
"id": 68,
|
||||
"name": "Theatre of Blood",
|
||||
"rank": 23177,
|
||||
"score": 310
|
||||
},
|
||||
{
|
||||
"id": 69,
|
||||
"name": "Theatre of Blood: Hard Mode",
|
||||
"rank": 1967,
|
||||
"score": 419
|
||||
},
|
||||
{
|
||||
"id": 70,
|
||||
"name": "Thermonuclear Smoke Devil",
|
||||
"rank": 8495,
|
||||
"score": 3066
|
||||
},
|
||||
{
|
||||
"id": 71,
|
||||
"name": "Tombs of Amascut",
|
||||
"rank": 122123,
|
||||
"score": 37
|
||||
},
|
||||
{
|
||||
"id": 72,
|
||||
"name": "Tombs of Amascut: Expert Mode",
|
||||
"rank": 240,
|
||||
"score": 1537
|
||||
},
|
||||
{
|
||||
"id": 73,
|
||||
"name": "TzKal-Zuk",
|
||||
"rank": 7756,
|
||||
"score": 7
|
||||
},
|
||||
{
|
||||
"id": 74,
|
||||
"name": "TzTok-Jad",
|
||||
"rank": 140561,
|
||||
"score": 8
|
||||
},
|
||||
{
|
||||
"id": 75,
|
||||
"name": "Vardorvis",
|
||||
"rank": 4736,
|
||||
"score": 2113
|
||||
},
|
||||
{
|
||||
"id": 76,
|
||||
"name": "Venenatis",
|
||||
"rank": 249,
|
||||
"score": 7096
|
||||
},
|
||||
{
|
||||
"id": 77,
|
||||
"name": "Vet'ion",
|
||||
"rank": 89194,
|
||||
"score": 51
|
||||
},
|
||||
{
|
||||
"id": 78,
|
||||
"name": "Vorkath",
|
||||
"rank": 15067,
|
||||
"score": 2780
|
||||
},
|
||||
{
|
||||
"id": 79,
|
||||
"name": "Wintertodt",
|
||||
"rank": 975929,
|
||||
"score": 62
|
||||
},
|
||||
{
|
||||
"id": 80,
|
||||
"name": "Zalcano",
|
||||
"rank": 196393,
|
||||
"score": 25
|
||||
},
|
||||
{
|
||||
"id": 81,
|
||||
"name": "Zulrah",
|
||||
"rank": 227062,
|
||||
"score": 340
|
||||
}
|
||||
]
|
||||
}
|
@@ -1,322 +1,611 @@
|
||||
import axios from 'axios';
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
import {
|
||||
parseStats,
|
||||
getRSNFormat,
|
||||
getSkillPage,
|
||||
getStats,
|
||||
getStatsByGamemode,
|
||||
getRSNFormat,
|
||||
Stats,
|
||||
getPlayerTableURL,
|
||||
getSkillPageURL,
|
||||
getStatsURL,
|
||||
BOSSES,
|
||||
InvalidFormatError,
|
||||
BH_MODES,
|
||||
parseJsonStats,
|
||||
HiscoresResponse
|
||||
} from '../src/index';
|
||||
import { PlayerSkillRow, Player, Stats } from '../src/types';
|
||||
import axios, { AxiosError } from 'axios';
|
||||
|
||||
const B0ATY_NAME = 'B0ATY';
|
||||
const B0ATY_FORMATTED_NAME = 'B0aty';
|
||||
const LYNX_TITAN_SPACE_NAME = 'lYnX tiTaN';
|
||||
const LYNX_TITAN_UNDERSCORE_NAME = 'lYnX_tiTaN';
|
||||
const LYNX_TITAN_HYPHEN_NAME = 'lYnX-tiTaN';
|
||||
const LYNX_TITAN_FORMATTED_NAME = 'Lynx Titan';
|
||||
|
||||
const attackTopPage = readFileSync(`${__dirname}/attackTopPage.html`, 'utf8');
|
||||
const b0atyNamePage = readFileSync(`${__dirname}/b0atyNamePage.html`, 'utf8');
|
||||
const b0atyStatsCsv = readFileSync(`${__dirname}/b0atyStats.csv`, 'utf8');
|
||||
const b0atyStatsJson: HiscoresResponse = JSON.parse(
|
||||
readFileSync(`${__dirname}/b0atyStats.json`, 'utf8')
|
||||
);
|
||||
const lynxTitanStats = JSON.parse(
|
||||
readFileSync(`${__dirname}/lynxTitanStats.json`, 'utf8')
|
||||
);
|
||||
const lynxTitanNamePage = readFileSync(
|
||||
`${__dirname}/lynxTitanNamePage.html`,
|
||||
'utf8'
|
||||
);
|
||||
|
||||
jest.spyOn(axios, 'get').mockImplementation((url) => {
|
||||
const lynxUrls = [
|
||||
getPlayerTableURL('main', LYNX_TITAN_SPACE_NAME),
|
||||
getPlayerTableURL('main', LYNX_TITAN_UNDERSCORE_NAME),
|
||||
getPlayerTableURL('main', LYNX_TITAN_HYPHEN_NAME)
|
||||
];
|
||||
if (lynxUrls.includes(url)) {
|
||||
return Promise.resolve({ data: lynxTitanNamePage });
|
||||
}
|
||||
if (getPlayerTableURL('main', B0ATY_NAME) === url) {
|
||||
return Promise.resolve({ data: b0atyNamePage });
|
||||
}
|
||||
if (getSkillPageURL('main', 'attack', 1) === url) {
|
||||
return Promise.resolve({ data: attackTopPage });
|
||||
}
|
||||
if (getStatsURL('main', LYNX_TITAN_FORMATTED_NAME, true) === url) {
|
||||
return Promise.resolve({ status: 200, data: lynxTitanStats });
|
||||
}
|
||||
throw new Error(`No mock response for URL: ${url}`);
|
||||
});
|
||||
|
||||
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
|
||||
23423,478
|
||||
89554,301
|
||||
89914,35
|
||||
99834,25
|
||||
99831,23
|
||||
89912,37
|
||||
32,12148
|
||||
3105,76
|
||||
1997,505
|
||||
127,4259
|
||||
361,915
|
||||
392,250
|
||||
1,6143
|
||||
4814,898
|
||||
13,4057
|
||||
37,225
|
||||
25,1110
|
||||
7827,43249
|
||||
382,2780
|
||||
944,3000
|
||||
561,398429
|
||||
37,111
|
||||
704,892
|
||||
1981,1452
|
||||
4981,23
|
||||
888,1046
|
||||
444,792
|
||||
613,4856
|
||||
102,4038
|
||||
156,334
|
||||
6240,133
|
||||
4569,250
|
||||
6893,603
|
||||
1,17798
|
||||
9320,125
|
||||
1030,2802
|
||||
4342,1655
|
||||
966,2951
|
||||
10151,1
|
||||
1289,2477
|
||||
1288,2407
|
||||
377,4669
|
||||
545,1567
|
||||
6083,94
|
||||
264,2897
|
||||
4052,1277
|
||||
41643,1477
|
||||
625,2391
|
||||
120,2981
|
||||
54,2958
|
||||
1,109
|
||||
99,39002
|
||||
3,22666
|
||||
2,84
|
||||
26,323
|
||||
44,6233
|
||||
201,1101
|
||||
82,3404
|
||||
555,7079
|
||||
5085,61
|
||||
8731,1423
|
||||
678,903
|
||||
18823,23
|
||||
63,375
|
||||
2870,6
|
||||
891,9102
|
||||
2871,7
|
||||
2872,8
|
||||
6984,138
|
||||
23,923141
|
||||
4043,2000
|
||||
4073,1020
|
||||
289,13070
|
||||
489,8
|
||||
967,47
|
||||
968,48
|
||||
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: {
|
||||
rogue: { rank: -1, score: -1 },
|
||||
hunter: { rank: -1, score: -1 },
|
||||
leaguePoints: { rank: 23423, score: 478 },
|
||||
deadmanPoints: { rank: 89554, score: 301 },
|
||||
bountyHunter: {
|
||||
hunterV2: { rank: 89914, score: 35 },
|
||||
rogueV2: { rank: 99834, score: 25 },
|
||||
hunter: { rank: 99831, score: 23 },
|
||||
rogue: { rank: 89912, score: 37 }
|
||||
},
|
||||
lms: { rank: 32875, score: 500 },
|
||||
lastManStanding: { rank: 4814, score: 898 },
|
||||
pvpArena: { rank: 13, score: 4057 },
|
||||
soulWarsZeal: { rank: 37, score: 225 },
|
||||
riftsClosed: { rank: 25, score: 1110 },
|
||||
colosseumGlory: { rank: 7827, score: 43249 },
|
||||
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 },
|
||||
amoxliatl: { rank: 561, score: 398429 },
|
||||
araxxor: { rank: 37, score: 111 },
|
||||
artio: { rank: 704, score: 892 },
|
||||
barrows: { rank: 1981, score: 1452 },
|
||||
bryophyta: { rank: 4981, score: 23 },
|
||||
callisto: { rank: 888, score: 1046 },
|
||||
calvarion: { rank: 444, score: 792 },
|
||||
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 },
|
||||
dukeSucellus: { rank: 1289, score: 2477 },
|
||||
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 },
|
||||
lunarChests: { rank: 54, score: 2958 },
|
||||
mimic: { rank: 1, score: 109 },
|
||||
nex: { rank: 99, score: 39002 },
|
||||
nightmare: { rank: 3, score: 22666 },
|
||||
phosanisNightmare: { rank: 2, score: 84 },
|
||||
obor: { rank: 26, score: 323 },
|
||||
phantomMuspah: { rank: 44, score: 6233 },
|
||||
sarachnis: { rank: 201, score: 1101 },
|
||||
scorpia: { rank: 82, score: 3404 },
|
||||
scurrius: { rank: 555, score: 7079 },
|
||||
skotizo: { rank: 5085, score: 61 },
|
||||
solHeredit: { rank: 8731, score: 1423 },
|
||||
spindel: { rank: 678, score: 903 },
|
||||
tempoross: { rank: 18823, score: 23 },
|
||||
gauntlet: { rank: 63, score: 375 },
|
||||
corruptedGauntlet: { rank: 2870, score: 6 },
|
||||
hueycoatl: { rank: 891, score: 9102 },
|
||||
leviathan: { rank: 2871, score: 7 },
|
||||
whisperer: { rank: 2872, score: 8 },
|
||||
theatreOfBlood: { rank: 6984, score: 138 },
|
||||
theatreOfBloodHardMode: { rank: 23, score: 923141 },
|
||||
thermonuclearSmokeDevil: { rank: 4043, score: 2000 },
|
||||
tombsOfAmascut: { rank: 4073, score: 1020 },
|
||||
tombsOfAmascutExpertMode: { rank: 289, score: 13070 },
|
||||
tzKalZuk: { rank: 489, score: 8 },
|
||||
tzTokJad: { rank: 967, score: 47 },
|
||||
vardorvis: { rank: 968, score: 48 },
|
||||
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 }
|
||||
}
|
||||
};
|
||||
|
||||
expect(parseStats(csv)).toStrictEqual(expectedOutput);
|
||||
});
|
||||
|
||||
test('Parse CSV with unknown activity', () => {
|
||||
const statsWithUnknownActivity = `${lynxTitanStats}
|
||||
-1,-1`;
|
||||
expect(() => parseStats(statsWithUnknownActivity)).toThrow(
|
||||
InvalidFormatError
|
||||
);
|
||||
});
|
||||
|
||||
test('Parse invalid CSV', () => {
|
||||
expect(() => parseStats('invalid')).toThrow(InvalidFormatError);
|
||||
});
|
||||
|
||||
describe('Get name format', () => {
|
||||
it('gets a name with a space', async () => {
|
||||
const data = await getRSNFormat(LYNX_TITAN_SPACE_NAME);
|
||||
expect(data).toBe(LYNX_TITAN_FORMATTED_NAME);
|
||||
});
|
||||
it('gets a name with an underscore', async () => {
|
||||
const data = await getRSNFormat(LYNX_TITAN_UNDERSCORE_NAME);
|
||||
expect(data).toBe(LYNX_TITAN_FORMATTED_NAME);
|
||||
});
|
||||
it('gets a name with a hyphen', async () => {
|
||||
const data = await getRSNFormat(LYNX_TITAN_HYPHEN_NAME);
|
||||
expect(data).toBe(LYNX_TITAN_FORMATTED_NAME);
|
||||
});
|
||||
it('gets a name with a number', async () => {
|
||||
const data = await getRSNFormat(B0ATY_NAME);
|
||||
expect(data).toBe(B0ATY_FORMATTED_NAME);
|
||||
});
|
||||
it('throws an error for a name with invalid characters', async () => {
|
||||
await expect(getRSNFormat('b&aty')).rejects.toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
test('Get rsn format', async done => {
|
||||
const callback = (data: string) => {
|
||||
expect(data).toBe('Lynx Titan');
|
||||
done();
|
||||
};
|
||||
|
||||
getRSNFormat('lYnX tiTaN').then(callback);
|
||||
});
|
||||
|
||||
test('Get attack top page', async done => {
|
||||
const callback = (data: PlayerSkillRow[]) => {
|
||||
test('Get attack top page', async () => {
|
||||
const data = await getSkillPage('attack');
|
||||
expect(data).toMatchObject([
|
||||
{
|
||||
rsn: expect.any(String),
|
||||
name: expect.any(String),
|
||||
rank: 1,
|
||||
level: 99,
|
||||
xp: 200000000,
|
||||
dead: false,
|
||||
dead: false
|
||||
},
|
||||
{
|
||||
rsn: expect.any(String),
|
||||
name: expect.any(String),
|
||||
rank: 2,
|
||||
level: 99,
|
||||
xp: 200000000,
|
||||
dead: false,
|
||||
dead: false
|
||||
},
|
||||
{ rsn: 'Drakon', rank: 3, level: 99, xp: 200000000, dead: false },
|
||||
{
|
||||
rsn: expect.any(String),
|
||||
name: expect.any(String),
|
||||
rank: 3,
|
||||
level: 99,
|
||||
xp: 200000000,
|
||||
dead: false
|
||||
},
|
||||
{
|
||||
name: expect.any(String),
|
||||
rank: 4,
|
||||
level: 99,
|
||||
xp: 200000000,
|
||||
dead: false,
|
||||
dead: false
|
||||
},
|
||||
{
|
||||
rsn: expect.any(String),
|
||||
name: expect.any(String),
|
||||
rank: 5,
|
||||
level: 99,
|
||||
xp: 200000000,
|
||||
dead: false,
|
||||
dead: false
|
||||
},
|
||||
{
|
||||
rsn: expect.any(String),
|
||||
name: expect.any(String),
|
||||
rank: 6,
|
||||
level: 99,
|
||||
xp: 200000000,
|
||||
dead: false,
|
||||
dead: false
|
||||
},
|
||||
{
|
||||
rsn: expect.any(String),
|
||||
name: expect.any(String),
|
||||
rank: 7,
|
||||
level: 99,
|
||||
xp: 200000000,
|
||||
dead: false,
|
||||
dead: false
|
||||
},
|
||||
{
|
||||
rsn: expect.any(String),
|
||||
name: expect.any(String),
|
||||
rank: 8,
|
||||
level: 99,
|
||||
xp: 200000000,
|
||||
dead: false,
|
||||
dead: false
|
||||
},
|
||||
{
|
||||
rsn: expect.any(String),
|
||||
name: expect.any(String),
|
||||
rank: 9,
|
||||
level: 99,
|
||||
xp: 200000000,
|
||||
dead: false,
|
||||
dead: false
|
||||
},
|
||||
{
|
||||
rsn: expect.any(String),
|
||||
name: expect.any(String),
|
||||
rank: 10,
|
||||
level: 99,
|
||||
xp: 200000000,
|
||||
dead: false,
|
||||
dead: false
|
||||
},
|
||||
{
|
||||
rsn: expect.any(String),
|
||||
name: expect.any(String),
|
||||
rank: 11,
|
||||
level: 99,
|
||||
xp: 200000000,
|
||||
dead: false,
|
||||
dead: false
|
||||
},
|
||||
{
|
||||
rsn: expect.any(String),
|
||||
name: expect.any(String),
|
||||
rank: 12,
|
||||
level: 99,
|
||||
xp: 200000000,
|
||||
dead: false,
|
||||
dead: false
|
||||
},
|
||||
{
|
||||
rsn: expect.any(String),
|
||||
name: expect.any(String),
|
||||
rank: 13,
|
||||
level: 99,
|
||||
xp: 200000000,
|
||||
dead: false,
|
||||
dead: false
|
||||
},
|
||||
{
|
||||
rsn: expect.any(String),
|
||||
name: expect.any(String),
|
||||
rank: 14,
|
||||
level: 99,
|
||||
xp: 200000000,
|
||||
dead: false,
|
||||
dead: false
|
||||
},
|
||||
{
|
||||
rsn: expect.any(String),
|
||||
name: expect.any(String),
|
||||
rank: 15,
|
||||
level: 99,
|
||||
xp: 200000000,
|
||||
dead: false,
|
||||
dead: false
|
||||
},
|
||||
{
|
||||
rsn: expect.any(String),
|
||||
name: expect.any(String),
|
||||
rank: 16,
|
||||
level: 99,
|
||||
xp: 200000000,
|
||||
dead: false,
|
||||
dead: false
|
||||
},
|
||||
{
|
||||
rsn: expect.any(String),
|
||||
name: expect.any(String),
|
||||
rank: 17,
|
||||
level: 99,
|
||||
xp: 200000000,
|
||||
dead: false,
|
||||
dead: false
|
||||
},
|
||||
{
|
||||
rsn: expect.any(String),
|
||||
name: expect.any(String),
|
||||
rank: 18,
|
||||
level: 99,
|
||||
xp: 200000000,
|
||||
dead: false,
|
||||
dead: false
|
||||
},
|
||||
{
|
||||
rsn: expect.any(String),
|
||||
name: expect.any(String),
|
||||
rank: 19,
|
||||
level: 99,
|
||||
xp: 200000000,
|
||||
dead: false,
|
||||
dead: false
|
||||
},
|
||||
{
|
||||
rsn: expect.any(String),
|
||||
name: expect.any(String),
|
||||
rank: 20,
|
||||
level: 99,
|
||||
xp: 200000000,
|
||||
dead: false,
|
||||
dead: false
|
||||
},
|
||||
{
|
||||
rsn: expect.any(String),
|
||||
name: expect.any(String),
|
||||
rank: 21,
|
||||
level: 99,
|
||||
xp: 200000000,
|
||||
dead: false,
|
||||
dead: false
|
||||
},
|
||||
{
|
||||
rsn: expect.any(String),
|
||||
name: expect.any(String),
|
||||
rank: 22,
|
||||
level: 99,
|
||||
xp: 200000000,
|
||||
dead: false,
|
||||
dead: false
|
||||
},
|
||||
{
|
||||
rsn: expect.any(String),
|
||||
name: expect.any(String),
|
||||
rank: 23,
|
||||
level: 99,
|
||||
xp: 200000000,
|
||||
dead: false,
|
||||
dead: false
|
||||
},
|
||||
{
|
||||
rsn: expect.any(String),
|
||||
name: expect.any(String),
|
||||
rank: 24,
|
||||
level: 99,
|
||||
xp: 200000000,
|
||||
dead: false,
|
||||
dead: false
|
||||
},
|
||||
{
|
||||
rsn: expect.any(String),
|
||||
name: expect.any(String),
|
||||
rank: 25,
|
||||
level: 99,
|
||||
xp: 200000000,
|
||||
dead: false,
|
||||
},
|
||||
dead: false
|
||||
}
|
||||
]);
|
||||
done();
|
||||
};
|
||||
|
||||
getSkillPage('attack').then(callback);
|
||||
});
|
||||
|
||||
test('Get non-existant player', async done => {
|
||||
const callback = (err: AxiosError) => {
|
||||
test('Get non-existent player', async () => {
|
||||
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 () => {
|
||||
const { skills, bosses, bountyHunter } = await getStatsByGamemode(
|
||||
LYNX_TITAN_FORMATTED_NAME
|
||||
);
|
||||
|
||||
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 }
|
||||
});
|
||||
|
||||
const bossKeys = Object.keys(bosses);
|
||||
expect(bossKeys).toStrictEqual(BOSSES);
|
||||
const bountyHunterKeys = Object.keys(bountyHunter);
|
||||
expect(bountyHunterKeys).toStrictEqual(BH_MODES);
|
||||
|
||||
expect.assertions(3);
|
||||
});
|
||||
|
||||
describe('Get stats options', () => {
|
||||
const rsn = 'player';
|
||||
let axiosMock: jest.Mock;
|
||||
beforeEach(() => {
|
||||
axios.get = jest.fn(
|
||||
(url) =>
|
||||
new Promise<any>((resolve) => {
|
||||
resolve(
|
||||
url === getPlayerTableURL('main', rsn)
|
||||
? { data: lynxTitanNamePage }
|
||||
: { status: 200, data: lynxTitanStats }
|
||||
);
|
||||
})
|
||||
);
|
||||
axiosMock = axios.get as any;
|
||||
axiosMock.mockClear();
|
||||
});
|
||||
it('fetches all gamemodes and formatted RSN when no options provided', async () => {
|
||||
await getStats(rsn);
|
||||
expect(axiosMock.mock.calls.map((val) => val[0])).toEqual([
|
||||
getStatsURL('main', rsn, true),
|
||||
getPlayerTableURL('main', rsn),
|
||||
getStatsURL('ironman', rsn, true),
|
||||
getStatsURL('hardcore', rsn, true),
|
||||
getStatsURL('ultimate', rsn, true)
|
||||
]);
|
||||
});
|
||||
it('skips fetching formatted RSN when option is provided', async () => {
|
||||
await getStats(rsn, { shouldGetFormattedRsn: false });
|
||||
expect(
|
||||
axiosMock.mock.calls.some(
|
||||
(val) => val[0] === getPlayerTableURL('main', rsn)
|
||||
)
|
||||
).toBeFalsy();
|
||||
});
|
||||
it('skips fetching game mode when option is provided', async () => {
|
||||
await getStats(rsn, {
|
||||
otherGamemodes: ['ironman', 'ultimate']
|
||||
});
|
||||
expect(
|
||||
axiosMock.mock.calls.some(
|
||||
(val) => val[0] === getStatsURL('hardcore', rsn)
|
||||
)
|
||||
).toBeFalsy();
|
||||
});
|
||||
it('omits excluded gamemodes', async () => {
|
||||
const response = await getStats(rsn, {
|
||||
otherGamemodes: ['ironman', 'ultimate']
|
||||
});
|
||||
expect(response.hardcore).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
test('CSV and JSON parsing outputs identical object', async () => {
|
||||
const csvOutput = parseStats(b0atyStatsCsv);
|
||||
const jsonOutput = parseJsonStats(b0atyStatsJson);
|
||||
expect(csvOutput).toEqual(jsonOutput);
|
||||
});
|
||||
|
1191
__tests__/lynxTitanNamePage.html
vendored
Normal file
1191
__tests__/lynxTitanNamePage.html
vendored
Normal file
File diff suppressed because it is too large
Load Diff
666
__tests__/lynxTitanStats.json
Normal file
666
__tests__/lynxTitanStats.json
Normal file
@@ -0,0 +1,666 @@
|
||||
{
|
||||
"skills": [
|
||||
{
|
||||
"id": 0,
|
||||
"name": "Overall",
|
||||
"rank": 1,
|
||||
"level": 2277,
|
||||
"xp": 4600000000
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Attack",
|
||||
"rank": 15,
|
||||
"level": 99,
|
||||
"xp": 200000000
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Defence",
|
||||
"rank": 28,
|
||||
"level": 99,
|
||||
"xp": 200000000
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Strength",
|
||||
"rank": 18,
|
||||
"level": 99,
|
||||
"xp": 200000000
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Hitpoints",
|
||||
"rank": 7,
|
||||
"level": 99,
|
||||
"xp": 200000000
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "Ranged",
|
||||
"rank": 8,
|
||||
"level": 99,
|
||||
"xp": 200000000
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"name": "Prayer",
|
||||
"rank": 11,
|
||||
"level": 99,
|
||||
"xp": 200000000
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"name": "Magic",
|
||||
"rank": 30,
|
||||
"level": 99,
|
||||
"xp": 200000000
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"name": "Cooking",
|
||||
"rank": 154,
|
||||
"level": 99,
|
||||
"xp": 200000000
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"name": "Woodcutting",
|
||||
"rank": 15,
|
||||
"level": 99,
|
||||
"xp": 200000000
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"name": "Fletching",
|
||||
"rank": 12,
|
||||
"level": 99,
|
||||
"xp": 200000000
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"name": "Fishing",
|
||||
"rank": 9,
|
||||
"level": 99,
|
||||
"xp": 200000000
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"name": "Firemaking",
|
||||
"rank": 48,
|
||||
"level": 99,
|
||||
"xp": 200000000
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"name": "Crafting",
|
||||
"rank": 4,
|
||||
"level": 99,
|
||||
"xp": 200000000
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"name": "Smithing",
|
||||
"rank": 3,
|
||||
"level": 99,
|
||||
"xp": 200000000
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"name": "Mining",
|
||||
"rank": 24,
|
||||
"level": 99,
|
||||
"xp": 200000000
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"name": "Herblore",
|
||||
"rank": 5,
|
||||
"level": 99,
|
||||
"xp": 200000000
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"name": "Agility",
|
||||
"rank": 24,
|
||||
"level": 99,
|
||||
"xp": 200000000
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"name": "Thieving",
|
||||
"rank": 12,
|
||||
"level": 99,
|
||||
"xp": 200000000
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"name": "Slayer",
|
||||
"rank": 2,
|
||||
"level": 99,
|
||||
"xp": 200000000
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"name": "Farming",
|
||||
"rank": 19,
|
||||
"level": 99,
|
||||
"xp": 200000000
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"name": "Runecraft",
|
||||
"rank": 6,
|
||||
"level": 99,
|
||||
"xp": 200000000
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"name": "Hunter",
|
||||
"rank": 4,
|
||||
"level": 99,
|
||||
"xp": 200000000
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"name": "Construction",
|
||||
"rank": 4,
|
||||
"level": 99,
|
||||
"xp": 200000000
|
||||
}
|
||||
],
|
||||
"activities": [
|
||||
{
|
||||
"id": 0,
|
||||
"name": "League Points",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Deadman Points",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Bounty Hunter - Hunter",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Bounty Hunter - Rogue",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Bounty Hunter (Legacy) - Hunter",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "Bounty Hunter (Legacy) - Rogue",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"name": "Clue Scrolls (all)",
|
||||
"rank": 832420,
|
||||
"score": 22
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"name": "Clue Scrolls (beginner)",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"name": "Clue Scrolls (easy)",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"name": "Clue Scrolls (medium)",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"name": "Clue Scrolls (hard)",
|
||||
"rank": 517091,
|
||||
"score": 22
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"name": "Clue Scrolls (elite)",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"name": "Clue Scrolls (master)",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"name": "LMS - Rank",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"name": "PvP Arena - Rank",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"name": "Soul Wars Zeal",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"name": "Rifts closed",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"name": "Colosseum Glory",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"name": "Abyssal Sire",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"name": "Alchemical Hydra",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"name": "Amoxliatl",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"name": "Araxxor",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"name": "Artio",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"name": "Barrows Chests",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"name": "Bryophyta",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 25,
|
||||
"name": "Callisto",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 26,
|
||||
"name": "Calvar'ion",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"name": "Cerberus",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"name": "Chambers of Xeric",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 29,
|
||||
"name": "Chambers of Xeric: Challenge Mode",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 30,
|
||||
"name": "Chaos Elemental",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 31,
|
||||
"name": "Chaos Fanatic",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 32,
|
||||
"name": "Commander Zilyana",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 33,
|
||||
"name": "Corporeal Beast",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 34,
|
||||
"name": "Crazy Archaeologist",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 35,
|
||||
"name": "Dagannoth Prime",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 36,
|
||||
"name": "Dagannoth Rex",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 37,
|
||||
"name": "Dagannoth Supreme",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 38,
|
||||
"name": "Deranged Archaeologist",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 39,
|
||||
"name": "Duke Sucellus",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 40,
|
||||
"name": "General Graardor",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 41,
|
||||
"name": "Giant Mole",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 42,
|
||||
"name": "Grotesque Guardians",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 43,
|
||||
"name": "Hespori",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 44,
|
||||
"name": "Kalphite Queen",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 45,
|
||||
"name": "King Black Dragon",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 46,
|
||||
"name": "Kraken",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 47,
|
||||
"name": "Kree'Arra",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 48,
|
||||
"name": "K'ril Tsutsaroth",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 49,
|
||||
"name": "Lunar Chests",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 50,
|
||||
"name": "Mimic",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 51,
|
||||
"name": "Nex",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 52,
|
||||
"name": "Nightmare",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 53,
|
||||
"name": "Phosani's Nightmare",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 54,
|
||||
"name": "Obor",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 55,
|
||||
"name": "Phantom Muspah",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 56,
|
||||
"name": "Sarachnis",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 57,
|
||||
"name": "Scorpia",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 58,
|
||||
"name": "Scurrius",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 59,
|
||||
"name": "Skotizo",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 60,
|
||||
"name": "Sol Heredit",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 61,
|
||||
"name": "Spindel",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 62,
|
||||
"name": "Tempoross",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 63,
|
||||
"name": "The Gauntlet",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 64,
|
||||
"name": "The Corrupted Gauntlet",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 65,
|
||||
"name": "The Hueycoatl",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 66,
|
||||
"name": "The Leviathan",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 67,
|
||||
"name": "The Whisperer",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 68,
|
||||
"name": "Theatre of Blood",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 69,
|
||||
"name": "Theatre of Blood: Hard Mode",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 70,
|
||||
"name": "Thermonuclear Smoke Devil",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 71,
|
||||
"name": "Tombs of Amascut",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 72,
|
||||
"name": "Tombs of Amascut: Expert Mode",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 73,
|
||||
"name": "TzKal-Zuk",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 74,
|
||||
"name": "TzTok-Jad",
|
||||
"rank": 251,
|
||||
"score": 186
|
||||
},
|
||||
{
|
||||
"id": 75,
|
||||
"name": "Vardorvis",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 76,
|
||||
"name": "Venenatis",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 77,
|
||||
"name": "Vet'ion",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 78,
|
||||
"name": "Vorkath",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 79,
|
||||
"name": "Wintertodt",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 80,
|
||||
"name": "Zalcano",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
},
|
||||
{
|
||||
"id": 81,
|
||||
"name": "Zulrah",
|
||||
"rank": -1,
|
||||
"score": -1
|
||||
}
|
||||
]
|
||||
}
|
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"transform": {
|
||||
"^.+\\.(t|j)sx?$": "ts-jest"
|
||||
},
|
||||
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
|
||||
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"]
|
||||
}
|
98
package.json
98
package.json
@@ -1,16 +1,18 @@
|
||||
{
|
||||
"name": "osrs-json-hiscores",
|
||||
"version": "1.2.2",
|
||||
"description": "The Oldschool Runescape API wrapper that does more!",
|
||||
"version": "2.19.0",
|
||||
"description": "The Old School Runescape API wrapper that does more!",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"files": [
|
||||
"lib/**/*"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "watch 'yarn run build' src",
|
||||
"dev": "yarn build --watch",
|
||||
"build": "tsc",
|
||||
"test": "jest --config jestconfig.json",
|
||||
"format": "prettier --write \"src/**/*.ts\"",
|
||||
"lint": "eslint --fix \"src/**/*.ts\"",
|
||||
"test": "jest",
|
||||
"prepublish": "yarn run build",
|
||||
"release": "np"
|
||||
},
|
||||
@@ -28,26 +30,90 @@
|
||||
"stats",
|
||||
"skills"
|
||||
],
|
||||
"publishConfig": {
|
||||
"registry": "https://registry.npmjs.org"
|
||||
},
|
||||
"author": "maxswa",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/maxswa/osrs-json-hiscores/issues"
|
||||
},
|
||||
"homepage": "https://github.com/maxswa/osrs-json-hiscores#readme",
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts}": [
|
||||
"eslint --fix",
|
||||
"prettier --write",
|
||||
"git add"
|
||||
]
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.eslint.json"
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"extends": [
|
||||
"airbnb-base",
|
||||
"airbnb-typescript/base",
|
||||
"prettier"
|
||||
],
|
||||
"ignorePatterns": [
|
||||
"**/@types/*"
|
||||
],
|
||||
"rules": {
|
||||
"max-classes-per-file": "off"
|
||||
}
|
||||
},
|
||||
"prettier": {
|
||||
"trailingComma": "none",
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"endOfLine": "auto"
|
||||
},
|
||||
"jest": {
|
||||
"transform": {
|
||||
"^.+\\.(t|j)sx?$": "ts-jest"
|
||||
},
|
||||
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
|
||||
"moduleFileExtensions": [
|
||||
"ts",
|
||||
"tsx",
|
||||
"js",
|
||||
"jsx",
|
||||
"json",
|
||||
"node"
|
||||
],
|
||||
"testEnvironment": "node"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.19.0",
|
||||
"cheerio": "^1.0.0-rc.3"
|
||||
"axios": "^1.6.2",
|
||||
"jsdom": "^22.1.0",
|
||||
"useragent-generator": "^1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cheerio": "^0.22.11",
|
||||
"@types/jest": "^24.0.14",
|
||||
"jest": "^24.8.0",
|
||||
"np": "^5.0.3",
|
||||
"ts-jest": "^24.0.2",
|
||||
"tslint": "^5.17.0",
|
||||
"tslint-config-airbnb": "^5.11.1",
|
||||
"tslint-config-prettier": "^1.18.0",
|
||||
"typescript": "^3.5.2",
|
||||
"watch": "^1.0.2"
|
||||
"@types/jest": "^29.5.3",
|
||||
"@types/jsdom": "^21.1.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"eslint": "^8.44.0",
|
||||
"eslint-config-airbnb-typescript": "^17.1.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"husky": "^5.2.0",
|
||||
"jest": "^29.6.1",
|
||||
"lint-staged": "^13.2.3",
|
||||
"np": "^7.7.0",
|
||||
"prettier": "^3.0.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"typescript": "^5.1.6"
|
||||
}
|
||||
}
|
||||
|
109
src/@types/useragent-generator.d.ts
vendored
Normal file
109
src/@types/useragent-generator.d.ts
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
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;
|
||||
}
|
533
src/hiscores.ts
533
src/hiscores.ts
@@ -1,5 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import * as cheerio from 'cheerio';
|
||||
import axios, { AxiosRequestConfig } from 'axios';
|
||||
import { BinaryData, JSDOM } from 'jsdom';
|
||||
import {
|
||||
Player,
|
||||
Activity,
|
||||
@@ -13,6 +13,9 @@ import {
|
||||
PlayerSkillRow,
|
||||
ActivityName,
|
||||
PlayerActivityRow,
|
||||
Bosses,
|
||||
GetStatsOptions,
|
||||
HiscoresResponse
|
||||
} from './types';
|
||||
import {
|
||||
getStatsURL,
|
||||
@@ -26,64 +29,324 @@ import {
|
||||
numberFromElement,
|
||||
rsnFromElement,
|
||||
getActivityPageURL,
|
||||
httpGet,
|
||||
BOSSES,
|
||||
InvalidFormatError,
|
||||
PlayerNotFoundError,
|
||||
HiScoresError,
|
||||
validateRSN,
|
||||
FORMATTED_SKILL_NAMES,
|
||||
FORMATTED_BH_NAMES,
|
||||
FORMATTED_CLUE_NAMES,
|
||||
FORMATTED_BOSS_NAMES,
|
||||
FORMATTED_LEAGUE_POINTS,
|
||||
FORMATTED_LMS,
|
||||
FORMATTED_PVP_ARENA,
|
||||
FORMATTED_SOUL_WARS,
|
||||
FORMATTED_RIFTS_CLOSED,
|
||||
FORMATTED_DEADMAN_POINTS,
|
||||
FORMATTED_COLOSSEUM_GLORY
|
||||
} from './utils';
|
||||
|
||||
export async function getStats(rsn: string): Promise<Player> {
|
||||
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');
|
||||
/**
|
||||
* Gets a player's stats from the official OSRS JSON endpoint.
|
||||
*
|
||||
* @param rsn Username of the player.
|
||||
* @param mode Gamemode to fetch ranks for.
|
||||
* @param config Optional axios request config object.
|
||||
* @returns Official JSON stats object.
|
||||
*/
|
||||
export async function getOfficialStats(
|
||||
rsn: string,
|
||||
mode: Gamemode = 'main',
|
||||
config?: AxiosRequestConfig
|
||||
): Promise<HiscoresResponse> {
|
||||
validateRSN(rsn);
|
||||
|
||||
const url = getStatsURL(mode, rsn, true);
|
||||
try {
|
||||
const response = await httpGet<HiscoresResponse>(url, config);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
if (!axios.isAxiosError(err)) throw err;
|
||||
|
||||
if (err.response?.status === 404) throw new PlayerNotFoundError();
|
||||
|
||||
throw new HiScoresError();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Screen scrapes the hiscores to get the formatted rsn of a player.
|
||||
*
|
||||
* @param rsn Username of the player.
|
||||
* @param config Optional axios request config object.
|
||||
* @returns Formatted version of the rsn.
|
||||
*/
|
||||
export async function getRSNFormat(
|
||||
rsn: string,
|
||||
config?: AxiosRequestConfig
|
||||
): Promise<string> {
|
||||
validateRSN(rsn);
|
||||
|
||||
const url = getPlayerTableURL('main', rsn);
|
||||
try {
|
||||
const response = await httpGet<string | Buffer | BinaryData | undefined>(
|
||||
url,
|
||||
config
|
||||
);
|
||||
const dom = new JSDOM(response.data);
|
||||
const anchor = dom.window.document.querySelector(
|
||||
'.personal-hiscores__row.personal-hiscores__row--type-highlight a'
|
||||
);
|
||||
if (anchor) {
|
||||
return rsnFromElement(anchor);
|
||||
}
|
||||
throw new PlayerNotFoundError();
|
||||
} catch {
|
||||
throw new HiScoresError();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses official JSON object of raw stats and returns a stats object.
|
||||
*
|
||||
* @param csv Raw JSON from the official OSRS API.
|
||||
* @returns Parsed stats object.
|
||||
*/
|
||||
export function parseJsonStats(json: HiscoresResponse): Stats {
|
||||
const getActivity = (formattedName: string): Activity => {
|
||||
const hiscoresActivity = json.activities.find(
|
||||
// We must match on name here since id is not guaranteed to be the same between updates
|
||||
({ name }) => name.toLowerCase() === formattedName.toLowerCase()
|
||||
);
|
||||
return {
|
||||
rank: hiscoresActivity?.rank ?? -1,
|
||||
score: hiscoresActivity?.score ?? -1
|
||||
};
|
||||
};
|
||||
const reduceActivity = <Key extends string, Reduced = Record<Key, Activity>>(
|
||||
keys: Key[],
|
||||
formattedNames: Record<Key, string>
|
||||
): Reduced =>
|
||||
keys.reduce<Reduced>(
|
||||
(reducer, key) => ({
|
||||
...reducer,
|
||||
[key]: getActivity(formattedNames[key])
|
||||
}),
|
||||
{} as Reduced
|
||||
);
|
||||
|
||||
const skills = SKILLS.reduce<Skills>((skillsObject, skillName) => {
|
||||
const hiscoresSkill = json.skills.find(
|
||||
// We must match on name here since id is not guaranteed to be the same between updates
|
||||
({ name }) =>
|
||||
name.toLowerCase() === FORMATTED_SKILL_NAMES[skillName].toLowerCase()
|
||||
);
|
||||
return {
|
||||
...skillsObject,
|
||||
[skillName]: {
|
||||
rank: hiscoresSkill?.rank ?? -1,
|
||||
level: hiscoresSkill?.level ?? -1,
|
||||
xp: hiscoresSkill?.xp ?? -1
|
||||
}
|
||||
};
|
||||
}, {} as Skills);
|
||||
|
||||
const bountyHunter = reduceActivity(BH_MODES, FORMATTED_BH_NAMES);
|
||||
const clues = reduceActivity(CLUES, FORMATTED_CLUE_NAMES);
|
||||
const bosses = reduceActivity(BOSSES, FORMATTED_BOSS_NAMES);
|
||||
|
||||
const leaguePoints = getActivity(FORMATTED_LEAGUE_POINTS);
|
||||
const deadmanPoints = getActivity(FORMATTED_DEADMAN_POINTS);
|
||||
const lastManStanding = getActivity(FORMATTED_LMS);
|
||||
const pvpArena = getActivity(FORMATTED_PVP_ARENA);
|
||||
const soulWarsZeal = getActivity(FORMATTED_SOUL_WARS);
|
||||
const riftsClosed = getActivity(FORMATTED_RIFTS_CLOSED);
|
||||
const colosseumGlory = getActivity(FORMATTED_COLOSSEUM_GLORY);
|
||||
|
||||
const stats: Stats = {
|
||||
skills,
|
||||
leaguePoints,
|
||||
deadmanPoints,
|
||||
bountyHunter,
|
||||
lastManStanding,
|
||||
pvpArena,
|
||||
soulWarsZeal,
|
||||
riftsClosed,
|
||||
colosseumGlory,
|
||||
clues,
|
||||
bosses
|
||||
};
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses CSV string of raw stats and returns a stats object.
|
||||
*
|
||||
* @param csv Raw CSV from the official OSRS API.
|
||||
* @returns Parsed stats object.
|
||||
*/
|
||||
export function parseStats(csv: string): Stats {
|
||||
const splitCSV = csv
|
||||
.split('\n')
|
||||
.filter((entry) => !!entry)
|
||||
.map((stat) => stat.split(','));
|
||||
|
||||
if (splitCSV.length !== SKILLS.length + ACTIVITIES.length) {
|
||||
throw new InvalidFormatError();
|
||||
}
|
||||
|
||||
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),
|
||||
]);
|
||||
const skillObjects: Skill[] = splitCSV
|
||||
.filter((stat) => stat.length === 3)
|
||||
.map((stat) => {
|
||||
const [rank, level, xp] = stat;
|
||||
const skill: Skill = {
|
||||
rank: parseInt(rank, 10),
|
||||
level: parseInt(level, 10),
|
||||
xp: parseInt(xp, 10)
|
||||
};
|
||||
return skill;
|
||||
});
|
||||
|
||||
const [ironRes, hcRes, ultRes, formattedName] = otherResponses;
|
||||
const activityObjects: Activity[] = splitCSV
|
||||
.filter((stat) => stat.length === 2)
|
||||
.map((stat) => {
|
||||
const [rank, score] = stat;
|
||||
const activity: Activity = {
|
||||
rank: parseInt(rank, 10),
|
||||
score: parseInt(score, 10)
|
||||
};
|
||||
return activity;
|
||||
});
|
||||
|
||||
const [leaguePoints, deadmanPoints] = activityObjects.splice(0, 2);
|
||||
const bhObjects = activityObjects.splice(0, BH_MODES.length);
|
||||
const clueObjects = activityObjects.splice(0, CLUES.length);
|
||||
const [lastManStanding, pvpArena, soulWarsZeal, riftsClosed, colosseumGlory] =
|
||||
activityObjects.splice(0, 5);
|
||||
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 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 bosses: Bosses = bossObjects.reduce<Bosses>((prev, curr, index) => {
|
||||
const newBosses = { ...prev };
|
||||
newBosses[BOSSES[index]] = curr;
|
||||
return newBosses;
|
||||
}, {} as Bosses);
|
||||
|
||||
const stats: Stats = {
|
||||
skills,
|
||||
leaguePoints,
|
||||
deadmanPoints,
|
||||
bountyHunter,
|
||||
lastManStanding,
|
||||
pvpArena,
|
||||
soulWarsZeal,
|
||||
riftsClosed,
|
||||
colosseumGlory,
|
||||
clues,
|
||||
bosses
|
||||
};
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches stats from the OSRS API and consolidates the info into a player object.
|
||||
*
|
||||
* **Note:** This function will make up to 5 separate network requests.
|
||||
* As such, it is highly subject to the performance of the official OSRS API.
|
||||
*
|
||||
* @param rsn Username of the player.
|
||||
* @returns Player object.
|
||||
*/
|
||||
export async function getStats(
|
||||
rsn: string,
|
||||
options?: GetStatsOptions
|
||||
): Promise<Player> {
|
||||
validateRSN(rsn);
|
||||
const otherGamemodes = options?.otherGamemodes ?? [
|
||||
'ironman',
|
||||
'hardcore',
|
||||
'ultimate'
|
||||
];
|
||||
const shouldGetFormattedRsn = options?.shouldGetFormattedRsn ?? true;
|
||||
|
||||
const main = await getOfficialStats(rsn, 'main', options?.axiosConfigs?.main);
|
||||
|
||||
const getModeStats = async (
|
||||
mode: Extract<Gamemode, 'ironman' | 'hardcore' | 'ultimate'>
|
||||
): Promise<HiscoresResponse | undefined> =>
|
||||
otherGamemodes.includes(mode)
|
||||
? getOfficialStats(rsn, mode, options?.axiosConfigs?.[mode]).catch(
|
||||
() => undefined
|
||||
)
|
||||
: undefined;
|
||||
const formattedName = shouldGetFormattedRsn
|
||||
? await getRSNFormat(rsn, options?.axiosConfigs?.rsn).catch(() => undefined)
|
||||
: undefined;
|
||||
|
||||
const player: Player = {
|
||||
rsn: formattedName,
|
||||
name: formattedName ?? rsn,
|
||||
mode: 'main',
|
||||
dead: false,
|
||||
deulted: false,
|
||||
deironed: false,
|
||||
deironed: false
|
||||
};
|
||||
player.main = parseStats(mainRes.data);
|
||||
player.main = parseJsonStats(main);
|
||||
|
||||
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) {
|
||||
const iron = await getModeStats('ironman');
|
||||
if (iron) {
|
||||
player.ironman = parseJsonStats(iron);
|
||||
const hc = await getModeStats('hardcore');
|
||||
const ult = await getModeStats('ultimate');
|
||||
if (hc) {
|
||||
player.mode = 'hardcore';
|
||||
player.hardcore = parseJsonStats(hc);
|
||||
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) {
|
||||
} else if (ult) {
|
||||
player.mode = 'ultimate';
|
||||
player.ultimate = parseJsonStats(ult);
|
||||
if (
|
||||
player.ironman.skills.overall.xp !== player.ultimate.skills.overall.xp
|
||||
) {
|
||||
player.deulted = true;
|
||||
player.mode = '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';
|
||||
}
|
||||
@@ -91,28 +354,27 @@ export async function getStats(rsn: string): Promise<Player> {
|
||||
}
|
||||
|
||||
return player;
|
||||
}
|
||||
throw Error('Player not found');
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches stats from the OSRS API and returns them as an object.
|
||||
*
|
||||
* @param rsn Username of the player.
|
||||
* @param mode Gamemode to fetch ranks for.
|
||||
* @param config Optional axios request config object.
|
||||
* @returns Stats object.
|
||||
*/
|
||||
export async function getStatsByGamemode(
|
||||
rsn: string,
|
||||
mode: Gamemode = 'main'
|
||||
mode: Gamemode = 'main',
|
||||
config?: AxiosRequestConfig
|
||||
): 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)) {
|
||||
validateRSN(rsn);
|
||||
if (!GAMEMODES.includes(mode)) {
|
||||
throw Error('Invalid game mode');
|
||||
}
|
||||
const response = await axios(getStatsURL(mode, rsn));
|
||||
if (response.status !== 200) {
|
||||
throw Error('Player not found');
|
||||
}
|
||||
const stats: Stats = parseStats(response.data);
|
||||
const response = await getOfficialStats(rsn, mode, config);
|
||||
const stats = parseJsonStats(response);
|
||||
|
||||
return stats;
|
||||
}
|
||||
@@ -120,7 +382,8 @@ export async function getStatsByGamemode(
|
||||
export async function getSkillPage(
|
||||
skill: SkillName,
|
||||
mode: Gamemode = 'main',
|
||||
page: number = 1
|
||||
page: number = 1,
|
||||
config?: AxiosRequestConfig
|
||||
): Promise<PlayerSkillRow[]> {
|
||||
if (!GAMEMODES.includes(mode)) {
|
||||
throw Error('Invalid game mode');
|
||||
@@ -131,31 +394,49 @@ 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 response = await httpGet<string | Buffer | BinaryData | undefined>(
|
||||
url,
|
||||
config
|
||||
);
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Screen scrapes a hiscores page of an activity or boss and returns an array of up to 25 players.
|
||||
*
|
||||
* @param activity Name of the activity or boss to fetch hiscores for.
|
||||
* @param mode Gamemode to fetch ranks for.
|
||||
* @param page Page number.
|
||||
* @param config Optional axios request config object.
|
||||
* @returns Array of `PlayerActivityRow` objects.
|
||||
*/
|
||||
export async function getActivityPage(
|
||||
activity: ActivityName,
|
||||
mode: Gamemode = 'main',
|
||||
page: number = 1
|
||||
page: number = 1,
|
||||
config?: AxiosRequestConfig
|
||||
): Promise<PlayerActivityRow[]> {
|
||||
if (!GAMEMODES.includes(mode)) {
|
||||
throw Error('Invalid game mode');
|
||||
@@ -166,115 +447,29 @@ 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 response = await httpGet<string | Buffer | BinaryData | undefined>(
|
||||
url,
|
||||
config
|
||||
);
|
||||
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;
|
||||
}
|
||||
|
||||
export async function getRSNFormat(rsn: string): Promise<string> {
|
||||
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');
|
||||
}
|
||||
|
||||
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, ' ');
|
||||
}
|
||||
throw Error('Player not found');
|
||||
} catch {
|
||||
throw Error('Player not found');
|
||||
}
|
||||
}
|
||||
|
||||
export function parseStats(csv: string): Stats {
|
||||
const splitCSV = csv
|
||||
.split('\n')
|
||||
.filter(entry => !!entry)
|
||||
.map(stat => stat.split(','));
|
||||
|
||||
const skillObjects: Skill[] = splitCSV
|
||||
.filter(stat => stat.length === 3)
|
||||
.map(stat => {
|
||||
const [rank, level, xp] = stat;
|
||||
const skill: Skill = {
|
||||
rank: parseInt(rank, 10),
|
||||
level: parseInt(level, 10),
|
||||
xp: parseInt(xp, 10),
|
||||
};
|
||||
return skill;
|
||||
});
|
||||
|
||||
const activityObjects: Activity[] = splitCSV
|
||||
.filter(stat => stat.length === 2)
|
||||
.map(stat => {
|
||||
const [rank, score] = stat;
|
||||
const activity: Activity = {
|
||||
rank: parseInt(rank, 10),
|
||||
score: parseInt(score, 10),
|
||||
};
|
||||
return activity;
|
||||
});
|
||||
|
||||
const bhObjects = activityObjects.splice(0, BH_MODES.length);
|
||||
const [lms] = activityObjects.splice(0, 1);
|
||||
const clueObjects = activityObjects.splice(0, CLUES.length);
|
||||
|
||||
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 clues: Clues = clueObjects.reduce<Clues>(
|
||||
(prev, curr, index) => {
|
||||
const newClues = { ...prev };
|
||||
newClues[CLUES[index]] = curr;
|
||||
return newClues;
|
||||
},
|
||||
{} as Clues
|
||||
);
|
||||
|
||||
const stats: Stats = {
|
||||
skills,
|
||||
bh,
|
||||
lms,
|
||||
clues,
|
||||
};
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
@@ -2,5 +2,6 @@ import * as hiscores from './hiscores';
|
||||
|
||||
export * from './hiscores';
|
||||
export * from './types';
|
||||
export * from './utils';
|
||||
|
||||
export default hiscores;
|
||||
|
197
src/types.ts
197
src/types.ts
@@ -1,4 +1,16 @@
|
||||
export type Gamemode = 'main' | 'iron' | 'hc' | 'ult' | 'dmm' | 'sdmm' | 'dmmt';
|
||||
import { AxiosRequestConfig } from 'axios';
|
||||
|
||||
export type Gamemode =
|
||||
| 'main'
|
||||
| 'ironman'
|
||||
| 'ultimate'
|
||||
| 'hardcore'
|
||||
| 'deadman'
|
||||
| 'seasonal'
|
||||
| 'tournament'
|
||||
| 'skiller'
|
||||
| 'oneDefence'
|
||||
| 'freshStart';
|
||||
|
||||
export interface Skill {
|
||||
rank: number;
|
||||
@@ -50,33 +62,122 @@ export type ClueType =
|
||||
|
||||
export type Clues = { [Type in ClueType]: Activity };
|
||||
|
||||
export type BHType = 'rogue' | 'hunter';
|
||||
export type BHType = 'rogue' | 'hunter' | 'rogueV2' | 'hunterV2';
|
||||
|
||||
export type BH = { [Type in BHType]: Activity };
|
||||
|
||||
export type Boss =
|
||||
| 'abyssalSire'
|
||||
| 'alchemicalHydra'
|
||||
| 'amoxliatl'
|
||||
| 'araxxor'
|
||||
| 'artio'
|
||||
| 'barrows'
|
||||
| 'bryophyta'
|
||||
| 'callisto'
|
||||
| 'calvarion'
|
||||
| 'cerberus'
|
||||
| 'chambersOfXeric'
|
||||
| 'chambersOfXericChallengeMode'
|
||||
| 'chaosElemental'
|
||||
| 'chaosFanatic'
|
||||
| 'commanderZilyana'
|
||||
| 'corporealBeast'
|
||||
| 'crazyArchaeologist'
|
||||
| 'dagannothPrime'
|
||||
| 'dagannothRex'
|
||||
| 'dagannothSupreme'
|
||||
| 'derangedArchaeologist'
|
||||
| 'dukeSucellus'
|
||||
| 'generalGraardor'
|
||||
| 'giantMole'
|
||||
| 'grotesqueGuardians'
|
||||
| 'hespori'
|
||||
| 'kalphiteQueen'
|
||||
| 'kingBlackDragon'
|
||||
| 'kraken'
|
||||
| 'kreeArra'
|
||||
| 'krilTsutsaroth'
|
||||
| 'lunarChests'
|
||||
| 'mimic'
|
||||
| 'nex'
|
||||
| 'nightmare'
|
||||
| 'phosanisNightmare'
|
||||
| 'obor'
|
||||
| 'phantomMuspah'
|
||||
| 'sarachnis'
|
||||
| 'scorpia'
|
||||
| 'scurrius'
|
||||
| 'skotizo'
|
||||
| 'solHeredit'
|
||||
| 'spindel'
|
||||
| 'tempoross'
|
||||
| 'gauntlet'
|
||||
| 'corruptedGauntlet'
|
||||
| 'hueycoatl'
|
||||
| 'leviathan'
|
||||
| 'whisperer'
|
||||
| 'theatreOfBlood'
|
||||
| 'theatreOfBloodHardMode'
|
||||
| 'thermonuclearSmokeDevil'
|
||||
| 'tombsOfAmascut'
|
||||
| 'tombsOfAmascutExpertMode'
|
||||
| 'tzKalZuk'
|
||||
| 'tzTokJad'
|
||||
| 'vardorvis'
|
||||
| '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'
|
||||
| 'deadmanPoints'
|
||||
| 'hunterBHV2'
|
||||
| 'rogueBHV2'
|
||||
| 'hunterBH'
|
||||
| 'rogueBH'
|
||||
| 'lastManStanding'
|
||||
| 'pvpArena'
|
||||
| 'soulWarsZeal'
|
||||
| 'riftsClosed'
|
||||
| 'allClues'
|
||||
| 'beginnerClues'
|
||||
| 'easyClues'
|
||||
| 'mediumClues'
|
||||
| 'hardClues'
|
||||
| 'eliteClues'
|
||||
| 'masterClues'
|
||||
| 'colosseumGlory'
|
||||
| Boss;
|
||||
|
||||
export interface Stats {
|
||||
skills: Skills;
|
||||
clues: Clues;
|
||||
bh: BH;
|
||||
lms: Activity;
|
||||
/**
|
||||
* Will only contain rank and score data for the `seasonal` gamemode.
|
||||
*/
|
||||
leaguePoints: Activity;
|
||||
/**
|
||||
* Will only contain rank and score data for the `tournament` gamemode.
|
||||
*/
|
||||
deadmanPoints: Activity;
|
||||
bountyHunter: BH;
|
||||
lastManStanding: Activity;
|
||||
pvpArena: Activity;
|
||||
soulWarsZeal: Activity;
|
||||
riftsClosed: Activity;
|
||||
colosseumGlory: 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 +185,71 @@ 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;
|
||||
}
|
||||
|
||||
export interface GetStatsOptions {
|
||||
/**
|
||||
* Other game modes to fetch ranks for.
|
||||
* @defaultvalue `['ironman', 'hardcore', 'ultimate']`
|
||||
*/
|
||||
otherGamemodes?: Extract<Gamemode, 'ironman' | 'hardcore' | 'ultimate'>[];
|
||||
/**
|
||||
* If true, the formatted RSN will be fetched. Otherwise it will return the provided, unformatted RSN.
|
||||
* @defaultvalue `true`
|
||||
*/
|
||||
shouldGetFormattedRsn?: boolean;
|
||||
/**
|
||||
* Map of configs for each requests that can take place in the `getStats` function.
|
||||
*/
|
||||
axiosConfigs?: Partial<Record<Gamemode, AxiosRequestConfig>> & {
|
||||
/**
|
||||
* The axios request config object to use for the RSN format request.
|
||||
*/
|
||||
rsn?: AxiosRequestConfig;
|
||||
};
|
||||
}
|
||||
|
||||
export interface HiscoresCommon {
|
||||
/**
|
||||
* This field behaves more like an index than a true unique ID.
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* The display name of this skill / activity.
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* The player's official hiscores rank in this skill / activity.
|
||||
*/
|
||||
rank: number;
|
||||
}
|
||||
|
||||
export interface HiscoresSkill extends HiscoresCommon {
|
||||
/**
|
||||
* The player's current level in this skill.
|
||||
*/
|
||||
level: number;
|
||||
/**
|
||||
* The player's current experience in this skill.
|
||||
*/
|
||||
xp: number;
|
||||
}
|
||||
|
||||
export interface HiscoresActivity extends HiscoresCommon {
|
||||
/**
|
||||
* The player's current score in this activity.
|
||||
*/
|
||||
score: number;
|
||||
}
|
||||
|
||||
export interface HiscoresResponse {
|
||||
skills: HiscoresSkill[];
|
||||
activities: HiscoresActivity[];
|
||||
}
|
||||
|
@@ -1,16 +1,32 @@
|
||||
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 JSON_STATS_URL = 'index_lite.json?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/`,
|
||||
skiller: `${BASE_URL}_skiller/`,
|
||||
oneDefence: `${BASE_URL}_skiller_defence/`,
|
||||
freshStart: `${BASE_URL}_fresh_start/`
|
||||
};
|
||||
export const SKILLS: SkillName[] = [
|
||||
'overall',
|
||||
@@ -36,7 +52,7 @@ export const SKILLS: SkillName[] = [
|
||||
'farming',
|
||||
'runecraft',
|
||||
'hunter',
|
||||
'construction',
|
||||
'construction'
|
||||
];
|
||||
export const CLUES: ClueType[] = [
|
||||
'all',
|
||||
@@ -45,27 +61,277 @@ export const CLUES: ClueType[] = [
|
||||
'medium',
|
||||
'hard',
|
||||
'elite',
|
||||
'master',
|
||||
];
|
||||
export const BH_MODES: BHType[] = ['rogue', 'hunter'];
|
||||
export const ACTIVITIES = [
|
||||
'hunterbh',
|
||||
'roguebh',
|
||||
'lms',
|
||||
'allclues',
|
||||
'beginnerclues',
|
||||
'easyclues',
|
||||
'mediumclues',
|
||||
'hardclues',
|
||||
'eliteclues',
|
||||
'masterclues',
|
||||
'master'
|
||||
];
|
||||
export const BH_MODES: BHType[] = ['hunterV2', 'rogueV2', 'hunter', 'rogue'];
|
||||
export const GAMEMODES: Gamemode[] = [
|
||||
'main',
|
||||
'iron',
|
||||
'hc',
|
||||
'ult',
|
||||
'dmm',
|
||||
'sdmm',
|
||||
'dmmt',
|
||||
'ironman',
|
||||
'hardcore',
|
||||
'ultimate',
|
||||
'deadman',
|
||||
'seasonal',
|
||||
'tournament'
|
||||
];
|
||||
export const BOSSES: Boss[] = [
|
||||
'abyssalSire',
|
||||
'alchemicalHydra',
|
||||
'amoxliatl',
|
||||
'araxxor',
|
||||
'artio',
|
||||
'barrows',
|
||||
'bryophyta',
|
||||
'callisto',
|
||||
'calvarion',
|
||||
'cerberus',
|
||||
'chambersOfXeric',
|
||||
'chambersOfXericChallengeMode',
|
||||
'chaosElemental',
|
||||
'chaosFanatic',
|
||||
'commanderZilyana',
|
||||
'corporealBeast',
|
||||
'crazyArchaeologist',
|
||||
'dagannothPrime',
|
||||
'dagannothRex',
|
||||
'dagannothSupreme',
|
||||
'derangedArchaeologist',
|
||||
'dukeSucellus',
|
||||
'generalGraardor',
|
||||
'giantMole',
|
||||
'grotesqueGuardians',
|
||||
'hespori',
|
||||
'kalphiteQueen',
|
||||
'kingBlackDragon',
|
||||
'kraken',
|
||||
'kreeArra',
|
||||
'krilTsutsaroth',
|
||||
'lunarChests',
|
||||
'mimic',
|
||||
'nex',
|
||||
'nightmare',
|
||||
'phosanisNightmare',
|
||||
'obor',
|
||||
'phantomMuspah',
|
||||
'sarachnis',
|
||||
'scorpia',
|
||||
'scurrius',
|
||||
'skotizo',
|
||||
'solHeredit',
|
||||
'spindel',
|
||||
'tempoross',
|
||||
'gauntlet',
|
||||
'corruptedGauntlet',
|
||||
'hueycoatl',
|
||||
'leviathan',
|
||||
'whisperer',
|
||||
'theatreOfBlood',
|
||||
'theatreOfBloodHardMode',
|
||||
'thermonuclearSmokeDevil',
|
||||
'tombsOfAmascut',
|
||||
'tombsOfAmascutExpertMode',
|
||||
'tzKalZuk',
|
||||
'tzTokJad',
|
||||
'vardorvis',
|
||||
'venenatis',
|
||||
'vetion',
|
||||
'vorkath',
|
||||
'wintertodt',
|
||||
'zalcano',
|
||||
'zulrah'
|
||||
];
|
||||
export const ACTIVITIES: ActivityName[] = [
|
||||
'leaguePoints',
|
||||
'deadmanPoints',
|
||||
'hunterBHV2',
|
||||
'rogueBHV2',
|
||||
'hunterBH',
|
||||
'rogueBH',
|
||||
'allClues',
|
||||
'beginnerClues',
|
||||
'easyClues',
|
||||
'mediumClues',
|
||||
'hardClues',
|
||||
'eliteClues',
|
||||
'masterClues',
|
||||
'lastManStanding',
|
||||
'pvpArena',
|
||||
'soulWarsZeal',
|
||||
'riftsClosed',
|
||||
'colosseumGlory',
|
||||
...BOSSES
|
||||
];
|
||||
|
||||
export type FormattedBossNames = {
|
||||
[key in Boss]: string;
|
||||
};
|
||||
|
||||
export const FORMATTED_BOSS_NAMES: FormattedBossNames = {
|
||||
abyssalSire: 'Abyssal Sire',
|
||||
alchemicalHydra: 'Alchemical Hydra',
|
||||
amoxliatl: 'Amoxliatl',
|
||||
araxxor: 'Araxxor',
|
||||
artio: 'Artio',
|
||||
barrows: 'Barrows Chests',
|
||||
bryophyta: 'Bryophyta',
|
||||
callisto: 'Callisto',
|
||||
calvarion: "Calvar'ion",
|
||||
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',
|
||||
dukeSucellus: 'Duke Sucellus',
|
||||
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",
|
||||
lunarChests: 'Lunar Chests',
|
||||
mimic: 'Mimic',
|
||||
nex: 'Nex',
|
||||
nightmare: 'Nightmare',
|
||||
phosanisNightmare: "Phosani's Nightmare",
|
||||
obor: 'Obor',
|
||||
phantomMuspah: 'Phantom Muspah',
|
||||
sarachnis: 'Sarachnis',
|
||||
scorpia: 'Scorpia',
|
||||
scurrius: 'Scurrius',
|
||||
skotizo: 'Skotizo',
|
||||
solHeredit: 'Sol Heredit',
|
||||
spindel: 'Spindel',
|
||||
tempoross: 'Tempoross',
|
||||
gauntlet: 'The Gauntlet',
|
||||
corruptedGauntlet: 'The Corrupted Gauntlet',
|
||||
hueycoatl: 'The Hueycoatl',
|
||||
leviathan: 'The Leviathan',
|
||||
whisperer: 'The Whisperer',
|
||||
theatreOfBlood: 'Theatre of Blood',
|
||||
theatreOfBloodHardMode: 'Theatre of Blood: Hard Mode',
|
||||
thermonuclearSmokeDevil: 'Thermonuclear Smoke Devil',
|
||||
tombsOfAmascut: 'Tombs of Amascut',
|
||||
tombsOfAmascutExpertMode: 'Tombs of Amascut: Expert Mode',
|
||||
tzKalZuk: 'TzKal-Zuk',
|
||||
tzTokJad: 'TzTok-Jad',
|
||||
vardorvis: 'Vardorvis',
|
||||
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 (Legacy) - Rogue',
|
||||
hunter: 'Bounty Hunter (Legacy) - Hunter',
|
||||
rogueV2: 'Bounty Hunter - Rogue',
|
||||
hunterV2: 'Bounty Hunter - Hunter'
|
||||
};
|
||||
|
||||
export const FORMATTED_LMS = 'LMS - Rank';
|
||||
export const FORMATTED_PVP_ARENA = 'PvP Arena - Rank';
|
||||
export const FORMATTED_SOUL_WARS = 'Soul Wars Zeal';
|
||||
export const FORMATTED_LEAGUE_POINTS = 'League Points';
|
||||
export const FORMATTED_DEADMAN_POINTS = 'Deadman Points';
|
||||
export const FORMATTED_RIFTS_CLOSED = 'Rifts closed';
|
||||
export const FORMATTED_COLOSSEUM_GLORY = 'Colosseum Glory';
|
||||
|
||||
export const INVALID_FORMAT_ERROR = 'Invalid hiscores format';
|
||||
export const PLAYER_NOT_FOUND_ERROR = 'Player not found';
|
||||
export const HISCORES_ERROR = 'HiScores not responding';
|
||||
|
||||
export class InvalidFormatError extends Error {
|
||||
__proto__ = Error;
|
||||
|
||||
constructor() {
|
||||
super(INVALID_FORMAT_ERROR);
|
||||
Object.setPrototypeOf(this, InvalidFormatError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
export class InvalidRSNError extends Error {
|
||||
__proto__ = Error;
|
||||
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
Object.setPrototypeOf(this, InvalidRSNError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
export class PlayerNotFoundError extends Error {
|
||||
__proto__ = Error;
|
||||
|
||||
constructor() {
|
||||
super(PLAYER_NOT_FOUND_ERROR);
|
||||
Object.setPrototypeOf(this, PlayerNotFoundError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
export class HiScoresError extends Error {
|
||||
__proto__ = Error;
|
||||
|
||||
constructor() {
|
||||
super(HISCORES_ERROR);
|
||||
Object.setPrototypeOf(this, HiScoresError.prototype);
|
||||
}
|
||||
}
|
||||
|
@@ -1,48 +1,129 @@
|
||||
import axios, { AxiosRequestConfig } from 'axios';
|
||||
import * as ua from 'useragent-generator';
|
||||
import { Gamemode, SkillName, ActivityName } from '../types';
|
||||
import {
|
||||
BASE_URL,
|
||||
GAMEMODE_URL,
|
||||
STATS_URL,
|
||||
SCORES_URL,
|
||||
SKILLS,
|
||||
ACTIVITIES,
|
||||
JSON_STATS_URL,
|
||||
InvalidRSNError
|
||||
} from './constants';
|
||||
|
||||
export const getStatsURL = (gamemode: Gamemode, rsn: string) =>
|
||||
`${BASE_URL}${GAMEMODE_URL[gamemode]}${STATS_URL}${encodeURIComponent(rsn)}`;
|
||||
/**
|
||||
* Will generate a stats URL for the official OSRS API.
|
||||
*
|
||||
* @param gamemode Gamemode to fetch ranks for.
|
||||
* @param rsn Username of the player.
|
||||
* @param json If the JSON endpoint is desired instead of CSV.
|
||||
* @returns Encoded stats URL.
|
||||
*/
|
||||
export const getStatsURL = (gamemode: Gamemode, rsn: string, json = false) =>
|
||||
`${GAMEMODE_URL[gamemode]}${
|
||||
json ? JSON_STATS_URL : STATS_URL
|
||||
}${encodeURIComponent(rsn)}`;
|
||||
|
||||
/**
|
||||
* Will generate a player table URL for the official OSRS hiscores website.
|
||||
*
|
||||
* @param gamemode Gamemode to fetch ranks for.
|
||||
* @param rsn Username of the player.
|
||||
* @returns Encoded player table URL.
|
||||
*/
|
||||
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
|
||||
)}`;
|
||||
|
||||
/**
|
||||
* Will generate a skill table URL for the official OSRS hiscores website.
|
||||
*
|
||||
* @param gamemode Gamemode to fetch ranks for.
|
||||
* @param skill Skill to fetch ranks for.
|
||||
* @param page Page number.
|
||||
* @returns
|
||||
*/
|
||||
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}`;
|
||||
|
||||
/**
|
||||
* Will generate an activity table URL for the official OSRS hiscores website.
|
||||
*
|
||||
* @param gamemode Gamemode to fetch ranks for.
|
||||
* @param activity Activity or boss to fetch ranks for.
|
||||
* @param page Page number.
|
||||
* @returns
|
||||
*/
|
||||
export const getActivityPageURL = (
|
||||
gamemode: Gamemode,
|
||||
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';
|
||||
/**
|
||||
* Extracts a number from an OSRS hiscores table cell element.
|
||||
*
|
||||
* @param el OSRS hiscores table cell element.
|
||||
* @returns Number parsed from cell text.
|
||||
*/
|
||||
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, ' ') : '';
|
||||
/**
|
||||
* Extracts a RSN from an OSRS hiscores table cell element.
|
||||
*
|
||||
* @param el OSRS hiscores table cell element.
|
||||
* @returns RSN parsed from cell text.
|
||||
*/
|
||||
export const rsnFromElement = (el: Element | null) => {
|
||||
const { innerHTML } = el ?? {};
|
||||
return innerHTML?.replace(/\uFFFD/g, ' ') ?? '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Will run an Axios `GET` request against a given URL after injecting a `User-Agent` header.
|
||||
*
|
||||
* @param url URL to run a `GET` request against.
|
||||
* @returns Axios response.
|
||||
*/
|
||||
export const httpGet = <Response>(
|
||||
url: string,
|
||||
config: AxiosRequestConfig = {}
|
||||
) =>
|
||||
axios.get<Response>(url, {
|
||||
headers: {
|
||||
// without User-Agent header requests may be rejected by DDoS protection mechanism
|
||||
'User-Agent': ua.firefox(80)
|
||||
},
|
||||
...config
|
||||
});
|
||||
|
||||
/**
|
||||
* Validates that a provided RSN has the same username restrictions as Jagex.
|
||||
* @param rsn Username to validate.
|
||||
* @throws Error if the RSN fails validation.
|
||||
*/
|
||||
export const validateRSN = (rsn: string) => {
|
||||
if (typeof rsn !== 'string') {
|
||||
throw new InvalidRSNError('RSN must be a string');
|
||||
} else if (!/^[a-zA-Z0-9 _-]+$/.test(rsn)) {
|
||||
throw new InvalidRSNError('RSN contains invalid character');
|
||||
} else if (rsn.length > 12 || rsn.length < 1) {
|
||||
throw new InvalidRSNError('RSN must be between 1 and 12 characters');
|
||||
}
|
||||
};
|
||||
|
11
tsconfig.eslint.json
Normal file
11
tsconfig.eslint.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"outDir": "./lib",
|
||||
"strict": true
|
||||
},
|
||||
"include": ["src", "**/__tests__/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
@@ -4,7 +4,9 @@
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"outDir": "./lib",
|
||||
"strict": true
|
||||
"strict": true,
|
||||
"lib": ["ES2015", "DOM", "DOM.Iterable"],
|
||||
"typeRoots": ["./node_modules/@types", "./src/@types"]
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "**/__tests__/*"]
|
||||
|
20
tslint.json
20
tslint.json
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"extends": ["tslint-config-airbnb", "tslint-config-prettier"],
|
||||
"rules": {
|
||||
"import-name": false,
|
||||
"indent": [true, "spaces", 2],
|
||||
"max-line-length": [true, 120],
|
||||
"ter-arrow-parens": [false],
|
||||
"strict-boolean-expressions": [false],
|
||||
"variable-name": [false],
|
||||
"semicolon": [true, "always", "ignore-bound-class-methods"],
|
||||
"prefer-array-literal": [false],
|
||||
"quotemark": [
|
||||
true,
|
||||
"single",
|
||||
"jsx-double",
|
||||
"avoid-escape",
|
||||
"avoid-template"
|
||||
]
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user