diff --git a/core/services/game/cache.py b/core/services/game/cache.py index cfb6982..48531ca 100644 --- a/core/services/game/cache.py +++ b/core/services/game/cache.py @@ -3,7 +3,7 @@ from typing import List from core.base_service import BaseService from core.dependence.redisdb import RedisDB -__all__ = ["GameCache", "GameCacheForStrategy", "GameCacheForMaterial"] +__all__ = ["GameCache", "GameCacheForStrategy"] class GameCache: @@ -27,7 +27,3 @@ class GameCache: class GameCacheForStrategy(BaseService.Component, GameCache): qname = "game:strategy" - - -class GameCacheForMaterial(BaseService.Component, GameCache): - qname = "game:material" diff --git a/core/services/game/services.py b/core/services/game/services.py index 9e3abf6..5b30a3f 100644 --- a/core/services/game/services.py +++ b/core/services/game/services.py @@ -1,10 +1,10 @@ from typing import List, Optional from core.base_service import BaseService -from core.services.game.cache import GameCacheForMaterial, GameCacheForStrategy +from core.services.game.cache import GameCacheForStrategy from modules.apihelper.client.components.hyperion import Hyperion -__all__ = ("GameMaterialService", "GameStrategyService") +__all__ = "GameStrategyService" class GameStrategyService(BaseService): @@ -50,52 +50,3 @@ class GameStrategyService(BaseService): artwork_info = await self._hyperion.get_post_info(2, post_id) await self._cache.set_url_list(character_name, artwork_info.image_urls) return artwork_info.image_urls[0] - - -class GameMaterialService(BaseService): - def __init__(self, cache: GameCacheForMaterial, collections: Optional[List[int]] = None): - self._cache = cache - self._hyperion = Hyperion() - self._collections = [428421, 1362644] if collections is None else collections - self._special = ["雷电将军", "珊瑚宫心海", "菲谢尔", "托马", "八重神子", "九条裟罗", "辛焱", "神里绫华"] - - async def _get_material_from_hyperion(self, collection_id: int, character_name: str) -> int: - post_id: int = -1 - post_full_in_collection = await self._hyperion.get_post_full_in_collection(collection_id) - for post_data in post_full_in_collection["posts"]: - topics = post_data["topics"] - for topic in topics: - if character_name == topic["name"]: - post_id = int(post_data["post"]["post_id"]) - break - if post_id != -1: - break - subject = post_data["post"]["subject"] - if character_name in subject: - post_id = int(post_data["post"]["post_id"]) - if post_id != -1: - break - return post_id - - async def get_material(self, character_name: str) -> str: - cache = await self._cache.get_url_list(character_name) - if len(cache) >= 1: - image_url_list = cache - else: - for collection_id in self._collections: - post_id = await self._get_material_from_hyperion(collection_id, character_name) - if post_id != -1: - break - else: - return "" - - artwork_info = await self._hyperion.get_post_info(2, post_id) - image_url_list = artwork_info.image_urls - if collection_id == 1362644 or character_name in self._special: - image_url_list.pop(0) - await self._cache.set_url_list(character_name, image_url_list) - if len(image_url_list) == 0: - return "" - if len(image_url_list) == 1: - return image_url_list[0] - return image_url_list[1] diff --git a/modules/apihelper/client/components/remote.py b/modules/apihelper/client/components/remote.py index c6eff17..c5a103e 100644 --- a/modules/apihelper/client/components/remote.py +++ b/modules/apihelper/client/components/remote.py @@ -11,6 +11,7 @@ class Remote: BASE_URL = f"https://raw.githubusercontent.com/{RESOURCE_DEFAULT_PATH}" CALENDAR = f"{BASE_URL}calendar.json" BIRTHDAY = f"{BASE_URL}birthday.json" + MATERIAL = f"{BASE_URL}roles_material.json" @staticmethod async def get_remote_calendar() -> Dict[str, Dict]: @@ -35,3 +36,15 @@ class Remote: return {} except HTTPError: return {} + + @staticmethod + async def get_remote_material() -> Dict[str, List[str]]: + """获取云端角色材料""" + try: + async with AsyncClient() as client: + req = await client.get(Remote.MATERIAL) + if req.status_code == 200: + return req.json() + return {} + except HTTPError: + return {} diff --git a/modules/material/talent.py b/modules/material/talent.py new file mode 100644 index 0000000..6db1537 --- /dev/null +++ b/modules/material/talent.py @@ -0,0 +1,28 @@ +from typing import List + + +class TalentMaterials: + def __init__(self, amount: List[int]): + self.amount = amount + + def cal_materials(self) -> List[int]: + """ + :return: [摩拉,天赋书x3,怪物素材x3,皇冠,周本素材] + """ + cost = [0, 0, 0, 0, 0, 0, 0, 0, 0] + cost_list = [ + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [12500, 3, 0, 0, 6, 0, 0, 0, 0], + [17500, 0, 2, 0, 0, 3, 0, 0, 0], + [25000, 0, 4, 0, 0, 4, 0, 0, 0], + [30000, 0, 6, 0, 0, 6, 0, 0, 0], + [37500, 0, 9, 0, 0, 9, 0, 0, 0], + [120000, 0, 0, 4, 0, 0, 4, 0, 1], + [260000, 0, 0, 6, 0, 0, 6, 0, 1], + [450000, 0, 0, 12, 0, 0, 9, 0, 2], + [700000, 0, 0, 16, 0, 0, 12, 1, 2], + ] + for i in self.amount: + for level in range(1, i): + cost = list(map(lambda x: x[0] + x[1], zip(cost, cost_list[level]))) + return cost diff --git a/plugins/genshin/material.py b/plugins/genshin/material.py index d93f3a2..1ea8270 100644 --- a/plugins/genshin/material.py +++ b/plugins/genshin/material.py @@ -1,10 +1,17 @@ +import re + from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update -from telegram.constants import ChatAction, ParseMode +from telegram.constants import ChatAction from telegram.ext import CallbackContext, CommandHandler, MessageHandler, filters +from core.dependence.assets import AssetsService from core.plugin import Plugin, handler -from core.services.game.services import GameMaterialService +from core.services.template.services import TemplateService +from metadata.genshin import MATERIAL_DATA from metadata.shortname import roleToName +from modules.apihelper.client.components.remote import Remote +from modules.material.talent import TalentMaterials +from modules.wiki.character import Character from utils.log import logger __all__ = ("MaterialPlugin",) @@ -15,8 +22,184 @@ class MaterialPlugin(Plugin): KEYBOARD = [[InlineKeyboardButton(text="查看角色培养素材列表并查询", switch_inline_query_current_chat="查看角色培养素材列表并查询")]] - def __init__(self, game_material_service: GameMaterialService = None): - self.game_material_service = game_material_service + def __init__( + self, + template_service: TemplateService, + assets_service: AssetsService, + ): + self.roles_material = {} + self.assets_service = assets_service + self.template_service = template_service + + async def initialize(self): + await self._refresh() + + async def _refresh(self): + self.roles_material = await Remote.get_remote_material() + + async def _parse_material(self, data: dict, character_name: str, talent_level: str) -> dict: + data = data["data"] + if character_name not in data.keys(): + return {} + character = self.assets_service.avatar(character_name) + level_up_material = self.assets_service.material(data[character_name]["level_up_materials"]) + ascension_material = self.assets_service.material(data[character_name]["ascension_materials"]) + local_material = self.assets_service.material(data[character_name]["materials"][0]) + enemy_material = self.assets_service.material(data[character_name]["materials"][1]) + level_up_materials = [ + { + "num": 46, + "rarity": MATERIAL_DATA[str(level_up_material.id)]["rank"], + "icon": (await level_up_material.icon()).as_uri(), + "name": data[character_name]["level_up_materials"], + }, + { + "num": 419, + "rarity": 4, + "icon": (await self.assets_service.material(104003).icon()).as_uri(), + "name": "大英雄的经验", + }, + { + "num": 1, + "rarity": 2, + "icon": (await ascension_material.icon()).as_uri(), + "name": MATERIAL_DATA[str(ascension_material.id)]["name"], + }, + { + "num": 9, + "rarity": 3, + "icon": (await self.assets_service.material(ascension_material.id - 1).icon()).as_uri(), + "name": MATERIAL_DATA[str(ascension_material.id - 1)]["name"], + }, + { + "num": 9, + "rarity": 4, + "icon": (await self.assets_service.material(str(ascension_material.id - 2)).icon()).as_uri(), + "name": MATERIAL_DATA[str(ascension_material.id - 2)]["name"], + }, + { + "num": 6, + "rarity": 5, + "icon": (await self.assets_service.material(ascension_material.id - 3).icon()).as_uri(), + "name": MATERIAL_DATA[str(ascension_material.id - 3)]["name"], + }, + { + "num": 168, + "rarity": MATERIAL_DATA[str(local_material.id)]["rank"], + "icon": (await local_material.icon()).as_uri(), + "name": MATERIAL_DATA[str(local_material.id)]["name"], + }, + { + "num": 18, + "rarity": MATERIAL_DATA[str(enemy_material.id)]["rank"], + "icon": (await self.assets_service.material(enemy_material.id).icon()).as_uri(), + "name": MATERIAL_DATA[str(enemy_material.id)]["name"], + }, + { + "num": 30, + "rarity": MATERIAL_DATA[str(enemy_material.id + 1)]["rank"], + "icon": (await self.assets_service.material(enemy_material.id + 1).icon()).as_uri(), + "name": MATERIAL_DATA[str(enemy_material.id + 1)]["name"], + }, + { + "num": 36, + "rarity": MATERIAL_DATA[str(enemy_material.id + 2)]["rank"], + "icon": (await self.assets_service.material(str(enemy_material.id + 2)).icon()).as_uri(), + "name": MATERIAL_DATA[str(enemy_material.id + 2)]["name"], + }, + ] + talent_book = self.assets_service.material(f"「{data[character_name]['talent'][0]}」的教导") + weekly_talent_material = self.assets_service.material(data[character_name]["talent"][1]) + talent_materials = [ + { + "num": 9, + "rarity": MATERIAL_DATA[str(talent_book.id)]["rank"], + "icon": (await self.assets_service.material(talent_book.id).icon()).as_uri(), + "name": MATERIAL_DATA[str(talent_book.id)]["name"], + }, + { + "num": 63, + "rarity": MATERIAL_DATA[str(talent_book.id + 1)]["rank"], + "icon": (await self.assets_service.material(talent_book.id + 1).icon()).as_uri(), + "name": MATERIAL_DATA[str(talent_book.id + 1)]["name"], + }, + { + "num": 114, + "rarity": MATERIAL_DATA[str(talent_book.id + 2)]["rank"], + "icon": (await self.assets_service.material(str(talent_book.id + 2)).icon()).as_uri(), + "name": MATERIAL_DATA[str(talent_book.id + 2)]["name"], + }, + { + "num": 18, + "rarity": MATERIAL_DATA[str(enemy_material.id)]["rank"], + "icon": (await self.assets_service.material(enemy_material.id).icon()).as_uri(), + "name": MATERIAL_DATA[str(enemy_material.id)]["name"], + }, + { + "num": 66, + "rarity": MATERIAL_DATA[str(enemy_material.id + 1)]["rank"], + "icon": (await self.assets_service.material(enemy_material.id + 1).icon()).as_uri(), + "name": MATERIAL_DATA[str(enemy_material.id + 1)]["name"], + }, + { + "num": 93, + "rarity": MATERIAL_DATA[str(enemy_material.id + 2)]["rank"], + "icon": (await self.assets_service.material(str(enemy_material.id + 2)).icon()).as_uri(), + "name": MATERIAL_DATA[str(enemy_material.id + 2)]["name"], + }, + { + "num": 3, + "rarity": 5, + "icon": (await self.assets_service.material(104319).icon()).as_uri(), + "name": "智识之冕", + }, + { + "num": 18, + "rarity": MATERIAL_DATA[str(weekly_talent_material.id)]["rank"], + "icon": (await self.assets_service.material(weekly_talent_material.id).icon()).as_uri(), + "name": MATERIAL_DATA[str(weekly_talent_material.id)]["name"], + }, + ] + + return { + "bot_username": self.application.bot.username, + "character": { + "element": character.enka.element.name, + "image": character.enka.images.banner.url, + "name": character_name, + "association": (await Character.get_by_name(character_name)).association.name, + }, + "level_up_materials": level_up_materials, + "talent_materials": talent_materials, + "talent_level": talent_level, + "talent_amount": TalentMaterials(list(map(int, talent_level.split("/")))).cal_materials(), + } + + async def render(self, character_name: str, talent_amount: str): + if not self.roles_material: + await self._refresh() + data = await self._parse_material(self.roles_material, character_name, talent_amount) + if not data: + return + return await self.template_service.render( + "genshin/material/roles_material.html", + data, + {"width": 960, "height": 1460}, + full_page=True, + ttl=7 * 24 * 60 * 60, + ) + + @staticmethod + def _is_valid(string: str): + """ + 判断字符串是否符合`8/9/10`的格式并保证每个数字都在[1,10] + """ + return bool( + re.match(r"^\d+/\d+/\d+$", string) + and all(1 <= int(num) <= 10 for num in string.split("/")) + and string != "1/1/1" + and string != "10/10/10" + ) @handler(CommandHandler, command="material", block=False) @handler(MessageHandler, filters=filters.Regex("^角色培养素材查询(.*)"), block=False) @@ -26,6 +209,9 @@ class MaterialPlugin(Plugin): args = self.get_args(context) if len(args) >= 1: character_name = args[0] + material_count = "8/8/8" + if len(args) >= 2 and self._is_valid(args[1]): + material_count = args[1] else: reply_message = await message.reply_text( "请回复你要查询的培养素材的角色名", reply_markup=InlineKeyboardMarkup(self.KEYBOARD) @@ -35,8 +221,10 @@ class MaterialPlugin(Plugin): self.add_delete_message_job(reply_message) return character_name = roleToName(character_name) - url = await self.game_material_service.get_material(character_name) - if not url: + logger.info("用户 %s[%s] 查询角色培养素材命令请求 || 参数 %s", user.full_name, user.id, character_name) + await message.reply_chat_action(ChatAction.UPLOAD_PHOTO) + result = await self.render(character_name, material_count) + if not result: reply_message = await message.reply_text( f"没有找到 {character_name} 的培养素材", reply_markup=InlineKeyboardMarkup(self.KEYBOARD) ) @@ -44,14 +232,4 @@ class MaterialPlugin(Plugin): self.add_delete_message_job(message) self.add_delete_message_job(reply_message) return - logger.info("用户 %s[%s] 查询角色培养素材命令请求 || 参数 %s", user.full_name, user.id, character_name) - await message.reply_chat_action(ChatAction.UPLOAD_PHOTO) - file_path = await self.download_resource(url, return_path=True) - caption = "From 米游社 " f"查看 [原图]({url})" - await message.reply_photo( - photo=open(file_path, "rb"), - caption=caption, - filename=f"{character_name}.png", - allow_sending_without_reply=True, - parse_mode=ParseMode.MARKDOWN_V2, - ) + await result.reply_photo(message) diff --git a/resources/fonts/SourceHanSerifCN-Heavy.woff b/resources/fonts/SourceHanSerifCN-Heavy.woff new file mode 100644 index 0000000..ad70066 Binary files /dev/null and b/resources/fonts/SourceHanSerifCN-Heavy.woff differ diff --git a/resources/genshin/material/example.html b/resources/genshin/material/example.html new file mode 100644 index 0000000..7d684c8 --- /dev/null +++ b/resources/genshin/material/example.html @@ -0,0 +1,363 @@ + + + + + Title + + + + + +
+
+
+
+
+
+
+ 流浪者 +
+
+
角色培养素材一览
+
+
+
+
+
+ +
+
+
★等级突破★
+
+   +  角色90级升级材料 +
+ 摩拉消耗 + x2,092,530 +
+
+
+
+
+
+
20
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
20
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
20
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
20
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
20
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
20
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
20
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
20
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
20
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
+ 突破至81级(其它材料数量不变): 摩拉 x + 1,444,540   + 大英雄的经验 + x257
+
+
+ +
+ +
+ +
+
★天赋升级★
+
+   +  满级天赋升级材料 +
+ 摩拉消耗 + x4,957,500 +
+
+
+
+
+
+
20
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
20
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
20
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
20
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
20
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
20
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
20
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
20
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
20
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + +
将角色天赋升至8/8/8时所需材料:
「诤言」的教导 x9「诤言」的指引 x63「诤言」的哲学 x30
蕈兽孢子 x18荧光孢粉 x66孢囊晶尘 x30
万劫之真意 x6摩拉 x1,507,500
+
+
+
+ +
+ Created by @{{ bot_username }} +
+
+ + diff --git a/resources/genshin/material/img/inazuma.webp b/resources/genshin/material/img/inazuma.webp new file mode 100644 index 0000000..efeac46 Binary files /dev/null and b/resources/genshin/material/img/inazuma.webp differ diff --git a/resources/genshin/material/img/liyue.webp b/resources/genshin/material/img/liyue.webp new file mode 100644 index 0000000..db3b05d Binary files /dev/null and b/resources/genshin/material/img/liyue.webp differ diff --git a/resources/genshin/material/img/mondstadt.webp b/resources/genshin/material/img/mondstadt.webp new file mode 100644 index 0000000..9380ed9 Binary files /dev/null and b/resources/genshin/material/img/mondstadt.webp differ diff --git a/resources/genshin/material/img/sumeru.webp b/resources/genshin/material/img/sumeru.webp new file mode 100644 index 0000000..a8af223 Binary files /dev/null and b/resources/genshin/material/img/sumeru.webp differ diff --git a/resources/genshin/material/roles_material.html b/resources/genshin/material/roles_material.html new file mode 100644 index 0000000..5d240bd --- /dev/null +++ b/resources/genshin/material/roles_material.html @@ -0,0 +1,167 @@ + + + + + Title + + + + + +
+
+
+
+
+
+
+ {{ character.name }} +
+
+
角色培养素材一览
+
+
+
+
+
+ +
+
+
★等级突破★
+
+   +  角色90级升级材料 +
+ 摩拉消耗 + x2,092,530 +
+
+
+
+ {% for material in level_up_materials %} +
+
+
{{ material.num }}
+
+
+
+
+
+
{{ material.name }}
+
+
+ {% endfor %} +
+
+
+ 突破至81级(其它材料数量不变): 摩拉 x + 1,444,540   + 大英雄的经验 + x257
+
+
+ +
+ +
+ +
+
★天赋升级★
+
+   +  满级天赋升级材料 +
+ 摩拉消耗 + x4,957,500 +
+
+
+
+ {% for material in talent_materials %} +
+
+
{{ material.num }}
+
+
+
+
+
+
{{ material.name }}
+
+
+ {% endfor %} +
+
+
+ + + + + {% for i in range(1, 4) %} + {% if talent_amount[i] > 0 %} + + {% endif %} + {% endfor %} + + + {% for i in range(4, 7) %} + {% if talent_amount[i] > 0 %} + + {% endif %} + {% endfor %} + + + {% for i in range(7, 9) %} + {% if talent_amount[i] > 0 %} + + {% endif %} + {% endfor %} + {% if talent_amount[0] > 0 %} + + {% endif %} + +
将角色天赋升至{{ talent_level }}时所需材料:
{{ talent_materials[i - 1].name }} x{{ talent_amount[i] }}
{{ talent_materials[i - 1].name }} x{{ talent_amount[i] }}
{{ talent_materials[i - 1].name }} x{{ talent_amount[i] }}摩拉 x{{ talent_amount[0] }}
+
+
+
+ +
+ Created by @{{ bot_username }} +
+
+ +