From fc206b52456d3ce3a05f3b34609449e815fdceea Mon Sep 17 00:00:00 2001 From: Pinapelz Date: Sun, 12 Oct 2025 17:08:53 -0700 Subject: append .user.js to userscripts --- scripts/dancearound/dancearound_play_history.js | 164 ------------- .../dancearound/dancearound_play_history.user.js | 164 +++++++++++++ scripts/dancerush/dancerush_play_history.js | 153 ------------ scripts/dancerush/dancerush_play_history.user.js | 153 ++++++++++++ scripts/musicdiver/musicdiver_recent_history.js | 135 ----------- .../musicdiver/musicdiver_recent_history.user.js | 135 +++++++++++ scripts/nostalgia/nostalgia_flower_scraper.js | 188 --------------- scripts/nostalgia/nostalgia_flower_scraper.user.js | 188 +++++++++++++++ scripts/projectdiva-arcade/diva_net_history.js | 262 --------------------- .../projectdiva-arcade/diva_net_history.user..js | 262 +++++++++++++++++++++ scripts/reflecbeat/reflecbeat_flower_scraper.js | 111 --------- .../reflecbeat/reflecbeat_flower_scraper.user.js | 111 +++++++++ 12 files changed, 1013 insertions(+), 1013 deletions(-) delete mode 100644 scripts/dancearound/dancearound_play_history.js create mode 100644 scripts/dancearound/dancearound_play_history.user.js delete mode 100644 scripts/dancerush/dancerush_play_history.js create mode 100644 scripts/dancerush/dancerush_play_history.user.js delete mode 100644 scripts/musicdiver/musicdiver_recent_history.js create mode 100644 scripts/musicdiver/musicdiver_recent_history.user.js delete mode 100644 scripts/nostalgia/nostalgia_flower_scraper.js create mode 100644 scripts/nostalgia/nostalgia_flower_scraper.user.js delete mode 100644 scripts/projectdiva-arcade/diva_net_history.js create mode 100644 scripts/projectdiva-arcade/diva_net_history.user..js delete mode 100644 scripts/reflecbeat/reflecbeat_flower_scraper.js create mode 100644 scripts/reflecbeat/reflecbeat_flower_scraper.user.js diff --git a/scripts/dancearound/dancearound_play_history.js b/scripts/dancearound/dancearound_play_history.js deleted file mode 100644 index 2a98994..0000000 --- a/scripts/dancearound/dancearound_play_history.js +++ /dev/null @@ -1,164 +0,0 @@ -// ==UserScript== -// @name DANCEAROUND Mirage Scraper -// @namespace http://tampermonkey.net/ -// @version 1.0 -// @description DANCEAROUND e-amusement site to Mirage import JSON -// @match https://p.eagate.573.jp/game/around/1st/playdata/index.html* -// @grant none -// @run-at document-idle -// ==/UserScript== - -(function () { - function waitFor(selector, timeout = 10000) { - return new Promise((resolve, reject) => { - const interval = 300; - let waited = 0; - const check = () => { - const el = document.querySelector(selector); - if (el) return resolve(el); - waited += interval; - if (waited >= timeout) return reject(`Timeout: ${selector}`); - setTimeout(check, interval); - }; - check(); - }); - } - - function getDifficulty(fumen, mdb) { - let difficulty, lamp; - - switch (fumen) { - case "ADVANCED": - difficulty = mdb.fumens.ADVANCED.level; - lamp = "ADVANCED"; - break; - case "BASIC": - difficulty = mdb.fumens.BASIC.level; - lamp = "BASIC"; - break; - case "MASTER": - difficulty = mdb.fumens.MASTER.level; - lamp = "MASTER"; - break; - } - - return { difficulty, lamp }; - } - function getLampText(status) { - switch (status) { - case 0: - return "C"; - case 1: - return "B"; - case 2: - return "A"; - case 3: - return "AA"; - case 4: - return "AAA"; - case 5: - return "AAA+"; - } - } - - function getClearStatusText(status){ - switch(status){ - case 1: - return "FAILURE"; - case 2: - return "PASSED"; - case 3: - return "FULL COMBO"; - case 4: - return "EXC"; - } - } - - async function fetchAndDownload() { - const url = "https://p.eagate.573.jp/game/around/1st/json/pdata_getdata.html"; - const payload = new URLSearchParams({ - service_kind: "play_hist", - pdata_kind: "play_hist", - }); - - try { - const response = await fetch(url, { - method: "POST", - credentials: "include", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - "X-Requested-With": "XMLHttpRequest", - }, - body: payload.toString(), - }); - - if (!response.ok) throw new Error(`HTTP ${response.status}`); - - const data = await response.json(); - const play_hist = data.data.easite_get_playerdata.music_hist.music; - const song_db = data.data.easite_get_playerdata.mdb; - let mirage = { - meta: { - game: "dancearound", - playtype: "Single", - service: "e-amusement PLAY HISTORY", - }, - }; - const remappedList = play_hist.map((entry) => { - const diff = getDifficulty(entry.music_type, song_db[entry.music_id].difficulty) - const numPlayers = (entry.p1 && entry.p2) ? 2 : 1; - return { - title: song_db[entry.music_id].title_name, - artist: song_db[entry.music_id].artist_name, - diff_lamp: diff.lamp, - num_players: numPlayers, - score: entry.score, - lamp: getLampText(entry.rank), - clear_status: getClearStatusText(entry.clear_status), - difficulty: diff.difficulty, - timestamp: entry.play_date, - judgements: { - "perfect": entry.perfect, - "great": entry.great, - "good": entry.good, - "bad": entry.bad - }, - optional: { - maxCombo: entry.combo, - } - - }; - }); - mirage.scores = remappedList; - - const blob = new Blob([JSON.stringify(mirage, null, 2)], { - type: "application/json", - }); - - const a = document.createElement("a"); - a.href = URL.createObjectURL(blob); - a.download = "dancearound_scores_mirage_import.json"; - a.click(); - } catch (err) { - console.error("Fetch/download error:", err); - alert("Failed to fetch or process JSON. See console for details."); - } - } - - waitFor("#id_ctpl_body") - .then((container) => { - const btn = document.createElement("button"); - btn.textContent = "πŸ“₯ DOWNLOAD PLAY HISTORY SCORE JSON"; - btn.style.cssText = ` - margin: 10px; padding: 8px 12px; - font-size: 14px; cursor: pointer; - background: #2563eb; color: white; - border: none; border-radius: 6px; - z-index: 9999; - `; - btn.onclick = fetchAndDownload; - - container.prepend(btn); - }) - .catch((err) => console.warn("Could not inject button:", err)); -})(); diff --git a/scripts/dancearound/dancearound_play_history.user.js b/scripts/dancearound/dancearound_play_history.user.js new file mode 100644 index 0000000..2a98994 --- /dev/null +++ b/scripts/dancearound/dancearound_play_history.user.js @@ -0,0 +1,164 @@ +// ==UserScript== +// @name DANCEAROUND Mirage Scraper +// @namespace http://tampermonkey.net/ +// @version 1.0 +// @description DANCEAROUND e-amusement site to Mirage import JSON +// @match https://p.eagate.573.jp/game/around/1st/playdata/index.html* +// @grant none +// @run-at document-idle +// ==/UserScript== + +(function () { + function waitFor(selector, timeout = 10000) { + return new Promise((resolve, reject) => { + const interval = 300; + let waited = 0; + const check = () => { + const el = document.querySelector(selector); + if (el) return resolve(el); + waited += interval; + if (waited >= timeout) return reject(`Timeout: ${selector}`); + setTimeout(check, interval); + }; + check(); + }); + } + + function getDifficulty(fumen, mdb) { + let difficulty, lamp; + + switch (fumen) { + case "ADVANCED": + difficulty = mdb.fumens.ADVANCED.level; + lamp = "ADVANCED"; + break; + case "BASIC": + difficulty = mdb.fumens.BASIC.level; + lamp = "BASIC"; + break; + case "MASTER": + difficulty = mdb.fumens.MASTER.level; + lamp = "MASTER"; + break; + } + + return { difficulty, lamp }; + } + function getLampText(status) { + switch (status) { + case 0: + return "C"; + case 1: + return "B"; + case 2: + return "A"; + case 3: + return "AA"; + case 4: + return "AAA"; + case 5: + return "AAA+"; + } + } + + function getClearStatusText(status){ + switch(status){ + case 1: + return "FAILURE"; + case 2: + return "PASSED"; + case 3: + return "FULL COMBO"; + case 4: + return "EXC"; + } + } + + async function fetchAndDownload() { + const url = "https://p.eagate.573.jp/game/around/1st/json/pdata_getdata.html"; + const payload = new URLSearchParams({ + service_kind: "play_hist", + pdata_kind: "play_hist", + }); + + try { + const response = await fetch(url, { + method: "POST", + credentials: "include", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + "X-Requested-With": "XMLHttpRequest", + }, + body: payload.toString(), + }); + + if (!response.ok) throw new Error(`HTTP ${response.status}`); + + const data = await response.json(); + const play_hist = data.data.easite_get_playerdata.music_hist.music; + const song_db = data.data.easite_get_playerdata.mdb; + let mirage = { + meta: { + game: "dancearound", + playtype: "Single", + service: "e-amusement PLAY HISTORY", + }, + }; + const remappedList = play_hist.map((entry) => { + const diff = getDifficulty(entry.music_type, song_db[entry.music_id].difficulty) + const numPlayers = (entry.p1 && entry.p2) ? 2 : 1; + return { + title: song_db[entry.music_id].title_name, + artist: song_db[entry.music_id].artist_name, + diff_lamp: diff.lamp, + num_players: numPlayers, + score: entry.score, + lamp: getLampText(entry.rank), + clear_status: getClearStatusText(entry.clear_status), + difficulty: diff.difficulty, + timestamp: entry.play_date, + judgements: { + "perfect": entry.perfect, + "great": entry.great, + "good": entry.good, + "bad": entry.bad + }, + optional: { + maxCombo: entry.combo, + } + + }; + }); + mirage.scores = remappedList; + + const blob = new Blob([JSON.stringify(mirage, null, 2)], { + type: "application/json", + }); + + const a = document.createElement("a"); + a.href = URL.createObjectURL(blob); + a.download = "dancearound_scores_mirage_import.json"; + a.click(); + } catch (err) { + console.error("Fetch/download error:", err); + alert("Failed to fetch or process JSON. See console for details."); + } + } + + waitFor("#id_ctpl_body") + .then((container) => { + const btn = document.createElement("button"); + btn.textContent = "πŸ“₯ DOWNLOAD PLAY HISTORY SCORE JSON"; + btn.style.cssText = ` + margin: 10px; padding: 8px 12px; + font-size: 14px; cursor: pointer; + background: #2563eb; color: white; + border: none; border-radius: 6px; + z-index: 9999; + `; + btn.onclick = fetchAndDownload; + + container.prepend(btn); + }) + .catch((err) => console.warn("Could not inject button:", err)); +})(); diff --git a/scripts/dancerush/dancerush_play_history.js b/scripts/dancerush/dancerush_play_history.js deleted file mode 100644 index 098f038..0000000 --- a/scripts/dancerush/dancerush_play_history.js +++ /dev/null @@ -1,153 +0,0 @@ -// ==UserScript== -// @name DANCERUSH Mirage Scraper -// @namespace http://tampermonkey.net/ -// @version 1.0 -// @description DANCERUSH e-amusement site to Mirage import JSON -// @match https://p.eagate.573.jp/game/dan/1st/playdata/entrance.html* -// @grant none -// @run-at document-idle -// ==/UserScript== - -(function () { - function waitFor(selector, timeout = 10000) { - return new Promise((resolve, reject) => { - const interval = 300; - let waited = 0; - const check = () => { - const el = document.querySelector(selector); - if (el) return resolve(el); - waited += interval; - if (waited >= timeout) return reject(`Timeout: ${selector}`); - setTimeout(check, interval); - }; - check(); - }); - } - - function getDifficulty(fumen, mdb) { - let difficulty, lamp; - - switch (fumen) { - case "1a": - difficulty = mdb.fumen_1a.difnum; - lamp = "NORMAL"; - break; - case "1b": - difficulty = mdb.fumen_1b.difnum; - lamp = "EASY"; - break; - case "2a": - difficulty = mdb.fumen_2a.difnum; - lamp = "NORMAL"; - break; - case "2b": - default: - difficulty = mdb.fumen_2b.difnum; - lamp = "EASY"; - break; - } - - return { difficulty, lamp }; - } - - function getCorrectPlayerJudgements(player_code, score_data){ - if(!score_data.p2){ - return score_data.p1; - } - if(player_code === score_data.p1.member_code){ - return score_data.p1; - } - else{ - return score_data.p2; - } - } - - - async function fetchAndDownload() { - const url = "https://p.eagate.573.jp/game/dan/1st/json/pdata_getdata.html"; - const payload = new URLSearchParams({ - service_kind: "play_hist", - pdata_kind: "play_hist", - }); - - try { - const response = await fetch(url, { - method: "POST", - credentials: "include", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - "X-Requested-With": "XMLHttpRequest", - }, - body: payload.toString(), - }); - - if (!response.ok) throw new Error(`HTTP ${response.status}`); - - const data = await response.json(); - const play_hist = data.data.easite_get_playerdata.music_hist.music; - const song_db = data.data.easite_get_playerdata.mdb; - let mirage = { - meta: { - game: "DANCERUSH STARDOM", - playtype: "Single", - service: "e-amusement PLAY HISTORY", - }, - }; - const remappedList = play_hist.map((entry) => { - const p_judgements = getCorrectPlayerJudgements(data.data.easite_get_playerdata.userid.code, entry) - const diff = getDifficulty(entry.music_type, song_db[entry.music_id].difficulty) - const numPlayers = (entry.p1 && entry.p2) ? 2 : 1; - return { - title: song_db[entry.music_id].info.title_name, - artist: song_db[entry.music_id].info.artist_name, - diff_lamp: diff.lamp, - num_players: numPlayers, - score: entry.score, - lamp: entry.rank, - difficulty: diff.difficulty, - timestamp: entry.lastplay_date, - judgements: { - "perfect": p_judgements.perfect, - "great": p_judgements.great, - "good": p_judgements.good, - "bad": p_judgements.bad - }, - optional: { - maxCombo: entry.combo, - } - - }; - }); - mirage.scores = remappedList; - - const blob = new Blob([JSON.stringify(mirage, null, 2)], { - type: "application/json", - }); - - const a = document.createElement("a"); - a.href = URL.createObjectURL(blob); - a.download = "dancerush_scores_mirage_import.json"; - a.click(); - } catch (err) { - console.error("Fetch/download error:", err); - alert("Failed to fetch or process JSON. See console for details."); - } - } - - waitFor("#id_ctpl_body") - .then((container) => { - const btn = document.createElement("button"); - btn.textContent = "πŸ“₯ DOWNLOAD PLAY HISTORY SCORE JSON"; - btn.style.cssText = ` - margin: 10px; padding: 8px 12px; - font-size: 14px; cursor: pointer; - background: #2563eb; color: white; - border: none; border-radius: 6px; - z-index: 9999; - `; - btn.onclick = fetchAndDownload; - - container.prepend(btn); - }) - .catch((err) => console.warn("Could not inject button:", err)); -})(); diff --git a/scripts/dancerush/dancerush_play_history.user.js b/scripts/dancerush/dancerush_play_history.user.js new file mode 100644 index 0000000..098f038 --- /dev/null +++ b/scripts/dancerush/dancerush_play_history.user.js @@ -0,0 +1,153 @@ +// ==UserScript== +// @name DANCERUSH Mirage Scraper +// @namespace http://tampermonkey.net/ +// @version 1.0 +// @description DANCERUSH e-amusement site to Mirage import JSON +// @match https://p.eagate.573.jp/game/dan/1st/playdata/entrance.html* +// @grant none +// @run-at document-idle +// ==/UserScript== + +(function () { + function waitFor(selector, timeout = 10000) { + return new Promise((resolve, reject) => { + const interval = 300; + let waited = 0; + const check = () => { + const el = document.querySelector(selector); + if (el) return resolve(el); + waited += interval; + if (waited >= timeout) return reject(`Timeout: ${selector}`); + setTimeout(check, interval); + }; + check(); + }); + } + + function getDifficulty(fumen, mdb) { + let difficulty, lamp; + + switch (fumen) { + case "1a": + difficulty = mdb.fumen_1a.difnum; + lamp = "NORMAL"; + break; + case "1b": + difficulty = mdb.fumen_1b.difnum; + lamp = "EASY"; + break; + case "2a": + difficulty = mdb.fumen_2a.difnum; + lamp = "NORMAL"; + break; + case "2b": + default: + difficulty = mdb.fumen_2b.difnum; + lamp = "EASY"; + break; + } + + return { difficulty, lamp }; + } + + function getCorrectPlayerJudgements(player_code, score_data){ + if(!score_data.p2){ + return score_data.p1; + } + if(player_code === score_data.p1.member_code){ + return score_data.p1; + } + else{ + return score_data.p2; + } + } + + + async function fetchAndDownload() { + const url = "https://p.eagate.573.jp/game/dan/1st/json/pdata_getdata.html"; + const payload = new URLSearchParams({ + service_kind: "play_hist", + pdata_kind: "play_hist", + }); + + try { + const response = await fetch(url, { + method: "POST", + credentials: "include", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + "X-Requested-With": "XMLHttpRequest", + }, + body: payload.toString(), + }); + + if (!response.ok) throw new Error(`HTTP ${response.status}`); + + const data = await response.json(); + const play_hist = data.data.easite_get_playerdata.music_hist.music; + const song_db = data.data.easite_get_playerdata.mdb; + let mirage = { + meta: { + game: "DANCERUSH STARDOM", + playtype: "Single", + service: "e-amusement PLAY HISTORY", + }, + }; + const remappedList = play_hist.map((entry) => { + const p_judgements = getCorrectPlayerJudgements(data.data.easite_get_playerdata.userid.code, entry) + const diff = getDifficulty(entry.music_type, song_db[entry.music_id].difficulty) + const numPlayers = (entry.p1 && entry.p2) ? 2 : 1; + return { + title: song_db[entry.music_id].info.title_name, + artist: song_db[entry.music_id].info.artist_name, + diff_lamp: diff.lamp, + num_players: numPlayers, + score: entry.score, + lamp: entry.rank, + difficulty: diff.difficulty, + timestamp: entry.lastplay_date, + judgements: { + "perfect": p_judgements.perfect, + "great": p_judgements.great, + "good": p_judgements.good, + "bad": p_judgements.bad + }, + optional: { + maxCombo: entry.combo, + } + + }; + }); + mirage.scores = remappedList; + + const blob = new Blob([JSON.stringify(mirage, null, 2)], { + type: "application/json", + }); + + const a = document.createElement("a"); + a.href = URL.createObjectURL(blob); + a.download = "dancerush_scores_mirage_import.json"; + a.click(); + } catch (err) { + console.error("Fetch/download error:", err); + alert("Failed to fetch or process JSON. See console for details."); + } + } + + waitFor("#id_ctpl_body") + .then((container) => { + const btn = document.createElement("button"); + btn.textContent = "πŸ“₯ DOWNLOAD PLAY HISTORY SCORE JSON"; + btn.style.cssText = ` + margin: 10px; padding: 8px 12px; + font-size: 14px; cursor: pointer; + background: #2563eb; color: white; + border: none; border-radius: 6px; + z-index: 9999; + `; + btn.onclick = fetchAndDownload; + + container.prepend(btn); + }) + .catch((err) => console.warn("Could not inject button:", err)); +})(); diff --git a/scripts/musicdiver/musicdiver_recent_history.js b/scripts/musicdiver/musicdiver_recent_history.js deleted file mode 100644 index 2b642e1..0000000 --- a/scripts/musicdiver/musicdiver_recent_history.js +++ /dev/null @@ -1,135 +0,0 @@ -// ==UserScript== -// @name MIRAGE MUSIC DIVER SCORE EXPORT -// @namespace https://mypage.musicdiver.jp/ -// @version 1.1 -// @description MUSIC DIVER My Page Recent History to Mirage import JSON -// @match https://mypage.musicdiver.jp/record?view=history* -// @grant none -// ==/UserScript== - -(function () { - "use strict"; - - let mirage = {}; - let remappedData = []; - - async function fetchRecordHistory() { - const url = "https://mypage.musicdiver.jp/api/record_history?lang=en"; - try { - const response = await fetch(url, { - headers: { - Accept: "application/json, text/javascript, */*; q=0.01", - "X-Requested-With": "XMLHttpRequest", - }, - credentials: "include", - }); - - const data = await response.json(); - if (data.responseCode !== 200) { - console.error("Music Diver API error:", data.responseMessage); - return; - } - - const diffMap = { - 0: "EASY", - 1: "NORMAL", - 2: "HARD", - 3: "EXTREME", - }; - - remappedData = data.response.map((rec) => { - const date = new Date(rec.created_at.replace(" ", "T") + "+09:00"); - const unixTime = date.getTime(); - - // Determine lamp based on flag precedence - let lamp = "FAILED"; - if (rec.clear_flag) lamp = "CLEAR"; - else if (rec.epic_flag) lamp = "EPIC"; - else if (rec.all_perfect_flag) lamp = "ALL PERFECT"; - else if (rec.full_combo_flag) lamp = "FULL COMBO"; - - return { - timestamp: unixTime, - title: rec.music_title, - artist: rec.artist_name, - difficulty: diffMap[rec.difficulty_id] || "UNKNOWN", - level: rec.level, - score: rec.score, - rank: rec.rank, - lamp, - judgements: { - critical: rec.critical_num, - perfect: rec.perfect_num, - great: rec.great_num, - good: rec.good_num, - bad: rec.bad_num, - miss: rec.miss_num, - }, - }; - }); - - mirage = { - meta: { - game: "musicdiver", - playtype: "Single", - service: "MUSIC DIVER My Page Recent History", - }, - scores: remappedData, - }; - - console.log("🎡 Music Diver Records:", remappedData); - console.log("Mirage export object:", mirage); - - showDownloadButton(); - } catch (err) { - console.error("Error fetching Music Diver data:", err); - } - } - - function showDownloadButton() { - // Avoid duplicates - if (document.getElementById("md-download-json")) return; - - const btn = document.createElement("button"); - btn.id = "md-download-json"; - btn.textContent = "⬇️ Download Mirage Score JSON"; - Object.assign(btn.style, { - position: "fixed", - bottom: "20px", - right: "20px", - zIndex: "9999", - padding: "10px 16px", - background: "#1e90ff", - color: "#fff", - border: "none", - borderRadius: "8px", - cursor: "pointer", - fontSize: "14px", - boxShadow: "0 2px 8px rgba(0,0,0,0.25)", - transition: "background 0.2s", - }); - - btn.onmouseenter = () => (btn.style.background = "#0070f0"); - btn.onmouseleave = () => (btn.style.background = "#1e90ff"); - - btn.addEventListener("click", () => { - if (!remappedData.length) { - alert("No data available yet. Try refreshing!"); - return; - } - const blob = new Blob([JSON.stringify(mirage, null, 2)], { - type: "application/json", - }); - const url = URL.createObjectURL(blob); - const a = document.createElement("a"); - a.href = url; - a.download = "musicdiver_records.json"; - a.click(); - URL.revokeObjectURL(url); - }); - - document.body.appendChild(btn); - } - - window.addEventListener("load", fetchRecordHistory); -})(); diff --git a/scripts/musicdiver/musicdiver_recent_history.user.js b/scripts/musicdiver/musicdiver_recent_history.user.js new file mode 100644 index 0000000..2b642e1 --- /dev/null +++ b/scripts/musicdiver/musicdiver_recent_history.user.js @@ -0,0 +1,135 @@ +// ==UserScript== +// @name MIRAGE MUSIC DIVER SCORE EXPORT +// @namespace https://mypage.musicdiver.jp/ +// @version 1.1 +// @description MUSIC DIVER My Page Recent History to Mirage import JSON +// @match https://mypage.musicdiver.jp/record?view=history* +// @grant none +// ==/UserScript== + +(function () { + "use strict"; + + let mirage = {}; + let remappedData = []; + + async function fetchRecordHistory() { + const url = "https://mypage.musicdiver.jp/api/record_history?lang=en"; + try { + const response = await fetch(url, { + headers: { + Accept: "application/json, text/javascript, */*; q=0.01", + "X-Requested-With": "XMLHttpRequest", + }, + credentials: "include", + }); + + const data = await response.json(); + if (data.responseCode !== 200) { + console.error("Music Diver API error:", data.responseMessage); + return; + } + + const diffMap = { + 0: "EASY", + 1: "NORMAL", + 2: "HARD", + 3: "EXTREME", + }; + + remappedData = data.response.map((rec) => { + const date = new Date(rec.created_at.replace(" ", "T") + "+09:00"); + const unixTime = date.getTime(); + + // Determine lamp based on flag precedence + let lamp = "FAILED"; + if (rec.clear_flag) lamp = "CLEAR"; + else if (rec.epic_flag) lamp = "EPIC"; + else if (rec.all_perfect_flag) lamp = "ALL PERFECT"; + else if (rec.full_combo_flag) lamp = "FULL COMBO"; + + return { + timestamp: unixTime, + title: rec.music_title, + artist: rec.artist_name, + difficulty: diffMap[rec.difficulty_id] || "UNKNOWN", + level: rec.level, + score: rec.score, + rank: rec.rank, + lamp, + judgements: { + critical: rec.critical_num, + perfect: rec.perfect_num, + great: rec.great_num, + good: rec.good_num, + bad: rec.bad_num, + miss: rec.miss_num, + }, + }; + }); + + mirage = { + meta: { + game: "musicdiver", + playtype: "Single", + service: "MUSIC DIVER My Page Recent History", + }, + scores: remappedData, + }; + + console.log("🎡 Music Diver Records:", remappedData); + console.log("Mirage export object:", mirage); + + showDownloadButton(); + } catch (err) { + console.error("Error fetching Music Diver data:", err); + } + } + + function showDownloadButton() { + // Avoid duplicates + if (document.getElementById("md-download-json")) return; + + const btn = document.createElement("button"); + btn.id = "md-download-json"; + btn.textContent = "⬇️ Download Mirage Score JSON"; + Object.assign(btn.style, { + position: "fixed", + bottom: "20px", + right: "20px", + zIndex: "9999", + padding: "10px 16px", + background: "#1e90ff", + color: "#fff", + border: "none", + borderRadius: "8px", + cursor: "pointer", + fontSize: "14px", + boxShadow: "0 2px 8px rgba(0,0,0,0.25)", + transition: "background 0.2s", + }); + + btn.onmouseenter = () => (btn.style.background = "#0070f0"); + btn.onmouseleave = () => (btn.style.background = "#1e90ff"); + + btn.addEventListener("click", () => { + if (!remappedData.length) { + alert("No data available yet. Try refreshing!"); + return; + } + const blob = new Blob([JSON.stringify(mirage, null, 2)], { + type: "application/json", + }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = "musicdiver_records.json"; + a.click(); + URL.revokeObjectURL(url); + }); + + document.body.appendChild(btn); + } + + window.addEventListener("load", fetchRecordHistory); +})(); diff --git a/scripts/nostalgia/nostalgia_flower_scraper.js b/scripts/nostalgia/nostalgia_flower_scraper.js deleted file mode 100644 index b621060..0000000 --- a/scripts/nostalgia/nostalgia_flower_scraper.js +++ /dev/null @@ -1,188 +0,0 @@ -// ==UserScript== -// @name Mirage Nostalgia Score Scraper for FLOWER -// @version 1.0 -// @description Scrapes scores from Flower's Nostalgia page and converts to Mirage -// @author Meta-link -// @match https://projectflower.eu/game/nostalgia/* -// @grant none -// ==/UserScript== - -(function() { - 'use strict'; - - let mirage = {}; - const scores = []; - - // Create progress bar container - const progressContainer = document.createElement("div"); - progressContainer.style.position = "fixed"; - progressContainer.style.top = "50px"; - progressContainer.style.left = "50%"; - progressContainer.style.transform = "translateX(-50%)"; - progressContainer.style.width = "50%"; - progressContainer.style.background = "#eee"; - progressContainer.style.border = "1px solid #ccc"; - progressContainer.style.borderRadius = "4px"; - progressContainer.style.zIndex = 9999; - progressContainer.style.display = "none"; - - const progressBar = document.createElement("div"); - progressBar.style.width = "0%"; - progressBar.style.height = "20px"; - progressBar.style.background = "#28a745"; - progressBar.style.borderRadius = "4px"; - progressContainer.appendChild(progressBar); - document.body.appendChild(progressContainer); - - function updateProgress(current, total) { - progressContainer.style.display = "block"; - const percent = Math.round((current / total) * 100); - progressBar.style.width = percent + "%"; - progressBar.textContent = `${current} / ${total} songs`; - } - - // Parse judgements from details row - function parseJudgements(detailsRow) { - const judgements = {}; - const optional = {}; - const cols = detailsRow.querySelectorAll("div.col-sm-2"); - cols.forEach(col => { - const labelElem = col.querySelector("strong"); - if (!labelElem) return; - - const label = labelElem.textContent.replace(/\s/g, ""); - const valueText = labelElem.nextSibling?.textContent?.trim() || col.textContent.replace(labelElem.textContent, "").trim(); - - if (label === "β—†Just") judgements.marvelous = Number(valueText) || 0; - else if (label === "Just") judgements.just = Number(valueText) || 0; - else if (label === "Good") judgements.good = Number(valueText) || 0; - else if (label === "Near") judgements.near = Number(valueText) || 0; - else if (label === "Miss") judgements.miss = Number(valueText) || 0; - else if (label === "Fast/Slow" || label === "Fast/Slow") { - const parts = valueText.split("/").map(x => Number(x.trim())); - optional.fast = parts[0] || 0; - optional.slow = parts[1] || 0; - } - }); - return { judgements, optional }; - } - - // Fetch artist from song page - async function getArtistFromLink(link) { - try { - const res = await fetch(link); - const text = await res.text(); - const parser = new DOMParser(); - const doc = parser.parseFromString(text, "text/html"); - const ths = doc.querySelectorAll("th"); - for (let th of ths) { - if (th.textContent.trim() === "Song Artist") { - const td = th.nextElementSibling; - if (td) return td.textContent.trim(); - } - } - } catch (e) { - console.error("Error fetching artist from", link, e); - } - return ""; - } - - // Parse the Nostalgia score table - async function parseNostalgia() { - const scoreLines = document.querySelectorAll(".table > tbody:nth-child(3) > tr"); - const songs = Array.from(scoreLines).filter(row => row.classList.contains("accordion-toggle")); - const totalSongs = songs.length; - - for (let i = 0; i < songs.length; i++) { - const row = songs[i]; - - // Title and song link - const songLinkElem = row.querySelector("td:nth-child(2) > a"); - const title = songLinkElem.querySelector("strong").textContent.trim(); - const songLink = songLinkElem.href; - - // Fetch artist from song page - const artist = await getArtistFromLink(songLink); - - // Chart info: difficulty + level - const chartCell = row.querySelector("td:nth-child(3)").textContent.trim(); - let difficulty = "Unknown"; - let level = 0; - const match = chartCell.match(/([A-Za-z]+)\s*(\d+)/); - if (match) { - difficulty = match[1]; - level = Number(match[2]); - } - - // Score - const score = parseInt(row.querySelector("td:nth-child(4) small").textContent.replaceAll(',', '')); - - // Lamp - const lamp = row.querySelector("td:nth-child(5) strong").textContent.trim(); - - // Time achieved - const timeTxt = row.querySelector("td:nth-child(6) small").textContent.trim(); - const timeAchieved = new Date(timeTxt).getTime(); - - // Judgements - const detailsRow = scoreLines[i * 2 + 1].querySelector("div[style*='padding: 5px']"); - const { judgements, optional } = parseJudgements(detailsRow); - - scores.push({ - title: title, - artist: artist, - difficulty: difficulty, - level: level, - score: score, - lamp: lamp, - timestamp: timeAchieved, - judgements: judgements, - optional: optional - }); - - updateProgress(i + 1, totalSongs); - } - - progressContainer.style.display = "none"; - exportScores(); - } - - // Export scores as JSON - function exportScores() { - mirage = { - meta: { - game: "nostalgia", - playtype: "Single", - service: "NOSTALGIA Flower Play History", - }, - scores: scores, - }; - - const blob = new Blob([JSON.stringify(mirage, null, 2)], {type: "application/json"}); - const url = URL.createObjectURL(blob); - const a = document.createElement("a"); - a.href = url; - a.download = "nostalgia_scores.json"; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - alert(`Exported ${scores.length} scores!`); - } - - // Add export button - const button = document.createElement("button"); - button.textContent = "Export Nostalgia Scores MIRAGE"; - button.style.position = "fixed"; - button.style.top = "10px"; - button.style.right = "10px"; - button.style.zIndex = 9999; - button.style.padding = "8px 12px"; - button.style.background = "#007bff"; - button.style.color = "white"; - button.style.border = "none"; - button.style.borderRadius = "4px"; - button.style.cursor = "pointer"; - button.onclick = parseNostalgia; - document.body.appendChild(button); - -})(); diff --git a/scripts/nostalgia/nostalgia_flower_scraper.user.js b/scripts/nostalgia/nostalgia_flower_scraper.user.js new file mode 100644 index 0000000..b621060 --- /dev/null +++ b/scripts/nostalgia/nostalgia_flower_scraper.user.js @@ -0,0 +1,188 @@ +// ==UserScript== +// @name Mirage Nostalgia Score Scraper for FLOWER +// @version 1.0 +// @description Scrapes scores from Flower's Nostalgia page and converts to Mirage +// @author Meta-link +// @match https://projectflower.eu/game/nostalgia/* +// @grant none +// ==/UserScript== + +(function() { + 'use strict'; + + let mirage = {}; + const scores = []; + + // Create progress bar container + const progressContainer = document.createElement("div"); + progressContainer.style.position = "fixed"; + progressContainer.style.top = "50px"; + progressContainer.style.left = "50%"; + progressContainer.style.transform = "translateX(-50%)"; + progressContainer.style.width = "50%"; + progressContainer.style.background = "#eee"; + progressContainer.style.border = "1px solid #ccc"; + progressContainer.style.borderRadius = "4px"; + progressContainer.style.zIndex = 9999; + progressContainer.style.display = "none"; + + const progressBar = document.createElement("div"); + progressBar.style.width = "0%"; + progressBar.style.height = "20px"; + progressBar.style.background = "#28a745"; + progressBar.style.borderRadius = "4px"; + progressContainer.appendChild(progressBar); + document.body.appendChild(progressContainer); + + function updateProgress(current, total) { + progressContainer.style.display = "block"; + const percent = Math.round((current / total) * 100); + progressBar.style.width = percent + "%"; + progressBar.textContent = `${current} / ${total} songs`; + } + + // Parse judgements from details row + function parseJudgements(detailsRow) { + const judgements = {}; + const optional = {}; + const cols = detailsRow.querySelectorAll("div.col-sm-2"); + cols.forEach(col => { + const labelElem = col.querySelector("strong"); + if (!labelElem) return; + + const label = labelElem.textContent.replace(/\s/g, ""); + const valueText = labelElem.nextSibling?.textContent?.trim() || col.textContent.replace(labelElem.textContent, "").trim(); + + if (label === "β—†Just") judgements.marvelous = Number(valueText) || 0; + else if (label === "Just") judgements.just = Number(valueText) || 0; + else if (label === "Good") judgements.good = Number(valueText) || 0; + else if (label === "Near") judgements.near = Number(valueText) || 0; + else if (label === "Miss") judgements.miss = Number(valueText) || 0; + else if (label === "Fast/Slow" || label === "Fast/Slow") { + const parts = valueText.split("/").map(x => Number(x.trim())); + optional.fast = parts[0] || 0; + optional.slow = parts[1] || 0; + } + }); + return { judgements, optional }; + } + + // Fetch artist from song page + async function getArtistFromLink(link) { + try { + const res = await fetch(link); + const text = await res.text(); + const parser = new DOMParser(); + const doc = parser.parseFromString(text, "text/html"); + const ths = doc.querySelectorAll("th"); + for (let th of ths) { + if (th.textContent.trim() === "Song Artist") { + const td = th.nextElementSibling; + if (td) return td.textContent.trim(); + } + } + } catch (e) { + console.error("Error fetching artist from", link, e); + } + return ""; + } + + // Parse the Nostalgia score table + async function parseNostalgia() { + const scoreLines = document.querySelectorAll(".table > tbody:nth-child(3) > tr"); + const songs = Array.from(scoreLines).filter(row => row.classList.contains("accordion-toggle")); + const totalSongs = songs.length; + + for (let i = 0; i < songs.length; i++) { + const row = songs[i]; + + // Title and song link + const songLinkElem = row.querySelector("td:nth-child(2) > a"); + const title = songLinkElem.querySelector("strong").textContent.trim(); + const songLink = songLinkElem.href; + + // Fetch artist from song page + const artist = await getArtistFromLink(songLink); + + // Chart info: difficulty + level + const chartCell = row.querySelector("td:nth-child(3)").textContent.trim(); + let difficulty = "Unknown"; + let level = 0; + const match = chartCell.match(/([A-Za-z]+)\s*(\d+)/); + if (match) { + difficulty = match[1]; + level = Number(match[2]); + } + + // Score + const score = parseInt(row.querySelector("td:nth-child(4) small").textContent.replaceAll(',', '')); + + // Lamp + const lamp = row.querySelector("td:nth-child(5) strong").textContent.trim(); + + // Time achieved + const timeTxt = row.querySelector("td:nth-child(6) small").textContent.trim(); + const timeAchieved = new Date(timeTxt).getTime(); + + // Judgements + const detailsRow = scoreLines[i * 2 + 1].querySelector("div[style*='padding: 5px']"); + const { judgements, optional } = parseJudgements(detailsRow); + + scores.push({ + title: title, + artist: artist, + difficulty: difficulty, + level: level, + score: score, + lamp: lamp, + timestamp: timeAchieved, + judgements: judgements, + optional: optional + }); + + updateProgress(i + 1, totalSongs); + } + + progressContainer.style.display = "none"; + exportScores(); + } + + // Export scores as JSON + function exportScores() { + mirage = { + meta: { + game: "nostalgia", + playtype: "Single", + service: "NOSTALGIA Flower Play History", + }, + scores: scores, + }; + + const blob = new Blob([JSON.stringify(mirage, null, 2)], {type: "application/json"}); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = "nostalgia_scores.json"; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + alert(`Exported ${scores.length} scores!`); + } + + // Add export button + const button = document.createElement("button"); + button.textContent = "Export Nostalgia Scores MIRAGE"; + button.style.position = "fixed"; + button.style.top = "10px"; + button.style.right = "10px"; + button.style.zIndex = 9999; + button.style.padding = "8px 12px"; + button.style.background = "#007bff"; + button.style.color = "white"; + button.style.border = "none"; + button.style.borderRadius = "4px"; + button.style.cursor = "pointer"; + button.onclick = parseNostalgia; + document.body.appendChild(button); + +})(); diff --git a/scripts/projectdiva-arcade/diva_net_history.js b/scripts/projectdiva-arcade/diva_net_history.js deleted file mode 100644 index f6b2b2c..0000000 --- a/scripts/projectdiva-arcade/diva_net_history.js +++ /dev/null @@ -1,262 +0,0 @@ -// ==UserScript== -// @name DIVA.NET Mirage Scraper -// @namespace http://tampermonkey.net/ -// @version 1.2 -// @description Scrape DIVA.NET play history (pages 1–20) into Mirage JSON -// @match https://project-diva-ac.net/divanet/personal/playHistory/* -// @grant none -// @run-at document-idle -// ==/UserScript== - -(function () { - // --- Utility: wait for selector --- - function waitFor(selector, timeout = 10000) { - return new Promise((resolve, reject) => { - const interval = 300; - let waited = 0; - const check = () => { - const el = document.querySelector(selector); - if (el) return resolve(el); - waited += interval; - if (waited >= timeout) return reject(`Timeout: ${selector}`); - setTimeout(check, interval); - }; - check(); - }); - } - - // --- Fetch artist name from info page --- - async function getArtistFromInfoPage(url) { - try { - const res = await fetch(url, { credentials: "include" }); - const html = await res.text(); - const doc = new DOMParser().parseFromString(html, "text/html"); - const rows = Array.from(doc.querySelectorAll("table tr")); - for (const row of rows) { - const label = row.querySelector("td:first-child font"); - const value = row.querySelector("td:last-child font"); - if (label && value && label.textContent.includes("δ½œζ›²θ€…")) { - return value.textContent.trim(); - } - } - return null; - } catch { - return null; - } - } - - // --- Parse a single page --- - async function parsePlayHistoryPage(html) { - const doc = new DOMParser().parseFromString(html, "text/html"); - const center = doc.querySelector(".center_middle"); - if (!center) return null; - - const findFont = (keyword) => - Array.from(center.querySelectorAll("font")).find((f) => - f.textContent && f.textContent.includes(keyword) - ); - - const getTextAfterLabel = (keyword) => { - const font = findFont(keyword); - if (!font) return null; - - let node = font.nextSibling; - const parts = []; - let scanned = 0; - - while (node && scanned < 20) { - scanned++; - if (node.nodeType === Node.TEXT_NODE) { - const txt = node.textContent.replace(/\s+/g, " ").trim(); - if (txt) parts.push(txt); - node = node.nextSibling; - continue; - } - if (node.nodeType === Node.ELEMENT_NODE) { - if (["BR", "HR"].includes(node.tagName)) { - node = node.nextSibling; - continue; - } - if (node.tagName === "FONT" && /\[.*\]/.test(node.textContent)) break; - - const txt = node.textContent.replace(/\s+/g, " ").trim(); - if (txt) parts.push(txt); - node = node.nextSibling; - continue; - } - node = node.nextSibling; - } - return parts.join(" ").trim() || null; - }; - - const entry = {}; - - // --- Timestamp --- - const datetimeRaw = getTextAfterLabel("ζ—₯ζ™‚") || ""; - const dtMatch = datetimeRaw.match(/(\d{2})\/(\d{2})\/(\d{2})\s+(\d{2}):(\d{2})/); - if (dtMatch) { - const [_, yy, mm, dd, hh, min] = dtMatch.map(Number); - const fullYear = 2000 + yy; - const timestamp = Date.UTC(fullYear, mm - 1, dd, hh - 9, min, 0); - entry.timestamp = timestamp; - } else entry.timestamp = null; - - // --- Title & Artist --- - const a = center.querySelector("a[href*='/divanet/pv/info/']"); - if (a) { - entry.title = a.textContent.trim(); - const songInfoUrl = new URL(a.getAttribute("href"), location.origin).href; - entry.artist = await getArtistFromInfoPage(songInfoUrl); - } else { - entry.title = getTextAfterLabel("曲名"); - entry.artist = null; - } - - // --- Difficulty --- - const diffRaw = getTextAfterLabel("ι›£ζ˜“εΊ¦") || ""; - const diffMatch = diffRaw.match(/([A-Zぁ-γ‚“γ‚‘-ン一-ιΎ―]+)\s*β˜…?\s*([\d.]+)/i); - if (diffMatch) { - entry.diff_lamp = diffMatch[1].trim().toUpperCase(); - entry.difficulty = parseFloat(diffMatch[2]); - } else { - entry.diff_lamp = diffRaw.trim(); - entry.difficulty = null; - } - - // --- Clear rank --- - entry.lamp = (getTextAfterLabel("CLEAR RANK") || "").trim(); - - // --- Achievement --- - const achRaw = getTextAfterLabel("ι”ζˆηŽ‡") || ""; - const achNum = (achRaw.match(/[\d.]+/) || [null])[0]; - entry.achievement = achNum ? parseFloat(achNum) : null; - - // --- Score --- - const scoreRaw = getTextAfterLabel("SCORE") || ""; - const scoreDigits = (scoreRaw.match(/(\d+)/) || [null])[0]; - entry.score = scoreDigits ? parseInt(scoreDigits, 10) : null; - - // --- Judgements --- - entry.judgements = {}; - const table = center.querySelector("table"); - if (table) { - const rows = Array.from(table.querySelectorAll("tr")); - for (let i = 0; i < rows.length; i++) { - const text = rows[i].textContent.replace(/\s+/g, " ").trim(); - const val = parseInt((text.match(/(\d+)/) || [0])[0], 10); - if (/COOL/i.test(text)) entry.judgements.cool = val; - else if (/FINE/i.test(text)) entry.judgements.fine = val; - else if (/SAFE/i.test(text)) entry.judgements.safe = val; - else if (/SAD/i.test(text)) entry.judgements.sad = val; - else if (/WORST|WRONG/i.test(text)) entry.judgements.worst = val; - else if (/COMBO/i.test(text)) - entry.maxCombo = parseInt((text.match(/(\d+)/g) || []).pop() || "0", 10); - } - } - - entry.judgements = { - cool: entry.judgements.cool ?? 0, - fine: entry.judgements.fine ?? 0, - safe: entry.judgements.safe ?? 0, - sad: entry.judgements.sad ?? 0, - worst: entry.judgements.worst ?? 0, - }; - entry.maxCombo = entry.maxCombo ?? 0; - - return entry; - } - - // --- Fetch all pages (1–20) --- - async function fetchAllPages(progressBar) { - const scores = []; - for (let i = 1; i <= 20; i++) { - const url = `https://project-diva-ac.net/divanet/personal/playHistoryDetail/${i}/0`; - try { - progressBar.style.width = `${(i / 20) * 100}%`; - console.log(`Fetching page ${i}...`); - const response = await fetch(url, { credentials: "include" }); - if (!response.ok) continue; - const html = await response.text(); - const parsed = await parsePlayHistoryPage(html); - if (parsed && parsed.title) scores.push(parsed); - } catch (e) { - console.warn(`Failed to fetch page ${i}:`, e); - } - } - progressBar.style.width = "100%"; - return scores; - } - - // --- Fetch & Download all as JSON --- - async function fetchAndDownload() { - try { - const progressContainer = document.createElement("div"); - progressContainer.style.cssText = ` - width: 200px; - height: 20px; - background: #eee; - border-radius: 10px; - overflow: hidden; - margin: 10px; - `; - - const progressBar = document.createElement("div"); - progressBar.style.cssText = ` - width: 0%; - height: 100%; - background: #2563eb; - transition: width 0.3s ease; - `; - - progressContainer.appendChild(progressBar); - document.querySelector(".center").prepend(progressContainer); - - const scores = await fetchAllPages(progressBar); - console.log(`Fetched ${scores.length} entries.`); - - const mirage = { - meta: { - game: "diva", - playtype: "Single", - service: "DIVA.NET PLAY HISTORY", - }, - scores: scores, - }; - - const blob = new Blob([JSON.stringify(mirage, null, 2)], { - type: "application/json", - }); - const a = document.createElement("a"); - a.href = URL.createObjectURL(blob); - a.download = "divanet_scores_mirage_import.json"; - a.click(); - - setTimeout(() => progressContainer.remove(), 1000); - } catch (err) { - console.error("Error during fetch/download:", err); - alert("Error while scraping pages β€” see console for details."); - } - } - - // --- Inject button --- - waitFor(".center") - .then((container) => { - const btn = document.createElement("button"); - btn.textContent = "πŸ“₯ DOWNLOAD PLAY HISTORY SCORE JSON"; - btn.style.cssText = ` - margin: 10px; - padding: 8px 12px; - font-size: 14px; - cursor: pointer; - background: #2563eb; - color: white; - border: none; - border-radius: 6px; - z-index: 9999; - position: relative; - `; - btn.onclick = fetchAndDownload; - container.prepend(btn); - }) - .catch((err) => console.warn("Could not inject button:", err)); -})(); diff --git a/scripts/projectdiva-arcade/diva_net_history.user..js b/scripts/projectdiva-arcade/diva_net_history.user..js new file mode 100644 index 0000000..f6b2b2c --- /dev/null +++ b/scripts/projectdiva-arcade/diva_net_history.user..js @@ -0,0 +1,262 @@ +// ==UserScript== +// @name DIVA.NET Mirage Scraper +// @namespace http://tampermonkey.net/ +// @version 1.2 +// @description Scrape DIVA.NET play history (pages 1–20) into Mirage JSON +// @match https://project-diva-ac.net/divanet/personal/playHistory/* +// @grant none +// @run-at document-idle +// ==/UserScript== + +(function () { + // --- Utility: wait for selector --- + function waitFor(selector, timeout = 10000) { + return new Promise((resolve, reject) => { + const interval = 300; + let waited = 0; + const check = () => { + const el = document.querySelector(selector); + if (el) return resolve(el); + waited += interval; + if (waited >= timeout) return reject(`Timeout: ${selector}`); + setTimeout(check, interval); + }; + check(); + }); + } + + // --- Fetch artist name from info page --- + async function getArtistFromInfoPage(url) { + try { + const res = await fetch(url, { credentials: "include" }); + const html = await res.text(); + const doc = new DOMParser().parseFromString(html, "text/html"); + const rows = Array.from(doc.querySelectorAll("table tr")); + for (const row of rows) { + const label = row.querySelector("td:first-child font"); + const value = row.querySelector("td:last-child font"); + if (label && value && label.textContent.includes("δ½œζ›²θ€…")) { + return value.textContent.trim(); + } + } + return null; + } catch { + return null; + } + } + + // --- Parse a single page --- + async function parsePlayHistoryPage(html) { + const doc = new DOMParser().parseFromString(html, "text/html"); + const center = doc.querySelector(".center_middle"); + if (!center) return null; + + const findFont = (keyword) => + Array.from(center.querySelectorAll("font")).find((f) => + f.textContent && f.textContent.includes(keyword) + ); + + const getTextAfterLabel = (keyword) => { + const font = findFont(keyword); + if (!font) return null; + + let node = font.nextSibling; + const parts = []; + let scanned = 0; + + while (node && scanned < 20) { + scanned++; + if (node.nodeType === Node.TEXT_NODE) { + const txt = node.textContent.replace(/\s+/g, " ").trim(); + if (txt) parts.push(txt); + node = node.nextSibling; + continue; + } + if (node.nodeType === Node.ELEMENT_NODE) { + if (["BR", "HR"].includes(node.tagName)) { + node = node.nextSibling; + continue; + } + if (node.tagName === "FONT" && /\[.*\]/.test(node.textContent)) break; + + const txt = node.textContent.replace(/\s+/g, " ").trim(); + if (txt) parts.push(txt); + node = node.nextSibling; + continue; + } + node = node.nextSibling; + } + return parts.join(" ").trim() || null; + }; + + const entry = {}; + + // --- Timestamp --- + const datetimeRaw = getTextAfterLabel("ζ—₯ζ™‚") || ""; + const dtMatch = datetimeRaw.match(/(\d{2})\/(\d{2})\/(\d{2})\s+(\d{2}):(\d{2})/); + if (dtMatch) { + const [_, yy, mm, dd, hh, min] = dtMatch.map(Number); + const fullYear = 2000 + yy; + const timestamp = Date.UTC(fullYear, mm - 1, dd, hh - 9, min, 0); + entry.timestamp = timestamp; + } else entry.timestamp = null; + + // --- Title & Artist --- + const a = center.querySelector("a[href*='/divanet/pv/info/']"); + if (a) { + entry.title = a.textContent.trim(); + const songInfoUrl = new URL(a.getAttribute("href"), location.origin).href; + entry.artist = await getArtistFromInfoPage(songInfoUrl); + } else { + entry.title = getTextAfterLabel("曲名"); + entry.artist = null; + } + + // --- Difficulty --- + const diffRaw = getTextAfterLabel("ι›£ζ˜“εΊ¦") || ""; + const diffMatch = diffRaw.match(/([A-Zぁ-γ‚“γ‚‘-ン一-ιΎ―]+)\s*β˜…?\s*([\d.]+)/i); + if (diffMatch) { + entry.diff_lamp = diffMatch[1].trim().toUpperCase(); + entry.difficulty = parseFloat(diffMatch[2]); + } else { + entry.diff_lamp = diffRaw.trim(); + entry.difficulty = null; + } + + // --- Clear rank --- + entry.lamp = (getTextAfterLabel("CLEAR RANK") || "").trim(); + + // --- Achievement --- + const achRaw = getTextAfterLabel("ι”ζˆηŽ‡") || ""; + const achNum = (achRaw.match(/[\d.]+/) || [null])[0]; + entry.achievement = achNum ? parseFloat(achNum) : null; + + // --- Score --- + const scoreRaw = getTextAfterLabel("SCORE") || ""; + const scoreDigits = (scoreRaw.match(/(\d+)/) || [null])[0]; + entry.score = scoreDigits ? parseInt(scoreDigits, 10) : null; + + // --- Judgements --- + entry.judgements = {}; + const table = center.querySelector("table"); + if (table) { + const rows = Array.from(table.querySelectorAll("tr")); + for (let i = 0; i < rows.length; i++) { + const text = rows[i].textContent.replace(/\s+/g, " ").trim(); + const val = parseInt((text.match(/(\d+)/) || [0])[0], 10); + if (/COOL/i.test(text)) entry.judgements.cool = val; + else if (/FINE/i.test(text)) entry.judgements.fine = val; + else if (/SAFE/i.test(text)) entry.judgements.safe = val; + else if (/SAD/i.test(text)) entry.judgements.sad = val; + else if (/WORST|WRONG/i.test(text)) entry.judgements.worst = val; + else if (/COMBO/i.test(text)) + entry.maxCombo = parseInt((text.match(/(\d+)/g) || []).pop() || "0", 10); + } + } + + entry.judgements = { + cool: entry.judgements.cool ?? 0, + fine: entry.judgements.fine ?? 0, + safe: entry.judgements.safe ?? 0, + sad: entry.judgements.sad ?? 0, + worst: entry.judgements.worst ?? 0, + }; + entry.maxCombo = entry.maxCombo ?? 0; + + return entry; + } + + // --- Fetch all pages (1–20) --- + async function fetchAllPages(progressBar) { + const scores = []; + for (let i = 1; i <= 20; i++) { + const url = `https://project-diva-ac.net/divanet/personal/playHistoryDetail/${i}/0`; + try { + progressBar.style.width = `${(i / 20) * 100}%`; + console.log(`Fetching page ${i}...`); + const response = await fetch(url, { credentials: "include" }); + if (!response.ok) continue; + const html = await response.text(); + const parsed = await parsePlayHistoryPage(html); + if (parsed && parsed.title) scores.push(parsed); + } catch (e) { + console.warn(`Failed to fetch page ${i}:`, e); + } + } + progressBar.style.width = "100%"; + return scores; + } + + // --- Fetch & Download all as JSON --- + async function fetchAndDownload() { + try { + const progressContainer = document.createElement("div"); + progressContainer.style.cssText = ` + width: 200px; + height: 20px; + background: #eee; + border-radius: 10px; + overflow: hidden; + margin: 10px; + `; + + const progressBar = document.createElement("div"); + progressBar.style.cssText = ` + width: 0%; + height: 100%; + background: #2563eb; + transition: width 0.3s ease; + `; + + progressContainer.appendChild(progressBar); + document.querySelector(".center").prepend(progressContainer); + + const scores = await fetchAllPages(progressBar); + console.log(`Fetched ${scores.length} entries.`); + + const mirage = { + meta: { + game: "diva", + playtype: "Single", + service: "DIVA.NET PLAY HISTORY", + }, + scores: scores, + }; + + const blob = new Blob([JSON.stringify(mirage, null, 2)], { + type: "application/json", + }); + const a = document.createElement("a"); + a.href = URL.createObjectURL(blob); + a.download = "divanet_scores_mirage_import.json"; + a.click(); + + setTimeout(() => progressContainer.remove(), 1000); + } catch (err) { + console.error("Error during fetch/download:", err); + alert("Error while scraping pages β€” see console for details."); + } + } + + // --- Inject button --- + waitFor(".center") + .then((container) => { + const btn = document.createElement("button"); + btn.textContent = "πŸ“₯ DOWNLOAD PLAY HISTORY SCORE JSON"; + btn.style.cssText = ` + margin: 10px; + padding: 8px 12px; + font-size: 14px; + cursor: pointer; + background: #2563eb; + color: white; + border: none; + border-radius: 6px; + z-index: 9999; + position: relative; + `; + btn.onclick = fetchAndDownload; + container.prepend(btn); + }) + .catch((err) => console.warn("Could not inject button:", err)); +})(); diff --git a/scripts/reflecbeat/reflecbeat_flower_scraper.js b/scripts/reflecbeat/reflecbeat_flower_scraper.js deleted file mode 100644 index 1152da9..0000000 --- a/scripts/reflecbeat/reflecbeat_flower_scraper.js +++ /dev/null @@ -1,111 +0,0 @@ -// ==UserScript== -// @name REFLEC BEAT SCORE EXPORT -// @namespace http://tampermonkey.net/ -// @version 1.2 -// @description Export REFLEC BEAT scores including full judgements and timestamps as JSON -// @match https://projectflower.eu/game/rb/profile/* -// @grant none -// ==/UserScript== - -(function() { - 'use strict'; - - // Add export button - const btn = document.createElement('button'); - btn.textContent = 'Mirage Export Scores JSON'; - btn.style.position = 'fixed'; - btn.style.top = '10px'; - btn.style.right = '10px'; - btn.style.zIndex = 9999; - btn.style.padding = '8px 12px'; - btn.style.background = '#4CAF50'; - btn.style.color = 'white'; - btn.style.border = 'none'; - btn.style.borderRadius = '4px'; - btn.style.cursor = 'pointer'; - document.body.appendChild(btn); - - btn.addEventListener('click', () => { - const json = { - meta: { - game: "reflecbeat", - playtype: "Single", - service: "REFLEC BEAT Flower Play History" - }, - scores: parseScoreLog() - }; - - // Download JSON - const blob = new Blob([JSON.stringify(json, null, 2)], { type: 'application/json' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = 'reflecbeat_scores.json'; - a.click(); - URL.revokeObjectURL(url); - }); - - function parseScoreLog() { - const rows = document.querySelectorAll('table.table tbody tr.accordion-toggle'); - const scores = []; - - rows.forEach(row => { - const titleElem = row.querySelector('td.pnmTitle a'); - const artistElem = row.querySelector('td.pnmTitle small'); - const difficultyElem = row.querySelector('td.gradeHARD, td.gradeMEDIUM, td.gradeEASY'); - const levelElem = difficultyElem?.querySelector('strong'); - const lampElem = row.querySelector('td.rb-rank strong'); // Rank / Lamp - const scoreText = row.querySelector('td.rb-rank span')?.innerText || ''; - const scoreMatch = scoreText.match(/(\d+(?:,\d+)*)\s*\((\d+(?:\.\d+)?)%\)/); - const scoreNum = scoreMatch ? parseInt(scoreMatch[1].replace(/,/g, '')) : null; - const scorePercent = scoreMatch ? parseFloat(scoreMatch[2]) : null; - - // Extract timestamp from in last column - const timestampElem = row.querySelector('td.hidden-from-mobile small'); - let timestamp = null; - if (timestampElem) { - const dateStr = timestampElem.innerText.trim(); // e.g., "2025-06-27 5:23 PM" - timestamp = new Date(dateStr).getTime(); // Unix ms - } - - // Get accordion ID - const accordionId = row.getAttribute('data-target')?.replace('#', ''); - const accordion = document.getElementById(accordionId); - - // Initialize metadata - let lifeLeft = '', justReflec = 0, just = 0, great = 0, good = 0, miss = 0; - - if (accordion) { - const divs = accordion.querySelectorAll('div.col-sm-3, div.col-sm-2, div.col-sm-4'); - divs.forEach(div => { - const label = div.querySelector('strong')?.innerText.trim(); - const value = div.childNodes[div.childNodes.length-1].nodeValue?.trim() || div.querySelector('a')?.innerText.trim() || ''; - switch(label) { - case 'Life Left': lifeLeft = value; break; - case 'Just Reflec': justReflec = parseInt(value) || 0; break; - case 'Just': just = parseInt(value) || 0; break; - case 'Great': great = parseInt(value) || 0; break; - case 'Good': good = parseInt(value) || 0; break; - case 'Miss': miss = parseInt(value) || 0; break; - } - }); - } - - scores.push({ - title: titleElem?.innerText.trim() || '', - artist: artistElem?.innerText.trim() || '', - difficulty: difficultyElem?.innerText.replace(levelElem?.innerText, '').trim() || '', - level: levelElem ? parseInt(levelElem.innerText.trim()) : null, - score: scoreNum, - scorePercent, - lamp: lampElem?.innerText.trim() || '', - lifeLeft: parseInt(lifeLeft) || null, - timestamp, // Unix ms - judgements: { justReflec, just, great, good, miss } - }); - }); - - return scores; - } - -})(); diff --git a/scripts/reflecbeat/reflecbeat_flower_scraper.user.js b/scripts/reflecbeat/reflecbeat_flower_scraper.user.js new file mode 100644 index 0000000..1152da9 --- /dev/null +++ b/scripts/reflecbeat/reflecbeat_flower_scraper.user.js @@ -0,0 +1,111 @@ +// ==UserScript== +// @name REFLEC BEAT SCORE EXPORT +// @namespace http://tampermonkey.net/ +// @version 1.2 +// @description Export REFLEC BEAT scores including full judgements and timestamps as JSON +// @match https://projectflower.eu/game/rb/profile/* +// @grant none +// ==/UserScript== + +(function() { + 'use strict'; + + // Add export button + const btn = document.createElement('button'); + btn.textContent = 'Mirage Export Scores JSON'; + btn.style.position = 'fixed'; + btn.style.top = '10px'; + btn.style.right = '10px'; + btn.style.zIndex = 9999; + btn.style.padding = '8px 12px'; + btn.style.background = '#4CAF50'; + btn.style.color = 'white'; + btn.style.border = 'none'; + btn.style.borderRadius = '4px'; + btn.style.cursor = 'pointer'; + document.body.appendChild(btn); + + btn.addEventListener('click', () => { + const json = { + meta: { + game: "reflecbeat", + playtype: "Single", + service: "REFLEC BEAT Flower Play History" + }, + scores: parseScoreLog() + }; + + // Download JSON + const blob = new Blob([JSON.stringify(json, null, 2)], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'reflecbeat_scores.json'; + a.click(); + URL.revokeObjectURL(url); + }); + + function parseScoreLog() { + const rows = document.querySelectorAll('table.table tbody tr.accordion-toggle'); + const scores = []; + + rows.forEach(row => { + const titleElem = row.querySelector('td.pnmTitle a'); + const artistElem = row.querySelector('td.pnmTitle small'); + const difficultyElem = row.querySelector('td.gradeHARD, td.gradeMEDIUM, td.gradeEASY'); + const levelElem = difficultyElem?.querySelector('strong'); + const lampElem = row.querySelector('td.rb-rank strong'); // Rank / Lamp + const scoreText = row.querySelector('td.rb-rank span')?.innerText || ''; + const scoreMatch = scoreText.match(/(\d+(?:,\d+)*)\s*\((\d+(?:\.\d+)?)%\)/); + const scoreNum = scoreMatch ? parseInt(scoreMatch[1].replace(/,/g, '')) : null; + const scorePercent = scoreMatch ? parseFloat(scoreMatch[2]) : null; + + // Extract timestamp from in last column + const timestampElem = row.querySelector('td.hidden-from-mobile small'); + let timestamp = null; + if (timestampElem) { + const dateStr = timestampElem.innerText.trim(); // e.g., "2025-06-27 5:23 PM" + timestamp = new Date(dateStr).getTime(); // Unix ms + } + + // Get accordion ID + const accordionId = row.getAttribute('data-target')?.replace('#', ''); + const accordion = document.getElementById(accordionId); + + // Initialize metadata + let lifeLeft = '', justReflec = 0, just = 0, great = 0, good = 0, miss = 0; + + if (accordion) { + const divs = accordion.querySelectorAll('div.col-sm-3, div.col-sm-2, div.col-sm-4'); + divs.forEach(div => { + const label = div.querySelector('strong')?.innerText.trim(); + const value = div.childNodes[div.childNodes.length-1].nodeValue?.trim() || div.querySelector('a')?.innerText.trim() || ''; + switch(label) { + case 'Life Left': lifeLeft = value; break; + case 'Just Reflec': justReflec = parseInt(value) || 0; break; + case 'Just': just = parseInt(value) || 0; break; + case 'Great': great = parseInt(value) || 0; break; + case 'Good': good = parseInt(value) || 0; break; + case 'Miss': miss = parseInt(value) || 0; break; + } + }); + } + + scores.push({ + title: titleElem?.innerText.trim() || '', + artist: artistElem?.innerText.trim() || '', + difficulty: difficultyElem?.innerText.replace(levelElem?.innerText, '').trim() || '', + level: levelElem ? parseInt(levelElem.innerText.trim()) : null, + score: scoreNum, + scorePercent, + lamp: lampElem?.innerText.trim() || '', + lifeLeft: parseInt(lifeLeft) || null, + timestamp, // Unix ms + judgements: { justReflec, just, great, good, miss } + }); + }); + + return scores; + } + +})(); -- cgit v1.2.3