aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPinapelz <yukais@pinapelz.com>2025-06-04 16:41:57 -0700
committerPinapelz <yukais@pinapelz.com>2025-06-04 16:42:47 -0700
commit80ccca7e64a2185b0e6133afb8bf4ae61d5f3946 (patch)
tree06a9c62c0d098c586ee07ef4149d90ee5b4cdfc7
parentf9f03b2c203860fa452aa981c745580062b7d84f (diff)
mai2: add tachi to tachi session bookmarklet
-rw-r--r--mai2/tachi/README.md18
-rw-r--r--mai2/tachi/tachi_to_tachi_session.js104
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);
+})();
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage