"""Prettifiers for genshinstats api returns. Fixes the huge problem of outdated field names in the api, that were leftover from during development """ import re from datetime import datetime character_icons = { "PlayerGirl": "Traveler", "PlayerBoy": "Traveler", "Ambor": "Amber", "Qin": "Jean", "Hutao": "Hu Tao", "Feiyan": "Yanfei", "Kazuha": "Kadehara Kazuha", "Sara": "Kujou Sara", "Shougun": "Raiden Shogun", "Tohma": "Thoma", } def _recognize_character_icon(url: str) -> str: """Recognizes a character's icon url and returns its name.""" exp = r"game_record/genshin/character_.*_(\w+)(?:@\dx)?.png" match = re.search(exp, url) if match is None: raise ValueError(f"{url!r} is not a character icon or image url") character = match.group(1) return character_icons.get(character) or character def prettify_stats(data): s = data["stats"] h = data["homes"][0] if data["homes"] else None return { "stats": { "achievements": s["achievement_number"], "active_days": s["active_day_number"], "characters": s["avatar_number"], "spiral_abyss": s["spiral_abyss"], "anemoculi": s["anemoculus_number"], "geoculi": s["geoculus_number"], "electroculi": s["electroculus_number"], "common_chests": s["common_chest_number"], "exquisite_chests": s["exquisite_chest_number"], "precious_chests": s["precious_chest_number"], "luxurious_chests": s["luxurious_chest_number"], "unlocked_waypoints": s["way_point_number"], "unlocked_domains": s["domain_number"], }, "characters": [ { "name": i["name"], "rarity": i["rarity"] if i["rarity"] < 100 else i["rarity"] - 100, # aloy has 105 stars "element": i["element"], "level": i["level"], "friendship": i["fetter"], "icon": i["image"], "id": i["id"], } for i in data["avatars"] ], "teapot": { # only unique data between realms are names and icons "realms": [{"name": s["name"], "icon": s["icon"]} for s in data["homes"]], "level": h["level"], "comfort": h["comfort_num"], "comfort_name": h["comfort_level_name"], "comfort_icon": h["comfort_level_icon"], "items": h["item_num"], "visitors": h["visit_num"], # currently not in use } if h else None, "explorations": [ { "name": i["name"], "explored": round(i["exploration_percentage"] / 10, 1), "type": i["type"], "level": i["level"], "icon": i["icon"], "offerings": i["offerings"], } for i in data["world_explorations"] ], } def prettify_characters(data): return [ { "name": i["name"], "rarity": i["rarity"] if i["rarity"] < 100 else i["rarity"] - 100, # aloy has 105 stars "element": i["element"], "level": i["level"], "friendship": i["fetter"], "constellation": sum(c["is_actived"] for c in i["constellations"]), "icon": i["icon"], "image": i["image"], "id": i["id"], "collab": i["rarity"] >= 100, **( {"traveler_name": "Aether" if "Boy" in i["icon"] else "Lumine"} if "Player" in i["icon"] else {} ), "weapon": { "name": i["weapon"]["name"], "rarity": i["weapon"]["rarity"], "type": i["weapon"]["type_name"], "level": i["weapon"]["level"], "ascension": i["weapon"]["promote_level"], "refinement": i["weapon"]["affix_level"], "description": i["weapon"]["desc"], "icon": i["weapon"]["icon"], "id": i["weapon"]["id"], }, "artifacts": [ { "name": a["name"], "pos_name": { 1: "flower", 2: "feather", 3: "hourglass", 4: "goblet", 5: "crown", }[a["pos"]], "full_pos_name": a["pos_name"], "pos": a["pos"], "rarity": a["rarity"], "level": a["level"], "set": { "name": a["set"]["name"], "effect_type": ["none", "single", "classic"][len(a["set"]["affixes"])], "effects": [ { "pieces": e["activation_number"], "effect": e["effect"], } for e in a["set"]["affixes"] ], "set_id": int(re.search(r"UI_RelicIcon_(\d+)_\d+", a["icon"]).group(1)), # type: ignore "id": a["set"]["id"], }, "icon": a["icon"], "id": a["id"], } for a in i["reliquaries"] ], "constellations": [ { "name": c["name"], "effect": c["effect"], "is_activated": c["is_actived"], "index": c["pos"], "icon": c["icon"], "id": c["id"], } for c in i["constellations"] ], "outfits": [ {"name": c["name"], "icon": c["icon"], "id": c["id"]} for c in i["costumes"] ], } for i in data ] def prettify_abyss(data): fchars = lambda d: [ { "value": a["value"], "name": _recognize_character_icon(a["avatar_icon"]), "rarity": a["rarity"] if a["rarity"] < 100 else a["rarity"] - 100, # aloy has 105 stars "icon": a["avatar_icon"], "id": a["avatar_id"], } for a in d ] todate = lambda x: datetime.fromtimestamp(int(x)).strftime("%Y-%m-%d") totime = lambda x: datetime.fromtimestamp(int(x)).isoformat(" ") return { "season": data["schedule_id"], "season_start_time": todate(data["start_time"]), "season_end_time": todate(data["end_time"]), "stats": { "total_battles": data["total_battle_times"], "total_wins": data["total_win_times"], "max_floor": data["max_floor"], "total_stars": data["total_star"], }, "character_ranks": { "most_played": fchars(data["reveal_rank"]), "most_kills": fchars(data["defeat_rank"]), "strongest_strike": fchars(data["damage_rank"]), "most_damage_taken": fchars(data["take_damage_rank"]), "most_bursts_used": fchars(data["normal_skill_rank"]), "most_skills_used": fchars(data["energy_skill_rank"]), }, "floors": [ { "floor": f["index"], "stars": f["star"], "max_stars": f["max_star"], "icon": f["icon"], "chambers": [ { "chamber": l["index"], "stars": l["star"], "max_stars": l["max_star"], "has_halves": len(l["battles"]) == 2, "battles": [ { "half": b["index"], "timestamp": totime(b["timestamp"]), "characters": [ { "name": _recognize_character_icon(c["icon"]), "rarity": c["rarity"] if c["rarity"] < 100 else c["rarity"] - 100, # aloy has 105 stars "level": c["level"], "icon": c["icon"], "id": c["id"], } for c in b["avatars"] ], } for b in l["battles"] ], } for l in f["levels"] ], } for f in data["floors"] ], } def prettify_activities(data): activities = { k: v if v.get("exists_data") else {"records": []} for activity in data["activities"] for k, v in activity.items() } return { "hyakunin": [ { "id": r["challenge_id"], "name": r["challenge_name"], "difficulty": r["difficulty"], "medal_icon": r["heraldry_icon"], "score": r["max_score"], "multiplier": r["score_multiple"], "lineups": [ { "characters": [ { "name": _recognize_character_icon(c["icon"]), "rarity": c["rarity"] if c["rarity"] < 100 else c["rarity"] - 100, # aloy has 105 stars "level": c["level"], "icon": c["icon"], "id": c["id"], "trial": c["is_trail_avatar"], } for c in l["avatars"] ], "skills": [ {"name": s["name"], "desc": s["desc"], "icon": s["icon"], "id": s["id"]} for s in l["skills"] ], } for l in r["lineups"] ], } for r in activities["sumo"]["records"] ], "labyrinth": None, } def prettify_notes(data): return { "resin": data["current_resin"], "until_resin_limit": data["resin_recovery_time"], "max_resin": data["max_resin"], "total_commissions": data["total_task_num"], "completed_commissions": data["finished_task_num"], "claimed_commission_reward": data["is_extra_task_reward_received"], "max_boss_discounts": data["resin_discount_num_limit"], "remaining_boss_discounts": data["remain_resin_discount_num"], "expeditions": [ { "icon": exp["avatar_side_icon"], "remaining_time": exp["remained_time"], "status": exp["status"], } for exp in data["expeditions"] ], "max_expeditions": data["max_expedition_num"], "realm_currency": data["current_home_coin"], "max_realm_currency": data["max_home_coin"], "until_realm_currency_limit": data["home_coin_recovery_time"], } def prettify_game_accounts(data): return [ { "uid": int(a["game_uid"]), "server": a["region_name"], "level": a["level"], "nickname": a["nickname"], # idk what these are for: "biz": a["game_biz"], "is_chosen": a["is_chosen"], "is_official": a["is_official"], } for a in data ] def prettify_wish_history(data, banner_name=None): return [ { "type": i["item_type"], "name": i["name"], "rarity": int(i["rank_type"]), "time": i["time"], "id": int(i["id"]), "banner": banner_name, "banner_type": int(i["gacha_type"]), "uid": int(i["uid"]), } for i in data ] def prettify_gacha_items(data): return [ { "name": i["name"], "type": i["item_type"], "rarity": int(i["rank_type"]), "id": 10000000 + int(i["item_id"]) - 1000 if len(i["item_id"]) == 4 else int(i["item_id"]), } for i in data ] def prettify_banner_details(data): per = lambda p: None if p == "0%" else float(p[:-1].replace(",", ".")) fprobs = ( lambda l: [ { "type": i["item_type"], "name": i["item_name"], "rarity": int(i["rank"]), "is_up": bool(i["is_up"]), "order_value": i["order_value"], } for i in l ] if l else [] ) fitems = ( lambda l: [ { "type": i["item_type"], "name": i["item_name"], "element": { "风": "Anemo", "火": "Pyro", "水": "Hydro", "雷": "Electro", "冰": "Cryo", "岩": "Geo", "?": "Dendro", "": None, }[i["item_attr"]], "icon": i["item_img"], } for i in l ] if l else [] ) return { "banner_type_name": { 100: "Novice Wishes", 200: "Permanent Wish", 301: "Character Event Wish", 302: "Weapon Event Wish", }[int(data["gacha_type"])], "banner_type": int(data["gacha_type"]), "banner": re.sub(r"<.*?>", "", data["title"]).strip(), "title": data["title"], "content": data["content"], "date_range": data["date_range"], "r5_up_prob": per(data["r5_up_prob"]), # probability for rate-up 5* "r4_up_prob": per(data["r4_up_prob"]), # probability for rate-up 4* "r5_prob": per(data["r5_prob"]), # probability for 5* "r4_prob": per(data["r4_prob"]), # probability for 4* "r3_prob": per(data["r3_prob"]), # probability for 3* "r5_guarantee_prob": per(data["r5_baodi_prob"]), # probability for 5* incl. guarantee "r4_guarantee_prob": per(data["r4_baodi_prob"]), # probability for 4* incl. guarantee "r3_guarantee_prob": per(data["r3_baodi_prob"]), # probability for 3* incl. guarantee "r5_up_items": fitems( data["r5_up_items"] ), # list of 5* rate-up items that you can get from banner "r4_up_items": fitems( data["r4_up_items"] ), # list of 4* rate-up items that you can get from banner "r5_items": fprobs(data["r5_prob_list"]), # list 5* of items that you can get from banner "r4_items": fprobs(data["r4_prob_list"]), # list 4* of items that you can get from banner "r3_items": fprobs(data["r3_prob_list"]), # list 3* of items that you can get from banner "items": fprobs( sorted( data["r5_prob_list"] + data["r4_prob_list"] + data["r3_prob_list"], key=lambda x: x["order_value"], ) ), } def prettify_trans(data, reasons={}): if data and "name" in data[0]: # transaction item return [ { "time": i["time"], "name": i["name"], "rarity": int(i["rank"]), "amount": int(i["add_num"]), "reason": reasons.get(int(i["reason"]), ""), "reason_id": int(i["reason"]), "uid": int(i["uid"]), "id": int(i["id"]), } for i in data ] else: # transaction return [ { "time": i["time"], "amount": int(i["add_num"]), "reason": reasons.get(int(i["reason"]), ""), "reason_id": int(i["reason"]), "uid": int(i["uid"]), "id": int(i["id"]), } for i in data ]