mai2: add tachi to tachi session bookmarklet

This commit is contained in:
Pinapelz
2025-06-04 16:42:47 -07:00
parent f9f03b2c20
commit 80ccca7e64
2 changed files with 122 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
# maimai DX Tachi to Tachi
This is a bookmarklet that will allow you to download data shown in the Web UI of Tachi into a `Batch-Manual` compatible form again.
This can be handy if you want to download/save data from the MYT network if you are already in a Tachi instance that has integrations with it.
# Scripts
## `tachi_to_tachi_session.js`
For a singular session. Choose the session to export and go to the `All Scores` tab
Page: `https://my-tachi-instance.com/u/USERNAME/games/chunithm/Single/sessions/SESSION_ID`
# Usage
1. Copy the contents of the script you want to use
2. Create a new bookmark and paste the full contents of the script into the URL section
3. Navigate to the relevant score page as shown in the previous section
4. Show as many scores as possible on a singular view, if you exceed the largest limit then repeat the steps above for the next page(s)
+104
View File
@@ -0,0 +1,104 @@
javascript:void(async function () {
function waitForRows() {
return new Promise((resolve) => {
const check = () => {
const rows = document.querySelectorAll("table tbody tr");
if (rows.length > 0) resolve(rows);
else setTimeout(check, 500);
};
check();
});
}
const difficultyMap = {
"DX Basic": "DX Basic",
"DX Advanced": "DX Advanced",
"DX Expert": "DX Expert",
"DX Master": "DX Master",
"DX Re:Master": "DX Re:Master"
};
const lampMap = {
"FAILED": "FAILED",
"CLEAR": "CLEAR",
"FULL COMBO": "FULL COMBO",
"FULL COMBO+": "FULL COMBO+",
"ALL PERFECT": "ALL PERFECT",
"ALL PERFECT+": "ALL PERFECT+"
};
const gradeList = ["D", "C", "B", "BB", "BBB", "A", "AA", "AAA", "S", "S+", "SS", "SS+", "SSS", "SSS+"];
const rows = await waitForRows();
const results = {
meta: {
game: "maimaidx",
playtype: "Single",
service: "Tachi to Tachi PB Export"
},
scores: []
};
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
if (row.classList.contains("expandable-pseudo-row") || row.classList.contains("fake-row")) continue;
const cells = row.querySelectorAll("td");
if (cells.length < 9) continue;
const difficultyText = cells[0].innerText.split("\n")[0].trim();
const difficulty = Object.keys(difficultyMap).find(d => difficultyText.includes(d)) || difficultyText;
const titleAnchor = cells[2].querySelector("a");
const title = titleAnchor?.childNodes[0]?.textContent.trim() || "";
const artist = titleAnchor?.querySelector("small")?.textContent.trim() || "";
const percentText = cells[3].innerText.trim();
const percent = parseFloat(percentText.match(/([\d.]+)%/)?.[1]) || 0;
const grade = gradeList.find(g => percentText.includes(g)) || "D";
const judgmentSpans = cells[4].querySelectorAll("span");
const [pcrit, perfect, great, good, miss] = Array.from(judgmentSpans).map(span => parseInt(span.textContent.trim()));
const lampText = cells[5].innerText.trim();
const lamp = lampMap[lampText] || "FAILED";
const timeText = cells[7].querySelector("small")?.textContent?.trim();
let timeAchieved = null;
if (timeText) {
const parsed = new Date(timeText);
if (!isNaN(parsed)) {
timeAchieved = parsed.getTime();
}
}
const score = {
identifier: title,
artist,
difficulty,
percent,
lamp,
judgements: {
pcrit,
perfect,
great,
good,
miss
},
matchType: "songTitle",
timeAchieved: timeAchieved
};
results.scores.push(score);
}
const blob = new Blob([JSON.stringify(results, null, 2)], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "maimaidx_session_scores.json";
document.body.appendChild(a);
a.click();
URL.revokeObjectURL(url);
})();