mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2025-12-15 06:12:53 +08:00
308 lines
12 KiB
Python
308 lines
12 KiB
Python
|
|
import random
|
||
|
|
from urllib.parse import unquote
|
||
|
|
|
||
|
|
import dateparser
|
||
|
|
import ujson as json
|
||
|
|
from lxml import etree
|
||
|
|
from nonebot_plugin_saa import Image, MessageFactory, Text
|
||
|
|
from PIL import ImageDraw
|
||
|
|
from pydantic import ValidationError
|
||
|
|
|
||
|
|
from zhenxun.services.log import logger
|
||
|
|
from zhenxun.utils.image_utils import BuildImage
|
||
|
|
|
||
|
|
from ..config import draw_config
|
||
|
|
from ..util import cn2py, load_font, remove_prohibited_str
|
||
|
|
from .base_handle import BaseData, BaseHandle
|
||
|
|
from .base_handle import UpChar as _UpChar
|
||
|
|
from .base_handle import UpEvent as _UpEvent
|
||
|
|
|
||
|
|
|
||
|
|
class AzurChar(BaseData):
|
||
|
|
type_: str # 舰娘类型
|
||
|
|
|
||
|
|
@property
|
||
|
|
def star_str(self) -> str:
|
||
|
|
return ["白", "蓝", "紫", "金"][self.star - 1]
|
||
|
|
|
||
|
|
|
||
|
|
class UpChar(_UpChar):
|
||
|
|
type_: str # 舰娘类型
|
||
|
|
|
||
|
|
|
||
|
|
class UpEvent(_UpEvent):
|
||
|
|
up_char: list[UpChar] # up对象
|
||
|
|
|
||
|
|
|
||
|
|
class AzurHandle(BaseHandle[AzurChar]):
|
||
|
|
def __init__(self):
|
||
|
|
super().__init__("azur", "碧蓝航线")
|
||
|
|
self.max_star = 4
|
||
|
|
self.config = draw_config.azur
|
||
|
|
self.ALL_CHAR: list[AzurChar] = []
|
||
|
|
self.UP_EVENT: UpEvent | None = None
|
||
|
|
|
||
|
|
def get_card(self, pool_name: str, **kwargs) -> AzurChar:
|
||
|
|
if pool_name == "轻型":
|
||
|
|
type_ = ["驱逐", "轻巡", "维修"]
|
||
|
|
elif pool_name == "重型":
|
||
|
|
type_ = ["重巡", "战列", "战巡", "重炮"]
|
||
|
|
else:
|
||
|
|
type_ = ["维修", "潜艇", "重巡", "轻航", "航母"]
|
||
|
|
up_pool_flag = pool_name == "活动"
|
||
|
|
# Up
|
||
|
|
up_ship = (
|
||
|
|
[x for x in self.UP_EVENT.up_char if x.zoom > 0] if self.UP_EVENT else []
|
||
|
|
)
|
||
|
|
# print(up_ship)
|
||
|
|
acquire_char = None
|
||
|
|
if up_ship and up_pool_flag:
|
||
|
|
up_zoom: list[tuple[float, float]] = [(0, up_ship[0].zoom / 100)]
|
||
|
|
# 初始化概率
|
||
|
|
cur_ = up_ship[0].zoom / 100
|
||
|
|
for i in range(len(up_ship)):
|
||
|
|
try:
|
||
|
|
up_zoom.append((cur_, cur_ + up_ship[i + 1].zoom / 100))
|
||
|
|
cur_ += up_ship[i + 1].zoom / 100
|
||
|
|
except IndexError:
|
||
|
|
pass
|
||
|
|
rand = random.random()
|
||
|
|
# 抽取up
|
||
|
|
for i, zoom in enumerate(up_zoom):
|
||
|
|
if zoom[0] <= rand <= zoom[1]:
|
||
|
|
try:
|
||
|
|
acquire_char = [
|
||
|
|
x for x in self.ALL_CHAR if x.name == up_ship[i].name
|
||
|
|
][0]
|
||
|
|
except IndexError:
|
||
|
|
pass
|
||
|
|
# 没有up或者未抽取到up
|
||
|
|
if not acquire_char:
|
||
|
|
star = self.get_star(
|
||
|
|
# [4, 3, 2, 1],
|
||
|
|
[4, 3, 2, 2],
|
||
|
|
[
|
||
|
|
self.config.AZUR_FOUR_P,
|
||
|
|
self.config.AZUR_THREE_P,
|
||
|
|
self.config.AZUR_TWO_P,
|
||
|
|
self.config.AZUR_ONE_P,
|
||
|
|
],
|
||
|
|
)
|
||
|
|
acquire_char = random.choice(
|
||
|
|
[
|
||
|
|
x
|
||
|
|
for x in self.ALL_CHAR
|
||
|
|
if x.star == star and x.type_ in type_ and not x.limited
|
||
|
|
]
|
||
|
|
)
|
||
|
|
return acquire_char
|
||
|
|
|
||
|
|
async def draw(self, count: int, **kwargs) -> MessageFactory:
|
||
|
|
index2card = self.get_cards(count, **kwargs)
|
||
|
|
cards = [card[0] for card in index2card]
|
||
|
|
up_list = [x.name for x in self.UP_EVENT.up_char] if self.UP_EVENT else []
|
||
|
|
result = self.format_result(index2card, **{**kwargs, "up_list": up_list})
|
||
|
|
gen_img = await self.generate_img(cards)
|
||
|
|
return MessageFactory([Image(gen_img.pic2bytes()), Text(result)])
|
||
|
|
|
||
|
|
async def generate_card_img(self, card: AzurChar) -> BuildImage:
|
||
|
|
sep_w = 5
|
||
|
|
sep_t = 5
|
||
|
|
sep_b = 20
|
||
|
|
w = 100
|
||
|
|
h = 100
|
||
|
|
bg = BuildImage(w + sep_w * 2, h + sep_t + sep_b)
|
||
|
|
frame_path = str(self.img_path / f"{card.star}_star.png")
|
||
|
|
frame = BuildImage(w, h, background=frame_path)
|
||
|
|
img_path = str(self.img_path / f"{cn2py(card.name)}.png")
|
||
|
|
img = BuildImage(w, h, background=img_path)
|
||
|
|
# 加圆角
|
||
|
|
await frame.circle_corner(6)
|
||
|
|
await img.circle_corner(6)
|
||
|
|
await bg.paste(img, (sep_w, sep_t))
|
||
|
|
await bg.paste(frame, (sep_w, sep_t))
|
||
|
|
# 加名字
|
||
|
|
text = card.name[:6] + "..." if len(card.name) > 7 else card.name
|
||
|
|
font = load_font(fontsize=14)
|
||
|
|
text_w, text_h = BuildImage.get_text_size(text, font)
|
||
|
|
draw = ImageDraw.Draw(bg.markImg)
|
||
|
|
draw.text(
|
||
|
|
(sep_w + (w - text_w) / 2, h + sep_t + (sep_b - text_h) / 2),
|
||
|
|
text,
|
||
|
|
font=font,
|
||
|
|
fill=["#808080", "#3b8bff", "#8000ff", "#c90", "#ee494c"][card.star - 1],
|
||
|
|
)
|
||
|
|
return bg
|
||
|
|
|
||
|
|
def _init_data(self):
|
||
|
|
self.ALL_CHAR = [
|
||
|
|
AzurChar(
|
||
|
|
name=value["名称"],
|
||
|
|
star=int(value["星级"]),
|
||
|
|
limited="可以建造" not in value["获取途径"],
|
||
|
|
type_=value["类型"],
|
||
|
|
)
|
||
|
|
for value in self.load_data().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_EVENT = UpEvent.parse_obj(data.get("char", {}))
|
||
|
|
except ValidationError:
|
||
|
|
logger.warning(f"{self.game_name}_up_char 解析出错")
|
||
|
|
|
||
|
|
def dump_up_char(self):
|
||
|
|
if self.UP_EVENT:
|
||
|
|
data = {"char": json.loads(self.UP_EVENT.json())}
|
||
|
|
self.dump_data(data, f"draw_card_up/{self.game_name}_up_char.json")
|
||
|
|
self.dump_data(data, f"draw_card_up/{self.game_name}_up_char.json")
|
||
|
|
|
||
|
|
async def _update_info(self):
|
||
|
|
info = {}
|
||
|
|
# 更新图鉴
|
||
|
|
url = "https://wiki.biligame.com/blhx/舰娘图鉴"
|
||
|
|
result = await self.get_url(url)
|
||
|
|
if not result:
|
||
|
|
logger.warning(f"更新 {self.game_name_cn} 出错")
|
||
|
|
return
|
||
|
|
dom = etree.HTML(result, etree.HTMLParser())
|
||
|
|
contents = dom.xpath(
|
||
|
|
"//div[@class='mw-body-content mw-content-ltr']/div[@class='mw-parser-output']"
|
||
|
|
)
|
||
|
|
for index, content in enumerate(contents):
|
||
|
|
char_list = content.xpath("./div[@id='CardSelectTr']/div")
|
||
|
|
for char in char_list:
|
||
|
|
try:
|
||
|
|
name = char.xpath("./span/a/text()")[0]
|
||
|
|
frame = char.xpath("./div/div/a/img/@alt")[0]
|
||
|
|
avatar = char.xpath("./div/img/@srcset")[0]
|
||
|
|
except IndexError:
|
||
|
|
continue
|
||
|
|
member_dict = {
|
||
|
|
"名称": remove_prohibited_str(name),
|
||
|
|
"头像": unquote(str(avatar).split(" ")[-2]),
|
||
|
|
"星级": self.parse_star(frame),
|
||
|
|
"类型": char.xpath("./@data-param1")[0].split(",")[-1],
|
||
|
|
}
|
||
|
|
info[member_dict["名称"]] = member_dict
|
||
|
|
# 更新额外信息
|
||
|
|
for key in info.keys():
|
||
|
|
# TODO: 各种舰娘·改获取错误
|
||
|
|
url = f"https://wiki.biligame.com/blhx/{key}"
|
||
|
|
result = await self.get_url(url)
|
||
|
|
if not result:
|
||
|
|
info[key]["获取途径"] = []
|
||
|
|
logger.warning(f"{self.game_name_cn} 获取额外信息错误 {key}")
|
||
|
|
continue
|
||
|
|
try:
|
||
|
|
dom = etree.HTML(result, etree.HTMLParser())
|
||
|
|
time = dom.xpath(
|
||
|
|
"//table[@class='wikitable sv-general']/tbody[1]/tr[4]/td[2]//text()"
|
||
|
|
)[0]
|
||
|
|
sources = []
|
||
|
|
if "无法建造" in time:
|
||
|
|
sources.append("无法建造")
|
||
|
|
elif "活动已关闭" in time:
|
||
|
|
sources.append("活动限定")
|
||
|
|
else:
|
||
|
|
sources.append("可以建造")
|
||
|
|
info[key]["获取途径"] = sources
|
||
|
|
except IndexError:
|
||
|
|
info[key]["获取途径"] = []
|
||
|
|
logger.warning(f"{self.game_name_cn} 获取额外信息错误 {key}")
|
||
|
|
self.dump_data(info)
|
||
|
|
logger.info(f"{self.game_name_cn} 更新成功")
|
||
|
|
# 下载头像
|
||
|
|
for value in info.values():
|
||
|
|
await self.download_img(value["头像"], value["名称"])
|
||
|
|
# 下载头像框
|
||
|
|
idx = 1
|
||
|
|
BLHX_URL = "https://patchwiki.biligame.com/images/blhx"
|
||
|
|
for url in [
|
||
|
|
"/1/15/pxho13xsnkyb546tftvh49etzdh74cf.png",
|
||
|
|
"/a/a9/k8t7nx6c8pan5vyr8z21txp45jxeo66.png",
|
||
|
|
"/a/a5/5whkzvt200zwhhx0h0iz9qo1kldnidj.png",
|
||
|
|
"/a/a2/ptog1j220x5q02hytpwc8al7f229qk9.png",
|
||
|
|
"/6/6d/qqv5oy3xs40d3055cco6bsm0j4k4gzk.png",
|
||
|
|
]:
|
||
|
|
await self.download_img(BLHX_URL + url, f"{idx}_star")
|
||
|
|
idx += 1
|
||
|
|
await self.update_up_char()
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def parse_star(star: str) -> int:
|
||
|
|
if star in ["舰娘头像外框普通.png", "舰娘头像外框白色.png"]:
|
||
|
|
return 1
|
||
|
|
elif star in ["舰娘头像外框稀有.png", "舰娘头像外框蓝色.png"]:
|
||
|
|
return 2
|
||
|
|
elif star in ["舰娘头像外框精锐.png", "舰娘头像外框紫色.png"]:
|
||
|
|
return 3
|
||
|
|
elif star in ["舰娘头像外框超稀有.png", "舰娘头像外框金色.png"]:
|
||
|
|
return 4
|
||
|
|
elif star in ["舰娘头像外框海上传奇.png", "舰娘头像外框彩色.png"]:
|
||
|
|
return 5
|
||
|
|
elif star in [
|
||
|
|
"舰娘头像外框最高方案.png",
|
||
|
|
"舰娘头像外框决战方案.png",
|
||
|
|
"舰娘头像外框超稀有META.png",
|
||
|
|
"舰娘头像外框精锐META.png",
|
||
|
|
]:
|
||
|
|
return 6
|
||
|
|
else:
|
||
|
|
return 6
|
||
|
|
|
||
|
|
async def update_up_char(self):
|
||
|
|
url = "https://wiki.biligame.com/blhx/游戏活动表"
|
||
|
|
result = await self.get_url(url)
|
||
|
|
if not result:
|
||
|
|
logger.warning(f"{self.game_name_cn}获取活动表出错")
|
||
|
|
return
|
||
|
|
try:
|
||
|
|
dom = etree.HTML(result, etree.HTMLParser())
|
||
|
|
dd = dom.xpath("//div[@class='timeline2']/dl/dd/a")[0]
|
||
|
|
url = "https://wiki.biligame.com" + dd.xpath("./@href")[0]
|
||
|
|
title = dd.xpath("string(.)")
|
||
|
|
result = await self.get_url(url)
|
||
|
|
if not result:
|
||
|
|
logger.warning(f"{self.game_name_cn}获取活动页面出错")
|
||
|
|
return
|
||
|
|
dom = etree.HTML(result, etree.HTMLParser())
|
||
|
|
timer = dom.xpath("//span[@class='eventTimer']")[0]
|
||
|
|
start_time = dateparser.parse(timer.xpath("./@data-start")[0])
|
||
|
|
end_time = dateparser.parse(timer.xpath("./@data-end")[0])
|
||
|
|
ships = dom.xpath("//table[@class='shipinfo']")
|
||
|
|
up_chars = []
|
||
|
|
for ship in ships:
|
||
|
|
name = ship.xpath("./tbody/tr/td[2]/p/a/@title")[0]
|
||
|
|
type_ = ship.xpath("./tbody/tr/td[2]/p/small/text()")[0] # 舰船类型
|
||
|
|
try:
|
||
|
|
p = float(str(ship.xpath(".//sup/text()")[0]).strip("%"))
|
||
|
|
except (IndexError, ValueError):
|
||
|
|
p = 0
|
||
|
|
star = self.parse_star(
|
||
|
|
ship.xpath("./tbody/tr/td[1]/div/div/div/a/img/@alt")[0]
|
||
|
|
)
|
||
|
|
up_chars.append(
|
||
|
|
UpChar(name=name, star=star, limited=False, zoom=p, type_=type_)
|
||
|
|
)
|
||
|
|
self.UP_EVENT = UpEvent(
|
||
|
|
title=title,
|
||
|
|
pool_img="",
|
||
|
|
start_time=start_time,
|
||
|
|
end_time=end_time,
|
||
|
|
up_char=up_chars,
|
||
|
|
)
|
||
|
|
self.dump_up_char()
|
||
|
|
except Exception as e:
|
||
|
|
logger.warning(f"{self.game_name_cn}UP更新出错", e=e)
|
||
|
|
|
||
|
|
async def _reload_pool(self) -> MessageFactory | None:
|
||
|
|
await self.update_up_char()
|
||
|
|
self.load_up_char()
|
||
|
|
if self.UP_EVENT:
|
||
|
|
return MessageFactory(
|
||
|
|
[Text(f"重载成功!\n当前活动:{self.UP_EVENT.title}")]
|
||
|
|
)
|