Skip to content

Instantly share code, notes, and snippets.

@Kattoor
Created May 22, 2021 16:32
Show Gist options
  • Save Kattoor/7f47e9c78482c1477dcd16d6cee7705a to your computer and use it in GitHub Desktop.
Save Kattoor/7f47e9c78482c1477dcd16d6cee7705a to your computer and use it in GitHub Desktop.
rs-gains-chart
{
"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"
}
}
/* 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?&timestamp=${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