aboutsummaryrefslogtreecommitdiffstats
path: root/mai2/aquadx/mai_aquadx_to_tachi.py
blob: 475d837ab8070aee06768e6c501df8eed1faa83f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import argparse
import json
import urllib.request

MAI2_AQUADX_JSON = "https://aquadx.net/d/mai2/00/all-music.json"

DIFFICULTY_MAPPING = {
    0: "DX Basic",
    1: "DX Advanced",
    2: "DX Expert",
    3: "DX Master",
    4: "DX Re:Master"
}

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"
}

def convert_chuni_aquadx_json_to_tachi_json(input_json: str, output_file: str, service: str, music_json: str):
    if music_json == "online":
        req = urllib.request.Request(MAI2_AQUADX_JSON, headers=headers)
        with urllib.request.urlopen(req) as response:
            music_json = json.load(response)
    else:
        with open(music_json, "r", encoding="utf-8") as file:
            music_json = json.load(file)

    with open(input_json, "r", encoding="utf-8") as f:
        raw_data = json.load(f)

    batch_manual = {
        "meta": {"game": "maimaidx", "playtype": "Single", "service": service},
        "scores": [],
    }

    processed_count = 0
    skipped_count = 0

    if "userPlaylogList" in raw_data:
        for entry in raw_data["userPlaylogList"]:
            level = entry.get("level", 0)

            processed_count += 1
            song_title = music_json[str(entry["musicId"])]["name"]

            raw_score_value = str(entry.get("achievement", 0))
            score_value = float(f"{raw_score_value[:2]}.{raw_score_value[2]}")
            is_clear = entry.get("isClear", False)
            is_full_combo = entry.get("isFullCombo", False)
            is_all_perfect = entry.get("isAllPerfect", False)
            lamp = "FAILED"
            if is_all_perfect:
                lamp = "ALL PERFECT"
            elif is_full_combo:
                lamp = "FULL COMBO"
            elif is_clear:
                lamp = "CLEAR"
            timestamp = entry.get("loginDate", None)
            combo = entry.get("maxCombo",0)
            fast = entry.get("fastCount", 0)
            slow = entry.get("lateCount", 0)

            pcrit = entry.get("tapCriticalPerfect", 0) + entry.get("holdCriticalPerfect", 0) + entry.get("slideCriticalPerfect", 0)  + entry.get("touchCriticalPerfect", 0) + entry.get("breakCriticalPerfect", 0)
            perfect = entry.get("tapPerfect", 0) + entry.get("holdPerfect", 0) + entry.get("slidePerfect", 0)  + entry.get("touchPerfect", 0) + entry.get("breakPerfect", 0)
            great = entry.get("tapGreat", 0) + entry.get("holdGreat", 0) + entry.get("slideGreat", 0)  + entry.get("touchGreat", 0) + entry.get("breakGreat", 0)
            good = entry.get("tapGood", 0) + entry.get("holdGood", 0) + entry.get("slideGood", 0)  + entry.get("touchGood", 0) + entry.get("breakGood", 0)
            miss = entry.get("tapMiss", 0) + entry.get("holdMiss", 0) + entry.get("slideMiss", 0)  + entry.get("touchMiss", 0) + entry.get("breakMiss", 0)

            score_entry = {
                "percent": score_value,
                "lamp": lamp,
                "matchType": "songTitle",
                "identifier": str(song_title),
                "difficulty": DIFFICULTY_MAPPING[level],
                "timeAchieved": timestamp * 1000 if timestamp else None,
                "judgements": {
                    "pcrit": pcrit,
                    "perfect": perfect,
                    "great": great,
                    "good": good,
                    "miss": miss
                },
                "optional": {
                    "maxCombo": combo,
                    "fast": fast,
                    "slow": slow

                },
            }

            batch_manual["scores"].append(score_entry)

    with open(output_file, "w", encoding="utf-8") as f:
        print("--- Processing Summary ---")
        print(f"Total scores processed: {processed_count}")
        print(f"Scores skipped (level 5 or invalid): {skipped_count}")
        print(f"Output saved to {output_file}")
        json.dump(batch_manual, f, indent=4, ensure_ascii=False)


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        prog="mai_aquadx_to_tachi.py",
        description="Converts AquaDX score data for maimai (DX) to Tachi compatible JSON",
        epilog="Dan and matchingClass not implemented. Please just use other methods for that kind of static info"
    )
    parser.add_argument("input_file", help="Path to the input JSON file exported from AquaDX")
    parser.add_argument(
        "-s",
        "--service",
        help="Service description to be shown on Tachi (Note for where this score came from)",
        default="AquaDX maimaidx Import",
    )
    parser.add_argument(
        "-o", "--output", help="Output filename", default="aquadx_mai_tachi.json"
    )
    parser.add_argument("--music", "--music-file", help="JSON file containing the mappings of song names to IDs. Check README for moe info", default="online")
    args = parser.parse_args()
    convert_chuni_aquadx_json_to_tachi_json(args.input_file, args.output, args.service, args.music)
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage