diff options
| author | Pinapelz <yukais@pinapelz.com> | 2025-06-04 16:41:57 -0700 |
|---|---|---|
| committer | Pinapelz <yukais@pinapelz.com> | 2025-06-04 16:42:47 -0700 |
| commit | 80ccca7e64a2185b0e6133afb8bf4ae61d5f3946 (patch) | |
| tree | 06a9c62c0d098c586ee07ef4149d90ee5b4cdfc7 | |
| parent | f9f03b2c203860fa452aa981c745580062b7d84f (diff) | |
mai2: add tachi to tachi session bookmarklet
| -rw-r--r-- | mai2/tachi/README.md | 18 | ||||
| -rw-r--r-- | mai2/tachi/tachi_to_tachi_session.js | 104 |
2 files changed, 122 insertions, 0 deletions
diff --git a/mai2/tachi/README.md b/mai2/tachi/README.md new file mode 100644 index 0000000..2a206db --- /dev/null +++ b/mai2/tachi/README.md @@ -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) diff --git a/mai2/tachi/tachi_to_tachi_session.js b/mai2/tachi/tachi_to_tachi_session.js new file mode 100644 index 0000000..f9befa5 --- /dev/null +++ b/mai2/tachi/tachi_to_tachi_session.js @@ -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); +})(); |
