import * as cheerio from 'cheerio'; import { IncomingMessage } from 'http'; import http from 'https'; //making long regex rather than splitting the string at ":" because regex would be easier to debug than logic in the case the website changes //while names cant have spaces, the name slot in ogtitle could be shown as "No Player Name" which has spaces const uniteApiRegex = { //$1 = name, $2 = id ogtitle: /Unite API - ([\w\d ]+) \((.*)\)/, //$1 = level, $2 = rank, $3 = elo, $4 = battles, $5 = wins, $6 = win rate ogdescription: /Pokémon Unite : Lv.(\d+) (\w+) \((\d+)\)\n Battles : (\d+)\n Wins : (\d+)\n Win Rate : (\d+)%/, } type uniteApiData = { name: string, id: string, level: string, rank: string, elo: string, battles: string, wins: string, winrate: string } /* og:title Unite API - IanWhysp (X188GF7) og:description Pokémon Unite : Lv.40 Master (1741) Battles : 1089 Wins : 576 Win Rate : 52% */ /** * gets the html of the uniteApi page for the player * @param name name of player * @returns the html of the page through a promise (rejects if the page status is not 200) */ function getHTML(name: string): Promise { name = name.replace(/[^\w\d]/g, ''); return new Promise((resolve, reject) => { const init = { host: 'uniteapi.dev', path: `/p/${encodeURIComponent(name)}`, method: 'GET', }; const callback = (response: IncomingMessage) => { if (response.statusCode !== 200) { reject(`HTTP ERROR ${response.statusCode}: ${response.statusMessage}`); return; } let result = Buffer.alloc(0); response.on('data', function(chunk) { result = Buffer.concat([result, chunk]); }); response.on('end', function() { // result has response body buffer resolve(result.toString()); }); }; const req = http.request(init, callback); req.end(); }); } /** * interprets the html from getHTML() * @param name name of player * @returns player data from site */ function readHTML(html: string): uniteApiData { let metaElems = cheerio.load(html)('meta').toArray(), foundData: uniteApiData = { name: "", id: "", level: "", rank: "", elo: "", battles: "", wins: "", winrate: "" }; //filter down to just ones named "og:..." metaElems = metaElems.filter(el => el.attribs.property?.startsWith('og:')); metaElems.forEach(el => { let attr = el.attribs; if (attr.property === 'og:title') { let data = uniteApiRegex.ogtitle.exec(attr.content); if (data !== null && data.length >= 3) { foundData.name = data[1]; foundData.id = data[2]; } } else if (attr.property === 'og:description') { let data = uniteApiRegex.ogdescription.exec(attr.content); if (data !== null && data.length >= 7) { foundData.level = data[1]; foundData.rank = data[2]; foundData.elo = data[3]; foundData.battles = data[4]; foundData.wins = data[5]; foundData.winrate = data[6]; } } }) return foundData; } /** * verifies the data * @param data uniteApi data * @returns boolean, valid or invalid */ function verifyData(data: uniteApiData): boolean { if (data.id.length) return true; return false; } /** * gets player data from uniteApi * @param name name of player * @returns player data */ export async function getPlayer(name: string): Promise { let html = await getHTML(name); let data = readHTML(html); if (verifyData(data)) return data; return null; } //await getPlayer('IanWhysp')