Skip to content

Instantly share code, notes, and snippets.

@RedRoserade
Last active December 10, 2023 13:01
Show Gist options
  • Save RedRoserade/5df1b82d0030b91195737ca8fae86a55 to your computer and use it in GitHub Desktop.
Save RedRoserade/5df1b82d0030b91195737ca8fae86a55 to your computer and use it in GitHub Desktop.
User Script for downloading results from Dirt Rally 2.0 events as CSV
// ==UserScript==
// @name Get Results - dirtgame.com
// @namespace https://github.com/redroserade
// @match https://dirtrally2.dirtgame.com/clubs/club/*
// @grant none
// @version 1.0
// @author https://github.com/redroserade
// @downloadURL https://gist.githubusercontent.com/RedRoserade/5df1b82d0030b91195737ca8fae86a55/raw/37f43f77d25d5b375483edc4cd2a00290d14ba8c/download-results.js
// @description Download results from Dirt Rally 2.0 events as a CSV file
// ==/UserScript==
async function downloadChampionships(clubId) {
const response = await fetch(`/api/Club/${clubId}/recentResults`);
const {championships} = await response.json();
return championships;
}
async function downloadStageLeaderboard(challenge, event, s, headers) {
// console.log("downloading", {challenge, event, s});
const response = await fetch("/api/Leaderboard", {
credentials: "include",
headers: {
...headers,
"Content-Type": "application/json"
},
body: JSON.stringify({
"challengeId": event.challengeId,
"selectedEventId":0,
"stageId":s.id,
"page":1,
"pageSize":100,
"orderByTotalTime":true,
"platformFilter":"None",
"playerFilter":"Everyone",
"filterByAssists":"Unspecified",
"filterByWheel":"Unspecified",
"nationalityFilter":"None",
"eventId":event.id
}),
method: "POST"
});
const leaderboard = await response.json();
return leaderboard;
}
async function downloadLeaderboard(challenge, event, headers) {
const results = await Promise.all(
event.stages.map(s => downloadStageLeaderboard(challenge, event, s, headers))
);
return results;
}
function timeToNumber(t) {
const [minutes, seconds, millis] = t.split(/:|\./);
const result = parseInt(`${minutes}${seconds}${millis}`);
console.log({t, result, minutes, seconds, millis});
return result;
}
function formatOutput(challenge, event, results) {
const resultsByDriver = new Map();
for (const [i, stageLeaderboard] of Object.entries(results)) {
const stage = event.stages[i];
console.log({ stage })
if (!stageLeaderboard.entries.length) {
console.log("No results", { stage });
continue;
}
console.log(stage, stageLeaderboard.entries);
for (const e of stageLeaderboard.entries) {
if (!resultsByDriver.has(e.name)) {
resultsByDriver.set(e.name, {
driverName: e.name,
vehicleName: e.vehicleName,
stages: Array(event.stages.length).fill({ stageName: stage.name, stageTime: null }),
totalTime: null,
});
}
const driverResults = resultsByDriver.get(e.name);
driverResults.stages[i] = { stageName: stage.name, stageTime: e.stageTime };
driverResults.totalTime = e.totalTime;
}
}
const sortedEntries = Array.from(resultsByDriver.values());
sortedEntries.sort((a, b) => timeToNumber(a.totalTime ?? Infinity) - timeToNumber(b.totalTime ?? Infinity));
console.log(sortedEntries);
const lines = [];
const firstLine = [
"Driver",
"Car",
...sortedEntries[0].stages.flatMap((s, i) => `Stage ${i + 1}: ${s.stageName}`),
"Total"
];
lines.push(firstLine.join("\t"));
for (const entry of sortedEntries) {
const lineParts = [
entry.driverName,
entry.vehicleName,
...entry.stages.map(s => s.stageTime ?? ""),
entry.totalTime,
];
lines.push(lineParts.join("\t"));
}
return lines.join("\n");
}
async function run(challenge, event, headers) {
console.log("running for", {challenge, event});
const results = await downloadLeaderboard(challenge, event, headers);
const output = formatOutput(challenge, event, results);
const contents = new Blob([output], { type: "text/csv;charset=utf-8" });
const url = URL.createObjectURL(contents);
const downloadLink = document.createElement("a");
try {
downloadLink.href = url;
downloadLink.download = `${event.challengeId}_${challenge.name}_${event.name}_results.csv`;
document.body.append(downloadLink);
downloadLink.click();
} finally {
downloadLink.remove();
URL.revokeObjectURL(url);
}
}
async function getHeaders() {
const response = await fetch("/api/ClientStore/GetInitialState", {
"credentials": "include",
"method": "GET",
"mode": "cors"
});
const { identity } = await response.json();
return {
"RaceNet.XSRFH": identity.token
};
}
async function setup() {
const headers = await getHeaders();
const [_, clubId] = (/\/clubs\/club\/(\d+).*/).exec(window.location.pathname);
const championships = await downloadChampionships(clubId);
console.log(championships);
const sel = document.createElement("select");
const noOpt = document.createElement("option");
noOpt.textContent = "Choose event from list...";
noOpt.value = "";
sel.appendChild(noOpt);
for (const c of championships) {
const g = document.createElement("optgroup");
g.label = `Championship: ${c.name}`;
sel.appendChild(g);
for (const e of c.events) {
const o = document.createElement("option");
o.textContent = `Event: ${e.name}`;
o.onclick = async () => {
try {
sel.disabled = true;
await run(c, e, headers);
} finally {
sel.disabled = false;
}
};
g.appendChild(o);
}
}
sel.style.position = "fixed";
sel.style.left = "20px";
sel.style.bottom = "20px";
document.body.appendChild(sel);
}
setup();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment