Last active
December 10, 2023 13:01
-
-
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
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
// ==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