Compare commits

..

1 Commits

Author SHA1 Message Date
maxswa
e77737f422 Add Guardians of the Rift. 2022-04-18 15:44:45 -04:00
18 changed files with 6191 additions and 8256 deletions

View File

@@ -10,19 +10,19 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.3.0
- uses: borales/actions-yarn@v4.2.0
- uses: actions/checkout@v2
- uses: borales/actions-yarn@v2.0.0
with:
cmd: install
- uses: borales/actions-yarn@v4.2.0
- uses: borales/actions-yarn@v2.0.0
with:
cmd: lint
- uses: borales/actions-yarn@v4.2.0
- uses: borales/actions-yarn@v2.0.0
with:
cmd: format
- uses: borales/actions-yarn@v4.2.0
- uses: borales/actions-yarn@v2.0.0
with:
cmd: build
- uses: borales/actions-yarn@v4.2.0
- uses: borales/actions-yarn@v2.0.0
with:
cmd: test

3
.gitignore vendored
View File

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

View File

@@ -3,26 +3,24 @@
[![npm](https://img.shields.io/npm/v/osrs-json-hiscores.svg?style=flat-square)](https://www.npmjs.com/package/osrs-json-hiscores)
[![downloads](https://img.shields.io/npm/dm/osrs-json-hiscores.svg?style=flat-square)](https://npm-stat.com/charts.html?package=osrs-json-hiscores)
[![types](https://img.shields.io/npm/types/osrs-json-hiscores.svg?style=flat-square)](https://github.com/maxswa/osrs-json-hiscores/blob/master/src/types.ts)
[![build](https://img.shields.io/github/actions/workflow/status/maxswa/osrs-json-hiscores/main.yml?style=flat-square&branch=main)](https://github.com/maxswa/osrs-json-hiscores/actions/workflows/main.yml?query=branch%3Amain)
[![build](https://img.shields.io/github/workflow/status/maxswa/osrs-json-hiscores/CI?style=flat-square)](https://github.com/maxswa/osrs-json-hiscores/actions/workflows/main.yml?query=branch%3Amain)
**The Old School RuneScape API wrapper that does more!**
**The Old School Runescape API wrapper that does more!**
## What it does
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).
The official hiscores API for Old School 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).
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.
Additional functions are provided that screen-scrape the OSRS leaderboards and return a list of players as json.
`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 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.
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)
---
@@ -44,14 +42,27 @@ $ yarn add osrs-json-hiscores
Install the package and then import it into your project:
```typescript
import { getStatsByGamemode, getSkillPage } from 'osrs-json-hiscores';
```javascript
const hiscores = require('osrs-json-hiscores');
```
Once you import it you can call the functions asynchronously:
```typescript
const stats = await getStatsByGamemode('Lynx Titan');
```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');
const topPage = await getSkillPage('overall');
```
@@ -70,8 +81,9 @@ const topPage = await getSkillPage('overall');
`getSkillPage` and `getActivityPage` require a skill / activity and optionally a gamemode and page:
```typescript
getSkillPage('attack', 'main', 1)
```javascript
hiscores
.getSkillPage('attack', 'main', 1)
.then((res) => console.log(res))
.catch((err) => console.error(err));
```
@@ -92,25 +104,19 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses
### Minigames
| Minigame | Param |
| ------------------------------- | :-----------------: |
| 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` |
| Collections Logged | `collectionsLogged` |
| Minigame | Param |
| ---------------------- | :---------------: |
| Bounty Hunter (Rogue) | `rogueBH` |
| Bounty Hunter (Hunter) | `hunterBH` |
| Last Man Standing | `lastManStanding` |
| Soul Wars Zeal | `soulWarsZeal` |
| Rifts Closed | `riftsClosed` |
### Points
### Leagues
| Activity | Param |
| -------------- | :-------------: |
| League Points | `leaguePoints` |
| Deadman Points | `deadmanPoints` |
| Activity | Param |
| ------------- | :------------: |
| League Points | `leaguePoints` |
### Bosses
@@ -118,13 +124,9 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses
| --------------------------------- | :----------------------------: |
| 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` |
@@ -137,7 +139,6 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses
| Dagannoth Rex | `dagannothRex` |
| Dagannoth Supreme | `dagannothSupreme` |
| Deranged Archaeologist | `derangedArchaeologist` |
| Duke Sucellus | `dukeSucellus` |
| General Graardor | `generalGraardor` |
| Giant Mole | `giantMole` |
| Grotesque Guardians | `grotesqueGuardians` |
@@ -147,39 +148,26 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses
| Kraken | `kraken` |
| Kreearra | `kreeArra` |
| K'ril Tsutsaroth | `krilTsutsaroth` |
| Lunar Chests | `lunarChests` |
| Mimic | `mimic` |
| Nex | `nex` |
| Nightmare | `nightmare` |
| The Nightmare of Ashihama | `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 Royal Titans | `royalTitans` |
| 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` |
| Yama | `yama` |
| Zalcano | `zalcano` |
| Zulrah | `zulrah` |
@@ -187,7 +175,7 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses
`getStats` returns a player object that looks like this:
```typescript
```javascript
{
name: 'Lynx Titan',
mode: 'main',
@@ -205,11 +193,8 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses
leaguePoints: {},
bountyHunter: {},
lastManStanding: {},
pvpArena: {},
soulWarsZeal: {},
riftsClosed: {},
colosseumGlory: {},
collectionsLogged: {},
bosses: {}
}
}
@@ -217,7 +202,7 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses
`getSkillPage` returns and array of 25 players (This represents a page on the hiscores):
```typescript
```javascript
[
{ rank: 1, name: 'Lynx Titan', level: 2277, xp: 4600000000, dead: false },
{},
@@ -230,7 +215,7 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses
Get the properly formatted name of any skill, boss, clue or other activity:
```typescript
```javascript
// kril === "K'ril Tsutsaroth"
const kril = FORMATTED_BOSS_NAMES['krilTsutsaroth'];
```

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,109 +0,0 @@
2210,2277,1150073045
23988,99,33320012
3114,99,51215128
1203,99,200000000
1372,99,196290925
3458,99,159426771
2746,99,17744970
2031,99,91566360
161852,99,13386665
25801,99,19461878
64826,99,13473417
26528,99,16672481
20829,99,19483653
58378,99,14103220
13188,99,16573909
36283,99,15061037
17137,99,14670565
8751,99,16169665
60803,99,14390315
1718,99,57850681
2117,99,110369178
5540,99,22739567
22326,99,18052465
1467,99,18050183
-1,-1
-1,-1
38135,10
19368,3
146220,8
100923,7
433,9802
1542,825
158,6000
2572,1606
47979,311
1081,460
880,600
4258,6785
-1,-1
7596,14780
8124,1060
10438,43487
1114,1155
3497,2192
3204,4796
1087,984
2230,2328
51533,143
4243,1830
256985,5
54424,293
114691,58
11700,2657
211708,34
1779,736
24958,215
46504,116
66096,287
3133,2000
26682,198
19337,1078
25021,1062
19290,1088
89474,25
7535,1320
23251,1228
82016,477
876,3642
7073,310
2476,2046
55411,580
68801,2899
4608,1740
3079,1444
8344,324
540,53
38395,626
4176,749
5519,500
1394,217
26127,379
49730,249
57206,91
6106,1245
185920,29
3232,49
4276,1569
57314,273
256794,7
113862,201
1969,538
12346,671
5606,149
550,2450
25557,310
1391,538
9325,3066
131996,37
214,1720
8902,7
148385,8
6120,2113
287,7096
97368,51
15591,2780
1000405,67
19722,173
213696,25
240082,340
Can't render this file because it has a wrong number of fields in line 25.

View File

@@ -1,684 +0,0 @@
{
"skills": [
{
"id": 0,
"name": "Overall",
"rank": 2210,
"level": 2277,
"xp": 1150073045
},
{
"id": 1,
"name": "Attack",
"rank": 23988,
"level": 99,
"xp": 33320012
},
{
"id": 2,
"name": "Defence",
"rank": 3114,
"level": 99,
"xp": 51215128
},
{
"id": 3,
"name": "Strength",
"rank": 1203,
"level": 99,
"xp": 200000000
},
{
"id": 4,
"name": "Hitpoints",
"rank": 1372,
"level": 99,
"xp": 196290925
},
{
"id": 5,
"name": "Ranged",
"rank": 3458,
"level": 99,
"xp": 159426771
},
{
"id": 6,
"name": "Prayer",
"rank": 2746,
"level": 99,
"xp": 17744970
},
{
"id": 7,
"name": "Magic",
"rank": 2031,
"level": 99,
"xp": 91566360
},
{
"id": 8,
"name": "Cooking",
"rank": 161852,
"level": 99,
"xp": 13386665
},
{
"id": 9,
"name": "Woodcutting",
"rank": 25801,
"level": 99,
"xp": 19461878
},
{
"id": 10,
"name": "Fletching",
"rank": 64826,
"level": 99,
"xp": 13473417
},
{
"id": 11,
"name": "Fishing",
"rank": 26528,
"level": 99,
"xp": 16672481
},
{
"id": 12,
"name": "Firemaking",
"rank": 20829,
"level": 99,
"xp": 19483653
},
{
"id": 13,
"name": "Crafting",
"rank": 58378,
"level": 99,
"xp": 14103220
},
{
"id": 14,
"name": "Smithing",
"rank": 13188,
"level": 99,
"xp": 16573909
},
{
"id": 15,
"name": "Mining",
"rank": 36283,
"level": 99,
"xp": 15061037
},
{
"id": 16,
"name": "Herblore",
"rank": 17137,
"level": 99,
"xp": 14670565
},
{
"id": 17,
"name": "Agility",
"rank": 8751,
"level": 99,
"xp": 16169665
},
{
"id": 18,
"name": "Thieving",
"rank": 60803,
"level": 99,
"xp": 14390315
},
{
"id": 19,
"name": "Slayer",
"rank": 1718,
"level": 99,
"xp": 57850681
},
{
"id": 20,
"name": "Farming",
"rank": 2117,
"level": 99,
"xp": 110369178
},
{
"id": 21,
"name": "Runecraft",
"rank": 5540,
"level": 99,
"xp": 22739567
},
{
"id": 22,
"name": "Hunter",
"rank": 22326,
"level": 99,
"xp": 18052465
},
{
"id": 23,
"name": "Construction",
"rank": 1467,
"level": 99,
"xp": 18050183
}
],
"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": 38135,
"score": 10
},
{
"id": 3,
"name": "Bounty Hunter - Rogue",
"rank": 19368,
"score": 3
},
{
"id": 4,
"name": "Bounty Hunter (Legacy) - Hunter",
"rank": 146220,
"score": 8
},
{
"id": 5,
"name": "Bounty Hunter (Legacy) - Rogue",
"rank": 100923,
"score": 7
},
{
"id": 6,
"name": "Clue Scrolls (all)",
"rank": 433,
"score": 9802
},
{
"id": 7,
"name": "Clue Scrolls (beginner)",
"rank": 1542,
"score": 825
},
{
"id": 8,
"name": "Clue Scrolls (easy)",
"rank": 158,
"score": 6000
},
{
"id": 9,
"name": "Clue Scrolls (medium)",
"rank": 2572,
"score": 1606
},
{
"id": 10,
"name": "Clue Scrolls (hard)",
"rank": 47979,
"score": 311
},
{
"id": 11,
"name": "Clue Scrolls (elite)",
"rank": 1081,
"score": 460
},
{
"id": 12,
"name": "Clue Scrolls (master)",
"rank": 880,
"score": 600
},
{
"id": 13,
"name": "LMS - Rank",
"rank": 4258,
"score": 6785
},
{
"id": 14,
"name": "PvP Arena - Rank",
"rank": -1,
"score": -1
},
{
"id": 15,
"name": "Soul Wars Zeal",
"rank": 7596,
"score": 14780
},
{
"id": 16,
"name": "Rifts closed",
"rank": 8124,
"score": 1060
},
{
"id": 17,
"name": "Colosseum Glory",
"rank": 10438,
"score": 43487
},
{
"id": 18,
"name": "Collections Logged",
"rank": 1114,
"score": 1155
},
{
"id": 19,
"name": "Abyssal Sire",
"rank": 3497,
"score": 2192
},
{
"id": 20,
"name": "Alchemical Hydra",
"rank": 3204,
"score": 4796
},
{
"id": 21,
"name": "Amoxliatl",
"rank": 1087,
"score": 984
},
{
"id": 22,
"name": "Araxxor",
"rank": 2230,
"score": 2328
},
{
"id": 23,
"name": "Artio",
"rank": 51533,
"score": 143
},
{
"id": 24,
"name": "Barrows Chests",
"rank": 4243,
"score": 1830
},
{
"id": 25,
"name": "Bryophyta",
"rank": 256985,
"score": 5
},
{
"id": 26,
"name": "Callisto",
"rank": 54424,
"score": 293
},
{
"id": 27,
"name": "Calvar'ion",
"rank": 114691,
"score": 58
},
{
"id": 28,
"name": "Cerberus",
"rank": 11700,
"score": 2657
},
{
"id": 29,
"name": "Chambers of Xeric",
"rank": 211708,
"score": 34
},
{
"id": 30,
"name": "Chambers of Xeric: Challenge Mode",
"rank": 1779,
"score": 736
},
{
"id": 31,
"name": "Chaos Elemental",
"rank": 24958,
"score": 215
},
{
"id": 32,
"name": "Chaos Fanatic",
"rank": 46504,
"score": 116
},
{
"id": 33,
"name": "Commander Zilyana",
"rank": 66096,
"score": 287
},
{
"id": 34,
"name": "Corporeal Beast",
"rank": 3133,
"score": 2000
},
{
"id": 35,
"name": "Crazy Archaeologist",
"rank": 26682,
"score": 198
},
{
"id": 36,
"name": "Dagannoth Prime",
"rank": 19337,
"score": 1078
},
{
"id": 37,
"name": "Dagannoth Rex",
"rank": 25021,
"score": 1062
},
{
"id": 38,
"name": "Dagannoth Supreme",
"rank": 19290,
"score": 1088
},
{
"id": 39,
"name": "Deranged Archaeologist",
"rank": 89474,
"score": 25
},
{
"id": 40,
"name": "Duke Sucellus",
"rank": 7535,
"score": 1320
},
{
"id": 41,
"name": "General Graardor",
"rank": 23251,
"score": 1228
},
{
"id": 42,
"name": "Giant Mole",
"rank": 82016,
"score": 477
},
{
"id": 43,
"name": "Grotesque Guardians",
"rank": 876,
"score": 3642
},
{
"id": 44,
"name": "Hespori",
"rank": 7073,
"score": 310
},
{
"id": 45,
"name": "Kalphite Queen",
"rank": 2476,
"score": 2046
},
{
"id": 46,
"name": "King Black Dragon",
"rank": 55411,
"score": 580
},
{
"id": 47,
"name": "Kraken",
"rank": 68801,
"score": 2899
},
{
"id": 48,
"name": "Kree'Arra",
"rank": 4608,
"score": 1740
},
{
"id": 49,
"name": "K'ril Tsutsaroth",
"rank": 3079,
"score": 1444
},
{
"id": 50,
"name": "Lunar Chests",
"rank": 8344,
"score": 324
},
{
"id": 51,
"name": "Mimic",
"rank": 540,
"score": 53
},
{
"id": 52,
"name": "Nex",
"rank": 38395,
"score": 626
},
{
"id": 53,
"name": "Nightmare",
"rank": 4176,
"score": 749
},
{
"id": 54,
"name": "Phosani's Nightmare",
"rank": 5519,
"score": 500
},
{
"id": 55,
"name": "Obor",
"rank": 1394,
"score": 217
},
{
"id": 56,
"name": "Phantom Muspah",
"rank": 26127,
"score": 379
},
{
"id": 57,
"name": "Sarachnis",
"rank": 49730,
"score": 249
},
{
"id": 58,
"name": "Scorpia",
"rank": 57206,
"score": 91
},
{
"id": 59,
"name": "Scurrius",
"rank": 6106,
"score": 1245
},
{
"id": 60,
"name": "Skotizo",
"rank": 185920,
"score": 29
},
{
"id": 61,
"name": "Sol Heredit",
"rank": 3232,
"score": 49
},
{
"id": 62,
"name": "Spindel",
"rank": 4276,
"score": 1569
},
{
"id": 63,
"name": "Tempoross",
"rank": 57314,
"score": 273
},
{
"id": 64,
"name": "The Gauntlet",
"rank": 256794,
"score": 7
},
{
"id": 65,
"name": "The Corrupted Gauntlet",
"rank": 113862,
"score": 201
},
{
"id": 66,
"name": "The Hueycoatl",
"rank": 1969,
"score": 538
},
{
"id": 67,
"name": "The Leviathan",
"rank": 12346,
"score": 671
},
{
"id": 68,
"name": "The Royal Titans",
"rank": 5606,
"score": 149
},
{
"id": 69,
"name": "The Whisperer",
"rank": 550,
"score": 2450
},
{
"id": 70,
"name": "Theatre of Blood",
"rank": 25557,
"score": 310
},
{
"id": 71,
"name": "Theatre of Blood: Hard Mode",
"rank": 1391,
"score": 538
},
{
"id": 72,
"name": "Thermonuclear Smoke Devil",
"rank": 9325,
"score": 3066
},
{
"id": 73,
"name": "Tombs of Amascut",
"rank": 131996,
"score": 37
},
{
"id": 74,
"name": "Tombs of Amascut: Expert Mode",
"rank": 214,
"score": 1720
},
{
"id": 75,
"name": "TzKal-Zuk",
"rank": 8902,
"score": 7
},
{
"id": 76,
"name": "TzTok-Jad",
"rank": 148385,
"score": 8
},
{
"id": 77,
"name": "Vardorvis",
"rank": 6120,
"score": 2113
},
{
"id": 78,
"name": "Venenatis",
"rank": 287,
"score": 7096
},
{
"id": 79,
"name": "Vet'ion",
"rank": 97368,
"score": 51
},
{
"id": 80,
"name": "Vorkath",
"rank": 15591,
"score": 2780
},
{
"id": 81,
"name": "Wintertodt",
"rank": 1000405,
"score": 67
},
{
"id": 82,
"name": "Yama",
"rank": 19722,
"score": 173
},
{
"id": 83,
"name": "Zalcano",
"rank": 213696,
"score": 25
},
{
"id": 84,
"name": "Zulrah",
"rank": 240082,
"score": 340
}
]
}

View File

@@ -12,13 +12,7 @@ import {
getSkillPageURL,
getStatsURL,
BOSSES,
InvalidFormatError,
BH_MODES,
parseJsonStats,
HiscoresResponse,
InvalidRSNError,
PlayerNotFoundError,
HiScoresError
INVALID_FORMAT_ERROR
} from '../src/index';
const B0ATY_NAME = 'B0ATY';
@@ -27,18 +21,10 @@ 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 NON_EXISTENT_NAME = 'nonExistent';
const ERROR_NAME = 'errorName';
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 lynxTitanStats = readFileSync(`${__dirname}/lynxTitanStats.csv`, 'utf8');
const lynxTitanNamePage = readFileSync(
`${__dirname}/lynxTitanNamePage.html`,
'utf8'
@@ -59,15 +45,9 @@ jest.spyOn(axios, 'get').mockImplementation((url) => {
if (getSkillPageURL('main', 'attack', 1) === url) {
return Promise.resolve({ data: attackTopPage });
}
if (getStatsURL('main', LYNX_TITAN_FORMATTED_NAME, true) === url) {
if (getStatsURL('main', LYNX_TITAN_FORMATTED_NAME) === url) {
return Promise.resolve({ status: 200, data: lynxTitanStats });
}
if (getPlayerTableURL('main', NON_EXISTENT_NAME) === url) {
return Promise.resolve({ data: '<html></html>' });
}
if (getPlayerTableURL('main', ERROR_NAME)) {
return Promise.reject();
}
throw new Error(`No mock response for URL: ${url}`);
});
@@ -97,9 +77,6 @@ test('Parse CSV to json', () => {
810,99,37688883
92,99,32005622
23423,478
89554,301
89914,35
99834,25
99831,23
89912,37
32,12148
@@ -110,20 +87,13 @@ test('Parse CSV to json', () => {
392,250
1,6143
4814,898
13,4057
37,225
25,1110
7827,43249
1114,1155
23,467
382,2780
944,3000
561,398429
37,111
704,892
1981,1452
4981,23
888,1046
444,792
613,4856
102,4038
156,334
@@ -136,7 +106,6 @@ test('Parse CSV to json', () => {
4342,1655
966,2951
10151,1
1289,2477
1288,2407
377,4669
545,1567
@@ -146,39 +115,26 @@ test('Parse CSV to json', () => {
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
5606,149
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
15233,245
-1,-1
3867,4583`;
@@ -210,19 +166,13 @@ test('Parse CSV to json', () => {
construction: { rank: 92, level: 99, xp: 32005622 }
},
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 }
},
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 },
collectionsLogged: { rank: 1114, score: 1155 },
riftsClosed: { rank: 23, score: 467 },
clues: {
all: { rank: 32, score: 12148 },
beginner: { rank: 3105, score: 76 },
@@ -235,13 +185,9 @@ test('Parse CSV to json', () => {
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 },
@@ -254,7 +200,6 @@ test('Parse CSV to json', () => {
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 },
@@ -264,39 +209,26 @@ test('Parse CSV to json', () => {
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 },
royalTitans: { rank: 5606, score: 149 },
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 },
yama: { rank: 15233, score: 245 },
zalcano: { rank: -1, score: -1 },
zulrah: { rank: 3867, score: 4583 }
}
@@ -309,12 +241,12 @@ test('Parse CSV with unknown activity', () => {
const statsWithUnknownActivity = `${lynxTitanStats}
-1,-1`;
expect(() => parseStats(statsWithUnknownActivity)).toThrow(
InvalidFormatError
INVALID_FORMAT_ERROR
);
});
test('Parse invalid CSV', () => {
expect(() => parseStats('invalid')).toThrow(InvalidFormatError);
expect(() => parseStats('invalid')).toThrow(INVALID_FORMAT_ERROR);
});
describe('Get name format', () => {
@@ -335,15 +267,7 @@ describe('Get name format', () => {
expect(data).toBe(B0ATY_FORMATTED_NAME);
});
it('throws an error for a name with invalid characters', async () => {
await expect(getRSNFormat('b&aty')).rejects.toThrow(InvalidRSNError);
});
it('throws an error for a non-existent player', async () => {
await expect(getRSNFormat(NON_EXISTENT_NAME)).rejects.toThrow(
PlayerNotFoundError
);
});
it('throws an error for a hiscores issue', async () => {
await expect(getRSNFormat(ERROR_NAME)).rejects.toThrow(HiScoresError);
await expect(getRSNFormat('b&aty')).rejects.toBeTruthy();
});
});
@@ -530,14 +454,14 @@ test('Get attack top page', async () => {
test('Get non-existent player', async () => {
getStats('fishy').catch((err) => {
if (err?.response) {
if (err.response) {
expect(err.response.status).toBe(404);
}
});
});
test('Get stats by gamemode', async () => {
const { skills, bosses, bountyHunter } = await getStatsByGamemode(
const { skills, bosses } = await getStatsByGamemode(
LYNX_TITAN_FORMATTED_NAME
);
@@ -570,67 +494,6 @@ test('Get stats by gamemode', async () => {
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);
expect.assertions(2);
});

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,678 +0,0 @@
{
"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": 149,
"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": 23,
"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": 3,
"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": 937868,
"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": 578038,
"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": "Collections Logged",
"rank": -1,
"score": -1
},
{
"id": 19,
"name": "Abyssal Sire",
"rank": -1,
"score": -1
},
{
"id": 20,
"name": "Alchemical Hydra",
"rank": -1,
"score": -1
},
{
"id": 21,
"name": "Amoxliatl",
"rank": -1,
"score": -1
},
{
"id": 22,
"name": "Araxxor",
"rank": -1,
"score": -1
},
{
"id": 23,
"name": "Artio",
"rank": -1,
"score": -1
},
{
"id": 24,
"name": "Barrows Chests",
"rank": -1,
"score": -1
},
{
"id": 25,
"name": "Bryophyta",
"rank": -1,
"score": -1
},
{
"id": 26,
"name": "Callisto",
"rank": -1,
"score": -1
},
{
"id": 27,
"name": "Calvar'ion",
"rank": -1,
"score": -1
},
{
"id": 28,
"name": "Cerberus",
"rank": -1,
"score": -1
},
{
"id": 29,
"name": "Chambers of Xeric",
"rank": -1,
"score": -1
},
{
"id": 30,
"name": "Chambers of Xeric: Challenge Mode",
"rank": -1,
"score": -1
},
{
"id": 31,
"name": "Chaos Elemental",
"rank": -1,
"score": -1
},
{
"id": 32,
"name": "Chaos Fanatic",
"rank": -1,
"score": -1
},
{
"id": 33,
"name": "Commander Zilyana",
"rank": -1,
"score": -1
},
{
"id": 34,
"name": "Corporeal Beast",
"rank": -1,
"score": -1
},
{
"id": 35,
"name": "Crazy Archaeologist",
"rank": -1,
"score": -1
},
{
"id": 36,
"name": "Dagannoth Prime",
"rank": -1,
"score": -1
},
{
"id": 37,
"name": "Dagannoth Rex",
"rank": -1,
"score": -1
},
{
"id": 38,
"name": "Dagannoth Supreme",
"rank": -1,
"score": -1
},
{
"id": 39,
"name": "Deranged Archaeologist",
"rank": -1,
"score": -1
},
{
"id": 40,
"name": "Duke Sucellus",
"rank": -1,
"score": -1
},
{
"id": 41,
"name": "General Graardor",
"rank": -1,
"score": -1
},
{
"id": 42,
"name": "Giant Mole",
"rank": -1,
"score": -1
},
{
"id": 43,
"name": "Grotesque Guardians",
"rank": -1,
"score": -1
},
{
"id": 44,
"name": "Hespori",
"rank": -1,
"score": -1
},
{
"id": 45,
"name": "Kalphite Queen",
"rank": -1,
"score": -1
},
{
"id": 46,
"name": "King Black Dragon",
"rank": -1,
"score": -1
},
{
"id": 47,
"name": "Kraken",
"rank": -1,
"score": -1
},
{
"id": 48,
"name": "Kree'Arra",
"rank": -1,
"score": -1
},
{
"id": 49,
"name": "K'ril Tsutsaroth",
"rank": -1,
"score": -1
},
{
"id": 50,
"name": "Lunar Chests",
"rank": -1,
"score": -1
},
{
"id": 51,
"name": "Mimic",
"rank": -1,
"score": -1
},
{
"id": 52,
"name": "Nex",
"rank": -1,
"score": -1
},
{
"id": 53,
"name": "Nightmare",
"rank": -1,
"score": -1
},
{
"id": 54,
"name": "Phosani's Nightmare",
"rank": -1,
"score": -1
},
{
"id": 55,
"name": "Obor",
"rank": -1,
"score": -1
},
{
"id": 56,
"name": "Phantom Muspah",
"rank": -1,
"score": -1
},
{
"id": 57,
"name": "Sarachnis",
"rank": -1,
"score": -1
},
{
"id": 58,
"name": "Scorpia",
"rank": -1,
"score": -1
},
{
"id": 59,
"name": "Scurrius",
"rank": -1,
"score": -1
},
{
"id": 60,
"name": "Skotizo",
"rank": -1,
"score": -1
},
{
"id": 61,
"name": "Sol Heredit",
"rank": -1,
"score": -1
},
{
"id": 62,
"name": "Spindel",
"rank": -1,
"score": -1
},
{
"id": 63,
"name": "Tempoross",
"rank": -1,
"score": -1
},
{
"id": 64,
"name": "The Gauntlet",
"rank": -1,
"score": -1
},
{
"id": 65,
"name": "The Corrupted Gauntlet",
"rank": -1,
"score": -1
},
{
"id": 66,
"name": "The Hueycoatl",
"rank": -1,
"score": -1
},
{
"id": 67,
"name": "The Leviathan",
"rank": -1,
"score": -1
},
{
"id": 68,
"name": "The Royal Titans",
"rank": -1,
"score": -1
},
{
"id": 69,
"name": "The Whisperer",
"rank": -1,
"score": -1
},
{
"id": 70,
"name": "Theatre of Blood",
"rank": -1,
"score": -1
},
{
"id": 71,
"name": "Theatre of Blood: Hard Mode",
"rank": -1,
"score": -1
},
{
"id": 72,
"name": "Thermonuclear Smoke Devil",
"rank": -1,
"score": -1
},
{
"id": 73,
"name": "Tombs of Amascut",
"rank": -1,
"score": -1
},
{
"id": 74,
"name": "Tombs of Amascut: Expert Mode",
"rank": -1,
"score": -1
},
{
"id": 75,
"name": "TzKal-Zuk",
"rank": -1,
"score": -1
},
{
"id": 76,
"name": "TzTok-Jad",
"rank": 317,
"score": 186
},
{
"id": 77,
"name": "Vardorvis",
"rank": -1,
"score": -1
},
{
"id": 78,
"name": "Venenatis",
"rank": -1,
"score": -1
},
{
"id": 79,
"name": "Vet'ion",
"rank": -1,
"score": -1
},
{
"id": 80,
"name": "Vorkath",
"rank": -1,
"score": -1
},
{
"id": 81,
"name": "Wintertodt",
"rank": -1,
"score": -1
},
{
"id": 82,
"name": "Zalcano",
"rank": -1,
"score": -1
},
{
"id": 83,
"name": "Zulrah",
"rank": -1,
"score": -1
}
]
}

View File

@@ -1,6 +1,6 @@
{
"name": "osrs-json-hiscores",
"version": "2.22.0",
"version": "2.7.0",
"description": "The Old School Runescape API wrapper that does more!",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@@ -30,9 +30,6 @@
"stats",
"skills"
],
"publishConfig": {
"registry": "https://registry.npmjs.org"
},
"author": "maxswa",
"license": "ISC",
"bugs": {
@@ -61,23 +58,18 @@
"@typescript-eslint"
],
"extends": [
"airbnb-base",
"airbnb-typescript/base",
"eslint-config-airbnb-typescript",
"prettier"
],
"ignorePatterns": [
"**/@types/*"
],
"rules": {
"max-classes-per-file": "off"
}
]
},
"prettier": {
"trailingComma": "none",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"endOfLine": "auto"
"singleQuote": true
},
"jest": {
"transform": {
@@ -95,25 +87,28 @@
"testEnvironment": "node"
},
"dependencies": {
"axios": "^1.6.2",
"jsdom": "^22.1.0",
"axios": "^0.21.1",
"jsdom": "^16.3.0",
"useragent-generator": "^1.1.0"
},
"devDependencies": {
"@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",
"@types/jest": "^26.0.21",
"@types/jsdom": "^16.2.3",
"@typescript-eslint/eslint-plugin": "^4.19.0",
"@typescript-eslint/parser": "^4.19.0",
"eslint": "^7.22.0",
"eslint-config-airbnb-typescript": "^12.3.1",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.23.1",
"eslint-plugin-react-hooks": "^4.2.0",
"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"
"jest": "^26.6.3",
"lint-staged": "^10.5.4",
"np": "6.5.0",
"prettier": "^2.2.1",
"ts-jest": "^26.5.4",
"typescript": "^4.2.3"
}
}

View File

@@ -1,5 +1,4 @@
import axios, { AxiosRequestConfig } from 'axios';
import { BinaryData, JSDOM } from 'jsdom';
import { JSDOM } from 'jsdom';
import {
Player,
Activity,
@@ -13,9 +12,7 @@ import {
PlayerSkillRow,
ActivityName,
PlayerActivityRow,
Bosses,
GetStatsOptions,
HiscoresResponse
Bosses
} from './types';
import {
getStatsURL,
@@ -31,159 +28,40 @@ import {
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,
FORMATTED_COLLECTIONS_LOGGED
INVALID_FORMAT_ERROR,
EXTRA_ACTIVITY_COUNT
} from './utils';
/**
* Gets a player's stats from the official OSRS JSON endpoint.
*
* @param rsn Username of the player.
* @param mode Gamemode to fetch ranks for.
* @param config Optional axios request config object.
* @returns Official JSON stats object.
*/
export async function getOfficialStats(
rsn: string,
mode: Gamemode = 'main',
config?: AxiosRequestConfig
): Promise<HiscoresResponse> {
validateRSN(rsn);
const url = getStatsURL(mode, rsn, true);
try {
const response = await httpGet<HiscoresResponse>(url, config);
return response.data;
} catch (err) {
if (!axios.isAxiosError(err)) throw err;
if (err.response?.status === 404) throw new PlayerNotFoundError();
throw new HiScoresError();
}
}
/**
* Screen scrapes the hiscores to get the formatted rsn of a player.
*
* @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,
mode: Gamemode = 'main'
): Promise<string> {
validateRSN(rsn);
const url = getPlayerTableURL(mode, 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);
}
} catch {
throw new HiScoresError();
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');
}
throw new PlayerNotFoundError();
}
/**
* 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()
const url = getPlayerTableURL('main', rsn);
try {
const response = await httpGet(url);
const dom = new JSDOM(response.data);
const spans = dom.window.document.querySelectorAll(
'span[style="color:#AA0022;"]'
);
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 collectionsLogged = getActivity(FORMATTED_COLLECTIONS_LOGGED);
const stats: Stats = {
skills,
leaguePoints,
deadmanPoints,
bountyHunter,
lastManStanding,
pvpArena,
soulWarsZeal,
riftsClosed,
colosseumGlory,
collectionsLogged,
clues,
bosses
};
return stats;
if (spans.length >= 2) {
const nameSpan = spans[1];
return rsnFromElement(nameSpan);
}
throw Error('Player not found');
} catch {
throw Error('Player not found');
}
}
/**
@@ -198,8 +76,15 @@ export function parseStats(csv: string): Stats {
.filter((entry) => !!entry)
.map((stat) => stat.split(','));
if (splitCSV.length !== SKILLS.length + ACTIVITIES.length) {
throw new InvalidFormatError();
if (
splitCSV.length !==
SKILLS.length +
BH_MODES.length +
CLUES.length +
BOSSES.length +
EXTRA_ACTIVITY_COUNT
) {
throw Error(INVALID_FORMAT_ERROR);
}
const skillObjects: Skill[] = splitCSV
@@ -225,17 +110,13 @@ export function parseStats(csv: string): Stats {
return activity;
});
const [leaguePoints, deadmanPoints] = activityObjects.splice(0, 2);
const [leaguePoints] = activityObjects.splice(0, 1);
const bhObjects = activityObjects.splice(0, BH_MODES.length);
const clueObjects = activityObjects.splice(0, CLUES.length);
const [
lastManStanding,
pvpArena,
soulWarsZeal,
riftsClosed,
colosseumGlory,
collectionsLogged
] = activityObjects.splice(0, 6);
const [lastManStanding, soulWarsZeal, riftsClosed] = activityObjects.splice(
0,
3
);
const bossObjects = activityObjects.splice(0, BOSSES.length);
const skills: Skills = skillObjects.reduce<Skills>((prev, curr, index) => {
@@ -265,14 +146,10 @@ export function parseStats(csv: string): Stats {
const stats: Stats = {
skills,
leaguePoints,
deadmanPoints,
bountyHunter,
lastManStanding,
pvpArena,
soulWarsZeal,
riftsClosed,
colosseumGlory,
collectionsLogged,
clues,
bosses
};
@@ -289,82 +166,81 @@ export function parseStats(csv: string): Stats {
* @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 = {
name: formattedName ?? rsn,
mode: 'main',
dead: false,
deulted: false,
deironed: false
};
player.main = parseJsonStats(main);
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 = 'ironman';
}
if (player.main.skills.overall.xp !== player.ironman.skills.overall.xp) {
player.deironed = true;
player.mode = 'main';
}
} else if (ult) {
player.mode = 'ultimate';
player.ultimate = parseJsonStats(ult);
if (
player.ironman.skills.overall.xp !== player.ultimate.skills.overall.xp
) {
player.deulted = true;
player.mode = 'ironman';
}
if (player.main.skills.overall.xp !== player.ironman.skills.overall.xp) {
player.deironed = true;
player.mode = 'main';
}
} else {
player.mode = 'ironman';
if (player.main.skills.overall.xp !== player.ironman.skills.overall.xp) {
player.deironed = true;
player.mode = 'main';
}
}
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');
}
return player;
const mainRes = await httpGet(getStatsURL('main', rsn));
if (mainRes.status === 200) {
const otherResponses = await Promise.all([
httpGet(getStatsURL('ironman', rsn)).catch((err) => err),
httpGet(getStatsURL('hardcore', rsn)).catch((err) => err),
httpGet(getStatsURL('ultimate', rsn)).catch((err) => err),
getRSNFormat(rsn).catch(() => undefined)
]);
const [ironRes, hcRes, ultRes, formattedName] = otherResponses;
const player: Player = {
name: formattedName ?? rsn,
mode: 'main',
dead: false,
deulted: false,
deironed: false
};
player.main = parseStats(mainRes.data);
if (ironRes.status === 200) {
player.ironman = parseStats(ironRes.data);
if (hcRes.status === 200) {
player.mode = 'hardcore';
player.hardcore = parseStats(hcRes.data);
if (
player.ironman.skills.overall.xp !== player.hardcore.skills.overall.xp
) {
player.dead = true;
player.mode = 'ironman';
}
if (
player.main.skills.overall.xp !== player.ironman.skills.overall.xp
) {
player.deironed = true;
player.mode = 'main';
}
} else if (ultRes.status === 200) {
player.mode = 'ultimate';
player.ultimate = parseStats(ultRes.data);
if (
player.ironman.skills.overall.xp !== player.ultimate.skills.overall.xp
) {
player.deulted = true;
player.mode = 'ironman';
}
if (
player.main.skills.overall.xp !== player.ironman.skills.overall.xp
) {
player.deironed = true;
player.mode = 'main';
}
} else {
player.mode = 'ironman';
if (
player.main.skills.overall.xp !== player.ironman.skills.overall.xp
) {
player.deironed = true;
player.mode = 'main';
}
}
}
return player;
}
throw Error('Player not found');
}
/**
@@ -372,20 +248,26 @@ export async function getStats(
*
* @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',
config?: AxiosRequestConfig
mode: Gamemode = 'main'
): Promise<Stats> {
validateRSN(rsn);
if (!GAMEMODES.includes(mode)) {
if (typeof rsn !== 'string') {
throw Error('RSN must be a string');
} else if (!/^[a-zA-Z0-9 _]+$/.test(rsn)) {
throw Error('RSN contains invalid character');
} else if (rsn.length > 12 || rsn.length < 1) {
throw Error('RSN must be between 1 and 12 characters');
} else if (!GAMEMODES.includes(mode)) {
throw Error('Invalid game mode');
}
const response = await getOfficialStats(rsn, mode, config);
const stats = parseJsonStats(response);
const response = await httpGet(getStatsURL(mode, rsn));
if (response.status !== 200) {
throw Error('Player not found');
}
const stats = parseStats(response.data);
return stats;
}
@@ -393,8 +275,7 @@ export async function getStatsByGamemode(
export async function getSkillPage(
skill: SkillName,
mode: Gamemode = 'main',
page: number = 1,
config?: AxiosRequestConfig
page: number = 1
): Promise<PlayerSkillRow[]> {
if (!GAMEMODES.includes(mode)) {
throw Error('Invalid game mode');
@@ -405,10 +286,7 @@ export async function getSkillPage(
}
const url = getSkillPageURL(mode, skill, page);
const response = await httpGet<string | Buffer | BinaryData | undefined>(
url,
config
);
const response = await httpGet(url);
const dom = new JSDOM(response.data);
const playersHTML = dom.window.document.querySelectorAll(
'.personal-hiscores__row'
@@ -440,14 +318,12 @@ export async function getSkillPage(
* @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,
config?: AxiosRequestConfig
page: number = 1
): Promise<PlayerActivityRow[]> {
if (!GAMEMODES.includes(mode)) {
throw Error('Invalid game mode');
@@ -458,10 +334,7 @@ export async function getActivityPage(
}
const url = getActivityPageURL(mode, activity, page);
const response = await httpGet<string | Buffer | BinaryData | undefined>(
url,
config
);
const response = await httpGet(url);
const dom = new JSDOM(response.data);
const playersHTML = dom.window.document.querySelectorAll(
'.personal-hiscores__row'

View File

@@ -1,5 +1,3 @@
import { AxiosRequestConfig } from 'axios';
export type Gamemode =
| 'main'
| 'ironman'
@@ -7,10 +5,7 @@ export type Gamemode =
| 'hardcore'
| 'deadman'
| 'seasonal'
| 'tournament'
| 'skiller'
| 'oneDefence'
| 'freshStart';
| 'tournament';
export interface Skill {
rank: number;
@@ -62,20 +57,16 @@ export type ClueType =
export type Clues = { [Type in ClueType]: Activity };
export type BHType = 'rogue' | 'hunter' | 'rogueV2' | 'hunterV2';
export type BHType = 'rogue' | 'hunter';
export type BH = { [Type in BHType]: Activity };
export type Boss =
| 'abyssalSire'
| 'alchemicalHydra'
| 'amoxliatl'
| 'araxxor'
| 'artio'
| 'barrows'
| 'bryophyta'
| 'callisto'
| 'calvarion'
| 'cerberus'
| 'chambersOfXeric'
| 'chambersOfXericChallengeMode'
@@ -88,7 +79,6 @@ export type Boss =
| 'dagannothRex'
| 'dagannothSupreme'
| 'derangedArchaeologist'
| 'dukeSucellus'
| 'generalGraardor'
| 'giantMole'
| 'grotesqueGuardians'
@@ -98,39 +88,26 @@ export type Boss =
| 'kraken'
| 'kreeArra'
| 'krilTsutsaroth'
| 'lunarChests'
| 'mimic'
| 'nex'
| 'nightmare'
| 'phosanisNightmare'
| 'obor'
| 'phantomMuspah'
| 'sarachnis'
| 'scorpia'
| 'scurrius'
| 'skotizo'
| 'solHeredit'
| 'spindel'
| 'tempoross'
| 'gauntlet'
| 'corruptedGauntlet'
| 'hueycoatl'
| 'leviathan'
| 'royalTitans'
| 'whisperer'
| 'theatreOfBlood'
| 'theatreOfBloodHardMode'
| 'thermonuclearSmokeDevil'
| 'tombsOfAmascut'
| 'tombsOfAmascutExpertMode'
| 'tzKalZuk'
| 'tzTokJad'
| 'vardorvis'
| 'venenatis'
| 'vetion'
| 'vorkath'
| 'wintertodt'
| 'yama'
| 'zalcano'
| 'zulrah';
@@ -138,13 +115,9 @@ export type Bosses = { [Type in Boss]: Activity };
export type ActivityName =
| 'leaguePoints'
| 'deadmanPoints'
| 'hunterBHV2'
| 'rogueBHV2'
| 'hunterBH'
| 'rogueBH'
| 'lastManStanding'
| 'pvpArena'
| 'soulWarsZeal'
| 'riftsClosed'
| 'allClues'
@@ -154,28 +127,16 @@ export type ActivityName =
| 'hardClues'
| 'eliteClues'
| 'masterClues'
| 'colosseumGlory'
| 'collectionsLogged'
| Boss;
export interface Stats {
skills: Skills;
clues: Clues;
/**
* 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;
collectionsLogged: Activity;
bosses: Bosses;
}
export type Modes = { [M in Gamemode]?: Stats };
@@ -197,63 +158,3 @@ export interface PlayerActivityRow extends Activity {
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[];
}

View File

@@ -9,7 +9,6 @@ import {
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 type GamemodeUrl = {
@@ -23,10 +22,7 @@ export const GAMEMODE_URL: GamemodeUrl = {
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/`
tournament: `${BASE_URL}_tournament/`
};
export const SKILLS: SkillName[] = [
'overall',
@@ -63,7 +59,7 @@ export const CLUES: ClueType[] = [
'elite',
'master'
];
export const BH_MODES: BHType[] = ['hunterV2', 'rogueV2', 'hunter', 'rogue'];
export const BH_MODES: BHType[] = ['hunter', 'rogue'];
export const GAMEMODES: Gamemode[] = [
'main',
'ironman',
@@ -76,13 +72,9 @@ export const GAMEMODES: Gamemode[] = [
export const BOSSES: Boss[] = [
'abyssalSire',
'alchemicalHydra',
'amoxliatl',
'araxxor',
'artio',
'barrows',
'bryophyta',
'callisto',
'calvarion',
'cerberus',
'chambersOfXeric',
'chambersOfXericChallengeMode',
@@ -95,7 +87,6 @@ export const BOSSES: Boss[] = [
'dagannothRex',
'dagannothSupreme',
'derangedArchaeologist',
'dukeSucellus',
'generalGraardor',
'giantMole',
'grotesqueGuardians',
@@ -105,47 +96,31 @@ export const BOSSES: Boss[] = [
'kraken',
'kreeArra',
'krilTsutsaroth',
'lunarChests',
'mimic',
'nex',
'nightmare',
'phosanisNightmare',
'obor',
'phantomMuspah',
'sarachnis',
'scorpia',
'scurrius',
'skotizo',
'solHeredit',
'spindel',
'tempoross',
'gauntlet',
'corruptedGauntlet',
'hueycoatl',
'leviathan',
'royalTitans',
'whisperer',
'theatreOfBlood',
'theatreOfBloodHardMode',
'thermonuclearSmokeDevil',
'tombsOfAmascut',
'tombsOfAmascutExpertMode',
'tzKalZuk',
'tzTokJad',
'vardorvis',
'venenatis',
'vetion',
'vorkath',
'wintertodt',
'yama',
'zalcano',
'zulrah'
];
export const ACTIVITIES: ActivityName[] = [
'leaguePoints',
'deadmanPoints',
'hunterBHV2',
'rogueBHV2',
'hunterBH',
'rogueBH',
'allClues',
@@ -156,11 +131,8 @@ export const ACTIVITIES: ActivityName[] = [
'eliteClues',
'masterClues',
'lastManStanding',
'pvpArena',
'soulWarsZeal',
'riftsClosed',
'colosseumGlory',
'collectionsLogged',
...BOSSES
];
@@ -171,13 +143,9 @@ export type FormattedBossNames = {
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',
@@ -190,7 +158,6 @@ export const FORMATTED_BOSS_NAMES: FormattedBossNames = {
dagannothRex: 'Dagannoth Rex',
dagannothSupreme: 'Dagannoth Supreme',
derangedArchaeologist: 'Deranged Archaeologist',
dukeSucellus: 'Duke Sucellus',
generalGraardor: 'General Graardor',
giantMole: 'Giant Mole',
grotesqueGuardians: 'Grotesque Guardians',
@@ -200,39 +167,26 @@ export const FORMATTED_BOSS_NAMES: FormattedBossNames = {
kraken: 'Kraken',
kreeArra: "Kree'Arra",
krilTsutsaroth: "K'ril Tsutsaroth",
lunarChests: 'Lunar Chests',
mimic: 'Mimic',
nex: 'Nex',
nightmare: 'Nightmare',
nightmare: 'The Nightmare of Ashihama',
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',
royalTitans: 'The Royal Titans',
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',
yama: 'Yama',
zalcano: 'Zalcano',
zulrah: 'Zulrah'
};
@@ -287,57 +241,18 @@ export type FormattedBHNames = {
};
export const FORMATTED_BH_NAMES: FormattedBHNames = {
rogue: 'Bounty Hunter (Legacy) - Rogue',
hunter: 'Bounty Hunter (Legacy) - Hunter',
rogueV2: 'Bounty Hunter - Rogue',
hunterV2: 'Bounty Hunter - Hunter'
rogue: 'Bounty Hunter - Rogue',
hunter: 'Bounty Hunter - Hunter'
};
export const FORMATTED_LMS = 'LMS - Rank';
export const FORMATTED_PVP_ARENA = 'PvP Arena - Rank';
export const FORMATTED_LMS = 'Last Man Standing';
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 FORMATTED_COLLECTIONS_LOGGED = 'Collections Logged';
export const FORMATTED_RIFTS_CLOSED = 'Rifts Closed';
/**
* Count of activities not including bosses, bounty hunter, or clues.
*/
export const EXTRA_ACTIVITY_COUNT = 4;
export const INVALID_FORMAT_ERROR = 'Invalid hiscores format';
export const PLAYER_NOT_FOUND_ERROR = 'Player not found';
export const HISCORES_ERROR = 'HiScores not responding';
export class InvalidFormatError extends Error {
__proto__ = Error;
constructor() {
super(INVALID_FORMAT_ERROR);
Object.setPrototypeOf(this, InvalidFormatError.prototype);
}
}
export class InvalidRSNError extends Error {
__proto__ = Error;
constructor(message: string) {
super(message);
Object.setPrototypeOf(this, InvalidRSNError.prototype);
}
}
export class PlayerNotFoundError extends Error {
__proto__ = Error;
constructor() {
super(PLAYER_NOT_FOUND_ERROR);
Object.setPrototypeOf(this, PlayerNotFoundError.prototype);
}
}
export class HiScoresError extends Error {
__proto__ = Error;
constructor() {
super(HISCORES_ERROR);
Object.setPrototypeOf(this, HiScoresError.prototype);
}
}

View File

@@ -1,4 +1,4 @@
import axios, { AxiosRequestConfig } from 'axios';
import axios from 'axios';
import * as ua from 'useragent-generator';
import { Gamemode, SkillName, ActivityName } from '../types';
import {
@@ -6,9 +6,7 @@ import {
STATS_URL,
SCORES_URL,
SKILLS,
ACTIVITIES,
JSON_STATS_URL,
InvalidRSNError
ACTIVITIES
} from './constants';
/**
@@ -16,13 +14,10 @@ import {
*
* @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)}`;
export const getStatsURL = (gamemode: Gamemode, rsn: string) =>
`${GAMEMODE_URL[gamemode]}${STATS_URL}${encodeURIComponent(rsn)}`;
/**
* Will generate a player table URL for the official OSRS hiscores website.
@@ -101,29 +96,10 @@ export const rsnFromElement = (el: Element | null) => {
* @param url URL to run a `GET` request against.
* @returns Axios response.
*/
export const httpGet = <Response>(
url: string,
config: AxiosRequestConfig = {}
) =>
axios.get<Response>(url, {
export const httpGet = (url: string) =>
axios.get(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');
}
};

View File

@@ -6,7 +6,10 @@
"outDir": "./lib",
"strict": true,
"lib": ["ES2015", "DOM", "DOM.Iterable"],
"typeRoots": ["./node_modules/@types", "./src/@types"]
"typeRoots": [
"./node_modules/@types",
"./src/@types"
]
},
"include": ["src"],
"exclude": ["node_modules", "**/__tests__/*"]

5496
yarn.lock

File diff suppressed because it is too large Load Diff