aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPinapelz <yukais@pinapelz.com>2025-11-11 18:15:18 -0800
committerPinapelz <yukais@pinapelz.com>2025-11-11 18:15:18 -0800
commita40442d9b71893bfbcff59b653a5513611495ff9 (patch)
tree76835d6febc4690660f12eab1f84381c2a812870
parent5aa04d60b1602dbb6166c5459a2f1c1792e634c0 (diff)
move donder hiroba script to submodule
-rw-r--r--.gitmodules3
-rw-r--r--frontend/src/components/modals/TaikoDonderHirobaModal.tsx99
-rw-r--r--scripts/taiko/donder-hiroba/.gitignore218
-rw-r--r--scripts/taiko/donder-hiroba/README.md0
-rw-r--r--scripts/taiko/donder-hiroba/requirements.txt2
-rw-r--r--scripts/taiko/donder-hiroba/taiko_donder_hiroba_export.py232
m---------scripts/taiko/donderhiroba0
7 files changed, 102 insertions, 452 deletions
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..e65d2f4
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "scripts/taiko/donderhiroba"]
+ path = scripts/taiko/donderhiroba
+ url = https://github.com/pinapelz/donder-hiroba-to-mirage-import
diff --git a/frontend/src/components/modals/TaikoDonderHirobaModal.tsx b/frontend/src/components/modals/TaikoDonderHirobaModal.tsx
new file mode 100644
index 0000000..c8f46c8
--- /dev/null
+++ b/frontend/src/components/modals/TaikoDonderHirobaModal.tsx
@@ -0,0 +1,99 @@
+import type { SupportedGame } from "../../types/game";
+
+interface DonerHirobaModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+ game: SupportedGame | undefined;
+ renderAsCard?: () => void;
+}
+
+const TaikoDonderHirobaModal = ({
+ isOpen,
+ onClose,
+ game,
+ renderAsCard,
+}: DonerHirobaModalProps) => {
+ if (renderAsCard) {
+ return (
+ <div className="bg-slate-800 rounded-lg border border-slate-700 p-6 hover:border-violet-500 transition-colors">
+ <div className="w-12 h-12 bg-blue-600/20 rounded-lg flex items-center justify-center mb-4">
+ <svg
+ className="w-6 h-6 text-blue-400"
+ fill="none"
+ stroke="currentColor"
+ viewBox="0 0 24 24"
+ >
+ <path
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ strokeWidth={2}
+ d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
+ />
+ </svg>
+ </div>
+ <h4 className="text-white font-semibold mb-2">Donder Hiroba Import</h4>
+ <p className="text-slate-400 text-sm mb-4">
+ Import Play History from Donder Hiroba
+ </p>
+ <button
+ onClick={renderAsCard}
+ className="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-3 sm:px-4 rounded-md text-sm sm:text-base font-medium transition-colors"
+ >
+ Export MyPage
+ </button>
+ </div>
+ );
+ }
+
+ if (!isOpen) return null;
+
+ const handleClose = () => {
+ onClose();
+ };
+ if (game === undefined) {
+ return "Sorry, due to some extreme error the game you're looking for doesn't seem to exist...";
+ }
+ return (
+ <div className="fixed inset-0 z-50 overflow-y-auto">
+ {/* Backdrop */}
+ <div
+ className="fixed inset-0 bg-black/50 backdrop-blur-sm transition-opacity"
+ onClick={handleClose}
+ />
+
+ {/* Modal */}
+ <div className="flex min-h-full items-center justify-center p-4">
+ <div className="relative bg-slate-900 rounded-lg border border-slate-700 w-full max-w-xl p-6 shadow-xl">
+ {/* Header */}
+ <div className="mb-6">
+ <h3 className="text-xl font-bold text-white mb-2">
+ Import {game.formattedName} Data (Donder Hiroba)
+ </h3>
+ <p className="text-slate-400 text-sm">
+ Exporting from Donder Hiroba requires slightly more setup than
+ other methods. Follow the instructions{" "}
+ <a
+ href="https://github.com/pinapelz/donder-hiroba-to-mirage-import/blob/main/README.md"
+ className="text-blue-500 hover:underline"
+ >
+ here
+ </a>
+ .
+ </p>
+ </div>
+ {/* Actions */}
+ <div className="flex justify-center">
+ <button
+ onClick={handleClose}
+ className="px-6 py-2 bg-violet-600 hover:bg-violet-700 text-white rounded-md font-medium transition-colors"
+ >
+ Got it
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+};
+
+export default TaikoDonderHirobaModal;
diff --git a/scripts/taiko/donder-hiroba/.gitignore b/scripts/taiko/donder-hiroba/.gitignore
deleted file mode 100644
index e9aae41..0000000
--- a/scripts/taiko/donder-hiroba/.gitignore
+++ /dev/null
@@ -1,218 +0,0 @@
-# Byte-compiled / optimized / DLL files
-__pycache__/
-*.py[codz]
-*$py.class
-
-# C extensions
-*.so
-
-# Distribution / packaging
-.Python
-build/
-develop-eggs/
-dist/
-downloads/
-eggs/
-.eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-wheels/
-share/python-wheels/
-*.egg-info/
-.installed.cfg
-*.egg
-MANIFEST
-
-# PyInstaller
-# Usually these files are written by a python script from a template
-# before PyInstaller builds the exe, so as to inject date/other infos into it.
-*.manifest
-*.spec
-
-# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
-
-# Unit test / coverage reports
-htmlcov/
-.tox/
-.nox/
-.coverage
-.coverage.*
-.cache
-nosetests.xml
-coverage.xml
-*.cover
-*.py.cover
-.hypothesis/
-.pytest_cache/
-cover/
-
-# Translations
-*.mo
-*.pot
-
-# Django stuff:
-*.log
-local_settings.py
-db.sqlite3
-db.sqlite3-journal
-
-# Flask stuff:
-instance/
-.webassets-cache
-
-# Scrapy stuff:
-.scrapy
-
-# Sphinx documentation
-docs/_build/
-
-# PyBuilder
-.pybuilder/
-target/
-
-# Jupyter Notebook
-.ipynb_checkpoints
-
-# IPython
-profile_default/
-ipython_config.py
-
-# pyenv
-# For a library or package, you might want to ignore these files since the code is
-# intended to run in multiple environments; otherwise, check them in:
-# .python-version
-
-# pipenv
-# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
-# However, in case of collaboration, if having platform-specific dependencies or dependencies
-# having no cross-platform support, pipenv may install dependencies that don't work, or not
-# install all needed dependencies.
-# Pipfile.lock
-
-# UV
-# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
-# This is especially recommended for binary packages to ensure reproducibility, and is more
-# commonly ignored for libraries.
-# uv.lock
-
-# poetry
-# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
-# This is especially recommended for binary packages to ensure reproducibility, and is more
-# commonly ignored for libraries.
-# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
-# poetry.lock
-# poetry.toml
-
-# pdm
-# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
-# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
-# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
-# pdm.lock
-# pdm.toml
-.pdm-python
-.pdm-build/
-
-# pixi
-# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
-# pixi.lock
-# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
-# in the .venv directory. It is recommended not to include this directory in version control.
-.pixi
-
-# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
-__pypackages__/
-
-# Celery stuff
-celerybeat-schedule
-celerybeat.pid
-
-# Redis
-*.rdb
-*.aof
-*.pid
-
-# RabbitMQ
-mnesia/
-rabbitmq/
-rabbitmq-data/
-
-# ActiveMQ
-activemq-data/
-
-# SageMath parsed files
-*.sage.py
-
-# Environments
-.env
-.envrc
-.venv
-env/
-venv/
-ENV/
-env.bak/
-venv.bak/
-
-# Spyder project settings
-.spyderproject
-.spyproject
-
-# Rope project settings
-.ropeproject
-
-# mkdocs documentation
-/site
-
-# mypy
-.mypy_cache/
-.dmypy.json
-dmypy.json
-
-# Pyre type checker
-.pyre/
-
-# pytype static type analyzer
-.pytype/
-
-# Cython debug symbols
-cython_debug/
-
-# PyCharm
-# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
-# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
-# and can be added to the global gitignore or merged into this file. For a more nuclear
-# option (not recommended) you can uncomment the following to ignore the entire idea folder.
-# .idea/
-
-# Abstra
-# Abstra is an AI-powered process automation framework.
-# Ignore directories containing user credentials, local state, and settings.
-# Learn more at https://abstra.io/docs
-.abstra/
-
-# Visual Studio Code
-# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
-# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
-# and can be added to the global gitignore or merged into this file. However, if you prefer,
-# you could uncomment the following to ignore the entire vscode folder
-# .vscode/
-
-# Ruff stuff:
-.ruff_cache/
-
-# PyPI configuration file
-.pypirc
-
-# Marimo
-marimo/_static/
-marimo/_lsp/
-__marimo__/
-
-# Streamlit
-.streamlit/secrets.toml
-taiko_charts.json
-mirage_donder_hiroba_export.json
diff --git a/scripts/taiko/donder-hiroba/README.md b/scripts/taiko/donder-hiroba/README.md
deleted file mode 100644
index e69de29..0000000
--- a/scripts/taiko/donder-hiroba/README.md
+++ /dev/null
diff --git a/scripts/taiko/donder-hiroba/requirements.txt b/scripts/taiko/donder-hiroba/requirements.txt
deleted file mode 100644
index adc36d4..0000000
--- a/scripts/taiko/donder-hiroba/requirements.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-beautifulsoup4==4.14.2
-Requests==2.32.5
diff --git a/scripts/taiko/donder-hiroba/taiko_donder_hiroba_export.py b/scripts/taiko/donder-hiroba/taiko_donder_hiroba_export.py
deleted file mode 100644
index dd32f3b..0000000
--- a/scripts/taiko/donder-hiroba/taiko_donder_hiroba_export.py
+++ /dev/null
@@ -1,232 +0,0 @@
-import requests
-from bs4 import BeautifulSoup
-import json
-import time
-import argparse
-import os
-
-
-SONG_CATEGORIES = ["pops", "kids", "anime", "vocaloid", "game", "variety", "classic", "namco"]
-SONG_LIST_BASE_URL = "https://taiko.namco-ch.net/taiko/en/songlist/"
-headers = {
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
-}
-PLAY_HISTORY_URL = "https://donderhiroba.jp/history_recent_score.php"
-DIFFICULTIES = ["support", "easy", "normal", "hard", "oni", "ura_oni"]
-
-DIFFICULTY_MAP = {
- "icon_course02_1_640.png": "EASY",
- "icon_course02_2_640.png": "NORMAL",
- "icon_course02_3_640.png": "HARD",
- "icon_course02_4_640.png": "ONI",
- "icon_course02_4_640.png": "URA_ONI"
-}
-
-CROWN_MAP = {
- "crown_02_640.png": "FULL COMBO",
- "crown_03_640.png": "CLEAR",
- "crown_04_640.png": "DONDERFUL COMBO",
-}
-
-LAMP_MAP = {
- "best_score_rank_2_640.png": "IKI 1",
- "best_score_rank_3_640.png": "IKI 2",
- "best_score_rank_4_640.png": "IKI 3",
- "best_score_rank_5_640.png": "MIYABI 1",
- "best_score_rank_6_640.png": "MIYABI 2",
- "best_score_rank_7_640.png": "MIYABI 3",
- "best_score_rank_8_640.png": "KIWAMI",
-}
-
-def load_chart_cache():
- with open("taiko_charts.json") as f:
- return dict(json.load(f))
-
-def build_taiko_chart_metadata():
- """
- Unfortnatly Donder Hiroba doesn't store any data about the level, need to fetch this elsewhere
- """
- chart_data = {}
- for category in SONG_CATEGORIES:
- url = f"{SONG_LIST_BASE_URL}/{category}.php"
- print(f"[DATA] Getting {category} category charts")
- resp = requests.get(url, headers=headers)
- soup = BeautifulSoup(resp.text, 'html.parser')
- table = soup.find("tbody")
- if table is None:
- raise Exception("Unable to fetch chart data for ", category)
- rows = table.find_all("tr")
- for row in rows:
- cols = row.find_all("td")
- if len(cols) < 6:
- continue
-
- curr_song = {}
- song_metadata = row.find_all("th")
- if not song_metadata:
- continue
-
- title_th = song_metadata[0]
- artist_tag = title_th.find("p")
- song_artist = artist_tag.get_text(strip=True) if artist_tag else ""
-
- for tag in title_th.find_all(["p", "span"]):
- tag.decompose()
- song_title = title_th.get_text(strip=True)
-
- for i in range(len(DIFFICULTIES)):
- if DIFFICULTIES[i] == "support":
- continue
- diff = str(cols[i].get_text())
- curr_song[DIFFICULTIES[i]] = None if diff == "-" else diff
-
- curr_song["artist"] = song_artist
- chart_data[song_title] = curr_song
-
- with open("taiko_charts.json", "w") as f:
- print("Writing charts to cache. Delete this file when new charts come out!")
- json.dump(chart_data, f)
- return chart_data
-
-def get_play_hist(token: str, chart_data):
- """
- Fetch and parse Donder Hiroba play history page.
- Extracts scores, difficulty, ranks, and performance breakdowns.
- Handles pagination by going through all pages until duplicate results are found.
- """
- all_results = []
- page = 1
- previous_page_titles = set()
-
- while True:
- page_url = f"{PLAY_HISTORY_URL}?page={page}" if page > 1 else PLAY_HISTORY_URL
- print(f"[INFO] Fetching page {page}...")
- play_hist_page = requests.get(page_url, cookies={"_token_v2": token}, headers=headers)
- soup = BeautifulSoup(play_hist_page.text, "html.parser")
- scores = soup.find_all(class_="scoreUser")
-
- if not scores:
- print(f"[INFO] No scores found on page {page}. Ending pagination.")
- break
-
- current_page_titles = set()
- page_results = []
-
- for s in scores:
- title_tag = s.find("h2")
- title = title_tag.text.strip() if title_tag else None
-
- total_score_tag = s.find("div", class_="scoreScore")
- total_score = total_score_tag.text.strip().replace("点", "") if total_score_tag else None
-
- # Skip unknown songs
- if not title or chart_data.get(title) is None:
- print(f"[WARN] {title} is unknown in chart_data. Skipping.")
- continue
-
- current_page_titles.add(title)
- difficulty = crown = lamp = None
- score_element = s.find("div", class_="playDataArea", attrs={"style": True})
- img_tags = score_element.find_all("img") if score_element else []
-
- for img in img_tags:
- src = img["src"].split("/")[-1]
- if src in DIFFICULTY_MAP:
- difficulty = DIFFICULTY_MAP[src]
- elif src in CROWN_MAP:
- crown = CROWN_MAP[src]
- elif src in LAMP_MAP:
- lamp = LAMP_MAP[src]
-
- judgements = {}
- combo = pound = None
-
- score_data_area = s.find("div", class_="scoreDataArea")
- if score_data_area:
- score_elements = score_data_area.find_all("div", class_="playDataArea", recursive=True)
- for el in score_elements:
- img = el.find("img", class_="score_name")
- val_tag = el.find("div", class_="playDataScore")
- if not img or not val_tag:
- continue
-
- src = img["src"].split("/")[-1]
- value = val_tag.get_text(strip=True).replace("回", "")
- if not value.isdigit():
- continue
- value = int(value)
-
- if "score_name_good" in src:
- judgements["good"] = value
- elif "score_name_ok" in src:
- judgements["ok"] = value
- elif "score_name_ng" in src:
- judgements["bad"] = value
- elif "score_name_combo" in src:
- combo = value
- elif "score_name_pound" in src:
- pound = value
-
- result_entry = {
- "title": title,
- "timestamp": 0,
- "artist": chart_data[title]["artist"],
- "difficulty": difficulty,
- "level": int(chart_data[title].get(difficulty.lower(), 0)) if difficulty else None,
- "crown_rank": crown,
- "score_rank": lamp,
- "score": int(total_score) if total_score and total_score.isdigit() else total_score,
- "judgements": judgements,
- "optional": {
- "combo": combo,
- "pound": pound
- }
- }
- page_results.append(result_entry)
- if page > 1 and current_page_titles.issubset(previous_page_titles):
- print(f"[INFO] Page {page} contains duplicate results. Stopping pagination.")
- break
-
- all_results.extend(page_results)
- print(f"[INFO] Page {page} processed: {len(page_results)} scores found")
-
- previous_page_titles.update(current_page_titles)
- page += 1
-
- print(f"[INFO] Total scores collected: {len(all_results)} across {page - 1} pages")
-
- return {
- "meta": {
- "game": "taiko",
- "playtype": "Single",
- "service": "Donder Hiroba Export"
- },
- "scores": all_results,
- }
-
-
-if __name__ == "__main__":
- print("[ALERT!] Please first refresh your scores on Donder Hiroba so that it has the latest info. Visit: https://donderhiroba.jp/score_list.php and click on the top right\n\n")
- print("!Your token will change after doing this!")
- parser = argparse.ArgumentParser(
- prog="taiko_donder_hiroba_export.py",
- description="Exports Taiko no Tatsujin scores from Donder Hiroba into a Mirage compatible JSON",
- )
- parser.add_argument("-t", "--token", help="Donder Hiroba _token_v2. See README for instructions on how to get this!", required=True)
- args = parser.parse_args()
- chart_data = {}
- if os.path.exists("taiko_charts.json"):
- file_time = os.path.getmtime("taiko_charts.json")
- current_time = time.time()
- if current_time - file_time > 7 * 24 * 60 * 60:
- print("Chart cache is older than 1 week, regenerating...")
- chart_data = build_taiko_chart_metadata()
- else:
- print("Using cached chart data")
- chart_data = load_chart_cache()
- else:
- print("No chart cache found, generating...")
- chart_data = build_taiko_chart_metadata()
- score_data = get_play_hist(args.token, chart_data)
- with open("mirage_donder_hiroba_export.json", "w") as f:
- json.dump(score_data, f)
diff --git a/scripts/taiko/donderhiroba b/scripts/taiko/donderhiroba
new file mode 160000
+Subproject dc706e35d870a2b136d5849ce04b6f43934b42c
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage