Compare commits

..

43 Commits

Author SHA1 Message Date
maxswa
e9f47b9f08 Restrict GH workflow push builds to main 2021-05-01 14:22:41 -04:00
maxswa
3a6671d3d1 Fix expected test result, typo. 2021-05-01 14:16:38 -04:00
maxswa
a4065022d6 Add hyphen to valid rsn regex. 2021-05-01 14:09:08 -04:00
maxswa
163f4dd184 Fix test script. 2021-03-25 00:59:18 -04:00
maxswa
e200a8d566 Add lint and format to github workflow. 2021-03-25 00:55:34 -04:00
maxswa
89944ca8cc Add jsdoc to hiscores and helper functions. 2021-03-25 00:53:50 -04:00
maxswa
29c76b4afb Add husky, lint-staged and set up pre-commit hook. 2021-03-25 00:53:25 -04:00
maxswa
e3e904a23f Swap tslint for eslint. 2021-03-25 00:20:02 -04:00
maxswa
0b96530a8e Add license file. 2021-03-25 00:10:43 -04:00
maxswa
175f945ab8 v2.4.0 2021-03-24 22:49:58 -04:00
maxswa
6b31e8b08c Add Tempoross to boss list. 2021-03-24 22:46:37 -04:00
maxswa
d097567a3c v2.3.2 2021-02-13 11:22:26 -05:00
maxswa
be5c093cf7 Downgrade np. 2021-02-13 11:13:52 -05:00
maxswa
db72f0c57d Bump np. 2021-02-13 11:08:57 -05:00
Max Swartwout
bd2ea21980 Merge pull request #26 from maxswa/bh-order-fix
Fixes #25 Flipped BH modes.
2021-02-13 11:07:27 -05:00
maxswa
d67837c161 Fix flipped BH modes. 2021-02-13 10:50:58 -05:00
Max Swartwout
4aa6ad2752 Add github action. 2021-02-13 10:48:31 -05:00
maxswa
2f788fabf6 v2.3.1 2021-01-12 19:56:17 -05:00
maxswa
0f5aea795c Upgrade dependencies. 2021-01-12 19:55:36 -05:00
Max Swartwout
7acb84e310 Merge pull request #21 from maxswa/dependabot/npm_and_yarn/dot-prop-4.2.1
Bump dot-prop from 4.2.0 to 4.2.1
2021-01-12 19:48:41 -05:00
Max Swartwout
9619bd2a2b Merge pull request #23 from molo-pl/master
Fixes #22 adding User-Agent request header to bypass Incapsula
2021-01-12 19:48:29 -05:00
molo-pl
55abd9f800 Fixes #22 adding User-Agent request header - post review changes 2021-01-12 23:29:25 +01:00
molo-pl
cf8a4cc26e Fixes #22 adding User-Agent request header to bypass Incapsula protection of OSRS hiscore pages 2021-01-12 16:05:47 +01:00
maxswa
0f68c1995c v2.3.0 2021-01-10 16:20:54 -05:00
dependabot[bot]
ae47df31f1 Bump dot-prop from 4.2.0 to 4.2.1
Bumps [dot-prop](https://github.com/sindresorhus/dot-prop) from 4.2.0 to 4.2.1.
- [Release notes](https://github.com/sindresorhus/dot-prop/releases)
- [Commits](https://github.com/sindresorhus/dot-prop/compare/v4.2.0...v4.2.1)

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2020-04-01 03:53:20 +00:00
Max Swartwout
0f47310814 Merge pull request #8 from maxswa/bug/rsn-format-error
Add catch for getRSNFormat error.
2020-03-31 23:52:30 -04:00
maxswa
30530fde01 Add catch for getRSNFormat error. 2020-01-29 12:24:59 -05:00
14 changed files with 3800 additions and 2306 deletions

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

@@ -0,0 +1,28 @@
name: CI
on:
push:
branches:
- main
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: borales/actions-yarn@v2.0.0
with:
cmd: install
- uses: borales/actions-yarn@v2.0.0
with:
cmd: lint
- uses: borales/actions-yarn@v2.0.0
with:
cmd: format
- uses: borales/actions-yarn@v2.0.0
with:
cmd: build
- uses: borales/actions-yarn@v2.0.0
with:
cmd: test

View File

@@ -1,6 +0,0 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": true,
"singleQuote": true
}

5
LICENSE Normal file
View 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.

View File

@@ -4,11 +4,11 @@
[![downloads](https://img.shields.io/npm/dm/osrs-json-hiscores.svg?style=flat-square)](https://npm-stat.com/charts.html?package=osrs-json-hiscores) [![downloads](https://img.shields.io/npm/dm/osrs-json-hiscores.svg?style=flat-square)](https://npm-stat.com/charts.html?package=osrs-json-hiscores)
[![types](https://img.shields.io/npm/types/osrs-json-hiscores.svg?style=flat-square)](https://github.com/maxswa/osrs-json-hiscores/blob/master/src/types.ts) [![types](https://img.shields.io/npm/types/osrs-json-hiscores.svg?style=flat-square)](https://github.com/maxswa/osrs-json-hiscores/blob/master/src/types.ts)
**The Oldschool Runescape API wrapper that does more!** **The Old School Runescape API wrapper that does more!**
## What it does ## What it does
The official hiscores API for Oldschool Runescape (OSRS) returns CSV. 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). 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. Additional functions are provided that screen-scrape the OSRS leaderboards and return a list of players as json.
@@ -50,8 +50,8 @@ Once you import it you can call the functions asynchronously:
```javascript ```javascript
hiscores hiscores
.getStats('Lynx Titan') .getStats('Lynx Titan')
.then(res => console.log(res)) .then((res) => console.log(res))
.catch(err => console.error(err)); .catch((err) => console.error(err));
``` ```
If you are using TypeScript or transpiling your JS you can use ES6 syntax: If you are using TypeScript or transpiling your JS you can use ES6 syntax:
@@ -83,8 +83,8 @@ const topPage = await getSkillPage('overall');
```javascript ```javascript
hiscores hiscores
.getSkillPage('attack', 'main', 1) .getSkillPage('attack', 'main', 1)
.then(res => console.log(res)) .then((res) => console.log(res))
.catch(err => console.error(err)); .catch((err) => console.error(err));
``` ```
Activities consist of all levels of clue scrolls as well as minigames and bosses: Activities consist of all levels of clue scrolls as well as minigames and bosses:
@@ -108,6 +108,7 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses
| Bounty Hunter (Rogue) | `rogueBH` | | Bounty Hunter (Rogue) | `rogueBH` |
| Bounty Hunter (Hunter) | `hunterBH` | | Bounty Hunter (Hunter) | `hunterBH` |
| Last Man Standing | `lastManStanding` | | Last Man Standing | `lastManStanding` |
| Soul Wars Zeal | `soulWarsZeal` |
### Leagues ### Leagues
@@ -146,10 +147,12 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses
| Kreearra | `kreeArra` | | Kreearra | `kreeArra` |
| K'ril Tsutsaroth | `krilTsutsaroth` | | K'ril Tsutsaroth | `krilTsutsaroth` |
| Mimic | `mimic` | | Mimic | `mimic` |
| The Nightmare of Ashihama | `nightmare` |
| Obor | `obor` | | Obor | `obor` |
| Sarachnis | `sarachnis` | | Sarachnis | `sarachnis` |
| Scorpia | `scorpia` | | Scorpia | `scorpia` |
| Skotizo | `skotizo` | | Skotizo | `skotizo` |
| Tempoross | `tempoross` |
| Gauntlet | `gauntlet` | | Gauntlet | `gauntlet` |
| Corrupted Gauntlet | `corruptedGauntlet` | | Corrupted Gauntlet | `corruptedGauntlet` |
| Theatre Of Blood | `theatreOfBlood` | | Theatre Of Blood | `theatreOfBlood` |
@@ -185,6 +188,7 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses
leaguePoints: {}, leaguePoints: {},
bountyHunter: {}, bountyHunter: {},
lastManStanding: {}, lastManStanding: {},
soulWarsZeal: {},
bosses: {} bosses: {}
} }
} }
@@ -196,7 +200,7 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses
[ [
{ rank: 1, name: 'Lynx Titan', level: 2277, xp: 4600000000, dead: false }, { rank: 1, name: 'Lynx Titan', level: 2277, xp: 4600000000, dead: false },
{}, {},
{}, {}
// ... // ...
]; ];
``` ```

View File

@@ -4,7 +4,7 @@ import {
getStats, getStats,
getStatsByGamemode, getStatsByGamemode,
getRSNFormat, getRSNFormat,
Stats, Stats
} from '../src/index'; } from '../src/index';
test('Parse CSV to json', () => { test('Parse CSV to json', () => {
@@ -32,9 +32,9 @@ test('Parse CSV to json', () => {
169,99,43127930 169,99,43127930
810,99,37688883 810,99,37688883
92,99,32005622 92,99,32005622
-1,-1 23423,478
-1,-1 99831,23
-1,-1 89912,37
32,12148 32,12148
3105,76 3105,76
1997,505 1997,505
@@ -43,6 +43,7 @@ test('Parse CSV to json', () => {
392,250 392,250
1,6143 1,6143
4814,898 4814,898
37,225
382,2780 382,2780
944,3000 944,3000
1981,1452 1981,1452
@@ -70,10 +71,12 @@ test('Parse CSV to json', () => {
625,2391 625,2391
120,2981 120,2981
1,109 1,109
3,22666
26,323 26,323
201,1101 201,1101
82,3404 82,3404
5085,61 5085,61
18823,23
63,375 63,375
2870,6 2870,6
6984,138 6984,138
@@ -112,14 +115,15 @@ test('Parse CSV to json', () => {
farming: { rank: 821, level: 99, xp: 62123353 }, farming: { rank: 821, level: 99, xp: 62123353 },
runecraft: { rank: 169, level: 99, xp: 43127930 }, runecraft: { rank: 169, level: 99, xp: 43127930 },
hunter: { rank: 810, level: 99, xp: 37688883 }, hunter: { rank: 810, level: 99, xp: 37688883 },
construction: { rank: 92, level: 99, xp: 32005622 }, construction: { rank: 92, level: 99, xp: 32005622 }
}, },
leaguePoints: { rank: -1, score: -1 }, leaguePoints: { rank: 23423, score: 478 },
bountyHunter: { bountyHunter: {
rogue: { rank: -1, score: -1 }, hunter: { rank: 99831, score: 23 },
hunter: { rank: -1, score: -1 }, rogue: { rank: 89912, score: 37 }
}, },
lastManStanding: { rank: 4814, score: 898 }, lastManStanding: { rank: 4814, score: 898 },
soulWarsZeal: { rank: 37, score: 225 },
clues: { clues: {
all: { rank: 32, score: 12148 }, all: { rank: 32, score: 12148 },
beginner: { rank: 3105, score: 76 }, beginner: { rank: 3105, score: 76 },
@@ -127,7 +131,7 @@ test('Parse CSV to json', () => {
medium: { rank: 127, score: 4259 }, medium: { rank: 127, score: 4259 },
hard: { rank: 361, score: 915 }, hard: { rank: 361, score: 915 },
elite: { rank: 392, score: 250 }, elite: { rank: 392, score: 250 },
master: { rank: 1, score: 6143 }, master: { rank: 1, score: 6143 }
}, },
bosses: { bosses: {
abyssalSire: { rank: 382, score: 2780 }, abyssalSire: { rank: 382, score: 2780 },
@@ -157,10 +161,12 @@ test('Parse CSV to json', () => {
kreeArra: { rank: 625, score: 2391 }, kreeArra: { rank: 625, score: 2391 },
krilTsutsaroth: { rank: 120, score: 2981 }, krilTsutsaroth: { rank: 120, score: 2981 },
mimic: { rank: 1, score: 109 }, mimic: { rank: 1, score: 109 },
nightmare: { rank: 3, score: 22666 },
obor: { rank: 26, score: 323 }, obor: { rank: 26, score: 323 },
sarachnis: { rank: 201, score: 1101 }, sarachnis: { rank: 201, score: 1101 },
scorpia: { rank: 82, score: 3404 }, scorpia: { rank: 82, score: 3404 },
skotizo: { rank: 5085, score: 61 }, skotizo: { rank: 5085, score: 61 },
tempoross: { rank: 18823, score: 23 },
gauntlet: { rank: 63, score: 375 }, gauntlet: { rank: 63, score: 375 },
corruptedGauntlet: { rank: 2870, score: 6 }, corruptedGauntlet: { rank: 2870, score: 6 },
theatreOfBlood: { rank: 6984, score: 138 }, theatreOfBlood: { rank: 6984, score: 138 },
@@ -172,17 +178,38 @@ test('Parse CSV to json', () => {
vorkath: { rank: 8623, score: 1340 }, vorkath: { rank: 8623, score: 1340 },
wintertodt: { rank: 605, score: 1694 }, wintertodt: { rank: 605, score: 1694 },
zalcano: { rank: -1, score: -1 }, zalcano: { rank: -1, score: -1 },
zulrah: { rank: 3867, score: 4583 }, zulrah: { rank: 3867, score: 4583 }
}, }
}; };
expect(parseStats(csv)).toStrictEqual(expectedOutput); expect(parseStats(csv)).toStrictEqual(expectedOutput);
}); });
test('Get name format', async () => { describe('Get name format', () => {
jest.setTimeout(30000); it('gets a name with a space', async () => {
const data = await getRSNFormat('lYnX tiTaN'); jest.setTimeout(30000);
expect(data).toBe('Lynx Titan'); const data = await getRSNFormat('lYnX tiTaN');
expect(data).toBe('Lynx Titan');
});
it('gets a name with an underscore', async () => {
jest.setTimeout(30000);
const data = await getRSNFormat('lYnX_tiTaN');
expect(data).toBe('Lynx Titan');
});
it('gets a name with a hyphen', async () => {
jest.setTimeout(30000);
const data = await getRSNFormat('lYnX-tiTaN');
expect(data).toBe('Lynx Titan');
});
it('gets a name with a number', async () => {
jest.setTimeout(30000);
const data = await getRSNFormat('B0ATY');
expect(data).toBe('B0aty');
});
it('throws an error for a name with invalid characters', async () => {
jest.setTimeout(30000);
await expect(getRSNFormat('b&aty')).rejects.toBeTruthy();
});
}); });
test('Get attack top page', async () => { test('Get attack top page', async () => {
@@ -194,176 +221,182 @@ test('Get attack top page', async () => {
rank: 1, rank: 1,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false
}, },
{ {
name: expect.any(String), name: expect.any(String),
rank: 2, rank: 2,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false
},
{
name: expect.any(String),
rank: 3,
level: 99,
xp: 200000000,
dead: false
}, },
{ name: 'Drakon', rank: 3, level: 99, xp: 200000000, dead: false },
{ {
name: expect.any(String), name: expect.any(String),
rank: 4, rank: 4,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false
}, },
{ {
name: expect.any(String), name: expect.any(String),
rank: 5, rank: 5,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false
}, },
{ {
name: expect.any(String), name: expect.any(String),
rank: 6, rank: 6,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false
}, },
{ {
name: expect.any(String), name: expect.any(String),
rank: 7, rank: 7,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false
}, },
{ {
name: expect.any(String), name: expect.any(String),
rank: 8, rank: 8,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false
}, },
{ {
name: expect.any(String), name: expect.any(String),
rank: 9, rank: 9,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false
}, },
{ {
name: expect.any(String), name: expect.any(String),
rank: 10, rank: 10,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false
}, },
{ {
name: expect.any(String), name: expect.any(String),
rank: 11, rank: 11,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false
}, },
{ {
name: expect.any(String), name: expect.any(String),
rank: 12, rank: 12,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false
}, },
{ {
name: expect.any(String), name: expect.any(String),
rank: 13, rank: 13,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false
}, },
{ {
name: expect.any(String), name: expect.any(String),
rank: 14, rank: 14,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false
}, },
{ {
name: expect.any(String), name: expect.any(String),
rank: 15, rank: 15,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false
}, },
{ {
name: expect.any(String), name: expect.any(String),
rank: 16, rank: 16,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false
}, },
{ {
name: expect.any(String), name: expect.any(String),
rank: 17, rank: 17,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false
}, },
{ {
name: expect.any(String), name: expect.any(String),
rank: 18, rank: 18,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false
}, },
{ {
name: expect.any(String), name: expect.any(String),
rank: 19, rank: 19,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false
}, },
{ {
name: expect.any(String), name: expect.any(String),
rank: 20, rank: 20,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false
}, },
{ {
name: expect.any(String), name: expect.any(String),
rank: 21, rank: 21,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false
}, },
{ {
name: expect.any(String), name: expect.any(String),
rank: 22, rank: 22,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false
}, },
{ {
name: expect.any(String), name: expect.any(String),
rank: 23, rank: 23,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false
}, },
{ {
name: expect.any(String), name: expect.any(String),
rank: 24, rank: 24,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false
}, },
{ {
name: expect.any(String), name: expect.any(String),
rank: 25, rank: 25,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false
}, }
]); ]);
}); });
test('Get non-existant player', async () => { test('Get non-existent player', async () => {
jest.setTimeout(30000); jest.setTimeout(30000);
getStats('fishy').catch(err => { getStats('fishy').catch((err) => {
if (err.response) { if (err.response) {
expect(err.response.status).toBe(404); expect(err.response.status).toBe(404);
} }
@@ -397,6 +430,6 @@ test('Get stats by gamemode', async () => {
farming: { 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 }, runecraft: { rank: expect.any(Number), level: 99, xp: 200000000 },
hunter: { 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 }, construction: { rank: expect.any(Number), level: 99, xp: 200000000 }
}); });
}); });

View File

@@ -1,7 +0,0 @@
{
"transform": {
"^.+\\.(t|j)sx?$": "ts-jest"
},
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"]
}

View File

@@ -1,7 +1,7 @@
{ {
"name": "osrs-json-hiscores", "name": "osrs-json-hiscores",
"version": "2.0.2", "version": "2.4.0",
"description": "The Oldschool Runescape API wrapper that does more!", "description": "The Old School Runescape API wrapper that does more!",
"main": "lib/index.js", "main": "lib/index.js",
"types": "lib/index.d.ts", "types": "lib/index.d.ts",
"files": [ "files": [
@@ -11,7 +11,8 @@
"dev": "watch 'yarn run build' src", "dev": "watch 'yarn run build' src",
"build": "tsc", "build": "tsc",
"format": "prettier --write \"src/**/*.ts\"", "format": "prettier --write \"src/**/*.ts\"",
"test": "jest --config jestconfig.json", "lint": "eslint --fix \"src/**/*.ts\"",
"test": "jest",
"prepublish": "yarn run build", "prepublish": "yarn run build",
"release": "np" "release": "np"
}, },
@@ -35,21 +36,80 @@
"url": "https://github.com/maxswa/osrs-json-hiscores/issues" "url": "https://github.com/maxswa/osrs-json-hiscores/issues"
}, },
"homepage": "https://github.com/maxswa/osrs-json-hiscores#readme", "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.json"
},
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint-config-airbnb-typescript",
"prettier"
],
"ignorePatterns": [
"**/@types/*"
]
},
"prettier": {
"trailingComma": "none",
"tabWidth": 2,
"semi": true,
"singleQuote": true
},
"jest": {
"transform": {
"^.+\\.(t|j)sx?$": "ts-jest"
},
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json",
"node"
],
"testEnvironment": "node"
},
"dependencies": { "dependencies": {
"axios": "^0.19.0", "axios": "^0.21.1",
"cheerio": "^1.0.0-rc.3" "jsdom": "^16.3.0",
"useragent-generator": "^1.1.0"
}, },
"devDependencies": { "devDependencies": {
"@types/cheerio": "^0.22.11", "@types/jest": "^26.0.21",
"@types/jest": "^24.0.14", "@types/jsdom": "^16.2.3",
"jest": "^24.8.0", "@typescript-eslint/eslint-plugin": "^4.19.0",
"np": "^5.0.3", "@typescript-eslint/parser": "^4.19.0",
"prettier": "^1.19.1", "eslint": "^7.22.0",
"ts-jest": "^24.0.2", "eslint-config-airbnb-typescript": "^12.3.1",
"tslint": "^5.17.0", "eslint-config-prettier": "^8.1.0",
"tslint-config-airbnb": "^5.11.1", "eslint-plugin-import": "^2.22.1",
"tslint-config-prettier": "^1.18.0", "eslint-plugin-jsx-a11y": "^6.4.1",
"typescript": "^3.5.2", "eslint-plugin-react": "^7.23.1",
"eslint-plugin-react-hooks": "^4.2.0",
"husky": "^5.2.0",
"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",
"watch": "^1.0.2" "watch": "^1.0.2"
} }
} }

109
src/@types/useragent-generator.d.ts vendored Normal file
View 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;
}

View File

@@ -1,5 +1,4 @@
import axios from 'axios'; import { JSDOM } from 'jsdom';
import * as cheerio from 'cheerio';
import { import {
Player, Player,
Activity, Activity,
@@ -13,8 +12,7 @@ import {
PlayerSkillRow, PlayerSkillRow,
ActivityName, ActivityName,
PlayerActivityRow, PlayerActivityRow,
Bosses, Bosses
Boss,
} from './types'; } from './types';
import { import {
getStatsURL, getStatsURL,
@@ -28,9 +26,129 @@ import {
numberFromElement, numberFromElement,
rsnFromElement, rsnFromElement,
getActivityPageURL, getActivityPageURL,
BOSSES, httpGet,
BOSSES
} from './utils'; } from './utils';
/**
* Screen scrapes the hiscores to get the formatted rsn of a player.
*
* @param rsn Username of the player.
* @returns Formatted version of the rsn.
*/
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 httpGet(url);
const dom = new JSDOM(response.data);
const spans = dom.window.document.querySelectorAll(
'span[style="color:#AA0022;"]'
);
if (spans.length >= 2) {
const nameSpan = spans[1];
return rsnFromElement(nameSpan);
}
throw Error('Player not found');
} catch {
throw Error('Player not found');
}
}
/**
* 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(','));
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 [leaguePoints] = activityObjects.splice(0, 1);
const bhObjects = activityObjects.splice(0, BH_MODES.length);
const clueObjects = activityObjects.splice(0, CLUES.length);
const [lastManStanding, soulWarsZeal] = activityObjects.splice(0, 2);
const bossObjects = activityObjects.splice(0, BOSSES.length);
const skills: Skills = skillObjects.reduce<Skills>((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,
bountyHunter,
lastManStanding,
soulWarsZeal,
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): Promise<Player> { export async function getStats(rsn: string): Promise<Player> {
if (typeof rsn !== 'string') { if (typeof rsn !== 'string') {
throw Error('RSN must be a string'); throw Error('RSN must be a string');
@@ -40,23 +158,23 @@ export async function getStats(rsn: string): Promise<Player> {
throw Error('RSN must be between 1 and 12 characters'); throw Error('RSN must be between 1 and 12 characters');
} }
const mainRes = await axios(getStatsURL('main', rsn)); const mainRes = await httpGet(getStatsURL('main', rsn));
if (mainRes.status === 200) { if (mainRes.status === 200) {
const otherResponses = await Promise.all([ const otherResponses = await Promise.all([
axios(getStatsURL('ironman', rsn)).catch(err => err), httpGet(getStatsURL('ironman', rsn)).catch((err) => err),
axios(getStatsURL('hardcore', rsn)).catch(err => err), httpGet(getStatsURL('hardcore', rsn)).catch((err) => err),
axios(getStatsURL('ultimate', rsn)).catch(err => err), httpGet(getStatsURL('ultimate', rsn)).catch((err) => err),
getRSNFormat(rsn), getRSNFormat(rsn).catch(() => undefined)
]); ]);
const [ironRes, hcRes, ultRes, formattedName] = otherResponses; const [ironRes, hcRes, ultRes, formattedName] = otherResponses;
const player: Player = { const player: Player = {
name: formattedName, name: formattedName ?? rsn,
mode: 'main', mode: 'main',
dead: false, dead: false,
deulted: false, deulted: false,
deironed: false, deironed: false
}; };
player.main = parseStats(mainRes.data); player.main = parseStats(mainRes.data);
@@ -108,6 +226,13 @@ export async function getStats(rsn: string): Promise<Player> {
throw Error('Player not found'); 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.
* @returns Stats object.
*/
export async function getStatsByGamemode( export async function getStatsByGamemode(
rsn: string, rsn: string,
mode: Gamemode = 'main' mode: Gamemode = 'main'
@@ -121,7 +246,7 @@ export async function getStatsByGamemode(
} else if (!GAMEMODES.includes(mode)) { } else if (!GAMEMODES.includes(mode)) {
throw Error('Invalid game mode'); throw Error('Invalid game mode');
} }
const response = await axios(getStatsURL(mode, rsn)); const response = await httpGet(getStatsURL(mode, rsn));
if (response.status !== 200) { if (response.status !== 200) {
throw Error('Player not found'); throw Error('Player not found');
} }
@@ -144,28 +269,40 @@ export async function getSkillPage(
} }
const url = getSkillPageURL(mode, skill, page); const url = getSkillPageURL(mode, skill, page);
const response = await axios(url); const response = await httpGet(url);
const $ = cheerio.load(response.data); const dom = new JSDOM(response.data);
const playersHTML = $('.personal-hiscores__row').toArray(); const playersHTML = dom.window.document.querySelectorAll(
'.personal-hiscores__row'
);
const players: PlayerSkillRow[] = playersHTML.map(row => { const players: PlayerSkillRow[] = [];
const cells = row.children.filter(el => el.name === 'td'); playersHTML.forEach((row) => {
const [rankEl, nameCell, levelEl, xpEl] = cells; const rankEl = row.querySelector('td');
const nameEl = nameCell.children.find(el => el.name === 'a'); const nameEl = row.querySelector('td a');
const isDead = !!nameCell.children.find(el => el.name === 'img'); const levelEl = row.querySelector('td.left + td');
const xpEl = row.querySelector('td.left + td + td');
const isDead = !!row.querySelector('td img');
return { players.push({
name: rsnFromElement(nameEl), name: rsnFromElement(nameEl),
rank: numberFromElement(rankEl), rank: numberFromElement(rankEl),
level: numberFromElement(levelEl), level: numberFromElement(levelEl),
xp: numberFromElement(xpEl), xp: numberFromElement(xpEl),
dead: isDead, dead: isDead
}; });
}); });
return players; 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.
* @returns Array of `PlayerActivityRow` objects.
*/
export async function getActivityPage( export async function getActivityPage(
activity: ActivityName, activity: ActivityName,
mode: Gamemode = 'main', mode: Gamemode = 'main',
@@ -180,117 +317,26 @@ export async function getActivityPage(
} }
const url = getActivityPageURL(mode, activity, page); const url = getActivityPageURL(mode, activity, page);
const response = await axios(url); const response = await httpGet(url);
const $ = cheerio.load(response.data); const dom = new JSDOM(response.data);
const playersHTML = $('.personal-hiscores__row').toArray(); const playersHTML = dom.window.document.querySelectorAll(
'.personal-hiscores__row'
);
const players: PlayerActivityRow[] = playersHTML.map(row => { const players: PlayerActivityRow[] = [];
const cells = row.children.filter(el => el.name === 'td'); playersHTML.forEach((row) => {
const [rankEl, nameCell, scoreEl] = cells; const rankEl = row.querySelector('td');
const nameEl = nameCell.children.find(el => el.name === 'a'); const nameEl = row.querySelector('td a');
const isDead = !!nameCell.children.find(el => el.name === 'img'); const scoreEl = row.querySelector('td.left + td');
const isDead = !!row.querySelector('td img');
return { players.push({
name: rsnFromElement(nameEl), name: rsnFromElement(nameEl),
rank: numberFromElement(rankEl), rank: numberFromElement(rankEl),
score: numberFromElement(scoreEl), score: numberFromElement(scoreEl),
dead: isDead, dead: isDead
}; });
}); });
return players; 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 [leaguePoints] = activityObjects.splice(0, 1);
const bhObjects = activityObjects.splice(0, BH_MODES.length);
const clueObjects = activityObjects.splice(0, CLUES.length);
const [lastManStanding] = activityObjects.splice(0, 1);
const bossObjects = activityObjects.splice(0, BOSSES.length);
const skills: Skills = skillObjects.reduce<Skills>((prev, curr, index) => {
const newSkills = { ...prev };
newSkills[SKILLS[index]] = curr;
return newSkills;
}, {} as Skills);
const 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,
bountyHunter,
lastManStanding,
clues,
bosses,
};
return stats;
}

View File

@@ -89,10 +89,12 @@ export type Boss =
| 'kreeArra' | 'kreeArra'
| 'krilTsutsaroth' | 'krilTsutsaroth'
| 'mimic' | 'mimic'
| 'nightmare'
| 'obor' | 'obor'
| 'sarachnis' | 'sarachnis'
| 'scorpia' | 'scorpia'
| 'skotizo' | 'skotizo'
| 'tempoross'
| 'gauntlet' | 'gauntlet'
| 'corruptedGauntlet' | 'corruptedGauntlet'
| 'theatreOfBlood' | 'theatreOfBlood'
@@ -113,6 +115,7 @@ export type ActivityName =
| 'hunterBH' | 'hunterBH'
| 'rogueBH' | 'rogueBH'
| 'lastManStanding' | 'lastManStanding'
| 'soulWarsZeal'
| 'allClues' | 'allClues'
| 'beginnerClues' | 'beginnerClues'
| 'easyClues' | 'easyClues'
@@ -128,6 +131,7 @@ export interface Stats {
leaguePoints: Activity; leaguePoints: Activity;
bountyHunter: BH; bountyHunter: BH;
lastManStanding: Activity; lastManStanding: Activity;
soulWarsZeal: Activity;
bosses: Bosses; bosses: Bosses;
} }
export type Modes = { [M in Gamemode]?: Stats }; export type Modes = { [M in Gamemode]?: Stats };

View File

@@ -4,10 +4,10 @@ import {
ClueType, ClueType,
Gamemode, Gamemode,
SkillName, SkillName,
ActivityName, ActivityName
} from '../types'; } from '../types';
export const BASE_URL = 'http://services.runescape.com/m=hiscore_oldschool'; export const BASE_URL = 'https://secure.runescape.com/m=hiscore_oldschool';
export const STATS_URL = 'index_lite.ws?player='; export const STATS_URL = 'index_lite.ws?player=';
export const SCORES_URL = 'overall.ws?'; export const SCORES_URL = 'overall.ws?';
@@ -22,7 +22,7 @@ export const GAMEMODE_URL: GamemodeUrl = {
ultimate: `${BASE_URL}_ultimate/`, ultimate: `${BASE_URL}_ultimate/`,
deadman: `${BASE_URL}_deadman/`, deadman: `${BASE_URL}_deadman/`,
seasonal: `${BASE_URL}_seasonal/`, seasonal: `${BASE_URL}_seasonal/`,
tournament: `${BASE_URL}_tournament/`, tournament: `${BASE_URL}_tournament/`
}; };
export const SKILLS: SkillName[] = [ export const SKILLS: SkillName[] = [
'overall', 'overall',
@@ -48,7 +48,7 @@ export const SKILLS: SkillName[] = [
'farming', 'farming',
'runecraft', 'runecraft',
'hunter', 'hunter',
'construction', 'construction'
]; ];
export const CLUES: ClueType[] = [ export const CLUES: ClueType[] = [
'all', 'all',
@@ -57,9 +57,9 @@ export const CLUES: ClueType[] = [
'medium', 'medium',
'hard', 'hard',
'elite', 'elite',
'master', 'master'
]; ];
export const BH_MODES: BHType[] = ['rogue', 'hunter']; export const BH_MODES: BHType[] = ['hunter', 'rogue'];
export const GAMEMODES: Gamemode[] = [ export const GAMEMODES: Gamemode[] = [
'main', 'main',
'ironman', 'ironman',
@@ -67,7 +67,7 @@ export const GAMEMODES: Gamemode[] = [
'ultimate', 'ultimate',
'deadman', 'deadman',
'seasonal', 'seasonal',
'tournament', 'tournament'
]; ];
export const BOSSES: Boss[] = [ export const BOSSES: Boss[] = [
'abyssalSire', 'abyssalSire',
@@ -97,10 +97,12 @@ export const BOSSES: Boss[] = [
'kreeArra', 'kreeArra',
'krilTsutsaroth', 'krilTsutsaroth',
'mimic', 'mimic',
'nightmare',
'obor', 'obor',
'sarachnis', 'sarachnis',
'scorpia', 'scorpia',
'skotizo', 'skotizo',
'tempoross',
'gauntlet', 'gauntlet',
'corruptedGauntlet', 'corruptedGauntlet',
'theatreOfBlood', 'theatreOfBlood',
@@ -112,7 +114,7 @@ export const BOSSES: Boss[] = [
'vorkath', 'vorkath',
'wintertodt', 'wintertodt',
'zalcano', 'zalcano',
'zulrah', 'zulrah'
]; ];
export const ACTIVITIES: ActivityName[] = [ export const ACTIVITIES: ActivityName[] = [
'leaguePoints', 'leaguePoints',
@@ -126,7 +128,8 @@ export const ACTIVITIES: ActivityName[] = [
'eliteClues', 'eliteClues',
'masterClues', 'masterClues',
'lastManStanding', 'lastManStanding',
...BOSSES, 'soulWarsZeal',
...BOSSES
]; ];
export type FormattedBossNames = { export type FormattedBossNames = {
@@ -161,10 +164,12 @@ export const FORMATTED_BOSS_NAMES: FormattedBossNames = {
kreeArra: "Kree'Arra", kreeArra: "Kree'Arra",
krilTsutsaroth: "K'ril Tsutsaroth", krilTsutsaroth: "K'ril Tsutsaroth",
mimic: 'Mimic', mimic: 'Mimic',
nightmare: 'The Nightmare of Ashihama',
obor: 'Obor', obor: 'Obor',
sarachnis: 'Sarachnis', sarachnis: 'Sarachnis',
scorpia: 'Scorpia', scorpia: 'Scorpia',
skotizo: 'Skotizo', skotizo: 'Skotizo',
tempoross: 'Tempoross',
gauntlet: 'The Gauntlet', gauntlet: 'The Gauntlet',
corruptedGauntlet: 'The Corrupted Gauntlet', corruptedGauntlet: 'The Corrupted Gauntlet',
theatreOfBlood: 'Theatre of Blood', theatreOfBlood: 'Theatre of Blood',
@@ -176,7 +181,7 @@ export const FORMATTED_BOSS_NAMES: FormattedBossNames = {
vorkath: 'Vorkath', vorkath: 'Vorkath',
wintertodt: 'Wintertodt', wintertodt: 'Wintertodt',
zalcano: 'Zalcano', zalcano: 'Zalcano',
zulrah: 'Zulrah', zulrah: 'Zulrah'
}; };
export type FormattedSkillNames = { export type FormattedSkillNames = {
@@ -207,7 +212,7 @@ export const FORMATTED_SKILL_NAMES: FormattedSkillNames = {
farming: 'Farming', farming: 'Farming',
runecraft: 'Runecraft', runecraft: 'Runecraft',
hunter: 'Hunter', hunter: 'Hunter',
construction: 'Construction', construction: 'Construction'
}; };
export type FormattedClueNames = { export type FormattedClueNames = {
@@ -221,7 +226,7 @@ export const FORMATTED_CLUE_NAMES: FormattedClueNames = {
medium: 'Clue Scrolls (medium)', medium: 'Clue Scrolls (medium)',
hard: 'Clue Scrolls (hard)', hard: 'Clue Scrolls (hard)',
elite: 'Clue Scrolls (elite)', elite: 'Clue Scrolls (elite)',
master: 'Clue Scrolls (master)', master: 'Clue Scrolls (master)'
}; };
export type FormattedBHNames = { export type FormattedBHNames = {
@@ -230,8 +235,9 @@ export type FormattedBHNames = {
export const FORMATTED_BH_NAMES: FormattedBHNames = { export const FORMATTED_BH_NAMES: FormattedBHNames = {
rogue: 'Bounty Hunter - Rogue', rogue: 'Bounty Hunter - Rogue',
hunter: 'Bounty Hunter - Hunter', hunter: 'Bounty Hunter - Hunter'
}; };
export const FORMATTED_LMS = 'Last Man Standing'; 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_LEAGUE_POINTS = 'League Points';

View File

@@ -1,20 +1,44 @@
import axios from 'axios';
import * as ua from 'useragent-generator';
import { Gamemode, SkillName, ActivityName } from '../types'; import { Gamemode, SkillName, ActivityName } from '../types';
import { import {
GAMEMODE_URL, GAMEMODE_URL,
STATS_URL, STATS_URL,
SCORES_URL, SCORES_URL,
SKILLS, SKILLS,
ACTIVITIES, ACTIVITIES
} from './constants'; } from './constants';
/**
* Will generate a stats URL for the official OSRS API.
*
* @param gamemode Gamemode to fetch ranks for.
* @param rsn Username of the player.
* @returns Encoded stats URL.
*/
export const getStatsURL = (gamemode: Gamemode, rsn: string) => export const getStatsURL = (gamemode: Gamemode, rsn: string) =>
`${GAMEMODE_URL[gamemode]}${STATS_URL}${encodeURIComponent(rsn)}`; `${GAMEMODE_URL[gamemode]}${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) => export const getPlayerTableURL = (gamemode: Gamemode, rsn: string) =>
`${GAMEMODE_URL[gamemode]}${SCORES_URL}table=0&user=${encodeURIComponent( `${GAMEMODE_URL[gamemode]}${SCORES_URL}table=0&user=${encodeURIComponent(
rsn 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 = ( export const getSkillPageURL = (
gamemode: Gamemode, gamemode: Gamemode,
skill: SkillName, skill: SkillName,
@@ -24,6 +48,14 @@ export const getSkillPageURL = (
skill skill
)}&page=${page}`; )}&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 = ( export const getActivityPageURL = (
gamemode: Gamemode, gamemode: Gamemode,
activity: ActivityName, activity: ActivityName,
@@ -35,13 +67,39 @@ export const getActivityPageURL = (
activity activity
)}&page=${page}`; )}&page=${page}`;
export const numberFromElement = (el: CheerioElement) => { /**
const innerText = el.firstChild.data; * Extracts a number from an OSRS hiscores table cell element.
const number = innerText ? innerText.replace(/[\n|,]/g, '') : '-1'; *
* @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); return parseInt(number, 10);
}; };
export const rsnFromElement = (el: CheerioElement | undefined) => { /**
const innerText = el?.firstChild.data; * Extracts a RSN from an OSRS hiscores table cell element.
return innerText ? innerText.replace(/\uFFFD/g, ' ') : ''; *
* @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 = (url: string) =>
axios.get(url, {
headers: {
// without User-Agent header requests may be rejected by DDoS protection mechanism
'User-Agent': ua.firefox(80)
}
});

View File

@@ -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"
]
}
}

5290
yarn.lock

File diff suppressed because it is too large Load Diff