diff options
| author | Pinapelz <yukais@pinapelz.com> | 2025-12-17 13:40:45 -0800 |
|---|---|---|
| committer | Pinapelz <yukais@pinapelz.com> | 2025-12-17 13:40:45 -0800 |
| commit | 0c5cf111b3d59474825d07224598637c9d34c481 (patch) | |
| tree | 842d8e24eb75bea23df5375622ccc0bcd2672dcd | |
| parent | cc197c39c06eabcb3cb38fc7cca6b13792b137d5 (diff) | |
| -rw-r--r-- | frontend/src/pages/Import.tsx | 38 | ||||
| -rw-r--r-- | scripts/dancerush/flower/dancerush_flower_scraper.user.js | 152 |
2 files changed, 188 insertions, 2 deletions
diff --git a/frontend/src/pages/Import.tsx b/frontend/src/pages/Import.tsx index 65aabc8..8dc9eec 100644 --- a/frontend/src/pages/Import.tsx +++ b/frontend/src/pages/Import.tsx @@ -19,7 +19,7 @@ const EagleUserscriptModal = lazy(() => import("../components/modals/EagleUsersc const TaikoDonderHirobaModal = lazy(() => import("../components/modals/TaikoDonderHirobaModal")); const TaikoEGTSModal = lazy(() => import("../components/modals/TaikoEGTSModal")); -type ModalType = 'json' | 'dancerush' | 'dancearound' | 'divanet' | 'musicdiver' | 'nostalgiaFlower' | 'nostalgiaEagle' | 'reflecbeatFlower' | 'reflecbeatEagle' | 'taiko' | 'taikoEGTS'; +type ModalType = 'json' | 'dancerush' | 'dancerushFlower' | 'dancerushEagle' | 'dancearound' | 'divanet' | 'musicdiver' | 'nostalgiaFlower' | 'nostalgiaEagle' | 'reflecbeatFlower' | 'reflecbeatEagle' | 'taiko' | 'taikoEGTS'; const Import = () => { const { user, isLoading, logout } = useAuth(); @@ -144,6 +144,14 @@ const Import = () => { mainGameName="DANCERUSH" onClick={() => setOpenModal('dancerush')} /> + <FlowerUserscriptCard + mainGameName="DANCERUSH" + onClick={() => setOpenModal('dancerushFlower')} + /> + <EagleUserscriptCard + mainGameName="DANCERUSH" + onClick={() => setOpenModal('dancerushEagle')} + /> </> ); case "dancearound": @@ -353,6 +361,32 @@ const Import = () => { }]} /> )} + {openModal === 'dancerushFlower' && ( + <FlowerUserscriptModal + isOpen={true} + onClose={() => setOpenModal(null)} + mainGameName="DANCERUSH" + userPage="https://projectflower.eu" + importPage="https://projectflower.eu/game/dancerush/profile/7414" + scripts={[{ + name: "Flower Play History (Exports only the page you are on)", + url: "https://github.com/pinapelz/Mirage/raw/refs/heads/main/scripts/dancerush/flower/dancerush_flower_scraper.user.js" + }]} + /> + )} + {openModal === 'dancerushEagle' && ( + <EagleUserscriptModal + isOpen={true} + onClose={() => setOpenModal(null)} + mainGameName="DANCERUSH" + userPage="https://eagle.ac" + importPage="https://eagle.ac/game/dancerush/profile/7414" + scripts={[{ + name: "Eagle Play History (Exports only the page you are on)", + url: "https://github.com/pinapelz/Mirage/raw/refs/heads/main/scripts/dancerush/flower/dancerush_flower_scraper.user.js" + }]} + /> + )} {openModal === 'dancearound' && ( <EamusementUserscriptModal isOpen={true} @@ -405,7 +439,7 @@ const Import = () => { onClose={() => setOpenModal(null)} mainGameName="NOSTALGIA" userPage="https://eagle.ac" - importPage="https://eagle.ac/game/nostalgia/54827307" + importPage="https://eagle.ac/game/nostalgia/43711497" scripts={[{ name: "Eagle Play History (Exports only the page you are on)", url: "https://github.com/pinapelz/Mirage/raw/refs/heads/main/scripts/nostalgia/flower/nostalgia_flower_scraper.user.js" diff --git a/scripts/dancerush/flower/dancerush_flower_scraper.user.js b/scripts/dancerush/flower/dancerush_flower_scraper.user.js new file mode 100644 index 0000000..97c6083 --- /dev/null +++ b/scripts/dancerush/flower/dancerush_flower_scraper.user.js @@ -0,0 +1,152 @@ +// ==UserScript== +// @name DANCERUSH STARDOM (Flower/Eagle) Play History Scraper + Artist +// @version 1.1 +// @description Export DANCERUSH scores including full judgements, timestamps, and fetched Artist names as JSON +// @match https://eagle.ac/game/dancerush/profile/* +// @downloadUrl https://github.com/pinapelz/Mirage/raw/refs/heads/main/scripts/dancerush/flower/dancerush_flower_scraper.user.js +// @grant none +// ==/UserScript== + +(function() { + 'use strict'; + + const artistCache = {}; + + const btn = document.createElement('button'); + btn.textContent = 'Mirage Export Dancerush 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 = '#00bcd4'; + btn.style.color = 'white'; + btn.style.border = 'none'; + btn.style.borderRadius = '4px'; + btn.style.cursor = 'pointer'; + btn.style.fontWeight = 'bold'; + document.body.appendChild(btn); + + btn.addEventListener('click', async () => { + const originalText = btn.textContent; + btn.disabled = true; + btn.style.background = '#9e9e9e'; + btn.textContent = 'Processing (Fetching Artists)...'; + + try { + const scores = await parseDancerushLog(); + const blob = new Blob([JSON.stringify(scores, null, 2)], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `dancerush_scores_${Date.now()}.json`; + a.click(); + URL.revokeObjectURL(url); + } catch (error) { + console.error("Scraping failed:", error); + alert("An error occurred while fetching song details. Check console."); + } finally { + btn.textContent = originalText; + btn.disabled = false; + btn.style.background = '#00bcd4'; + } + }); + + async function fetchArtistName(url) { + if (!url) return "Unknown Artist"; + if (artistCache[url]) return artistCache[url]; + + try { + const response = await fetch(url); + const html = await response.text(); + const parser = new DOMParser(); + const doc = parser.parseFromString(html, 'text/html'); + + // Find the table row that contains "Song Artist" + const rows = Array.from(doc.querySelectorAll('table.table tr')); + const artistRow = rows.find(r => r.querySelector('th')?.innerText.includes('Song Artist')); + const artistName = artistRow?.querySelector('td b')?.innerText.trim() || "Unknown Artist"; + + artistCache[url] = artistName; + return artistName; + } catch (e) { + console.warn(`Could not fetch artist for ${url}`, e); + return "Unknown Artist"; + } + } + + async function parseDancerushLog() { + const rows = document.querySelectorAll('table.table tbody tr.accordion-toggle'); + const scores = []; + for (let index = 0; index < rows.length; index++) { + const row = rows[index]; + const titleElem = row.querySelector('td.text-right.text-middle strong'); + const diffElem = row.querySelector('td.success div strong'); + const scoreElem = row.querySelector('td:nth-child(4) strong'); + const lampElem = row.querySelector('td:nth-child(5) strong'); + const timestampElem = row.querySelector('td.text-right small'); + const songLink = row.querySelector('td.text-right.text-middle a')?.href || ""; + const artistName = await fetchArtistName(songLink); + + const targetId = row.getAttribute('data-target')?.replace('#', ''); + const detailContainer = document.getElementById(targetId); + + let judgements = { perfect: 0, great: 0, good: 0, bad: 0 }; + let maxCombo = 0; + + if (detailContainer) { + const statDivs = detailContainer.querySelectorAll('div.col-sm-1'); + statDivs.forEach(div => { + const label = div.querySelector('strong')?.innerText.trim(); + const value = parseInt(div.innerHTML.split('<br>')[1]) || 0; + + if (label === 'Perfect') judgements.perfect = value; + if (label === 'Great') judgements.great = value; + if (label === 'Good') judgements.good = value; + if (label === 'Bad') judgements.bad = value; + if (label === 'Combo') maxCombo = value; + }); + } + + const diffText = diffElem ? diffElem.innerText.trim().split(/\s+/) : ["UNKNOWN", "0"]; + const diffLamp = diffText[0]; + const difficultyLevel = parseInt(diffText[1]); + const rawScorePercent = parseFloat(scoreElem?.innerText.trim()) || 0; + const scoreInt = Math.round(rawScorePercent * 1000); + + let lampId = 4; + if (lampElem?.innerText.includes("Full Combo")) lampId = 5; + if (lampElem?.innerText.includes("Excellent")) lampId = 6; + if (lampElem?.innerText.includes("Failed")) lampId = 1; + + let timestampMs = Date.now(); + if (timestampElem) { + timestampMs = new Date(timestampElem.innerText.trim()).getTime(); + } + + scores.push({ + timestamp: timestampMs, + lamp: lampId, + score: scoreInt, + title: titleElem?.innerText.trim() || "Unknown", + artist: artistName, + optional: { + maxCombo: maxCombo + }, + diff_lamp: diffLamp, + timestamp: timestampMs, + difficulty: difficultyLevel, + judgements: judgements, + num_players: 1 + }); + } + return { + "meta": { + "game": "dancerush", + "playtype": "Single", + "service": "FLOWER/EAGLE Dancerush Export", + }, + "scores": scores + }; + } +})(); |
