diff --git a/package.json b/package.json index f8f15ca..990598c 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "copy-webpack-plugin": "^4.0.1", "cron": "^1.2.1", "css-loader": "^0.28.4", + "delay": "^2.0.0", "dotenv": "^4.0.0", "extract-text-webpack-plugin": "^3.0.0", "file-loader": "^0.11.2", diff --git a/src/updater/retrieveWeapons.js b/src/updater/retrieveWeapons.js new file mode 100644 index 0000000..2967cb2 --- /dev/null +++ b/src/updater/retrieveWeapons.js @@ -0,0 +1,55 @@ +const delay = require('delay'); +const splatnet = require('./splatnet'); + +/** + * The SplatNet 2 API doesn't currently provide a way to retrieve weapon data directly. + * There are, however, several API endpoints that provide weapon data along with their results. + * + * This function retrieves recent league match rankings and parses weapon data from those results. + * This data is provided in blocks of 2 hours. Generally, retrieving the last 2-3 blocks of data + * is enough to retrieve all currently-available weapons. + */ + +module.exports = async function retrieveWeapons(iterations = 1, useDelay = true) { + // Start from "now" and work backwards + let date = new Date(); + date.setUTCMinutes(0); + date.setUTCSeconds(0); + + // Hours must be in multiples of 2 + if (date.getUTCHours() % 2 == 1) + date.setUTCHours(date.getUTCHours() - 1); + + let weapons = {}; + + // We'll retrieve up to the number of iterations specified of result data. + // For example: 2017-09-01 06:00, then 2017-09-01 04:00, then 2017-09-01 02:00, etc. + + for (let i = 0; i < iterations; i++) { + // Start with the previous time block (2 hours ago) even on the first run + date.setUTCHours(date.getUTCHours() - 2); + + let year = date.getUTCFullYear(); + let month = date.getUTCMonth() + 1; + let day = date.getUTCDate(); + let hour = date.getUTCHours(); + + // Get both team and pair rankings + console.info(`Getting league rankings for ${year}-${month}-${day} hour ${hour}...`); + let teamRanking = await splatnet.getLeagueMatchRanking(year, month, day, hour, 'T'); + let pairRanking = await splatnet.getLeagueMatchRanking(year, month, day, hour, 'P'); + + // Process the results + for (let ranking of [teamRanking, pairRanking]) { + for (rankInfo of ranking.rankings) { + for (player of rankInfo.tag_members) + weapons[player.weapon.id] = player.weapon; + } + } + + if (useDelay && (i + 1) < iterations) + await delay(500); + } + + return weapons; +} diff --git a/src/updater/splatnet.js b/src/updater/splatnet.js index 9bda066..fb50a26 100644 --- a/src/updater/splatnet.js +++ b/src/updater/splatnet.js @@ -50,6 +50,24 @@ async function getMerchandises() { return response.data; } +async function getLeagueMatchRanking(year, month, day, hour, type = 'T', region = 'ALL') { + // Hour should be in multiples of 2, e.g., 00, 02, 04, ..., 22. + // Type should be 'T' (team) or 'P' (pair). + // Region should be 'ALL', 'JP', 'US', or 'EU'. + + // Make sure year is specified as two digits + year = year % 100; + + // Make sure we have leading zeros + year = ('0' + year).substr(-2); + month = ('0' + month).substr(-2); + day = ('0' + day).substr(-2); + hour = ('0' + hour).substr(-2); + + let response = await api.get(`league_match_ranking/${year}${month}${day}${hour}${type}/${region}`); + return response.data; +} + async function getImage(imagePath) { let response = await axios.get(`${splatnetBaseUrl}${imagePath}`, { responseType: 'arraybuffer' }); return response.data; @@ -62,5 +80,6 @@ module.exports = { getEUFestivals, getJPFestivals, getMerchandises, + getLeagueMatchRanking, getImage, } diff --git a/src/updater/update.js b/src/updater/update.js index 0dccff7..73ad65b 100644 --- a/src/updater/update.js +++ b/src/updater/update.js @@ -3,6 +3,7 @@ const path = require('path'); const fs = require('fs'); const mkdirp = require('mkdirp'); const splatnet = require('./splatnet'); +const retrieveWeapons = require('./retrieveWeapons'); const salmoncalendar = require('./salmoncalendar'); const raven = require('raven'); @@ -100,6 +101,40 @@ async function updateMerchandises() { } } +async function updateWeapons() { + // This one is handled a little differently since we need to add to the existing data + // instead of just overwriting it. We don't have a way to retrieve a complete set + // of weapon data, so we have to make sure not to lose any weapons we already know about. + + let filename = `${dataPath}/weapons.json`; + + // Look for weapons used over the past 24 hours if we don't have an existing list of weapons. + let iterations = 12; + let weapons = {}; + + if (fs.existsSync(filename)) { + weapons = JSON.parse(fs.readFileSync(filename)); + + // If we already have a weapons file, only retrieve the most recent results + iterations = 1; + } + + // We're now ready to update the weapons + await handleRequest({ + title: 'weapons', + filename, + request: retrieveWeapons(iterations), + transformer: responseData => { + // Add the new weapons to the existing list of weapons + return Object.assign(weapons, responseData); + }, + }); + + // Get weapon images + for (let weapon of Object.values(weapons)) + await maybeDownloadImage(weapon.image); +} + function updateSalmonRunCalendar() { return handleRequest({ title: 'Salmon Run calendar', @@ -113,6 +148,7 @@ async function updateAll() { await updateTimeline(); await updateFestivals(); await updateMerchandises(); + await updateWeapons(); await updateSalmonRunCalendar(); return 'Done.'; @@ -150,6 +186,7 @@ async function handleRequest(options) { } catch (e) { raven.captureException(e); console.error(`Couldn\'t update ${options.title}.`); + console.error(e); } } @@ -175,6 +212,7 @@ module.exports = { updateTimeline, updateFestivals, updateMerchandises, + updateWeapons, updateSalmonRunCalendar, updateAll, } diff --git a/yarn.lock b/yarn.lock index 00776cf..e51be9b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1550,6 +1550,12 @@ del@^3.0.0: pify "^3.0.0" rimraf "^2.2.8" +delay@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/delay/-/delay-2.0.0.tgz#9112eadc03e4ec7e00297337896f273bbd91fae5" + dependencies: + p-defer "^1.0.0" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -3348,6 +3354,10 @@ osenv@0, osenv@^0.1.4: os-homedir "^1.0.0" os-tmpdir "^1.0.0" +p-defer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" + p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"