import random import re from datetime import datetime from urllib.parse import unquote import dateparser import ujson as json from lxml import etree from nonebot_plugin_alconna import UniMessage from PIL import ImageDraw from pydantic import ValidationError from zhenxun.services.log import logger from zhenxun.utils.image_utils import BuildImage from zhenxun.utils.message import MessageUtils from ..config import draw_config from ..util import cn2py, load_font, remove_prohibited_str from .base_handle import BaseData, BaseHandle, UpChar, UpEvent class GuardianData(BaseData): pass class GuardianChar(GuardianData): pass class GuardianArms(GuardianData): pass class GuardianHandle(BaseHandle[GuardianData]): def __init__(self): super().__init__("guardian", "坎公骑冠剑") self.data_files.append("guardian_arms.json") self.config = draw_config.guardian self.ALL_CHAR: list[GuardianChar] = [] self.ALL_ARMS: list[GuardianArms] = [] self.UP_CHAR: UpEvent | None = None self.UP_ARMS: UpEvent | None = None def get_card(self, pool_name: str, mode: int = 1) -> GuardianData: if pool_name == "char": if mode == 1: star = self.get_star( [3, 2, 1], [ self.config.GUARDIAN_THREE_CHAR_P, self.config.GUARDIAN_TWO_CHAR_P, self.config.GUARDIAN_ONE_CHAR_P, ], ) else: star = self.get_star( [3, 2], [ self.config.GUARDIAN_THREE_CHAR_P, self.config.GUARDIAN_TWO_CHAR_P, ], ) up_event = self.UP_CHAR self.max_star = 3 all_data = self.ALL_CHAR else: if mode == 1: star = self.get_star( [5, 4, 3, 2], [ self.config.GUARDIAN_FIVE_ARMS_P, self.config.GUARDIAN_FOUR_ARMS_P, self.config.GUARDIAN_THREE_ARMS_P, self.config.GUARDIAN_TWO_ARMS_P, ], ) else: star = self.get_star( [5, 4], [ self.config.GUARDIAN_FIVE_ARMS_P, self.config.GUARDIAN_FOUR_ARMS_P, ], ) up_event = self.UP_ARMS self.max_star = 5 all_data = self.ALL_ARMS acquire_char = None # 是否UP if up_event and star == self.max_star and pool_name: # 获取up角色列表 up_list = [x.name for x in up_event.up_char if x.star == star] # 成功获取up角色 if random.random() < 0.5: up_name = random.choice(up_list) try: acquire_char = [x for x in all_data if x.name == up_name][0] except IndexError: pass if not acquire_char: chars = [x for x in all_data if x.star == star and not x.limited] acquire_char = random.choice(chars) return acquire_char def get_cards(self, count: int, pool_name: str) -> list[tuple[GuardianData, int]]: card_list = [] card_count = 0 # 保底计算 for i in range(count): card_count += 1 # 十连保底 if card_count == 10: card = self.get_card(pool_name, 2) card_count = 0 else: card = self.get_card(pool_name, 1) if card.star > self.max_star - 2: card_count = 0 card_list.append((card, i + 1)) return card_list def format_pool_info(self, pool_name: str) -> str: info = "" up_event = self.UP_CHAR if pool_name == "char" else self.UP_ARMS if up_event: if pool_name == "char": up_list = [x.name for x in up_event.up_char if x.star == 3] info += f'三星UP:{" ".join(up_list)}\n' else: up_list = [x.name for x in up_event.up_char if x.star == 5] info += f'五星UP:{" ".join(up_list)}\n' info = f"当前up池:{up_event.title}\n{info}" return info.strip() async def draw(self, count: int, pool_name: str, **kwargs) -> UniMessage: index2card = self.get_cards(count, pool_name) cards = [card[0] for card in index2card] up_event = self.UP_CHAR if pool_name == "char" else self.UP_ARMS up_list = [x.name for x in up_event.up_char] if up_event else [] result = self.format_result(index2card, up_list=up_list) pool_info = self.format_pool_info(pool_name) img = await self.generate_img(cards) return MessageUtils.build_message([pool_info, img, result]) async def generate_card_img(self, card: GuardianData) -> BuildImage: sep_w = 1 sep_h = 1 block_w = 170 block_h = 90 img_w = 90 img_h = 90 if isinstance(card, GuardianChar): block_color = "#2e2923" font_color = "#e2ccad" star_w = 90 star_h = 30 star_name = f"{card.star}_star.png" frame_path = "" else: block_color = "#EEE4D5" font_color = "#A65400" star_w = 45 star_h = 45 star_name = f"{card.star}_star_rank.png" frame_path = str(self.img_path / "avatar_frame.png") bg = BuildImage(block_w + sep_w * 2, block_h + sep_h * 2, color="#F6F4ED") block = BuildImage(block_w, block_h, color=block_color) star_path = str(self.img_path / star_name) star = BuildImage(star_w, star_h, background=star_path) img_path = str(self.img_path / f"{cn2py(card.name)}.png") img = BuildImage(img_w, img_h, background=img_path) await block.paste(img, (0, 0)) if frame_path: frame = BuildImage(img_w, img_h, background=frame_path) await block.paste(frame, (0, 0)) await block.paste( star, (int((block_w + img_w - star_w) / 2), block_h - star_h - 30), ) # 加名字 text = card.name[:4] + "..." if len(card.name) > 5 else card.name font = load_font(fontsize=14) text_w, _ = BuildImage.get_text_size(text, font) draw = ImageDraw.Draw(block.markImg) draw.text( ((block_w + img_w - text_w) / 2, 55), text, font=font, fill=font_color, ) await bg.paste(block, (sep_w, sep_h)) return bg def _init_data(self): self.ALL_CHAR = [ GuardianChar(name=value["名称"], star=int(value["星级"]), limited=False) for value in self.load_data().values() ] self.ALL_ARMS = [ GuardianArms(name=value["名称"], star=int(value["星级"]), limited=False) for value in self.load_data("guardian_arms.json").values() ] self.load_up_char() def load_up_char(self): try: data = self.load_data(f"draw_card_up/{self.game_name}_up_char.json") self.UP_CHAR = UpEvent.parse_obj(data.get("char", {})) self.UP_ARMS = UpEvent.parse_obj(data.get("arms", {})) except ValidationError: logger.warning(f"{self.game_name}_up_char 解析出错") def dump_up_char(self): if self.UP_CHAR and self.UP_ARMS: data = { "char": json.loads(self.UP_CHAR.json()), "arms": json.loads(self.UP_ARMS.json()), } self.dump_data(data, f"draw_card_up/{self.game_name}_up_char.json") async def _update_info(self): # guardian.json guardian_info = {} url = "https://wiki.biligame.com/gt/英雄筛选表" result = await self.get_url(url) if not result: logger.warning(f"更新 {self.game_name_cn} 出错") else: dom = etree.HTML(result, etree.HTMLParser()) char_list = dom.xpath("//table[@id='CardSelectTr']/tbody/tr") for char in char_list: try: # name = char.xpath("./td[1]/a/@title")[0] # avatar = char.xpath("./td[1]/a/img/@src")[0] # star = char.xpath("./td[1]/span/img/@alt")[0] name = char.xpath("./th[1]/a[1]/@title")[0] avatar = char.xpath("./th[1]/a/img/@src")[0] star = char.xpath("./th[1]/span/img/@alt")[0] except IndexError: continue member_dict = { "头像": unquote(str(avatar)), "名称": remove_prohibited_str(name), "星级": int(str(star).split(" ")[0].replace("Rank", "")), } guardian_info[member_dict["名称"]] = member_dict self.dump_data(guardian_info) logger.info(f"{self.game_name_cn} 更新成功") # guardian_arms.json guardian_arms_info = {} url = "https://wiki.biligame.com/gt/武器" result = await self.get_url(url) if not result: logger.warning(f"更新 {self.game_name_cn} 武器出错") else: dom = etree.HTML(result, etree.HTMLParser()) char_list = dom.xpath("//table[@id='CardSelectTr']/tbody/tr") for char in char_list: try: name = char.xpath("./td[2]/a/@title")[0] avatar = char.xpath("./td[1]/div/div/div/a/img/@src")[0] url = char.xpath("./td[3]/img/@srcset")[0] if r := re.search(r"Rank-mini-star_(\d).png", url): star = r.group(1) else: continue except IndexError: continue member_dict = { "头像": unquote(str(avatar)), "名称": remove_prohibited_str(name), "星级": int(str(star).strip()), } guardian_arms_info[member_dict["名称"]] = member_dict self.dump_data(guardian_arms_info, "guardian_arms.json") logger.info(f"{self.game_name_cn} 武器更新成功") url = "https://wiki.biligame.com/gt/盾牌" result = await self.get_url(url) if not result: logger.warning(f"更新 {self.game_name_cn} 盾牌出错") else: dom = etree.HTML(result, etree.HTMLParser()) char_list = dom.xpath( "//div[@class='resp-tabs-container']/div[2]/div/table[1]/tbody/tr" ) for char in char_list: try: name = char.xpath("./td[2]/a/@title")[0] avatar = char.xpath("./td[1]/div/div/div/a/img/@src")[0] star = char.xpath("./td[3]/text()")[0] except IndexError: continue member_dict = { "头像": unquote(str(avatar)), "名称": remove_prohibited_str(name), "星级": int(str(star).strip()), } guardian_arms_info[member_dict["名称"]] = member_dict self.dump_data(guardian_arms_info, "guardian_arms.json") logger.info(f"{self.game_name_cn} 盾牌更新成功") # 下载头像 for value in guardian_info.values(): await self.download_img(value["头像"], value["名称"]) for value in guardian_arms_info.values(): await self.download_img(value["头像"], value["名称"]) # 下载星星 idx = 1 GT_URL = "https://patchwiki.biligame.com/images/gt" for url in [ "/4/4b/ardr3bi2yf95u4zomm263tc1vke6i3i.png", "/5/55/6vow7lh76gzus6b2g9cfn325d1sugca.png", "/b/b9/du8egrd2vyewg0cuyra9t8jh0srl0ds.png", ]: await self.download_img(GT_URL + url, f"{idx}_star") idx += 1 # 另一种星星 idx = 1 for url in [ "/6/66/4e2tfa9kvhfcbikzlyei76i9crva145.png", "/1/10/r9ihsuvycgvsseyneqz4xs22t53026m.png", "/7/7a/o0k86ru9k915y04azc26hilxead7xp1.png", "/c/c9/rxz99asysz0rg391j3b02ta09mnpa7v.png", "/2/2a/sfxz0ucv1s6ewxveycz9mnmrqs2rw60.png", ]: await self.download_img(GT_URL + url, f"{idx}_star_rank") idx += 1 # 头像框 await self.download_img( GT_URL + "/8/8e/ogbqslbhuykjhnc8trtoa0p0nhfzohs.png", f"avatar_frame" ) await self.update_up_char() async def update_up_char(self): url = "https://wiki.biligame.com/gt/首页" result = await self.get_url(url) if not result: logger.warning(f"{self.game_name_cn}获取公告出错") return try: dom = etree.HTML(result, etree.HTMLParser()) announcement = dom.xpath( "//div[@class='mw-parser-output']/div/div[3]/div[2]/div/div[2]/div[3]" )[0] title = announcement.xpath("./font/p/b/text()")[0] match = re.search(r"从(.*?)开始.*?至(.*?)结束", title) if not match: logger.warning(f"{self.game_name_cn}找不到UP时间") return start, end = match.groups() start_time = dateparser.parse(start.replace("月", "/").replace("日", "")) end_time = dateparser.parse(end.replace("月", "/").replace("日", "")) if not (start_time and end_time) or not ( start_time <= datetime.now() <= end_time ): return divs = announcement.xpath("./font/div") char_index = 0 arms_index = 0 for index, div in enumerate(divs): if div.xpath("string(.)") == "角色": char_index = index elif div.xpath("string(.)") == "武器": arms_index = index chars = divs[char_index + 1 : arms_index] arms = divs[arms_index + 1 :] up_chars = [] up_arms = [] for char in chars: name = char.xpath("./p/a/@title")[0] up_chars.append(UpChar(name=name, star=3, limited=False, zoom=0)) for arm in arms: name = arm.xpath("./p/a/@title")[0] up_arms.append(UpChar(name=name, star=5, limited=False, zoom=0)) self.UP_CHAR = UpEvent( title=title, pool_img="", start_time=start_time, end_time=end_time, up_char=up_chars, ) self.UP_ARMS = UpEvent( title=title, pool_img="", start_time=start_time, end_time=end_time, up_char=up_arms, ) self.dump_up_char() logger.info(f"成功获取{self.game_name_cn}当前up信息...当前up池: {title}") except Exception as e: logger.warning(f"{self.game_name_cn}UP更新出错 {type(e)}:{e}") async def _reload_pool(self) -> UniMessage | None: await self.update_up_char() self.load_up_char() if self.UP_CHAR and self.UP_ARMS: return MessageUtils.build_message( f"重载成功!\n当前UP池子:{self.UP_CHAR.title}" )