Swap tslint for eslint.

This commit is contained in:
maxswa
2021-03-25 00:20:02 -04:00
parent 0b96530a8e
commit e3e904a23f
10 changed files with 1433 additions and 396 deletions

View File

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

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)
[![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
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).
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
hiscores
.getStats('Lynx Titan')
.then(res => console.log(res))
.catch(err => console.error(err));
.then((res) => console.log(res))
.catch((err) => console.error(err));
```
If you are using TypeScript or transpiling your JS you can use ES6 syntax:
@@ -83,8 +83,8 @@ const topPage = await getSkillPage('overall');
```javascript
hiscores
.getSkillPage('attack', 'main', 1)
.then(res => console.log(res))
.catch(err => console.error(err));
.then((res) => console.log(res))
.catch((err) => console.error(err));
```
Activities consist of all levels of clue scrolls as well as minigames and bosses:
@@ -200,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 },
{},
{},
{}
// ...
];
```

View File

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

View File

@@ -1,7 +1,7 @@
{
"name": "osrs-json-hiscores",
"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",
"types": "lib/index.d.ts",
"files": [
@@ -11,6 +11,7 @@
"dev": "watch 'yarn run build' src",
"build": "tsc",
"format": "prettier --write \"src/**/*.ts\"",
"lint": "eslint --fix \"src/**/*.ts\"",
"test": "jest --config jestconfig.json",
"prepublish": "yarn run build",
"release": "np"
@@ -35,22 +36,66 @@
"url": "https://github.com/maxswa/osrs-json-hiscores/issues"
},
"homepage": "https://github.com/maxswa/osrs-json-hiscores#readme",
"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": {
"axios": "^0.21.1",
"jsdom": "^16.3.0",
"useragent-generator": "^1.1.0"
},
"devDependencies": {
"@types/jest": "^26.0.20",
"@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",
"jest": "^26.6.3",
"np": "6.5.0",
"prettier": "^1.19.1",
"ts-jest": "^26.4.4",
"tslint": "^5.17.0",
"tslint-config-airbnb": "^5.11.1",
"tslint-config-prettier": "^1.18.0",
"typescript": "^3.5.2",
"prettier": "^2.2.1",
"ts-jest": "^26.5.4",
"typescript": "^4.2.3",
"watch": "^1.0.2"
}
}

View File

@@ -1,51 +1,109 @@
declare module 'useragent-generator' {
/********************
/** *****************
* Google Chrome *
/*******************/
export function chrome(opt: number | string | { version: string, os?: string }): string;
/****************** */
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 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;
function iOS(
opt:
| number
| string
| { iOSVersion: string; chromeVersion?: string; device?: string }
): string;
}
export function chromium(opt: number | string | { version: string, os?: 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 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;
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 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;
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 function ie(
opt: number | string | { version: string; os?: string }
): string;
export namespace ie {
function windowsPhone(opt: number | string | { version: string, device?: string }): string;
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;
/************************
/******************** */
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 googleBot(
opt?: number | string | { version?: string }
): string;
export function bingBot(opt?: number | string | { version?: string }): string;
export function yahooBot(): string;
}

View File

@@ -1,3 +1,4 @@
import { JSDOM } from 'jsdom';
import {
Player,
Activity,
@@ -11,7 +12,7 @@ import {
PlayerSkillRow,
ActivityName,
PlayerActivityRow,
Bosses,
Bosses
} from './types';
import {
getStatsURL,
@@ -26,9 +27,106 @@ import {
rsnFromElement,
getActivityPageURL,
httpGet,
BOSSES,
BOSSES
} from './utils';
import { JSDOM } from 'jsdom';
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');
}
}
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;
}
export async function getStats(rsn: string): Promise<Player> {
if (typeof rsn !== 'string') {
@@ -42,20 +140,20 @@ export async function getStats(rsn: string): Promise<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),
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,
name: formattedName ?? rsn,
mode: 'main',
dead: false,
deulted: false,
deironed: false,
deironed: false
};
player.main = parseStats(mainRes.data);
@@ -150,7 +248,7 @@ export async function getSkillPage(
);
const players: PlayerSkillRow[] = [];
playersHTML.forEach(row => {
playersHTML.forEach((row) => {
const rankEl = row.querySelector('td');
const nameEl = row.querySelector('td a');
const levelEl = row.querySelector('td.left + td');
@@ -162,7 +260,7 @@ export async function getSkillPage(
rank: numberFromElement(rankEl),
level: numberFromElement(levelEl),
xp: numberFromElement(xpEl),
dead: isDead,
dead: isDead
});
});
@@ -190,7 +288,7 @@ export async function getActivityPage(
);
const players: PlayerActivityRow[] = [];
playersHTML.forEach(row => {
playersHTML.forEach((row) => {
const rankEl = row.querySelector('td');
const nameEl = row.querySelector('td a');
const scoreEl = row.querySelector('td.left + td');
@@ -200,107 +298,9 @@ export async function getActivityPage(
name: rsnFromElement(nameEl),
rank: numberFromElement(rankEl),
score: numberFromElement(scoreEl),
dead: isDead,
dead: isDead
});
});
return players;
}
export async function getRSNFormat(rsn: string): Promise<string> {
if (typeof rsn !== 'string') {
throw Error('RSN must be a string');
} else if (!/^[a-zA-Z0-9 _]+$/.test(rsn)) {
throw Error('RSN contains invalid character');
} else if (rsn.length > 12 || rsn.length < 1) {
throw Error('RSN must be between 1 and 12 characters');
}
const url = getPlayerTableURL('main', rsn);
try {
const response = await 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');
}
}
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;
}

View File

@@ -4,7 +4,7 @@ import {
ClueType,
Gamemode,
SkillName,
ActivityName,
ActivityName
} from '../types';
export const BASE_URL = 'https://secure.runescape.com/m=hiscore_oldschool';
@@ -22,7 +22,7 @@ export const GAMEMODE_URL: GamemodeUrl = {
ultimate: `${BASE_URL}_ultimate/`,
deadman: `${BASE_URL}_deadman/`,
seasonal: `${BASE_URL}_seasonal/`,
tournament: `${BASE_URL}_tournament/`,
tournament: `${BASE_URL}_tournament/`
};
export const SKILLS: SkillName[] = [
'overall',
@@ -48,7 +48,7 @@ export const SKILLS: SkillName[] = [
'farming',
'runecraft',
'hunter',
'construction',
'construction'
];
export const CLUES: ClueType[] = [
'all',
@@ -57,7 +57,7 @@ export const CLUES: ClueType[] = [
'medium',
'hard',
'elite',
'master',
'master'
];
export const BH_MODES: BHType[] = ['hunter', 'rogue'];
export const GAMEMODES: Gamemode[] = [
@@ -67,7 +67,7 @@ export const GAMEMODES: Gamemode[] = [
'ultimate',
'deadman',
'seasonal',
'tournament',
'tournament'
];
export const BOSSES: Boss[] = [
'abyssalSire',
@@ -114,7 +114,7 @@ export const BOSSES: Boss[] = [
'vorkath',
'wintertodt',
'zalcano',
'zulrah',
'zulrah'
];
export const ACTIVITIES: ActivityName[] = [
'leaguePoints',
@@ -129,7 +129,7 @@ export const ACTIVITIES: ActivityName[] = [
'masterClues',
'lastManStanding',
'soulWarsZeal',
...BOSSES,
...BOSSES
];
export type FormattedBossNames = {
@@ -181,7 +181,7 @@ export const FORMATTED_BOSS_NAMES: FormattedBossNames = {
vorkath: 'Vorkath',
wintertodt: 'Wintertodt',
zalcano: 'Zalcano',
zulrah: 'Zulrah',
zulrah: 'Zulrah'
};
export type FormattedSkillNames = {
@@ -212,7 +212,7 @@ export const FORMATTED_SKILL_NAMES: FormattedSkillNames = {
farming: 'Farming',
runecraft: 'Runecraft',
hunter: 'Hunter',
construction: 'Construction',
construction: 'Construction'
};
export type FormattedClueNames = {
@@ -226,7 +226,7 @@ export const FORMATTED_CLUE_NAMES: FormattedClueNames = {
medium: 'Clue Scrolls (medium)',
hard: 'Clue Scrolls (hard)',
elite: 'Clue Scrolls (elite)',
master: 'Clue Scrolls (master)',
master: 'Clue Scrolls (master)'
};
export type FormattedBHNames = {
@@ -235,7 +235,7 @@ export type FormattedBHNames = {
export const FORMATTED_BH_NAMES: FormattedBHNames = {
rogue: 'Bounty Hunter - Rogue',
hunter: 'Bounty Hunter - Hunter',
hunter: 'Bounty Hunter - Hunter'
};
export const FORMATTED_LMS = 'Last Man Standing';

View File

@@ -6,7 +6,7 @@ import {
STATS_URL,
SCORES_URL,
SKILLS,
ACTIVITIES,
ACTIVITIES
} from './constants';
export const getStatsURL = (gamemode: Gamemode, rsn: string) =>
@@ -48,11 +48,10 @@ export const rsnFromElement = (el: Element | null) => {
return innerHTML?.replace(/\uFFFD/g, ' ') || '';
};
export const httpGet = (url: string) => {
return axios.get(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)
}
});
};

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

1347
yarn.lock

File diff suppressed because it is too large Load Diff