From 69a53da8aeafcbb7b0878813c12a1a258de45f6c Mon Sep 17 00:00:00 2001 From: tretrauit Date: Fri, 30 Jun 2023 20:55:30 +0700 Subject: [PATCH] feat: wrap all objects on /resource endpoint --- vollerei/common/api/resource.py | 202 +++++++++++++++++++++++++++++--- vollerei/hsr/launcher/api.py | 2 +- 2 files changed, 188 insertions(+), 16 deletions(-) diff --git a/vollerei/common/api/resource.py b/vollerei/common/api/resource.py index 45d352c..865c8bf 100644 --- a/vollerei/common/api/resource.py +++ b/vollerei/common/api/resource.py @@ -4,11 +4,35 @@ Class wrapper for API endpoint /resource class Segment: + """ + A segment of the game archive. + + Attributes: + path (str): Segment download path. + md5 (str): Segment md5 checksum. + package_size (int | None): Segment package size. + """ + path: str md5: str # str -> int and checked if int is 0 then None package_size: int | None + def __init__(self, path: str, md5: str, package_size: int | None) -> None: + self.path = path + self.md5 = md5 + self.package_size = package_size + + @staticmethod + def from_dict(data: dict) -> "Segment": + return Segment( + data["path"], + data["md5"], + int(data["package_size"]) + if data["package_size"] and data["package_size"] != "0" + else None, + ) + class VoicePack: """ @@ -17,7 +41,12 @@ class VoicePack: `name` maybe converted from `path` if the server returns empty string. Attributes: - TODO + language (str): Language of the voice pack. + name (str): Voice pack archive name. + path (str): Voice pack download path. + size (int): Voice pack size. + md5 (str): Voice pack md5 checksum. + package_size (int): Voice pack package size. """ language: str @@ -29,6 +58,33 @@ class VoicePack: # str -> int package_size: int + def __init__( + self, + language: str, + name: str, + path: str, + size: int, + md5: str, + package_size: int, + ) -> None: + self.language = language + self.name = name + self.path = path + self.size = size + self.md5 = md5 + self.package_size = package_size + + @staticmethod + def from_dict(data: dict) -> "VoicePack": + return VoicePack( + data["language"], + data["name"], + data["path"], + int(data["size"]), + data["md5"], + int(data["package_size"]), + ) + class Diff: """ @@ -49,6 +105,39 @@ class Diff: # str -> int package_size: int + def __init__( + self, + name: str, + version: str, + path: str, + size: int, + md5: str, + is_recommended_update: bool, + voice_packs: list[VoicePack], + package_size: int, + ) -> None: + self.name = name + self.version = version + self.path = path + self.size = size + self.md5 = md5 + self.is_recommended_update = is_recommended_update + self.voice_packs = voice_packs + self.package_size = package_size + + @staticmethod + def from_dict(data: dict) -> "Diff": + return Diff( + data["name"], + data["version"], + data["path"], + int(data["size"]), + data["md5"], + data["is_recommended_update"], + [VoicePack.from_dict(i) for i in data["voice_packs"]], + int(data["package_size"]), + ) + class Latest: """ @@ -58,13 +147,35 @@ class Latest: and if `path` is empty too then it'll convert the name from the first segment of `segments` list. + `path` maybe None if the server returns empty string, in that case + you'll have to download the game using `segments` list and merge them. + + `voice_packs` will be empty for Star Rail, they force you to download + in-game instead. + + `decompressed_path` is useful for repairing game files by only having + to re-download the corrupted files. + + `segments` is a list of game archive segments, you'll have to download + them and merge them together to get the full game archive. Not available + on Star Rail. + Attributes: - TODO + name (str): Game archive name. + version (str): Game version in the archive. + path (str | None): Game archive download path. + size (int): Game archive size in bytes. + md5 (str): Game archive MD5 checksum. + entry (str): Game entry file (e.g. GenshinImpact.exe). + voice_packs (list[VoicePack]): Game voice packs. + decompressed_path (str | None): Game archive decompressed path. + segments (list[Segment]): Game archive segments. + package_size (int): Game archive package size in bytes. """ name: str version: str - path: str + path: str | None # str -> int size: int md5: str @@ -110,14 +221,14 @@ class Latest: return Latest( data["name"], data["version"], - data["path"], - data["size"], + data["path"] if data["path"] != "" else None, + int(data["size"]), data["md5"], data["entry"], [VoicePack.from_dict(i) for i in data["voice_packs"]], - data["decompressed_path"], + data["decompressed_path"] if data["decompressed_path"] != "" else None, [Segment.from_dict(i) for i in data["segments"]], - data["package_size"], + int(data["package_size"]), ) @@ -149,27 +260,96 @@ class Plugin: # str -> int package_size: int + def __init__( + self, + name: str, + version: str | None, + path: str, + size: int, + md5: str, + entry: str | None, + package_size: int, + ) -> None: + self.name = name + self.version = version + self.path = path + self.size = size + self.md5 = md5 + self.entry = entry + self.package_size = package_size + + @staticmethod + def from_dict(data: dict) -> "Plugin": + return Plugin( + data["name"], + data["version"] if data["version"] != "" else None, + data["path"], + int(data["size"]), + data["md5"], + data["entry"] if data["entry"] != "" else None, + int(data["package_size"]), + ) + class LauncherPlugin: plugins: list[Plugin] # str -> int version: int + def __init__(self, plugins: list[Plugin], version: int) -> None: + self.plugins = plugins + self.version = version + + @staticmethod + def from_dict(data: dict) -> "LauncherPlugin": + return LauncherPlugin( + [Plugin.from_dict(i) for i in data["plugins"]], int(data["version"]) + ) + class DeprecatedPackage: name: str md5: str + def __init__(self, name: str, md5: str) -> None: + self.name = name + self.md5 = md5 + + @staticmethod + def from_dict(data: dict) -> "DeprecatedPackage": + return DeprecatedPackage(data["name"], data["md5"]) + class DeprecatedFile: path: str # str but checked for empty string md5: str | None + def __init__(self, path: str, md5: str | None) -> None: + self.path = path + self.md5 = md5 + + @staticmethod + def from_dict(data: dict) -> "DeprecatedFile": + return DeprecatedFile(data["path"], data["md5"] if data["md5"] != "" else None) + class Resource: """ Data class for /resource endpoint + + I'm still unclear about `force_update` and `sdk` attributes, so I'll + leave them as None for now. + + Attributes: + game (Game): Game resource information. + plugin (LauncherPlugin): Launcher plugin information. + web_url (str): Game official launcher web URL. + force_update (None): Not used by official launcher I guess? + pre_download_game (Game | None): Pre-download game resource information. + deprecated_packages (list[DeprecatedPackage]): Deprecated game packages. + sdk (None): Maybe for Bilibili version of Genshin? + deprecated_files (list[DeprecatedFile]): Deprecated game files. """ # I'm generous enough to convert the string into int @@ -198,14 +378,6 @@ class Resource: sdk: None, deprecated_files: list[DeprecatedFile], ) -> None: - # Fixups - game_latest_path = game.latest.path - if game.latest.name == "": - print("A") - if game_latest_path == "": - game.latest.name = game.latest.segments[0].path.split("/")[-1] - else: - game.latest.name = game_latest_path.split("/")[-1] self.game = game self.plugin = plugin self.web_url = web_url diff --git a/vollerei/hsr/launcher/api.py b/vollerei/hsr/launcher/api.py index 61185b1..1fa6a9a 100644 --- a/vollerei/hsr/launcher/api.py +++ b/vollerei/hsr/launcher/api.py @@ -10,7 +10,7 @@ def get_resource(channel: GameChannel = GameChannel.Overseas) -> Resource: Get game resource information from the launcher API. Args: - channel: Game channel to get the resource information from. (os, cn) + channel: Game channel to get the resource information from. Returns: Resource: Game resource information.