aboutsummaryrefslogtreecommitdiffstats
path: root/ddr
diff options
context:
space:
mode:
authorPinapelz <yukais@pinapelz.com>2025-05-25 02:09:49 -0700
committerPinapelz <yukais@pinapelz.com>2025-05-25 02:13:16 -0700
commit17432220723e110240d9fa81590a1cf380259d82 (patch)
treeadeeff3ea8287a0d9776dd5d8c37536a36e3a1a1 /ddr
parent3c36b9b06bcd5f808db176c003a8d4bde9f95d13 (diff)
ddr_eamuse: ddr world to tachi
Diffstat (limited to 'ddr')
-rw-r--r--ddr/eamuse/README.md18
-rw-r--r--ddr/eamuse/ddr_eamuse_to_tachi.py166
2 files changed, 184 insertions, 0 deletions
diff --git a/ddr/eamuse/README.md b/ddr/eamuse/README.md
new file mode 100644
index 0000000..a636e04
--- /dev/null
+++ b/ddr/eamuse/README.md
@@ -0,0 +1,18 @@
+Converts your e-amusement DDR scores to a Tachi Batch-Manual JSON. Due to how e-amusement stores scores, only your BEST score can be derived from each chart.
+
+> [!NOTE]
+> You must be subscribed to e-amusement Basic Course to use this
+
+> [How to get Cookies?](../../common_docs/how_to_get_cookie_header.md)
+>
+> Get the cookies from this page: https://p.eagate.573.jp/game/ddr/ddrworld/playdata/music_data_single.html
+
+
+# Arguments
+| Argument | Short | Description | Default |
+|:---------------:|:-------:|:---------------------------------------------------------------------------------------------------------------------------:|:------------------------------:|
+| `--service` | `-s` | Service description to be shown on Tachi (Note for where this score came from) | `e-amusement DDR PB Import` |
+| `--cookies` | `-c` | Header string of e-amusement page cookies. See this script's README.md | — |
+| `--playstyle` | `-p` | Playstyle. Must be either `'SP'` or `'DP'`. | `SP` |
+| `--game` | `-g` | Version of the game | `WORLD` |
+| `--output` | `-o` | Output filename | `ddr_pb_tachi.json` |
diff --git a/ddr/eamuse/ddr_eamuse_to_tachi.py b/ddr/eamuse/ddr_eamuse_to_tachi.py
new file mode 100644
index 0000000..0a0378f
--- /dev/null
+++ b/ddr/eamuse/ddr_eamuse_to_tachi.py
@@ -0,0 +1,166 @@
+from bs4 import BeautifulSoup
+from urllib.parse import urljoin
+import json
+import argparse
+import requests
+from datetime import datetime
+import pytz
+
+GAME_DATA = {
+ "WORLD": {
+ "SP_PAGES": 25,
+ "DP_PAGES": 25,
+ "MUSIC_DATA_PAGE": "https://p.eagate.573.jp/game/ddr/ddrworld/playdata/music_data_single.html",
+ "MUSIC_DETAIL_BASE": "https://p.eagate.573.jp/game/ddr/ddrworld/playdata/",
+ "DIFFICULTY_MAP" : {
+ 0: "BEGINNER", 1: "BASIC", 2: "DIFFICULT", 3: "EXPERT", 4: "CHALLENGE"
+ }
+ }
+}
+
+def get_site_data_with_cookie(url, cookie_header):
+ cookies = {}
+ for cookie in cookie_header.split(";"):
+ name, value = cookie.strip().split("=", 1)
+ cookies[name] = value
+ headers = {
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36",
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
+ "Accept-Language": "en-US,en;q=0.5",
+ "Connection": "keep-alive",
+ "Upgrade-Insecure-Requests": "1",
+ }
+ response = requests.get(url, cookies=cookies, headers=headers)
+ response.raise_for_status()
+ return response.text
+
+
+def parse_detailed_score_page(url: str, version: str, cookies: str):
+ site_data = get_site_data_with_cookie(url, cookies)
+ score_data = {"optional": {}}
+ soup = BeautifulSoup(site_data, 'html.parser')
+ difficulty_param = None
+ parsed_url = url.split('?')
+ if len(parsed_url) > 1:
+ query_params = parsed_url[1].split('&')
+ for param in query_params:
+ key_value = param.split('=')
+ if len(key_value) == 2 and key_value[0] == 'difficulty':
+ difficulty_param = int(key_value[1])
+ break
+ score_data['difficulty'] = GAME_DATA[version]["DIFFICULTY_MAP"].get(int(difficulty_param), "UNKNOWN MAYBE ERROR. FIX SCRIPT")
+ score_data["matchType"] = "songTitle"
+ music_info_table = soup.find('table', id='music_info')
+ if music_info_table:
+ cells = music_info_table.find_all('td')
+ if len(cells) >= 2:
+ title_arist_data = cells[1].get_text(separator="<br/>").split("<br/>")
+ score_data['identifier'] = title_arist_data[0]
+ music_detail_table = soup.find('table', id='music_detail_table')
+ rows = music_detail_table.find_all('tr')
+ for row in rows:
+ headers = row.find_all('th')
+ data_cells = row.find_all('td')
+ for th, td in zip(headers, data_cells):
+ if 'ハイスコア' == th.text:
+ score_data['score'] = int(td.text.strip())
+ elif 'フレアランク' in th.text:
+ flare_rank = td.text.strip()
+ if flare_rank == "なし":
+ flare_rank = "0"
+ score_data["optional"]["flare"] = flare_rank
+ elif '最大コンボ数' in th.text:
+ score_data["optional"]["maxCombo"] = int(td.text.strip())
+ elif '最終プレー時間' in th.text:
+ time_played = td.text.strip()
+ naive_datetime = datetime.strptime(time_played, "%Y-%m-%d %H:%M:%S")
+ jst_timezone = pytz.timezone("Asia/Tokyo")
+ localized_datetime = jst_timezone.localize(naive_datetime)
+ score_data["timeAchieved"] = int(localized_datetime.timestamp() * 1000)
+ elif 'ハイスコア時のランク' in th.text:
+ if td.text.strip() == "E":
+ score_data["lamp"] = "FAILED"
+ else:
+ score_data["lamp"] = "CLEAR"
+ clear_detail_table = soup.find('table', id='clear_detail_table')
+ rows = clear_detail_table.find_all('tr')
+ for row in rows:
+ headers = row.find_all('th')
+ data_cells = row.find_all('td')
+ for th, td in zip(headers, data_cells):
+ if 'グッドフルコンボ' in th.text:
+ if int(td.text.strip()) != 0:
+ score_data["lamp"] = "FULL COMBO"
+ elif 'グレートフルコンボ' in th.text:
+ if int(td.text.strip()) != 0:
+ score_data["lamp"] = "GREAT FULL COMBO"
+ elif 'パーフェクトフルコンボ' in th.text:
+ if int(td.text.strip()) != 0:
+ score_data["lamp"] = "PERFECT FULL COMBO"
+ elif 'マーベラスフルコンボ' in th.text:
+ if int(td.text.strip()) != 0:
+ score_data["lamp"] = "MARVELOUS FULL COMBO"
+ elif 'LIFE4 クリア' in th.text and score_data["lamp"] == "CLEAR":
+ if int(td.text.strip()) != 0:
+ score_data["lamp"] = "LIFE4"
+ return score_data
+
+def convert_ddr_data_to_tachi_json(version: str, playstyle: str, service: str, cookies: str, output: str):
+ batch_manual = {
+ "meta": {
+ "game": "ddr",
+ "playtype": playstyle,
+ "service": service
+ },
+ "scores": []
+ }
+ to_inspect_urls = []
+ for page_num in range(GAME_DATA[version][f"{playstyle}_PAGES"]):
+ found_charts = 0
+ print(f"Checking Page {page_num+1}/{GAME_DATA[version][f'{playstyle}_PAGES']} for scores", end="")
+ url = f"{GAME_DATA[version]['MUSIC_DATA_PAGE']}?offset={page_num}"
+ site_data = get_site_data_with_cookie(url, cookies)
+ soup = BeautifulSoup(site_data, 'html.parser')
+ rows = soup.find_all('tr', class_='data')
+ for row in rows:
+ cells = row.find_all('td', class_='rank')
+ for cell in cells:
+ score_div = cell.find('div', class_='data_score')
+ if not score_div or score_div.text.strip() == '---':
+ continue
+ link = cell.find('a', class_='music_info')
+ if link and link.has_attr('href'):
+ to_inspect_urls.append(urljoin(GAME_DATA[version]["MUSIC_DETAIL_BASE"], link['href']))
+ found_charts += 1
+ print(f" -> Found {found_charts} charts with scores!")
+ num_urls = len(to_inspect_urls)
+ progress = 0
+ for url in to_inspect_urls:
+ print(f"\rPulling Individual Scores ---> Progress: {progress + 1}/{num_urls}", end="")
+ score = parse_detailed_score_page(url, version=version, cookies=cookies)
+ batch_manual["scores"].append(score)
+ progress += 1
+ return batch_manual
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(
+ prog="ddr_eamuse_pb_to_tachi",
+ description="Converts e-amusement DDR personal best records to a Tachi compatibile JSON",
+ )
+ parser.add_argument("-s", "--service", help="Service description to be shown on Tachi (Note for where this score came from)", default="e-amusement DDR PB Import")
+ parser.add_argument("-c", "--cookies", help="Header string of e-amusement page cookies. See this script's README.md" )
+ parser.add_argument("-p", "--playstyle", help="Playstyle. Must be either 'single' or 'double'", default="SP")
+ parser.add_argument("-g", "--game", help="Version of the game", default="WORLD")
+ parser.add_argument("-o", "--output", help="Output filename", default="ddr_pb_tachi.json")
+ args = parser.parse_args()
+ assert args.playstyle == "SP" or args.playstyle == "DP"
+ assert args.game in ["WORLD"]
+try:
+ output_json = convert_ddr_data_to_tachi_json(args.game, args.playstyle, args.service, args.cookies, args.output)
+ with open(args.output, "w", encoding="utf-8") as json_file:
+ json.dump(output_json, json_file, ensure_ascii=False, indent=4)
+
+ print("Conversion completed. JSON saved as " + args.output)
+except Exception as e:
+ print(f"Error: {str(e)}")
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage