osrs-json-hiscores
Version:
The Old School Runescape API wrapper that does more!
501 lines (500 loc) • 23.4 kB
JavaScript
;
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getActivityPage = exports.getSkillPage = exports.getStatsByGamemode = exports.getStats = exports.parseStats = exports.parseJsonStats = exports.getRSNFormat = exports.getOfficialStats = void 0;
var axios_1 = require("axios");
var jsdom_1 = require("jsdom");
var utils_1 = require("./utils");
/**
* Gets a player's stats from the official OSRS JSON endpoint.
*
* @param rsn Username of the player.
* @param mode Gamemode to fetch ranks for.
* @param config Optional axios request config object.
* @returns Official JSON stats object.
*/
function getOfficialStats(rsn, mode, config) {
var _a;
if (mode === void 0) { mode = 'main'; }
return __awaiter(this, void 0, void 0, function () {
var url, response, err_1;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
(0, utils_1.validateRSN)(rsn);
url = (0, utils_1.getStatsURL)(mode, rsn, true);
_b.label = 1;
case 1:
_b.trys.push([1, 3, , 4]);
return [4 /*yield*/, (0, utils_1.httpGet)(url, config)];
case 2:
response = _b.sent();
return [2 /*return*/, response.data];
case 3:
err_1 = _b.sent();
if (!axios_1.default.isAxiosError(err_1))
throw err_1;
if (((_a = err_1.response) === null || _a === void 0 ? void 0 : _a.status) === 404)
throw new utils_1.PlayerNotFoundError();
throw new utils_1.HiScoresError();
case 4: return [2 /*return*/];
}
});
});
}
exports.getOfficialStats = getOfficialStats;
/**
* Screen scrapes the hiscores to get the formatted rsn of a player.
*
* @param rsn Username of the player.
* @param config Optional axios request config object.
* @returns Formatted version of the rsn.
*/
function getRSNFormat(rsn, config, mode) {
if (mode === void 0) { mode = 'main'; }
return __awaiter(this, void 0, void 0, function () {
var url, response, dom, anchor, _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
(0, utils_1.validateRSN)(rsn);
url = (0, utils_1.getPlayerTableURL)(mode, rsn);
_b.label = 1;
case 1:
_b.trys.push([1, 3, , 4]);
return [4 /*yield*/, (0, utils_1.httpGet)(url, config)];
case 2:
response = _b.sent();
dom = new jsdom_1.JSDOM(response.data);
anchor = dom.window.document.querySelector('.personal-hiscores__row.personal-hiscores__row--type-highlight a');
if (anchor) {
return [2 /*return*/, (0, utils_1.rsnFromElement)(anchor)];
}
return [3 /*break*/, 4];
case 3:
_a = _b.sent();
throw new utils_1.HiScoresError();
case 4: throw new utils_1.PlayerNotFoundError();
}
});
});
}
exports.getRSNFormat = getRSNFormat;
/**
* Parses official JSON object of raw stats and returns a stats object.
*
* @param csv Raw JSON from the official OSRS API.
* @returns Parsed stats object.
*/
function parseJsonStats(json) {
var getActivity = function (formattedName) {
var _a, _b;
var hiscoresActivity = json.activities.find(
// We must match on name here since id is not guaranteed to be the same between updates
function (_a) {
var name = _a.name;
return name.toLowerCase() === formattedName.toLowerCase();
});
return {
rank: (_a = hiscoresActivity === null || hiscoresActivity === void 0 ? void 0 : hiscoresActivity.rank) !== null && _a !== void 0 ? _a : -1,
score: (_b = hiscoresActivity === null || hiscoresActivity === void 0 ? void 0 : hiscoresActivity.score) !== null && _b !== void 0 ? _b : -1
};
};
var reduceActivity = function (keys, formattedNames) {
return keys.reduce(function (reducer, key) {
var _a;
return (__assign(__assign({}, reducer), (_a = {}, _a[key] = getActivity(formattedNames[key]), _a)));
}, {});
};
var skills = utils_1.SKILLS.reduce(function (skillsObject, skillName) {
var _a;
var _b, _c, _d;
var hiscoresSkill = json.skills.find(
// We must match on name here since id is not guaranteed to be the same between updates
function (_a) {
var name = _a.name;
return name.toLowerCase() === utils_1.FORMATTED_SKILL_NAMES[skillName].toLowerCase();
});
return __assign(__assign({}, skillsObject), (_a = {}, _a[skillName] = {
rank: (_b = hiscoresSkill === null || hiscoresSkill === void 0 ? void 0 : hiscoresSkill.rank) !== null && _b !== void 0 ? _b : -1,
level: (_c = hiscoresSkill === null || hiscoresSkill === void 0 ? void 0 : hiscoresSkill.level) !== null && _c !== void 0 ? _c : -1,
xp: (_d = hiscoresSkill === null || hiscoresSkill === void 0 ? void 0 : hiscoresSkill.xp) !== null && _d !== void 0 ? _d : -1
}, _a));
}, {});
var bountyHunter = reduceActivity(utils_1.BH_MODES, utils_1.FORMATTED_BH_NAMES);
var clues = reduceActivity(utils_1.CLUES, utils_1.FORMATTED_CLUE_NAMES);
var bosses = reduceActivity(utils_1.BOSSES, utils_1.FORMATTED_BOSS_NAMES);
var leaguePoints = getActivity(utils_1.FORMATTED_LEAGUE_POINTS);
var deadmanPoints = getActivity(utils_1.FORMATTED_DEADMAN_POINTS);
var lastManStanding = getActivity(utils_1.FORMATTED_LMS);
var pvpArena = getActivity(utils_1.FORMATTED_PVP_ARENA);
var soulWarsZeal = getActivity(utils_1.FORMATTED_SOUL_WARS);
var riftsClosed = getActivity(utils_1.FORMATTED_RIFTS_CLOSED);
var colosseumGlory = getActivity(utils_1.FORMATTED_COLOSSEUM_GLORY);
var collectionsLogged = getActivity(utils_1.FORMATTED_COLLECTIONS_LOGGED);
var stats = {
skills: skills,
leaguePoints: leaguePoints,
deadmanPoints: deadmanPoints,
bountyHunter: bountyHunter,
lastManStanding: lastManStanding,
pvpArena: pvpArena,
soulWarsZeal: soulWarsZeal,
riftsClosed: riftsClosed,
colosseumGlory: colosseumGlory,
collectionsLogged: collectionsLogged,
clues: clues,
bosses: bosses
};
return stats;
}
exports.parseJsonStats = parseJsonStats;
/**
* Parses CSV string of raw stats and returns a stats object.
*
* @param csv Raw CSV from the official OSRS API.
* @returns Parsed stats object.
*/
function parseStats(csv) {
var splitCSV = csv
.split('\n')
.filter(function (entry) { return !!entry; })
.map(function (stat) { return stat.split(','); });
if (splitCSV.length !== utils_1.SKILLS.length + utils_1.ACTIVITIES.length) {
throw new utils_1.InvalidFormatError();
}
var skillObjects = splitCSV
.filter(function (stat) { return stat.length === 3; })
.map(function (stat) {
var rank = stat[0], level = stat[1], xp = stat[2];
var skill = {
rank: parseInt(rank, 10),
level: parseInt(level, 10),
xp: parseInt(xp, 10)
};
return skill;
});
var activityObjects = splitCSV
.filter(function (stat) { return stat.length === 2; })
.map(function (stat) {
var rank = stat[0], score = stat[1];
var activity = {
rank: parseInt(rank, 10),
score: parseInt(score, 10)
};
return activity;
});
var _a = activityObjects.splice(0, 2), leaguePoints = _a[0], deadmanPoints = _a[1];
var bhObjects = activityObjects.splice(0, utils_1.BH_MODES.length);
var clueObjects = activityObjects.splice(0, utils_1.CLUES.length);
var _b = activityObjects.splice(0, 6), lastManStanding = _b[0], pvpArena = _b[1], soulWarsZeal = _b[2], riftsClosed = _b[3], colosseumGlory = _b[4], collectionsLogged = _b[5];
var bossObjects = activityObjects.splice(0, utils_1.BOSSES.length);
var skills = skillObjects.reduce(function (prev, curr, index) {
var newSkills = __assign({}, prev);
newSkills[utils_1.SKILLS[index]] = curr;
return newSkills;
}, {});
var bountyHunter = bhObjects.reduce(function (prev, curr, index) {
var newBH = __assign({}, prev);
newBH[utils_1.BH_MODES[index]] = curr;
return newBH;
}, {});
var clues = clueObjects.reduce(function (prev, curr, index) {
var newClues = __assign({}, prev);
newClues[utils_1.CLUES[index]] = curr;
return newClues;
}, {});
var bosses = bossObjects.reduce(function (prev, curr, index) {
var newBosses = __assign({}, prev);
newBosses[utils_1.BOSSES[index]] = curr;
return newBosses;
}, {});
var stats = {
skills: skills,
leaguePoints: leaguePoints,
deadmanPoints: deadmanPoints,
bountyHunter: bountyHunter,
lastManStanding: lastManStanding,
pvpArena: pvpArena,
soulWarsZeal: soulWarsZeal,
riftsClosed: riftsClosed,
colosseumGlory: colosseumGlory,
collectionsLogged: collectionsLogged,
clues: clues,
bosses: bosses
};
return stats;
}
exports.parseStats = parseStats;
/**
* 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.
*/
function getStats(rsn, options) {
var _a, _b, _c, _d;
return __awaiter(this, void 0, void 0, function () {
var otherGamemodes, shouldGetFormattedRsn, main, getModeStats, formattedName, _e, player, iron, hc, ult;
var _this = this;
return __generator(this, function (_f) {
switch (_f.label) {
case 0:
(0, utils_1.validateRSN)(rsn);
otherGamemodes = (_a = options === null || options === void 0 ? void 0 : options.otherGamemodes) !== null && _a !== void 0 ? _a : [
'ironman',
'hardcore',
'ultimate'
];
shouldGetFormattedRsn = (_b = options === null || options === void 0 ? void 0 : options.shouldGetFormattedRsn) !== null && _b !== void 0 ? _b : true;
return [4 /*yield*/, getOfficialStats(rsn, 'main', (_c = options === null || options === void 0 ? void 0 : options.axiosConfigs) === null || _c === void 0 ? void 0 : _c.main)];
case 1:
main = _f.sent();
getModeStats = function (mode) { return __awaiter(_this, void 0, void 0, function () {
var _a;
return __generator(this, function (_b) {
return [2 /*return*/, otherGamemodes.includes(mode)
? getOfficialStats(rsn, mode, (_a = options === null || options === void 0 ? void 0 : options.axiosConfigs) === null || _a === void 0 ? void 0 : _a[mode]).catch(function () { return undefined; })
: undefined];
});
}); };
if (!shouldGetFormattedRsn) return [3 /*break*/, 3];
return [4 /*yield*/, getRSNFormat(rsn, (_d = options === null || options === void 0 ? void 0 : options.axiosConfigs) === null || _d === void 0 ? void 0 : _d.rsn).catch(function () { return undefined; })];
case 2:
_e = _f.sent();
return [3 /*break*/, 4];
case 3:
_e = undefined;
_f.label = 4;
case 4:
formattedName = _e;
player = {
name: formattedName !== null && formattedName !== void 0 ? formattedName : rsn,
mode: 'main',
dead: false,
deulted: false,
deironed: false
};
player.main = parseJsonStats(main);
return [4 /*yield*/, getModeStats('ironman')];
case 5:
iron = _f.sent();
if (!iron) return [3 /*break*/, 8];
player.ironman = parseJsonStats(iron);
return [4 /*yield*/, getModeStats('hardcore')];
case 6:
hc = _f.sent();
return [4 /*yield*/, getModeStats('ultimate')];
case 7:
ult = _f.sent();
if (hc) {
player.mode = 'hardcore';
player.hardcore = parseJsonStats(hc);
if (player.ironman.skills.overall.xp !== player.hardcore.skills.overall.xp) {
player.dead = true;
player.mode = 'ironman';
}
if (player.main.skills.overall.xp !== player.ironman.skills.overall.xp) {
player.deironed = true;
player.mode = 'main';
}
}
else if (ult) {
player.mode = 'ultimate';
player.ultimate = parseJsonStats(ult);
if (player.ironman.skills.overall.xp !== player.ultimate.skills.overall.xp) {
player.deulted = true;
player.mode = 'ironman';
}
if (player.main.skills.overall.xp !== player.ironman.skills.overall.xp) {
player.deironed = true;
player.mode = 'main';
}
}
else {
player.mode = 'ironman';
if (player.main.skills.overall.xp !== player.ironman.skills.overall.xp) {
player.deironed = true;
player.mode = 'main';
}
}
_f.label = 8;
case 8: return [2 /*return*/, player];
}
});
});
}
exports.getStats = getStats;
/**
* 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.
* @param config Optional axios request config object.
* @returns Stats object.
*/
function getStatsByGamemode(rsn, mode, config) {
if (mode === void 0) { mode = 'main'; }
return __awaiter(this, void 0, void 0, function () {
var response, stats;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
(0, utils_1.validateRSN)(rsn);
if (!utils_1.GAMEMODES.includes(mode)) {
throw Error('Invalid game mode');
}
return [4 /*yield*/, getOfficialStats(rsn, mode, config)];
case 1:
response = _a.sent();
stats = parseJsonStats(response);
return [2 /*return*/, stats];
}
});
});
}
exports.getStatsByGamemode = getStatsByGamemode;
function getSkillPage(skill, mode, page, config) {
if (mode === void 0) { mode = 'main'; }
if (page === void 0) { page = 1; }
return __awaiter(this, void 0, void 0, function () {
var url, response, dom, playersHTML, players;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!utils_1.GAMEMODES.includes(mode)) {
throw Error('Invalid game mode');
}
else if (!Number.isInteger(page) || page < 1) {
throw Error('Page must be an integer greater than 0');
}
else if (!utils_1.SKILLS.includes(skill)) {
throw Error('Invalid skill');
}
url = (0, utils_1.getSkillPageURL)(mode, skill, page);
return [4 /*yield*/, (0, utils_1.httpGet)(url, config)];
case 1:
response = _a.sent();
dom = new jsdom_1.JSDOM(response.data);
playersHTML = dom.window.document.querySelectorAll('.personal-hiscores__row');
players = [];
playersHTML.forEach(function (row) {
var rankEl = row.querySelector('td');
var nameEl = row.querySelector('td a');
var levelEl = row.querySelector('td.left + td');
var xpEl = row.querySelector('td.left + td + td');
var isDead = !!row.querySelector('td img');
players.push({
name: (0, utils_1.rsnFromElement)(nameEl),
rank: (0, utils_1.numberFromElement)(rankEl),
level: (0, utils_1.numberFromElement)(levelEl),
xp: (0, utils_1.numberFromElement)(xpEl),
dead: isDead
});
});
return [2 /*return*/, players];
}
});
});
}
exports.getSkillPage = getSkillPage;
/**
* 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.
* @param config Optional axios request config object.
* @returns Array of `PlayerActivityRow` objects.
*/
function getActivityPage(activity, mode, page, config) {
if (mode === void 0) { mode = 'main'; }
if (page === void 0) { page = 1; }
return __awaiter(this, void 0, void 0, function () {
var url, response, dom, playersHTML, players;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!utils_1.GAMEMODES.includes(mode)) {
throw Error('Invalid game mode');
}
else if (!Number.isInteger(page) || page < 1) {
throw Error('Page must be an integer greater than 0');
}
else if (!utils_1.ACTIVITIES.includes(activity)) {
throw Error('Invalid activity');
}
url = (0, utils_1.getActivityPageURL)(mode, activity, page);
return [4 /*yield*/, (0, utils_1.httpGet)(url, config)];
case 1:
response = _a.sent();
dom = new jsdom_1.JSDOM(response.data);
playersHTML = dom.window.document.querySelectorAll('.personal-hiscores__row');
players = [];
playersHTML.forEach(function (row) {
var rankEl = row.querySelector('td');
var nameEl = row.querySelector('td a');
var scoreEl = row.querySelector('td.left + td');
var isDead = !!row.querySelector('td img');
players.push({
name: (0, utils_1.rsnFromElement)(nameEl),
rank: (0, utils_1.numberFromElement)(rankEl),
score: (0, utils_1.numberFromElement)(scoreEl),
dead: isDead
});
});
return [2 /*return*/, players];
}
});
});
}
exports.getActivityPage = getActivityPage;