Created
May 22, 2021 16:32
-
-
Save Kattoor/7f47e9c78482c1477dcd16d6cee7705a to your computer and use it in GitHub Desktop.
rs-gains-chart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "rs-gains-chart", | |
"version": "1.0.0", | |
"description": "", | |
"author": "Jasper Catthoor", | |
"dependencies": { | |
"axios": "^0.21.1", | |
"chart.js": "^3.2.0", | |
"chartjs-adapter-moment": "^1.0.0", | |
"chartjs-node-canvas": "^3.2.0", | |
"moment": "^2.29.1" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* CONFIGURABLE SECTION START */ | |
const cookie = 'JSESSIONID=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'; | |
const amountOfDaysToQuery = 5; | |
const chartWidth = 2000; | |
const chartHeight = 800; | |
const saveIntermediateAsJson = true; | |
const Skills = { | |
Attack: {id: 0, color: 'rgb(152, 20, 20)'}, | |
Defence: {id: 1, color: 'rgb(20, 126, 152)'}, | |
Strength: {id: 2, color: 'rgb(19, 183, 135)'}, | |
Constitution: {id: 3, color: 'rgb(170, 206, 218)'}, | |
Ranged: {id: 4, color: 'rgb(19, 183, 81)'}, | |
Prayer: {id: 5, color: 'rgb(109, 191, 242)'}, | |
Magic: {id: 6, color: 'rgb(195, 227, 220)'}, | |
/*Cooking: {id: 7, color: 'rgb(85, 50, 133)'}, | |
WoodCutting: {id: 8, color: 'rgb(126, 79, 53)'}, | |
Fletching: {id: 9, color: 'rgb(20, 152, 147)'}, | |
Fishing: {id: 10, color: 'rgb(62, 112, 185)'}, | |
Firemaking: {id: 11, color: 'rgb(247, 95, 40)'}, | |
Crafting: {id: 12, color: 'rgb(182, 149, 44)'}, | |
Smithing: {id: 13, color: 'rgb(101, 136, 126)'}, | |
Mining: {id: 14, color: 'rgb(86, 73, 94)'}, | |
Herblore: {id: 15, color: 'rgb(18, 69, 58)'}, | |
Agility: {id: 16, color: 'rgb(40, 74, 149)'}, | |
Thieving: {id: 17, color: 'rgb(54, 23, 94)'}, | |
Slayer: {id: 18, color: 'rgb(72, 65, 47)'}, | |
Farming: {id: 19, color: 'rgb(31, 125, 84)'}, | |
RuneCrafting: {id: 20, color: 'rgb(215, 235, 163)'}, | |
Hunter: {id: 21, color: 'rgb(195, 139, 78)'}, | |
Construction: {id: 22, color: 'rgb(168, 186, 188)'}, | |
Summoning: {id: 23, color: 'rgb(222, 161, 176)'}, | |
Dungeoneering: {id: 24, color: 'rgb(114, 57, 32)'}, | |
Divination: {id: 25, color: 'rgb(148, 63, 186)'}, | |
Invention: {id: 26, color: 'rgb(247, 181, 40)'}, | |
Archaeology: {id: 27, color: 'rgb(185, 87, 30)'},*/ | |
}; | |
/* CONFIGURABLE SECTION END */ | |
require('chartjs-adapter-moment'); | |
const fs = require('fs'); | |
const axios = require('axios'); | |
const {ChartJSNodeCanvas} = require('chartjs-node-canvas'); | |
const oneMinuteInMs = 1000 * 60; | |
const oneHourInMs = oneMinuteInMs * 60; | |
const oneDayInMs = oneHourInMs * 24; | |
const headers = {headers: {"cookie": cookie}}; | |
(async () => { | |
const epochsToQuery = getTimestamps(); | |
const data = await querySkillData(epochsToQuery); | |
if (saveIntermediateAsJson) | |
fs.writeFileSync('data.json', JSON.stringify(data)); | |
const dataAggregatedByDate = aggregateByDay(data); | |
if (saveIntermediateAsJson) | |
fs.writeFileSync('data-aggregated-by-day.json', JSON.stringify(dataAggregatedByDate)); | |
await createChart(dataAggregatedByDate); | |
})(); | |
/* | |
* Returns | |
* [ | |
* epoch_at_start_of_day(today - amountOfDaysToQuery), | |
* ..., | |
* epoch_at_start_of_day(today) | |
* ] | |
* */ | |
function getTimestamps() { | |
const time = Math.round(new Date().getTime() / 1000) * 1000; | |
const days = []; | |
for (let i = 0; i < amountOfDaysToQuery; i++) { | |
const xDaysAgoTimestamp = time - ((amountOfDaysToQuery - 1 - i) * oneDayInMs); | |
days[i] = xDaysAgoTimestamp - (xDaysAgoTimestamp % oneDayInMs); | |
} | |
return days; | |
} | |
async function querySkillData(epochsToQuery) { | |
const data = {}; | |
for ([skillName, {id, color}] of Object.entries(Skills)) { | |
console.log(`Fetching data for skill [${skillName}]`); | |
for (const epochToQuery of epochsToQuery) { | |
const url = `https://apps.runescape.com/runemetrics/aggregations/skill/gain/day?×tamp=${epochToQuery}&skill=${id}`; | |
const response = await axios.get(url, headers); | |
handleResponse(response, skillName, epochToQuery, data); | |
} | |
} | |
return data; | |
} | |
function handleResponse(response, skillName, epochToQuery, data) { | |
if (response.data[0].gain) { | |
if (!data[skillName]) { | |
data[skillName] = {skillRecords: [], color}; | |
} | |
const skillRecords = data[skillName].skillRecords; | |
const records = response.data[0].gain.requested; | |
Object.entries(records) | |
.filter(([key]) => !isNaN(key)) | |
.map(([key, value]) => | |
skillRecords.push({timestamp: new Date(epochToQuery + key * oneMinuteInMs), value: +value.xp})); | |
data[skillName].skillRecords = skillRecords; | |
} | |
} | |
function aggregateByDay(data) { | |
return Object.entries(data).map(([skillName, {skillRecords, color}]) => { | |
const skillRecordsByDayAccumulated = skillRecords.reduce((acc, skillRecord) => { | |
const date = skillRecord.timestamp.toISOString().split('T')[0]; | |
let skillRecordForThisDay = acc.find(recordGroupedByDay => recordGroupedByDay.timestamp === date); | |
if (skillRecordForThisDay === undefined) { | |
skillRecordForThisDay = {timestamp: date, value: 0}; | |
acc.push(skillRecordForThisDay); | |
} | |
skillRecordForThisDay.value += skillRecord.value; | |
return acc; | |
}, []); | |
return {skillName, skillRecords: skillRecordsByDayAccumulated, color}; | |
}); | |
} | |
async function createChart(dataAggregatedByDate) { | |
const chartJSNodeCanvas = new ChartJSNodeCanvas({width: chartWidth, height: chartHeight}); | |
const chartConfiguration = { | |
data: { | |
labels: [], | |
datasets: dataAggregatedByDate.map(({skillName, skillRecords, color}) => ({ | |
label: skillName, | |
type: 'bar', | |
data: skillRecords.map(record => ({x: record.timestamp, y: record.value / 10})), | |
unite: null, | |
backgroundColor: color | |
})), | |
}, | |
options: { | |
elements: {bar: {borderWidth: 0}}, | |
scales: { | |
x: { | |
type: 'time', | |
stacked: true, | |
time: { | |
unit: 'day' | |
}, | |
ticks: { | |
color: 'white' | |
}, | |
grid: { | |
borderColor: 'white' | |
}, | |
color: 'white', | |
}, | |
y: { | |
stacked: true, | |
ticks: { | |
color: 'white' | |
}, | |
color: 'white', | |
gridLines: { | |
borderColor: 'white', | |
display: true, | |
} | |
} | |
}, | |
plugins: { | |
legend: { | |
labels: { | |
color: 'white' | |
}, | |
position: 'left', | |
} | |
} | |
} | |
}; | |
const image = await chartJSNodeCanvas.renderToBuffer(chartConfiguration); | |
fs.writeFileSync('chart.png', image); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment