Compare commits

...

24 Commits

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-11 09:18:50 +00:00
maxswa
7de2d9a95a v2.2.0 2020-07-23 18:22:38 -04:00
Max Swartwout
a143306519 Merge pull request #15 from maxswa/release/2.2.0
Release/2.2.0
2020-07-23 18:20:49 -04:00
maxswa
86b81abfd8 Upgrade dependencies. 2020-07-23 18:18:58 -04:00
maxswa
8d065742d0 Replace cheerio with jsdom. 2020-07-23 18:13:51 -04:00
10 changed files with 2101 additions and 2223 deletions

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

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

View File

@@ -108,6 +108,7 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses
| Bounty Hunter (Rogue) | `rogueBH` |
| Bounty Hunter (Hunter) | `hunterBH` |
| Last Man Standing | `lastManStanding` |
| Soul Wars Zeal | `soulWarsZeal` |
### Leagues
@@ -186,6 +187,7 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses
leaguePoints: {},
bountyHunter: {},
lastManStanding: {},
soulWarsZeal: {},
bosses: {}
}
}

View File

@@ -32,9 +32,9 @@ test('Parse CSV to json', () => {
169,99,43127930
810,99,37688883
92,99,32005622
-1,-1
-1,-1
-1,-1
23423,478
99831,23
89912,37
32,12148
3105,76
1997,505
@@ -43,6 +43,7 @@ test('Parse CSV to json', () => {
392,250
1,6143
4814,898
37,225
382,2780
944,3000
1981,1452
@@ -115,12 +116,13 @@ test('Parse CSV to json', () => {
hunter: { rank: 810, level: 99, xp: 37688883 },
construction: { rank: 92, level: 99, xp: 32005622 },
},
leaguePoints: { rank: -1, score: -1 },
leaguePoints: { rank: 23423, score: 478 },
bountyHunter: {
rogue: { rank: -1, score: -1 },
hunter: { rank: -1, score: -1 },
hunter: { rank: 99831, score: 23 },
rogue: { rank: 89912, score: 37 },
},
lastManStanding: { rank: 4814, score: 898 },
soulWarsZeal: { rank: 37, score: 225 },
clues: {
all: { rank: 32, score: 12148 },
beginner: { rank: 3105, score: 76 },

View File

@@ -1,6 +1,6 @@
{
"name": "osrs-json-hiscores",
"version": "2.1.0",
"version": "2.3.2",
"description": "The Oldschool Runescape API wrapper that does more!",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@@ -36,16 +36,17 @@
},
"homepage": "https://github.com/maxswa/osrs-json-hiscores#readme",
"dependencies": {
"axios": "^0.19.0",
"cheerio": "^1.0.0-rc.3"
"axios": "^0.21.1",
"jsdom": "^16.3.0",
"useragent-generator": "^1.1.0"
},
"devDependencies": {
"@types/cheerio": "^0.22.11",
"@types/jest": "^24.0.14",
"jest": "^24.8.0",
"np": "^5.0.3",
"@types/jest": "^26.0.20",
"@types/jsdom": "^16.2.3",
"jest": "^26.6.3",
"np": "6.5.0",
"prettier": "^1.19.1",
"ts-jest": "^24.0.2",
"ts-jest": "^26.4.4",
"tslint": "^5.17.0",
"tslint-config-airbnb": "^5.11.1",
"tslint-config-prettier": "^1.18.0",

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

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

View File

@@ -1,5 +1,3 @@
import axios from 'axios';
import * as cheerio from 'cheerio';
import {
Player,
Activity,
@@ -14,7 +12,6 @@ import {
ActivityName,
PlayerActivityRow,
Bosses,
Boss,
} from './types';
import {
getStatsURL,
@@ -28,8 +25,10 @@ import {
numberFromElement,
rsnFromElement,
getActivityPageURL,
httpGet,
BOSSES,
} from './utils';
import { JSDOM } from 'jsdom';
export async function getStats(rsn: string): Promise<Player> {
if (typeof rsn !== 'string') {
@@ -40,12 +39,12 @@ export async function getStats(rsn: string): Promise<Player> {
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) {
const otherResponses = await Promise.all([
axios(getStatsURL('ironman', rsn)).catch(err => err),
axios(getStatsURL('hardcore', rsn)).catch(err => err),
axios(getStatsURL('ultimate', rsn)).catch(err => err),
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),
]);
@@ -121,7 +120,7 @@ export async function getStatsByGamemode(
} else if (!GAMEMODES.includes(mode)) {
throw Error('Invalid game mode');
}
const response = await axios(getStatsURL(mode, rsn));
const response = await httpGet(getStatsURL(mode, rsn));
if (response.status !== 200) {
throw Error('Player not found');
}
@@ -144,23 +143,27 @@ export async function getSkillPage(
}
const url = getSkillPageURL(mode, skill, page);
const response = await axios(url);
const $ = cheerio.load(response.data);
const playersHTML = $('.personal-hiscores__row').toArray();
const response = await httpGet(url);
const dom = new JSDOM(response.data);
const playersHTML = dom.window.document.querySelectorAll(
'.personal-hiscores__row'
);
const players: PlayerSkillRow[] = playersHTML.map(row => {
const cells = row.children.filter(el => el.name === 'td');
const [rankEl, nameCell, levelEl, xpEl] = cells;
const nameEl = nameCell.children.find(el => el.name === 'a');
const isDead = !!nameCell.children.find(el => el.name === 'img');
const players: PlayerSkillRow[] = [];
playersHTML.forEach(row => {
const rankEl = row.querySelector('td');
const nameEl = row.querySelector('td a');
const levelEl = row.querySelector('td.left + td');
const xpEl = row.querySelector('td.left + td + td');
const isDead = !!row.querySelector('td img');
return {
players.push({
name: rsnFromElement(nameEl),
rank: numberFromElement(rankEl),
level: numberFromElement(levelEl),
xp: numberFromElement(xpEl),
dead: isDead,
};
});
});
return players;
@@ -180,22 +183,25 @@ export async function getActivityPage(
}
const url = getActivityPageURL(mode, activity, page);
const response = await axios(url);
const $ = cheerio.load(response.data);
const playersHTML = $('.personal-hiscores__row').toArray();
const response = await httpGet(url);
const dom = new JSDOM(response.data);
const playersHTML = dom.window.document.querySelectorAll(
'.personal-hiscores__row'
);
const players: PlayerActivityRow[] = playersHTML.map(row => {
const cells = row.children.filter(el => el.name === 'td');
const [rankEl, nameCell, scoreEl] = cells;
const nameEl = nameCell.children.find(el => el.name === 'a');
const isDead = !!nameCell.children.find(el => el.name === 'img');
const players: PlayerActivityRow[] = [];
playersHTML.forEach(row => {
const rankEl = row.querySelector('td');
const nameEl = row.querySelector('td a');
const scoreEl = row.querySelector('td.left + td');
const isDead = !!row.querySelector('td img');
return {
players.push({
name: rsnFromElement(nameEl),
rank: numberFromElement(rankEl),
score: numberFromElement(scoreEl),
dead: isDead,
};
});
});
return players;
@@ -212,11 +218,14 @@ export async function getRSNFormat(rsn: string): Promise<string> {
const url = getPlayerTableURL('main', rsn);
try {
const response = await axios(url);
const $ = cheerio.load(response.data);
const rawName = $('[style="color:#AA0022;"]')[1].children[0].data;
if (rawName) {
return rawName.replace(/\uFFFD/g, ' ');
const 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 {
@@ -256,7 +265,7 @@ export function parseStats(csv: string): Stats {
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 [lastManStanding, soulWarsZeal] = activityObjects.splice(0, 2);
const bossObjects = activityObjects.splice(0, BOSSES.length);
const skills: Skills = skillObjects.reduce<Skills>((prev, curr, index) => {
@@ -288,6 +297,7 @@ export function parseStats(csv: string): Stats {
leaguePoints,
bountyHunter,
lastManStanding,
soulWarsZeal,
clues,
bosses,
};

View File

@@ -114,6 +114,7 @@ export type ActivityName =
| 'hunterBH'
| 'rogueBH'
| 'lastManStanding'
| 'soulWarsZeal'
| 'allClues'
| 'beginnerClues'
| 'easyClues'
@@ -129,6 +130,7 @@ export interface Stats {
leaguePoints: Activity;
bountyHunter: BH;
lastManStanding: Activity;
soulWarsZeal: Activity;
bosses: Bosses;
}
export type Modes = { [M in Gamemode]?: Stats };

View File

@@ -59,7 +59,7 @@ export const CLUES: ClueType[] = [
'elite',
'master',
];
export const BH_MODES: BHType[] = ['rogue', 'hunter'];
export const BH_MODES: BHType[] = ['hunter', 'rogue'];
export const GAMEMODES: Gamemode[] = [
'main',
'ironman',
@@ -127,6 +127,7 @@ export const ACTIVITIES: ActivityName[] = [
'eliteClues',
'masterClues',
'lastManStanding',
'soulWarsZeal',
...BOSSES,
];
@@ -236,4 +237,5 @@ export const FORMATTED_BH_NAMES: FormattedBHNames = {
};
export const FORMATTED_LMS = 'Last Man Standing';
export const FORMATTED_SOUL_WARS = 'Soul Wars Zeal';
export const FORMATTED_LEAGUE_POINTS = 'League Points';

View File

@@ -1,3 +1,5 @@
import axios from 'axios';
import * as ua from 'useragent-generator';
import { Gamemode, SkillName, ActivityName } from '../types';
import {
GAMEMODE_URL,
@@ -35,13 +37,22 @@ export const getActivityPageURL = (
activity
)}&page=${page}`;
export const numberFromElement = (el: CheerioElement) => {
const innerText = el.firstChild.data;
const number = innerText ? innerText.replace(/[\n|,]/g, '') : '-1';
export const numberFromElement = (el: Element | null) => {
const { innerHTML } = el || {};
const number = innerHTML?.replace(/[\n|,]/g, '') ?? '-1';
return parseInt(number, 10);
};
export const rsnFromElement = (el: CheerioElement | undefined) => {
const innerText = el?.firstChild.data;
return innerText ? innerText.replace(/\uFFFD/g, ' ') : '';
export const rsnFromElement = (el: Element | null) => {
const { innerHTML } = el || {};
return innerHTML?.replace(/\uFFFD/g, ' ') || '';
};
export const httpGet = (url: string) => {
return axios.get(url, {
headers: {
// without User-Agent header requests may be rejected by DDoS protection mechanism
'User-Agent': ua.firefox(80)
}
});
};

4115
yarn.lock

File diff suppressed because it is too large Load Diff