From 6886cbcd25cdd44a04eb878eef52b31e103764a1 Mon Sep 17 00:00:00 2001 From: Deviant <112615715+DEViantUA@users.noreply.github.com> Date: Tue, 30 Apr 2024 01:43:44 +0300 Subject: [PATCH] Add files via upload Signed-off-by: Deviant <112615715+DEViantUA@users.noreply.github.com> --- LICENSE | 68 +-- MANIFEST.in | 1 + README.md | 165 +++--- pyproject.toml | 23 +- requirements.txt | 11 + setup.py | 42 ++ starrailcard/__init__.py | 43 ++ starrailcard/client.py | 137 +++-- starrailcard/src/api/api.py | 74 ++- starrailcard/src/api/enka.py | 101 ++++ starrailcard/src/api/enka_parsed.py | 543 ++++++++++++++++++ starrailcard/src/api/error.py | 13 + starrailcard/src/data/avatar.json | 5 +- starrailcard/src/data/element.json | 18 +- starrailcard/src/data/keys.json | 2 +- starrailcard/src/data/paths.json | 20 +- starrailcard/src/data/relict_sets.json | 4 +- starrailcard/src/data/stats.json | 166 +++--- starrailcard/src/data/weapons.json | 5 +- .../src/generator/style_profile_phone.py | 15 +- .../src/generator/style_relict_score.py | 46 +- starrailcard/src/generator/style_ticket.py | 72 ++- starrailcard/src/model/StarRailCard.py | 10 +- starrailcard/src/model/api_mihomo.py | 117 +++- .../tools/calculator/src/assets/score.json | 211 ++++++- starrailcard/src/tools/calculator/stats.py | 16 +- starrailcard/src/tools/enums.py | 6 +- starrailcard/src/tools/http.py | 220 +++++-- starrailcard/src/tools/json_data.py | 3 +- starrailcard/src/tools/options.py | 28 +- starrailcard/src/tools/pill/image_controle.py | 107 +++- starrailcard/src/tools/ukrainization.py | 6 +- 32 files changed, 1811 insertions(+), 487 deletions(-) create mode 100644 MANIFEST.in create mode 100644 requirements.txt create mode 100644 setup.py create mode 100644 starrailcard/src/api/enka.py create mode 100644 starrailcard/src/api/enka_parsed.py create mode 100644 starrailcard/src/api/error.py diff --git a/LICENSE b/LICENSE index 8bf771a..adacdef 100644 --- a/LICENSE +++ b/LICENSE @@ -1,34 +1,34 @@ -MIT License with Additional Restrictions: - -Copyright (c) 2024 Deviant - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -1. Redistributions of the Software in source code or any form must retain the -above copyright notice, this list of conditions and the following additional -restrictions. - -2. Redistributions of the Software are not allowed to be modified or -distributed under a different name without prior written permission of the -copyright holder. - -3. Redistributions of the Software or derivative works based on the Software -are not allowed to be sold or used for commercial purposes without prior -written permission of the copyright holder. - -4. Redistributions of the Software or derivative works based on the Software -are not allowed to be copied, reproduced, or distributed without prior written -permission of the copyright holder. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +MIT License with Additional Restrictions: + +Copyright (c) 2024 Deviant + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +1. Redistributions of the Software in source code or any form must retain the +above copyright notice, this list of conditions and the following additional +restrictions. + +2. Redistributions of the Software are not allowed to be modified or +distributed under a different name without prior written permission of the +copyright holder. + +3. Redistributions of the Software or derivative works based on the Software +are not allowed to be sold or used for commercial purposes without prior +written permission of the copyright holder. + +4. Redistributions of the Software or derivative works based on the Software +are not allowed to be copied, reproduced, or distributed without prior written +permission of the copyright holder. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..9246aa6 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +recursive-include starrailcard/src * diff --git a/README.md b/README.md index 3441aba..225f88f 100644 --- a/README.md +++ b/README.md @@ -1,88 +1,77 @@ -

- Баннер -

- -[![PyPi](https://img.shields.io/pypi/v/starrailcard?label=PyPi)](https://pypi.org/project/starrailcard/) -[![Telegram](https://img.shields.io/badge/Telegram-gray)](https://t.me/enkacardchat) -[![License](https://img.shields.io/badge/License-MIT%20with%20Additional%20Restrictions-blue)](https://github.com/DEViantUA/StarRailCard/blob/main/LICENSE) -![GitHub size](https://img.shields.io/github/repo-size/DEViantUA/StarRailCard?label=GitHub%20size&branch=main) -![GitHub issues](https://img.shields.io/github/issues/DEViantUA/StarRailCard?label=Issues%20Count) -![GitHub pull requests](https://img.shields.io/github/issues-pr/DEViantUA/StarRailCard?label=Pull%20Count) -![GitHub stars](https://img.shields.io/github/stars/DEViantUA/StarRailCard?label=Stars%20Count) -![GitHub forks](https://img.shields.io/github/forks/DEViantUA/StarRailCard?label=Forks%20Count) - - - -## StarRailCard -
- - Description: - -
-Welcome to the world of StarRailCard – your magical guide to the universe of Honkai Star Rail! This Python module provides the ability to create captivating character cards based on player data from Honkai Star Rail, obtained through their unique user identifiers (UIDs). StarRailCard streamlines the process of generating personalized character assembly cards, relying on the information provided by players. - ---- - -* **Easy Installation:** Set up StarRailCard in just a few simple steps to start using it without any hassle. -* **Support for Other Programming Languages:** StarRailCard provides support for multiple programming languages, making it accessible to a wide range of developers. -* **Color Adaptation:** StarRailCard seamlessly adapts its color scheme to match the user's custom images, ensuring a harmonious blend between character cards and background images. -* **Flexible Configuration:** Customize StarRailCard according to your preferences with flexible configuration options, allowing you to tailor the generation process to your liking. -* **Multi-Language Support:** With support for all languages available in the game, including Ukrainian, StarRailCard can generate character cards in any language. -* **Personalized Character Cards:** Create character assembly cards based on specific player data to highlight their uniqueness and individuality. -* **Animation Support:** StarRailCard supports animated elements, adding extra vitality and dynamism to character cards. -* **Custom Fonts and Images:** Use custom fonts and character images to create character cards with a unique style. -* **Instant Data Update and Retrieval:** Get updated character and player profile data instantly, ensuring the information on cards is always up-to-date. -* **Integration with MiHoMo API Wrapper:** Seamlessly integrate StarRailCard with the MiHoMo API wrapper for quick access to game and character data. -
- -* [Documentation](https://github.com/DEViantUA/StarRailCard/wiki/Documentation) -* [View cards](https://github.com/DEViantUA/StarRailCard/wiki/View-cards) -* [API](https://github.com/DEViantUA/StarRailCard/wiki/StarRailCard-API) -* [Languages-Supported](https://github.com/DEViantUA/StarRailCard/wiki/Languages-Supported) -* [F.A.Q.](https://github.com/DEViantUA/StarRailCard/wiki/F.A.Q.) -* [PyPi](https://pypi.org/project/starrailcard/) -* [Telegram](https://t.me/enkacardchat) -* [Patreon](https://www.patreon.com/deviantapi) -* [GitHub](https://github.com/DEViantUA/StarRailCard) - -### Api: -> You can use the API to generate cards if you are using a different programming language. -[Documentation](https://github.com/DEViantUA/StarRailCard/wiki/StarRailCard-API) - -## Installation: -``` -pip install starrailcard -``` - -## Launch: -``` python -import asyncio -import starrailcard - -async def main(): - async with starrailcard.Card() as card: - data = await card.creat(700649319, style=2) - print(data) - -asyncio.run(main()) -``` - -
-Create a profile card. - -``` python -import asyncio -import starrailcard - -async def main(): - async with starrailcard.Card() as card: - data = await card.creat_profile(700649319) - print(data) - -asyncio.run(main()) -``` -
- -# Thank the author for the code: -* **Patreon**: https://www.patreon.com/deviantapi -* **Ko-Fi**: https://ko-fi.com/dezzso +

+ Баннер +

+ +## StarRailCard +
+ + Description: + +
+Welcome to the world of StarRailCard – your magical guide to the universe of Honkai Star Rail! This Python module provides the ability to create captivating character cards based on player data from Honkai Star Rail, obtained through their unique user identifiers (UIDs). StarRailCard streamlines the process of generating personalized character assembly cards, relying on the information provided by players. + +--- + +* **Easy Installation:** Set up StarRailCard in just a few simple steps to start using it without any hassle. +* **Support for Other Programming Languages:** StarRailCard provides support for multiple programming languages, making it accessible to a wide range of developers. +* **Color Adaptation:** StarRailCard seamlessly adapts its color scheme to match the user's custom images, ensuring a harmonious blend between character cards and background images. +* **Flexible Configuration:** Customize StarRailCard according to your preferences with flexible configuration options, allowing you to tailor the generation process to your liking. +* **Multi-Language Support:** With support for all languages available in the game, including Ukrainian, StarRailCard can generate character cards in any language. +* **Personalized Character Cards:** Create character assembly cards based on specific player data to highlight their uniqueness and individuality. +* **Animation Support:** StarRailCard supports animated elements, adding extra vitality and dynamism to character cards. +* **Custom Fonts and Images:** Use custom fonts and character images to create character cards with a unique style. +* **Instant Data Update and Retrieval:** Get updated character and player profile data instantly, ensuring the information on cards is always up-to-date. +* **Integration with MiHoMo API Wrapper:** Seamlessly integrate StarRailCard with the MiHoMo API wrapper for quick access to game and character data. +
+ +* [Documentation](https://github.com/DEViantUA/StarRailCard/wiki/Documentation) +* [View cards](https://github.com/DEViantUA/StarRailCard/wiki/View-cards) +* [API](https://github.com/DEViantUA/StarRailCard/wiki/StarRailCard-API) +* [Languages-Supported](https://github.com/DEViantUA/StarRailCard/wiki/Languages-Supported) +* [F.A.Q.](https://github.com/DEViantUA/StarRailCard/wiki/F.A.Q.) +* [PyPi](https://pypi.org/project/starrailcard/) +* [Telegram](https://t.me/enkacardchat) +* [Patreon](https://www.patreon.com/deviantapi) +* [GitHub](https://github.com/DEViantUA/StarRailCard) + +### Api: +> You can use the API to generate cards if you are using a different programming language. +[Documentation](https://github.com/DEViantUA/StarRailCard/wiki/StarRailCard-API) + +## Installation: +``` +pip install starrailcard +``` + +## Launch: +``` python +import asyncio +import starrailcard + +async def main(): + async with starrailcard.Card() as card: + data = await card.creat(700649319, style=2) + print(data) + +asyncio.run(main()) +``` + +
+Create a profile card. + +``` python +import asyncio +import starrailcard + +async def main(): + async with starrailcard.Card() as card: + data = await card.creat_profile(700649319) + print(data) + +asyncio.run(main()) +``` +
+ +# Thank the author for the code: +* **Patreon**: https://www.patreon.com/deviantapi +* **Ko-Fi**: https://ko-fi.com/dezzso diff --git a/pyproject.toml b/pyproject.toml index a7646dd..bbac628 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "starrailcard" -version = "2.0.0" +version = "2.1.5" description = "This Python module provides the ability to create captivating character cards based on player data from Honkai Star Rail, obtained through their unique user identifiers (UIDs). StarRailCard streamlines the process of generating personalized character assembly cards, relying on the information provided by players." authors = ["DEViantUA "] license = "MIT License with Additional Restrictions" @@ -13,17 +13,18 @@ homepage = "https://github.com/DEViantUA/StarRailCard" keywords = ["honkai", "cards", "generation", "honkaistarraill","raill", "starraill", "builds", "honkairail", "honkai"] [tool.poetry.dependencies] -Pillow = "^10.0.1" +Pillow = "*" python = "^3.9" -aiofiles = "^0.7.0" -aiohttp = "^3.8.1" -cachetools = "^5.3.1" -imageio = "^2.14.0" -moviepy = "^1.0.3" -more-itertools = "^8.9.0" -numpy = "^1.21.2" -pydantic = "^1.9.0" -beautifulsoup4 = "^4.12.3" +aiofiles = "*" +aiohttp = "*" +cachetools = "*" +imageio = "*" +moviepy = "*" +more-itertools = "*" +numpy = "*" +pydantic = "*" +beautifulsoup4 = "*" +anyio = "*" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6966fd9 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,11 @@ +aiofiles +aiohttp +cachetools +imageio +moviepy +more-itertools +numpy +Pillow +pydantic +beautifulsoup4 +anyio \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..2b226c5 --- /dev/null +++ b/setup.py @@ -0,0 +1,42 @@ +import setuptools +import re + +with open('starrailcard/__init__.py') as f: + """ + Get version from utils.py + Ref: https://github.com/Rapptz/discord.py/blob/52f3a3496bea13fefc08b38f9ed01641e565d0eb/setup.py#L9 + """ + version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', f.read(), re.M).group(1) + +setuptools.setup( + name="starrailcard", + version=version, + author="DeviantUa", + author_email="deviantapi@gmail.com", + description= "This Python module provides the ability to create captivating character cards based on player data from Honkai Star Rail, obtained through their unique user identifiers (UIDs). StarRailCard streamlines the process of generating personalized character assembly cards, relying on the information provided by players.", + long_description=open("README.md", "r", encoding="utf-8").read(), + long_description_content_type="text/markdown", + url="https://github.com/DEViantUA/StarRailCard", + keywords = ["honkai", "cards", "generation", "honkaistarraill","raill", "starraill", "builds", "honkairail", "honkai"] , + packages=setuptools.find_packages(), + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], + install_requires=[ + "pydantic", + "aiohttp", + "cachetools", + "Pillow", + "aiofiles", + "imageio", + "moviepy", + "more-itertools", + "numpy", + "beautifulsoup4", + "anyio" + ], + python_requires=">=3.9", + include_package_data=True +) \ No newline at end of file diff --git a/starrailcard/__init__.py b/starrailcard/__init__.py index 2f019b1..bc23f59 100644 --- a/starrailcard/__init__.py +++ b/starrailcard/__init__.py @@ -1,3 +1,46 @@ +""" +MIT License with Additional Restrictions: + +Copyright (c) 2024 Deviant + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +1. Redistributions of the Software in source code or any form must retain the +above copyright notice, this list of conditions and the following additional +restrictions. + +2. Redistributions of the Software are not allowed to be modified or +distributed under a different name without prior written permission of the +copyright holder. + +3. Redistributions of the Software or derivative works based on the Software +are not allowed to be sold or used for commercial purposes without prior +written permission of the copyright holder. + +4. Redistributions of the Software or derivative works based on the Software +are not allowed to be copied, reproduced, or distributed without prior written +permission of the copyright holder. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +__title__ = 'StarRailCard.py' +__author__ = 'DeviantUa' +__version__ = '2.1.6' +__license__ = 'MIT' +__copyright__ = 'Copyright 2024-present DeviantUa' + from .client import * from .utils import * from .src.tools.enums import * \ No newline at end of file diff --git a/starrailcard/client.py b/starrailcard/client.py index 24ffc4c..5e3155b 100644 --- a/starrailcard/client.py +++ b/starrailcard/client.py @@ -1,18 +1,22 @@ # Copyright 2024 DEViantUa # All rights reserved. -import asyncio +import anyio +#import asyncio from .src.tools import http, ukrainization, options, translator, cashe, git from .src.generator import style_relict_score, style_ticket, style_profile_phone -from .src.api import api -from .src.model import StarRailCard +from .src.api import api, enka +from .src.model import StarRailCard,api_mihomo +from .src.tools.pill import image_controle + class Card: - def __init__(self, lang = "en", character_art = None, character_id = None, seeleland = False, - user_font = None, save = False, asset_save = False, remove_logo = False, - cashe = {"maxsize": 150, "ttl": 300}): + def __init__(self, lang: str = "en", character_art = None, character_id = None, seeleland: bool = False, + user_font = None, save: bool = False, asset_save: bool = False, boost_speed: bool = False, remove_logo: bool = False, + cashe = {"maxsize": 150, "ttl": 300}, enka: bool = False, api_data: api_mihomo.MiHoMoApi = None, proxy: str = None, + user_agent: str = None): """Main class for generating cards Args: @@ -23,10 +27,13 @@ def __init__(self, lang = "en", character_art = None, character_id = None, seele user_font (srt, optional): Font path or font name. Defaults to None. save (bool, optional): Whether to save images or not (Does not work for animated cards). Defaults to False. asset_save (bool, optional): Save assets to the device so that in subsequent calls you do not have to download them, but open them from the device. Defaults to False. + boost_speed (bool, optional): Allows you to download generation resources to your device for further use without downloading. !!!Fills up the device memory!!!. remove_logo (bool, optional): Remove GItHub logo. Defaults to False. cashe (dict, optional): Set your cache settings. Defaults to {"maxsize": 150, "ttl": 300}. + api_data (MiHoMoApi, optional): Pass your data received via: api.ApiMiHoMo(uid,"en").get() + proxy (str, optional): Proxy as a string: http://111.111.111.111:8888 + user_agent (str, optional): Custom User-Agent. """ - self.session = None self.lang = lang self.character_art = character_art self.character_id = character_id @@ -36,10 +43,16 @@ def __init__(self, lang = "en", character_art = None, character_id = None, seele self.asset_save = asset_save self.remove_logo = remove_logo self.cashe = cashe + self.enka = enka + self.boost_speed = boost_speed + self.api_data = api_data + self.proxy = proxy + self.user_agent = options.get_user_agent(user_agent) + async def __aenter__(self): cashe.Cache.get_cache(maxsize = self.cashe.get("maxsize", 150), ttl = self.cashe.get("ttl", 300)) - self.session = await http.AioSession.creat_session() + await http.AioSession.enter(self.proxy) await git.ImageCache.set_assets_dowload(self.asset_save) @@ -52,6 +65,7 @@ async def __aenter__(self): else: self.character_art = await options.get_character_art(self.character_art) + if not self.lang in translator.SUPPORTED_LANGUAGES: self.lang = "en" @@ -60,10 +74,12 @@ async def __aenter__(self): self.translateLang = translator.Translator(self.lang) - if self.user_font: await git.change_font(font_path = self.user_font) + image_controle._boost_speed = self.boost_speed + + if self.remove_logo: print(""" Thank you for using our StarRailCard! @@ -76,8 +92,8 @@ async def __aenter__(self): return self async def __aexit__(self, exc_type, exc, tb): - await self.session.close() - + await http.AioSession.exit(exc_type, exc, tb) + async def set_lang(self, lang): """Sets the language @@ -116,8 +132,23 @@ async def creat_profile(self, uid, style = 1, hide_uid = False, background = Non Returns: StarRail: A class object containing profile information and a profile card """ - data = await api.ApiMiHoMo(uid, lang= self.lang, force_update = force_update).get() - + if self.api_data is None: + if self.enka: + try: + data = await enka.ApiEnkaNetwork(uid, lang= self.lang).get() + except Exception as e: + print("To use the EnkaNetwork API you need to download/update the asset\nExample: await enka.ApiEnkaNetwork().update_assets()") + data = await api.ApiMiHoMo(uid, lang= self.lang, force_update = force_update, user_agent= self.user_agent).get() + else: + data = await api.ApiMiHoMo(uid, lang= self.lang, force_update = force_update, user_agent= self.user_agent).get() + else: + data = self.api_data + + try: + player = data.player.model_dump() + except: + player = data.player + response = { "settings": { "uid": int(uid), @@ -127,7 +158,7 @@ async def creat_profile(self, uid, style = 1, hide_uid = False, background = Non "force_update": force_update, "style": int(style) }, - "player": data.player, + "player": player, "card": None, "character_name": [], "character_id": [], @@ -143,7 +174,8 @@ async def creat_profile(self, uid, style = 1, hide_uid = False, background = Non response["character_id"].append(key.id) response["character_name"].append(key.name) - await options.save_card(uid,response["card"],"profile") + if self.save: + await options.save_card(uid,response["card"],"profile") return StarRailCard.StarRail(**response) @@ -161,9 +193,20 @@ async def creat(self, uid, style = 1, hide_uid = False, force_update = False, st StarRail: A class object containing profile information and a character cards """ - data = await api.ApiMiHoMo(uid, lang= self.lang, force_update = force_update).get() - task = [] - + if self.api_data is None: + if self.enka: + try: + data = await enka.ApiEnkaNetwork(uid, lang= self.lang).get() + except Exception as e: + print("To use the EnkaNetwork API you need to download/update the asset") + data = await api.ApiMiHoMo(uid, lang= self.lang, force_update = force_update, user_agent= self.user_agent).get() + else: + data = await api.ApiMiHoMo(uid, lang= self.lang, force_update = force_update, user_agent= self.user_agent).get() + else: + data = self.api_data + + result = [] + style, style_settings = await options.style_setting(style, style_settings) try: @@ -186,37 +229,43 @@ async def creat(self, uid, style = 1, hide_uid = False, force_update = False, st "character_id": [], } - for key in data.characters: - response["character_id"].append(key.id) - response["character_name"].append(key.name) + async with anyio.create_task_group() as tasks: - if self.character_id: - if not str(key.id) in self.character_id: - continue - - art = None - if self.character_art: - if str(key.id) in self.character_art: - art = self.character_art[str(key.id)] + for key in data.characters: + async def get_result(key): + try: + response["character_id"].append(key.id) + response["character_name"].append(key.name) + + if self.character_id: + if not str(key.id) in self.character_id: + return + + art = None + if self.character_art: + if str(key.id) in self.character_art: + art = self.character_art[str(key.id)] + if style == 1: + result.append(await style_relict_score.Creat(key,self.translateLang,art,hide_uid,uid, self.seeleland,self.remove_logo).start()) + elif style == 2: + result.append(await style_ticket.Creat(key,self.translateLang,art,hide_uid,uid, self.seeleland,self.remove_logo).start()) + except Exception as e: + print(f"Error in get_result for character {key.id}: {e}") + + tasks.start_soon(get_result, key) - if style == 1: - task.append(style_relict_score.Creat(key,self.translateLang,art,hide_uid,uid, self.seeleland,self.remove_logo).start()) - elif style == 2: - task.append(style_ticket.Creat(key,self.translateLang,art,hide_uid,uid, self.seeleland,self.remove_logo).start()) - - response["card"] = await asyncio.gather(*task) + response["card"] = result if self.lang == "ua": StarRailCard.UA_LANG = True else: StarRailCard.UA_LANG = False - task_save = [] - if self.save: - for key in response["card"]: - if key["animation"]: - continue - task_save.append(options.save_card(uid,key["card"],key["id"])) - - await asyncio.gather(*task_save) - return StarRailCard.StarRail(**response) + if self.save: + async with anyio.create_task_group() as tasks: + for key in response["card"]: + if key["animation"]: + continue + tasks.start_soon(options.save_card,uid,key["card"],key["id"]) + + return StarRailCard.StarRail(**response) \ No newline at end of file diff --git a/starrailcard/src/api/api.py b/starrailcard/src/api/api.py index c34bdd5..5aa5e10 100644 --- a/starrailcard/src/api/api.py +++ b/starrailcard/src/api/api.py @@ -1,25 +1,69 @@ -# Copyright 2024 DEViantUa -# All rights reserved. +import aiohttp +from typing import Optional -from ..tools import http, translator, ukrainization +from ..tools import http, translator, ukrainization, options from ..model import api_mihomo -from typing import Final +from .error import StarRailCardError +from ..tools.json_data import JsonManager +from ..tools.enums import PathData + +_API_MIHOMO: str = "https://api.mihomo.me/sr_info_parsed/{uid}" -_API_MIHOMO: Final[str] = "https://api.mihomo.me/sr_info_parsed/{uid}?version=v2&lang={lang}&is_force_update={force_update}" class ApiMiHoMo: - def __init__(self,uid, lang = "en", force_update = False) -> None: - self.force_update = force_update - self.uid = uid + """Class for interacting with the MiHoMo API.""" + + def __init__(self, uid: str, lang: str = "en", v: int = 2, force_update: bool = False, user_agent: str = None, proxy = None) -> None: + """Initialize the ApiMiHoMo object.""" + self.force_update: bool = force_update + self.uid: str = uid + self.lang: str = translator.SUPPORTED_LANGUAGES.get(lang, "en") + self.ua_lang: str = lang == "en" + self.user_agent: str = options.get_user_agent(user_agent) + self.proxy: str = proxy api_mihomo.UA_LANG = False if lang == "ua": + self.ua_lang = True api_mihomo.UA_LANG = True - self.lang = translator.SUPPORTED_LANGUAGES.get(lang, "en") - - async def get(self): - data = await http.AioSession.get(_API_MIHOMO.format(uid = self.uid, lang = self.lang, force_update = self.force_update)) - if api_mihomo.UA_LANG: - await ukrainization.TranslateDataManager().load_translate_data() - return api_mihomo.MiHoMoApi(**data) \ No newline at end of file + self.v = v + + async def get(self) -> Optional[api_mihomo.MiHoMoApi]: + """Get data from the MiHoMo API.""" + try: + params = { + 'lang': self.lang, + 'is_force_update': str(self.force_update), + 'version': f"v{self.v}" + } + + headers = { + "User-Agent": self.user_agent + } + + data = await http.AioSession.get(_API_MIHOMO.format(uid=self.uid), headers = headers, params=params, proxy= self.proxy) + + if data is None: + raise StarRailCardError(4, "Failed to get data from API, please try again") + + if 'detail' in data: + detail = data['detail'] + if detail == 'User not found': + raise StarRailCardError(3, "User not found") + elif detail == 'Invalid parameters': + raise StarRailCardError(2, "Invalid parameters") + elif detail == 'Invalid uid': + raise StarRailCardError(1, "Invalid uid") + else: + raise StarRailCardError(0, detail) + + except aiohttp.ClientConnectionError: + raise StarRailCardError(1, "Server is not responding") + except aiohttp.ClientResponseError as e: + raise StarRailCardError(e.status, f"Server returned status code {e.status}") + + if self.ua_lang: + await ukrainization.TranslateDataManager().load_translate_data() + + return api_mihomo.MiHoMoApi(player=data["player"], characters=data["characters"], dont_update_link= False) \ No newline at end of file diff --git a/starrailcard/src/api/enka.py b/starrailcard/src/api/enka.py new file mode 100644 index 0000000..1aad2ea --- /dev/null +++ b/starrailcard/src/api/enka.py @@ -0,0 +1,101 @@ +import aiohttp +from typing import Optional, Union +import os + + +from ..tools import http, translator, ukrainization +from .enka_parsed import * +from .error import StarRailCardError +from ..tools.json_data import JsonManager +from ..tools.enums import PathData +from ..tools.translator import SUPPORTED_LANGUAGES +from .enka_parsed import AssetEnkaParsed +from ..model import api_mihomo + +_API_ENKA = "https://enka.network/api/hsr/uid/{uid}" +_ASSETS_ENKA = "https://raw.githubusercontent.com/EnkaNetwork/API-docs/master/store/hsr/{asset}.json" +_INDEX_MIHOMO = "https://raw.githubusercontent.com/Mar-7th/StarRailRes/master/index_new/{lang}/{index}.json" + +_ASSET_NAME = [ + "avatars", + "characters", + "meta", + "ranks", + "relics", + "skills", + "skilltree", + "weps", + "hsr" +] + +_INDEX_NAME = [ + "paths", + "elements", + "character_skills", + "character_skill_trees", + "properties", + "light_cone_ranks", + "relics", + "relic_main_affixes", + "relic_sub_affixes", + "relic_sets", + "character_ranks" +] + + + +class ApiEnkaNetwork: + def __init__(self, uid: Union[int, str] = 0, lang: str = "en", parsed: bool = True) -> None: + self.uid: str = uid + self.parsed: bool = parsed + self.lang: str = translator.SUPPORTED_LANGUAGES.get(lang, "en") + self.ua_lang: str = lang == "en" + api_mihomo.UA_LANG = False + if lang == "ua": + self.ua_lang = True + api_mihomo.UA_LANG = True + + async def get(self): + """Get data from the MiHoMo API.""" + try: + data = await http.AioSession.get(_API_ENKA.format(uid=self.uid)) + + if self.parsed: + data = await AssetEnkaParsed(data).collect() + + except aiohttp.ClientConnectionError: + raise StarRailCardError(1, "Server is not responding") + except aiohttp.ClientResponseError as e: + raise StarRailCardError(e.status, f"Server returned status code {e.status}") + + if self.ua_lang: + await ukrainization.TranslateDataManager().load_translate_data() + + return api_mihomo.MiHoMoApi(player=data["player"], characters=data["characters"], dont_update_link=True) + + async def update_assets(self): + print("===START UPDATE INDEX===") + for name in _INDEX_NAME: + for lang in SUPPORTED_LANGUAGES: + if lang == "ua": + continue + data = await http.AioSession.get(_INDEX_MIHOMO.format(lang = lang, index=name)) + if not os.path.exists(PathData.ENKA_INDEX.value / lang): + os.makedirs(PathData.ENKA_INDEX.value / lang) + await JsonManager(PathData.ENKA_INDEX.value / lang /f"{name}.json").write(data) + + print(f"- Updated file: {name}") + print("===END UPDATE INDEX===") + print() + print("===START UPDATE ASSETS===") + for name in _ASSET_NAME: + if name == "hsr": + data = await http.AioSession.get(_ASSETS_ENKA.format(asset=name)) + else: + data = await http.AioSession.get(_ASSETS_ENKA.format(asset=f"honker_{name}")) + await JsonManager(PathData.ENKA.value / f"{name}.json").write(data) + print(f"- Updated file: {name}") + + print("===END UPDATE ASSETS===") + + \ No newline at end of file diff --git a/starrailcard/src/api/enka_parsed.py b/starrailcard/src/api/enka_parsed.py new file mode 100644 index 0000000..282f881 --- /dev/null +++ b/starrailcard/src/api/enka_parsed.py @@ -0,0 +1,543 @@ +from ..tools.json_data import JsonManager +from ..tools.enums import PathData +import json + +_DEFFAULT_ASSETS_LINK = "https://enka.network/ui/hsr/" + +_DEFFAULT_ASSETS_HSR_LINK = "https://raw.githubusercontent.com/Mar-7th/StarRailRes/master/{catalog}/{key}.png" +_DEFFAULT_ASSETS_HSR_LINK_SKILL = "https://raw.githubusercontent.com/Mar-7th/StarRailRes/master/" + + +fiel = { + "BaseHP": "hp", + "BaseAttack": "atk", + "BaseDefence": "def", +} + +proc = { + "false": False, + 'true': True +} + +class AssetEnkaParsed: + def __init__(self, data: dict, lang: str = "en") -> None: + self.data = data["detailInfo"] + self.lang = lang + + async def creat_player(self): + avatar_link = await AssetEnkaParsed.get_icon_avatar(self.data["headIcon"]) + self.player = { + "uid": 0,#self.data["uid"], + "nickname": self.data["nickname"], + "level": self.data["level"], + "is_display": self.data["isDisplayAvatar"], + "avatar": {"id": str(self.data["headIcon"]), "name": avatar_link.split("/")[-1:][0], "icon": _DEFFAULT_ASSETS_LINK + avatar_link}, + "signature": self.data["signature"], + "friend_count": self.data["friendCount"], + "world_level": self.data["worldLevel"], + "space_info": { + "pass_area_progress": self.data["recordInfo"]["challengeInfo"]["scheduleMaxLevel"], + "light_cone_count": self.data["recordInfo"]["equipmentCount"], + "avatar_count": self.data["recordInfo"]["avatarCount"], + "achievement_count": self.data["recordInfo"]["achievementCount"], + } + } + + + async def creat_avatar(self): + self.charter = [] + for key in self.data["avatarDetailList"]: + charter_info = await AssetEnkaParsed.get_info_character(key["avatarId"],self.lang) + path_info = await JsonManager(PathData.ENKA_INDEX.value / self.lang / "paths.json").read() + element_info = await JsonManager(PathData.ENKA_INDEX.value / self.lang / "elements.json").read() + skills_info = await JsonManager(PathData.ENKA_INDEX.value / self.lang / "character_skills.json").read() + skill_trees_info = await JsonManager(PathData.ENKA_INDEX.value / self.lang / "character_skill_trees.json").read() + character_ranks = await JsonManager(PathData.ENKA_INDEX.value / self.lang / "character_ranks.json").read() + light_cone = await AssetEnkaParsed.add_light_cone(key,self.lang,path_info) + relics = [await AssetEnkaParsed.add_relics(keys, self.lang) for keys in key["relicList"]] + skill_trees = await AssetEnkaParsed.add_skill_trees(skill_trees_info,charter_info,skills_info,key) + + data = { + "id": key["avatarId"], + "name": charter_info["AvatarName"]["name"], + "rarity": charter_info["Rarity"], + "rank": key.get("rank",0), + "level": key["level"], + "promotion": key["promotion"], + "icon": _DEFFAULT_ASSETS_HSR_LINK.format(catalog = "icon/avatar", key = key["avatarId"]), + "preview": _DEFFAULT_ASSETS_HSR_LINK.format(catalog = "image/character_preview", key = key["avatarId"]), + "portrait": _DEFFAULT_ASSETS_HSR_LINK.format(catalog = "image/character_portrait", key = key["avatarId"]), + "path": await AssetEnkaParsed.get_path(path_info,charter_info), + "rank_icons": [_DEFFAULT_ASSETS_HSR_LINK_SKILL + character_ranks.get(str(key))["icon"] for key in charter_info["RankIDList"]], + "element": await AssetEnkaParsed.get_element(element_info,charter_info["Element"]), + "skills": await AssetEnkaParsed.add_skills(element_info,charter_info,key, self.lang), + "skill_trees": skill_trees, + "light_cone": light_cone if light_cone != {} else None, + "relics": relics, + "relic_sets": await AssetEnkaParsed.add_relict_sets(key["relicList"], self.lang), + "additions": await AssetEnkaParsed.add_additions(key, self.lang, light_cone.get("attributes",[])), + "attributes": await AssetEnkaParsed.add_attributes(relics, skill_trees, self.lang), + "properties": None, #await AssetEnkaParsed.add_properties(key["skillTreeList"], self.lang), + "pos": [key.get("pos", 0)] + } + + self.charter.append(data) + + + + async def collect(self): + await self.creat_player() + await self.creat_avatar() + + await JsonManager(PathData.ENKA.value / "TEST_API.json").write(self.charter) + + return {"player": self.player, "characters":self.charter} + ''' + @classmethod + async def add_properties(cls, skillTreeList, lang): + property = await JsonManager(PathData.ENKA_INDEX.value / lang / "properties.json").read() + skill_trees_info = await JsonManager(PathData.ENKA_INDEX.value / lang / "character_skill_trees.json").read() + ''' + + @classmethod + async def add_attributes(cls, relict, skill, lang): + skill_trees_info = await JsonManager(PathData.ENKA_INDEX.value / lang / "character_skill_trees.json").read() + property = await JsonManager(PathData.ENKA_INDEX.value / lang / "properties.json").read() + + data = {} + + for key in relict: + if not key["main_affix"]["field"] in data: + data[key["main_affix"]["field"]] = { + "field": key["main_affix"]["field"], + "name": key["main_affix"]["name"], + "icon": key["main_affix"]["icon"], + "value": key["main_affix"]["value"], + "display": AssetEnkaParsed.get_display(key["main_affix"]["value"], key["main_affix"]["percent"]), + "percent": key["main_affix"]["percent"] + } + else: + data[key["main_affix"]["field"]]["value"] += key["main_affix"]["value"] + data[key["main_affix"]["field"]]["display"] = AssetEnkaParsed.get_display(data[key["main_affix"]["field"]]["value"], key["main_affix"]["percent"]) + + for keys in key["sub_affix"]: + if not keys["field"] in data: + data[keys["field"]] = { + "field": keys["field"], + "name": keys["name"], + "icon": keys["icon"], + "value": keys["value"], + "display": AssetEnkaParsed.get_display(keys["value"], keys["percent"]), + "percent": keys["percent"] + } + else: + + data[keys["field"]]["value"] += keys["value"] + data[keys["field"]]["display"] = AssetEnkaParsed.get_display(data[keys["field"]]["value"], keys["percent"]) + + for key in skill: + info = skill_trees_info.get(str(key["id"]))["levels"] + if info == []: + continue + info = info[0]["properties"] + if info == []: + continue + info = info[0] + + property_info = property.get(info["type"]) + + if not property_info["field"] in data: + data[property_info["field"]] = { + "field": property_info["field"], + "name": property_info["name"], + "icon": property_info["icon"], + "value": info["value"], + "display": AssetEnkaParsed.get_display(info["value"], property_info["percent"]), + "percent": property_info["percent"] + } + else: + + data[property_info["field"]]["value"] += info["value"] + data[property_info["field"]]["display"] = AssetEnkaParsed.get_display(data[property_info["field"]]["value"], property_info["percent"]) + + + + return [data[key] for key in data] + + @classmethod + async def add_additions(cls, data,lang, light_cone): + promotion_id = data["promotion"] + avatarId = data["avatarId"] + level = data["level"] + + if level > 1: + level = data["level"]-1 + + property = await JsonManager(PathData.ENKA_INDEX.value / lang / "properties.json").read() + meta = await JsonManager(PathData.ENKA.value / "meta.json").read() + meta = meta["avatar"].get(str(avatarId)).get(str(promotion_id)) + + + name = { + "hp": "HPBase", + "atk": "AttackBase", + "def": "DefenceBase", + "spd": "SpeedBase", + "crit_rate": "CriticalChance", + "crit_dmg": "CriticalDamage", + } + + base = [ + { + "field":"hp", + "name":"BaseHP", + "icon": _DEFFAULT_ASSETS_HSR_LINK_SKILL + "icon/property/IconMaxHP.png", + "value": 0, + "display": None, + "percent": False + }, + { + "field":"atk", + "name": "BaseAttack", + "icon": _DEFFAULT_ASSETS_HSR_LINK_SKILL + "icon/property/IconAttack.png", + "value": 0, + "display": None, + "percent": False + }, + { + "field":"def", + "name": "BaseDefence", + "icon": _DEFFAULT_ASSETS_HSR_LINK_SKILL + "icon/property/IconDefence.png", + "value": 0, + "display": None, + "percent": False + }, + { + "field":"spd", + "name": "SpeedDelta", + "icon": _DEFFAULT_ASSETS_HSR_LINK_SKILL + "icon/property/IconSpeed.png", + "value": 0, + "display": None, + "percent": False + }, + { + "field":"crit_rate", + "name": "CriticalChanceBase", + "icon": _DEFFAULT_ASSETS_HSR_LINK_SKILL + "icon/property/IconCriticalChance.png", + "value":0., + "display": None, + "percent": True + }, + { + "field":"crit_dmg", + "name": "CriticalDamageBase", + "icon": _DEFFAULT_ASSETS_HSR_LINK_SKILL + "icon/property/IconCriticalDamage.png", + "value":0, + "display": None, + "percent": True + } + ] + + for key in base: + name_meta = name.get(key["field"]) + key["name"] = property.get(key["name"])["name"] + if not key["percent"]: + if light_cone != []: + for keys in light_cone: + if key["field"] == keys["field"]: + key["value"] = meta[name_meta] + (meta.get(name_meta.replace("Base", "Add"), 0) * level) + keys["value"] + else: + key["value"] = meta[name_meta] + (meta.get(name_meta.replace("Base", "Add"), 0) * level) + else: + key["value"] = meta[name_meta] + key["display"] = AssetEnkaParsed.get_display(key["value"], key["percent"]) + + return base + + @classmethod + async def add_relict_sets(cls, data, lang): + + new_data = [] + sets_num = {} + done = [] + data_relics = await JsonManager(PathData.ENKA.value / "relics.json").read() + data_relics_sets = await JsonManager(PathData.ENKA_INDEX.value / lang / "relic_sets.json").read() + + for key in data: + relics = data_relics.get(str(key["tid"])) + if not relics["SetID"] in sets_num: + sets_num[relics["SetID"]] = 1 + else: + sets_num[relics["SetID"]] += 1 + + property = await JsonManager(PathData.ENKA_INDEX.value / lang / "properties.json").read() + + for key in data: + relics = data_relics.get(str(key["tid"])) + if sets_num[relics["SetID"]] < 2 or relics["SetID"] in done: + continue + + relics_sets = data_relics_sets.get(str(relics["SetID"])) + + index_desc = 0 + if sets_num[relics["SetID"]] >= 4: + index_desc = 1 + + properties = relics_sets["properties"][index_desc] + if properties == []: + properties = relics_sets["properties"][0] + new_data.append({"id": str(relics["SetID"]), + "name": relics_sets["name"], + "icon": _DEFFAULT_ASSETS_HSR_LINK_SKILL + relics_sets["icon"], + "num": sets_num[relics["SetID"]], + "desc": relics_sets["desc"][index_desc], + "properties":[ + { + "type": properties[0]["type"], + "field": property.get(properties[0]["type"])["field"], + "name": property.get(properties[0]["type"])["name"], + "icon": _DEFFAULT_ASSETS_HSR_LINK_SKILL + property.get(properties[0]["type"])["icon"], + "value": properties[0]["value"], + "display": AssetEnkaParsed.get_display(properties[0]["value"], property.get(properties[0]["type"])["percent"]), + "percent": property.get(properties[0]["type"])["percent"] + } if properties != [] else [] + ] + } + ) + done.append(relics["SetID"]) + + return new_data + + + @classmethod + async def add_relics(cls, data,lang): + + relics = await JsonManager(PathData.ENKA.value / "relics.json").read() + relics = relics.get(str(data["tid"])) + + relicsMiHoMo = await JsonManager(PathData.ENKA_INDEX.value / lang /"relics.json").read() + relicsMiHoMo = relicsMiHoMo.get(str(data["tid"])) + + hash = await JsonManager(PathData.ENKA.value / "hsr.json").read() + hash = hash.get(lang) + + main_affix = await JsonManager(PathData.ENKA_INDEX.value / lang / "relic_main_affixes.json").read() + main_affix = main_affix.get(relicsMiHoMo["main_affix_id"]) + main_affix = main_affix["affixes"].get(str(data["mainAffixId"])) + + + + property = await JsonManager(PathData.ENKA_INDEX.value / lang / "properties.json").read() + + data = { + "id": data["tid"], + "name": relicsMiHoMo["name"], + "set_id": data["_flat"]["setID"], + "set_name": hash.get(str(data["_flat"]["setName"])), + "rarity": relics["Rarity"], + "level": data["level"], + "icon": f"https://enka.network/ui/hsr/{relics['Icon']}", + "main_affix":{ + "type": main_affix["property"], + "field": property.get(main_affix["property"])["field"], + "name": property.get(main_affix["property"])["name"], + "icon": _DEFFAULT_ASSETS_HSR_LINK_SKILL + property.get(main_affix["property"])["icon"], + "value": data["_flat"]["props"][0]["value"], + "display": AssetEnkaParsed.get_display(data["_flat"]["props"][0]["value"], property.get(main_affix["property"])["percent"]), + "percent": property.get(main_affix["property"])["percent"] + }, + "sub_affix": [ await AssetEnkaParsed.add_sub_affix(key, index, property, lang, data["_flat"]["props"]) for index, key in enumerate(data["subAffixList"], start = 2)] + } + + return data + + @classmethod + async def add_sub_affix(cls, affix, index, property, lang, flat): + affixId = str(affix["affixId"]) + sub_affix = await JsonManager(PathData.ENKA_INDEX.value / lang / "relic_sub_affixes.json").read() + sub_affix = sub_affix.get(str(index)) + + return { + "type": sub_affix["affixes"][affixId]["property"], + "field":property.get(sub_affix["affixes"][affixId]["property"])["field"], + "name": property.get(sub_affix["affixes"][affixId]["property"])["name"], + "icon": _DEFFAULT_ASSETS_HSR_LINK_SKILL + property.get(sub_affix["affixes"][affixId]["property"])["icon"], + "value": flat[index-1]["value"], + "display": AssetEnkaParsed.get_display(flat[index-1]["value"], property.get(sub_affix["affixes"][affixId]["property"])["percent"]), + "percent":property.get(sub_affix["affixes"][affixId]["property"])["percent"], + "count": affix.get("cnt", 0), + "step": affix.get("step", 0) + } + + + @classmethod + async def add_light_cone(cls, key,lang,path_info): + if key.get("equipment", {}) != {}: + data = await AssetEnkaParsed.get_info_light_cone(key["equipment"]["tid"],lang) + property = await JsonManager(PathData.ENKA_INDEX.value / lang / "properties.json").read() + light_cone_ranks = await JsonManager(PathData.ENKA_INDEX.value / lang / "light_cone_ranks.json").read() + + return { + "id": key["equipment"]["tid"], + "name": data["EquipmentName"]["name"], + "rarity": data["Rarity"], + "rank": key["equipment"]["rank"], + "level": key["equipment"]["level"], + "promotion": key["equipment"]["promotion"], + "icon": _DEFFAULT_ASSETS_HSR_LINK.format(catalog = "icon/light_cone", key = key["equipment"]["tid"]), + "preview": _DEFFAULT_ASSETS_HSR_LINK.format(catalog = "image/light_cone_preview", key = key["equipment"]["tid"]), + "portrait": _DEFFAULT_ASSETS_HSR_LINK.format(catalog = "image/light_cone_portrait", key = key["equipment"]["tid"]), + "path": await AssetEnkaParsed.get_path(path_info,data), + "attributes": [ + { + "field": fiel.get(keys["type"]), + "name": property.get(keys["type"])["name"], + "icon": _DEFFAULT_ASSETS_HSR_LINK_SKILL + property.get(keys["type"])["icon"], + "value": keys["value"], + "display": AssetEnkaParsed.get_display(keys["value"],property.get(keys["type"])["percent"]), + "percent":property.get(keys["type"])["percent"] + } + for keys in key["equipment"]["_flat"]["props"]], + + "properties": [{ + "type": keys["type"], + "field": property.get(keys["type"])["field"], + "name": property.get(keys["type"])["name"], + "icon": property.get(keys["type"])["icon"], + "value": keys["value"], + "display": AssetEnkaParsed.get_display(keys["value"],property.get(keys["type"])["percent"]), + "percent": property.get(keys["type"])["percent"] + } for keys in light_cone_ranks.get(str(key["equipment"]["tid"]))["properties"][key["equipment"]["rank"]-1] ] + } + + return {} + + @classmethod + async def get_info_light_cone(cls, ids,lang): + data = await JsonManager(PathData.ENKA.value / "weps.json").read() + + charter = data.get(str(ids)) + + hash = await JsonManager(PathData.ENKA.value / "hsr.json").read() + + charter["EquipmentName"]["name"] = hash.get(lang).get(str(charter["EquipmentName"]["Hash"])) + + return charter + + @classmethod + async def add_skill_trees(cls, skill_trees_info,charter_info,skills_info,key): + + return [{ + "id": keys["pointId"], + "level": keys["level"], + "anchor": skill_trees_info[str(keys["pointId"])]["anchor"], + "icon": _DEFFAULT_ASSETS_HSR_LINK_SKILL + skill_trees_info[str(keys["pointId"])]["icon"], + "max_level": AssetEnkaParsed.get_max_level(skill_trees_info[str(keys["pointId"])],charter_info,skills_info,key.get("rank",0)), + "parent": AssetEnkaParsed.get_parent(skill_trees_info[str(keys["pointId"])]), + } for keys in key["skillTreeList"]] + + @classmethod + async def add_skills(cls, element_info,charter_info,key,lang): + skill_trees_info = await JsonManager(PathData.ENKA_INDEX.value / lang / "character_skill_trees.json").read() + skills_info = await JsonManager(PathData.ENKA_INDEX.value / lang / "character_skills.json").read() + + data = [] + + for keys in key["skillTreeList"]: + pointId = str(keys["pointId"]) + if skill_trees_info.get(pointId)["level_up_skills"] == []: + continue + + skills_info_id = str(skill_trees_info.get(pointId)["level_up_skills"][0]["id"]) + + if not int(skills_info_id) in charter_info["SkillList"]: + continue + + info = skills_info.get(skills_info_id) + + max_level = skill_trees_info.get(pointId)["max_level"] + if keys["level"] > max_level: + max_level = info["max_level"] + + data.append( + { + "id": skills_info_id, + "name": info["name"], + "level": keys["level"], + "max_level": max_level, + "element": await AssetEnkaParsed.get_element(element_info, info["element"]), + "type": info["type"], + "type_text": info["type_text"], + "effect": info["effect"], + "effect_text": info["effect_text"], + "simple_desc": info["simple_desc"], + "desc": info["desc"], + "icon": _DEFFAULT_ASSETS_HSR_LINK_SKILL + info["icon"], + } + ) + + return data + + @classmethod + async def get_icon_avatar(cls, ids): + data = await JsonManager(PathData.ENKA.value / "avatars.json").read() + + return data.get(str(ids))["Icon"] + + @classmethod + async def get_info_character(cls, ids,lang): + data = await JsonManager(PathData.ENKA.value / "characters.json").read() + + charter = data.get(str(ids)) + + hash = await JsonManager(PathData.ENKA.value / "hsr.json").read() + + charter["AvatarFullName"]["name"] = hash.get(lang).get(str(charter["AvatarFullName"]["Hash"])) + charter["AvatarName"]["name"] = hash.get(lang).get(str(charter["AvatarName"]["Hash"])) + + return charter + + @classmethod + async def get_element(cls, element_info,element): + if element != "": + return {"id": element_info[element]["id"], + "name": element_info[element]["name"], + "color": element_info[element]["color"], + "icon": _DEFFAULT_ASSETS_HSR_LINK.format(catalog = "icon/element", key = element_info[element]["id"].replace("Thunder", "Lightning")) + } + else: + return None + + @classmethod + async def get_path(cls, path_info,charter_info): + return { + "id": path_info[charter_info["AvatarBaseType"]]["id"], + "name": path_info[charter_info["AvatarBaseType"]]["name"], + "icon": _DEFFAULT_ASSETS_HSR_LINK.format(catalog = "icon/path", key = path_info[charter_info["AvatarBaseType"]]["text"]), + } + + @classmethod + def get_max_level(cls, skill_trees_info,charter_info,skills_info,rank): + max_level = skill_trees_info["max_level"] + if skill_trees_info.get("level_up_skills", []) != []: + if int(skill_trees_info.get("level_up_skills")[0]["id"]) in charter_info["RankIDList"][:rank]: + max_level = skills_info[skill_trees_info.get("level_up_skills")[0]["id"]]["max_level"] + + return max_level + + @classmethod + def get_parent(cls, parent): + if parent["pre_points"] != []: + return parent["pre_points"][0] + return None + + @classmethod + def get_display(cls, value,percent): + if percent: + percentage_value = value * 100 + return f"{percentage_value:.1f}%" + else: + if value < 1: + value = value * 100 + + return str(round(value)) \ No newline at end of file diff --git a/starrailcard/src/api/error.py b/starrailcard/src/api/error.py new file mode 100644 index 0000000..185a8f2 --- /dev/null +++ b/starrailcard/src/api/error.py @@ -0,0 +1,13 @@ +class ApiError(Exception): + """Base exception for errors when working with the MiHoMo API.""" + + def __init__(self, code: int, message: str, status: int = 400) -> None: + """Initialize the error with a code and message.""" + super().__init__(f"[{code}] {message} Status: {status}") + self.code = code + self.message = message + self.status = status + +class StarRailCardError(ApiError): + """Exception specific to MiHoMo errors.""" + pass \ No newline at end of file diff --git a/starrailcard/src/data/avatar.json b/starrailcard/src/data/avatar.json index b2b13fd..998740c 100644 --- a/starrailcard/src/data/avatar.json +++ b/starrailcard/src/data/avatar.json @@ -46,5 +46,8 @@ "1311": "Скруллум", "1306": "Іскра", "1307": "Чорний Лебідь", - "1312": "Міша" + "1312": "Міша", + "1301": "Галлахер", + "1304": "Авантюрин", + "1308": "Ахерон" } \ No newline at end of file diff --git a/starrailcard/src/data/element.json b/starrailcard/src/data/element.json index 75cd147..94f719e 100644 --- a/starrailcard/src/data/element.json +++ b/starrailcard/src/data/element.json @@ -1,9 +1,9 @@ -{ - "Physical": "Фізичний", - "Fire": "Вогняний", - "Ice": "Льодяний", - "Thunder": "Електричний", - "Wind": "Вітряний", - "Quantum": "Квантовий", - "Imaginary": "Уявний" -} +{ + "Physical": "Фізичний", + "Fire": "Вогняний", + "Ice": "Льодяний", + "Thunder": "Електричний", + "Wind": "Вітряний", + "Quantum": "Квантовий", + "Imaginary": "Уявний" +} \ No newline at end of file diff --git a/starrailcard/src/data/keys.json b/starrailcard/src/data/keys.json index 4489571..bffbb80 100644 --- a/starrailcard/src/data/keys.json +++ b/starrailcard/src/data/keys.json @@ -1,3 +1,3 @@ { - "key": "iF2YBR80g4bTSv9VgSnH3U8DvQWcID51FBpFwvylwvq1QsxF4NxKsAZ4/nB9BQ8cfYZeAj+8ALFvRrbO39bs" + "key": "sacvfdcbgfhbgfnfgbdf/dsfsdvcx8cfYZeAj+8ALFvRrbO39bs" } \ No newline at end of file diff --git a/starrailcard/src/data/paths.json b/starrailcard/src/data/paths.json index d9cd9a8..f4c8e12 100644 --- a/starrailcard/src/data/paths.json +++ b/starrailcard/src/data/paths.json @@ -1,10 +1,10 @@ -{ - "Warrior": "Руйнування", - "Rogue": "Полювання", - "Mage": "Ерудиція", - "Shaman": "Гармонія", - "Warlock": "Небуття", - "Knight": "Збереження", - "Priest": "Достаток", - "Unknown": "Звичайний" -} +{ + "Warrior": "Руйнування", + "Rogue": "Полювання", + "Mage": "Ерудиція", + "Shaman": "Гармонія", + "Warlock": "Небуття", + "Knight": "Збереження", + "Priest": "Достаток", + "Unknown": "Звичайний" +} \ No newline at end of file diff --git a/starrailcard/src/data/relict_sets.json b/starrailcard/src/data/relict_sets.json index f543c77..334e0e3 100644 --- a/starrailcard/src/data/relict_sets.json +++ b/starrailcard/src/data/relict_sets.json @@ -28,5 +28,7 @@ "311": "Небесний фронт Глатірамер", "312": "Земля мрій Пенаконія", "117": "Першовідкривач мертвих вод", - "118": "Годинник сонних маніпуляцій" + "118": "Годинник сонних маніпуляцій", + "313": "Безплідна Сігонія", + "314": "Мир Ідзумо і Царство Такама" } \ No newline at end of file diff --git a/starrailcard/src/data/stats.json b/starrailcard/src/data/stats.json index 0390991..fe458bd 100644 --- a/starrailcard/src/data/stats.json +++ b/starrailcard/src/data/stats.json @@ -1,83 +1,83 @@ -{ - "MaxHP": "Максимальне HP", - "CriticalValue": "Значення КРИТ", - "AddedRatio": "Збільшення шкоди", - "Attack": "Сила атаки", - "Defence": "Захист", - "Speed": "Швидкість", - "CriticalChance": "Шанс критичного удару", - "CriticalDamage": "Критична шкода", - "BreakDamageAddedRatio": "Ефект пробиття", - "BreakDamageAddedRatioBase": "Ефект пробиття", - "HealRatio": "Бонус лікування", - "MaxSP": "Максимальна енергія", - "SPRatio": "Швидкість відновлення енергії", - "StatusProbability": "Шанс потрапляння ефектів", - "StatusResistance": "Стійкість до ефектів", - "CriticalChanceBase": "Шанс критичного удару", - "CriticalDamageBase": "Критична шкода", - "HealRatioBase": "Бонус лікування", - "StanceBreakAddedRatio": "0", - "SPRatioBase": "Швидкість відновлення енергії", - "StatusProbabilityBase": "Шанс потрапляння ефектів", - "StatusResistanceBase": "Стійкість до ефектів", - "PhysicalAddedRatio": "Бонус фізичного ушкодження", - "PhysicalResistance": "Бонус фізичного захисту", - "FireAddedRatio": "Бонус вогняного ушкодження", - "FireResistance": "Бонус вогняного захисту", - "IceAddedRatio": "Бонус льодового ушкодження", - "IceResistance": "Бонус льодового захисту", - "ThunderAddedRatio": "Бонус електричного ушкодження", - "ThunderResistance": "Бонус електричного захисту", - "WindAddedRatio": "Бонус вітряного ушкодження", - "WindResistance": "Бонус вітряного захисту", - "QuantumAddedRatio": "Бонус квантового ушкодження", - "QuantumResistance": "Бонус квантового захисту", - "ImaginaryAddedRatio": "Бонус уявного ушкодження", - "ImaginaryResistance": "Бонус уявного захисту", - "BaseHP": "Базове ХП", - "HPDelta": "ХП", - "HPAddedRatio": "ХП", - "BaseAttack": "Базова сила атаки", - "AttackDelta": "Сила атаки", - "AttackAddedRatio": "Сила атаки", - "BaseDefence": "Базовий захист", - "DefenceDelta": "Захист", - "DefenceAddedRatio": "Захист", - "BaseSpeed": "Базова швидкість", - "HealTakenRatio": "Збільшує отримуване лікування на #1[f1]%.", - "PhysicalResistanceDelta": "Бонус фізичного захисту", - "FireResistanceDelta": "Бонус вогняного захисту", - "IceResistanceDelta": "Бонус льодового захисту", - "ThunderResistanceDelta": "Бонус електричного захисту", - "WindResistanceDelta": "Бонус вітряного захисту", - "QuantumResistanceDelta": "Бонус квантового захисту", - "ImaginaryResistanceDelta": "Бонус уявного захисту", - "SpeedDelta": "Швидкість", - "break_dmg": "Ефект пробиття", - "crit_rate": "Шанс крит. удару", - "crit_dmg": "Крит. шкода", - "heal_rate": "Бонус лікування", - "sp_rate": "Швидкість відновлення енергії", - "effect_hit": "Шанс попадання ефектів", - "effect_res": "Стійкість до ефектів", - "physical_dmg": "Бонус фізичної шкоди", - "physical_res": "Бонус фізичного захисту", - "fire_dmg": "Бонус вогненної шкоди", - "fire_res": "Бонус вогненного захисту", - "ice_dmg": "Бонус льодової шкоди", - "ice_res": "Бонус льодового захисту", - "lightning_dmg": "Бонус електричної шкоди", - "lightning_res": "Бонус електричної захисту", - "wind_dmg": "Бонус вітряної шкоди", - "wind_res": "Бонус вітряного захисту", - "quantum_dmg": "Бонус квантової шкоди", - "quantum_res": "Бонус квантового захисту", - "imaginary_dmg": "Бонус уявної шкоди", - "imaginary_res": "Бонус уявного захисту", - "hp": "ХП", - "atk": "Сила атаки", - "def": "Захист", - "spd": "Швидкість", - "all_dmg": "Підвищення шкоди" -} +{ + "MaxHP": "Максимальне HP", + "CriticalValue": "Значення КРИТ", + "AddedRatio": "Збільшення шкоди", + "Attack": "Сила атаки", + "Defence": "Захист", + "Speed": "Швидкість", + "CriticalChance": "Шанс критичного удару", + "CriticalDamage": "Критична шкода", + "BreakDamageAddedRatio": "Ефект пробиття", + "BreakDamageAddedRatioBase": "Ефект пробиття", + "HealRatio": "Бонус лікування", + "MaxSP": "Максимальна енергія", + "SPRatio": "Швидкість відновлення енергії", + "StatusProbability": "Шанс потрапляння ефектів", + "StatusResistance": "Стійкість до ефектів", + "CriticalChanceBase": "Шанс критичного удару", + "CriticalDamageBase": "Критична шкода", + "HealRatioBase": "Бонус лікування", + "StanceBreakAddedRatio": "0", + "SPRatioBase": "Швидкість відновлення енергії", + "StatusProbabilityBase": "Шанс потрапляння ефектів", + "StatusResistanceBase": "Стійкість до ефектів", + "PhysicalAddedRatio": "Бонус фізичного ушкодження", + "PhysicalResistance": "Бонус фізичного захисту", + "FireAddedRatio": "Бонус вогняного ушкодження", + "FireResistance": "Бонус вогняного захисту", + "IceAddedRatio": "Бонус льодового ушкодження", + "IceResistance": "Бонус льодового захисту", + "ThunderAddedRatio": "Бонус електричного ушкодження", + "ThunderResistance": "Бонус електричного захисту", + "WindAddedRatio": "Бонус вітряного ушкодження", + "WindResistance": "Бонус вітряного захисту", + "QuantumAddedRatio": "Бонус квантового ушкодження", + "QuantumResistance": "Бонус квантового захисту", + "ImaginaryAddedRatio": "Бонус уявного ушкодження", + "ImaginaryResistance": "Бонус уявного захисту", + "BaseHP": "Базове ХП", + "HPDelta": "ХП", + "HPAddedRatio": "ХП", + "BaseAttack": "Базова сила атаки", + "AttackDelta": "Сила атаки", + "AttackAddedRatio": "Сила атаки", + "BaseDefence": "Базовий захист", + "DefenceDelta": "Захист", + "DefenceAddedRatio": "Захист", + "BaseSpeed": "Базова швидкість", + "HealTakenRatio": "Збільшує отримуване лікування на #1[f1]%.", + "PhysicalResistanceDelta": "Бонус фізичного захисту", + "FireResistanceDelta": "Бонус вогняного захисту", + "IceResistanceDelta": "Бонус льодового захисту", + "ThunderResistanceDelta": "Бонус електричного захисту", + "WindResistanceDelta": "Бонус вітряного захисту", + "QuantumResistanceDelta": "Бонус квантового захисту", + "ImaginaryResistanceDelta": "Бонус уявного захисту", + "SpeedDelta": "Швидкість", + "break_dmg": "Ефект пробиття", + "crit_rate": "Шанс крит. удару", + "crit_dmg": "Крит. шкода", + "heal_rate": "Бонус лікування", + "sp_rate": "Швидкість відновлення енергії", + "effect_hit": "Шанс попадання ефектів", + "effect_res": "Стійкість до ефектів", + "physical_dmg": "Бонус фізичної шкоди", + "physical_res": "Бонус фізичного захисту", + "fire_dmg": "Бонус вогненної шкоди", + "fire_res": "Бонус вогненного захисту", + "ice_dmg": "Бонус льодової шкоди", + "ice_res": "Бонус льодового захисту", + "lightning_dmg": "Бонус електричної шкоди", + "lightning_res": "Бонус електричної захисту", + "wind_dmg": "Бонус вітряної шкоди", + "wind_res": "Бонус вітряного захисту", + "quantum_dmg": "Бонус квантової шкоди", + "quantum_res": "Бонус квантового захисту", + "imaginary_dmg": "Бонус уявної шкоди", + "imaginary_res": "Бонус уявного захисту", + "hp": "ХП", + "atk": "Сила атаки", + "def": "Захист", + "spd": "Швидкість", + "all_dmg": "Підвищення шкоди" +} \ No newline at end of file diff --git a/starrailcard/src/data/weapons.json b/starrailcard/src/data/weapons.json index 4af8a59..c3590bf 100644 --- a/starrailcard/src/data/weapons.json +++ b/starrailcard/src/data/weapons.json @@ -91,5 +91,8 @@ "21041": "Шоу починається", "21042": "Збережене в серці обіцяння", "23021": "Світські витівки", - "23022": "Змінені спогади" + "23022": "Змінені спогади", + "21043": "Концерт для двох", + "23023": "Несправедлива доля", + "23024": "Вздовж відхиляючогося узбережжя" } \ No newline at end of file diff --git a/starrailcard/src/generator/style_profile_phone.py b/starrailcard/src/generator/style_profile_phone.py index 8c2039f..67599e0 100644 --- a/starrailcard/src/generator/style_profile_phone.py +++ b/starrailcard/src/generator/style_profile_phone.py @@ -34,9 +34,9 @@ async def creat_background(self): background_image.alpha_composite(background_shadow) self.background_profile.paste(background_image,(0,0),maska.convert("L")) self.background_profile.alpha_composite(frame) + async def creat_charter(self,key): - if key.rarity == 5: charter_profile = await _of.avatar_five else: @@ -55,7 +55,6 @@ async def creat_charter(self,key): splash,mask = await asyncio.gather(pill.get_dowload_img(url_id),_of.maska_character) splash = await pill.get_centr_size((109,109),splash) charter_profile.paste(splash,(16,28),mask.convert("L")) - name = await pill.create_image_with_text(key.name,16, max_width=123, color=(255, 255, 255, 255)) charter_profile.alpha_composite(name,(136,int(49-name.size[1]/2))) @@ -65,11 +64,9 @@ async def creat_charter(self,key): element_icon = await pill.get_dowload_img(key.element.icon, size=(28,28)) path_icon = await pill.get_dowload_img(key.path.icon, size=(28,28)) - starts = await options.get_stars(key.light_cone.rarity) charter_profile.alpha_composite(path_icon,(263,28)) charter_profile.alpha_composite(element_icon,(293,28)) - charter_profile.alpha_composite(starts.resize((85,18)),(224,114)) d = ImageDraw.Draw(charter_profile) @@ -80,7 +77,8 @@ async def creat_charter(self,key): icon = await pill.get_dowload_img(key.light_cone.icon, size = (66,66)) charter_profile.alpha_composite(icon,(136,70)) d.text((201,95), f"{self.lang.lvl}: {key.light_cone.level}/{options.max_lvl(key.light_cone.promotion)}", font=self.font_charter, fill=(255, 255, 255, 255)) - + starts = await options.get_stars(key.light_cone.rarity) + charter_profile.alpha_composite(starts.resize((85,18)),(224,114)) background = await _of.light_cone_ups background = background.copy() @@ -92,14 +90,14 @@ async def creat_charter(self,key): d.text((10-x, 4), up, font= font_12, fill=(255, 217, 144, 255)) charter_profile.alpha_composite(background.resize((17,17)), (202,114)) - + return charter_profile async def get_charter(self): task = [] for key in self.profile.characters: task.append(self.creat_charter(key)) - + self.charter = await asyncio.gather(*task) async def creat_avatar(self): @@ -130,7 +128,6 @@ async def creat_avatar(self): async def build(self): - self.background_profile.alpha_composite(self.background_profile_avatar,(29,53)) x,y = 0,407 @@ -144,12 +141,10 @@ async def build(self): if not self.remove_logo: logo = await _of.LOGO_GIT_INV self.background_profile.alpha_composite(logo,(752,0)) - async def start(self): _of.set_mapping(3) - self.font_charter = await pill.get_font(13) await asyncio.gather(self.creat_background(), self.get_charter(), self.creat_avatar()) diff --git a/starrailcard/src/generator/style_relict_score.py b/starrailcard/src/generator/style_relict_score.py index 2560238..e15737d 100644 --- a/starrailcard/src/generator/style_relict_score.py +++ b/starrailcard/src/generator/style_relict_score.py @@ -1,7 +1,8 @@ # Copyright 2024 DEViantUa # All rights reserved. -import asyncio +import anyio +import functools import io from PIL import ImageDraw,Image,ImageFilter,ImageChops, ImageSequence @@ -435,7 +436,7 @@ async def build(self): else: self.background.alpha_composite(bg) - async def start(self): + async def start(self): _of.set_mapping(1) if self.art: @@ -465,20 +466,34 @@ async def start(self): self.element_color = await pill.get_colors(self.art, 15, common=True, radius=5, quality=800) await self.creat_bacground() + + async with anyio.create_task_group() as tasks: + tasks.start_soon(self.creat_light_cone) + tasks.start_soon(self.creat_stats) + tasks.start_soon(self.creat_name) + tasks.start_soon(self.creat_constant) + tasks.start_soon(self.creat_relict_sets) + tasks.start_soon(self.get_score) + tasks.start_soon(self.get_path) + tasks.start_soon(self.creat_seeleland) + + async def wait_all(*funcs): + results = [None] * len(funcs) + + async with anyio.create_task_group() as tasks: + async def process(func, i): + results[i] = await func() + + for i, func in enumerate(funcs): + tasks.start_soon(process, func, i) - await asyncio.gather( - self.creat_light_cone(), - self.creat_stats(), - self.creat_name(), - self.creat_constant(), - self.creat_relict_sets(), - self.get_score(), - self.get_path(), - self.creat_seeleland() - ) - relic_tasks = [self.creat_relict(key) for key in self.data.relics] - self.relict = await asyncio.gather(*relic_tasks) + return results + self.relict = await wait_all(*[ + functools.partial(self.creat_relict, key) + for key in self.data.relics + ]) + await self.creat_score_total() await self.build_relict() @@ -490,7 +505,8 @@ async def start(self): "name": self.data.name, "rarity": self.data.rarity, "card": self.background, - "size": RelictScore.bacground_size + "size": RelictScore.bacground_size, + "color": self.element_color } return data \ No newline at end of file diff --git a/starrailcard/src/generator/style_ticket.py b/starrailcard/src/generator/style_ticket.py index e26e45c..931332d 100644 --- a/starrailcard/src/generator/style_ticket.py +++ b/starrailcard/src/generator/style_ticket.py @@ -1,9 +1,11 @@ # Copyright 2024 DEViantUa # All rights reserved. -import asyncio +import anyio + import io from PIL import ImageDraw,Image,ImageChops, ImageSequence +import functools from ..tools import git, options from ..tools.calculator import stats @@ -86,7 +88,8 @@ async def creat_bacground(self): async def creat_light_cone(self): if self.data.light_cone is None: - return Image.new(Ticket.RGBA, (0, 0), (0, 0, 0, 0)) + self.light_cone_background = Image.new("RGBA", (447, 255), (0, 0, 0, 0)) + return maska = await _of.maska_light_cones frame_light_cones = await _of.frame_light_cones @@ -122,17 +125,17 @@ async def creat_light_cone(self): d = ImageDraw.Draw(background_stat) rank = options.ups(self.data.light_cone.rank) x = int(font.getlength(rank) / 2) - d.text((205 - x, 130), rank, font=font, fill=font_color) + d.text((17 - x, 2), rank, font=font, fill=font_color) font = await pill.get_font(19) max_level = options.max_lvl(self.data.light_cone.promotion) - d.text((226,134), f"{self.lang.lvl}: {self.data.light_cone.level}/{max_level}", font=font, fill=(255, 255, 255, 255)) + d.text((40,9), f"{self.lang.lvl}: {self.data.light_cone.level}/{max_level}", font=font, fill=(255, 255, 255, 255)) - d.text((238, 180), self.data.light_cone.attributes[0].display, font=font, fill=(255, 255, 255, 255)) #HP - d.text((341, 180), self.data.light_cone.attributes[1].display, font=font, fill=(255, 255, 255, 255)) #ATK - d.text((238, 226), self.data.light_cone.attributes[2].display, font=font, fill=(255, 255, 255, 255)) #DEF + d.text((49, 53), self.data.light_cone.attributes[0].display, font=font, fill=(255, 255, 255, 255)) #HP + d.text((152, 53), self.data.light_cone.attributes[1].display, font=font, fill=(255, 255, 255, 255)) #ATK + d.text((49, 100), self.data.light_cone.attributes[2].display, font=font, fill=(255, 255, 255, 255)) #DEF stars = await options.get_stars(self.data.light_cone.rarity,type=3) @@ -144,13 +147,10 @@ async def creat_light_cone(self): self.light_cone_background.alpha_composite(stars,(0,6)) self.light_cone_background.alpha_composite(line, (191, 19)) self.light_cone_background.alpha_composite(name_light_cone, (200, 23)) + + y = int(22 + line.size[1]) - if line.size[1] > 50: - y = int(0 - line.size[1] / 2) - else: - y = -45 - - self.light_cone_background.alpha_composite(background_stat, (0, y)) + self.light_cone_background.alpha_composite(background_stat, (188, y)) async def creat_relict(self,relict): @@ -598,7 +598,6 @@ async def build(self): self.background.alpha_composite(bg) async def start(self): - _of.set_mapping(2) if self.art: @@ -608,7 +607,6 @@ async def start(self): if self.gif: n = 2 frame_count = 0 - color = True with io.BytesIO(self.art) as f: self.art = Image.open(f) for frame in ImageSequence.Iterator(self.art): @@ -627,21 +625,39 @@ async def start(self): self.element_color = (255,213,167,255) await self.creat_bacground() - await asyncio.gather( - self.creat_light_cone(), - self.creat_stats(), - self.creat_name(), - self.creat_constant(), - self.creat_relict_sets(), - self.get_score(), - self.creat_path(), - self.creat_seeleland() - ) - relic_tasks = [self.creat_relict(key) for key in self.data.relics] - self.relict = await asyncio.gather(*relic_tasks) + async with anyio.create_task_group() as tasks: + tasks.start_soon(self.creat_light_cone) + tasks.start_soon(self.creat_stats) + tasks.start_soon(self.creat_name) + tasks.start_soon(self.creat_constant) + tasks.start_soon(self.creat_relict_sets) + tasks.start_soon(self.get_score) + tasks.start_soon(self.creat_path) + tasks.start_soon(self.creat_seeleland) + + async def wait_all(*funcs): + results = [None] * len(funcs) + + async with anyio.create_task_group() as tasks: + async def process(func, i): + results[i] = await func() + + for i, func in enumerate(funcs): + tasks.start_soon(process, func, i) + + return results - await self.creat_score_total() + self.relict = await wait_all(*[ + functools.partial(self.creat_relict, key) + for key in self.data.relics + ]) + + + #self.relict = await asyncio.gather(*relic_tasks) + + + await self.creat_score_total() await self.build_relict() await self.build() diff --git a/starrailcard/src/model/StarRailCard.py b/starrailcard/src/model/StarRailCard.py index ab8ba40..e1a4b84 100644 --- a/starrailcard/src/model/StarRailCard.py +++ b/starrailcard/src/model/StarRailCard.py @@ -10,7 +10,7 @@ from moviepy.editor import ImageSequenceClip except: pass - + import numpy as np from ..tools.ukrainization import TranslateDataManager @@ -38,7 +38,7 @@ class Avatar(BaseModel): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.icon = MAIN_LINK.format(icon = self.icon) + #self.icon = MAIN_LINK.format(icon = self.icon) if UA_LANG: self.name = TranslateDataManager._data.avatar.get(self.id, self.name) @@ -66,14 +66,14 @@ class Config: arbitrary_types_allowed = True async def get_info(self, lang = "en"): - await AioSession.get_session() + #await AioSession.get_session() url = f"https://api.yatta.top/hsr/v2/{lang}/avatar/{self.id}" data = await AioSession.get(url, response_format = "json") data["data"]["icon"] = {"icon": f'https://api.yatta.top/hsr/assets/UI/avatar/medium/{data["data"]["icon"]}.png', "splash": f'https://api.yatta.top/hsr/assets/UI/avatar/large/{data["data"]["icon"]}.png', "avatar": f'https://raw.githubusercontent.com/Mar-7th/StarRailRes/master/icon/avatar/{data["data"]["icon"]}.png' } - await AioSession.close_session() + #await AioSession.close_session() return data["data"] @@ -136,4 +136,4 @@ def get_charter(self, setting = False, name = False): if name: return {name: id for id, name in zip(self.character_id, self.character_name)} - return {id: name for id, name in zip(self.character_id, self.character_name)} + return {id: name for id, name in zip(self.character_id, self.character_name)} \ No newline at end of file diff --git a/starrailcard/src/model/api_mihomo.py b/starrailcard/src/model/api_mihomo.py index 7b668fa..132ec63 100644 --- a/starrailcard/src/model/api_mihomo.py +++ b/starrailcard/src/model/api_mihomo.py @@ -40,8 +40,8 @@ class Avatar(BaseModel): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.icon = MAIN_LINK.format(icon = self.icon) - + #self.icon = MAIN_LINK.format(icon = self.icon) + if UA_LANG: self.name = TranslateDataManager._data.avatar.get(self.id, self.name) @@ -53,7 +53,7 @@ class Player(BaseModel): friend_count: int avatar: Avatar signature: Optional[str] - is_display: bool + is_display: Optional[bool] space_info: Optional[SpaceInfo] class RelicMainAffix(BaseModel): @@ -68,7 +68,7 @@ class RelicMainAffix(BaseModel): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.icon = MAIN_LINK.format(icon = self.icon) + #self.icon = MAIN_LINK.format(icon = self.icon) if UA_LANG: self.name = TranslateDataManager._data.stats.get(self.type, self.name) @@ -86,7 +86,7 @@ class RelicSubAffix(BaseModel): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.icon = MAIN_LINK.format(icon = self.icon) + #self.icon = MAIN_LINK.format(icon = self.icon) if UA_LANG: self.name = TranslateDataManager._data.stats.get(self.type, self.name) @@ -104,7 +104,7 @@ class Relic(BaseModel): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.icon = MAIN_LINK.format(icon = self.icon) + #self.icon = MAIN_LINK.format(icon = self.icon) if UA_LANG: self.set_name = TranslateDataManager._data.relict_sets.get(self.set_id, self.set_name) @@ -121,7 +121,7 @@ class RelicSetProperties(BaseModel): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.icon = MAIN_LINK.format(icon = self.icon) + #self.icon = MAIN_LINK.format(icon = self.icon) if UA_LANG: self.name = TranslateDataManager._data.stats.get(self.type, self.name) @@ -136,7 +136,7 @@ class RelicSet(BaseModel): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.icon = MAIN_LINK.format(icon = self.icon) + #self.icon = MAIN_LINK.format(icon = self.icon) if UA_LANG: self.name = TranslateDataManager._data.relict_sets.get(self.id, self.name) @@ -151,7 +151,7 @@ class LightConeAttributes(BaseModel): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.icon = MAIN_LINK.format(icon = self.icon) + #self.icon = MAIN_LINK.format(icon = self.icon) if UA_LANG: self.name = TranslateDataManager._data.stats.get(self.field, self.name) @@ -163,7 +163,7 @@ class Path(BaseModel): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.icon = MAIN_LINK.format(icon = self.icon) + #self.icon = MAIN_LINK.format(icon = self.icon) if UA_LANG: self.name = TranslateDataManager._data.paths.get(self.id, self.name) @@ -180,13 +180,13 @@ class LightCone(BaseModel): portrait: Optional[str] path: Path attributes: List[LightConeAttributes] - properties: List[LightConeAttributes] + properties: Optional[List[LightConeAttributes]] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.icon = MAIN_LINK.format(icon = self.icon) - self.preview = MAIN_LINK.format(icon = self.preview) - self.portrait = MAIN_LINK.format(icon = self.portrait) + #self.icon = MAIN_LINK.format(icon = self.icon) + #self.preview = MAIN_LINK.format(icon = self.preview) + #self.portrait = MAIN_LINK.format(icon = self.portrait) if UA_LANG: self.name = TranslateDataManager._data.weapons.get(self.id, self.name) @@ -203,7 +203,7 @@ class Element(BaseModel): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.icon = MAIN_LINK.format(icon = self.icon) + #self.icon = MAIN_LINK.format(icon = self.icon) self.color = ColorElement(hex = self.color, rgba = hex_to_rgba(self.color)) if UA_LANG: @@ -233,7 +233,7 @@ class SkillTree(BaseModel): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.icon = MAIN_LINK.format(icon = self.icon) + #self.icon = MAIN_LINK.format(icon = self.icon) class CharacterAttributes(BaseModel): field: Optional[str] @@ -245,7 +245,7 @@ class CharacterAttributes(BaseModel): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.icon = MAIN_LINK.format(icon = self.icon) + #self.icon = MAIN_LINK.format(icon = self.icon) if UA_LANG: self.name = TranslateDataManager._data.stats.get(self.field, self.name) @@ -270,19 +270,19 @@ class Character(BaseModel): relic_sets: Optional[List[RelicSet]] attributes: List[CharacterAttributes] additions: List[CharacterAttributes] - properties: List[CharacterAttributes] + properties: Optional[List[CharacterAttributes]] pos: list def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.icon = MAIN_LINK.format(icon = self.icon) - self.preview = MAIN_LINK.format(icon = self.preview) - self.portrait = MAIN_LINK.format(icon = self.portrait) - self.pos = self.pos[0] - new_rank_icons = [] - for key in self.rank_icons: - new_rank_icons.append(MAIN_LINK.format(icon = key)) - self.rank_icons = new_rank_icons + #self.icon = MAIN_LINK.format(icon = self.icon) + #self.preview = MAIN_LINK.format(icon = self.preview) + #self.portrait = MAIN_LINK.format(icon = self.portrait) + #self.pos = self.pos[0] + #new_rank_icons = [] + #for key in self.rank_icons: + #new_rank_icons.append(MAIN_LINK.format(icon = key)) + #self.rank_icons = new_rank_icons if UA_LANG: self.name = TranslateDataManager._data.avatar.get(self.id, self.name) @@ -290,3 +290,68 @@ def __init__(self, *args, **kwargs): class MiHoMoApi(BaseModel): player: Player characters: List[Character] + dont_update_link: Optional[bool] = False + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if not self.dont_update_link: + # Формирование новых ссылок на иконки + self.player.avatar.icon = MAIN_LINK.format(icon = self.player.avatar.icon) + for character in self.characters: + character.icon = MAIN_LINK.format(icon=character.icon) + character.preview = MAIN_LINK.format(icon=character.preview) + character.portrait = MAIN_LINK.format(icon=character.portrait) + character.rank_icons = [MAIN_LINK.format(icon=icon) for icon in character.rank_icons] + + if character.light_cone: + character.light_cone.icon = MAIN_LINK.format(icon=character.light_cone.icon) + character.light_cone.preview = MAIN_LINK.format(icon=character.light_cone.preview) + character.light_cone.portrait = MAIN_LINK.format(icon=character.light_cone.portrait) + + for relic in character.relics: + relic.icon = MAIN_LINK.format(icon=relic.icon) + for sub_affix in relic.sub_affix: + sub_affix.icon = MAIN_LINK.format(icon=sub_affix.icon) + relic.main_affix.icon = MAIN_LINK.format(icon=relic.main_affix.icon) + + for relic_set in character.relic_sets: + relic_set.icon = MAIN_LINK.format(icon=relic_set.icon) + for property in relic_set.properties: + property.icon = MAIN_LINK.format(icon=property.icon) + + for skill in character.skills: + if skill.icon: + skill.icon = MAIN_LINK.format(icon=skill.icon) + + for skill_tree in character.skill_trees: + if skill_tree.icon: + skill_tree.icon = MAIN_LINK.format(icon=skill_tree.icon) + + for attribute in character.attributes: + attribute.icon = MAIN_LINK.format(icon=attribute.icon) + + for addition in character.additions: + addition.icon = MAIN_LINK.format(icon=addition.icon) + + if character.properties: + for property in character.properties: + property.icon = MAIN_LINK.format(icon=property.icon) + + character.element.icon = MAIN_LINK.format(icon = character.element.icon) + + for skills in character.skills: + if "icon" in skills: + skills = MAIN_LINK.format(icon = skills) + + character.path.icon = MAIN_LINK.format(icon=character.path.icon) + + + + if not character.light_cone is None: + for light_cone_attr in character.light_cone.attributes: + light_cone_attr.icon = MAIN_LINK.format(icon=light_cone_attr.icon) + + if character.light_cone.properties: + for property in character.light_cone.properties: + property.icon = MAIN_LINK.format(icon=property.icon) + diff --git a/starrailcard/src/tools/calculator/src/assets/score.json b/starrailcard/src/tools/calculator/src/assets/score.json index 2147a96..d8d5a03 100644 --- a/starrailcard/src/tools/calculator/src/assets/score.json +++ b/starrailcard/src/tools/calculator/src/assets/score.json @@ -2182,6 +2182,65 @@ }, "max": 9.4 }, + "1301": { + "main": { + "1": { + "HPDelta": 1 + }, + "2": { + "AttackDelta": 1 + }, + "3": { + "HPAddedRatio": 0.4, + "AttackAddedRatio": 0, + "DefenceAddedRatio": 0.4, + "CriticalChanceBase": 0, + "CriticalDamageBase": 0, + "HealRatioBase": 1, + "StatusProbabilityBase": 0 + }, + "4": { + "HPAddedRatio": 0.4, + "AttackAddedRatio": 0, + "DefenceAddedRatio": 0.4, + "SpeedDelta": 1 + }, + "5": { + "HPAddedRatio": 1, + "AttackAddedRatio": 0, + "DefenceAddedRatio": 1, + "PhysicalAddedRatio": 0, + "FireAddedRatio": 0, + "IceAddedRatio": 0, + "ThunderAddedRatio": 0, + "WindAddedRatio": 0, + "QuantumAddedRatio": 0, + "ImaginaryAddedRatio": 0 + }, + "6": { + "BreakDamageAddedRatioBase": 1, + "SPRatioBase": 1, + "HPAddedRatio": 0.4, + "AttackAddedRatio": 0, + "DefenceAddedRatio": 0.4 + } + }, + "weight": { + "HPDelta": 0.1, + "AttackDelta": 0, + "DefenceDelta": 0.1, + "HPAddedRatio": 0.4, + "AttackAddedRatio": 0.8, + "DefenceAddedRatio": 0.4, + "SpeedDelta": 0.8, + "CriticalChanceBase": 0, + "CriticalDamageBase": 0, + "StatusProbabilityBase": 0, + "StatusResistanceBase": 0.6, + "BreakDamageAddedRatioBase": 1 + }, + "max": 10.18 + }, "1302": { "main": { "1": { @@ -2300,6 +2359,65 @@ }, "max": 10.0 }, + "1304": { + "main": { + "1": { + "HPDelta": 1 + }, + "2": { + "AttackDelta": 1 + }, + "3": { + "HPAddedRatio": 1, + "AttackAddedRatio": 0, + "DefenceAddedRatio": 1, + "CriticalChanceBase": 0, + "CriticalDamageBase": 0, + "HealRatioBase": 0, + "StatusProbabilityBase": 0 + }, + "4": { + "HPAddedRatio": 0.8, + "AttackAddedRatio": 0, + "DefenceAddedRatio": 0.8, + "SpeedDelta": 1 + }, + "5": { + "HPAddedRatio": 1, + "AttackAddedRatio": 0, + "DefenceAddedRatio": 1, + "PhysicalAddedRatio": 0, + "FireAddedRatio": 0, + "IceAddedRatio": 0, + "ThunderAddedRatio": 0, + "WindAddedRatio": 0, + "QuantumAddedRatio": 0, + "ImaginaryAddedRatio": 0 + }, + "6": { + "BreakDamageAddedRatioBase": 1, + "SPRatioBase": 1, + "HPAddedRatio": 0.8, + "AttackAddedRatio": 0, + "DefenceAddedRatio": 0.8 + } + }, + "weight": { + "HPDelta": 0.3, + "AttackDelta": 0, + "DefenceDelta": 0.3, + "HPAddedRatio": 0.8, + "AttackAddedRatio": 0, + "DefenceAddedRatio": 0.8, + "SpeedDelta": 1, + "CriticalChanceBase": 0, + "CriticalDamageBase": 0, + "StatusProbabilityBase": 0, + "StatusResistanceBase": 0.5, + "BreakDamageAddedRatioBase": 1 + }, + "max": 10.0 + }, "1305": { "main": { "1": { @@ -2369,23 +2487,23 @@ }, "3": { "HPAddedRatio": 0, - "AttackAddedRatio": 0.8, - "DefenceAddedRatio": 0, - "CriticalChanceBase": 1, + "AttackAddedRatio": 0, + "DefenceAddedRatio": 0.8, + "CriticalChanceBase": 0, "CriticalDamageBase": 1, "HealRatioBase": 0, "StatusProbabilityBase": 0 }, "4": { "HPAddedRatio": 0, - "AttackAddedRatio": 0.8, - "DefenceAddedRatio": 0, + "AttackAddedRatio": 0, + "DefenceAddedRatio": 0.8, "SpeedDelta": 1 }, "5": { - "HPAddedRatio": 0, - "AttackAddedRatio": 0.8, - "DefenceAddedRatio": 0, + "HPAddedRatio": 1, + "AttackAddedRatio": 0, + "DefenceAddedRatio": 1, "PhysicalAddedRatio": 0, "FireAddedRatio": 0, "IceAddedRatio": 0, @@ -2396,24 +2514,24 @@ }, "6": { "BreakDamageAddedRatioBase": 0, - "SPRatioBase": 0, + "SPRatioBase": 1, "HPAddedRatio": 0, - "AttackAddedRatio": 1, + "AttackAddedRatio": 0, "DefenceAddedRatio": 0 } }, "weight": { "HPDelta": 0, - "AttackDelta": 0.1, - "DefenceDelta": 0, + "AttackDelta": 0, + "DefenceDelta": 0.3, "HPAddedRatio": 0, - "AttackAddedRatio": 0.6, - "DefenceAddedRatio": 0, - "SpeedDelta": 0.8, - "CriticalChanceBase": 1, + "AttackAddedRatio": 0, + "DefenceAddedRatio": 0.8, + "SpeedDelta": 1, + "CriticalChanceBase": 0, "CriticalDamageBase": 1, "StatusProbabilityBase": 0, - "StatusResistanceBase": 0, + "StatusResistanceBase": 0.6, "BreakDamageAddedRatioBase": 0 }, "max": 0.0 @@ -2477,6 +2595,65 @@ }, "max": 0.0 }, + "1308": { + "main": { + "1": { + "HPDelta": 1 + }, + "2": { + "AttackDelta": 1 + }, + "3": { + "HPAddedRatio": 0, + "AttackAddedRatio": 0.7, + "DefenceAddedRatio": 0, + "CriticalChanceBase": 1, + "CriticalDamageBase": 1, + "HealRatioBase": 0, + "StatusProbabilityBase": 0 + }, + "4": { + "HPAddedRatio": 0, + "AttackAddedRatio": 1, + "DefenceAddedRatio": 0, + "SpeedDelta": 1 + }, + "5": { + "HPAddedRatio": 0, + "AttackAddedRatio": 1, + "DefenceAddedRatio": 0, + "PhysicalAddedRatio": 0, + "FireAddedRatio": 0, + "IceAddedRatio": 0, + "ThunderAddedRatio": 1, + "WindAddedRatio": 0, + "QuantumAddedRatio": 0, + "ImaginaryAddedRatio": 0 + }, + "6": { + "BreakDamageAddedRatioBase": 0, + "SPRatioBase": 0, + "HPAddedRatio": 0, + "AttackAddedRatio": 1, + "DefenceAddedRatio": 0 + } + }, + "weight": { + "HPDelta": 0, + "AttackDelta": 0.3, + "DefenceDelta": 0, + "HPAddedRatio": 0, + "AttackAddedRatio": 0.7, + "DefenceAddedRatio": 0, + "SpeedDelta": 0.6, + "CriticalChanceBase": 1, + "CriticalDamageBase": 0.9, + "StatusProbabilityBase": 0, + "StatusResistanceBase": 0, + "BreakDamageAddedRatioBase": 0 + }, + "max": 0.0 + }, "1312": { "main": { "1": { diff --git a/starrailcard/src/tools/calculator/stats.py b/starrailcard/src/tools/calculator/stats.py index d389735..8abe796 100644 --- a/starrailcard/src/tools/calculator/stats.py +++ b/starrailcard/src/tools/calculator/stats.py @@ -19,6 +19,7 @@ def decrypt_url(encrypted_url, key): _LINK_SCORE = "https://raw.githubusercontent.com/"+decrypt_url(n,42)+"/"+decrypt_url(r,35)+"/"+"main"+"/generate/weight.json" _LINK_DATA = "https://raw.githubusercontent.com/"+decrypt_url(n,42)+"/"+decrypt_url(r,35)+"/"+"main"+"/generate/{name}.json" +_LINK_SCORE_V2 = "https://raw.githubusercontent.com/"+decrypt_url(n,42)+"/StarRailScore/master/score.json" _PATH = Path(__file__).parent /"src"/"assets" @@ -56,7 +57,7 @@ def __init__(self, data) -> None: self.result = { "score": {}, - "total_score": {"count": 0, "rank": {"name": "N/A", "color": None}}, + "total_score": {"count": 0, "rank": {"name": "N/A", "color": (255,255,255)}}, "bad": [] } @@ -103,9 +104,10 @@ async def get_relic_score(self, chara_id, relic_json): async def start(self): if not self.data.id in self.score: - await self.update_score() + await self.update_score(self.data.id) self.score = open_score("score") - + if not self.data.id in self.score: + return self.result for key in self.data.relics: relic_score_json, bad = await self.get_relic_score(self.data.id,key) self.result["bad"] = list(set(self.result["bad"] + bad)) @@ -125,12 +127,14 @@ async def start(self): return self.result - async def update_score(self): + async def update_score(self, charter_id = None): for key in _PATH_FILE_NAME: if key == "score": data = await get_score(_LINK_SCORE) + if not charter_id is None: + if data.get(str(charter_id)) is None: + data = await get_score(_LINK_SCORE_V2) else: data = await get_score(_LINK_DATA.format(name=key)) - - save(key,data) + save(key,data) \ No newline at end of file diff --git a/starrailcard/src/tools/enums.py b/starrailcard/src/tools/enums.py index 537cb0e..33e1d8c 100644 --- a/starrailcard/src/tools/enums.py +++ b/starrailcard/src/tools/enums.py @@ -1,7 +1,7 @@ # Copyright 2024 DEViantUa # All rights reserved. -from enum import Enum +from enum import Enum from pathlib import Path class Ukrainization(Enum): @@ -18,8 +18,10 @@ def __iter__(cls): class PathData(Enum): UKRAINIZATION = Path(__file__).parent.parent / "data" + ENKA = Path(__file__).parent.parent / "assets" / "enka_api" + ENKA_INDEX = Path(__file__).parent.parent / "assets" / "enka_api" / "index" class Style(Enum): RELICT_SCORE = 1 TICKET = 2 - ENKA = 3 + ENKA = 3 \ No newline at end of file diff --git a/starrailcard/src/tools/http.py b/starrailcard/src/tools/http.py index eba11a6..de11558 100644 --- a/starrailcard/src/tools/http.py +++ b/starrailcard/src/tools/http.py @@ -4,21 +4,152 @@ import aiohttp import asyncio import json +import inspect +import threading +import weakref + +class SharedObject: + __slots__ = ( + 'func', 'args', 'kwargs', + 'object', + 'users', + 'sync_lock', 'async_locks', + ) + + def __init__(self, func, /, *args, **kwargs): + self.func = func + self.args = args + self.kwargs = kwargs + + self.sync_lock = threading.Lock() + self.async_locks = weakref.WeakKeyDictionary() + + self.users = 0 + + @property + def async_lock(self, /): + token = asyncio.get_running_loop() + + try: + lock = self.async_locks[token] + except KeyError: + self.async_locks[token] = lock = asyncio.Lock() + + return lock + + def __enter__(self, /): + object = None + + with self.sync_lock: + if self.users == 0: + object = self.func(*self.args, **self.kwargs) + + if hasattr(object, '__enter__'): + object = object.__enter__() + + self.object = object + else: + object = self.object + + self.users += 1 + + return object + + def __exit__(self, /, *exc_info): + result = None + + with self.sync_lock: + self.users -= 1 + + if self.users == 0: + object = self.object + + if hasattr(object, '__exit__'): + result = object.__exit__(*exc_info) + elif hasattr(object, 'close'): + object.close() + + del self.object + + return result + + async def __aenter__(self, /): + object = None + + with self.sync_lock: + async with self.async_lock: + if self.users == 0: + object = self.func(*self.args, **self.kwargs) + + if inspect.isawaitable(object): + object = await object + + if hasattr(object, '__aenter__'): + object = await object.__aenter__() + elif hasattr(object, '__enter__'): + object = object.__enter__() + + self.object = object + else: + object = self.object + + self.users += 1 + + return object + + async def __aexit__(self, /, *exc_info): + result = None + + with self.sync_lock: + async with self.async_lock: + self.users -= 1 + + if self.users == 0: + object = self.object + + if hasattr(object, '__aexit__'): + result = await object.__aexit__(*exc_info) + elif hasattr(object, 'aclose'): + await object.aclose() + elif hasattr(object, 'close'): + if inspect.isawaitable(coro := object.close()): + await coro + elif hasattr(object, '__exit__'): + result = object.__exit__(*exc_info) + + del self.object + + return result -class AioSession: - _session = None - _semaphore = asyncio.Semaphore(5) + +class AioSession: + session = SharedObject(aiohttp.ClientSession) + proxy = None + + @classmethod + async def enter(cls, proxy = None, /): + cls.proxy = proxy + + return await cls.session.__aenter__() + + @classmethod + async def exit(cls, /, *exc_info): + if not exc_info: + exc_info = (None, None, None) + + return await cls.session.__aexit__(*exc_info) + @classmethod async def creat_session(cls): """Creates a session Returns: aiohttp.ClientSession: The session instance. """ - cls._session = aiohttp.ClientSession() + cls.session = aiohttp.ClientSession() + + return cls.session - return cls._session - @classmethod async def get_session(cls): """ @@ -27,20 +158,20 @@ async def get_session(cls): Returns: aiohttp.ClientSession: The session instance. """ - if cls._session is None: - cls._session = aiohttp.ClientSession() - return cls._session + if cls.session is None: + cls.session = aiohttp.ClientSession() + return cls.session @classmethod async def close_session(cls): """ Closes the session if it exists. """ - if cls._session is not None: - await cls._session.close() - cls._session = None + if cls.session is not None: + await cls.session.close() + cls.session = None - @classmethod + '''@classmethod def session(cls): """ Returns the current session instance. @@ -48,10 +179,10 @@ def session(cls): Returns: aiohttp.ClientSession: The current session instance, or None if the session is not created. """ - return cls._session + return cls.session''' @classmethod - async def get(cls, url, headers=None, response_format='json'): + async def get(cls, url, headers=None, response_format='json', proxy=None, **kwargs): """ Sends a GET request using the current session instance. @@ -59,6 +190,7 @@ async def get(cls, url, headers=None, response_format='json'): url (str): The URL to send the request to. headers (dict): Optional headers to include in the request. response_format (str): The format of the response data to return. + **kwargs: Additional arguments to pass to the aiohttp GET method. Returns: Depends on the specified response format: @@ -66,20 +198,20 @@ async def get(cls, url, headers=None, response_format='json'): - str: for 'text' format; - bytes: for 'bytes' format. """ - response = None - try: - if cls._session is not None: - async with cls._session.get(url, headers=headers) as response: + async with cls.session as session: + if not cls.proxy is None: + proxy = cls.proxy + + try: + async with session.get(url, headers=headers, proxy=proxy, **kwargs) as response: data = await cls.process_response(response, response_format) return data - except Exception as e: - print(f"Error during GET request: {e}") - finally: - if response: - await response.release() + except Exception as e: + print(f"Error during GET request: {e}") + @classmethod - async def post(cls, url, data=None, headers=None, response_format='json'): + async def post(cls, url, data=None, headers=None, response_format='json', proxy=None, **kwargs): """ Sends a POST request using the current session instance. @@ -88,6 +220,7 @@ async def post(cls, url, data=None, headers=None, response_format='json'): data: The data to send in the request body. headers (dict): Optional headers to include in the request. response_format (str): The format of the response data to return. + **kwargs: Additional arguments to pass to the aiohttp POST method. Returns: Depends on the specified response format: @@ -95,20 +228,19 @@ async def post(cls, url, data=None, headers=None, response_format='json'): - str: for 'text' format; - bytes: for 'bytes' format. """ - response = None - try: - if cls._session is not None: - async with cls._session.post(url, data=data, headers=headers) as response: + if not cls.proxy is None: + proxy = cls.proxy + + async with cls.session as session: + try: + async with session.post(url, data=data, headers=headers, proxy=proxy, **kwargs) as response: data = await cls.process_response(response, response_format) return data - except Exception as e: - print(f"Error during POST request: {e}") - finally: - if response: - await response.release() + except Exception as e: + print(f"Error during POST request: {e}") @classmethod - async def request(cls, method, url, headers=None, response_format='json', **kwargs): + async def request(cls, method, url, headers=None, response_format='json', proxy=None, **kwargs): """ Sends a custom HTTP request using the current session instance. @@ -125,17 +257,16 @@ async def request(cls, method, url, headers=None, response_format='json', **kwar - str: for 'text' format; - bytes: for 'bytes' format. """ - response = None - try: - if cls._session is not None: - async with cls._session.request(method, url, headers=headers, **kwargs) as response: + if not cls.proxy is None: + proxy = cls.proxy + + async with cls.session as session: + try: + async with session.request(method, url, headers=headers, proxy=proxy, **kwargs) as response: data = await cls.process_response(response, response_format) return data - except Exception as e: - print(f"Error during custom request: {e}") - finally: - if response: - await response.release() + except Exception as e: + print(f"Error during custom request: {e}") @classmethod @@ -153,6 +284,7 @@ async def process_response(cls, response, response_format): - str: for 'text' format; - bytes: for 'bytes' format. """ + if response_format == 'json': try: return await response.json() diff --git a/starrailcard/src/tools/json_data.py b/starrailcard/src/tools/json_data.py index 19a7039..2461175 100644 --- a/starrailcard/src/tools/json_data.py +++ b/starrailcard/src/tools/json_data.py @@ -4,7 +4,6 @@ import json import aiofiles - class JsonManager: def __init__(self, file_path): self.file_path = file_path @@ -21,7 +20,7 @@ async def read(self): async def write(self, data): try: - async with aiofiles.open(self.file_path, mode='w') as file: + async with aiofiles.open(self.file_path, mode='w', encoding="utf-8") as file: await file.write(json.dumps(data, indent=4, ensure_ascii=False)) except Exception as e: print(f"Error writing to file '{self.file_path}': {e}") \ No newline at end of file diff --git a/starrailcard/src/tools/options.py b/starrailcard/src/tools/options.py index bcfbc17..0c0ce16 100644 --- a/starrailcard/src/tools/options.py +++ b/starrailcard/src/tools/options.py @@ -1,18 +1,35 @@ # Copyright 2024 DEViantUa # All rights reserved. - import random import aiofiles import io from PIL import Image import os +import sys import datetime from .git import ImageCache from .http import AioSession +from ... import __version__ _git = ImageCache() +def get_user_agent(user_agent): + python_version = sys.version_info + + if not user_agent is None: + if user_agent == "StarRailCard/{version} (Python {major}.{minor}.{micro})".format(version=__version__,major=python_version.major,minor=python_version.minor,micro=python_version.micro): + return user_agent + + return f"StarRailCad/{__version__}: {user_agent}" + + return "StarRailCard/{version} (Python {major}.{minor}.{micro})".format( + version=__version__, + major=python_version.major, + minor=python_version.minor, + micro=python_version.micro + ) + _DEFAULT_SCORE = {'count': 0, 'rolls': {}, 'rank': {'name': 'N/A', @@ -72,7 +89,7 @@ async def get_charter_id(data): return data async def style_setting(style, settings): - if str(style) in ["1","2","3"]: + if str(style) in ["1","2"]: return style, settings return 1, {} @@ -202,7 +219,12 @@ async def get_seeleland(uid, charter_id): if type(data) == list: for key in data: if "lb" in key: - return key["lb"]["tutorial"] + if "tutorial" in key["lb"]: + return key["lb"]["tutorial"] + else: + '''for keys in key["lb"]: + return key["lb"][keys]''' + return None else: for key in data: return data[key] diff --git a/starrailcard/src/tools/pill/image_controle.py b/starrailcard/src/tools/pill/image_controle.py index 324cb74..4ae4ac2 100644 --- a/starrailcard/src/tools/pill/image_controle.py +++ b/starrailcard/src/tools/pill/image_controle.py @@ -1,16 +1,18 @@ # Copyright 2024 DEViantUa # All rights reserved. -from PIL import Image -from io import BytesIO - +import os import re import json - +from PIL import Image +from io import BytesIO +from ..git import assets from .. import cashe, http _caches = cashe.Cache.get_cache() +_boost_speed = False + async def resize_image(image, scale): width, height = image.size @@ -62,40 +64,89 @@ async def get_centr_scale(size, file_name): return background_image -async def get_dowload_img(link,size = None, thumbnail_size = None, gif = False): +async def get_image_from_boost_speed(link): + path = f"/boost_speed/{link.split('master')[1]}" + full_path = os.path.join(assets, path.lstrip('/')) + + if os.path.exists(full_path): + try: + return Image.open(full_path).convert("RGBA") + except Exception as e: + raise IOError(f"Error reading image file: {e}") # Прекращаем выполнение функции + else: + return None + +async def download_image(link, headers=None): + try: + image = await http.AioSession.get(link, headers=headers, response_format="bytes") + return image + except: + raise TypeError(f"Error Dowload image: {link}") + +async def save_image(image, full_path): + directory = os.path.dirname(full_path) + if not os.path.exists(directory): + os.makedirs(directory) + + with open(full_path, 'wb') as f: + f.write(image) + +async def open_image(image_data): + try: + return Image.open(BytesIO(image_data)).convert("RGBA") + except Exception as e: + raise TypeError(f"Error Open image: {e}") + +async def get_dowload_img(link, size=None, thumbnail_size=None, gif=False): cache_key = json.dumps((link, size, thumbnail_size), sort_keys=True) - + image_boost = None + + if _boost_speed: + if "StarRailRes" in link: + image_boost = await get_image_from_boost_speed(link) + if not gif: if cache_key in _caches: return _caches[cache_key] - headers_p = None - if "pximg" in link: - headers_p = { - "referer": "https://www.pixiv.net/", - } - try: - image = await http.AioSession.get(link,headers=headers_p, response_format= "bytes") - except: - raise - if gif: - return image + if image_boost is None: + headers_p = None + + if "pximg" in link: + headers_p = { + "referer": "https://www.pixiv.net/", + } + image = await download_image(link, headers_p) - image = Image.open(BytesIO(image)).convert("RGBA") - if size: - image = image.resize(size) - _caches[cache_key] = image - return image - elif thumbnail_size: - image.thumbnail(thumbnail_size) - _caches[cache_key] = image - return image + if _boost_speed: + if "StarRailRes" in link: + full_path = os.path.join(assets, f"/boost_speed/{link.split('master')[1]}".lstrip('/')) + await save_image(image, full_path) + + image = await open_image(image) else: - _caches[cache_key] = image - return image + image = image_boost + if gif: + return image + + try: + if size: + image = image.resize(size) + _caches[cache_key] = image + return image + elif thumbnail_size: + image.thumbnail(thumbnail_size) + _caches[cache_key] = image + return image + else: + _caches[cache_key] = image + return image + except Exception as e: + raise TypeError(f"Error setting image: {link}") + async def crop_image(img): width, height = img.size target_pixel_x = 275 diff --git a/starrailcard/src/tools/ukrainization.py b/starrailcard/src/tools/ukrainization.py index 340b264..17d2174 100644 --- a/starrailcard/src/tools/ukrainization.py +++ b/starrailcard/src/tools/ukrainization.py @@ -27,13 +27,13 @@ def __init__(self): async def load_translate_data(self): for file in Ukrainization: - self.data[file] = await JsonManager(str(PathData.UKRAINIZATION.value / f'{file}.json')).read() + self.data[file.value] = await JsonManager(str(PathData.UKRAINIZATION.value / f'{file.value}.json')).read() TranslateDataManager._data = ukrainization_model.UkrainizationModel(**self.data) async def update(self): for file in Ukrainization: - data = await AioSession.get(MAIN_LINK.format(file = f"{file}.json")) - await JsonManager(str(PathData.UKRAINIZATION.value / f'{file}.json')).write(data) + data = await AioSession.get(MAIN_LINK.format(file = f"{file.value}.json")) + await JsonManager(str(PathData.UKRAINIZATION.value / f'{file.value}.json')).write(data) async def check_update(self): keys = await JsonManager(str(PathData.UKRAINIZATION.value / "keys.json")).read()