aboutsummaryrefslogtreecommitdiffstats
path: root/scripts/taiko/donder-hiroba
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 /scripts/taiko/donder-hiroba
parent5aa04d60b1602dbb6166c5459a2f1c1792e634c0 (diff)
move donder hiroba script to submodule
Diffstat (limited to 'scripts/taiko/donder-hiroba')
-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
4 files changed, 0 insertions, 452 deletions
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)
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage