diff --git a/tests/launcher_api_test.py b/tests/launcher_api_test.py index e898b79..0b369f8 100644 --- a/tests/launcher_api_test.py +++ b/tests/launcher_api_test.py @@ -1,6 +1,7 @@ import unittest import asyncio import worthless +from worthless.classes import launcher client = worthless.Launcher() @@ -15,13 +16,13 @@ class LauncherTest(unittest.TestCase): launcher_info = asyncio.run(client.get_launcher_info()) print("get_launcher_info test.") print("get_launcher_info: ", launcher_info) - self.assertIsInstance(launcher_info, dict) + self.assertIsInstance(launcher_info, launcher.Info) def test_get_launcher_full_info(self): launcher_info = asyncio.run(client.get_launcher_full_info()) print("get_launcher_full_info test.") print("get_launcher_full_info: ", launcher_info) - self.assertIsInstance(launcher_info, dict) + self.assertIsInstance(launcher_info, launcher.Info) def test_get_launcher_background_url(self): bg_url = asyncio.run(client.get_launcher_background_url()) diff --git a/worthless/__main__.py b/worthless/__main__.py index e4a0f2c..c3a60a9 100755 --- a/worthless/__main__.py +++ b/worthless/__main__.py @@ -1,6 +1,6 @@ #!/usr/bin/python3 -from app import main +from worthless import gui if __name__ == '__main__': - main() + gui.main() diff --git a/worthless/classes/launcher/__init__.py b/worthless/classes/launcher/__init__.py index e69de29..49b577a 100644 --- a/worthless/classes/launcher/__init__.py +++ b/worthless/classes/launcher/__init__.py @@ -0,0 +1,7 @@ +from worthless.classes.launcher import background, banner, iconbutton, iconotherlink, info, post +Background = background.Background +Banner = banner.Banner +IconButton = iconbutton.IconButton +IconOtherLink = iconotherlink.IconOtherLink +Info = info.Info +Post = post.Post diff --git a/worthless/classes/launcher/iconbutton.py b/worthless/classes/launcher/iconbutton.py index a29ecb8..83d7b36 100644 --- a/worthless/classes/launcher/iconbutton.py +++ b/worthless/classes/launcher/iconbutton.py @@ -1,4 +1,4 @@ -from worthless.classes.launcher.launchericonotherlink import IconOtherLink +from worthless.classes.launcher.iconotherlink import IconOtherLink class IconButton: diff --git a/worthless/classes/launcher/info.py b/worthless/classes/launcher/info.py index f2f3022..35b5901 100644 --- a/worthless/classes/launcher/info.py +++ b/worthless/classes/launcher/info.py @@ -1,3 +1,28 @@ -class LauncherInfo: - def __init__(self): - pass +from worthless.classes.launcher import background, banner, iconbutton, post +Background = background.Background +Banner = banner.Banner +IconButton = iconbutton.IconButton +Post = post.Post + + +class Info: + def __init__(self, lc_background: Background, lc_banner: list[Banner], icon: list[IconButton], lc_post: list[Post]): + self.background = lc_background + self.banner = lc_banner + self.icon = icon + self.post = lc_post + + @staticmethod + def from_dict(data): + bg = Background.from_dict(data["adv"]) + lc_banner = [] + for b in data["banner"]: + lc_banner.append(Banner.from_dict(b)) + lc_icon = [] + for i in data["icon"]: + lc_icon.append(IconButton.from_dict(i)) + lc_post = [] + for p in data["post"]: + lc_post.append(Post.from_dict(p)) + return Info(bg, lc_banner, lc_icon, lc_post) + diff --git a/worthless/classes/launcher/post.py b/worthless/classes/launcher/post.py index 037eab5..a6bc7fa 100644 --- a/worthless/classes/launcher/post.py +++ b/worthless/classes/launcher/post.py @@ -1,29 +1,38 @@ -class Banner: - """Contains a launcher banner information +class Post: + """Contains a launcher post information - Note that the banner name is in chinese, so you may not find this variable useful. - Also, the banner has a variable called `order`, you can use that to sort the banner - like the official launcher does. + The `type` variable can be POST_TYPE_ANNOUNCE, POST_TYPE_ACTIVITY and POST_TYPE_INFO + where announce is an announcement, activity is an activity/event and info is an information. + + The `show_time` variable is the time in DD/MM format when the post will be shown. + + Also, `tittle` is not my typo, and it's the server intention (probably there are clients + where their developers wrote `tittle` instead of `title`), and the post has a variable + called `order`, you can use that to sort the post like the official launcher does. Attributes: - - :class:`str` banner_id: The launcher banner id. - - :class:`str` name: The banner name. - - :class:`str` img: The banner image url. - - :class:`str` url: The banner target url. - - :class:`str` order: The banner order. - - :class:`str` name: The banner name. + - :class:`str` post_id: The launcher post id. + - :class:`str` type: The post type, as explained above. + - :class:`str` tittle: The post title. + - :class:`str` url: The post target url. + - :class:`str` show_time: The time when the post will be shown. + - :class:`str` order: The post order. + - :class:`str` title: The post title. - :class:`dict` raw: The banner raw information. """ - def __init__(self, banner_id, name, img, url, order, raw): - self.banner_id = banner_id - self.name = name - self.img = img + def __init__(self, post_id, post_type, tittle, url, show_time, order, title, raw): + self.post_id = post_id + self.type = post_type # Shadow built-in name `type` + self.tittle = tittle self.url = url + self.show_time = show_time self.order = order + self.title = title self.raw = raw @staticmethod - def from_dict(data) -> 'Banner': - """Creates a launcher banner from a dictionary.""" - return Banner(data["banner_id"], data["name"], data["img"], data["url"], data["order"], data) + def from_dict(data) -> 'Post': + """Creates a launcher post from a dictionary.""" + return Post(data["post_id"], data["type"], data["tittle"], data["url"], + data["show_time"], data["order"], data["title"], data) diff --git a/worthless/launcher.py b/worthless/launcher.py index 9d2e6ce..d5150cc 100644 --- a/worthless/launcher.py +++ b/worthless/launcher.py @@ -1,28 +1,42 @@ import aiohttp import locale from worthless import constants +from pathlib import Path +from worthless.classes import launcher class Launcher: - def __init__(self): + """ + Contains functions to get information from server and client like the official launcher. + """ + + def __init__(self, gamedir=Path.cwd()): + """Initialize the launcher API + + Args: + gamedir (Path): Path to the game directory. + """ self._api = constants.LAUNCHER_API_URL self._lang = self._get_system_language() + if isinstance(gamedir, str): + gamedir = Path(gamedir) + self._gamedir = gamedir.resolve() - # Workaround because miHoYo uses retcode for their API @staticmethod async def _get(url, **kwargs) -> dict: + # Workaround because miHoYo uses retcode for their API instead of HTTP status code async with aiohttp.ClientSession() as session: rsp = await session.get(url, **kwargs) rsp_json = await rsp.json() - # Why retcode seriously? They can just make the page not returning 200 status code. if rsp_json["retcode"] != 0: + # TODO: Add more information to the error message raise aiohttp.ClientResponseError(code=rsp_json["retcode"], message=rsp_json["message"], history=rsp.history, request_info=rsp.request_info) return rsp_json - async def _get_launcher_info(self, adv=True) -> dict: + async def _get_launcher_info(self, adv=True) -> launcher.Info: params = {"key": "gcStgarh", "filter_adv": str(adv).lower(), "launcher_id": "10", @@ -31,13 +45,17 @@ class Launcher: if rsp["data"]["adv"] is None: params["language"] = "en-us" rsp = await self._get(self._api + "/content", params=params) - return rsp + lc_info = launcher.Info.from_dict(rsp["data"]) + return lc_info @staticmethod def _get_system_language() -> str: + """Gets system language compatible with server parameters. + + Return: + System language with format xx-xx. """ - Get system language compatible with server parameters. - """ + try: lang = locale.getdefaultlocale()[0] lowercase_lang = lang.lower().replace("_", "-") @@ -45,38 +63,84 @@ class Launcher: except ValueError: return "en-us" + async def override_gamedir(self, gamedir: str) -> None: + """Overrides game directory with another directory. + + Args: + gamedir (str): New directory to override with. + """ + + self._gamedir = Path(gamedir).resolve() + async def override_language(self, language: str) -> None: + """Overrides system detected language with another language. + + Args: + language (str): Language to override with. """ - Override system detected language with another language. - :param language: The language to override with. - :return: None - """ + self._lang = language.lower().replace("_", "-") async def get_version_info(self) -> dict: + """Gets version info from the server. + + This function gets version info including audio pack and their download url from the server. + + Returns: + A dict containing version info from the server. + Raises: + aiohttp.ClientResponseError: An error occurred while fetching the information. + """ + rsp = await self._get(self._api + "/resource", params={"key": "gcStgarh", "launcher_id": "10"}) return rsp - async def get_launcher_info(self) -> dict: - """ - This function will get short launcher info from the server (only contains background image and FAQ) - :return: dict: Launcher info from the server. + async def get_launcher_info(self) -> launcher.Info: + """Gets short launcher info from the server + + This function only gets background image and the FAQ url from the server. + + Returns: + A dict containing short launcher info from the server. + Raises: + aiohttp.ClientResponseError: An error occurred while fetching the information. """ + return await self._get_launcher_info(adv=True) - async def get_launcher_full_info(self) -> dict: - """ - This function will get full launcher info from the server. - Since the server content is very long for a short explanation, you should see it manually. - :return: dict: Launcher info from the server. + async def get_launcher_full_info(self) -> launcher.Info: + """Gets full launcher info from the server. + + Returns: + A dict containing full launcher info from the server. + Raises: + aiohttp.ClientResponseError: An error occurred while fetching the information. """ + return await self._get_launcher_info(adv=False) async def get_launcher_background_url(self) -> str: + """Gets launcher background image url from the server. + + Returns: + Background image url. + Raises: + aiohttp.ClientResponseError: An error occurred while fetching the background image. """ - This function will get launcher background image from the server. - :return: str: Background image URL. - """ + rsp = await self.get_launcher_info() - return rsp["data"]["adv"]["background"] + return rsp.background.background + + async def get_system_game_info(self, table_handle, keys, require_all_keys): + # TODO: Implement + raise NotImplementedError("Not implemented yet.") + pass + + async def get_system_game_version(self) -> str: + """Gets the game version from the current system. + :return: str: System game version. + """ + + rsp = await self.get_version_info() + return rsp["data"]["system"]["game_version"] diff --git a/worthless/patcher.py b/worthless/patcher.py index e69de29..df7cc84 100644 --- a/worthless/patcher.py +++ b/worthless/patcher.py @@ -0,0 +1,39 @@ +import constants +from pathlib import Path + + +class Patcher: + def __init__(self, gamedir=Path.cwd()): + self._gamedir = gamedir + self._patch_url = constants.PATCH_GIT_URL + + def override_patch_url(self, url) -> None: + """ + Override the patch url. + :param url: Patch repository url, the url must be a valid git repository. + :return: None + """ + self._patch_url = url + + def download_patch(self) -> None: + """ + If `git` exists, this will clone the patch git url and save it to a temporary directory. + Else, this will download the patch from the patch url and save it to a temporary directory. (Not reliable) + :return: None + """ + pass + + def apply_patch(self, crash_fix=False) -> None: + """ + Patch the game (and optionally patch the login door crash fix if specified) + :param crash_fix: Whether to patch the login door crash fix or not + :return: None + """ + pass + + def revert_patch(self): + """ + Revert the patch (and revert the login door crash fix if patched) + :return: None + """ + pass