Compare commits

..

11 Commits

Author SHA1 Message Date
maxswa
58e8eaad59 v1.2.2 2019-07-15 09:48:13 -04:00
Max Swartwout
6b6d561a1f Merge pull request #2 from maxswa/dependabot/npm_and_yarn/lodash-4.17.14
Bump lodash from 4.17.11 to 4.17.14
2019-07-15 09:44:30 -04:00
dependabot[bot]
26aefe95a0 Bump lodash from 4.17.11 to 4.17.14
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.11 to 4.17.14.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.11...4.17.14)

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

View File

@@ -2,7 +2,7 @@
[![npm](https://img.shields.io/npm/v/osrs-json-hiscores.svg?style=flat-square)](https://www.npmjs.com/package/osrs-json-hiscores) [![npm](https://img.shields.io/npm/v/osrs-json-hiscores.svg?style=flat-square)](https://www.npmjs.com/package/osrs-json-hiscores)
[![downloads](https://img.shields.io/npm/dm/osrs-json-hiscores.svg?style=flat-square)](https://npm-stat.com/charts.html?package=osrs-json-hiscores) [![downloads](https://img.shields.io/npm/dm/osrs-json-hiscores.svg?style=flat-square)](https://npm-stat.com/charts.html?package=osrs-json-hiscores)
[![types](https://img.shields.io/npm/types/osrs-json-hiscores.svg?style=flat-square)](https://github.com/maxswa/osrs-json-hiscores/src/types.ts) [![types](https://img.shields.io/npm/types/osrs-json-hiscores.svg?style=flat-square)](https://github.com/maxswa/osrs-json-hiscores/blob/master/src/types.ts)
**The Oldschool Runescape API wrapper that does more!** **The Oldschool Runescape API wrapper that does more!**
@@ -15,6 +15,14 @@ Additional functions are provided that screen-scrape the OSRS leaderboards and r
`osrs-json-hiscores` has TypeScript support, with full definitions for all functions and custom data types. `osrs-json-hiscores` has TypeScript support, with full definitions for all functions and custom data types.
---
### Disclaimer
Jagex does not provide `Access-Control-Allow-Origin` headers in their responses. This means that [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) will block all browser requests to their hiscores API. In order to get around this, osrs-json-hiscores should be installed on the server side and exposed to the front end via a simple API. Here is an example of this in use: [codesandbox.io/s/osrs-json-hiscores-demo](https://codesandbox.io/s/osrs-json-hiscores-demo-qz656)
---
## Installation ## Installation
With npm: With npm:
@@ -41,7 +49,7 @@ Once you import it you can call the functions asynchronously:
```javascript ```javascript
hiscores hiscores
.getStats('Lynx Titan', 'full') .getStats('Lynx Titan')
.then(res => console.log(res)) .then(res => console.log(res))
.catch(err => console.error(err)); .catch(err => console.error(err));
``` ```
@@ -57,7 +65,8 @@ const stats = await hiscores.getStats('Lynx Titan');
const topPage = await getSkillPage('overall'); const topPage = await getSkillPage('overall');
``` ```
`getStats` will return a `full` player object with game mode by default, but it will also accept any of the following game modes: `getStats` will return a full player object with gamemode.
`getStatsByGameMode` will return a stats object and accepts a gamemode parameter:
| Game mode | Param | | Game mode | Param |
| ---------------- | :----: | | ---------------- | :----: |

View File

@@ -1,5 +1,11 @@
import { parseStats, getRSNFormat, getSkillPage, getStats } from '../src/index'; import {
import { PlayerSkillRow, Player } from '../src/types'; parseStats,
getRSNFormat,
getSkillPage,
getStats,
getStatsByGamemode,
} from '../src/index';
import { PlayerSkillRow, Player, Stats } from '../src/types';
import axios, { AxiosError } from 'axios'; import axios, { AxiosError } from 'axios';
test('Parse CSV to json', () => { test('Parse CSV to json', () => {
@@ -93,10 +99,16 @@ test('Get rsn format', async done => {
test('Get attack top page', async done => { test('Get attack top page', async done => {
const callback = (data: PlayerSkillRow[]) => { const callback = (data: PlayerSkillRow[]) => {
expect(data).toStrictEqual([ expect(data).toMatchObject([
{ rsn: 'Heur', rank: 1, level: 99, xp: 200000000, dead: false },
{ {
rsn: 'Unohdettu2', rsn: expect.any(String),
rank: 1,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 2, rank: 2,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
@@ -104,105 +116,159 @@ test('Get attack top page', async done => {
}, },
{ rsn: 'Drakon', rank: 3, level: 99, xp: 200000000, dead: false }, { rsn: 'Drakon', rank: 3, level: 99, xp: 200000000, dead: false },
{ {
rsn: 'Ame Umehara', rsn: expect.any(String),
rank: 4, rank: 4,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false,
}, },
{ rsn: 'Jakee', rank: 5, level: 99, xp: 200000000, dead: false },
{ rsn: 'Hitsuji', rank: 6, level: 99, xp: 200000000, dead: false },
{ rsn: 'Howson', rank: 7, level: 99, xp: 200000000, dead: false },
{ rsn: 'Dr PFAFF', rank: 8, level: 99, xp: 200000000, dead: false },
{ {
rsn: 'Malt Lickeys', rsn: expect.any(String),
rank: 5,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 6,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 7,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 8,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 9, rank: 9,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false,
}, },
{ rsn: 'Burned', rank: 10, level: 99, xp: 200000000, dead: false },
{ {
rsn: 'Blue Limes', rsn: expect.any(String),
rank: 10,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 11, rank: 11,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false,
}, },
{ {
rsn: 'Mini Finbarr', rsn: expect.any(String),
rank: 12, rank: 12,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false,
}, },
{ {
rsn: 'Unohdettu3', rsn: expect.any(String),
rank: 13, rank: 13,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false,
}, },
{ {
rsn: 'Eslihero', rsn: expect.any(String),
rank: 14, rank: 14,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false,
}, },
{ {
rsn: 'Lynx Titan', rsn: expect.any(String),
rank: 15, rank: 15,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false,
}, },
{ {
rsn: 'AndrewWigins', rsn: expect.any(String),
rank: 16, rank: 16,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false,
}, },
{ rsn: 'iMelee', rank: 17, level: 99, xp: 200000000, dead: false },
{ {
rsn: 'Portuguese', rsn: expect.any(String),
rank: 17,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 18, rank: 18,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false,
}, },
{ {
rsn: 'MarkoOSRS', rsn: expect.any(String),
rank: 19, rank: 19,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false,
}, },
{ rsn: 'Cairo', rank: 20, level: 99, xp: 200000000, dead: false },
{ {
rsn: 'Hey Jase', rsn: expect.any(String),
rank: 20,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 21, rank: 21,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false,
}, },
{ {
rsn: 'Sleighur', rsn: expect.any(String),
rank: 22, rank: 22,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false,
}, },
{ {
rsn: 'KMSat200mALL', rsn: expect.any(String),
rank: 23, rank: 23,
level: 99, level: 99,
xp: 200000000, xp: 200000000,
dead: false, dead: false,
}, },
{ rsn: 'Yumemi', rank: 24, level: 99, xp: 200000000, dead: false }, {
{ rsn: 'Fiiggy', rank: 25, level: 99, xp: 200000000, dead: false }, rsn: expect.any(String),
rank: 24,
level: 99,
xp: 200000000,
dead: false,
},
{
rsn: expect.any(String),
rank: 25,
level: 99,
xp: 200000000,
dead: false,
},
]); ]);
done(); done();
}; };
@@ -220,3 +286,37 @@ test('Get non-existant player', async done => {
getStats('fishy').catch(callback); getStats('fishy').catch(callback);
}); });
test('Get stats by gamemode', async done => {
const callback = (stats: Stats) => {
expect(stats.skills).toStrictEqual({
overall: { rank: 1, level: 2277, xp: 4600000000 },
attack: { rank: 15, level: 99, xp: 200000000 },
defence: { rank: 27, level: 99, xp: 200000000 },
strength: { rank: 18, level: 99, xp: 200000000 },
hitpoints: { rank: 7, level: 99, xp: 200000000 },
ranged: { rank: 7, level: 99, xp: 200000000 },
prayer: { rank: 11, level: 99, xp: 200000000 },
magic: { rank: 32, level: 99, xp: 200000000 },
cooking: { rank: 158, level: 99, xp: 200000000 },
woodcutting: { rank: 15, level: 99, xp: 200000000 },
fletching: { rank: 12, level: 99, xp: 200000000 },
fishing: { rank: 9, level: 99, xp: 200000000 },
firemaking: { rank: 49, level: 99, xp: 200000000 },
crafting: { rank: 4, level: 99, xp: 200000000 },
smithing: { rank: 3, level: 99, xp: 200000000 },
mining: { rank: 25, level: 99, xp: 200000000 },
herblore: { rank: 5, level: 99, xp: 200000000 },
agility: { rank: 24, level: 99, xp: 200000000 },
thieving: { rank: 12, level: 99, xp: 200000000 },
slayer: { rank: 2, level: 99, xp: 200000000 },
farming: { rank: 19, level: 99, xp: 200000000 },
runecraft: { rank: 7, level: 99, xp: 200000000 },
hunter: { rank: 4, level: 99, xp: 200000000 },
construction: { rank: 4, level: 99, xp: 200000000 },
});
done();
};
getStatsByGamemode('Lynx Titan').then(callback);
});

View File

@@ -1,6 +1,6 @@
{ {
"name": "osrs-json-hiscores", "name": "osrs-json-hiscores",
"version": "1.2.0", "version": "1.2.2",
"description": "The Oldschool Runescape API wrapper that does more!", "description": "The Oldschool Runescape API wrapper that does more!",
"main": "lib/index.js", "main": "lib/index.js",
"types": "lib/index.d.ts", "types": "lib/index.d.ts",

View File

@@ -2,12 +2,11 @@ import axios from 'axios';
import * as cheerio from 'cheerio'; import * as cheerio from 'cheerio';
import { import {
Player, Player,
Mode,
Activity, Activity,
Skill, Skill,
Stats, Stats,
Skills, Skills,
BH as BHStats, BH,
Clues, Clues,
Gamemode, Gamemode,
SkillName, SkillName,
@@ -20,7 +19,6 @@ import {
SKILLS, SKILLS,
BH_MODES, BH_MODES,
CLUES, CLUES,
MODES,
getPlayerTableURL, getPlayerTableURL,
getSkillPageURL, getSkillPageURL,
GAMEMODES, GAMEMODES,
@@ -30,92 +28,93 @@ import {
getActivityPageURL, getActivityPageURL,
} from './utils'; } from './utils';
export async function getStats( export async function getStats(rsn: string): Promise<Player> {
rsn: string,
mode: Mode = 'full'
): Promise<Player> {
if (typeof rsn !== 'string') { if (typeof rsn !== 'string') {
throw Error('RSN must be a string'); throw Error('RSN must be a string');
} else if (!/^[a-zA-Z0-9 _]+$/.test(rsn)) { } else if (!/^[a-zA-Z0-9 _]+$/.test(rsn)) {
throw Error('RSN contains invalid character'); throw Error('RSN contains invalid character');
} else if (rsn.length > 12 || rsn.length < 1) { } else if (rsn.length > 12 || rsn.length < 1) {
throw Error('RSN must be between 1 and 12 characters'); throw Error('RSN must be between 1 and 12 characters');
} else if (!MODES.includes(mode)) {
throw Error('Invalid game mode');
} }
if (mode === 'full') { const mainRes = await axios(getStatsURL('main', rsn));
const mainRes = await axios(getStatsURL('main', rsn)); if (mainRes.status === 200) {
if (mainRes.status === 200) { const otherResponses = await Promise.all([
const otherResponses = await Promise.all([ axios(getStatsURL('iron', rsn)).catch(err => err),
axios(getStatsURL('iron', rsn)).catch(err => err), axios(getStatsURL('hc', rsn)).catch(err => err),
axios(getStatsURL('hc', rsn)).catch(err => err), axios(getStatsURL('ult', rsn)).catch(err => err),
axios(getStatsURL('ult', rsn)).catch(err => err), getRSNFormat(rsn),
getRSNFormat(rsn), ]);
]);
const [ironRes, hcRes, ultRes, formattedName] = otherResponses; const [ironRes, hcRes, ultRes, formattedName] = otherResponses;
const player: Player = {
rsn: formattedName,
mode: 'main',
dead: false,
deulted: false,
deironed: false,
};
player.main = parseStats(mainRes.data);
if (ironRes.status === 200) {
player.iron = parseStats(ironRes.data);
if (hcRes.status === 200) {
player.mode = 'hc';
player.hc = parseStats(hcRes.data);
if (player.iron.skills.overall.xp !== player.hc.skills.overall.xp) {
player.dead = true;
player.mode = 'iron';
}
if (player.main.skills.overall.xp !== player.iron.skills.overall.xp) {
player.deironed = true;
player.mode = 'main';
}
} else if (ultRes.status === 200) {
player.mode = 'ult';
player.ult = parseStats(ultRes.data);
if (player.iron.skills.overall.xp !== player.ult.skills.overall.xp) {
player.deulted = true;
player.mode = 'iron';
}
if (player.main.skills.overall.xp !== player.iron.skills.overall.xp) {
player.deironed = true;
player.mode = 'main';
}
} else {
player.mode = 'iron';
if (player.main.skills.overall.xp !== player.iron.skills.overall.xp) {
player.deironed = true;
player.mode = 'main';
}
}
}
return player;
}
throw Error('Player not found');
} else {
const response = await axios(getStatsURL(mode, rsn));
if (response.status !== 200) {
throw Error('Player not found');
}
const player: Player = { const player: Player = {
rsn, rsn: formattedName,
mode, mode: 'main',
dead: false, dead: false,
deulted: false, deulted: false,
deironed: false, deironed: false,
[mode]: parseStats(response.data),
}; };
player.main = parseStats(mainRes.data);
if (ironRes.status === 200) {
player.iron = parseStats(ironRes.data);
if (hcRes.status === 200) {
player.mode = 'hc';
player.hc = parseStats(hcRes.data);
if (player.iron.skills.overall.xp !== player.hc.skills.overall.xp) {
player.dead = true;
player.mode = 'iron';
}
if (player.main.skills.overall.xp !== player.iron.skills.overall.xp) {
player.deironed = true;
player.mode = 'main';
}
} else if (ultRes.status === 200) {
player.mode = 'ult';
player.ult = parseStats(ultRes.data);
if (player.iron.skills.overall.xp !== player.ult.skills.overall.xp) {
player.deulted = true;
player.mode = 'iron';
}
if (player.main.skills.overall.xp !== player.iron.skills.overall.xp) {
player.deironed = true;
player.mode = 'main';
}
} else {
player.mode = 'iron';
if (player.main.skills.overall.xp !== player.iron.skills.overall.xp) {
player.deironed = true;
player.mode = 'main';
}
}
}
return player; return player;
} }
throw Error('Player not found');
}
export async function getStatsByGamemode(
rsn: string,
mode: Gamemode = 'main'
): Promise<Stats> {
if (typeof rsn !== 'string') {
throw Error('RSN must be a string');
} else if (!/^[a-zA-Z0-9 _]+$/.test(rsn)) {
throw Error('RSN contains invalid character');
} else if (rsn.length > 12 || rsn.length < 1) {
throw Error('RSN must be between 1 and 12 characters');
} else if (!GAMEMODES.includes(mode)) {
throw Error('Invalid game mode');
}
const response = await axios(getStatsURL(mode, rsn));
if (response.status !== 200) {
throw Error('Player not found');
}
const stats: Stats = parseStats(response.data);
return stats;
} }
export async function getSkillPage( export async function getSkillPage(
@@ -219,10 +218,11 @@ export function parseStats(csv: string): Stats {
const skillObjects: Skill[] = splitCSV const skillObjects: Skill[] = splitCSV
.filter(stat => stat.length === 3) .filter(stat => stat.length === 3)
.map(stat => { .map(stat => {
const [rank, level, xp] = stat;
const skill: Skill = { const skill: Skill = {
rank: parseInt(stat[0], 10), rank: parseInt(rank, 10),
level: parseInt(stat[1], 10), level: parseInt(level, 10),
xp: parseInt(stat[2], 10), xp: parseInt(xp, 10),
}; };
return skill; return skill;
}); });
@@ -230,9 +230,10 @@ export function parseStats(csv: string): Stats {
const activityObjects: Activity[] = splitCSV const activityObjects: Activity[] = splitCSV
.filter(stat => stat.length === 2) .filter(stat => stat.length === 2)
.map(stat => { .map(stat => {
const [rank, score] = stat;
const activity: Activity = { const activity: Activity = {
rank: parseInt(stat[0], 10), rank: parseInt(rank, 10),
score: parseInt(stat[1], 10), score: parseInt(score, 10),
}; };
return activity; return activity;
}); });
@@ -250,13 +251,13 @@ export function parseStats(csv: string): Stats {
{} as Skills {} as Skills
); );
const bh: BHStats = bhObjects.reduce<BHStats>( const bh: BH = bhObjects.reduce<BH>(
(prev, curr, index) => { (prev, curr, index) => {
const newBH = { ...prev }; const newBH = { ...prev };
newBH[BH_MODES[index]] = curr; newBH[BH_MODES[index]] = curr;
return newBH; return newBH;
}, },
{} as BHStats {} as BH
); );
const clues: Clues = clueObjects.reduce<Clues>( const clues: Clues = clueObjects.reduce<Clues>(

View File

@@ -1,7 +1,5 @@
export type Gamemode = 'main' | 'iron' | 'hc' | 'ult' | 'dmm' | 'sdmm' | 'dmmt'; export type Gamemode = 'main' | 'iron' | 'hc' | 'ult' | 'dmm' | 'sdmm' | 'dmmt';
export type Mode = Gamemode | 'full';
export interface Skill { export interface Skill {
rank: number; rank: number;
level: number; level: number;

View File

@@ -1,4 +1,4 @@
import { SkillName, ClueType, BHType, Gamemode, Mode } from '../types'; import { SkillName, ClueType, BHType, Gamemode } from '../types';
export const BASE_URL = 'http://services.runescape.com/m=hiscore_oldschool'; export const BASE_URL = 'http://services.runescape.com/m=hiscore_oldschool';
export const STATS_URL = 'index_lite.ws?player='; export const STATS_URL = 'index_lite.ws?player=';
@@ -69,4 +69,3 @@ export const GAMEMODES: Gamemode[] = [
'sdmm', 'sdmm',
'dmmt', 'dmmt',
]; ];
export const MODES: Mode[] = [...GAMEMODES, 'full'];

View File

@@ -3089,9 +3089,9 @@ lodash.zip@^4.2.0:
integrity sha1-7GZi5IlkCO1KtsVCo5kLcswIACA= integrity sha1-7GZi5IlkCO1KtsVCo5kLcswIACA=
lodash@^4.15.0, lodash@^4.17.11, lodash@^4.3.0: lodash@^4.15.0, lodash@^4.17.11, lodash@^4.3.0:
version "4.17.11" version "4.17.14"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.14.tgz#9ce487ae66c96254fe20b599f21b6816028078ba"
integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== integrity sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==
log-symbols@^1.0.2: log-symbols@^1.0.2:
version "1.0.2" version "1.0.2"