Compare commits

...
27 Commits
Author SHA1 Message Date
HibiKier 067da9dc57 update 0.1.4.3 2022-03-03 00:18:59 +08:00
HibiKier 0eb9f9fc6f Update README.md 2022-03-01 21:02:36 +08:00
HibiKier aee1340714 Update README.md 2022-03-01 21:02:18 +08:00
HibiKier a72a9bd8cb Update __version__ 2022-03-01 21:01:52 +08:00
HibiKier 7250410653 Update __version__ 2022-03-01 20:59:52 +08:00
HibiKier 12f55800b0 x 2022-03-01 20:26:11 +08:00
HibiKier 53b375b4b2 Add files via upload 2022-02-27 18:26:39 +08:00
HibiKier fba27abe29 Update pyproject.toml 2022-02-27 17:56:56 +08:00
HibiKier 4caa3dec04 Update poetry.lock 2022-02-27 17:56:26 +08:00
HibiKier 516b0f5ccb Add files via upload 2022-02-27 17:34:19 +08:00
HibiKier b5098be08e Update __version__ 2022-02-27 17:32:40 +08:00
HibiKier 5b08a4650c Update README.md 2022-02-27 17:31:51 +08:00
HibiKier eb02157b94 Add files via upload 2022-02-27 17:30:26 +08:00
HibiKier 222e1719a4 Delete plugins/draw_card directory 2022-02-27 17:29:57 +08:00
HibiKier 3c8983e753 Update task_hook.py 2022-02-26 00:11:26 +08:00
HibiKier 1fe41dd823 Update __version__ 2022-02-25 23:21:03 +08:00
HibiKier e8df59203c Update README.md 2022-02-25 23:17:42 +08:00
HibiKier 0f6690606b Update README.md 2022-02-25 22:50:24 +08:00
HibiKier a0115309dd Merge pull request #124 from Nova-Noir/main
Fixed error in custom_welcome_msg and message_builder
2022-02-24 16:12:39 +08:00
HibiKier ca18d45786 Update message_builder.py 2022-02-24 16:12:25 +08:00
NovaNo1r 76df21036a Fixed error in custom_welcome_msg and message_builder 2022-02-23 21:45:53 -08:00
HibiKier c674d581b7 Update README.md 2022-02-23 13:52:10 +08:00
HibiKier fc53c3c487 Update _data_source.py 2022-02-23 13:51:19 +08:00
HibiKier 7a4e31df80 Update __init__.py 2022-02-23 13:49:57 +08:00
HibiKier f1d308d559 Update __init__.py 2022-02-23 13:49:21 +08:00
HibiKier b7914bac05 Update README.md 2022-02-21 19:05:42 +08:00
HibiKier 8c40d88cc4 Update .gitignore 2022-02-21 18:20:43 +08:00
580 changed files with 3974 additions and 3468 deletions
+1 -1
View File
@@ -139,7 +139,7 @@ dmypy.json
cython_debug/
test.py
server.py
server_ip.py
member_activity_handle.py
Yu-Gi-Oh/
search_image/
+27 -1
View File
@@ -191,7 +191,7 @@
# 配置gocq
在 https://github.com/Mrs4s/go-cqhttp 下载Releases最新版本,运行后选择反向代理,
后将gocq的配置文件config.yml中的universal改为universal: ws://127.0.0.1:8080/cqhttp/ws
后将gocq的配置文件config.yml中的universal改为universal: ws://127.0.0.1:8080/onebot/v11/ws
# 获取代码
git clone https://github.com/HibiKier/zhenxun_bot.git
@@ -236,6 +236,32 @@ __Docker 最新版本由 [Sakuracio](https://github.com/Sakuracio) 提供__
## 更新
### 2022/3/3 \[v0.1.4.3]
* 修复手动同意群聊请求依旧退出
### 2022/3/1 \[v0.1.4.2]
* 0.1.4内容
### 2022/2/27 \[v0.1.4.1]
* 优化抽卡
### 2022/2/25 \[v0.1.4]
* PIX提供配置MAX_ONCE_NUM2FORWARD:当单次发送图片超过指定张数且在群聊时,将转为合并消息
* 修复点歌无法正确发送
* 修复我有一个朋友有时文本会包含CQ码
* 修复群欢消息被动控制文本未删除 [@pull/124](https://github.com/HibiKier/zhenxun_bot/pull/124)
* message_builder.image不再提供参数:abspath
### 2022/2/23
* 插件状态将从已加载插件模块中读取
* 修复b站订阅插件订阅失败问题
* 修复重启命令无法使用
### 2022/2/21 \[v0.1.3.2]
* 群权限为-1时,超级用户发送的命令依旧生效
+1 -1
View File
@@ -1 +1 @@
__version__: v0.1.3.2
__version__: v0.1.4.3
+24 -20
View File
@@ -3,7 +3,7 @@ from nonebot.adapters.onebot.v11.message import MessageSegment
from services.log import logger
from configs.path_config import DATA_PATH
from utils.message_builder import image
from utils.utils import get_bot
from utils.utils import get_bot, get_matchers
from pathlib import Path
from models.group_member_info import GroupInfoUser
from datetime import datetime
@@ -75,7 +75,7 @@ async def custom_group_welcome(
await AsyncHttpx.download_file(
img, DATA_PATH / "custom_welcome_msg" / f"{group_id}.jpg"
)
img_result = image(abspath=DATA_PATH / "custom_welcome_msg" / f"{group_id}.jpg")
img_result = image(DATA_PATH / "custom_welcome_msg" / f"{group_id}.jpg")
logger.info(f"USER {user_id} GROUP {group_id} 更换群欢迎消息图片")
except Exception as e:
logger.error(f"GROUP {group_id} 替换群消息失败 e:{e}")
@@ -187,24 +187,28 @@ def _get_plugin_status() -> MessageSegment:
"""
rst = "\t功能\n"
flag_str = "状态".rjust(4) + "\n"
for module in plugins_manager.get_data():
flag = plugins_manager.get_plugin_block_type(module)
flag = flag.upper() + " CLOSE" if flag else "OPEN"
try:
plugin_name = plugins_manager.get(module)["plugin_name"]
if (
"[Hidden]" in plugin_name
or "[Admin]" in plugin_name
or "[Superuser]" in plugin_name
):
continue
rst += f"{plugin_name}"
except KeyError:
rst += f"{module}"
if plugins_manager.get(module)["error"]:
rst += "[ERROR]"
rst += "\n"
flag_str += f"{flag}\n"
tmp_name = []
for matcher in get_matchers():
if matcher.plugin_name not in tmp_name:
tmp_name.append(matcher.plugin_name)
module = matcher.plugin_name
flag = plugins_manager.get_plugin_block_type(module)
flag = flag.upper() + " CLOSE" if flag else "OPEN"
try:
plugin_name = plugins_manager.get(module)["plugin_name"]
if (
"[Hidden]" in plugin_name
or "[Admin]" in plugin_name
or "[Superuser]" in plugin_name
):
continue
rst += f"{plugin_name}"
except KeyError:
rst += f"{module}"
if plugins_manager.get(module)["error"]:
rst += "[ERROR]"
rst += "\n"
flag_str += f"{flag}\n"
height = len(rst.split("\n")) * 24
a = BuildImage(250, height, font_size=20)
a.text((10, 10), rst)
+1 -1
View File
@@ -129,7 +129,7 @@ async def _(bot: Bot, event: GroupIncreaseNoticeEvent):
at_flag = True
if (DATA_PATH / "custom_welcome_msg" / f"{event.group_id}.jpg").exists():
img = image(
abspath=DATA_PATH / "custom_welcome_msg" / f"{event.group_id}.jpg"
DATA_PATH / "custom_welcome_msg" / f"{event.group_id}.jpg"
)
if msg or img:
msg = msg.strip() + img
+11
View File
@@ -2,12 +2,15 @@ from nonebot.matcher import Matcher
from nonebot.message import run_preprocessor, IgnoredException
from nonebot.typing import T_State
from ._utils import status_message_manager
from utils.image_utils import text2image
from typing import Dict, Any
from nonebot.adapters.onebot.v11 import (
Bot,
MessageEvent,
PrivateMessageEvent,
GroupMessageEvent,
)
import re
# 为什么AI会自己和自己聊天
@@ -38,3 +41,11 @@ async def _(matcher: Matcher, bot: Bot, event: MessageEvent, state: T_State):
status_message_manager.delete(event.user_id)
raise IgnoredException("有命令就别说话了")
# @Bot.on_calling_api
# async def handle_api_call(bot: Bot, api: str, data: Dict[str, Any]):
# if api in ["send_msg", "send_group_msg", "send_private_msg"]:
# msg = str(data["message"])
# if (r := re.search("\[\[To_Img\|?(.*?)]]", msg)) or (r := re.search("[[To_Img\|?(.*?)[[")):
+12 -2
View File
@@ -14,10 +14,20 @@ async def handle_api_call(bot: Bot, api: str, data: Dict[str, Any]):
or api == "send_group_msg"
)
and (
(r := re.search("^\[\[_task\|(.*)]]", str(data["message"]).strip()))
(
r := re.search(
"^\[\[_task\|(.*)]]",
data["message"].strip()
if isinstance(data["message"], str)
else str(data["message"]["text"]).strip(),
)
)
or (
r := re.search(
"^[[_task\|(.*)]]", str(data["message"]).strip()
"^[[_task\|(.*)]]",
data["message"].strip()
if isinstance(data["message"], str)
else str(data["message"]["text"]).strip(),
)
)
)
+2 -1
View File
@@ -102,7 +102,7 @@ async def _(bot: Bot, cmd: Tuple[str, ...] = Command(), arg: Message = CommandAr
if is_number(id_):
id_ = int(id_)
if cmd[:2] == "同意":
rid = await requests_manager.approve(bot, id_, "group")
rid = requests_manager.get_group_id(id_)
if rid:
await friend_handle.send("同意群聊请求成功..")
if await GroupInfo.get_group_info(rid):
@@ -116,6 +116,7 @@ async def _(bot: Bot, cmd: Tuple[str, ...] = Command(), arg: Message = CommandAr
group_info["member_count"],
1
)
await requests_manager.approve(bot, id_, "group")
else:
await friend_handle.send("同意群聊请求失败,可能是未找到此id的请求..")
else:
+16 -8
View File
@@ -17,7 +17,7 @@ from utils.utils import is_number, scheduler, get_bot
from typing import Optional
from services.log import logger
from nonebot import Driver
from nonebot.params import CommandArg, ArgPlainText
from nonebot.params import CommandArg, ArgStr
import nonebot
__zx_plugin_name__ = "B站订阅"
@@ -112,14 +112,22 @@ async def _(event: MessageEvent, state: T_State, arg: Message = CommandArg()):
state["id"] = int(id_)
@add_sub.got("sub_type")
@add_sub.got("sub_user")
@add_sub.got("id")
async def _(event: MessageEvent, state: T_State, id_: str = ArgPlainText("id")):
season_data = state["season_data"]
if not is_number(id_) or int(id_) < 1 or int(id_) > len(season_data):
await add_sub.reject_arg("id", "Id必须为数字且在范围内!请重新输入...")
id_ = season_data[int(id_) - 1]["media_id"]
sub_type = state["sub_type"]
sub_user = state["sub_user"]
async def _(
event: MessageEvent,
state: T_State,
id_: str = ArgStr("id"),
sub_type: str = ArgStr("sub_type"),
sub_user: str = ArgStr("sub_user"),
):
if sub_type in ["season", "动漫", "番剧"] and state.get("season_data"):
season_data = state["season_data"]
if not is_number(id_) or int(id_) < 1 or int(id_) > len(season_data):
await add_sub.reject_arg("id", "Id必须为数字且在范围内!请重新输入...")
id_ = season_data[int(id_) - 1]["media_id"]
id_ = int(id_)
if sub_type in ["主播", "直播"]:
await add_sub.send(await add_live_sub(id_, sub_user))
elif sub_type.lower() in ["up", "用户"]:
-58
View File
@@ -1,58 +0,0 @@
from .music_163 import get_song_id, get_song_info
from nonebot.adapters.onebot.v11 import Bot, Event, GroupMessageEvent, Message
from nonebot.params import CommandArg
from nonebot.typing import T_State
from services.log import logger
from nonebot import on_command
__zx_plugin_name__ = "点歌"
__plugin_usage__ = """
usage:
在线点歌
指令:
点歌 [歌名]
""".strip()
__plugin_des__ = "为你点播了一首曾经的歌"
__plugin_cmd__ = ["点歌 [歌名]"]
__plugin_type__ = ("一些工具",)
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
__plugin_settings__ = {
"level": 5,
"default_status": True,
"limit_superuser": False,
"cmd": ["点歌"],
}
music = on_command("点歌", priority=5, block=True)
@music.handle()
async def handle_first_receive(state: T_State, arg: Message = CommandArg()):
args = arg.extract_plain_text().strip()
if args:
state["song_name"] = args
@music.got("song_name", prompt="歌名是?")
async def _(bot: Bot, event: Event, state: T_State):
song = state["song_name"]
song_id = await get_song_id(song)
if not song_id:
await music.finish("没有找到这首歌!", at_sender=True)
for _ in range(3):
song_content = [{"type": "music", "data": {"type": 163, "id": song_id}}]
logger.info(
f"(USER {event.user_id}, GROUP "
f"{event.group_id if isinstance(event, GroupMessageEvent) else 'private'})"
f" 点歌 :{song}"
)
await music.finish(song_content)
else:
await music.finish("网易云繁忙...")
+2 -3
View File
@@ -1,5 +1,4 @@
from nonebot.adapters.onebot.v11 import Bot
from nonebot.typing import T_State
from nonebot.permission import SUPERUSER
from nonebot import on_command
from .data_source import check_update, get_latest_version_data
@@ -8,6 +7,7 @@ from utils.utils import scheduler, get_bot
from pathlib import Path
from configs.config import Config
from nonebot.rule import to_me
from nonebot.params import ArgStr
import platform
import os
@@ -73,8 +73,7 @@ async def _():
@restart.got("flag", prompt="确定是否重启真寻?(重启失败咱们将失去联系,请谨慎!)")
async def _(state: T_State):
flag = state["flag"]
async def _(flag: str = ArgStr("flag")):
if flag.lower() in ["true", "是", "好", "确定", "确定是"]:
await restart.send("开始重启真寻..请稍等...")
open("is_restart", "w")
Executable → Regular
+172 -329
View File
@@ -1,25 +1,30 @@
from nonebot import on_regex, on_keyword
from nonebot.adapters.onebot.v11 import Bot, MessageEvent, Message
from nonebot.permission import SUPERUSER
from nonebot.typing import T_State
from nonebot.params import RegexGroup
from utils.utils import scheduler
from .genshin_handle import genshin_draw, update_genshin_info, reset_count, reload_genshin_pool
from .prts_handle import update_prts_info, prts_draw, reload_prts_pool
from .pretty_handle import update_pretty_info, pretty_draw, reload_pretty_pool
from .guardian_handle import update_guardian_info, guardian_draw, reload_guardian_pool
from .pcr_handle import update_pcr_info, pcr_draw
from .azur_handle import update_azur_info, azur_draw
from .fgo_handle import update_fgo_info, fgo_draw
from .onmyoji_handle import update_onmyoji_info, onmyoji_draw
from .update_game_info import update_info
from .util import is_number, check_num
from .rule import is_switch
from .config import draw_config
from .async_update_game_info import async_update_game
from typing import Tuple, Any
import re
import asyncio
import traceback
from cn2an import cn2an
from dataclasses import dataclass
from typing import Optional, Set, Tuple
import nonebot
from nonebot import on_regex, on_keyword
from nonebot.adapters.onebot.v11 import MessageEvent
from nonebot.log import logger
from nonebot.matcher import Matcher
from nonebot.params import RegexGroup
from nonebot.permission import SUPERUSER
from nonebot.typing import T_Handler
from nonebot_plugin_apscheduler import scheduler
from .handles.base_handle import BaseHandle
from .handles.azur_handle import AzurHandle
from .handles.fgo_handle import FgoHandle
from .handles.genshin_handle import GenshinHandle
from .handles.guardian_handle import GuardianHandle
from .handles.onmyoji_handle import OnmyojiHandle
from .handles.pcr_handle import PcrHandle
from .handles.pretty_handle import PrettyHandle
from .handles.prts_handle import PrtsHandle
from .config import draw_config
__zx_plugin_name__ = "游戏抽卡"
@@ -27,15 +32,15 @@ __plugin_usage__ = """
usage:
模拟赛马娘,原神,明日方舟,坎公骑冠剑,公主连结(国/台),碧蓝航线,FGO,阴阳师进行抽卡
指令:
原神[1-300]抽: 原神常驻池
原神角色[1-300]抽: 原神角色UP池子
原神武器[1-300]抽: 原神武器UP池子
原神[1-180]抽: 原神常驻池
原神角色[1-180]抽: 原神角色UP池子
原神武器[1-180]抽: 原神武器UP池子
重置原神抽卡: 清空当前卡池的抽卡次数[即从0开始计算UP概率]
方舟[1-300]抽: 方舟卡池,当有当期UP时指向UP池
赛马娘[1-200]抽: 赛马娘卡池,当有当期UP时指向UP池
坎公骑冠剑[1-300]抽: 坎公骑冠剑卡池,当有当期UP时指向UP池
pcr/公主连接[1-300]抽: 公主连接卡池
碧蓝航线/碧蓝[重型/轻型/特型][1-300]抽: 碧蓝航线重型/轻型/特型卡池
碧蓝航线/碧蓝[重型/轻型/特型/活动][1-300]抽: 碧蓝航线重型/轻型/特型/活动卡池
fgo[1-300]抽: fgo卡池
阴阳师[1-300]抽: 阴阳师卡池
* 以上指令可以通过 XX一井 来指定最大抽取数量 *
@@ -58,9 +63,9 @@ usage:
""".strip()
__plugin_des__ = "就算是模拟抽卡也不能改变自己是个非酋"
__plugin_cmd__ = [
"原神[1-300]抽",
"原神角色[1-300]抽",
"原神武器[1-300]抽",
"原神[1-180]抽",
"原神角色[1-180]抽",
"原神武器[1-180]抽",
"重置原神抽卡",
"方舟[1-300]抽",
"赛马娘[1-200]抽",
@@ -89,325 +94,163 @@ __plugin_settings__ = {
"cmd": ["游戏抽卡", "抽卡"],
}
prts = on_regex(r'.*?方舟([1-9|一][0-9]{0,2}[抽|井|连])', rule=is_switch('prts'), priority=5, block=True)
prts_update = on_keyword({'更新方舟信息', '更新明日方舟信息'}, permission=SUPERUSER, priority=1, block=True)
prts_up_reload = on_keyword({'重载方舟卡池'}, priority=1, block=True)
genshin = on_regex(r'.*?原神(武器|角色)?池?[1-9|一][0-9]{0,2}[抽|井|连]', rule=is_switch('genshin'), priority=5, block=True)
genshin_update = on_keyword({'更新原神信息'}, permission=SUPERUSER, priority=1, block=True)
genshin_reset = on_keyword({'重置原神抽卡'}, priority=1, block=True)
genshin_up_reload = on_keyword({'重载原神卡池'}, priority=1, block=True)
pretty = on_regex(r'.*?马娘卡?[1-9|一][0-9]{0,2}[抽|井|连]', rule=is_switch('pretty'), priority=5, block=True)
pretty_update = on_keyword({'更新马娘信息', '更新赛马娘信息'}, permission=SUPERUSER, priority=1, block=True)
pretty_up_reload = on_keyword({'重载赛马娘卡池'}, priority=1, block=True)
guardian = on_regex(r'.*?坎公骑冠剑武?器?[1-9|一][0-9]{0,2}[抽|井|连]', rule=is_switch('guardian'), priority=5, block=True)
guardian_update = on_keyword({'更新坎公骑冠剑信息'}, permission=SUPERUSER, priority=1, block=True)
guardian_up_reload = on_keyword({'重载坎公骑冠剑卡池'}, priority=1, block=True)
pcr = on_regex(r'.*?(pcr|公主连结|公主连接|公主链接|公主焊接)[1-9|一][0-9]{0,2}[抽|井|连]', rule=is_switch('pcr'), priority=5, block=True)
pcr_update = on_keyword({'更新pcr信息', '更新公主连结信息'}, permission=SUPERUSER, priority=1, block=True)
azur = on_regex(r'.*?碧蓝航?线?(轻型|重型|特型)池?[1-9|一][0-9]{0,2}[抽|连]', rule=is_switch('azur'), priority=5, block=True)
azur_update = on_keyword({'更新碧蓝信息', '更新碧蓝航线信息'}, permission=SUPERUSER, priority=1, block=True)
fgo = on_regex(r'.*?fgo[1-9|一][0-9]{0,2}[抽|连]', rule=is_switch('fgo'), priority=5, block=True)
fgo_update = on_keyword({'更新fgo信息'}, permission=SUPERUSER, priority=1, block=True)
onmyoji = on_regex(r'.*?阴阳师[1-9|一][0-9]{0,2}[抽|连]', rule=is_switch('onmyoji'), priority=5, block=True)
onmyoji_update = on_keyword({'更新阴阳师信息'}, permission=SUPERUSER, priority=1, block=True)
@dataclass
class Game:
keywords: Set[str]
handle: BaseHandle
flag: bool
max_count: int = 300 # 一次最大抽卡数
reload_time: Optional[int] = None # 重载UP池时间(小时)
@prts.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State, reg: Tuple[Any, ...] = RegexGroup()):
msg = str(event.get_message()).strip()
if msg in ['方舟一井', '方舟1井']:
num = 300
else:
rmsg = re.search(r'.*?方舟(.*)[抽|连]', msg)
if rmsg:
num, flag = check_num(rmsg.group(1), 300)
if not flag:
await prts.finish(num, at_sender=True)
else:
return
await prts.send(await prts_draw(int(num)), at_sender=True)
games = (
Game({"azur", "碧蓝", "碧蓝航线"}, AzurHandle(), draw_config.AZUR_FLAG),
Game({"fgo", "命运冠位指定"}, FgoHandle(), draw_config.FGO_FLAG),
Game(
{"genshin", "原神"},
GenshinHandle(),
draw_config.GENSHIN_FLAG,
max_count=180,
reload_time=18,
),
Game(
{"guardian", "坎公骑冠剑"},
GuardianHandle(),
draw_config.GUARDIAN_FLAG,
reload_time=4,
),
Game({"onmyoji", "阴阳师"}, OnmyojiHandle(), draw_config.ONMYOJI_FLAG),
Game({"pcr", "公主连结", "公主连接", "公主链接", "公主焊接"}, PcrHandle(), draw_config.PCR_FLAG),
Game(
{"pretty", "马娘", "赛马娘"},
PrettyHandle(),
draw_config.PRETTY_FLAG,
max_count=200,
reload_time=4,
),
Game({"prts", "方舟", "明日方舟"}, PrtsHandle(), draw_config.PRTS_FLAG, reload_time=4),
)
@prts_up_reload.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State):
text = await reload_prts_pool()
await prts_up_reload.finish(Message(f'重载完成!\n{text}'))
@genshin.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State):
msg = str(event.get_message()).strip()
rmsg = re.search(r'.*?原神(武器|角色)?池?(.*)[抽|井|连]', msg)
if rmsg:
pool_name = rmsg.group(1)
if pool_name == '武器':
pool_name = 'arms'
elif pool_name == '角色':
pool_name = 'char'
else:
pool_name = ''
num = rmsg.group(2)
if msg.find('一井') != -1 or msg.find('1井') != -1:
num = 180
else:
num, flag = check_num(num, 180)
if not flag:
await genshin.finish(num, at_sender=True)
else:
return
await genshin.send(await genshin_draw(event.user_id, int(num), pool_name), at_sender=True)
@genshin_up_reload.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State):
text = await reload_genshin_pool()
await genshin_reset.finish(Message(f'重载成功!\n{text}'))
@genshin_reset.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State):
reset_count(event.user_id)
await genshin_reset.send('重置了原神抽卡次数', at_sender=True)
@pretty.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State):
msg = str(event.get_message()).strip()
if msg.find('1井') != -1 or msg.find('一井') != -1:
num = 200
if msg.find("卡") == -1:
pool_name = 'char'
else:
pool_name = 'card'
else:
rmsg = re.search(r'.*?马娘(.*)[抽|连]', msg)
if rmsg:
num = rmsg.group(1)
if num[0] == '卡':
num = num[1:]
pool_name = 'card'
def create_matchers():
def draw_handler(game: Game) -> T_Handler:
async def handler(
matcher: Matcher, event: MessageEvent, args: Tuple[str, ...] = RegexGroup()
):
pool_name, num, unit = args
if num == "单":
num = 1
else:
pool_name = 'char'
num, flag = check_num(num, 200)
if not flag:
await pretty.finish(num, at_sender=True)
else:
return
await pretty.send(await pretty_draw(int(num), pool_name), at_sender=True)
try:
num = int(cn2an(num, mode="smart"))
except ValueError:
await matcher.finish("必!须!是!数!字!")
if unit == "井":
num *= game.max_count
if num < 1:
await matcher.finish("虚空抽卡???")
elif num > game.max_count:
await matcher.finish("一井都满不足不了你嘛!快爬开!")
pool_name = (
pool_name.replace("池", "")
.replace("武器", "arms")
.replace("角色", "char")
.replace("卡牌", "card")
.replace("卡", "card")
)
try:
res = await game.handle.draw(num, pool_name=pool_name, user_id=event.user_id)
except:
logger.warning(traceback.format_exc())
await matcher.finish("出错了...")
await matcher.finish(res, at_sender=True)
return handler
def update_handler(game: Game) -> T_Handler:
async def handler(matcher: Matcher):
await game.handle.update_info()
await matcher.finish("更新完成!")
return handler
def reload_handler(game: Game) -> T_Handler:
async def handler(matcher: Matcher):
res = await game.handle.reload_pool()
if res:
await matcher.finish(res)
return handler
def reset_handler(game: Game) -> T_Handler:
async def handler(matcher: Matcher, event: MessageEvent):
if game.handle.reset_count(event.user_id):
await matcher.finish("重置成功!")
return handler
def scheduled_job(game: Game) -> T_Handler:
async def handler():
await game.handle.reload_pool()
return handler
for game in games:
pool_pattern = r"([^\s单0-9零一二三四五六七八九百十]{0,3})"
num_pattern = r"(单|[0-9零一二三四五六七八九百十]{1,3})"
unit_pattern = r"([抽|井|连])"
draw_regex = r".*?(?:{})\s*{}\s*{}\s*{}".format(
"|".join(game.keywords), pool_pattern, num_pattern, unit_pattern
)
update_keywords = {f"更新{keyword}信息" for keyword in game.keywords}
reload_keywords = {f"重载{keyword}卡池" for keyword in game.keywords}
reset_keywords = {f"重置{keyword}抽卡" for keyword in game.keywords}
if game.flag:
on_regex(draw_regex, priority=5, block=True).append_handler(
draw_handler(game)
)
on_keyword(
update_keywords, permission=SUPERUSER, priority=1, block=True
).append_handler(update_handler(game))
on_keyword(reload_keywords, priority=1, block=True).append_handler(
reload_handler(game)
)
on_keyword(reset_keywords, priority=1, block=True).append_handler(
reset_handler(game)
)
if game.reload_time:
scheduler.add_job(
scheduled_job(game), trigger="cron", hour=game.reload_time, minute=1
)
@pretty_up_reload.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State):
text = await reload_pretty_pool()
await genshin_reset.finish(Message(f'重载成功!\n{text}'))
@guardian.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State):
msg = str(event.get_message()).strip()
pool_name = 'char'
if msg.find('1井') != -1 or msg.find('一井') != -1:
num = 300
if msg.find('武器') != -1:
pool_name = 'arms'
else:
rmsg = re.search(r'.*?坎公骑冠剑(.*)[抽|连]', msg)
if rmsg:
num = rmsg.group(1)
if num.find('武器') != -1:
pool_name = 'arms'
num = num.replace('武器', '')
num, flag = check_num(num, 300)
if not flag:
await guardian.finish(num, at_sender=True)
else:
return
await guardian.send(await guardian_draw(int(num), pool_name), at_sender=True)
@guardian_up_reload.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State):
text = await reload_guardian_pool()
await genshin_reset.finish(Message(f'重载成功!\n{text}'))
@pcr.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State):
msg = str(event.get_message()).strip()
if msg.find('1井') != -1 or msg.find('一井') != -1:
num = 300
else:
rmsg = re.search(r'.*?(pcr|公主连结)(.*)[抽|井|连]', msg)
if rmsg:
num, flag = check_num(rmsg.group(2), 300)
if not flag:
await pcr.finish(num, at_sender=True)
else:
return
await pcr.send(await pcr_draw(int(num)), at_sender=True)
@azur.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State):
msg = str(event.get_message()).strip()
rmsg = re.search(r'.*?碧蓝航?线?(轻型|重型|特型)池?(.*)[抽|连]', msg)
if rmsg:
pool_name = rmsg.group(1)
num, flag = check_num(rmsg.group(2), 300)
if not flag:
await azur.finish(num, at_sender=True)
else:
return
await azur.send(await azur_draw(int(num), pool_name), at_sender=True)
@fgo.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State):
msg = str(event.get_message()).strip()
rmsg = re.search(r'.*?fgo(.*)[抽|连]', msg)
if rmsg:
num, flag = check_num(rmsg.group(1), 300)
if not flag:
await fgo.finish(num, at_sender=True)
else:
return
await fgo.send(await fgo_draw(int(num)), at_sender=True)
@onmyoji.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State):
msg = str(event.get_message()).strip()
rmsg = re.search(r'.*?阴阳师(.*)[抽|连]', msg)
if rmsg:
num, flag = check_num(rmsg.group(1), 300)
if not flag:
await onmyoji.finish(num, at_sender=True)
else:
return
await onmyoji.send(await onmyoji_draw(int(num)), at_sender=True)
@prts_update.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State):
await update_prts_info()
await prts_update.finish('更新完成!')
@genshin_update.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State):
await update_genshin_info()
await genshin_update.finish('更新完成!')
@pretty_update.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State):
await update_pretty_info()
await genshin_update.finish('更新完成!')
@guardian_update.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State):
await update_guardian_info()
await genshin_update.finish('更新完成!')
@pcr_update.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State):
await update_pcr_info()
await genshin_update.finish('更新完成!')
@azur_update.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State):
await update_azur_info()
await genshin_update.finish('更新完成!')
@fgo_update.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State):
await update_fgo_info()
await genshin_update.finish('更新完成!')
@onmyoji_update.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State):
await update_onmyoji_info()
await genshin_update.finish('更新完成!')
create_matchers()
# 更新资源
@scheduler.scheduled_job(
'cron',
"cron",
hour=4,
minute=1,
)
async def _():
tasks = []
if draw_config.PRTS_FLAG:
tasks.append(asyncio.ensure_future(update_prts_info()))
if draw_config.GENSHIN_FLAG:
tasks.append(asyncio.ensure_future(update_genshin_info()))
if draw_config.PRETTY_FLAG:
tasks.append(asyncio.ensure_future(update_pretty_info()))
if draw_config.GUARDIAN_FLAG:
tasks.append(asyncio.ensure_future(update_guardian_info()))
if draw_config.PCR_FLAG:
tasks.append(asyncio.ensure_future(update_pcr_info()))
if draw_config.AZUR_FLAG:
tasks.append(asyncio.ensure_future(update_azur_info()))
if draw_config.FGO_FLAG:
tasks.append(asyncio.ensure_future(update_fgo_info()))
if draw_config.ONMYOJI_FLAG:
tasks.append(asyncio.ensure_future(update_onmyoji_info()))
for game in games:
if game.flag:
tasks.append(asyncio.ensure_future(game.handle.update_info()))
await asyncio.gather(*tasks)
# 每天四点重载方舟up卡池
@scheduler.scheduled_job(
'cron',
hour=4,
minute=1,
)
driver = nonebot.get_driver()
@driver.on_startup
async def _():
if draw_config.PRTS_FLAG:
await reload_prts_pool()
# 每天四点重载赛马娘up卡池
@scheduler.scheduled_job(
'cron',
hour=4,
minute=1,
)
async def _():
if draw_config.PRETTY_FLAG:
await reload_pretty_pool()
# 每天下午六点点重载原神up卡池
@scheduler.scheduled_job(
'cron',
hour=18,
minute=1,
)
async def _():
if draw_config.PRTS_FLAG:
await reload_genshin_pool()
# 重载坎公骑冠剑卡池
@scheduler.scheduled_job(
'cron',
hour=4,
minute=1,
)
async def _():
if draw_config.GUARDIAN_FLAG:
await reload_guardian_pool()
tasks = []
for game in games:
if game.flag:
game.handle.init_data()
if not game.handle.data_exists():
tasks.append(asyncio.ensure_future(game.handle.update_info()))
await asyncio.gather(*tasks)
-395
View File
@@ -1,395 +0,0 @@
from bs4 import BeautifulSoup
import re
from datetime import datetime, timedelta
from .config import DRAW_DATA_PATH
from asyncio.exceptions import TimeoutError
from nonebot.log import logger
from utils.http_utils import AsyncHttpx
try:
import ujson as json
except ModuleNotFoundError:
import json
headers = {'User-Agent': '"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; TencentTraveler 4.0)"'}
prts_up_char = DRAW_DATA_PATH / "draw_card_up" / "prts_up_char.json"
genshin_up_char = DRAW_DATA_PATH / "draw_card_up" / "genshin_up_char.json"
pretty_up_char = DRAW_DATA_PATH / "draw_card_up" / "pretty_up_char.json"
guardian_up_char = DRAW_DATA_PATH / "draw_card_up" / "guardian_up_char.json"
prts_url = "https://ak.hypergryph.com/news.html"
genshin_url = "https://wiki.biligame.com/ys/%E7%A5%88%E6%84%BF"
pretty_url = "https://wiki.biligame.com/umamusume/%E5%85%AC%E5%91%8A"
guardian_url = "https://wiki.biligame.com/gt/%E9%A6%96%E9%A1%B5"
# 是否过时
def is_expired(data: dict):
times = data['time'].split('-')
for i in range(len(times)):
times[i] = str(datetime.now().year) + '-' + times[i].split('日')[0].strip().replace('月', '-')
start_date = datetime.strptime(times[0], '%Y-%m-%d').date()
end_date = datetime.strptime(times[1], '%Y-%m-%d').date()
now = datetime.now().date()
return not start_date <= now <= end_date
# 检查写入
def check_write(data: dict, up_char_file):
try:
if is_expired(data['char']):
for x in list(data.keys()):
data[x]['title'] = ''
else:
with open(up_char_file, 'w', encoding='utf8') as f:
json.dump(data, f, indent=4, ensure_ascii=False)
if not up_char_file.exists():
with open(up_char_file, 'w', encoding='utf8') as f:
json.dump(data, f, indent=4, ensure_ascii=False)
else:
with open(up_char_file, 'r', encoding='utf8') as f:
old_data = json.load(f)
if is_expired(old_data['char']):
return old_data
else:
with open(up_char_file, 'w', encoding='utf8') as f:
json.dump(data, f, indent=4, ensure_ascii=False)
except ValueError:
pass
return data
class PrtsAnnouncement:
def __init__(self):
self.game_name = '明日方舟'
async def _get_announcement_text(self):
res = await AsyncHttpx.get(prts_url, timeout=7)
soup = BeautifulSoup(res.text, 'lxml')
ol = soup.find('ol', {'class': 'articleList active', 'data-category-key': 'LATEST'})
for li in ol:
type_ = li.find('span', {'class': 'articleItemCate'}).text
if type_ == '活动':
a = li.find('a')['href']
return (await AsyncHttpx.get(f'https://ak.hypergryph.com{a}', timeout=7)).text
async def update_up_char(self):
prts_up_char.parent.mkdir(parents=True, exist_ok=True)
if prts_up_char.exists():
with open(prts_up_char, 'r', encoding='utf8') as f:
data = json.load(f)
if not data.get('char'):
prts_up_char.unlink()
try:
data = {'char': {'up_char': {'6': {}, '5': {}, '4': {}}, 'title': '', 'time': '', 'pool_img': ''}}
text = await self._get_announcement_text()
soup = BeautifulSoup(text, 'lxml')
content = soup.find('div', {'class': 'article-content'})
contents = [x for x in content.contents if x.text or str(x).find('img') != -1]
start_index = -1
end_index = -1
for i in range(len(contents)):
if str(contents[i]).startswith('<p>'):
r = re.search('(.*)(寻访|复刻).*?开启', contents[i].text)
if r:
if str(contents[i+3].text).find('★') != -1:
img = contents[i-1].find('img')
if img:
data['char']['pool_img'] = img['src']
start_index = i
for j in range(i, len(contents)):
if str(contents[j]).find('注意') != -1:
end_index = j
break
break
contents = contents[start_index: end_index]
title = contents[0].text
data['char']['title'] = title[title.find('【'): title.find('】') + 1]
data['char']['time'] = str(contents[1].text).split(':', maxsplit=1)[1]
for p in contents[2:]:
p = str(p.text)
r = None
if p.find('★') != -1:
if p.find('权值') == -1:
r = re.search(r'.*?:(.*)(占(.*)★.*?的(.*)%)', p)
else:
r = re.search(r'.*?:(.*)(在(.*)★.*?以(.*)倍权值.*?)', p)
star = r.group(2)
if r:
chars = r.group(1)
if chars.find('/') != -1:
chars = chars.strip().split('/')
elif chars.find('\\') != -1:
chars = chars.strip().split('\\')
else:
chars = chars.split('\n')
chars = [x.replace('[限定]', '').strip() for x in chars]
probability = r.group(3)
probability = probability if int(probability) > 10 else f'权{probability}'
for char in chars:
if char.strip():
data['char']['up_char'][star][char.strip()] = probability
except TimeoutError:
logger.warning(f'更新明日方舟UP池信息超时...')
if prts_up_char.exists():
with open(prts_up_char, 'r', encoding='utf8') as f:
data = json.load(f)
except Exception as e:
logger.error(f'更新明日方舟未知错误 e:{e}')
if prts_up_char.exists():
with open(prts_up_char, 'r', encoding='utf8') as f:
data = json.load(f)
return check_write(data, prts_up_char)
class GenshinAnnouncement:
def __init__(self):
self.game_name = '原神'
async def _get_announcement_text(self):
return (await AsyncHttpx.get(genshin_url, timeout=7)).text
async def update_up_char(self):
genshin_up_char.parent.mkdir(exist_ok=True, parents=True)
data = {
'char': {'up_char': {'5': {}, '4': {}}, 'title': '', 'time': '', 'pool_img': ''},
'arms': {'up_char': {'5': {}, '4': {}}, 'title': '', 'time': '', 'pool_img': ''}
}
text = await self._get_announcement_text()
soup = BeautifulSoup(text, 'lxml')
try:
div = soup.find_all('div', {'class': 'row'})[1]
tables = div.find_all('table', {'class': 'wikitable'})
for table in tables:
trs = table.find('tbody').find_all('tr')
pool_img = trs[0].find('th').find('img')
if pool_img['title'].find('角色活动') == -1:
type_ = 'arms'
else:
type_ = 'char'
try:
data[type_]['pool_img'] = str(pool_img['srcset']).split(' ')[0]
except KeyError:
data[type_]['pool_img'] = pool_img['src']
data[type_]['title'] = str(pool_img['title']).split(f'期{"角色" if type_ == "char" else "武器"}')[0][:-3]
data[type_]['time'] = trs[1].find('td').text
if data[type_]['time'][-1] == '\n':
data[type_]['time'] = data[type_]['time'][:-1]
if '版本更新后' in data[type_]['time']:
sp = data[type_]['time'].split('~')
end_time = datetime.strptime(sp[1].strip(), "%Y/%m/%d %H:%M")
start_time = end_time - timedelta(days=20)
data[type_]['time'] = start_time.strftime('%Y/%m/%d') + ' ~ ' + end_time.strftime('%Y/%m/%d')
tmp = ''
for tm in data[type_]['time'].split('~'):
date_time_sp = tm.split('/')
date_time_sp[2] = date_time_sp[2].strip().replace(' ', '日 ')
tmp += date_time_sp[1] + '月' + date_time_sp[2] + ' - '
data[type_]['time'] = tmp[:-2].strip()
for a in trs[2].find('td').find_all('a'):
char_name = a['title']
data[type_]['up_char']['5'][char_name] = "50"
for a in trs[3].find('td').find_all('a'):
char_name = a['title']
data[type_]['up_char']['4'][char_name] = "50"
except TimeoutError:
logger.warning(f'更新原神UP池信息超时...')
if genshin_up_char.exists():
with open(genshin_up_char, 'r', encoding='utf8') as f:
data = json.load(f)
except Exception as e:
logger.error(f'更新原神UP失败,疑似UP池已结束, e:{e}')
if genshin_up_char.exists():
with open(genshin_up_char, 'r', encoding='utf8') as f:
data = json.load(f)
data['char']['title'] = ''
data['arms']['title'] = ''
with open(genshin_up_char, 'w', encoding='utf8') as wf:
json.dump(data, wf, ensure_ascii=False, indent=4)
return data
return check_write(data, genshin_up_char)
class PrettyAnnouncement:
def __init__(self):
self.game_name = '赛马娘'
async def _get_announcement_text(self):
res = await AsyncHttpx.get(pretty_url, timeout=7)
soup = BeautifulSoup(res.text, 'lxml')
divs = soup.find('div', {'id': 'mw-content-text'}).find('div').find_all('div')
for div in divs:
a = div.find('a')
try:
title = a['title']
except (KeyError, TypeError):
continue
if title.find('新角色追加') != -1:
url = a['href']
break
return (await AsyncHttpx.get(f'https://wiki.biligame.com/{url}', timeout=7)).text, title[:-2]
async def update_up_char(self):
data = {
'char': {'up_char': {'3': {}, '2': {}, '1': {}}, 'title': '', 'time': '', 'pool_img': ''},
'card': {'up_char': {'3': {}, '2': {}, '1': {}}, 'title': '', 'time': '', 'pool_img': ''}
}
try:
text, title = await self._get_announcement_text()
soup = BeautifulSoup(text, 'lxml')
context = soup.find('div', {'class': 'toc-sticky'})
if not context:
context = soup.find('div', {'class': 'mw-parser-output'})
data['char']['title'] = title
data['card']['title'] = title
for big in context.find_all('big'):
r = re.search(r'\d{1,2}/\d{1,2} \d{1,2}:\d{1,2}', str(big.text))
if r:
time = str(big.text)
break
else:
logger.warning('赛马娘UP无法找到活动日期....取消更新UP池子...')
return
time = time.replace('~', '-').replace('/', '月').split(' ')
time = time[0] + '日 ' + time[1] + ' - ' + time[3] + '日 ' + time[4]
data['char']['time'] = time
data['card']['time'] = time
for p in context.find_all('p'):
if str(p).find('当期UP赛马娘') != -1 and str(p).find('■') != -1:
if not data['char']['pool_img']:
try:
data['char']['pool_img'] = p.find('img')['src']
except TypeError:
for center in context.find_all('center'):
try:
img = center.find('img')
if img and str(img['alt']).find('新马娘') != -1 and str(img['alt']).find('总览') == 1:
data['char']['pool_img'] = img['src']
except (TypeError, KeyError):
pass
r = re.findall(r'.*?当期UP赛马娘([\s\S]*)<奖励内容>.*?', str(p))
if r:
for x in r:
x = str(x).split('\n')
for msg in x:
if msg.find('★') != -1:
msg = msg.replace('<br/>', '')
char_name = msg[msg.find('['):].strip()
if (star := len(msg[:msg.find('[')].strip())) == 3:
data['char']['up_char']['3'][char_name] = '70'
elif star == 2:
data['char']['up_char']['2'][char_name] = '70'
elif star == 1:
data['char']['up_char']['1'][char_name] = '70'
if str(p).find('(当期UP对象)') != -1 and str(p).find('赛马娘') == -1 and str(p).find('■') != -1:
# data['card']['pool_img'] = p.find('img')['src']
if not data['char']['pool_img']:
try:
data['char']['pool_img'] = p.find('img')['src']
except TypeError:
for center in context.find_all('center'):
try:
img = center.find('img')
if img and str(img['alt']).find('新卡') != -1 and str(img['alt']).find('总览') == 1:
data['card']['pool_img'] = img['src']
except (TypeError, KeyError):
pass
r = re.search(r'■全?新?支援卡(当期UP对象)([\s\S]*)</p>', str(p))
if r:
rmsg = r.group(1).strip()
rmsg = rmsg.split('<br/>')
rmsg = [x for x in rmsg if x]
for x in rmsg:
x = x.replace('\n', '').replace('・', '')
star = x[:x.find('[')].strip()
char_name = x[x.find('['):].strip()
if star == 'SSR':
data['card']['up_char']['3'][char_name] = '70'
if star == 'SR':
data['card']['up_char']['2'][char_name] = '70'
if star == 'R':
data['card']['up_char']['1'][char_name] = '70'
# 日文->中文
with open(DRAW_DATA_PATH / 'pretty_card.json', 'r', encoding='utf8') as f:
all_data = json.load(f)
for star in data['card']['up_char'].keys():
for name in list(data['card']['up_char'][star].keys()):
char_name = name.split(']')[1].strip()
tp_name = name[name.find('['): name.find(']') + 1].strip().replace('[', '【').replace(']', '】')
for x in all_data.keys():
if all_data[x]['名称'].find(tp_name) != -1 and all_data[x]['关联角色'] == char_name:
data['card']['up_char'][star].pop(name)
data['card']['up_char'][star][all_data[x]['中文名']] = '70'
except TimeoutError:
logger.warning(f'更新赛马娘UP池信息超时...')
if pretty_up_char.exists():
with open(pretty_up_char, 'r', encoding='utf8') as f:
data = json.load(f)
except Exception as e:
logger.error(f'赛马娘up更新未知错误 {type(e)}:{e}')
if pretty_up_char.exists():
with open(pretty_up_char, 'r', encoding='utf8') as f:
data = json.load(f)
return check_write(data, pretty_up_char)
class GuardianAnnouncement:
def __init__(self):
self.game_name = '坎公骑冠剑'
async def _get_announcement_text(self):
return (await AsyncHttpx.get(guardian_url, timeout=7)).text
async def update_up_char(self):
data = {
'char': {'up_char': {'3': {}}, 'title': '', 'time': '', 'pool_img': ''},
'arms': {'up_char': {'5': {}}, 'title': '', 'time': '', 'pool_img': ''}
}
try:
text = await self._get_announcement_text()
soup = BeautifulSoup(text, 'lxml')
context = soup.select('div.col-sm-3:nth-child(3) > div:nth-child(2) > div:nth-child(1) '
'> div:nth-child(2) > div:nth-child(3) > font:nth-child(1)')[0]
title = context.find('p').find('b').text
tmp = title.split(',')
time = ''
for msg in tmp:
r = re.search(r'[从|至](.*)(开始|结束)', msg)
if r:
time += r.group(1).strip() + ' - '
time = time[:-3]
title = time.split(' - ')[0] + 'UP卡池'
data['char']['title'] = title
data['arms']['title'] = title
data['char']['time'] = time
data['arms']['time'] = time
start_idx = -1
end_idx = -1
index = 0
divs = context.find_all('div')
for x in divs:
if x.text == '角色':
start_idx = index
if x.text == '武器':
end_idx = index
break
index += 1
for x in divs[start_idx + 1: end_idx]:
name = x.find('p').find_all('a')[-1].text
data['char']['up_char']['3'][name] = '0'
for x in divs[end_idx + 1:]:
name = x.find('p').find_all('a')[-1].text
data['arms']['up_char']['5'][name] = '0'
except TimeoutError:
logger.warning(f'更新坎公骑冠剑UP池信息超时...')
if guardian_up_char.exists():
with open(guardian_up_char, 'r', encoding='utf8') as f:
data = json.load(f)
except Exception as e:
logger.error(f'坎公骑冠剑up更新未知错误 {type(e)}:{e}')
return check_write(data, guardian_up_char)
@@ -1,63 +0,0 @@
import asyncio
import nonebot
from nonebot.log import logger
from .pcr_handle import update_pcr_info, init_pcr_data
from .azur_handle import update_azur_info, init_azur_data
from .prts_handle import update_prts_info, init_prts_data
from .pretty_handle import update_pretty_info, init_pretty_data
from .guardian_handle import update_guardian_info, init_guardian_data
from .genshin_handle import update_genshin_info, init_genshin_data
from .fgo_handle import update_fgo_info, init_fgo_data
from .onmyoji_handle import update_onmyoji_info, init_onmyoji_data
from .config import draw_config, DRAW_DATA_PATH
driver = nonebot.get_driver()
@driver.on_startup
async def async_update_game():
tasks = []
init_lst = [init_pcr_data, init_pretty_data, init_azur_data, init_prts_data, init_genshin_data, init_guardian_data,
init_fgo_data, init_onmyoji_data]
if draw_config.PRTS_FLAG and not (DRAW_DATA_PATH / 'prts.json').exists():
tasks.append(asyncio.ensure_future(update_prts_info()))
init_lst.remove(init_prts_data)
if draw_config.PRETTY_FLAG and (not (DRAW_DATA_PATH / 'pretty.json').exists() or
not (DRAW_DATA_PATH / 'pretty_card.json').exists()):
tasks.append(asyncio.ensure_future(update_pretty_info()))
init_lst.remove(init_pretty_data)
if draw_config.GUARDIAN_FLAG and not (DRAW_DATA_PATH / 'guardian.json').exists():
tasks.append(asyncio.ensure_future(update_guardian_info()))
init_lst.remove(init_guardian_data)
if draw_config.PCR_FLAG and not (DRAW_DATA_PATH / 'pcr.json').exists():
tasks.append(asyncio.ensure_future(update_pcr_info()))
init_lst.remove(init_pcr_data)
if draw_config.GENSHIN_FLAG and (not (DRAW_DATA_PATH / 'genshin.json').exists() or
not (DRAW_DATA_PATH / 'genshin_arms.json').exists()):
tasks.append(asyncio.ensure_future(update_genshin_info()))
init_lst.remove(init_genshin_data)
if draw_config.AZUR_FLAG and not (DRAW_DATA_PATH / 'azur.json').exists():
tasks.append(asyncio.ensure_future(update_azur_info()))
init_lst.remove(init_azur_data)
if draw_config.FGO_FLAG and (not (DRAW_DATA_PATH / 'fgo.json').exists() or
not (DRAW_DATA_PATH / 'fgo_card.json').exists()):
tasks.append(asyncio.ensure_future(update_fgo_info()))
init_lst.remove(init_fgo_data)
if draw_config.ONMYOJI_FLAG and not (DRAW_DATA_PATH / 'onmyoji.json').exists():
tasks.append(asyncio.ensure_future(update_onmyoji_info()))
init_lst.remove(init_onmyoji_data)
try:
await asyncio.gather(*tasks)
for func in init_lst:
await func()
except asyncio.exceptions.CancelledError:
logger.warning('更新异常:CancelledError,再次更新...')
await async_update_game()
-62
View File
@@ -1,62 +0,0 @@
from nonebot.adapters.onebot.v11 import MessageSegment
import random
from .update_game_simple_info import update_simple_info
from .util import generate_img, init_star_rst, BaseData, set_list, get_star, max_card, format_card_information
from .config import draw_config, DRAW_DATA_PATH
from dataclasses import dataclass
from .init_card_pool import init_game_pool
try:
import ujson as json
except ModuleNotFoundError:
import json
ALL_CHAR = []
@dataclass
class AzurChar(BaseData):
type_: str # 舰娘类型
async def azur_draw(count: int, pool_name: str):
# 0 1 2
cnlist = ['金', '紫', '蓝', '白']
star_list = [0, 0, 0, 0]
char_list, char_dict, max_star_list, star_list, max_star_index_list = \
format_card_information(count, star_list, _get_azur_card, pool_name, guaranteed=False)
rst = init_star_rst(star_list, cnlist, max_star_list, max_star_index_list)
if count > 90:
char_list = set_list(char_list)
return MessageSegment.image("base64://" + await generate_img(char_list, 'azur', star_list)) \
+ '\n' + rst[:-1] + '\n' + max_card(char_dict)
async def update_azur_info():
global ALL_CHAR
url = 'https://wiki.biligame.com/blhx/舰娘图鉴'
data, code = await update_simple_info(url, 'azur')
if code == 200:
ALL_CHAR = init_game_pool('azur', data, AzurChar)
async def init_azur_data():
global ALL_CHAR
if draw_config.AZUR_FLAG:
with (DRAW_DATA_PATH / 'azur.json').open('r', encoding='utf8') as f:
azur_dict = json.load(f)
ALL_CHAR = init_game_pool('azur', azur_dict, AzurChar)
# 抽取卡池
def _get_azur_card(pool_name: str, mode: int = 1):
global ALL_CHAR
azur_config = draw_config.azur
if pool_name == '轻型':
type_ = ['驱逐', '轻巡', '维修']
elif pool_name == '重型':
type_ = ['重巡', '战列', '战巡', '重炮']
else:
type_ = ['维修', '潜艇', '重巡', '轻航', '航母']
star = get_star([4, 3, 2, 1], [azur_config.AZUR_FOUR_P, azur_config.AZUR_THREE_P, azur_config.AZUR_TWO_P, azur_config.AZUR_ONE_P])
chars = [x for x in ALL_CHAR if x.star == star and x.type_ in type_ and not x.limited]
return random.choice(chars), 4 - star
Executable → Regular
+57 -53
View File
@@ -1,8 +1,9 @@
import nonebot
from pathlib import Path
from nonebot.log import logger
from pydantic import BaseModel, Extra, ValidationError
from configs.path_config import IMAGE_PATH, DATA_PATH
from configs.config import Config
from pydantic import BaseModel, Extra
from configs.config import Config as AConfig
from configs.path_config import DATA_PATH
try:
import ujson as json
@@ -15,8 +16,8 @@ class GenshinConfig(BaseModel, extra=Extra.ignore):
GENSHIN_FIVE_P: float = 0.006
GENSHIN_FOUR_P: float = 0.051
GENSHIN_THREE_P: float = 0.43
GENSHIN_G_FIVE_P: float = 0.13
GENSHIN_G_FOUR_P: float = 0.016
GENSHIN_G_FIVE_P: float = 0.016
GENSHIN_G_FOUR_P: float = 0.13
I72_ADD: float = 0.0585
@@ -87,43 +88,20 @@ class OnmyojiConfig(BaseModel, extra=Extra.ignore):
ONMYOJI_R: float = 0.7875
class PathDict(BaseModel, extra=Extra.ignore):
genshin: str = "原神"
prts: str = "明日方舟"
pretty: str = "赛马娘"
guardian: str = "坎公骑冠剑"
pcr: str = "公主连结"
azur: str = "碧蓝航线"
fgo: str = "命运-冠位指定"
onmyoji: str = "阴阳师"
class DrawConfig(BaseModel, extra=Extra.ignore):
class Config(BaseModel, extra=Extra.ignore):
# 开关
PRTS_FLAG: bool = Config.get_config("draw_card", "PRTS_FLAG")
GENSHIN_FLAG: bool = Config.get_config("draw_card", "GENSHIN_FLAG")
PRETTY_FLAG: bool = Config.get_config("draw_card", "PRETTY_FLAG")
GUARDIAN_FLAG: bool = Config.get_config("draw_card", "GUARDIAN_FLAG")
PCR_FLAG: bool = Config.get_config("draw_card", "PCR_FLAG")
AZUR_FLAG: bool = Config.get_config("draw_card", "AZUR_FLAG")
FGO_FLAG: bool = Config.get_config("draw_card", "FGO_FLAG")
ONMYOJI_FLAG: bool = Config.get_config("draw_card", "ONMYOJI_FLAG")
PRTS_FLAG: bool = AConfig.get_config("draw_card", "PRTS_FLAG")
GENSHIN_FLAG: bool = AConfig.get_config("draw_card", "GENSHIN_FLAG")
PRETTY_FLAG: bool = AConfig.get_config("draw_card", "PRETTY_FLAG")
GUARDIAN_FLAG: bool = AConfig.get_config("draw_card", "GUARDIAN_FLAG")
PCR_FLAG: bool = AConfig.get_config("draw_card", "PCR_FLAG")
AZUR_FLAG: bool = AConfig.get_config("draw_card", "AZUR_FLAG")
FGO_FLAG: bool = AConfig.get_config("draw_card", "FGO_FLAG")
ONMYOJI_FLAG: bool = AConfig.get_config("draw_card", "ONMYOJI_FLAG")
# 其他配置
PCR_TAI: bool = Config.get_config("draw_card", "PCR_TAI")
SEMAPHORE: int = Config.get_config("draw_card", "SEMAPHORE")
# 路径
path_dict: dict = {
"genshin": "原神",
"prts": "明日方舟",
"pretty": "赛马娘",
"guardian": "坎公骑冠剑",
"pcr": "公主连结",
"azur": "碧蓝航线",
"fgo": "命运-冠位指定",
"onmyoji": "阴阳师",
}
PCR_TAI: bool = AConfig.get_config("draw_card", "PCR_TAI")
SEMAPHORE: int = AConfig.get_config("draw_card", "SEMAPHORE")
# 抽卡概率
prts: PrtsConfig = PrtsConfig()
@@ -137,27 +115,53 @@ class DrawConfig(BaseModel, extra=Extra.ignore):
driver = nonebot.get_driver()
global_config = driver.config
DRAW_DATA_PATH = DATA_PATH / "draw_card"
DRAW_IMAGE_PATH = IMAGE_PATH / "draw_card"
# DRAW_PATH = Path(draw_path) if draw_path else Path("data/draw_card").absolute()
config_path = DRAW_DATA_PATH / "draw_card_config" / "draw_card_config.json"
draw_config: Config = DrawConfig()
DRAW_PATH = DATA_PATH / "draw_card"
config_path = DRAW_PATH / "draw_card_config" / "draw_card_config.json"
draw_config: Config = Config()
for game_flag, game_name in zip(
[
"PRTS_FLAG",
"GENSHIN_FLAG",
"PRETTY_FLAG",
"GUARDIAN_FLAG",
"PCR_FLAG",
"AZUR_FLAG",
"FGO_FLAG",
"ONMYOJI_FLAG",
"PCR_TAI",
],
["明日方舟", "原神", "赛马娘", "坎公骑冠剑", "公主连结", "碧蓝航线", "命运-冠位指定(FGO)", "阴阳师", "pcr台服卡池"],
):
AConfig.add_plugin_config(
"draw_card",
game_flag,
True,
name="游戏抽卡",
help_=f"{game_name} 抽卡开关",
default_value=True,
)
AConfig.add_plugin_config(
"draw_card", "SEMAPHORE", 5, help_=f"异步数据下载数量限制", default_value=5
)
@driver.on_startup
def check_config():
global draw_config
draw_config = Config()
if not config_path.exists():
config_path.parent.mkdir(parents=True, exist_ok=True)
draw_config = DrawConfig()
draw_config = Config()
logger.warning("draw_card:配置文件不存在,已重新生成配置文件.....")
json.dump(
draw_config.dict(),
config_path.open("w", encoding="utf8"),
indent=4,
ensure_ascii=False,
)
with config_path.open("w", encoding="utf8") as fp:
json.dump(
draw_config.dict(),
fp,
indent=4,
ensure_ascii=False,
)
+117 -61
View File
@@ -1,4 +1,12 @@
from typing import Optional, Union
from typing import Optional
from pydantic import BaseModel
import time
class BaseUserCount(BaseModel):
count: int = 1 # 当前抽卡次数
time_: int = time.time() # 抽卡时间,当超过一定时间时将重置抽卡次数
timeout: int = 60 # 超时时间60秒
class DrawCountManager:
@@ -6,7 +14,9 @@ class DrawCountManager:
抽卡统计保底
"""
def __init__(self, game_draw_count_rule: tuple, star2name: tuple):
def __init__(
self, game_draw_count_rule: tuple, star2name: tuple, max_draw_count: int
):
"""
初始化保底统计
@@ -14,26 +24,43 @@ class DrawCountManager:
抽卡保底需要的次数和返回的对应名称,例如星级等
:param game_draw_count_rule:抽卡规则
:param star2name:星级对应的名称
:param max_draw_count:最大累计抽卡次数,当下次单次抽卡超过该次数时将会清空数据
"""
# 只有保底
self._data = {}
self._guarantee_tuple = game_draw_count_rule
self._star2name = star2name
self._max_draw_count = max_draw_count
def increase(self, key: int, value: int = 1):
"""
用户抽卡次数加1
"""
if self._data.get(key) is None:
self._data[key] = {
"count": value,
}
for x in range(len(self._guarantee_tuple)):
self._data[key][f"count_{x}"] = 0
self._data[key] = BaseUserCount()
else:
self._data[key][f"count"] += value
if self._data[key][f"count"] > self._guarantee_tuple[-1]:
self._data[key][f"count"] = self._data[key][f"count"] % self._guarantee_tuple[-1]
self._data[key].count += 1
def get_max_guarantee(self):
"""
获取最大保底抽卡次数
"""
return self._guarantee_tuple[-1]
def get_user_count(self, key: int) -> int:
"""
获取当前抽卡次数
"""
return self._data[key].count
def update_time(self, key: int):
"""
更新抽卡时间
"""
self._data[key].time_ = time.time()
def reset(self, key: int):
"""
@@ -41,73 +68,102 @@ class DrawCountManager:
"""
del self._data[key]
def set_count(self, key: int, type_: int, count: int):
if self._data.get(key):
self._data[key][f"count_{type_}"] = count
def check(self, key: int, *args) -> Optional[Union[str, int]]:
"""
是否保底
"""
pass
def get_user_count(self, key: int, type_: Optional[int] = None) -> int:
"""
获取用户当前抽卡次数
"""
if self._data.get(key):
if type_ is None:
return self._data[key]["count"]
return self._data[key][f"count_{type_}"]
return 0
def record_count(self, key: int, type_: int):
"""
抽出对应星级后记录当前次数
"""
if self._data.get(key):
self._data[key][f"count_{type_}"] = self._data[key]["count"]
class GenshinCountManager(DrawCountManager):
class UserCount(BaseUserCount):
five_index: int = 0 # 获取五星时的抽卡次数
four_index: int = 0 # 获取四星时的抽卡次数
is_up: bool = False # 下次五星是否必定为up
def increase(self, key: int, value: int = 1):
"""
用户抽卡次数加1
"""
if self._data.get(key) is None:
self._data[key] = {
"is_up": False,
"count": value,
}
for x in range(len(self._guarantee_tuple)):
self._data[key][f"count_{x}"] = 0
self._data[key] = self.UserCount()
else:
self._data[key][f"count"] += value
if self._data[key][f"count"] > self._guarantee_tuple[-1]:
self._data[key][f"count"] = self._data[key][f"count"] % 180
self._data[key].count += 1
def set_is_up(self, key: int, value: bool):
if self._data.get(key):
self._data[key]["is_up"] = value
"""
设置下次是否必定up
"""
self._data[key].is_up = value
def is_up(self, key: int) -> bool:
if self._data.get(key):
return self._data[key]["is_up"]
return False
"""
判断该次保底是否必定为up
"""
return self._data[key].is_up
def check(self, key: int, *args) -> Optional[Union[str, int]]:
def get_user_five_index(self, key: int) -> int:
"""
获取用户上次获取五星的次数
"""
return self._data[key].five_index
def get_user_four_index(self, key: int) -> int:
"""
获取用户上次获取四星的次数
"""
return self._data[key].four_index
def mark_five_index(self, key: int):
"""
标记用户该次次数为五星
"""
self._data[key].five_index = self._data[key].count
def mark_four_index(self, key: int):
"""
标记用户该次次数为四星
"""
self._data[key].four_index = self._data[key].count
def check_timeout(self, key: int):
"""
检查用户距离上次抽卡是否超时
"""
if key in self._data.keys() and self._is_timeout(key):
del self._data[key]
def check_count(self, key: int, count: int):
"""
检查用户该次抽卡次数累计是否超过最大限制次数
"""
if (
key in self._data.keys()
and self._data[key].count + count > self._max_draw_count
):
del self._data[key]
def _is_timeout(self, key: int) -> bool:
return time.time() - self._data[key].time_ > self._data[key].timeout
def get_user_guarantee_count(self, key: int) -> int:
return (
self.get_max_guarantee()
- (
(
self._data[key].count % self.get_max_guarantee()
if self._data[key].count > 0
else 0
)
- self._data[key].five_index
)
) % self.get_max_guarantee() or self.get_max_guarantee()
def check(self, key: int) -> Optional[int]:
"""
是否保底
"""
# print(self._data)
if self._data.get(key):
for i in [1, 0]:
count = self._data[key]["count"]
if count - self._data[key][f"count_{i}"] == self._guarantee_tuple[i]:
if i in [2, 1]:
# print("clean four count")
self._data[key][f"count_0"] = self._data[key]['count']
self._data[key][f"count_{i}"] = self._data[key]['count']
return self._star2name[i]
user: GenshinCountManager.UserCount = self._data[key]
if user.count - user.five_index == 90:
user.five_index = 90
return 5
if user.count - user.four_index == 10:
user.four_index = user.count
return 4
return None
-111
View File
@@ -1,111 +0,0 @@
from nonebot.adapters.onebot.v11 import MessageSegment
import random
from .update_game_requests_info import update_requests_info
from .util import generate_img, init_star_rst, BaseData, set_list, get_star, max_card
from .config import DRAW_DATA_PATH, draw_config
from dataclasses import dataclass
from .init_card_pool import init_game_pool
try:
import ujson as json
except ModuleNotFoundError:
import json
ALL_CHAR = []
ALL_CARD = []
@dataclass
class FgoChar(BaseData):
pass
async def fgo_draw(count: int):
# 0 1 2
cnlist = ['★★★★★', '★★★★', '★★★']
obj_list, obj_dict, max_star_list, star_list, max_star_index_list = _format_card_information(count)
rst = init_star_rst(star_list, cnlist, max_star_list, max_star_index_list)
if count > 90:
obj_list = set_list(obj_list)
return MessageSegment.image("base64://" + await generate_img(obj_list, 'fgo', star_list)) \
+ '\n' + rst[:-1] + '\n' + max_card(obj_dict)
async def update_fgo_info():
global ALL_CHAR, ALL_CARD
data, code = await update_requests_info('fgo')
if code == 200:
ALL_CHAR = init_game_pool('fgo', data, FgoChar)
data, code = await update_requests_info('fgo_card')
if code == 200:
ALL_CARD = init_game_pool('fgo_card', data, FgoChar)
async def init_fgo_data():
global ALL_CHAR, ALL_CARD
if draw_config.FGO_FLAG:
with (DRAW_DATA_PATH / 'fgo.json').open('r', encoding='utf8') as f:
fgo_dict = json.load(f)
ALL_CHAR = init_game_pool('fgo', fgo_dict, FgoChar)
with (DRAW_DATA_PATH / 'fgo_card.json').open('r', encoding='utf8') as f:
fgo_dict = json.load(f)
ALL_CARD = init_game_pool('fgo', fgo_dict, FgoChar)
# 抽取卡池
def _get_fgo_card(mode: int = 1):
global ALL_CHAR, ALL_CARD
fgo_config = draw_config.fgo
if mode == 1:
star = get_star([8, 7, 6, 5, 4, 3], [fgo_config.FGO_SERVANT_FIVE_P, fgo_config.FGO_SERVANT_FOUR_P, fgo_config.FGO_SERVANT_THREE_P,
fgo_config.FGO_CARD_FIVE_P, fgo_config.FGO_CARD_FOUR_P, fgo_config.FGO_CARD_THREE_P])
elif mode == 2:
star = get_star([5, 4], [fgo_config.FGO_CARD_FIVE_P, fgo_config.FGO_CARD_FOUR_P])
else:
star = get_star([8, 7, 6], [fgo_config.FGO_SERVANT_FIVE_P, fgo_config.FGO_SERVANT_FOUR_P, fgo_config.FGO_SERVANT_THREE_P])
if star > 5:
type_ = 'servant'
star -= 3
chars = [x for x in ALL_CHAR if x.star == star if not x.limited]
else:
type_ = 'card'
chars = [x for x in ALL_CARD if x.star == star if not x.limited]
return random.choice(chars), 5 - star, type_
# 整理数据
def _format_card_information(count: int):
max_star_lst = [] # 获取的最高星级角色列表
max_index_lst = [] # 获取最高星级角色的次数
star_list = [0, 0, 0]
obj_list = [] # 获取所有角色
obj_dict = {} # 获取角色次数字典
servant_count = 0 # 保底计算
card_count = 0 # 保底计算
for i in range(count):
servant_count += 1
card_count += 1
# 四星卡片保底
if card_count == 9:
obj, code, type_ = _get_fgo_card(2)
# 三星从者保底
elif servant_count == 10:
obj, code, type_ = _get_fgo_card(3)
_count = 0
# 普通抽
else:
obj, code, type_ = _get_fgo_card()
star_list[code] += 1
if type_ == 'card' and code < 2:
card_count = 0
if type_ == 'servant':
servant_count = 0
if code == 0:
max_star_lst.append(obj.name)
max_index_lst.append(i)
try:
obj_dict[obj.name] += 1
except KeyError:
obj_dict[obj.name] = 1
obj_list.append(obj)
return obj_list, obj_dict, max_star_lst, star_list, max_index_lst
-288
View File
@@ -1,288 +0,0 @@
from nonebot.adapters.onebot.v11 import MessageSegment, Message
import random
from .update_game_info import update_info
from .util import (
generate_img,
init_rst,
BaseData,
set_list,
get_star,
init_up_char,
)
from .config import DRAW_DATA_PATH, draw_config
from .count_manager import GenshinCountManager
from dataclasses import dataclass
from .init_card_pool import init_game_pool
from .announcement import GenshinAnnouncement
try:
import ujson as json
except ModuleNotFoundError:
import json
announcement = GenshinAnnouncement()
draw_count_manager = GenshinCountManager((10, 90), ("4", "5"))
# genshin_five = {}
# genshin_count = {}
# genshin_pl_count = {}
ALL_CHAR = []
ALL_ARMS = []
UP_CHAR = []
UP_ARMS = []
_CURRENT_CHAR_POOL_TITLE = ""
_CURRENT_ARMS_POOL_TITLE = ""
POOL_IMG = ""
@dataclass
class GenshinChar(BaseData):
pass
async def genshin_draw(user_id: int, count: int, pool_name: str):
# 0 1 2
star = ["★★★★★", "★★★★", "★★★"]
(
char_list,
five_dict,
star_num_list,
) = _format_card_information(count, user_id, pool_name)
title = ""
up_type = []
up_list = []
if pool_name == "char" and _CURRENT_CHAR_POOL_TITLE:
up_type = UP_CHAR
title = _CURRENT_CHAR_POOL_TITLE
elif pool_name == "arms" and _CURRENT_ARMS_POOL_TITLE:
up_type = UP_ARMS
title = _CURRENT_ARMS_POOL_TITLE
tmp = ""
if up_type:
for x in up_type:
for operator in x.operators:
up_list.append(operator)
if x.star == 5:
tmp += f'五星UP:{" ".join(x.operators)} \n'
elif x.star == 4:
tmp += f'四星UP:{" ".join(x.operators)}'
rst = init_rst(five_dict, star_num_list, star, up_list)
pool_info = f"当前up池:{title}\n{tmp}" if title else ""
if count > 90:
char_list = set_list(char_list)
return (
pool_info
+ "\n"
+ MessageSegment.image(
"base64://" + await generate_img(char_list, "genshin", star_num_list)
)
+ rst
+ f'\n距离保底发还剩 {draw_count_manager.get_user_count(user_id, 1) % 90} 抽'
+ "\n【五星:0.6%,四星:5.1%\n第72抽开始五星概率每抽加0.585%】"
)
async def update_genshin_info():
global ALL_CHAR, ALL_ARMS
url = "https://wiki.biligame.com/ys/角色筛选"
data, code = await update_info(url, "genshin")
if code == 200:
ALL_CHAR = init_game_pool("genshin", data, GenshinChar)
url = "https://wiki.biligame.com/ys/武器图鉴"
data, code = await update_info(
url,
"genshin_arms",
)
if code == 200:
ALL_ARMS = init_game_pool("genshin_arms", data, GenshinChar)
await _genshin_init_up_char()
async def init_genshin_data():
global ALL_CHAR, ALL_ARMS
if draw_config.GENSHIN_FLAG:
if not (DRAW_DATA_PATH / "genshin.json").exists() or not (DRAW_DATA_PATH / "genshin_arms.json").exists():
await update_genshin_info()
else:
with (DRAW_DATA_PATH / "genshin.json").open("r", encoding="utf8") as f:
genshin_dict = json.load(f)
with (DRAW_DATA_PATH / "genshin_arms.json").open("r", encoding="utf8") as f:
genshin_ARMS_dict = json.load(f)
ALL_CHAR = init_game_pool("genshin", genshin_dict, GenshinChar)
ALL_ARMS = init_game_pool("genshin_arms", genshin_ARMS_dict, GenshinChar)
await _genshin_init_up_char()
# 抽取卡池
def _get_genshin_card(mode: int = 1, pool_name: str = "", add: float = 0.0, is_up: bool = False):
"""
mode 1:普通抽 2:四星保底 3:五星保底
"""
global ALL_ARMS, ALL_CHAR, UP_ARMS, UP_CHAR, _CURRENT_ARMS_POOL_TITLE, _CURRENT_CHAR_POOL_TITLE
genshin_config = draw_config.genshin
if mode == 1:
star = get_star(
[5, 4, 3], [genshin_config.GENSHIN_FIVE_P + add, genshin_config.GENSHIN_FOUR_P, genshin_config.GENSHIN_THREE_P]
)
elif mode == 2:
star = get_star([5, 4], [genshin_config.GENSHIN_G_FIVE_P + add, genshin_config.GENSHIN_G_FOUR_P])
else:
star = 5
if pool_name == "char":
data_lst = UP_CHAR
flag = _CURRENT_CHAR_POOL_TITLE
type_all_lst = ALL_CHAR + [
x for x in ALL_ARMS if x.star == star and x.star < 5
]
elif pool_name == "arms":
data_lst = UP_ARMS
flag = _CURRENT_ARMS_POOL_TITLE
type_all_lst = ALL_ARMS + [
x for x in ALL_CHAR if x.star == star and x.star < 5
]
else:
data_lst = ""
flag = ""
type_all_lst = ""
all_lst = ALL_ARMS + ALL_CHAR
# 是否UP
try:
if flag and star > 3:
# 获取up角色列表
up_char_lst = [x.operators for x in data_lst if x.star == star][0]
# print(up_char_lst)
# 成功获取up角色
if random.random() < 0.5 or is_up:
up_char_name = random.choice(up_char_lst)
acquire_char = [x for x in all_lst if x.name == up_char_name][0]
else:
# 无up
all_char_lst = [
x
for x in type_all_lst
if x.star == star and x.name not in up_char_lst and not x.limited
]
acquire_char = random.choice(all_char_lst)
else:
chars = [x for x in all_lst if x.star == star and not x.limited]
acquire_char = random.choice(chars)
except IndexError:
chars = [x for x in all_lst if x.star == star and not x.limited]
acquire_char = random.choice(chars)
return acquire_char, 5 - star
def _format_card_information(_count: int, user_id, pool_name):
char_list = [] # 获取角色列表
star_num_list = [0, 0, 0] # 各个星级数量
five_dict = {} # 五星数量
add = 0.0
pool = UP_CHAR if pool_name == 'char' else UP_ARMS
for _ in range(_count):
draw_count_manager.increase(user_id)
star = draw_count_manager.check(user_id)
if (draw_count_manager.get_user_count(user_id) - draw_count_manager.get_user_count(user_id, 1)) % 90 >= 72:
add += draw_config.genshin.I72_ADD
if star:
star = int(star)
if star == 4:
char, code = _get_genshin_card(2, pool_name, add=add)
draw_count_manager.record_count(user_id, 0)
else:
char, code = _get_genshin_card(3, pool_name, add, draw_count_manager.is_up(user_id))
else:
char, code = _get_genshin_card(1, pool_name, add, draw_count_manager.is_up(user_id))
if code == 0:
add = 0
if not five_dict.get(char.name):
five_dict[char.name] = [draw_count_manager.get_user_count(user_id)]
else:
five_dict[char.name].append(draw_count_manager.get_user_count(user_id) % 90)
draw_count_manager.set_count(user_id, 2, draw_count_manager.get_user_count(user_id, 1))
draw_count_manager.record_count(user_id, 0)
draw_count_manager.record_count(user_id, 1)
if char.name not in [x.operators for x in pool if x.star == 5][0]:
draw_count_manager.set_is_up(user_id, True)
else:
draw_count_manager.set_is_up(user_id, False)
star_num_list[code] += 1
char_list.append(char)
return char_list, five_dict, star_num_list
# if genshin_count.get(user_id) and _count <= 90:
# f_count = genshin_count[user_id]
# else:
# f_count = 0
# if genshin_pl_count.get(user_id) and _count <= 90:
# count = genshin_pl_count[user_id]
# else:
# count = 0
# for i in range(_count):
# count += 1
# f_count += 1
# # 十连保底
# if count == 10 and f_count != 90:
# if f_count >= 72:
# add += I72_ADD
# char, code = _get_genshin_card(2, pool_name, add=add)
# count = 0
# # 大保底
# elif f_count == 90:
# char, code = _get_genshin_card(3, pool_name)
# else:
# if f_count >= 72:
# add += I72_ADD
# char, code = _get_genshin_card(pool_name=pool_name, add=add)
# if code == 1:
# count = 0
# star_list[code] += 1
# if code == 0:
# if _count <= 90:
# genshin_five[user_id] = f_count
# add = 0.0
# f_count = 0
# five_list.append(char.name)
# five_index_list.append(i)
# try:
# five_dict[char.name] += 1
# except KeyError:
# five_dict[char.name] = 1
# char_list.append(char)
# if _count <= 90:
# genshin_count[user_id] = f_count
# genshin_pl_count[user_id] = count
# return char_list, five_list, five_index_list, five_dict, star_list
def reset_count(user_id: int):
draw_count_manager.reset(user_id)
# 获取up和概率
async def _genshin_init_up_char():
global _CURRENT_CHAR_POOL_TITLE, _CURRENT_ARMS_POOL_TITLE, UP_CHAR, UP_ARMS, POOL_IMG
(
_CURRENT_CHAR_POOL_TITLE,
_CURRENT_ARMS_POOL_TITLE,
POOL_IMG,
UP_CHAR,
UP_ARMS,
) = await init_up_char(announcement)
async def reload_genshin_pool():
await _genshin_init_up_char()
return Message(
f"当前UP池子:{_CURRENT_CHAR_POOL_TITLE} & {_CURRENT_ARMS_POOL_TITLE} {POOL_IMG}"
)
-159
View File
@@ -1,159 +0,0 @@
from nonebot.adapters.onebot.v11 import MessageSegment, Message
from .update_game_info import update_info
from .util import init_star_rst, generate_img, max_card, BaseData,\
set_list, get_star, format_card_information, init_up_char
import random
from .config import DRAW_DATA_PATH, draw_config
from dataclasses import dataclass
from .init_card_pool import init_game_pool
from .announcement import GuardianAnnouncement
try:
import ujson as json
except ModuleNotFoundError:
import json
announcement = GuardianAnnouncement()
ALL_CHAR = []
ALL_ARMS = []
_CURRENT_CHAR_POOL_TITLE = ''
_CURRENT_ARMS_POOL_TITLE = ''
UP_CHAR = []
UP_ARMS = []
POOL_IMG = ''
@dataclass
class GuardianChar(BaseData):
pass
@dataclass
class GuardianArms(BaseData):
pass
async def guardian_draw(count: int, pool_name):
if pool_name == 'arms':
cnlist = ['★★★★★', '★★★★', '★★★', '★★']
star_list = [0, 0, 0, 0]
else:
cnlist = ['★★★', '★★', '★']
star_list = [0, 0, 0]
title = ''
up_type = []
up_list = []
if pool_name == 'char' and _CURRENT_CHAR_POOL_TITLE:
up_type = UP_CHAR
title = _CURRENT_CHAR_POOL_TITLE
elif pool_name == 'arms' and _CURRENT_ARMS_POOL_TITLE:
up_type = UP_ARMS
title = _CURRENT_ARMS_POOL_TITLE
tmp = ''
if up_type:
for x in up_type:
for operator in x.operators:
up_list.append(operator)
if pool_name == 'char':
if x.star == 3:
tmp += f'三星UP:{" ".join(x.operators)} \n'
else:
if x.star == 5:
tmp += f'五星UP:{" ".join(x.operators)}'
obj_list, obj_dict, max_list, star_list, max_index_list = format_card_information(count, star_list,
_get_guardian_card, pool_name)
rst = init_star_rst(star_list, cnlist, max_list, max_index_list, up_list)
pool_info = f'当前up池:{title}\n{tmp}' if title else ''
if count > 90:
obj_list = set_list(obj_list)
return pool_info + '\n' + MessageSegment.image(
"base64://" + await generate_img(obj_list, 'guardian', star_list)) \
+ '\n' + rst[:-1] + '\n' + max_card(obj_dict)
async def update_guardian_info():
global ALL_CHAR, ALL_ARMS
url = 'https://wiki.biligame.com/gt/英雄筛选表'
data, code = await update_info(url, 'guardian')
if code == 200:
ALL_CHAR = init_game_pool('guardian', data, GuardianChar)
url = 'https://wiki.biligame.com/gt/武器'
tmp, code_1 = await update_info(url, 'guardian_arms')
url = 'https://wiki.biligame.com/gt/盾牌'
data, code_2 = await update_info(url, 'guardian_arms')
if code_1 == 200 and code_2 == 200:
data.update(tmp)
ALL_ARMS = init_game_pool('guardian_arms', data, GuardianArms)
await _guardian_init_up_char()
async def init_guardian_data():
global ALL_CHAR, ALL_ARMS
if draw_config.GUARDIAN_FLAG:
if not (DRAW_DATA_PATH / 'guardian.json').exists() or not (DRAW_DATA_PATH / 'guardian_arms.json').exists():
await update_guardian_info()
else:
with (DRAW_DATA_PATH / 'guardian.json').open('r', encoding='utf8') as f:
guardian_char_dict = json.load(f)
with (DRAW_DATA_PATH / 'guardian_arms.json').open('r', encoding='utf8') as f:
guardian_arms_dict = json.load(f)
ALL_CHAR = init_game_pool('guardian', guardian_char_dict, GuardianChar)
ALL_ARMS = init_game_pool('guardian_arms', guardian_arms_dict, GuardianArms)
await _guardian_init_up_char()
# 抽取卡池
def _get_guardian_card(pool_name: str = '', mode: int = 1):
global ALL_ARMS, ALL_CHAR, UP_ARMS, UP_CHAR, _CURRENT_ARMS_POOL_TITLE, _CURRENT_CHAR_POOL_TITLE
guardian_config = draw_config.guardian
if pool_name == 'char':
if mode == 1:
star = get_star([3, 2, 1], [guardian_config.GUARDIAN_THREE_CHAR_P, guardian_config.GUARDIAN_TWO_CHAR_P, guardian_config.GUARDIAN_ONE_CHAR_P])
else:
star = get_star([3, 2], [guardian_config.GUARDIAN_THREE_CHAR_P, guardian_config.GUARDIAN_TWO_CHAR_P])
up_lst = UP_CHAR
flag = _CURRENT_CHAR_POOL_TITLE
_max_star = 3
all_data = ALL_CHAR
else:
if mode == 1:
star = get_star([5, 4, 3, 2], [guardian_config.GUARDIAN_FIVE_ARMS_P, guardian_config.GUARDIAN_FOUR_ARMS_P,
guardian_config.GUARDIAN_THREE_ARMS_P, guardian_config.GUARDIAN_TWO_ARMS_P])
else:
star = get_star([5, 4], [guardian_config.GUARDIAN_FIVE_ARMS_P, guardian_config.GUARDIAN_FOUR_ARMS_P])
up_lst = UP_ARMS
flag = _CURRENT_ARMS_POOL_TITLE
_max_star = 5
all_data = ALL_ARMS
# 是否UP
if flag and star == _max_star and pool_name:
# 获取up角色列表
up_char_lst = [x.operators for x in up_lst if x.star == star][0]
# 成功获取up角色
if random.random() < 0.5:
up_char_name = random.choice(up_char_lst)
acquire_char = [x for x in all_data if x.name == up_char_name][0]
else:
# 无up
all_char_lst = [x for x in all_data if x.star == star and x.name not in up_char_lst and not x.limited]
acquire_char = random.choice(all_char_lst)
else:
chars = [x for x in all_data if x.star == star and not x.limited]
acquire_char = random.choice(chars)
return acquire_char, _max_star - star
# 获取up和概率
async def _guardian_init_up_char():
global _CURRENT_CHAR_POOL_TITLE, _CURRENT_ARMS_POOL_TITLE, UP_CHAR, UP_ARMS, POOL_IMG
_CURRENT_CHAR_POOL_TITLE, _CURRENT_ARMS_POOL_TITLE, POOL_IMG, UP_CHAR, UP_ARMS = await init_up_char(announcement)
async def reload_guardian_pool():
await _guardian_init_up_char()
return Message(f'当前UP池子:{_CURRENT_CHAR_POOL_TITLE} & {_CURRENT_ARMS_POOL_TITLE}')
+317
View File
@@ -0,0 +1,317 @@
import random
import dateparser
from lxml import etree
from typing import List, Optional
from urllib.parse import unquote
from pydantic import ValidationError
from nonebot.log import logger
from nonebot.adapters.onebot.v11 import Message
from utils.message_builder import image
from .base_handle import BaseHandle, BaseData, UpEvent as _UpEvent, UpChar as _UpChar
from ..config import draw_config
from ..util import remove_prohibited_str, cn2py
from utils.image_utils import BuildImage
import asyncio
try:
import ujson as json
except ModuleNotFoundError:
import json
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: Optional[UpEvent] = 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]
# print(up_ship)
acquire_char = None
if up_ship and up_pool_flag:
up_zoom = [(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],
[
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) -> Message:
# return await asyncio.get_event_loop().run_in_executor(None, self._draw, count)
async def draw(self, count: int, **kwargs) -> Message:
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.up_char else []
result = self.format_result(index2card, **{**kwargs, "up_list": up_list})
return image(b64=self.generate_img(cards).pic2bs4()) + result
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, font="msyh.ttf")
frame_path = self.img_path / f"{card.star}_star.png"
frame = BuildImage(w, h, background=frame_path)
img_path = self.img_path / f"{cn2py(card.name)}.png"
img = BuildImage(w, h, background=img_path)
# 加圆角
img.circle_corner(6)
bg.paste(img, (sep_w, sep_t), alpha=True)
bg.paste(frame, (sep_w, sep_t), alpha=True)
bg.circle_corner(6)
# 加名字
text = card.name[:6] + "..." if len(card.name) > 7 else card.name
text_w, text_h = bg.getsize(text)
bg.text(
(sep_w + (w - text_w) / 2, h + sep_t + (sep_b - text_h) / 2),
text,
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")
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='resp-tabs-container']/div[@class='resp-tab-content']"
)
for index, content in enumerate(contents):
char_list = content.xpath("./table/tbody/tr[2]/td/div/div/div/div")
for char in char_list:
try:
name = char.xpath("./a/@title")[0]
frame = char.xpath("./div/a/img/@alt")[0]
avatar = char.xpath("./a/img/@srcset")[0]
except IndexError:
continue
member_dict = {
"名称": remove_prohibited_str(name),
"头像": unquote(str(avatar).split(" ")[-2]),
"星级": self.parse_star(frame),
"类型": self.parse_type(index),
}
info[member_dict["名称"]] = member_dict
# 更新额外信息
for key in info.keys():
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
@staticmethod
def parse_type(index: int) -> str:
azur_types = [
"驱逐",
"轻巡",
"重巡",
"超巡",
"战巡",
"战列",
"航母",
"航站",
"轻航",
"重炮",
"维修",
"潜艇",
"运输",
]
try:
return azur_types[index]
except IndexError:
return azur_types[0]
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:
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更新出错 {type(e)}:{e}")
async def _reload_pool(self) -> Optional[Message]:
await self.update_up_char()
self.load_up_char()
if self.UP_EVENT:
return Message(f"重载成功!\n当前活动:{self.UP_EVENT.title}")
+298
View File
@@ -0,0 +1,298 @@
import math
import random
import aiohttp
import asyncio
import aiofiles
from PIL import Image
from datetime import datetime
from pydantic import BaseModel, Extra
from asyncio.exceptions import TimeoutError
from typing import Dict, List, Optional, TypeVar, Generic, Tuple
from nonebot.adapters.onebot.v11 import Message
from configs.path_config import IMAGE_PATH
from utils.message_builder import image
from nonebot.log import logger
import asyncio
try:
import ujson as json
except ModuleNotFoundError:
import json
from utils.image_utils import BuildImage
from ..config import DRAW_PATH, draw_config
from ..util import cn2py, circled_number
class BaseData(BaseModel, extra=Extra.ignore):
name: str # 名字
star: int # 星级
limited: bool # 限定
def __eq__(self, other: "BaseData"):
return self.name == other.name
def __hash__(self):
return hash(self.name)
@property
def star_str(self) -> str:
return "".join(["★" for _ in range(self.star)])
class UpChar(BaseData):
zoom: float # up提升倍率
class UpEvent(BaseModel):
title: str # up池标题
pool_img: str # up池封面
start_time: Optional[datetime] # 开始时间
end_time: Optional[datetime] # 结束时间
up_char: List[UpChar] # up对象
TC = TypeVar("TC", bound="BaseData")
class BaseHandle(Generic[TC]):
def __init__(self, game_name: str, game_name_cn: str, game_card_color: str = "#ffffff"):
self.game_name = game_name
self.game_name_cn = game_name_cn
self.max_star = 1 # 最大星级
self.data_path = DRAW_PATH
self.img_path = IMAGE_PATH / f"draw_card/{self.game_name}"
self.up_path = DRAW_PATH / "draw_card_up"
self.img_path.mkdir(parents=True, exist_ok=True)
self.up_path.mkdir(parents=True, exist_ok=True)
self.data_files: List[str] = [f"{self.game_name}.json"]
self.game_card_color: str = game_card_color
async def draw(self, count: int, **kwargs) -> Message:
return await asyncio.get_event_loop().run_in_executor(None, self._draw, count)
def _draw(self, count: int, **kwargs) -> Message:
index2card = self.get_cards(count, **kwargs)
cards = [card[0] for card in index2card]
result = self.format_result(index2card)
return image(b64=self.generate_img(cards).pic2bs4()) + result
# 抽取卡池
def get_card(self, **kwargs) -> TC:
raise NotImplementedError
def get_cards(self, count: int, **kwargs) -> List[Tuple[TC, int]]:
return [(self.get_card(**kwargs), i) for i in range(count)]
# 获取星级
@staticmethod
def get_star(star_list: List[int], probability_list: List[float]) -> int:
return random.choices(star_list, weights=probability_list, k=1)[0]
def format_result(self, index2card: List[Tuple[TC, int]], **kwargs) -> str:
card_list = [card[0] for card in index2card]
results = [
self.format_star_result(card_list, **kwargs),
self.format_max_star(index2card, **kwargs),
self.format_max_card(card_list, **kwargs),
]
results = [rst for rst in results if rst]
return "\n".join(results)
def format_star_result(self, card_list: List[TC], **kwargs) -> str:
star_dict: Dict[str, int] = {} # 记录星级及其次数
card_list_sorted = sorted(card_list, key=lambda c: c.star, reverse=True)
for card in card_list_sorted:
try:
star_dict[card.star_str] += 1
except KeyError:
star_dict[card.star_str] = 1
rst = ""
for star_str, count in star_dict.items():
rst += f"[{star_str}×{count}] "
return rst.strip()
def format_max_star(
self, card_list: List[Tuple[TC, int]], up_list: List[str] = [], **kwargs
) -> str:
up_list = up_list or kwargs.get("up_list", [])
rst = ""
for card, index in card_list:
if card.star == self.max_star:
if card.name in up_list:
rst += f"第 {index} 抽获取UP {card.name}\n"
else:
rst += f"第 {index} 抽获取 {card.name}\n"
return rst.strip()
def format_max_card(self, card_list: List[TC], **kwargs) -> str:
card_dict: Dict[TC, int] = {} # 记录卡牌抽取次数
for card in card_list:
try:
card_dict[card] += 1
except KeyError:
card_dict[card] = 1
max_count = max(card_dict.values())
max_card = list(card_dict.keys())[list(card_dict.values()).index(max_count)]
if max_count <= 1:
return ""
return f"抽取到最多的是{max_card.name},共抽取了{max_count}次"
def generate_img(
self,
cards: List[TC],
num_per_line: int = 5,
max_per_line: Tuple[int, int] = (40, 10),
) -> BuildImage:
"""
生成统计图片
:param cards: 卡牌列表
:param num_per_line: 单行角色显示数量
:param max_per_line: 当card_list超过一定数值时,更改单行数量
"""
if len(cards) > max_per_line[0]:
num_per_line = max_per_line[1]
if len(cards) > 90:
card_dict: Dict[TC, int] = {} # 记录卡牌抽取次数
for card in cards:
try:
card_dict[card] += 1
except KeyError:
card_dict[card] = 1
card_list = list(card_dict)
num_list = list(card_dict.values())
else:
card_list = cards
num_list = [1] * len(cards)
card_imgs: List[BuildImage] = []
for card, num in zip(card_list, num_list):
card_img = self.generate_card_img(card)
# 数量 > 1 时加数字上标
if num > 1:
label = circled_number(num)
label_w = int(min(card_img.w, card_img.h) / 7)
label = label.resize(
(
int(label_w * label.width / label.height),
label_w,
),
Image.ANTIALIAS,
)
card_img.paste(label, alpha=True)
card_imgs.append(card_img)
img_w = card_imgs[0].w
img_h = card_imgs[0].h
if len(card_imgs) < num_per_line:
w = img_w * len(card_imgs)
else:
w = img_w * num_per_line
h = img_h * math.ceil(len(card_imgs) / num_per_line)
img = BuildImage(w, h, img_w, img_h, color=self.game_card_color)
for card_img in card_imgs:
img.paste(card_img)
return img
def generate_card_img(self, card: TC) -> BuildImage:
img = str(self.img_path / f"{cn2py(card.name)}.png")
return BuildImage(100, 100, background=img)
def load_data(self, filename: str = "") -> dict:
if not filename:
filename = f"{self.game_name}.json"
filepath = self.data_path / filename
if not filepath.exists():
return {}
with filepath.open("r", encoding="utf8") as f:
return json.load(f)
def dump_data(self, data: dict, filename: str = ""):
if not filename:
filename = f"{self.game_name}.json"
filepath = self.data_path / filename
with filepath.open("w", encoding="utf8") as f:
json.dump(data, f, ensure_ascii=False, indent=4)
def data_exists(self) -> bool:
for file in self.data_files:
if not (self.data_path / file).exists():
return False
return True
def _init_data(self):
raise NotImplementedError
def init_data(self):
try:
self._init_data()
except Exception as e:
logger.warning(f"{self.game_name_cn} 导入角色数据错误:{type(e)}:{e}")
async def _update_info(self):
raise NotImplementedError
def client(self) -> aiohttp.ClientSession:
headers = {
"User-Agent": '"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; TencentTraveler 4.0)"'
}
return aiohttp.ClientSession(headers=headers)
async def update_info(self):
try:
async with asyncio.Semaphore(draw_config.SEMAPHORE):
async with self.client() as session:
self.session = session
await self._update_info()
except Exception as e:
logger.warning(f"{self.game_name_cn} 更新数据错误:{type(e)}:{e}")
self.init_data()
async def get_url(self, url: str) -> str:
result = ""
retry = 5
for i in range(retry):
try:
async with self.session.get(url, timeout=10) as response:
result = await response.text()
break
except TimeoutError:
logger.warning(f"访问 {url} 超时, 重试 {i + 1}/{retry}")
await asyncio.sleep(1)
return result
async def download_img(self, url: str, name: str) -> bool:
img_path = self.img_path / f"{cn2py(name)}.png"
if img_path.exists():
return True
try:
async with self.session.get(url, timeout=10) as response:
async with aiofiles.open(str(img_path), "wb") as f:
await f.write(await response.read())
return True
except TimeoutError:
logger.warning(f"下载 {self.game_name_cn} 图片超时,名称:{name},url:{url}")
return False
except:
logger.warning(f"下载 {self.game_name_cn} 链接错误,名称:{name},url:{url}")
return False
async def _reload_pool(self) -> Optional[Message]:
return None
async def reload_pool(self) -> Optional[Message]:
try:
async with self.client() as session:
self.session = session
return await self._reload_pool()
except Exception as e:
logger.warning(f"{self.game_name_cn} 重载UP池错误:{type(e)}:{e}")
def reset_count(self, user_id: int) -> bool:
return False
+221
View File
@@ -0,0 +1,221 @@
import random
from lxml import etree
from typing import List, Tuple
from PIL import ImageDraw
from nonebot.log import logger
try:
import ujson as json
except ModuleNotFoundError:
import json
from .base_handle import BaseHandle, BaseData
from ..config import draw_config
from ..util import remove_prohibited_str, cn2py, load_font
from utils.image_utils import BuildImage
class FgoData(BaseData):
pass
class FgoChar(FgoData):
pass
class FgoCard(FgoData):
pass
class FgoHandle(BaseHandle[FgoData]):
def __init__(self):
super().__init__("fgo", "命运-冠位指定")
self.data_files.append("fgo_card.json")
self.max_star = 5
self.config = draw_config.fgo
self.ALL_CHAR: List[FgoChar] = []
self.ALL_CARD: List[FgoCard] = []
def get_card(self, mode: int = 1) -> FgoData:
if mode == 1:
star = self.get_star(
[8, 7, 6, 5, 4, 3],
[
self.config.FGO_SERVANT_FIVE_P,
self.config.FGO_SERVANT_FOUR_P,
self.config.FGO_SERVANT_THREE_P,
self.config.FGO_CARD_FIVE_P,
self.config.FGO_CARD_FOUR_P,
self.config.FGO_CARD_THREE_P,
],
)
elif mode == 2:
star = self.get_star(
[5, 4], [self.config.FGO_CARD_FIVE_P, self.config.FGO_CARD_FOUR_P]
)
else:
star = self.get_star(
[8, 7, 6],
[
self.config.FGO_SERVANT_FIVE_P,
self.config.FGO_SERVANT_FOUR_P,
self.config.FGO_SERVANT_THREE_P,
],
)
if star > 5:
star -= 3
chars = [x for x in self.ALL_CHAR if x.star == star and not x.limited]
else:
chars = [x for x in self.ALL_CARD if x.star == star and not x.limited]
return random.choice(chars)
def get_cards(self, count: int, **kwargs) -> List[Tuple[FgoData, int]]:
card_list = [] # 获取所有角色
servant_count = 0 # 保底计算
card_count = 0 # 保底计算
for i in range(count):
servant_count += 1
card_count += 1
if card_count == 9: # 四星卡片保底
mode = 2
elif servant_count == 10: # 三星从者保底
mode = 3
else: # 普通抽
mode = 1
card = self.get_card(mode)
if isinstance(card, FgoCard) and card.star > self.max_star - 2:
card_count = 0
if isinstance(card, FgoChar):
servant_count = 0
card_list.append((card, i + 1))
return card_list
def generate_card_img(self, card: FgoData) -> BuildImage:
sep_w = 5
sep_t = 5
sep_b = 20
w = 128
h = 140
bg = BuildImage(w + sep_w * 2, h + sep_t + sep_b)
img_path = str(self.img_path / f"{cn2py(card.name)}.png")
img = BuildImage(w, h, background=img_path)
bg.paste(img, (sep_w, sep_t), alpha=True)
# 加名字
text = card.name[:6] + "..." if len(card.name) > 7 else card.name
font = load_font(fontsize=16)
text_w, text_h = font.getsize(text)
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="gray",
)
return bg
def _init_data(self):
self.ALL_CHAR = [
FgoChar(
name=value["名称"],
star=int(value["星级"]),
limited=True
if not ("圣晶石召唤" in value["入手方式"] or "圣晶石召唤(Story卡池)" in value["入手方式"])
else False,
)
for value in self.load_data().values()
]
self.ALL_CARD = [
FgoCard(name=value["名称"], star=int(value["星级"]), limited=False)
for value in self.load_data("fgo_card.json").values()
]
async def _update_info(self):
# fgo.json
fgo_info = {}
for i in range(500):
url = f"http://fgo.vgtime.com/servant/ajax?card=&wd=&ids=&sort=12777&o=desc&pn={i}"
result = await self.get_url(url)
if not result:
logger.warning(f"更新 {self.game_name_cn} page {i} 出错")
continue
fgo_data = json.loads(result)
if int(fgo_data["nums"]) <= 0:
break
for x in fgo_data["data"]:
name = remove_prohibited_str(x["name"])
member_dict = {
"id": x["id"],
"card_id": x["charid"],
"头像": x["icon"],
"名称": remove_prohibited_str(x["name"]),
"职阶": x["classes"],
"星级": int(x["star"]),
"hp": x["lvmax4hp"],
"atk": x["lvmax4atk"],
"card_quick": x["cardquick"],
"card_arts": x["cardarts"],
"card_buster": x["cardbuster"],
"宝具": x["tprop"],
}
fgo_info[name] = member_dict
# 更新额外信息
for key in fgo_info.keys():
url = f'http://fgo.vgtime.com/servant/{fgo_info[key]["id"]}'
result = await self.get_url(url)
if not result:
fgo_info[key]["入手方式"] = ["圣晶石召唤"]
logger.warning(f"{self.game_name_cn} 获取额外信息错误 {key}")
continue
try:
dom = etree.HTML(result, etree.HTMLParser())
obtain = dom.xpath(
"//table[contains(string(.),'入手方式')]/tr[8]/td[3]/text()"
)[0]
obtain = str(obtain).strip()
if "限时活动免费获取 活动结束后无法获得" in obtain:
obtain = ["活动获取"]
elif "非限时UP无法获得" in obtain:
obtain = ["限时召唤"]
else:
if "&" in obtain:
obtain = obtain.split("&")
else:
obtain = obtain.split(" ")
obtain = [s.strip() for s in obtain if s.strip()]
fgo_info[key]["入手方式"] = obtain
except IndexError:
fgo_info[key]["入手方式"] = ["圣晶石召唤"]
logger.warning(f"{self.game_name_cn} 获取额外信息错误 {key}")
self.dump_data(fgo_info)
logger.info(f"{self.game_name_cn} 更新成功")
# fgo_card.json
fgo_card_info = {}
for i in range(500):
url = f"http://fgo.vgtime.com/equipment/ajax?wd=&ids=&sort=12958&o=desc&pn={i}"
result = await self.get_url(url)
if not result:
logger.warning(f"更新 {self.game_name_cn}卡牌 page {i} 出错")
continue
fgo_data = json.loads(result)
if int(fgo_data["nums"]) <= 0:
break
for x in fgo_data["data"]:
name = remove_prohibited_str(x["name"])
member_dict = {
"id": x["id"],
"card_id": x["equipid"],
"头像": x["icon"],
"名称": name,
"星级": int(x["star"]),
"hp": x["lvmax_hp"],
"atk": x["lvmax_atk"],
"skill_e": str(x["skill_e"]).split("<br />")[:-1],
}
fgo_card_info[name] = member_dict
self.dump_data(fgo_card_info, "fgo_card.json")
logger.info(f"{self.game_name_cn} 卡牌更新成功")
# 下载头像
for value in fgo_info.values():
await self.download_img(value["头像"], value["名称"])
for value in fgo_card_info.values():
await self.download_img(value["头像"], value["名称"])
+429
View File
@@ -0,0 +1,429 @@
import random
import dateparser
from lxml import etree
from PIL import Image, ImageDraw
from urllib.parse import unquote
from typing import List, Optional, Tuple
from pydantic import ValidationError
from datetime import datetime, timedelta
from nonebot.adapters.onebot.v11 import Message
from utils.message_builder import image
from nonebot.log import logger
import asyncio
try:
import ujson as json
except ModuleNotFoundError:
import json
from .base_handle import BaseHandle, BaseData, UpChar, UpEvent
from ..config import draw_config
from ..count_manager import GenshinCountManager
from ..util import remove_prohibited_str, cn2py, load_font
from utils.image_utils import BuildImage
class GenshinData(BaseData):
pass
class GenshinChar(GenshinData):
pass
class GenshinArms(GenshinData):
pass
class GenshinHandle(BaseHandle[GenshinData]):
def __init__(self):
super().__init__("genshin", "原神", "#ebebeb")
self.data_files.append("genshin_arms.json")
self.max_star = 5
self.config = draw_config.genshin
self.ALL_CHAR: List[GenshinData] = []
self.ALL_ARMS: List[GenshinData] = []
self.UP_CHAR: Optional[UpEvent] = None
self.UP_ARMS: Optional[UpEvent] = None
self.count_manager = GenshinCountManager((10, 90), ("4", "5"), 180)
# 抽取卡池
def get_card(
self, pool_name: str, mode: int = 1, add: float = 0.0, is_up: bool = False
):
"""
mode 1:普通抽 2:四星保底 3:五星保底
"""
if mode == 1:
star = self.get_star(
[5, 4, 3],
[
self.config.GENSHIN_FIVE_P + add,
self.config.GENSHIN_FOUR_P,
self.config.GENSHIN_THREE_P,
],
)
elif mode == 2:
star = self.get_star(
[5, 4],
[self.config.GENSHIN_G_FIVE_P + add, self.config.GENSHIN_G_FOUR_P],
)
else:
star = 5
if pool_name == "char":
up_event = self.UP_CHAR
all_list = self.ALL_CHAR + [
x for x in self.ALL_ARMS if x.star == star and x.star < 5
]
elif pool_name == "arms":
up_event = self.UP_ARMS
all_list = self.ALL_ARMS + [
x for x in self.ALL_CHAR if x.star == star and x.star < 5
]
else:
up_event = None
all_list = self.ALL_ARMS + self.ALL_CHAR
acquire_char = None
# 是否UP
if up_event and star > 3:
# 获取up角色列表
up_list = [x.name for x in up_event.up_char if x.star == star]
# 成功获取up角色
if random.random() < 0.5 or is_up:
up_name = random.choice(up_list)
try:
acquire_char = [x for x in all_list if x.name == up_name][0]
except IndexError:
pass
if not acquire_char:
chars = [x for x in all_list if x.star == star and not x.limited]
acquire_char = random.choice(chars)
return acquire_char
def get_cards(
self, count: int, user_id: int, pool_name: str
) -> List[Tuple[GenshinData, int]]:
card_list = [] # 获取角色列表
add = 0.0
count_manager = self.count_manager
count_manager.check_timeout(user_id) # 检查上次抽卡次数是否超时
count_manager.check_count(user_id, count) # 检查次数累计
pool = self.UP_CHAR if pool_name == "char" else self.UP_ARMS
for i in range(count):
count_manager.increase(user_id)
star = count_manager.check(user_id) # 是否有四星或五星保底
if (
count_manager.get_user_count(user_id)
- count_manager.get_user_five_index(user_id)
) % count_manager.get_max_guarantee() >= 72:
add += draw_config.genshin.I72_ADD
if star:
if star == 4:
card = self.get_card(pool_name, 2, add=add)
else:
card = self.get_card(
pool_name, 3, add, count_manager.is_up(user_id)
)
else:
card = self.get_card(pool_name, 1, add, count_manager.is_up(user_id))
# print(f"{count_manager.get_user_count(user_id)}:",
# count_manager.get_user_five_index(user_id), star, card.star, add)
# 四星角色
if card.star == 4:
count_manager.mark_four_index(user_id)
# 五星角色
elif card.star == self.max_star:
add = 0
count_manager.mark_five_index(user_id) # 记录五星保底
count_manager.mark_four_index(user_id) # 记录四星保底
if pool and card.name in [
x.name for x in pool.up_char if x.star == self.max_star
]:
count_manager.set_is_up(user_id, True)
else:
count_manager.set_is_up(user_id, False)
card_list.append((card, count_manager.get_user_count(user_id)))
count_manager.update_time(user_id)
return card_list
def generate_card_img(self, card: GenshinData) -> BuildImage:
sep_w = 10
sep_h = 5
frame_w = 112
frame_h = 132
img_w = 106
img_h = 106
bg = BuildImage(frame_w + sep_w * 2, frame_h + sep_h * 2, color="#EBEBEB")
frame_path = str(self.img_path / "avatar_frame.png")
frame = Image.open(frame_path)
# 加名字
text = card.name
font = load_font(fontsize=14)
text_w, text_h = font.getsize(text)
draw = ImageDraw.Draw(frame)
draw.text(
((frame_w - text_w) / 2, frame_h - 15 - text_h / 2),
text,
font=font,
fill="gray",
)
img_path = str(self.img_path / f"{cn2py(card.name)}.png")
img = BuildImage(img_w, img_h, background=img_path)
if isinstance(card, GenshinArms):
# 武器卡背景不是透明的,切去上方两个圆弧
r = 12
circle = Image.new("L", (r * 2, r * 2), 0)
alpha = Image.new("L", img.size, 255)
alpha.paste(circle, (-r - 3, -r - 3)) # 左上角
alpha.paste(circle, (img_h - r + 3, -r - 3)) # 右上角
img.markImg.putalpha(alpha)
star_path = str(self.img_path / f"{card.star}_star.png")
star = Image.open(star_path)
bg.paste(frame, (sep_w, sep_h), alpha=True)
bg.paste(img, (sep_w + 3, sep_h + 3), alpha=True)
bg.paste(star, (sep_w + int((frame_w - star.width) / 2), sep_h - 6), alpha=True)
return bg
def format_pool_info(self, pool_name: str) -> str:
info = ""
up_event = None
if pool_name == "char":
up_event = self.UP_CHAR
elif pool_name == "arms":
up_event = self.UP_ARMS
if up_event:
star5_list = [x.name for x in up_event.up_char if x.star == 5]
star4_list = [x.name for x in up_event.up_char if x.star == 4]
if star5_list:
info += f"五星UP:{' '.join(star5_list)}\n"
if star4_list:
info += f"四星UP:{' '.join(star4_list)}\n"
info = f"当前up池:{up_event.title}\n{info}"
return info.strip()
async def draw(self, count: int, user_id: int, pool_name: str = "", **kwargs) -> Message:
return await asyncio.get_event_loop().run_in_executor(None, self._draw, count, user_id, pool_name)
def _draw(self, count: int, user_id: int, pool_name: str = "", **kwargs) -> Message:
index2cards = self.get_cards(count, user_id, pool_name)
cards = [card[0] for card in index2cards]
up_event = None
if pool_name == "char":
up_event = self.UP_CHAR
elif pool_name == "arms":
up_event = self.UP_ARMS
up_list = [x.name for x in up_event.up_char] if up_event else []
result = self.format_star_result(cards)
result += (
"\n" + max_star_str
if (max_star_str := self.format_max_star(index2cards, up_list=up_list))
else ""
)
result += f"\n距离保底发还剩 {self.count_manager.get_user_guarantee_count(user_id)} 抽"
# result += "\n【五星:0.6%,四星:5.1%,第72抽开始五星概率每抽加0.585%】"
pool_info = self.format_pool_info(pool_name)
img = self.generate_img(cards)
bk = BuildImage(img.w, img.h + 50, font_size=20, color="#ebebeb")
bk.paste(img)
bk.text((0, img.h + 10), "【五星:0.6%,四星:5.1%,第72抽开始五星概率每抽加0.585%】")
return pool_info + image(b64=bk.pic2bs4()) + result
def _init_data(self):
self.ALL_CHAR = [
GenshinChar(
name=value["名称"],
star=int(value["星级"]),
limited=value["常驻/限定"] == "限定UP",
)
for key, value in self.load_data().items()
if "旅行者" not in key
]
self.ALL_ARMS = [
GenshinArms(
name=value["名称"],
star=int(value["星级"]),
limited="祈愿" not in value["获取途径"],
)
for value in self.load_data("genshin_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):
# genshin.json
char_info = {}
url = "https://wiki.biligame.com/ys/角色筛选"
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/@srcset")[0]
star = char.xpath("./td[3]/text()")[0]
except IndexError:
continue
member_dict = {
"头像": unquote(str(avatar).split(" ")[-2]),
"名称": remove_prohibited_str(name),
"星级": int(str(star).strip()[:1]),
}
char_info[member_dict["名称"]] = member_dict
# 更新额外信息
for key in char_info.keys():
result = await self.get_url(f"https://wiki.biligame.com/ys/{key}")
if not result:
char_info[key]["常驻/限定"] = "未知"
logger.warning(f"{self.game_name_cn} 获取额外信息错误 {key}")
continue
try:
dom = etree.HTML(result, etree.HTMLParser())
limit = dom.xpath(
"//table[contains(string(.),'常驻/限定')]/tbody/tr[6]/td/text()"
)[0]
char_info[key]["常驻/限定"] = str(limit).strip()
except IndexError:
char_info[key]["常驻/限定"] = "未知"
logger.warning(f"{self.game_name_cn} 获取额外信息错误 {key}")
self.dump_data(char_info)
logger.info(f"{self.game_name_cn} 更新成功")
# genshin_arms.json
arms_info = {}
url = "https://wiki.biligame.com/ys/武器图鉴"
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/@srcset")[0]
star = char.xpath("./td[4]/img/@alt")[0]
sources = str(char.xpath("./td[5]/text()")[0]).split(",")
except IndexError:
continue
member_dict = {
"头像": unquote(str(avatar).split(" ")[-2]),
"名称": remove_prohibited_str(name),
"星级": int(str(star).strip()[:1]),
"获取途径": [s.strip() for s in sources if s.strip()],
}
arms_info[member_dict["名称"]] = member_dict
self.dump_data(arms_info, "genshin_arms.json")
logger.info(f"{self.game_name_cn} 武器更新成功")
# 下载头像
for value in char_info.values():
await self.download_img(value["头像"], value["名称"])
for value in arms_info.values():
await self.download_img(value["头像"], value["名称"])
# 下载星星
idx = 1
YS_URL = "https://patchwiki.biligame.com/images/ys"
for url in [
"/1/13/7xzg7tgf8dsr2hjpmdbm5gn9wvzt2on.png",
"/b/bc/sd2ige6d7lvj7ugfumue3yjg8gyi0d1.png",
"/e/ec/l3mnhy56pyailhn3v7r873htf2nofau.png",
"/9/9c/sklp02ffk3aqszzvh8k1c3139s0awpd.png",
"/c/c7/qu6xcndgj6t14oxvv7yz2warcukqv1m.png",
]:
await self.download_img(YS_URL + url, f"{idx}_star")
idx += 1
# 下载头像框
await self.download_img(
YS_URL + "/2/2e/opbcst4xbtcq0i4lwerucmosawn29ti.png", f"avatar_frame"
)
await self.update_up_char()
async def update_up_char(self):
url = "https://wiki.biligame.com/ys/祈愿"
result = await self.get_url(url)
if not result:
logger.warning(f"{self.game_name_cn}获取祈愿页面出错")
return
dom = etree.HTML(result, etree.HTMLParser())
tables = dom.xpath(
"//div[@class='mw-parser-output']/div[@class='row']/div/table[@class='wikitable']/tbody"
)
if not tables or len(tables) < 2:
logger.warning(f"{self.game_name_cn}获取活动祈愿出错")
return
try:
for index, table in enumerate(tables):
title = table.xpath("./tr[1]/th/img/@title")[0]
title = str(title).split("」")[0] + "」" if "」" in title else title
pool_img = str(table.xpath("./tr[1]/th/img/@srcset")[0]).split(" ")[-2]
time = table.xpath("./tr[2]/td/text()")[0]
star5_list = table.xpath("./tr[3]/td/a/@title")
star4_list = table.xpath("./tr[4]/td/a/@title")
start, end = str(time).split("~")
start_time = dateparser.parse(start)
end_time = dateparser.parse(end)
if not start_time and end_time:
start_time = end_time - timedelta(days=20)
if start_time and end_time and start_time <= datetime.now() <= end_time:
up_event = UpEvent(
title=title,
pool_img=pool_img,
start_time=start_time,
end_time=end_time,
up_char=[
UpChar(name=name, star=5, limited=False, zoom=50)
for name in star5_list
]
+ [
UpChar(name=name, star=4, limited=False, zoom=50)
for name in star4_list
],
)
if index == 0:
self.UP_CHAR = up_event
elif index == 1:
self.UP_ARMS = up_event
if self.UP_CHAR and self.UP_ARMS:
self.dump_up_char()
logger.info(
f"成功获取{self.game_name_cn}当前up信息...当前up池: {self.UP_CHAR.title} & {self.UP_ARMS.title}"
)
except Exception as e:
logger.warning(f"{self.game_name_cn}UP更新出错 {type(e)}:{e}")
def reset_count(self, user_id: int) -> bool:
self.count_manager.reset(user_id)
return True
async def _reload_pool(self) -> Optional[Message]:
await self.update_up_char()
self.load_up_char()
if self.UP_CHAR and self.UP_ARMS:
return Message(
Message.template("重载成功!\n当前UP池子:{} & {}{:image}{:image}").format(
self.UP_CHAR.title,
self.UP_ARMS.title,
self.UP_CHAR.pool_img,
self.UP_ARMS.pool_img,
)
)
@@ -0,0 +1,400 @@
import re
import random
import dateparser
from lxml import etree
from PIL import ImageDraw
from datetime import datetime
from urllib.parse import unquote
from typing import List, Optional, Tuple
from pydantic import ValidationError
from nonebot.adapters.onebot.v11 import Message
from utils.message_builder import image
from nonebot.log import logger
import asyncio
try:
import ujson as json
except ModuleNotFoundError:
import json
from .base_handle import BaseHandle, BaseData, UpChar, UpEvent
from ..config import draw_config
from ..util import remove_prohibited_str, cn2py, load_font
from utils.image_utils import BuildImage
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: Optional[UpEvent] = None
self.UP_ARMS: Optional[UpEvent] = 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) -> Message:
return await asyncio.get_event_loop().run_in_executor(None, self._draw, count, pool_name)
def _draw(self, count: int, pool_name: str, **kwargs) -> Message:
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)
return pool_info + image(b64=self.generate_img(cards).pic2bs4()) + result
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)
block.paste(img, (0, 0), alpha=True)
if frame_path:
frame = BuildImage(img_w, img_h, background=frame_path)
block.paste(frame, (0, 0), alpha=True)
block.paste(
star,
(int((block_w + img_w - star_w) / 2), block_h - star_h - 30),
alpha=True,
)
# 加名字
text = card.name[:4] + "..." if len(card.name) > 5 else card.name
font = load_font(fontsize=14)
text_w, _ = font.getsize(text)
draw = ImageDraw.Draw(block.markImg)
draw.text(
((block_w + img_w - text_w) / 2, 55),
text,
font=font,
fill=font_color,
)
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]
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(
"//div[@class='resp-tabs-container']/div[1]/div/table[2]/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} 武器更新成功")
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) -> Optional[Message]:
await self.update_up_char()
self.load_up_char()
if self.UP_CHAR and self.UP_ARMS:
return Message(f"重载成功!\n当前UP池子:{self.UP_CHAR.title}")
+179
View File
@@ -0,0 +1,179 @@
import random
from lxml import etree
from typing import List, Tuple
from nonebot.log import logger
from PIL import Image, ImageDraw
from PIL.Image import Image as IMG
try:
import ujson as json
except ModuleNotFoundError:
import json
from .base_handle import BaseHandle, BaseData
from ..config import draw_config
from ..util import remove_prohibited_str, cn2py, load_font
from utils.image_utils import BuildImage
class OnmyojiChar(BaseData):
@property
def star_str(self) -> str:
return ["N", "R", "SR", "SSR", "SP"][self.star - 1]
class OnmyojiHandle(BaseHandle[OnmyojiChar]):
def __init__(self):
super().__init__("onmyoji", "阴阳师")
self.max_star = 5
self.config = draw_config.onmyoji
self.ALL_CHAR: List[OnmyojiChar] = []
def get_card(self, **kwargs) -> OnmyojiChar:
star = self.get_star(
[5, 4, 3, 2],
[
self.config.ONMYOJI_SP,
self.config.ONMYOJI_SSR,
self.config.ONMYOJI_SR,
self.config.ONMYOJI_R,
],
)
chars = [x for x in self.ALL_CHAR if x.star == star and not x.limited]
return random.choice(chars)
def format_max_star(self, card_list: List[Tuple[OnmyojiChar, int]]) -> str:
rst = ""
for card, index in card_list:
if card.star == self.max_star:
rst += f"第 {index} 抽获取SP {card.name}\n"
elif card.star == self.max_star - 1:
rst += f"第 {index} 抽获取SSR {card.name}\n"
return rst.strip()
@staticmethod
def star_label(star: int) -> IMG:
text, color1, color2 = [
("N", "#7E7E82", "#F5F6F7"),
("R", "#014FA8", "#37C6FD"),
("SR", "#6E0AA4", "#E94EFD"),
("SSR", "#E5511D", "#FAF905"),
("SP", "#FA1F2D", "#FFBBAF"),
][star - 1]
w = 200
h = 110
# 制作渐变色图片
base = Image.new("RGBA", (w, h), color1)
top = Image.new("RGBA", (w, h), color2)
mask = Image.new("L", (w, h))
mask_data = []
for y in range(h):
mask_data.extend([int(255 * (y / h))] * w)
mask.putdata(mask_data)
base.paste(top, (0, 0), mask)
# 透明图层
font = load_font("gorga.otf", 100)
alpha = Image.new("L", (w, h))
draw = ImageDraw.Draw(alpha)
draw.text((20, -30), text, fill="white", font=font)
base.putalpha(alpha)
# stroke
bg = Image.new("RGBA", (w, h))
draw = ImageDraw.Draw(bg)
draw.text(
(20, -30),
text,
font=font,
fill="gray",
stroke_width=3,
stroke_fill="gray",
)
bg.paste(base, (0, 0), base)
return bg
def generate_img(self, card_list: List[OnmyojiChar]) -> BuildImage:
return super().generate_img(card_list, num_per_line=10)
def generate_card_img(self, card: OnmyojiChar) -> BuildImage:
bg = BuildImage(73, 240, color="#F1EFE9")
img_path = str(self.img_path / f"{cn2py(card.name)}_mark_btn.png")
img = BuildImage(0, 0, background=img_path)
img = Image.open(img_path).convert("RGBA")
label = self.star_label(card.star).resize((60, 33), Image.ANTIALIAS)
bg.paste(img, (0, 0), alpha=True)
bg.paste(label, (0, 135), alpha=True)
font = load_font("msyh.ttf", 16)
draw = ImageDraw.Draw(bg.markImg)
text = "\n".join([t for t in card.name[:4]])
_, text_h = font.getsize_multiline(text, spacing=0)
draw.text(
(40, 150 + (90 - text_h) / 2), text, font=font, fill="gray", spacing=0
)
return bg
def _init_data(self):
self.ALL_CHAR = [
OnmyojiChar(
name=value["名称"],
star=["N", "R", "SR", "SSR", "SP"].index(value["星级"]) + 1,
limited=True
if key
in [
"奴良陆生",
"卖药郎",
"鬼灯",
"阿香",
"蜜桃&芥子",
"犬夜叉",
"杀生丸",
"桔梗",
"朽木露琪亚",
"黑崎一护",
"灶门祢豆子",
"灶门炭治郎",
]
else False,
)
for key, value in self.load_data().items()
]
async def _update_info(self):
info = {}
url = "https://yys.res.netease.com/pc/zt/20161108171335/js/app/all_shishen.json?v74="
result = await self.get_url(url)
if not result:
logger.warning(f"更新 {self.game_name_cn} 出错")
return
data = json.loads(result)
for x in data:
name = remove_prohibited_str(x["name"])
member_dict = {
"id": x["id"],
"名称": name,
"星级": x["level"],
}
info[name] = member_dict
# logger.info(f"{name} is update...")
# 更新头像
for key in info.keys():
url = f'https://yys.163.com/shishen/{info[key]["id"]}.html'
result = await self.get_url(url)
if not result:
info[key]["头像"] = ""
continue
try:
dom = etree.HTML(result, etree.HTMLParser())
avatar = dom.xpath("//div[@class='pic_wrap']/img/@src")[0]
avatar = "https:" + avatar
info[key]["头像"] = avatar
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["名称"])
# 下载书签形式的头像
url = f"https://yys.res.netease.com/pc/zt/20161108171335/data/mark_btn/{value['id']}.png"
await self.download_img(url, value["名称"] + "_mark_btn")
+147
View File
@@ -0,0 +1,147 @@
import random
from lxml import etree
from typing import List, Tuple
from PIL import ImageDraw
from urllib.parse import unquote
from nonebot.log import logger
from .base_handle import BaseHandle, BaseData
from ..config import draw_config
from ..util import remove_prohibited_str, cn2py, load_font
from utils.image_utils import BuildImage
class PcrChar(BaseData):
pass
class PcrHandle(BaseHandle[PcrChar]):
def __init__(self):
super().__init__("pcr", "公主连结")
self.max_star = 3
self.config = draw_config.pcr
self.ALL_CHAR: List[PcrChar] = []
def get_card(self, mode: int = 1) -> PcrChar:
if mode == 2:
star = self.get_star(
[3, 2], [self.config.PCR_G_THREE_P, self.config.PCR_G_TWO_P]
)
else:
star = self.get_star(
[3, 2, 1],
[self.config.PCR_THREE_P, self.config.PCR_TWO_P, self.config.PCR_ONE_P],
)
chars = [x for x in self.ALL_CHAR if x.star == star and not x.limited]
return random.choice(chars)
def get_cards(self, count: int, **kwargs) -> List[Tuple[PcrChar, int]]:
card_list = []
card_count = 0 # 保底计算
for i in range(count):
card_count += 1
# 十连保底
if card_count == 10:
card = self.get_card(2)
card_count = 0
else:
card = self.get_card(1)
if card.star > self.max_star - 2:
card_count = 0
card_list.append((card, i + 1))
return card_list
def generate_card_img(self, card: PcrChar) -> BuildImage:
sep_w = 5
sep_h = 5
star_h = 15
img_w = 90
img_h = 90
font_h = 20
bg = BuildImage(img_w + sep_w * 2, img_h + font_h + sep_h * 2, color="#EFF2F5")
star_path = str(self.img_path / "star.png")
star = BuildImage(star_h, 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)
bg.paste(img, (sep_w, sep_h), alpha=True)
for i in range(card.star):
bg.paste(star, (sep_w + img_w - star_h * (i + 1), sep_h), alpha=True)
# 加名字
text = card.name[:5] + "..." if len(card.name) > 6 else card.name
font = load_font(fontsize=14)
text_w, text_h = font.getsize(text)
draw = ImageDraw.Draw(bg.markImg)
draw.text(
(sep_w + (img_w - text_w) / 2, sep_h + img_h + (font_h - text_h) / 2),
text,
font=font,
fill="gray",
)
return bg
def _init_data(self):
self.ALL_CHAR = [
PcrChar(
name=value["名称"],
star=int(value["星级"]),
limited=True if "(" in key else False,
)
for key, value in self.load_data().items()
]
async def _update_info(self):
info = {}
if draw_config.PCR_TAI:
url = "https://wiki.biligame.com/pcr/角色图鉴"
result = await self.get_url(url)
if not result:
logger.warning(f"更新 {self.game_name_cn} 出错")
return
dom = etree.HTML(result, etree.HTMLParser())
char_list = dom.xpath(
"//div[@class='resp-tab-content']/div[@class='unit-icon']"
)
for char in char_list:
try:
name = char.xpath("./a/@title")[0]
avatar = char.xpath("./a/img/@srcset")[0]
star = len(char.xpath("./div[1]/img"))
except IndexError:
continue
member_dict = {
"头像": unquote(str(avatar).split(" ")[-2]),
"名称": remove_prohibited_str(name),
"星级": star,
}
info[member_dict["名称"]] = member_dict
else:
url = "https://wiki.biligame.com/pcr/角色筛选表"
result = await self.get_url(url)
if not result:
logger.warning(f"更新 {self.game_name_cn} 出错")
return
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/@srcset")[0]
star = char.xpath("./td[4]/text()")[0]
except IndexError:
continue
member_dict = {
"头像": unquote(str(avatar).split(" ")[-2]),
"名称": remove_prohibited_str(name),
"星级": int(str(star).strip()),
}
info[member_dict["名称"]] = member_dict
self.dump_data(info)
logger.info(f"{self.game_name_cn} 更新成功")
# 下载头像
for value in info.values():
await self.download_img(value["头像"], value["名称"])
# 下载星星
await self.download_img(
"https://patchwiki.biligame.com/images/pcr/0/02/s75ys2ecqhu2xbdw1wf1v9ccscnvi5g.png",
"star",
)
+426
View File
@@ -0,0 +1,426 @@
import re
import random
import dateparser
from lxml import etree
from PIL import ImageDraw
from bs4 import BeautifulSoup
from datetime import datetime
from urllib.parse import unquote
from typing import List, Optional, Tuple
from pydantic import ValidationError
from nonebot.adapters.onebot.v11 import Message
from utils.message_builder import image
from nonebot.log import logger
import asyncio
try:
import ujson as json
except ModuleNotFoundError:
import json
from .base_handle import BaseHandle, BaseData, UpChar, UpEvent
from ..config import draw_config
from ..util import remove_prohibited_str, cn2py, load_font
from utils.image_utils import BuildImage
class PrettyData(BaseData):
pass
class PrettyChar(PrettyData):
pass
class PrettyCard(PrettyData):
@property
def star_str(self) -> str:
return ["R", "SR", "SSR"][self.star - 1]
class PrettyHandle(BaseHandle[PrettyData]):
def __init__(self):
super().__init__("pretty", "赛马娘", "#eff2f5")
self.data_files.append("pretty_card.json")
self.max_star = 3
self.config = draw_config.pretty
self.ALL_CHAR: List[PrettyChar] = []
self.ALL_CARD: List[PrettyCard] = []
self.UP_CHAR: Optional[UpEvent] = None
self.UP_CARD: Optional[UpEvent] = None
def get_card(self, pool_name: str, mode: int = 1) -> PrettyData:
if mode == 1:
star = self.get_star(
[3, 2, 1],
[
self.config.PRETTY_THREE_P,
self.config.PRETTY_TWO_P,
self.config.PRETTY_ONE_P,
],
)
else:
star = self.get_star(
[3, 2], [self.config.PRETTY_THREE_P, self.config.PRETTY_TWO_P]
)
up_pool = None
if pool_name == "char":
up_pool = self.UP_CHAR
all_list = self.ALL_CHAR
else:
up_pool = self.UP_CARD
all_list = self.ALL_CARD
all_char = [x for x in all_list if x.star == star and not x.limited]
acquire_char = None
# 有UP池子
if up_pool and star in [x.star for x in up_pool.up_char]:
up_list = [x.name for x in up_pool.up_char if x.star == star]
# 抽到UP
if random.random() < 1 / len(all_char) * (0.7 / 0.1385):
up_name = random.choice(up_list)
try:
acquire_char = [x for x in all_list if x.name == up_name][0]
except IndexError:
pass
if not acquire_char:
acquire_char = random.choice(all_char)
return acquire_char
def get_cards(self, count: int, pool_name: str) -> List[Tuple[PrettyData, 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_CARD
if up_event:
star3_list = [x.name for x in up_event.up_char if x.star == 3]
star2_list = [x.name for x in up_event.up_char if x.star == 2]
star1_list = [x.name for x in up_event.up_char if x.star == 1]
if star3_list:
if pool_name == "char":
info += f'三星UP:{" ".join(star3_list)}\n'
else:
info += f'SSR UP:{" ".join(star3_list)}\n'
if star2_list:
if pool_name == "char":
info += f'二星UP:{" ".join(star2_list)}\n'
else:
info += f'SR UP:{" ".join(star2_list)}\n'
if star1_list:
if pool_name == "char":
info += f'一星UP:{" ".join(star1_list)}\n'
else:
info += f'R UP:{" ".join(star1_list)}\n'
info = f"当前up池:{up_event.title}\n{info}"
return info.strip()
async def draw(self, count: int, pool_name: str, **kwargs) -> Message:
return await asyncio.get_event_loop().run_in_executor(None, self._draw, count, pool_name)
def _draw(self, count: int, pool_name: str, **kwargs) -> Message:
pool_name = "char" if not pool_name else pool_name
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_CARD
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)
return pool_info + image(b64=self.generate_img(cards).pic2bs4()) + result
def generate_card_img(self, card: PrettyData) -> BuildImage:
if isinstance(card, PrettyChar):
star_h = 30
img_w = 200
img_h = 219
font_h = 50
bg = BuildImage(img_w, img_h + font_h, color="#EFF2F5")
star_path = str(self.img_path / "star.png")
star = BuildImage(star_h, 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)
star_w = star_h * card.star
for i in range(card.star):
bg.paste(star, (int((img_w - star_w) / 2) + star_h * i, 0), alpha=True)
bg.paste(img, (0, 0), alpha=True)
# 加名字
text = card.name[:5] + "..." if len(card.name) > 6 else card.name
font = load_font(fontsize=30)
text_w, _ = font.getsize(text)
draw = ImageDraw.Draw(bg.markImg)
draw.text(
((img_w - text_w) / 2, img_h),
text,
font=font,
fill="gray",
)
return bg
else:
sep_w = 10
img_w = 200
img_h = 267
font_h = 75
bg = BuildImage(img_w + sep_w * 2, img_h + font_h, color="#EFF2F5")
label_path = str(self.img_path / f"{card.star}_label.png")
label = BuildImage(40, 40, background=label_path)
img_path = str(self.img_path / f"{cn2py(card.name)}.png")
img = BuildImage(img_w, img_h, background=img_path)
bg.paste(img, (sep_w, 0), alpha=True)
bg.paste(label, (30, 3), alpha=True)
# 加名字
text = ""
texts = []
font = load_font(fontsize=25)
for t in card.name:
if font.getsize(text + t)[0] > 190:
texts.append(text)
text = ""
if len(texts) >= 2:
texts[-1] += "..."
break
else:
text += t
if text:
texts.append(text)
text = "\n".join(texts)
text_w, _ = font.getsize_multiline(text)
draw = ImageDraw.Draw(bg.markImg)
draw.text(
((img_w - text_w) / 2, img_h),
text,
font=font,
align="center",
fill="gray",
)
return bg
def _init_data(self):
self.ALL_CHAR = [
PrettyChar(
name=value["名称"],
star=int(value["初始星级"]),
limited=False,
)
for value in self.load_data().values()
]
self.ALL_CARD = [
PrettyCard(
name=value["中文名"],
star=["R", "SR", "SSR"].index(value["稀有度"]) + 1,
limited=True if "卡池" not in value["获取方式"] else False,
)
for value in self.load_data("pretty_card.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_CARD = UpEvent.parse_obj(data.get("card", {}))
except ValidationError:
logger.warning(f"{self.game_name}_up_char 解析出错")
def dump_up_char(self):
if self.UP_CHAR and self.UP_CARD:
data = {
"char": json.loads(self.UP_CHAR.json()),
"card": json.loads(self.UP_CARD.json()),
}
self.dump_data(data, f"draw_card_up/{self.game_name}_up_char.json")
async def _update_info(self):
# pretty.json
pretty_info = {}
url = "https://wiki.biligame.com/umamusume/赛马娘图鉴"
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/@srcset")[0]
star = len(char.xpath("./td[3]/img"))
except IndexError:
continue
member_dict = {
"头像": unquote(str(avatar).split(" ")[-2]),
"名称": remove_prohibited_str(name),
"初始星级": star,
}
pretty_info[member_dict["名称"]] = member_dict
self.dump_data(pretty_info)
logger.info(f"{self.game_name_cn} 更新成功")
# pretty_card.json
pretty_card_info = {}
url = "https://wiki.biligame.com/umamusume/支援卡图鉴"
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]/div/a/@title")[0]
name_cn = char.xpath("./td[3]/a/text()")[0]
avatar = char.xpath("./td[1]/div/a/img/@srcset")[0]
star = str(char.xpath("./td[5]/text()")[0]).strip()
sources = str(char.xpath("./td[7]/text()")[0]).strip()
except IndexError:
continue
member_dict = {
"头像": unquote(str(avatar).split(" ")[-2]),
"名称": remove_prohibited_str(name),
"中文名": remove_prohibited_str(name_cn),
"稀有度": star,
"获取方式": [sources] if sources else [],
}
pretty_card_info[member_dict["中文名"]] = member_dict
self.dump_data(pretty_card_info, "pretty_card.json")
logger.info(f"{self.game_name_cn} 卡牌更新成功")
# 下载头像
for value in pretty_info.values():
await self.download_img(value["头像"], value["名称"])
for value in pretty_card_info.values():
await self.download_img(value["头像"], value["中文名"])
# 下载星星
PRETTY_URL = "https://patchwiki.biligame.com/images/umamusume"
await self.download_img(
PRETTY_URL + "/1/13/e1hwjz4vmhtvk8wlyb7c0x3ld1s2ata.png", "star"
)
# 下载稀有度标志
idx = 1
for url in [
"/f/f7/afqs7h4snmvovsrlifq5ib8vlpu2wvk.png",
"/3/3b/d1jmpwrsk4irkes1gdvoos4ic6rmuht.png",
"/0/06/q23szwkbtd7pfkqrk3wcjlxxt9z595o.png",
]:
await self.download_img(PRETTY_URL + url, f"{idx}_label")
idx += 1
await self.update_up_char()
async def update_up_char(self):
announcement_url = "https://wiki.biligame.com/umamusume/公告"
result = await self.get_url(announcement_url)
if not result:
logger.warning(f"{self.game_name_cn}获取公告出错")
return
dom = etree.HTML(result, etree.HTMLParser())
announcements = dom.xpath("//div[@id='mw-content-text']/div/div/span/a")
title = ""
url = ""
for announcement in announcements:
try:
title = announcement.xpath("./@title")[0]
url = "https://wiki.biligame.com/" + announcement.xpath("./@href")[0]
if re.match(r".*?\d{8}$", title) or re.match(
r"^\d{1,2}月\d{1,2}日.*?", title
):
break
except IndexError:
continue
if not title:
logger.warning(f"{self.game_name_cn}未找到新UP公告")
return
result = await self.get_url(url)
if not result:
logger.warning(f"{self.game_name_cn}获取UP公告出错")
return
try:
start_time = None
end_time = None
char_img = ""
card_img = ""
up_chars = []
up_cards = []
soup = BeautifulSoup(result, "lxml")
heads = soup.find_all("span", {"class": "mw-headline"})
for head in heads:
if "时间" in head.text:
time = head.find_next("p").text.split("\n")[0]
if "~" in time:
start, end = time.split("~")
start_time = dateparser.parse(start)
end_time = dateparser.parse(end)
elif "赛马娘" in head.text:
char_img = head.find_next("a", {"class": "image"}).find("img")[
"src"
]
lines = str(head.find_next("p").text).split("\n")
chars = [
line
for line in lines
if "★" in line and "(" in line and ")" in line
]
for char in chars:
star = char.count("★")
name = re.split(r"[()]", char)[-2].strip()
up_chars.append(
UpChar(name=name, star=star, limited=False, zoom=70)
)
elif "支援卡" in head.text:
card_img = head.find_next("a", {"class": "image"}).find("img")[
"src"
]
lines = str(head.find_next("p").text).split("\n")
cards = [
line
for line in lines
if "R" in line and "(" in line and ")" in line
]
for card in cards:
star = 3 if "SSR" in card else 2 if "SR" in card else 1
name = re.split(r"[()]", card)[-2].strip()
up_cards.append(
UpChar(name=name, star=star, limited=False, zoom=70)
)
if start_time and end_time:
if start_time <= datetime.now() <= end_time:
self.UP_CHAR = UpEvent(
title=title,
pool_img=char_img,
start_time=start_time,
end_time=end_time,
up_char=up_chars,
)
self.UP_CARD = UpEvent(
title=title,
pool_img=card_img,
start_time=start_time,
end_time=end_time,
up_char=up_cards,
)
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) -> Optional[Message]:
await self.update_up_char()
self.load_up_char()
if self.UP_CHAR and self.UP_CARD:
return Message(
Message.template("重载成功!\n当前UP池子:{}{:image}{:image}").format(
self.UP_CHAR.title,
self.UP_CHAR.pool_img,
self.UP_CARD.pool_img,
)
)
+292
View File
@@ -0,0 +1,292 @@
import re
import random
import dateparser
from lxml import etree
from PIL import ImageDraw
from bs4 import BeautifulSoup
from datetime import datetime
from urllib.parse import unquote
from typing import List, Optional, Tuple
from pydantic import ValidationError
from nonebot.adapters.onebot.v11 import Message
from utils.message_builder import image
from nonebot.log import logger
import asyncio
try:
import ujson as json
except ModuleNotFoundError:
import json
from .base_handle import BaseHandle, BaseData, UpChar, UpEvent
from ..config import draw_config
from ..util import remove_prohibited_str, cn2py, load_font
from utils.image_utils import BuildImage
class Operator(BaseData):
recruit_only: bool # 公招限定
event_only: bool # 活动获得干员
# special_only: bool # 升变/异格干员
class PrtsHandle(BaseHandle[Operator]):
def __init__(self):
super().__init__("prts", "明日方舟", "#eff2f5")
self.max_star = 6
self.config = draw_config.prts
self.ALL_OPERATOR: List[Operator] = []
self.UP_EVENT: Optional[UpEvent] = None
def get_card(self, add: float) -> Operator:
star = self.get_star(
[6, 5, 4, 3],
[
self.config.PRTS_SIX_P + add,
self.config.PRTS_FIVE_P,
self.config.PRTS_FOUR_P,
self.config.PRTS_THREE_P,
],
)
all_operators = [
x
for x in self.ALL_OPERATOR
if x.star == star and not any([x.limited, x.event_only, x.recruit_only])
]
acquire_operator = None
if self.UP_EVENT:
up_operators = [x for x in self.UP_EVENT.up_char if x.star == star]
# UPs
try:
zooms = [x.zoom for x in up_operators]
zoom_sum = sum(zooms)
if random.random() < zoom_sum:
up_name = random.choices(up_operators, weights=zooms, k=1)[0].name
acquire_operator = [
x for x in self.ALL_OPERATOR if x.name == up_name
][0]
except IndexError:
pass
if not acquire_operator:
acquire_operator = random.choice(all_operators)
return acquire_operator
def get_cards(self, count: int) -> List[Tuple[Operator, int]]:
card_list = [] # 获取所有角色
add = 0.0
count_idx = 0
for i in range(count):
count_idx += 1
card = self.get_card(add)
if card.star == self.max_star:
add = 0.0
count_idx = 0
elif count_idx > 50:
add += 0.02
card_list.append((card, i + 1))
return card_list
def format_pool_info(self) -> str:
info = ""
if self.UP_EVENT:
star6_list = [x.name for x in self.UP_EVENT.up_char if x.star == 6]
star5_list = [x.name for x in self.UP_EVENT.up_char if x.star == 5]
star4_list = [x.name for x in self.UP_EVENT.up_char if x.star == 4]
if star6_list:
info += f"六星UP:{' '.join(star6_list)}\n"
if star5_list:
info += f"五星UP:{' '.join(star5_list)}\n"
if star4_list:
info += f"四星UP:{' '.join(star4_list)}\n"
info = f"当前up池: {self.UP_EVENT.title}\n{info}"
return info.strip()
async def draw(self, count: int, **kwargs) -> Message:
return await asyncio.get_event_loop().run_in_executor(None, self._draw, count)
def _draw(self, count: int, **kwargs) -> Message:
index2card = self.get_cards(count)
cards = [card[0] for card in self.get_cards(count)]
up_list = [x.name for x in self.UP_EVENT.up_char] if self.UP_EVENT else []
result = self.format_result(index2card, up_list=up_list)
pool_info = self.format_pool_info()
return pool_info + image(b64=self.generate_img(cards).pic2bs4()) + result
def generate_card_img(self, card: Operator) -> BuildImage:
sep_w = 5
sep_h = 5
star_h = 15
img_w = 120
img_h = 120
font_h = 20
bg = BuildImage(img_w + sep_w * 2, img_h + font_h + sep_h * 2, color="#EFF2F5")
star_path = str(self.img_path / "star.png")
star = BuildImage(star_h, 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)
bg.paste(img, (sep_w, sep_h), alpha=True)
for i in range(card.star):
bg.paste(star, (sep_w + img_w - 5 - star_h * (i + 1), sep_h), alpha=True)
# 加名字
text = card.name[:7] + "..." if len(card.name) > 8 else card.name
font = load_font(fontsize=16)
text_w, text_h = font.getsize(text)
draw = ImageDraw.Draw(bg.markImg)
draw.text(
(sep_w + (img_w - text_w) / 2, sep_h + img_h + (font_h - text_h) / 2),
text,
font=font,
fill="gray",
)
return bg
def _init_data(self):
self.ALL_OPERATOR = [
Operator(
name=value["名称"],
star=int(value["星级"]),
limited="干员寻访" not in value["获取途径"],
recruit_only=True
if "干员寻访" not in value["获取途径"] and "公开招募" in value["获取途径"]
else False,
event_only=True if "活动获取" in value["获取途径"] else False,
)
for key, value in self.load_data().items()
if "阿米娅" not in key
]
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")
async def _update_info(self):
info = {}
url = "https://wiki.biligame.com/arknights/干员数据表"
result = await self.get_url(url)
if not result:
logger.warning(f"更新 {self.game_name_cn} 出错")
return
dom = etree.HTML(result, etree.HTMLParser())
char_list = dom.xpath("//table[@id='CardSelectTr']/tbody/tr")
for char in char_list:
try:
avatar = char.xpath("./td[1]/div/div/div/a/img/@srcset")[0]
name = char.xpath("./td[2]/a/text()")[0]
star = char.xpath("./td[5]/text()")[0]
sources = str(char.xpath("./td[8]/text()")[0]).split("\n")
except IndexError:
continue
member_dict = {
"头像": unquote(str(avatar).split(" ")[-2]),
"名称": remove_prohibited_str(str(name).strip()),
"星级": int(str(star).strip()),
"获取途径": [s for s in sources if s],
}
info[member_dict["名称"]] = member_dict
self.dump_data(info)
logger.info(f"{self.game_name_cn} 更新成功")
# 下载头像
for value in info.values():
await self.download_img(value["头像"], value["名称"])
# 下载星星
await self.download_img(
"https://patchwiki.biligame.com/images/pcr/0/02/s75ys2ecqhu2xbdw1wf1v9ccscnvi5g.png",
"star",
)
await self.update_up_char()
async def update_up_char(self):
announcement_url = "https://ak.hypergryph.com/news.html"
result = await self.get_url(announcement_url)
if not result:
logger.warning(f"{self.game_name_cn}获取公告出错")
return
dom = etree.HTML(result, etree.HTMLParser())
activity_urls = dom.xpath(
"//ol[@class='articleList' and @data-category-key='ACTIVITY']/li/a/@href"
)
start_time = None
end_time = None
up_chars = []
pool_img = ""
title = ""
for activity_url in activity_urls:
activity_url = f"https://ak.hypergryph.com{activity_url}"
result = await self.get_url(activity_url)
if not result:
logger.warning(f"{self.game_name_cn}获取公告 {activity_url} 出错")
continue
soup = BeautifulSoup(result, "lxml")
contents = soup.find_all("p")
for index, content in enumerate(contents):
if re.search("(.*)(寻访|复刻).*?开启", content.text):
title = content.text
if "【" in title and "】" in title:
title = re.split(r"[【】]", title)[1]
lines = [str(contents[index + i + 1].text) for i in range(5)]
time = ""
chars: List[str] = []
for line in lines:
match = re.search(
r"(\d{1,2}月\d{1,2}日.*?-.*?\d{1,2}月\d{1,2}日.*?$)", line
)
if match:
time = match.group(1)
if "★" in line:
chars.append(line)
if not time:
continue
start, end = time.replace("月", "/").replace("日", "").split("-")[:2]
start_time = dateparser.parse(start)
end_time = dateparser.parse(end)
pool_img = content.find_previous("img")["src"]
for char in chars:
star = char.split("(")[0].count("★")
name = re.split(r"[:(]", char)[1]
names = name.split("/") if "/" in name else [name]
names = [name.replace("[限定]", "").strip() for name in names]
if "权值" in char:
match = re.search(r"(在.*?以.*?(\d+).*?倍权值.*?)", char)
else:
match = re.search(r"(占.*?的.*?(\d+).*?%)", char)
zoom = 1
if match:
zoom = float(match.group(1))
zoom = zoom / 100 if zoom > 10 else zoom
for name in names:
up_chars.append(
UpChar(name=name, star=star, limited=False, zoom=zoom)
)
break
if title and start_time and end_time:
if start_time <= datetime.now() <= end_time:
self.UP_EVENT = UpEvent(
title=title,
pool_img=pool_img,
start_time=start_time,
end_time=end_time,
up_char=up_chars,
)
self.dump_up_char()
logger.info(f"成功获取{self.game_name_cn}当前up信息...当前up池: {title}")
break
async def _reload_pool(self) -> Optional[Message]:
await self.update_up_char()
self.load_up_char()
if self.UP_EVENT:
return f"重载成功!\n当前UP池子:{self.UP_EVENT.title}" + image(
self.UP_EVENT.pool_img
)
-204
View File
@@ -1,204 +0,0 @@
from typing import Any
from .config import DRAW_DATA_PATH
from .util import is_number
from nonebot.log import logger
try:
import ujson as json
except ModuleNotFoundError:
import json
def init_game_pool(game: str, data: dict, operator: Any):
tmp_lst = []
if game == "prts":
for key in data.keys():
limited = False
recruit_only = False
event_only = False
if "限定寻访" in data[key]["获取途径"]:
limited = True
if "干员寻访" not in data[key]["获取途径"] and "公开招募" in data[key]["获取途径"]:
recruit_only = True
if "活动获取" in data[key]["获取途径"]:
event_only = True
if "干员寻访" not in data[key]["获取途径"] and "凭证交易所" == data[key]["获取途径"][0]:
limited = True
if "干员寻访" not in data[key]["获取途径"] and "信用累计奖励" == data[key]["获取途径"][0]:
limited = True
if key.find("阿米娅") != -1:
continue
try:
tmp_lst.append(
operator(
name=key,
star=int(data[key]["星级"]),
limited=limited,
recruit_only=recruit_only,
event_only=event_only,
)
)
except Exception as e:
logger.warning(f"明日方舟导入角色 {key} 数据错误:{type(e)}:{e}")
if game == "genshin":
for key in data.keys():
if key.find("旅行者") != -1:
continue
limited = False
if data[key]["常驻/限定"] == "限定UP":
limited = True
try:
tmp_lst.append(
operator(name=key, star=int(data[key]["稀有度"][:1]), limited=limited)
)
except Exception as e:
logger.warning(f"原神导入角色 {key} 数据错误:{type(e)}:{e}")
if game == "genshin_arms":
for key in data.keys():
if data[key]["获取途径"].find("祈愿") != -1:
limited = False
if data[key]["获取途径"].find("限定祈愿") != -1:
limited = True
try:
tmp_lst.append(
operator(
name=key, star=int(data[key]["稀有度"][:1]), limited=limited
)
)
except Exception as e:
logger.warning(f"原神导入武器 {key} 数据错误:{type(e)}:{e}")
if game == "pretty":
for key in data.keys():
try:
tmp_lst.append(
operator(name=key, star=data[key]["初始星级"], limited=False)
)
except Exception as e:
logger.warning(f"赛马娘导入角色 {key} 数据错误:{type(e)}:{e}")
if game == "pretty_card":
for key in data.keys():
limited = False
if "卡池" not in data[key]["获取方式"]:
limited = True
if not data[key]["获取方式"]:
limited = False
try:
tmp_lst.append(
operator(
name=data[key]["中文名"],
star=len(data[key]["稀有度"]),
limited=limited,
)
)
except Exception as e:
logger.warning(f"赛马娘导入卡片 {key} 数据错误:{type(e)}:{e}")
if game in ["guardian", "guardian_arms"]:
for key in data.keys():
tmp_lst.append(
operator(name=data[key]["名称"], star=int(data[key]["星级"]), limited=False)
)
if game == "pcr":
for key in data.keys():
limited = False
if key.find("(") != -1:
limited = True
try:
tmp_lst.append(
operator(
name=data[key]["名称"], star=int(data[key]["星级"]), limited=limited
)
)
except Exception as e:
logger.warning(f"公主连接导入角色 {key} 数据错误:{type(e)}:{e}")
if game == "azur":
for key in data.keys():
if is_number(data[key]["星级"]):
limited = False
if "可以建造" not in data[key]["获取途径"]:
limited = True
try:
tmp_lst.append(
operator(
name=data[key]["名称"],
star=int(data[key]["星级"]),
limited=limited,
type_=data[key]["类型"],
)
)
except Exception as e:
logger.warning(f"碧蓝航线导入角色 {key} 数据错误:{type(e)}:{e}")
if game in ["fgo", "fgo_card"]:
for key in data.keys():
limited = False
try:
if (
"圣晶石召唤" not in data[key]["入手方式"]
and "圣晶石召唤(Story卡池)" not in data[key]["入手方式"]
):
limited = True
except KeyError:
pass
try:
tmp_lst.append(
operator(
name=data[key]["名称"], star=int(data[key]["星级"]), limited=limited
)
)
except Exception as e:
logger.warning(f"FGO导入角色 {key} 数据错误:{type(e)}:{e}")
if game == "onmyoji":
for key in data.keys():
limited = False
if key in [
"奴良陆生",
"卖药郎",
"鬼灯",
"阿香",
"蜜桃&芥子",
"犬夜叉",
"杀生丸",
"桔梗",
"朽木露琪亚",
"黑崎一护",
"灶门祢豆子",
"灶门炭治郎",
]:
limited = True
try:
tmp_lst.append(
operator(
name=data[key]["名称"], star=data[key]["星级"], limited=limited
)
)
except Exception as e:
logger.warning(f"阴阳师导入角色 {key} 数据错误:{type(e)}:{e}")
# print(tmp_lst)
char_name_lst = [x.name for x in tmp_lst]
up_char_file = (
DRAW_DATA_PATH
/ f"draw_card"
/ "draw_card_up"
/ f"{game.split('_')[0]}_up_char.json"
)
if up_char_file.exists():
data = json.load(open(up_char_file, "r", encoding="utf8"))
if len(game.split("_")) == 1:
key = "char"
else:
key = list(data.keys())[1]
for x in data[key]["up_char"]:
for char in data[key]["up_char"][x]:
if char not in char_name_lst:
if game.find("prts") != -1:
tmp_lst.append(
operator(
name=char,
star=int(x),
recruit_only=False,
event_only=False,
limited=False,
)
)
else:
tmp_lst.append(operator(name=char, star=int(x), limited=False))
return tmp_lst
-81
View File
@@ -1,81 +0,0 @@
from nonebot.adapters.onebot.v11 import MessageSegment
import random
from .update_game_requests_info import update_requests_info
from .util import generate_img, init_star_rst, BaseData, set_list, get_star, max_card
from .config import DRAW_DATA_PATH, draw_config
from dataclasses import dataclass
from .init_card_pool import init_game_pool
try:
import ujson as json
except ModuleNotFoundError:
import json
ALL_CHAR = []
@dataclass
class OnmyojiChar(BaseData):
pass
async def onmyoji_draw(count: int):
# 0 1 2
cnlist = ['SP', 'SSR', 'SR', 'R']
obj_list, obj_dict, star_list, rst = format_card_information(count)
rst = init_star_rst(star_list, cnlist, [], []) + rst
if count > 90:
obj_list = set_list(obj_list)
return MessageSegment.image("base64://" + await generate_img(obj_list, 'onmyoji', star_list)) \
+ '\n' + rst[:-1] + '\n' + max_card(obj_dict)
async def update_onmyoji_info():
global ALL_CHAR
data, code = await update_requests_info('onmyoji')
if code == 200:
ALL_CHAR = init_game_pool('onmyoji', data, OnmyojiChar)
async def init_onmyoji_data():
global ALL_CHAR
if draw_config.ONMYOJI_FLAG:
with (DRAW_DATA_PATH / 'onmyoji.json').open('r', encoding='utf8') as f:
azur_dict = json.load(f)
ALL_CHAR = init_game_pool('onmyoji', azur_dict, OnmyojiChar)
onmyoji_star = {
5: 'SP',
4: 'SSR',
3: 'SR',
2: 'R',
}
# 抽取卡池
def _get_onmyoji_card():
global ALL_CHAR
onmyoji_config = draw_config.onmyoji
star = get_star([5, 4, 3, 2], [onmyoji_config.ONMYOJI_SP, onmyoji_config.ONMYOJI_SSR, onmyoji_config.ONMYOJI_SR, onmyoji_config.ONMYOJI_R])
chars = [x for x in ALL_CHAR if x.star == onmyoji_star[star] and not x.limited]
return random.choice(chars), 5 - star
def format_card_information(count: int):
star_list = [0, 0, 0, 0]
obj_list = [] # 获取所有角色
obj_dict = {} # 获取角色次数字典
rst = ''
for i in range(count):
obj, code = _get_onmyoji_card()
star_list[code] += 1
if code == 0:
rst += f'第 {i+1} 抽获取SP {obj.name}\n'
elif code == 1:
rst += f'第 {i+1} 抽获取SSR {obj.name}\n'
try:
obj_dict[obj.name] += 1
except KeyError:
obj_dict[obj.name] = 1
obj_list.append(obj)
return obj_list, obj_dict, star_list, rst
-93
View File
@@ -1,93 +0,0 @@
from nonebot.adapters.onebot.v11 import MessageSegment
import random
from .update_game_info import update_info
from .update_game_simple_info import update_simple_info
from .util import generate_img, init_star_rst, BaseData, set_list, get_star, max_card
from .config import DRAW_DATA_PATH, draw_config
from dataclasses import dataclass
from .init_card_pool import init_game_pool
try:
import ujson as json
except ModuleNotFoundError:
import json
ALL_CHAR = []
@dataclass
class PcrChar(BaseData):
pass
async def pcr_draw(count: int):
# 0 1 2
cnlist = ['★★★', '★★', '★']
char_list, three_list, three_index_list, char_dict, star_list = _format_card_information(count)
rst = init_star_rst(star_list, cnlist, three_list, three_index_list)
if count > 90:
char_list = set_list(char_list)
return MessageSegment.image("base64://" + await generate_img(char_list, 'pcr', star_list)) \
+ '\n' + rst[:-1] + '\n' + max_card(char_dict)
async def update_pcr_info():
global ALL_CHAR
if draw_config.PCR_TAI:
url = 'https://wiki.biligame.com/pcr/角色图鉴'
data, code = await update_simple_info(url, 'pcr')
else:
url = 'https://wiki.biligame.com/pcr/角色筛选表'
data, code = await update_info(url, 'pcr')
if code == 200:
ALL_CHAR = init_game_pool('pcr', data, PcrChar)
async def init_pcr_data():
global ALL_CHAR
if draw_config.PCR_FLAG:
with (DRAW_DATA_PATH / 'pcr.json').open('r', encoding='utf8') as f:
pcr_dict = json.load(f)
ALL_CHAR = init_game_pool('pcr', pcr_dict, PcrChar)
# 抽取卡池
def _get_pcr_card(mode: int = 1):
global ALL_CHAR
pcr_config = draw_config.pcr
if mode == 2:
star = get_star([3, 2], [pcr_config.PCR_G_THREE_P, pcr_config.PCR_G_TWO_P])
else:
star = get_star([3, 2, 1], [pcr_config.PCR_THREE_P, pcr_config.PCR_TWO_P, pcr_config.PCR_ONE_P])
chars = [x for x in ALL_CHAR if x.star == star and not x.limited]
return random.choice(chars), 3 - star
def _format_card_information(_count: int):
char_list = []
star_list = [0, 0, 0]
three_index_list = []
three_list = []
char_dict = {}
# 保底计算
count = 0
for i in range(_count):
count += 1
# 十连保底
if count == 10:
char, code = _get_pcr_card(2)
count = 0
else:
char, code = _get_pcr_card()
if code < 2:
count = 0
star_list[code] += 1
if code == 0:
three_list.append(char.name)
three_index_list.append(i)
try:
char_dict[char.name] += 1
except KeyError:
char_dict[char.name] = 1
char_list.append(char)
return char_list, three_list, three_index_list, char_dict, star_list
-148
View File
@@ -1,148 +0,0 @@
from nonebot.adapters.onebot.v11 import MessageSegment
from .announcement import PrettyAnnouncement
from .update_game_info import update_info
from .util import init_star_rst, generate_img, max_card, BaseData, \
set_list, get_star, format_card_information, init_up_char
import random
from .config import DRAW_DATA_PATH, draw_config
from dataclasses import dataclass
from .init_card_pool import init_game_pool
try:
import ujson as json
except ModuleNotFoundError:
import json
announcement = PrettyAnnouncement()
ALL_CHAR = []
ALL_CARD = []
_CURRENT_CHAR_POOL_TITLE = ""
_CURRENT_CARD_POOL_TITLE = ""
UP_CHAR = []
UP_CARD = []
POOL_IMG = []
@dataclass
class PrettyChar(BaseData):
pass
async def pretty_draw(count: int, pool_name):
if pool_name == 'card':
cnlist = ['SSR', 'SR', 'R']
else:
cnlist = ['★★★', '★★', '★']
star_list = [0, 0, 0]
obj_list, obj_dict, three_list, star_list, three_olist = format_card_information(count, star_list,
_get_pretty_card, pool_name)
up_type = []
up_list = []
title = ''
if pool_name == 'char' and _CURRENT_CHAR_POOL_TITLE:
up_type = UP_CHAR
title = _CURRENT_CHAR_POOL_TITLE
elif pool_name == 'card' and _CURRENT_CARD_POOL_TITLE:
up_type = UP_CARD
title = _CURRENT_CARD_POOL_TITLE
tmp = ''
if up_type:
for x in up_type:
for operator in x.operators:
up_list.append(operator.split(']')[1] if pool_name == 'char' else operator)
if x.star == 3:
if pool_name == 'char':
tmp += f'三星UP:{" ".join(x.operators)} \n'
else:
tmp += f'SSR UP:{" ".join(x.operators)} \n'
elif x.star == 2:
if pool_name == 'char':
tmp += f'二星UP:{" ".join(x.operators)} \n'
else:
tmp += f'SR UP:{" ".join(x.operators)} \n'
elif x.star == 1:
if pool_name == 'char':
tmp += f'一星UP:{" ".join(x.operators)} '
else:
tmp += f'R UP:{" ".join(x.operators)} '
tmp = tmp[:-1] if tmp and tmp[-1] == '\n' else tmp
pool_info = f'当前up池:{title}\n{tmp}' if title else ''
rst = init_star_rst(star_list, cnlist, three_list, three_olist, up_list)
if count > 90:
obj_list = set_list(obj_list)
return pool_info + MessageSegment.image(
"base64://" + await generate_img(obj_list, 'pretty', star_list)) \
+ '\n' + rst[:-1] + '\n' + max_card(obj_dict)
async def update_pretty_info():
global ALL_CHAR, ALL_CARD
url = 'https://wiki.biligame.com/umamusume/赛马娘图鉴'
data, code = await update_info(url, 'pretty')
if code == 200:
ALL_CHAR = init_game_pool('pretty', data, PrettyChar)
url = 'https://wiki.biligame.com/umamusume/支援卡图鉴'
data, code = await update_info(url, 'pretty_card')
if code == 200:
ALL_CARD = init_game_pool('pretty_card', data, PrettyChar)
await _pretty_init_up_char()
async def init_pretty_data():
global ALL_CHAR, ALL_CARD
if draw_config.PRETTY_FLAG:
with (DRAW_DATA_PATH / 'pretty.json').open('r', encoding='utf8') as f:
pretty_char_dict = json.load(f)
with (DRAW_DATA_PATH / 'pretty_card.json').open('r', encoding='utf8') as f:
pretty_card_dict = json.load(f)
ALL_CHAR = init_game_pool('pretty', pretty_char_dict, PrettyChar)
ALL_CARD = init_game_pool('pretty_card', pretty_card_dict, PrettyChar)
await _pretty_init_up_char()
# 抽取卡池
def _get_pretty_card(pool_name: str, mode: int = 1):
global ALL_CHAR, ALL_CARD, _CURRENT_CHAR_POOL_TITLE, _CURRENT_CARD_POOL_TITLE
pretty_config = draw_config.pretty
if mode == 1:
star = get_star([3, 2, 1], [pretty_config.PRETTY_THREE_P, pretty_config.PRETTY_TWO_P, pretty_config.PRETTY_ONE_P])
else:
star = get_star([3, 2], [pretty_config.PRETTY_THREE_P, pretty_config.PRETTY_TWO_P])
if pool_name == 'card':
title = _CURRENT_CARD_POOL_TITLE
up_data = UP_CARD
data = ALL_CARD
else:
title = _CURRENT_CHAR_POOL_TITLE
up_data = UP_CHAR
data = ALL_CHAR
# 有UP池子
if title and star in [x.star for x in up_data]:
all_char_lst = [x for x in data if x.star == star and not x.limited]
# 抽到UP
if random.random() < 1 / len(all_char_lst) * (0.7 / 0.1385):
all_up_star = [x.operators for x in up_data if x.star == star][0]
acquire_operator = random.choice(all_up_star)
if pool_name == 'char':
acquire_operator = acquire_operator.split(']')[1]
acquire_operator = [x for x in data if x.name == acquire_operator][0]
else:
acquire_operator = random.choice([x for x in data if x.star == star and not x.limited])
else:
acquire_operator = random.choice([x for x in data if x.star == star and not x.limited])
return acquire_operator, 3 - star
# 获取up和概率
async def _pretty_init_up_char():
global _CURRENT_CHAR_POOL_TITLE, _CURRENT_CARD_POOL_TITLE, UP_CHAR, UP_CARD, POOL_IMG
_CURRENT_CHAR_POOL_TITLE, _CURRENT_CARD_POOL_TITLE, POOL_IMG, UP_CHAR, UP_CARD = await init_up_char(announcement)
async def reload_pretty_pool():
await _pretty_init_up_char()
return f'当前UP池子:{_CURRENT_CHAR_POOL_TITLE} & {_CURRENT_CARD_POOL_TITLE} {POOL_IMG}'
-171
View File
@@ -1,171 +0,0 @@
from nonebot.adapters.onebot.v11 import MessageSegment, Message
import random
from .config import DRAW_DATA_PATH, draw_config
from .update_game_info import update_info
from .util import generate_img, init_star_rst, max_card, BaseData, UpEvent, set_list, get_star
from .init_card_pool import init_game_pool
from .announcement import PrtsAnnouncement
from dataclasses import dataclass
from nonebot.log import logger
try:
import ujson as json
except ModuleNotFoundError:
import json
announcement = PrtsAnnouncement()
prts_dict = {}
UP_OPERATOR = []
ALL_OPERATOR = []
_CURRENT_POOL_TITLE = ''
POOL_IMG = ''
@dataclass
class Operator(BaseData):
recruit_only: bool # 公招限定
event_only: bool # 活动获得干员
# special_only: bool # 升变/异格干员
async def prts_draw(count: int = 300):
cnlist = ['★★★★★★', '★★★★★', '★★★★', '★★★']
star_list = [0, 0, 0, 0]
operator_list, operator_dict, six_list, star_list, six_index_list = format_card_information(count, star_list)
up_list = []
tmp = ''
if _CURRENT_POOL_TITLE:
for x in UP_OPERATOR:
for operator in x.operators:
up_list.append(operator)
if x.star == 6:
tmp += f'六星UP:{" ".join(x.operators)} \n'
elif x.star == 5:
tmp += f'五星UP:{" ".join(x.operators)} \n'
elif x.star == 4:
tmp += f'四星UP:{" ".join(x.operators)}'
rst = init_star_rst(star_list, cnlist, six_list, six_index_list, up_list)
if count > 90:
operator_list = set_list(operator_list)
pool_info = f"当前up池: {_CURRENT_POOL_TITLE}\n{tmp}" if _CURRENT_POOL_TITLE else ""
return pool_info + MessageSegment.image(
"base64://" + await generate_img(operator_list, 'prts', star_list)) \
+ '\n' + rst[:-1] + '\n' + max_card(operator_dict)
async def update_prts_info():
global prts_dict, ALL_OPERATOR
url = 'https://wiki.biligame.com/arknights/干员数据表'
data, code = await update_info(url, 'prts', ['头像', '名称', '阵营', '星级', '性别', '是否感染', '获取途径', '初始生命', '初始防御',
'初始法抗', '再部署', '部署费用', '阻挡数', '攻击速度', '标签'])
if code == 200:
prts_dict = data
ALL_OPERATOR = init_game_pool('prts', prts_dict, Operator)
await _init_up_char()
async def init_prts_data():
global prts_dict, ALL_OPERATOR
if draw_config.PRTS_FLAG:
with (DRAW_DATA_PATH / 'prts.json').open('r', encoding='utf8') as f:
prts_dict = json.load(f)
ALL_OPERATOR = init_game_pool('prts', prts_dict, Operator)
await _init_up_char()
# 抽取干员
def _get_operator_card(add: float):
prts_config = draw_config.prts
star = get_star([6, 5, 4, 3], [prts_config.PRTS_SIX_P + add, prts_config.PRTS_FIVE_P, prts_config.PRTS_FOUR_P, prts_config.PRTS_THREE_P])
if _CURRENT_POOL_TITLE:
zooms = [x.zoom for x in UP_OPERATOR if x.star == star]
zoom = 0
weight = 0
# 分配概率和权重
for z in zooms:
if z < 1:
zoom = z
else:
weight = z
up_operator_name = ""
# UPs
try:
if 0 < zoom:
up_operators = [x.operators for x in UP_OPERATOR if x.star == star and x.zoom < 1][0]
up_operator_name = random.choice(up_operators)
acquire_operator = [x for x in ALL_OPERATOR if x.name == up_operator_name][0]
else:
all_star_operators = [x for x in ALL_OPERATOR if x.star == star
and not any([x.limited, x.event_only, x.recruit_only])]
weight_up_operators = [x.operators for x in UP_OPERATOR if x.star == star and x.zoom > 1]
# 权重
if weight_up_operators and random.random() < 1.0 / float(len(all_star_operators)) * weight:
up_operator_name = random.choice(weight_up_operators[0])
acquire_operator = [x for x in ALL_OPERATOR if x.name == up_operator_name][0]
else:
acquire_operator = random.choice(all_star_operators)
except IndexError:
acquire_operator = Operator(up_operator_name, star, True, False, False)
else:
acquire_operator = random.choice([x for x in ALL_OPERATOR if x.star == star
and not any([x.limited, x.event_only, x.recruit_only])])
return acquire_operator, 6 - star
# 整理数据
def format_card_information(count: int, star_list: list):
max_star_lst = [] # 获取的最高星级角色列表
max_index_lst = [] # 获取最高星级角色的次数
obj_list = [] # 获取所有角色
obj_dict = {} # 获取角色次数字典
add = 0.0
count_idx = 0
for i in range(count):
count_idx += 1
obj, code = _get_operator_card(add)
star_list[code] += 1
if code == 0:
max_star_lst.append(obj.name)
max_index_lst.append(i)
add = 0.0
count_idx = 0
elif count_idx > 50:
add += 0.02
try:
obj_dict[obj.name] += 1
except KeyError:
obj_dict[obj.name] = 1
obj_list.append(obj)
return obj_list, obj_dict, max_star_lst, star_list, max_index_lst
# 获取up干员和概率
async def _init_up_char():
global _CURRENT_POOL_TITLE, POOL_IMG, UP_OPERATOR
UP_OPERATOR = []
up_char_dict = await announcement.update_up_char()
_CURRENT_POOL_TITLE = up_char_dict['char']['title']
if _CURRENT_POOL_TITLE:
POOL_IMG = MessageSegment.image(up_char_dict['char']['pool_img'])
up_char_dict = up_char_dict['char']['up_char']
logger.info(f'成功获取明日方舟当前up信息...当前up池: {_CURRENT_POOL_TITLE}')
average_dict = {'6': {}, '5': {}, '4': {}}
for star in up_char_dict.keys():
for key in up_char_dict[star].keys():
if average_dict[star].get(up_char_dict[star][key]):
average_dict[star][up_char_dict[star][key]].append(key)
else:
average_dict[star][up_char_dict[star][key]] = [key]
for star in average_dict.keys():
for str_zoom in average_dict[star].keys():
if str_zoom[0] == '权':
zoom = float(str_zoom[1:])
else:
zoom = float(str_zoom) / 100
UP_OPERATOR.append(UpEvent(star=int(star), operators=average_dict[star][str_zoom], zoom=zoom))
async def reload_prts_pool():
await _init_up_char()
return Message(f'当前UP池:{_CURRENT_POOL_TITLE} {POOL_IMG}')
Executable → Regular
-29
View File
@@ -1,29 +0,0 @@
from nonebot.rule import Rule
from nonebot.adapters.onebot.v11 import Bot, MessageEvent
from nonebot.typing import T_State
from .config import draw_config
def is_switch(game_name: str) -> Rule:
async def _is_switch(bot: Bot, event: MessageEvent, state: T_State) -> bool:
if game_name == 'prts':
return draw_config.PRTS_FLAG
if game_name == 'genshin':
return draw_config.GENSHIN_FLAG
if game_name == 'pretty':
return draw_config.PRETTY_FLAG
if game_name == 'guardian':
return draw_config.GUARDIAN_FLAG
if game_name == 'pcr':
return draw_config.PCR_FLAG
if game_name == 'azur':
return draw_config.AZUR_FLAG
if game_name == 'fgo':
return draw_config.FGO_FLAG
if game_name == 'onmyoji':
return draw_config.ONMYOJI_FLAG
else:
return False
return Rule(_is_switch)
-295
View File
@@ -1,295 +0,0 @@
from typing import Tuple
from .config import DRAW_DATA_PATH
from asyncio.exceptions import TimeoutError
from bs4 import BeautifulSoup
from .util import download_img
from urllib.parse import unquote
from .util import remove_prohibited_str
from utils.http_utils import AsyncHttpx
from nonebot.log import logger
import bs4
import re
try:
import ujson as json
except ModuleNotFoundError:
import json
headers = {'User-Agent': '"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; TencentTraveler 4.0)"'}
async def update_info(url: str, game_name: str, info_list: list = None) -> Tuple[dict, int]:
info_path = DRAW_DATA_PATH / f"{game_name}.json"
try:
with info_path.open('r', encoding='utf8') as f:
data = json.load(f)
except (ValueError, FileNotFoundError):
data = {}
try:
response = await AsyncHttpx.get(url, timeout=7)
soup = BeautifulSoup(response.text, 'lxml')
_tbody = get_tbody(soup, game_name, url)
trs = _tbody.find_all('tr')
att_dict, start_index, index = init_attr(game_name)
if game_name == 'guardian':
start_index = 1
if game_name == 'azur':
start_index = 0
for th in trs[0].find_all('th')[start_index:]:
text = th.text
if text[-1] == '\n':
text = text[:-1]
att_dict[text] = index
index += 1
for tr in trs[1:]:
member_dict = {}
tds = tr.find_all('td')
if not info_list:
info_list = att_dict.keys()
for key in info_list:
key, attr = parse_key(key, game_name)
td = tds[att_dict[key]]
last_tag = unquote(_find_last_tag(td, attr, game_name), 'utf-8')
member_dict[key] = last_tag
member_dict = intermediate_check(member_dict, key, game_name, td)
avatar_img = await _modify_avatar_url(game_name, member_dict["名称"])
member_dict['头像'] = avatar_img if avatar_img else member_dict['头像']
member_dict, name = replace_update_name(member_dict, game_name)
await download_img(member_dict['头像'], game_name, name)
data[name] = member_dict
logger.info(f'{name} is update...')
data = await _last_check(data, game_name)
except TimeoutError:
logger.warning(f'更新 {game_name} 超时...')
return {}, 999
with info_path.open('w', encoding='utf8') as wf:
wf.write(json.dumps(data, ensure_ascii=False, indent=4))
return data, 200
def _find_last_tag(element: bs4.element.Tag, attr: str, game_name: str) -> str:
last_tag = []
for des in element.descendants:
last_tag.append(des)
if len(last_tag) == 1 and last_tag[0] == '\n':
last_tag = ''
elif last_tag[-1] == '\n':
last_tag = last_tag[-2]
else:
last_tag = last_tag[-1]
if attr and str(last_tag):
last_tag = last_tag[attr]
elif str(last_tag).find('<img') != -1:
if last_tag.get('srcset'):
last_tag = str(last_tag.get('srcset')).strip().split(' ')[-2].strip()
else:
last_tag = last_tag['src']
else:
last_tag = str(last_tag)
if str(last_tag) and str(last_tag)[-1] == '\n':
last_tag = str(last_tag)[:-1]
if game_name not in ['pretty', 'pretty_card', 'guardian'] and last_tag.find('http') == -1:
last_tag = last_tag.split('.')[0]
return last_tag
# 获取大图(小图快爬)
async def _modify_avatar_url(game_name: str, char_name: str):
if game_name == 'prts':
res = await AsyncHttpx.get(f'https://wiki.biligame.com/arknights/{char_name}', timeout=7)
soup = BeautifulSoup(res.text, 'lxml')
try:
img_url = str(soup.find('img', {'class': 'img-bg'})['srcset']).split(' ')[-2]
except KeyError:
img_url = str(soup.find('img', {'class': 'img-bg'})['src'])
return img_url
if game_name == 'genshin':
return None
if game_name == 'pretty_card':
res = await AsyncHttpx.get(f'https://wiki.biligame.com/umamusume/{char_name}', timeout=7)
soup = BeautifulSoup(res.text, 'lxml')
try:
img_url = soup.find('div', {'class': 'support_card-left'}).find('div').find('img').get('src')
return img_url
except AttributeError:
logger.warning("pretty_card 获取大图像失败")
return None
if game_name == 'guardian':
# 未上传图片太多,换成像素图
# async with session.get(f'https://wiki.biligame.com/gt/{char_name}', timeout=7) as res:
# soup = BeautifulSoup(await res.text(), 'lxml')
# soup = soup.find('table', {'class': 'wikitable'}).find('tbody').find('tr')
# try:
# img_url = str(soup.find('img', {'class': 'img-kk'})['srcset']).split(' ')[-2]
# except KeyError:
# img_url = str(soup.find('img', {'class': 'img-kk'})['src'])
# except TypeError:
# print(f'{char_name} 图片还未上传,跳过...')
# img_url = ''
# return img_url
return None
# 数据最后处理(是否需要额外数据或处理数据)
async def _last_check(data: dict, game_name: str):
# if game_name == 'prts':
# tasks = []
# for key in data.keys():
# tasks.append(asyncio.ensure_future(_async_update_prts_extra_info(key, session)))
# asyResult = await asyncio.gather(*tasks)
# for x in asyResult:
# for key in x.keys():
# data[key]['获取途径'] = x[key]['获取途径']
if game_name == 'genshin':
for key in data.keys():
res = await AsyncHttpx.get(f'https://wiki.biligame.com/ys/{key}', timeout=7)
soup = BeautifulSoup(res.text, 'lxml')
_trs = ''
for table in soup.find_all('table', {'class': 'wikitable'}):
if str(table).find('常驻/限定') != -1:
_trs = table.find('tbody').find_all('tr')
break
for tr in _trs:
data[key]['常驻/限定'] = '未知'
if str(tr).find('限定UP') != -1:
data[key]['常驻/限定'] = '限定UP'
logger.info(f'原神获取额外数据 {key}...{data[key]["常驻/限定"]}')
break
elif str(tr).find('常驻UP') != -1:
data[key]['常驻/限定'] = '常驻UP'
logger.info(f'原神获取额外数据 {key}...{data[key]["常驻/限定"]}')
break
if game_name == 'pretty':
for keys in data.keys():
for key in data[keys].keys():
r = re.search(r'.*?40px-(.*)图标.png', str(data[keys][key]))
if r:
data[keys][key] = r.group(1)
logger.info(f'赛马娘额外修改数据...{keys}[{key}]=> {r.group(1)}')
if game_name == 'guardian':
for keys in data.keys():
for key in data[keys].keys():
r = re.search(r'.*?-star_(.*).png', str(data[keys][key]))
if r:
data[keys][key] = r.group(1)
logger.info(f'坎公骑士剑额外修改数据...{keys}[{key}] => {r.group(1)}')
return data
# 对抓取每行数据是否需要额外处理?
def intermediate_check(member_dict: dict, key: str, game_name: str, td: bs4.element.Tag):
if game_name == "genshin_arms":
if key == "稀有度":
member_dict["稀有度"] = td.find("img")["alt"].split('.')[0]
if game_name == 'prts':
if key == '获取途径':
msg = re.search('<td.*?>([\\s\\S]*)</td>', str(td)).group(1).strip()
msg = msg[:-1] if msg and msg[-1] == '\n' else msg
if msg.find('<a') != -1:
for a in td.find_all('a'):
msg = msg.replace(str(a), a.text)
member_dict['获取途径'] = msg.split('<br/>')
if game_name == 'pretty':
if key == '初始星级':
member_dict['初始星级'] = len(td.find_all('img'))
if game_name == 'pretty_card':
if key == '获取方式':
obtain = []
for x in str(td.text).replace('\n', '').strip().split('、'):
if x:
obtain.append(x)
member_dict['获取方式'] = obtain
if game_name == 'guardian':
if key == '头像':
member_dict['星级'] = str(td.find('span').find('img')['alt'])[-5]
try:
member_dict['头像'] = str(td.find('img')['srcset']).split(' ')[0]
except KeyError:
member_dict['头像'] = str(td.find('img')['src'])
return member_dict
def init_attr(game_name: str):
att_dict = {'头像': 0, '名称': 1}
start_index = 2
index = 2
if game_name == 'guardian':
att_dict = {'头像': 0, '名称': 0}
start_index = 1
index = 1
return att_dict, start_index, index
# 解析key
def parse_key(key: str, game_name):
attr = ''
if game_name == 'genshin_arms':
if key.find('.') != -1:
key = key.split('.')
attr = key[-1]
key = key[0]
return key, attr
# 拿到名称
def replace_update_name(member_dict: dict, game_name: str):
name = member_dict['名称']
if game_name == 'pretty_card':
name = member_dict['中文名']
name = remove_prohibited_str(name)
member_dict['中文名'] = name
else:
name = remove_prohibited_str(name)
member_dict['名称'] = name
return member_dict, name
# 拿到tbody,不同游戏tbody可能不同
def get_tbody(soup: bs4.BeautifulSoup, game_name: str, url: str):
max_count = 0
_tbody = None
if game_name == 'guardian_arms':
if url[-2:] == '盾牌':
div = soup.find('div', {'class': 'resp-tabs-container'}).find_all('div', {'class': 'resp-tab-content'})[1]
_tbody = div.find('tbody')
else:
div = soup.find('div', {'class': 'resp-tabs-container'}).find_all('div', {'class': 'resp-tab-content'})[0]
_tbody = div.find('table', {'id': 'CardSelectTr'}).find('tbody')
else:
for tbody in soup.find_all('tbody'):
if len(tbody.find_all('tr')) > max_count:
_tbody = tbody
max_count = len(tbody.find_all('tr'))
return _tbody
# async def _async_update_prts_extra_info(key: str, session: aiohttp.ClientSession):
# for i in range(10):
# try:
# async with session.get(f'https://wiki.biligame.com/arknights/{key}', timeout=7) as res:
# soup = BeautifulSoup(await res.text(), 'lxml')
# obtain = str(soup.find('table', {'class': 'wikitable'}).find('tbody').find_all('td')[-1])
# obtain = re.search(r'<td.*?>([\s\S]*)</.*?', obtain).group(1)
# obtain = obtain[:-1] if obtain[-1] == '\n' else obtain
# if obtain.find('<br/>'):
# obtain = obtain.split('<br/>')
# elif obtain.find('<br>'):
# obtain = obtain.split('<br>')
# for i in range(len(obtain)):
# if obtain[i].find('<a') != -1:
# text = ''
# for msg in obtain[i].split('</a>'):
# r = re.search('>(.*)', msg)
# if r:
# text += r.group(1) + ' '
# obtain[i] = obtain[i].split('<a')[0] + text[:-1] + obtain[i].split('</a>')[-1]
# print(f'明日方舟获取额外信息 {key}...{obtain}')
# x = {key: {}}
# x[key]['获取途径'] = obtain
# return x
# except TimeoutError:
# print(f'访问 https://wiki.biligame.com/arknights/{key} 第 {i}次 超时...已再次访问')
# return {}
@@ -1,156 +0,0 @@
from .config import DRAW_DATA_PATH, draw_config
from asyncio.exceptions import TimeoutError
from .util import download_img
from bs4 import BeautifulSoup
from .util import remove_prohibited_str
from utils.http_utils import AsyncHttpx
from nonebot.log import logger
import asyncio
try:
import ujson as json
except ModuleNotFoundError:
import json
headers = {'User-Agent': '"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; TencentTraveler 4.0)"'}
async def update_requests_info(game_name: str):
info_path = DRAW_DATA_PATH / f"{game_name}.json"
try:
with info_path.open('r', encoding='utf8') as f:
data = json.load(f)
except (ValueError, FileNotFoundError):
data = {}
try:
if game_name in ['fgo', 'fgo_card']:
if game_name == 'fgo':
url = 'http://fgo.vgtime.com/servant/ajax?card=&wd=&ids=&sort=12777&o=desc&pn='
else:
url = 'http://fgo.vgtime.com/equipment/ajax?wd=&ids=&sort=12958&o=desc&pn='
for i in range(9999):
response = await AsyncHttpx.get(f'{url}{i}', timeout=7)
fgo_data = json.loads(response.text)
if int(fgo_data['nums']) == 0:
break
for x in fgo_data['data']:
x['name'] = remove_prohibited_str(x['name'])
key = x['name']
data = add_to_data(data, x, game_name)
await download_img(data[key]['头像'], game_name, key)
logger.info(f'{key} is update...')
if game_name == 'onmyoji':
url = 'https://yys.res.netease.com/pc/zt/20161108171335/js/app/all_shishen.json?v74='
response = await AsyncHttpx.get(url, timeout=7)
onmyoji_data = response.json()
for x in onmyoji_data:
x['name'] = remove_prohibited_str(x['name'])
key = x['name']
data = add_to_data(data, x, game_name)
logger.info(f'{key} is update...')
data = await _last_check(data, game_name)
except TimeoutError:
logger.warning(f'更新 {game_name} 超时...')
return {}, 999
except Exception as e:
logger.warning(f'更新 {game_name} 失败 {type(e)}:{e}...')
return {}, 999
with info_path.open('w', encoding='utf8') as wf:
json.dump(data, wf, ensure_ascii=False, indent=4)
return data, 200
# 添加到字典
def add_to_data(data: dict, x: dict, game_name: str) -> dict:
member_dict = {}
if game_name == 'fgo':
member_dict = {
'id': x['id'],
'card_id': x['charid'],
'头像': x['icon'],
'名称': x['name'],
'职阶': x['classes'],
'星级': x['star'],
'hp': x['lvmax4hp'],
'atk': x['lvmax4atk'],
'card_quick': x['cardquick'],
'card_arts': x['cardarts'],
'card_buster': x['cardbuster'],
'宝具': x['tprop'],
}
if game_name == 'fgo_card':
member_dict = {
'id': x['id'],
'card_id': x['equipid'],
'头像': x['icon'],
'名称': x['name'],
'星级': x['star'],
'hp': x['lvmax_hp'],
'atk': x['lvmax_atk'],
'skill_e': x['skill_e'].split('<br />')[: -1],
}
if game_name == 'onmyoji':
member_dict = {
'id': x['id'],
'名称': x['name'],
'星级': x['level'],
}
data[member_dict['名称']] = member_dict
return data
# 获取额外数据
async def _last_check(data: dict, game_name: str) -> dict:
if game_name == 'fgo':
url = 'http://fgo.vgtime.com/servant/'
tasks = []
semaphore = asyncio.Semaphore(draw_config.SEMAPHORE)
for key in data.keys():
tasks.append(asyncio.ensure_future(
_async_update_fgo_extra_info(url, key, data[key]['id'], semaphore)))
result = await asyncio.gather(*tasks)
for x in result:
for key in x.keys():
data[key]['入手方式'] = x[key]['入手方式']
if game_name == 'onmyoji':
url = 'https://yys.163.com/shishen/{}.html'
for key in data.keys():
response = await AsyncHttpx.get(f'{url.format(data[key]["id"])}', timeout=7)
soup = BeautifulSoup(response.text, 'lxml')
data[key]['头像'] = "https:" + soup.find('div', {'class': 'pic_wrap'}).find('img')['src']
await download_img(data[key]['头像'], game_name, key)
return data
async def _async_update_fgo_extra_info(url: str, key: str, _id: str, semaphore):
# 防止访问超时
async with semaphore:
for i in range(10):
try:
response = await AsyncHttpx.get(f'{url}{_id}', timeout=7)
soup = BeautifulSoup(response.text, 'lxml')
obtain = soup.find('table', {'class': 'uk-table uk-codex-table'}).find_all('td')[-1].text
if obtain.find('限时活动免费获取 活动结束后无法获得') != -1:
obtain = ['活动获取']
elif obtain.find('非限时UP无法获得') != -1:
obtain = ['限时召唤']
else:
if obtain.find('&') != -1:
obtain = obtain.strip().split('&')
else:
obtain = obtain.strip().split(' ')
logger.info(f'Fgo获取额外信息 {key}....{obtain}')
x = {key: {}}
x[key]['入手方式'] = obtain
return x
except TimeoutError:
logger.warning(f'访问{url}{_id} 第 {i}次 超时...已再次访问')
except Exception as e:
logger.warning(f'访问{url}{_id} 第 {i}次 发生错误:{e}...已再次访问')
return {}
@@ -1,225 +0,0 @@
from typing import Tuple
from .config import DRAW_DATA_PATH, draw_config
from asyncio.exceptions import TimeoutError
from bs4 import BeautifulSoup
from .util import download_img
from .util import remove_prohibited_str
from urllib.parse import unquote
from utils.http_utils import AsyncHttpx
from nonebot.log import logger
import bs4
import asyncio
try:
import ujson as json
except ModuleNotFoundError:
import json
headers = {
"User-Agent": '"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; TencentTraveler 4.0)"'
}
async def update_simple_info(url: str, game_name: str) -> Tuple[dict, int]:
info_path = DRAW_DATA_PATH / f"{game_name}.json"
try:
with info_path.open("r", encoding="utf8") as f:
data = json.load(f)
except (ValueError, FileNotFoundError):
data = {}
try:
response = await AsyncHttpx.get(url, timeout=7)
soup = BeautifulSoup(response.text, "lxml")
divs = get_char_divs(soup, game_name)
for div in divs:
type_lst = get_type_lst(div, game_name)
index = 0
for char_lst in type_lst:
try:
contents = get_char_lst_contents(char_lst, game_name)
except AttributeError:
continue
for char in contents[1:]:
try:
data = await retrieve_char_data(
char, game_name, data, index
)
except AttributeError:
continue
index += 1
data = await _last_check(data, game_name)
except TimeoutError:
logger.warning(f"更新 {game_name} 超时...")
return {}, 999
with info_path.open("w", encoding="utf8") as wf:
wf.write(json.dumps(data, ensure_ascii=False, indent=4))
return data, 200
# 获取所有包含需要图片的divs
def get_char_divs(soup: bs4.BeautifulSoup, game_name: str) -> bs4.element.ResultSet:
# if game_name == "pcr":
# return soup.find_all("div", {"class": "tabbertab"})
if game_name in ["azur", "pcr"]:
return soup.find_all("div", {"class": "resp-tabs"})
# 拿到所有类型
def get_type_lst(div: bs4.element.Tag, game_name: str):
if game_name in ["pcr", "azur"]:
return div.find("div", {"class": "resp-tabs-container"}).find_all(
"div", {"class": "resp-tab-content"}
)
# 获取所有角色div
def get_char_lst_contents(char_lst: bs4.element.Tag, game_name: str):
contents = []
# print(len(char_lst.find_all('tr')))
if game_name == "pcr":
contents = char_lst.contents
if game_name == "azur":
contents = char_lst.find("table").find("tbody").contents[-1].find("td").contents
return [x for x in contents if x != "\n"]
# 额外数据
async def _last_check(
data: dict, game_name: str
) -> dict:
if game_name == "azur":
idx = 1
for url in [
"https://patchwiki.biligame.com/images/blhx/thumb/1/15/pxho13xsnkyb546tftvh49etzdh74cf.png/60px"
"-舰娘头像外框普通.png",
"https://patchwiki.biligame.com/images/blhx/thumb/a/a9/k8t7nx6c8pan5vyr8z21txp45jxeo66.png/60px"
"-舰娘头像外框稀有.png",
"https://patchwiki.biligame.com/images/blhx/thumb/a/a5/5whkzvt200zwhhx0h0iz9qo1kldnidj.png/60px"
"-舰娘头像外框精锐.png",
"https://patchwiki.biligame.com/images/blhx/thumb/a/a2/ptog1j220x5q02hytpwc8al7f229qk9.png/60px-"
"舰娘头像外框超稀有.png",
]:
await download_img(url, "azur", f"{idx}_star")
idx += 1
tasks = []
semaphore = asyncio.Semaphore(draw_config.SEMAPHORE)
for key in data.keys():
tasks.append(
asyncio.ensure_future(
_async_update_azur_extra_info(key, semaphore)
)
)
result = await asyncio.gather(*tasks)
for x in result:
for key in x.keys():
data[key]["获取途径"] = x[key]["获取途径"]
return data
azur_type = {
"0": "驱逐",
"1": "轻巡",
"2": "重巡",
"3": "超巡",
"4": "战巡",
"5": "战列",
"6": "航母",
"7": "航站",
"8": "轻航",
"9": "重炮",
"10": "维修",
"11": "潜艇",
"12": "运输",
}
# 整理数据
async def retrieve_char_data(
char: bs4.element.Tag,
game_name: str,
data: dict,
index: int = 0,
) -> dict:
member_dict = {}
if game_name == "pcr":
member_dict = {
"头像": unquote(char.find("a").find("img")["src"]),
"名称": remove_prohibited_str(char.find("a")["title"]),
"星级": 3 - index,
}
if game_name == "azur":
char = char.find("div").find("div").find("div")
avatar_img = char.find("a").find("img")
char = char.find("div")
try:
member_dict["名称"] = remove_prohibited_str(char.find("a")["title"])
except TypeError:
member_dict["名称"] = char.find("a")["title"][:-4]
try:
member_dict["头像"] = unquote(str(avatar_img["srcset"]).split(" ")[-2])
except KeyError:
member_dict["头像"] = unquote(str(avatar_img["src"]).split(" ")[-2])
except TypeError:
member_dict["头像"] = "img link not find..."
logger.warning(f'{member_dict["名称"]} 图片缺失....')
star = char.find("img")["alt"]
if star == "舰娘头像外框普通.png":
star = 1
elif star == "舰娘头像外框稀有.png":
star = 2
elif star == "舰娘头像外框精锐.png":
star = 3
elif star == "舰娘头像外框超稀有.png":
star = 4
elif star == "舰娘头像外框海上传奇.png":
star = 5
elif star in [
"舰娘头像外框最高方案.png",
"舰娘头像外框决战方案.png",
"舰娘头像外框超稀有META.png",
"舰娘头像外框精锐META.png",
]:
star = 6
else:
star = 6
member_dict["星级"] = star
member_dict["类型"] = azur_type[str(index)]
await download_img(member_dict["头像"], game_name, member_dict["名称"])
data[member_dict["名称"]] = member_dict
logger.info(f'{member_dict["名称"]} is update...')
return data
async def _async_update_azur_extra_info(
key: str, semaphore
):
if key[-1] == "改":
return {key: {"获取途径": ["无法建造"]}}
async with semaphore:
for i in range(20):
try:
res = await AsyncHttpx.get(f"https://wiki.biligame.com/blhx/{key}", timeout=7)
soup = BeautifulSoup(res.text, "lxml")
try:
construction_time = str(
soup.find("table", {"class": "wikitable sv-general"}).find(
"tbody"
)
)
x = {key: {"获取途径": []}}
if construction_time.find("无法建造") != -1:
x[key]["获取途径"].append("无法建造")
elif construction_time.find("活动已关闭") != -1:
x[key]["获取途径"].append("活动限定")
else:
x[key]["获取途径"].append("可以建造")
logger.info(f'碧蓝航线获取额外信息 {key}...{x[key]["获取途径"]}')
except AttributeError:
x = {key: {"获取途径": []}}
logger.warning(f"碧蓝航线获取额外信息错误 {key}...{[]}")
return x
except TimeoutError:
logger.warning(
f"访问 https://wiki.biligame.com/blhx/{key} 第 {i}次 超时...已再次访问"
)
return {}
Executable → Regular
+39 -340
View File
@@ -1,346 +1,16 @@
import platform
from asyncio.exceptions import TimeoutError
from utils.utils import cn2py
from utils.http_utils import AsyncHttpx
from typing import List, Tuple, Union, Set
from .config import draw_config, DRAW_IMAGE_PATH
import nonebot
from PIL import UnidentifiedImageError
from utils.image_utils import BuildImage
from nonebot.adapters.onebot.v11 import MessageSegment
from nonebot.log import logger
import random
from dataclasses import dataclass
import os
import asyncio
import pypinyin
from PIL.ImageFont import FreeTypeFont
from PIL import Image, ImageDraw, ImageFont
from PIL.Image import Image as IMG
from configs.path_config import FONT_PATH
driver = nonebot.get_driver()
loop = asyncio.get_event_loop()
@dataclass
class BaseData:
name: str
star: int
limited: bool # 限定
@dataclass
class UpEvent:
star: int # 对应up星级
operators: List[BaseData] # 干员列表
zoom: float # up提升倍率
async def download_img(url: str, path: str, name: str) -> bool:
path = path.split("_")[0]
codename = cn2py(name)
img_path = DRAW_IMAGE_PATH / f"{path}" / f"{codename}.png"
if not img_path.exists():
try:
if await AsyncHttpx.download_file(url, img_path):
logger.info(
f"下载 {draw_config.path_dict[path]} 图片成功,名称:{name},url:{url}"
)
return True
except TimeoutError:
logger.warning(f"下载 {draw_config.path_dict[path]} 图片超时,名称:{name},url:{url}")
# logger.info(f'{path_dict[path]} 图片 {name} 已存在')
return False
@driver.on_startup
def _check_dir():
for dir_name in draw_config.path_dict.keys():
dir_path = DRAW_IMAGE_PATH / f"{dir_name}"
if not dir_path.exists():
dir_path.mkdir(parents=True, exist_ok=True)
async def generate_img(
card_set: Union[Set[BaseData], List[BaseData]], game_name: str, star_list: list
) -> str:
# try:
img_list = []
background_list = []
for x in card_set:
if game_name == "prts":
if x.star == 6:
background_list.append("#FFD700")
elif x.star == 5:
background_list.append("#DAA520")
elif x.star == 4:
background_list.append("#9370D8")
else:
background_list.append("white")
img_path = DRAW_IMAGE_PATH / f"{game_name}" / f"{x.star}_star.png"
if game_name == "azur":
if img_path.exists():
background_list.append(str(img_path))
py_name = cn2py(x.name)
img_list.append(str(DRAW_IMAGE_PATH / f"{game_name}" / f"{py_name}.png"))
img_len = len(img_list)
w = 100 * 10
if img_len <= 10:
w = 100 * img_len
h = 100
elif img_len % 10 == 0:
h = 100 * int(img_len / 10)
else:
h = 100 * int(img_len / 10) + 100
card_img = await asyncio.get_event_loop().run_in_executor(
None, _pst, h, img_list, game_name, background_list
)
num = 0
for n in star_list:
num += n
A = BuildImage(w, h)
A.paste(card_img)
return A.pic2bs4()
def _pst(h: int, img_list: list, game_name: str, background_list: list):
card_img = BuildImage(100 * 10, h, 100, 100)
idx = 0
for img in img_list:
try:
if game_name == "prts":
bk = BuildImage(100, 100, color=background_list[idx])
b = BuildImage(94, 94, background=img)
bk.paste(b, (3, 3))
b = bk
elif game_name == "azur" and background_list:
bk = BuildImage(100, 100, background=background_list[idx])
b = BuildImage(98, 90, background=img)
bk.paste(b, (1, 5))
b = bk
else:
try:
b = BuildImage(100, 100, background=img)
except UnidentifiedImageError as e:
logger.warning(f"无法识别图片 已删除图片,下次更新重新下载... e:{e}")
if os.path.exists(img):
os.remove(img)
b = BuildImage(100, 100, color="black")
except FileNotFoundError:
logger.warning(f"{img} not exists")
b = BuildImage(100, 100, color="black")
card_img.paste(b)
idx += 1
return card_img
# 初始化输出数据
def init_star_rst(
star_list: list,
cnlist: list,
max_star_list: list,
max_star_index_list: list,
up_list: list = None,
) -> str:
if not up_list:
up_list = []
rst = ""
for i in range(len(star_list)):
if star_list[i]:
rst += f"[{cnlist[i]}×{star_list[i]}] "
rst += "\n"
for i in range(len(max_star_list)):
if max_star_list[i] in up_list:
rst += f"第 {max_star_index_list[i]+1} 抽获取UP {max_star_list[i]}\n"
else:
rst += f"第 {max_star_index_list[i]+1} 抽获取 {max_star_list[i]}\n"
return rst
# 更好的初始化
def init_rst(
max_star_char_dict: dict,
star_num_list: List[int],
star: List[str],
up_list: list = None,
):
# print(max_star_char_dict)
# print(star_num_list)
# print(up_list)
up_list = up_list if up_list else []
rst = ""
for i in range(len(star_num_list)):
if star_num_list[i]:
rst += f"[{star[i]}×{star_num_list[i]}] "
rst += "\n"
_tmp = []
for name in max_star_char_dict.keys():
_tmp += max_star_char_dict[name]
for index in sorted(_tmp):
for name in max_star_char_dict.keys():
if index in max_star_char_dict[name]:
if name in up_list:
rst += f"第 {index} 抽获取UP {name}\n"
else:
rst += f"第 {index} 抽获取 {name}\n"
return rst[:-1] if rst else ""
def max_card(_dict: dict):
_max_value = max(_dict.values())
_max_user = list(_dict.keys())[list(_dict.values()).index(_max_value)]
return f"抽取到最多的是{_max_user},共抽取了{_max_value}次"
# ThreeHighest = nlargest(3, operator_dict, key=operator_dict.get)
# rst = '最喜欢你的前三位是干员是:\n'
# for name in ThreeHighest:
# rst += f'{name} 共投了 {operator_dict[name]} 份简历\n'
# return rst[:-1]
# 获取up和概率
async def init_up_char(announcement):
UP_CHAR = []
UP_ARMS = []
tmp = ""
up_char_dict = await announcement.update_up_char()
for x in list(up_char_dict.keys()):
tmp += up_char_dict[x]["title"] + "[\n]"
tmp = tmp.split("[\n]")
_CURRENT_CHAR_POOL_TITLE = tmp[0]
if len(up_char_dict) > 1:
_CURRENT_ARMS_POOL_TITLE = tmp[1]
else:
_CURRENT_ARMS_POOL_TITLE = ""
POOL_IMG = ""
x = [x for x in list(up_char_dict.keys())]
if _CURRENT_CHAR_POOL_TITLE:
POOL_IMG += MessageSegment.image(up_char_dict[x[0]]["pool_img"])
try:
if _CURRENT_ARMS_POOL_TITLE:
POOL_IMG += MessageSegment.image(up_char_dict[x[1]]["pool_img"])
except (IndexError, KeyError):
pass
logger.info(
f"成功获取{announcement.game_name}当前up信息...当前up池: {_CURRENT_CHAR_POOL_TITLE} & {_CURRENT_ARMS_POOL_TITLE}"
)
for key in up_char_dict.keys():
for star in up_char_dict[key]["up_char"].keys():
up_char_lst = []
for char in up_char_dict[key]["up_char"][star].keys():
up_char_lst.append(char)
if up_char_lst:
if key == "char":
UP_CHAR.append(
UpEvent(star=int(star), operators=up_char_lst, zoom=0)
)
else:
UP_ARMS.append(
UpEvent(star=int(star), operators=up_char_lst, zoom=0)
)
return (
_CURRENT_CHAR_POOL_TITLE,
_CURRENT_ARMS_POOL_TITLE,
POOL_IMG,
UP_CHAR,
UP_ARMS,
)
def is_number(s) -> bool:
try:
float(s)
return True
except ValueError:
pass
try:
import unicodedata
unicodedata.numeric(s)
return True
except (TypeError, ValueError):
pass
return False
def set_list(lst: List[BaseData]) -> list:
tmp = []
name_lst = []
for x in lst:
if x.name not in name_lst:
tmp.append(x)
name_lst.append(x.name)
return tmp
# 获取星级
def get_star(star_lst: List[int], probability_lst: List[float]) -> int:
rand = random.random()
add = 0
tmp_lst = [(0, probability_lst[0])]
for i in range(1, len(probability_lst) - 1):
add += probability_lst[i - 1]
tmp_lst.append((tmp_lst[i - 1][1], probability_lst[i] + add))
tmp_lst.append((tmp_lst[-1][1], 1))
for i in range(len(tmp_lst)):
if tmp_lst[i][0] <= rand <= tmp_lst[i][1]:
return star_lst[i]
# 整理数据
def format_card_information(
count: int, star_list: List[int], func, pool_name: str = "", guaranteed: bool = True
):
max_star_lst = [] # 获取的最高星级角色列表
max_index_lst = [] # 获取最高星级角色的次数
obj_list = [] # 获取所有角色
obj_dict = {} # 获取角色次数字典
_count = -1
if guaranteed:
_count = 0
for i in range(count):
if guaranteed:
_count += 1
if pool_name:
if _count == 10:
obj, code = func(pool_name, 2)
_count = 0
else:
obj, code = func(pool_name)
else:
if _count == 10:
obj, code = func(mode=2)
_count = 0
else:
obj, code = func()
star_list[code] += 1
if code == 0:
max_star_lst.append(obj.name)
max_index_lst.append(i)
if guaranteed:
_count = 0
if code == 1:
if guaranteed:
_count = 0
try:
obj_dict[obj.name] += 1
except KeyError:
obj_dict[obj.name] = 1
obj_list.append(obj)
return obj_list, obj_dict, max_star_lst, star_list, max_index_lst
# 检测次数是否合法
def check_num(num: str, max_num: int) -> Tuple[str, bool]:
if is_number(num):
try:
num = int(num)
except ValueError:
return "必!须!是!数!字!", False
if num > max_num:
return "一井都满不足不了你嘛!快爬开!", False
if num < 1:
return "虚空抽卡???", False
else:
return str(num), True
def cn2py(word) -> str:
temp = ""
for i in pypinyin.pinyin(word, style=pypinyin.NORMAL):
temp += "".join(i)
return temp
# 移除windows和linux下特殊字符
@@ -354,3 +24,32 @@ def remove_prohibited_str(name: str) -> str:
else:
name = name.replace("/", "\\")
return name
def load_font(font_name: str = "msyh.ttf", fontsize: int = 16) -> FreeTypeFont:
return ImageFont.truetype(
str(FONT_PATH / f"{font_name}"), fontsize, encoding="utf-8"
)
def circled_number(num: int) -> IMG:
font = load_font(fontsize=450)
text = str(num)
text_w = font.getsize(text)[0]
w = 240 + text_w
w = w if w >= 500 else 500
img = Image.new("RGBA", (w, 500))
draw = ImageDraw.Draw(img)
draw.ellipse(((0, 0), (500, 500)), fill="red")
draw.ellipse(((w - 500, 0), (w, 500)), fill="red")
draw.rectangle(((250, 0), (w - 250, 500)), fill="red")
draw.text(
(120, -60),
text,
font=font,
fill="white",
stroke_width=10,
stroke_fill="white",
)
return img
+1 -1
View File
@@ -132,4 +132,4 @@ async def get_fudu_img_hash(url, group_id):
logger.warning(f"复读下载图片失败...")
except Exception as e:
logger.warning(f"复读读取图片Hash出错 {type(e)}:{e}")
return ""
return ""
@@ -5,6 +5,7 @@ from apscheduler.jobstores.base import ConflictingIdError
from nonebot import Driver
from .._models import Genshin
from datetime import datetime, timedelta
from apscheduler.jobstores.base import JobLookupError
from services.log import logger
from nonebot.plugin import require
from configs.config import Config
@@ -108,6 +109,11 @@ async def _():
def add_job(user_id: int, uid: int):
# 移除
try:
scheduler.remove_job(f"genshin_resin_remind_{uid}_{user_id}")
except JobLookupError:
pass
date = datetime.now(pytz.timezone("Asia/Shanghai")) + timedelta(seconds=30)
try:
scheduler.add_job(
+1 -1
View File
@@ -37,7 +37,7 @@ async def _(event: GroupMessageEvent):
img = ""
msg = ""
if (DATA_PATH / "custom_welcome_msg" / f"{event.group_id}.jpg").exists():
img = image(abspath=DATA_PATH / "custom_welcome_msg" / f"{event.group_id}.jpg")
img = image(DATA_PATH / "custom_welcome_msg" / f"{event.group_id}.jpg")
custom_welcome_msg_json = (
DATA_PATH / "custom_welcome_msg" / "custom_welcome_msg.json"
)
+54
View File
@@ -0,0 +1,54 @@
from .music_163 import get_song_id, get_song_info
from nonebot.adapters.onebot.v11 import Bot, MessageEvent, GroupMessageEvent, Message
from nonebot.params import CommandArg
from nonebot.typing import T_State
from services.log import logger
from nonebot import on_command
from utils.message_builder import music
__zx_plugin_name__ = "点歌"
__plugin_usage__ = """
usage:
在线点歌
指令:
点歌 [歌名]
""".strip()
__plugin_des__ = "为你点播了一首曾经的歌"
__plugin_cmd__ = ["点歌 [歌名]"]
__plugin_type__ = ("一些工具",)
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
__plugin_settings__ = {
"level": 5,
"default_status": True,
"limit_superuser": False,
"cmd": ["点歌"],
}
music_handler = on_command("点歌", priority=5, block=True)
@music_handler.handle()
async def handle_first_receive(state: T_State, arg: Message = CommandArg()):
if args := arg.extract_plain_text().strip():
state["song_name"] = args
@music_handler.got("song_name", prompt="歌名是?")
async def _(bot: Bot, event: MessageEvent, state: T_State):
song = state["song_name"]
song_id = await get_song_id(song)
if not song_id:
await music_handler.finish("没有找到这首歌!", at_sender=True)
await music_handler.send(music("163", song_id))
logger.info(
f"(USER {event.user_id}, GROUP "
f"{event.group_id if isinstance(event, GroupMessageEvent) else 'private'})"
f" 点歌 :{song}"
)
+41 -41
View File
@@ -1,41 +1,41 @@
from utils.http_utils import AsyncHttpx
import json
headers = {"referer": "http://music.163.com"}
cookies = {"appver": "2.0.2"}
async def search_song(song_name: str):
"""
搜索歌曲
:param song_name: 歌名
"""
r = await AsyncHttpx.post(
f"http://music.163.com/api/search/get/",
data={"s": song_name, "limit": 1, "type": 1, "offset": 0},
)
if r.status_code != 200:
return None
return json.loads(r.text)
async def get_song_id(song_name: str) -> int:
""" """
r = await search_song(song_name)
try:
return r["result"]["songs"][0]["id"]
except KeyError:
return 0
async def get_song_info(songId: int):
"""
获取歌曲信息
"""
r = await AsyncHttpx.post(
f"http://music.163.com/api/song/detail/?id={songId}&ids=%5B{songId}%5D",
)
if r.status_code != 200:
return None
return json.loads(r.text)
from utils.http_utils import AsyncHttpx
import json
headers = {"referer": "http://music.163.com"}
cookies = {"appver": "2.0.2"}
async def search_song(song_name: str):
"""
搜索歌曲
:param song_name: 歌名
"""
r = await AsyncHttpx.post(
f"http://music.163.com/api/search/get/",
data={"s": song_name, "limit": 1, "type": 1, "offset": 0},
)
if r.status_code != 200:
return None
return json.loads(r.text)
async def get_song_id(song_name: str) -> int:
""" """
r = await search_song(song_name)
try:
return r["result"]["songs"][0]["id"]
except KeyError:
return 0
async def get_song_info(songId: int):
"""
获取歌曲信息
"""
r = await AsyncHttpx.post(
f"http://music.163.com/api/song/detail/?id={songId}&ids=%5B{songId}%5D",
)
if r.status_code != 200:
return None
return json.loads(r.text)
+5 -6
View File
@@ -1,9 +1,8 @@
from io import BytesIO
from random import choice
from nonebot import on_regex
from nonebot.typing import T_State
from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent
from utils.utils import get_message_at, get_user_avatar
from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, Message
from utils.utils import get_message_at, get_user_avatar, get_message_text
from utils.message_builder import image
from utils.image_utils import BuildImage
from nonebot.params import RegexGroup
@@ -28,14 +27,14 @@ __plugin_settings__ = {
}
one_friend = on_regex(
"^我.*?朋友.*?(想问问|说|让我问问|想问|让我问|想知道|让我帮他问问|让我帮他问|让我帮忙问|让我帮忙问问|问)(.*)",
"^我.*?朋友.*?[想问问|说|让我问问|想问|让我问|想知道|让我帮他问问|让我帮他问|让我帮忙问|让我帮忙问问|问](.*)",
priority=4,
block=True,
)
@one_friend.handle()
async def _(bot: Bot, event: GroupMessageEvent, state: T_State, reg_group: Tuple[Any, ...] = RegexGroup()):
async def _(bot: Bot, event: GroupMessageEvent, reg_group: Tuple[Any, ...] = RegexGroup()):
qq = get_message_at(event.json())
if not qq:
qq = choice(
@@ -51,7 +50,7 @@ async def _(bot: Bot, event: GroupMessageEvent, state: T_State, reg_group: Tuple
qq = qq[0]
at_user = await bot.get_group_member_info(group_id=event.group_id, user_id=qq)
user_name = at_user["card"] or at_user["nickname"]
msg = reg_group[1]
msg = get_message_text(Message(reg_group[0])).strip()
if not msg:
msg = "都不知道问什么"
msg = msg.replace("他", "我").replace("她", "我").replace("它", "我")
+2 -2
View File
@@ -235,10 +235,10 @@ async def get_image(img_url: str, user_id: int) -> Optional[str]:
img_url = old_img_url
continue
async with aiofiles.open(
TEMP_PATH / f"pix_{user_id}_{img_url[-10:-4]}.jpg", "wb"
TEMP_PATH / f"pix_{user_id}_{img_url.split('/')[-1][:-4]}.jpg", "wb"
) as f:
await f.write(response.content)
return TEMP_PATH / f"pix_{user_id}_{img_url[-10:-4]}.jpg"
return TEMP_PATH / f"pix_{user_id}_{img_url.split('/')[-1][:-4]}.jpg"
except TimeoutError:
logger.warning(f"PIX:{img_url} 图片下载超时...")
pass
+39 -18
View File
@@ -1,15 +1,10 @@
from utils.utils import is_number
from configs.config import Config
from ._model.omega_pixiv_illusts import OmegaPixivIllusts
from utils.message_builder import image
from utils.message_builder import image, custom_forward_msg
from utils.manager import withdraw_message_manager
from services.log import logger
from nonebot.adapters.onebot.v11 import (
Bot,
MessageEvent,
GroupMessageEvent,
Message
)
from nonebot.adapters.onebot.v11 import Bot, MessageEvent, GroupMessageEvent, Message
from nonebot.params import CommandArg
from ._data_source import get_image
from ._model.pixiv import Pixiv
@@ -49,6 +44,13 @@ __plugin_settings__ = {
"cmd": ["pix", "Pix", "PIX", "pIx"],
}
__plugin_block_limit__ = {"rst": "您有PIX图片正在处理,请稍等..."}
__plugin_configs__ = {
"MAX_ONCE_NUM2FORWARD": {
"value": None,
"help": "单次发送的图片数量达到指定值时转发为合并消息",
"default_value": None,
}
}
pix = on_command("pix", aliases={"PIX", "Pix"}, priority=5, block=True)
@@ -64,12 +66,12 @@ async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()):
if PIX_RATIO is None:
pix_omega_pixiv_ratio = Config.get_config("pix", "PIX_OMEGA_PIXIV_RATIO")
PIX_RATIO = pix_omega_pixiv_ratio[0] / (
pix_omega_pixiv_ratio[0] + pix_omega_pixiv_ratio[1]
pix_omega_pixiv_ratio[0] + pix_omega_pixiv_ratio[1]
)
OMEGA_RATIO = 1 - PIX_RATIO
num = 1
keyword = arg.extract_plain_text().strip()
x = keyword.split(" ")
x = keyword.split()
if "-s" in x:
x.remove("-s")
nsfw_tag = 1
@@ -80,12 +82,15 @@ async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()):
nsfw_tag = 0
if nsfw_tag != 0 and str(event.user_id) not in bot.config.superusers:
await pix.finish("你不能看这些噢,这些都是是留给管理员看的...")
if len(x) > 1:
if n := len(x) == 1 and is_number(x[0]):
num = int(x[-1])
keyword = ""
elif n > 1:
if is_number(x[-1]):
num = int(x[-1])
if num > 10:
if str(event.user_id) not in bot.config.superusers or (
str(event.user_id) in bot.config.superusers and num > 30
str(event.user_id) in bot.config.superusers and num > 30
):
num = random.randint(1, 10)
await pix.send(f"太贪心了,就给你发 {num}张 好了")
@@ -125,11 +130,12 @@ async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()):
tmp_.append(x.pid)
if not all_image:
await pix.finish(f"未在图库中找到与 {keyword} 相关Tag/UID/PID的图片...", at_sender=True)
msg_list = []
for _ in range(num):
img_url = None
author = None
if not all_image:
await pix.finish("坏了...发完了,没图了...")
# if not all_image:
# await pix.finish("坏了...发完了,没图了...")
img = random.choice(all_image)
all_image.remove(img)
if isinstance(img, OmegaPixivIllusts):
@@ -144,7 +150,7 @@ async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()):
_img = await get_image(img_url, event.user_id)
if _img:
if Config.get_config("pix", "SHOW_INFO"):
msg_id = await pix.send(
msg_list.append(
Message(
f"title:{title}\n"
f"author:{author}\n"
@@ -153,16 +159,31 @@ async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()):
)
)
else:
msg_id = await pix.send(image(_img))
msg_list.append(image(_img))
logger.info(
f"(USER {event.user_id}, GROUP {event.group_id if isinstance(event, GroupMessageEvent) else 'private'})"
f" 查看PIX图库PID: {pid}"
)
else:
msg_id = await pix.send(f"下载图片似乎出了一点问题,PID:{pid}")
msg_list.append("这张图似乎下载失败了")
logger.info(
f"(USER {event.user_id}, GROUP {event.group_id if isinstance(event, GroupMessageEvent) else 'private'})"
f" 查看PIX图库PID: {pid},下载图片出错"
)
withdraw_message_manager.withdraw_message(event, msg_id, Config.get_config("pix", "WITHDRAW_PIX_MESSAGE"))
if (
Config.get_config("pix", "MAX_ONCE_NUM2FORWARD")
and num >= Config.get_config("pix", "MAX_ONCE_NUM2FORWARD")
and isinstance(event, GroupMessageEvent)
):
msg_id = await bot.send_group_forward_msg(
group_id=event.group_id, messages=custom_forward_msg(msg_list, bot.self_id)
)
withdraw_message_manager.withdraw_message(
event, msg_id, Config.get_config("pix", "WITHDRAW_PIX_MESSAGE")
)
else:
for msg in msg_list:
msg_id = await pix.send(msg)
withdraw_message_manager.withdraw_message(
event, msg_id, Config.get_config("pix", "WITHDRAW_PIX_MESSAGE")
)
Generated
+606 -12
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -36,6 +36,8 @@ ujson = "^5.1.0"
matplotlib = "^3.5.1"
black = "^22.1.0"
pypinyin = "^0.46.0"
dateparser = "^1.1.0"
cn2an = "^0.5.16"
[tool.poetry.dev-dependencies]
Binary file not shown.

Before

Width:  |  Height:  |  Size: 474 KiB

After

Width:  |  Height:  |  Size: 474 KiB

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Before

Width:  |  Height:  |  Size: 393 KiB

After

Width:  |  Height:  |  Size: 393 KiB

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 176 KiB

Before

Width:  |  Height:  |  Size: 654 KiB

After

Width:  |  Height:  |  Size: 654 KiB

Before

Width:  |  Height:  |  Size: 365 KiB

After

Width:  |  Height:  |  Size: 365 KiB

Before

Width:  |  Height:  |  Size: 290 KiB

After

Width:  |  Height:  |  Size: 290 KiB

Before

Width:  |  Height:  |  Size: 415 KiB

After

Width:  |  Height:  |  Size: 415 KiB

Before

Width:  |  Height:  |  Size: 608 KiB

After

Width:  |  Height:  |  Size: 608 KiB

Before

Width:  |  Height:  |  Size: 499 KiB

After

Width:  |  Height:  |  Size: 499 KiB

Before

Width:  |  Height:  |  Size: 596 KiB

After

Width:  |  Height:  |  Size: 596 KiB

Before

Width:  |  Height:  |  Size: 745 KiB

After

Width:  |  Height:  |  Size: 745 KiB

Before

Width:  |  Height:  |  Size: 349 KiB

After

Width:  |  Height:  |  Size: 349 KiB

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Before

Width:  |  Height:  |  Size: 365 KiB

After

Width:  |  Height:  |  Size: 365 KiB

Before

Width:  |  Height:  |  Size: 415 KiB

After

Width:  |  Height:  |  Size: 415 KiB

Before

Width:  |  Height:  |  Size: 349 KiB

After

Width:  |  Height:  |  Size: 349 KiB

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Before

Width:  |  Height:  |  Size: 153 KiB

After

Width:  |  Height:  |  Size: 153 KiB

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Some files were not shown because too many files have changed in this diff Show More