📝 Add code comments

This commit is contained in:
Karako 2024-03-17 06:51:13 +08:00
parent a14a5ad927
commit 042e4e91ca
No known key found for this signature in database
2 changed files with 39 additions and 29 deletions

View File

@ -20,16 +20,16 @@ P = ParamSpec("P")
def get_material_serial_name(names: Iterable[str]) -> str: def get_material_serial_name(names: Iterable[str]) -> str:
"""获取材料的系列名,找出相同字符即可"""
counter = None counter = None
for name in names: for name in names:
if counter is None: counter = (counter or Counter(name)) & Counter(name)
counter = Counter(name)
continue
counter = counter & Counter(name)
return "".join(counter.keys()).replace("", "").replace("", "") return "".join(counter.keys()).replace("", "").replace("", "")
class Spider(ABC): class Spider(ABC):
"""每日素材表爬虫的基类"""
_lock = RLock() _lock = RLock()
_client: AsyncClient | None = None _client: AsyncClient | None = None
@ -47,25 +47,29 @@ class Spider(ABC):
@abstractmethod @abstractmethod
async def __call__(self) -> dict[int, FarmingData]: async def __call__(self) -> dict[int, FarmingData]:
pass """爬虫的具体实现的方法"""
@classmethod @classmethod
@final @final
async def execute(cls, assets: AssetsService) -> dict[int, FarmingData]: async def execute(cls, assets: AssetsService) -> dict[int, FarmingData]:
"""根据爬虫的优先级来爬取每日素材表(优先级高的先爬,失败了再换优先级低的爬)"""
result = None result = None
for spider in sorted( for spider in sorted(
map(lambda x: x(assets), cls.__subclasses__()), key=lambda x: (x.priority, x.__class__.__name__) map(lambda x: x(assets), cls.__subclasses__()), key=lambda x: (x.priority, x.__class__.__name__)
): ):
if (result := await spider()) is not None: if result := await spider():
return result return result
if result is None: if result is None: # 所有爬虫都爬取失败
logger.error("每日素材刷新失败,请稍后重试") logger.error("每日素材刷新失败,请稍后重试")
class Ambr(Spider): class Ambr(Spider):
"""爬取 Ambr"""
farming_url = "https://api.ambr.top/v2/chs/dailyDungeon" farming_url = "https://api.ambr.top/v2/chs/dailyDungeon"
async def _request(self, url: str) -> dict | None: async def _request(self, url: str) -> dict | None:
"""对指定 url 发起 get 请求,并返回 json 中的 data 字段数据,可自动重试"""
response = None response = None
for attempts in range(RETRY_TIMES): for attempts in range(RETRY_TIMES):
try: try:
@ -80,16 +84,17 @@ class Ambr(Spider):
if response is not None: if response is not None:
return response.json()["data"] return response.json()["data"]
async def _parse_item_data(self, material_json_data: dict): async def _parse_item_data(self, material_json_data: dict) -> list[AvatarData | WeaponData]:
"""解析角色或武器数据"""
items = [] items = []
for key in ["avatar", "weapon"]: for key in ["avatar", "weapon"]:
cls = AvatarData if key == "avatar" else WeaponData cls = AvatarData if key == "avatar" else WeaponData
if chunk_data := material_json_data["additions"]["requiredBy"].get(key): if chunk_data := material_json_data["additions"]["requiredBy"].get(key):
for data in filter(lambda x: x["rank"] > 3, chunk_data): for data in filter(lambda x: x["rank"] > 3, chunk_data): # 跳过 3 星及其以下的武器
item_id = data["id"] item_id = data["id"]
if isinstance(item_id, str): if isinstance(item_id, str):
continue # 旅行者 continue # 跳过旅行者,旅行者的 id 类型为 str
item_icon = await getattr(self.assets, key)(item_id).icon() item_icon = await getattr(self.assets, key)(item_id).icon()
items.append( items.append(
cls( cls(
@ -102,6 +107,7 @@ class Ambr(Spider):
return items return items
async def _parse_weekday_data(self, weekday_data: dict) -> list[AreaData]: async def _parse_weekday_data(self, weekday_data: dict) -> list[AreaData]:
"""解析某一天的数据"""
area_data_list = [] area_data_list = []
for domain in weekday_data.values(): for domain in weekday_data.values():
area_name = AREAS[int(domain["city"]) - 1] area_name = AREAS[int(domain["city"]) - 1]
@ -127,6 +133,7 @@ class Ambr(Spider):
return area_data_list return area_data_list
async def __call__(self) -> dict[int, FarmingData]: async def __call__(self) -> dict[int, FarmingData]:
"""从 Ambr 上爬取每日素材表的具体实现方法"""
week_map = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"] week_map = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]
if (full_farming_json_data := await self._request(self.farming_url)) is None: if (full_farming_json_data := await self._request(self.farming_url)) is None:
logger.error("从 Ambr 上爬取每日素材失败,请稍后重试") logger.error("从 Ambr 上爬取每日素材失败,请稍后重试")

View File

@ -15,7 +15,7 @@ from core.dependence.assets import AssetsService
from gram_core.plugin import Plugin, handler from gram_core.plugin import Plugin, handler
from gram_core.services.template.models import FileType, RenderGroupResult from gram_core.services.template.models import FileType, RenderGroupResult
from gram_core.services.template.services import TemplateService from gram_core.services.template.services import TemplateService
from plugins.genshin.farming._const import AREAS from plugins.genshin.farming._const import AREAS, WEEK_MAP
from plugins.genshin.farming._model import AreaData, AvatarData, FullFarmingData, RenderData, UserOwned, WeaponData from plugins.genshin.farming._model import AreaData, AvatarData, FullFarmingData, RenderData, UserOwned, WeaponData
from plugins.genshin.farming._spider import Spider from plugins.genshin.farming._spider import Spider
from plugins.tools.genshin import CharacterDetails, CookiesNotFoundError, GenshinHelper, PlayerNotFoundError from plugins.tools.genshin import CharacterDetails, CookiesNotFoundError, GenshinHelper, PlayerNotFoundError
@ -24,19 +24,18 @@ from utils.log import logger
from utils.uid import mask_number from utils.uid import mask_number
if TYPE_CHECKING: if TYPE_CHECKING:
from simnet.models.genshin.chronicle.characters import Character
from telegram import Message, Update from telegram import Message, Update
from telegram.ext import ContextTypes from telegram.ext import ContextTypes
R = TypeVar("R") R = TypeVar("R")
P = ParamSpec("P") P = ParamSpec("P")
_RETRY_TIMES = 5
_WEEK_MAP = ["", "", "", "", "", "", ""]
DATA_FILE_PATH = DATA_DIR.joinpath("daily_material.json").resolve() DATA_FILE_PATH = DATA_DIR.joinpath("daily_material.json").resolve()
def sort_item(item: AvatarData | WeaponData) -> tuple: def sort_item(item: AvatarData | WeaponData) -> tuple:
"""对角色/武器进行排序: 拥有 > 星级 > 等级 > 命座/精炼"""
rarity = item.rarity rarity = item.rarity
level = item.level level = item.level
if isinstance(item, AvatarData): if isinstance(item, AvatarData):
@ -66,6 +65,7 @@ class DailyFarming(Plugin):
self.character_details = character_details self.character_details = character_details
async def _refresh_farming_data(self) -> bool: async def _refresh_farming_data(self) -> bool:
"""刷新并保存每日素材表"""
async with self.lock: async with self.lock:
if (result := await Spider.execute(self.assets_service)) is not None: if (result := await Spider.execute(self.assets_service)) is not None:
self.full_farming_data.__root__ = result self.full_farming_data.__root__ = result
@ -109,9 +109,10 @@ class DailyFarming(Plugin):
await aiofiles.os.remove(DATA_FILE_PATH) await aiofiles.os.remove(DATA_FILE_PATH)
await refresh_task() await refresh_task()
async def _get_character_skill(self, client, character) -> list[int]: async def _get_character_skill(self, client: "GenshinClient", character: "Character") -> list[int]:
if getattr(client, "damaged", False): """获取角色的天赋等级"""
return [] if getattr(client, "damaged", False): # 检查 client 是否损坏
return [] # 若损坏则直接退出
try: try:
detail = await self.character_details.get_character_details(client, character) detail = await self.character_details.get_character_details(client, character)
return [t.level for t in detail.talents if t.type in ["attack", "skill", "burst"]] return [t.level for t in detail.talents if t.type in ["attack", "skill", "burst"]]
@ -132,7 +133,7 @@ class DailyFarming(Plugin):
logger.debug("获取账号数据成功: UID=%s", client.player_id) logger.debug("获取账号数据成功: UID=%s", client.player_id)
characters = await client.get_genshin_characters(client.player_id) characters = await client.get_genshin_characters(client.player_id)
for character in filter(lambda x: x.name != "旅行者", characters): for character in filter(lambda x: x.name != "旅行者", characters): # 跳过旅行者
character_id = str(character.id) character_id = str(character.id)
character_assets = self.assets_service.avatar(character_id) character_assets = self.assets_service.avatar(character_id)
character_icon = await character_assets.icon(False) character_icon = await character_assets.icon(False)
@ -179,29 +180,29 @@ class DailyFarming(Plugin):
# 有上述异常的, client 会返回 None # 有上述异常的, client 会返回 None
return None, user_data return None, user_data
async def _get_render_data(self, user_id, weekday, title, time_text): async def _get_render_data(self, user_id: int, weekday: int, title: str, time_text: str) -> RenderData:
# 尝试获取用户已绑定的原神账号信息 """获取模板渲染所需的数据"""
client, user_owned = await self._get_user_items(user_id) client, user_owned = await self._get_user_items(user_id) # 尝试获取用户已绑定的原神账号信息
today_farming = self.full_farming_data.weekday(weekday) today_farming = self.full_farming_data.weekday(weekday)
area_avatars: list[AreaData] = [] area_avatars: list[AreaData] = []
area_weapons: list[AreaData] = [] area_weapons: list[AreaData] = []
for area_data in today_farming.areas: for area_data in today_farming.areas:
items = [] items = []
new_area_data = deepcopy(area_data) new_area_data = deepcopy(area_data)
for avatar in area_data.avatars:
for avatar in area_data.avatars: # 将原有角色信息替换为用户已拥有的角色信息
items.append(user_owned.avatars.get(str(avatar.id), avatar)) items.append(user_owned.avatars.get(str(avatar.id), avatar))
new_area_data.avatars = list(sorted(items, key=sort_item, reverse=True)) new_area_data.avatars = list(sorted(items, key=sort_item, reverse=True))
for weapon in area_data.weapons: for weapon in area_data.weapons: # 武器同上
if weapons := user_owned.weapons.get(str(weapon.id), []): if weapons := user_owned.weapons.get(str(weapon.id), []):
items.extend(weapons) items.extend(weapons)
else: else:
items.append(weapon) items.append(weapon)
new_area_data.weapons = list(sorted(items, key=sort_item, reverse=True)) new_area_data.weapons = list(sorted(items, key=sort_item, reverse=True))
[area_weapons, area_avatars][bool(area_data.avatars)].append(new_area_data) [area_weapons, area_avatars][bool(area_data.avatars)].append(new_area_data) # 角色与武器分开存放
return RenderData( return RenderData(
title=title, title=title,
@ -223,7 +224,7 @@ class DailyFarming(Plugin):
weekday, title, time_text, full = _parse_time(args) weekday, title, time_text, full = _parse_time(args)
self.log_user(update, logger.info, "每日素材命令请求 || 参数 weekday=%s full=%s", _WEEK_MAP[weekday - 1], full) self.log_user(update, logger.info, "每日素材命令请求 || 参数 weekday=%s full=%s", WEEK_MAP[weekday - 1], full)
if weekday == 7: if weekday == 7:
the_day = "今天" if title == "今日" else "这天" the_day = "今天" if title == "今日" else "这天"
@ -280,6 +281,7 @@ class DailyFarming(Plugin):
@handler.command("refresh_farming_data", admin=True, block=False) @handler.command("refresh_farming_data", admin=True, block=False)
async def refresh_farming_data(self, update: "Update", _): async def refresh_farming_data(self, update: "Update", _):
"""管理员用的、用于刷新每日素材表的指令"""
user = update.effective_user user = update.effective_user
message = update.effective_message message = update.effective_message
@ -290,7 +292,7 @@ class DailyFarming(Plugin):
return return
notice = await message.reply_text("派蒙正在重新摘抄每日素材表,请稍等~", parse_mode=ParseMode.HTML) notice = await message.reply_text("派蒙正在重新摘抄每日素材表,请稍等~", parse_mode=ParseMode.HTML)
async with self.lock: # 锁住第一把锁 async with self.lock:
await self._refresh_farming_data() await self._refresh_farming_data()
await notice.edit_text( await notice.edit_text(
"每日素材表" "每日素材表"
@ -300,17 +302,18 @@ class DailyFarming(Plugin):
def _parse_time(args: list[str]) -> tuple[int, str, str, bool]: def _parse_time(args: list[str]) -> tuple[int, str, str, bool]:
"""用于解析`daily_farming`指令的参数"""
now = datetime.now() now = datetime.now()
try: try:
weekday = (_ := int(args[0])) - (_ > 0) weekday = (_ := int(args[0])) - (_ > 0)
weekday = (weekday % 7 + 7) % 7 weekday = (weekday % 7 + 7) % 7
time_text = title = f"星期{_WEEK_MAP[weekday]}" time_text = title = f"星期{WEEK_MAP[weekday]}"
except (ValueError, IndexError): except (ValueError, IndexError):
title = "今日" title = "今日"
weekday = now.weekday() - (1 if now.hour < 4 else 0) weekday = now.weekday() - (1 if now.hour < 4 else 0)
weekday = 6 if weekday < 0 else weekday weekday = 6 if weekday < 0 else weekday
time_text = f"星期{_WEEK_MAP[weekday]}" time_text = f"星期{WEEK_MAP[weekday]}"
full = bool(args and args[-1] == "full") full = bool(args and args[-1] == "full")