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
+
+
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
+ 突破至81级(其它材料数量不变): 摩拉 x
+ 1,444,540
+ 大英雄的经验
+ x257
+
+
+
+
+
+
+
★天赋升级★
+
+
+
满级天赋升级材料
+
+ 摩拉消耗
+ x4,957,500
+
+
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
+
Philosophies of Resistance
+
+
+
+
+
+
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 %}
+
+ {% endfor %}
+
+
+
+ 突破至81级(其它材料数量不变): 摩拉 x
+ 1,444,540
+ 大英雄的经验
+ x257
+
+
+
+
+
+
+
★天赋升级★
+
+
+
满级天赋升级材料
+
+ 摩拉消耗
+ x4,957,500
+
+
+
+
+ {% for material in talent_materials %}
+
+ {% endfor %}
+
+
+
+
+ 将角色天赋升至{{ talent_level }}时所需材料:
+ |
+
+ {% for i in range(1, 4) %}
+ {% if talent_amount[i] > 0 %}
+ {{ talent_materials[i - 1].name }} x{{ talent_amount[i] }} |
+ {% endif %}
+ {% endfor %}
+
+
+ {% for i in range(4, 7) %}
+ {% if talent_amount[i] > 0 %}
+ {{ talent_materials[i - 1].name }} x{{ talent_amount[i] }} |
+ {% endif %}
+ {% endfor %}
+
+
+ {% for i in range(7, 9) %}
+ {% if talent_amount[i] > 0 %}
+ {{ talent_materials[i - 1].name }} x{{ talent_amount[i] }} |
+ {% endif %}
+ {% endfor %}
+ {% if talent_amount[0] > 0 %}
+ 摩拉 x{{ talent_amount[0] }} |
+ {% endif %}
+
+
+
+
+
+
+
+ Created by @{{ bot_username }}
+
+
+
+