diff --git a/.env.example b/.env.example index d59cbc8..5e6da1c 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,12 @@ # debug 开关 DEBUG=false +AUTO_RELOAD=false +RELOAD_DELAY=0.25 +RELOAD_DIRS=[] +RELOAD_INCLUDE=[] +RELOAD_EXCLUDE=[] + # MySQL DB_HOST=127.0.0.1 DB_PORT=3306 @@ -17,14 +23,14 @@ REDIS_PASSWORD="" # 联系 https://t.me/BotFather 使用 /newbot 命令创建机器人并获取 token BOT_TOKEN="xxxxxxx" -# bot 管理员 -ADMINS=[{ "username": "", "user_id": -1 }] +# bot 所有者 +OWNER=0 # 记录错误并发送消息通知开发人员 可选配置项 # ERROR_NOTIFICATION_CHAT_ID=chat_id # 文章推送群组 可选配置项 -# CHANNELS=[{ "name": "", "chat_id": 1}] +# CHANNELS=[] # 是否允许机器人邀请到其他群 默认不允许 如果允许 可以允许全部人或有认证选项 可选配置项 # JOIN_GROUPS = "NO_ALLOW" @@ -33,20 +39,20 @@ ADMINS=[{ "username": "", "user_id": -1 }] # VERIFY_GROUPS=[] # logger 配置 可选配置项 -LOGGER_NAME="TGPaimon" +# LOGGER_NAME="TGPaimon" # 打印时的宽度 -LOGGER_WIDTH=180 +# LOGGER_WIDTH=180 # log 文件存放目录 -LOGGER_LOG_PATH="logs" +# LOGGER_LOG_PATH="logs" # log 时间格式,参考 datetime.strftime -LOGGER_TIME_FORMAT="[%Y-%m-%d %X]" +# LOGGER_TIME_FORMAT="[%Y-%m-%d %X]" # log 高亮关键词 -LOGGER_RENDER_KEYWORDS=["BOT"] +# LOGGER_RENDER_KEYWORDS=["BOT"] # traceback 相关配置 -LOGGER_TRACEBACK_MAX_FRAMES=20 -LOGGER_LOCALS_MAX_DEPTH=0 -LOGGER_LOCALS_MAX_LENGTH=10 -LOGGER_LOCALS_MAX_STRING=80 +# LOGGER_TRACEBACK_MAX_FRAMES=20 +# LOGGER_LOCALS_MAX_DEPTH=0 +# LOGGER_LOCALS_MAX_LENGTH=10 +# LOGGER_LOCALS_MAX_STRING=80 # 可被 logger 打印的 record 的名称(默认包含了 LOGGER_NAME ) LOGGER_FILTERED_NAMES=["uvicorn","ErrorPush","ApiHelper"] @@ -77,7 +83,7 @@ LOGGER_FILTERED_NAMES=["uvicorn","ErrorPush","ApiHelper"] # ENKA_NETWORK_API_AGENT="" # Web Server -# 目前只用于预览模板,仅开发环境启动 +# WEB_SWITCH=False # 是否开启 # WEB_URL=http://localhost:8080/ # WEB_HOST=localhost # WEB_PORT=8080 diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml new file mode 100644 index 0000000..a51d080 --- /dev/null +++ b/.github/workflows/integration-test.yml @@ -0,0 +1,54 @@ +name: Integration Test + +on: + push: + branches: + - main + paths: + - 'tests/integration/**' + pull_request: + types: [ opened, synchronize ] + paths: + - 'core/services/**' + - 'core/dependence/**' + - 'tests/integration/**' + +jobs: + pytest: + name: pytest + runs-on: ubuntu-latest + services: + mysql: + image: mysql:5.7 + env: + MYSQL_DATABASE: integration_test + MYSQL_ROOT_PASSWORD: 123456test + ports: + - 3306:3306 + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + redis: + image: redis + ports: + - 6379:6379 + steps: + - name: Checkout code + uses: actions/checkout@v3 + - name: Set up Python 3.11 + uses: actions/setup-python@v2 + with: + python-version: 3.11 + - name: Setup integration test environment + run: cp tests/integration/.env.example .env && cp tests/integration/.env.example tests/integration/.env + - name: Create venv + run: | + pip install --upgrade pip + python3 -m venv venv + - name: Install requirements + run: | + source venv/bin/activate + python3 -m pip install --upgrade poetry + python3 -m poetry install --extras all + - name: Run test + run: | + source venv/bin/activate + python3 -m pytest tests/integration \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dfd3895..74b5387 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,19 +1,17 @@ -name: test +name: Test modules on: push: branches: - main paths: - - 'tests/**' + - 'tests/unit/**' pull_request: types: [ opened, synchronize ] paths: - 'modules/apihelper/**' - 'modules/wiki/**' - - 'tests/**' - schedule: - - cron: '0 4 * * 3' + - 'tests/unit/**' jobs: pytest: @@ -22,16 +20,15 @@ jobs: continue-on-error: ${{ matrix.experimental }} strategy: matrix: - python-version: [ '3.10' ] os: [ ubuntu-latest, windows-latest ] - experimental: [ false ] fail-fast: False steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + - name: Checkout code + uses: actions/checkout@v3 + - name: Set up Python 3.11 + uses: actions/setup-python@v2 with: - python-version: ${{ matrix.python-version }} + python-version: 3.11 - name: restore or create a python virtualenv id: cache uses: syphar/restore-virtualenv@v1.2 @@ -45,4 +42,4 @@ jobs: poetry install --extras test - name: Test with pytest run: | - python -m pytest \ No newline at end of file + python -m pytest tests/unit \ No newline at end of file diff --git a/.gitignore b/.gitignore index 78c6a5f..7ee81cb 100644 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,5 @@ plugins/private .pytest_cache ### mtp ### -paimon.session -PaimonBot.session -PaimonBot.session-journal +paigram.session +paigram.session-journal diff --git a/README.md b/README.md index 18a9674..d0a1534 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@
{chat.id}
\n群名称:{chat.title}
\n"
+ if chat.username:
+ text += f"群用户名:@{chat.username}\n"
+ if chat.description:
+ text += f"群简介:{html.escape(chat.description)}
\n"
+ if admins:
+ for admin in admins:
+ text += f'{html.escape(admin.user.full_name)} '
+ if isinstance(admin, ChatMemberAdministrator):
+ text += "C" if admin.can_change_info else "_"
+ text += "D" if admin.can_delete_messages else "_"
+ text += "R" if admin.can_restrict_members else "_"
+ text += "I" if admin.can_invite_users else "_"
+ text += "T" if admin.can_manage_topics else "_"
+ text += "P" if admin.can_pin_messages else "_"
+ text += "V" if admin.can_manage_video_chats else "_"
+ text += "N" if admin.can_promote_members else "_"
+ text += "A" if admin.is_anonymous else "_"
+ elif isinstance(admin, ChatMemberOwner):
+ text += "创建者"
+ text += "\n"
+ return text
+
+ async def parse_private_chat(self, chat: Chat) -> str:
+ text = (
+ f'MENTION\n'
+ f"用户 ID:{chat.id}
\n"
+ f"用户名称:{chat.full_name}
\n"
+ )
+ if chat.username:
+ text += f"用户名:@{chat.username}\n"
+ player_info = await self.players_service.get_player(chat.id)
+ if player_info is not None:
+ if player_info.region == RegionEnum.HYPERION:
+ text += "米游社绑定:"
+ else:
+ text += "原神绑定:"
+ cookies_info = await self.cookies_service.get(chat.id, player_info.account_id, player_info.region)
+ if cookies_info is None:
+ temp = "UID 绑定"
+ else:
+ temp = "Cookie 绑定"
+ text += f"{temp}
\n游戏 ID:{player_info.player_id}
"
+ return text
+
+ @handler(CommandHandler, command="get_chat", block=False, admin=True)
+ async def get_chat_command(self, update: Update, context: CallbackContext):
+ user = update.effective_user
+ logger.info("用户 %s[%s] get_chat 命令请求", user.full_name, user.id)
+ message = update.effective_message
+ args = self.get_args(context)
+ if not args:
+ await message.reply_text("参数错误,请指定群 id !")
+ return
+ try:
+ chat_id = int(args[0])
+ except ValueError:
+ await message.reply_text("参数错误,请指定群 id !")
+ return
+ try:
+ chat = await self.get_chat(args[0])
+ if chat_id < 0:
+ admins = await chat.get_administrators() if chat_id < 0 else None
+ text = await self.parse_group_chat(chat, admins)
+ else:
+ text = await self.parse_private_chat(chat)
+ await message.reply_text(text, parse_mode="HTML")
+ except (BadRequest, Forbidden) as exc:
+ await message.reply_text(f"通过 id 获取会话信息失败,API 返回:{exc.message}")
diff --git a/plugins/other/post.py b/plugins/admin/post.py
similarity index 86%
rename from plugins/other/post.py
rename to plugins/admin/post.py
index d0d7468..b0060be 100644
--- a/plugins/other/post.py
+++ b/plugins/admin/post.py
@@ -1,29 +1,25 @@
-from typing import Optional, List, Tuple
+from typing import List, Optional, Tuple
-from bs4 import BeautifulSoup, Tag
+from bs4 import BeautifulSoup
from telegram import (
- Update,
- ReplyKeyboardMarkup,
- ReplyKeyboardRemove,
InlineKeyboardButton,
InlineKeyboardMarkup,
+ InputMediaPhoto,
Message,
+ ReplyKeyboardMarkup,
+ ReplyKeyboardRemove,
+ Update,
)
-from telegram.constants import ParseMode, MessageLimit
+from telegram.constants import MessageLimit, ParseMode
from telegram.error import BadRequest
from telegram.ext import CallbackContext, ConversationHandler, filters
from telegram.helpers import escape_markdown
-from core.baseplugin import BasePlugin
-from core.bot import bot
from core.config import config
from core.plugin import Plugin, conversation, handler
from modules.apihelper.client.components.hyperion import Hyperion
from modules.apihelper.error import APIHelperException
from modules.apihelper.models.genshin.hyperion import ArtworkImage
-from utils.decorators.admins import bot_admins_rights_check
-from utils.decorators.error import error_callable
-from utils.decorators.restricts import restricts
from utils.log import logger
@@ -40,7 +36,7 @@ CHECK_POST, SEND_POST, CHECK_COMMAND, GTE_DELETE_PHOTO = range(10900, 10904)
GET_POST_CHANNEL, GET_TAGS, GET_TEXT = range(10904, 10907)
-class Post(Plugin.Conversation, BasePlugin.Conversation):
+class Post(Plugin.Conversation):
"""文章推送"""
MENU_KEYBOARD = ReplyKeyboardMarkup([["推送频道", "添加TAG"], ["编辑文字", "删除图片"], ["退出"]], True, True)
@@ -48,9 +44,11 @@ class Post(Plugin.Conversation, BasePlugin.Conversation):
def __init__(self):
self.bbs = Hyperion()
self.last_post_id_list: List[int] = []
+
+ async def initialize(self):
if config.channels and len(config.channels) > 0:
logger.success("文章定时推送处理已经开启")
- bot.app.job_queue.run_repeating(self.task, 60)
+ self.application.job_queue.run_repeating(self.task, 60)
async def task(self, context: CallbackContext):
temp_post_id_list: List[int] = []
@@ -109,8 +107,6 @@ class Post(Plugin.Conversation, BasePlugin.Conversation):
@conversation.entry_point
@handler.callback_query(pattern=r"^post_admin\|", block=False)
- @bot_admins_rights_check
- @error_callable
async def callback_query_start(self, update: Update, context: CallbackContext) -> int:
post_handler_data = context.chat_data.get("post_handler_data")
if post_handler_data is None:
@@ -143,10 +139,7 @@ class Post(Plugin.Conversation, BasePlugin.Conversation):
return ConversationHandler.END
@conversation.entry_point
- @handler.command(command="post", filters=filters.ChatType.PRIVATE, block=True)
- @restricts()
- @bot_admins_rights_check
- @error_callable
+ @handler.command(command="post", filters=filters.ChatType.PRIVATE, block=False, admin=True)
async def command_start(self, update: Update, context: CallbackContext) -> int:
user = update.effective_user
message = update.effective_message
@@ -161,8 +154,7 @@ class Post(Plugin.Conversation, BasePlugin.Conversation):
return CHECK_POST
@conversation.state(state=CHECK_POST)
- @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
- @error_callable
+ @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=False)
async def check_post(self, update: Update, context: CallbackContext) -> int:
post_handler_data: PostHandlerData = context.chat_data.get("post_handler_data")
message = update.effective_message
@@ -176,41 +168,24 @@ class Post(Plugin.Conversation, BasePlugin.Conversation):
return ConversationHandler.END
return await self.send_post_info(post_handler_data, message, post_id)
- @staticmethod
- def parse_post_text(soup: BeautifulSoup, post_subject: str) -> str:
- def parse_tag(_tag: Tag) -> str:
- if _tag.name == "a" and _tag.get("href"):
- return f"[{escape_markdown(_tag.get_text(), version=2)}]({_tag.get('href')})"
- return escape_markdown(_tag.get_text(), version=2)
-
- post_p = soup.find_all("p")
- post_text = f"*{escape_markdown(post_subject, version=2)}*\n\n"
- start = True
- for p in post_p:
- t = p.get_text()
- if not t and start:
- continue
- start = False
- for tag in p.contents:
- post_text += parse_tag(tag)
- post_text += "\n"
- return post_text
-
async def send_post_info(self, post_handler_data: PostHandlerData, message: Message, post_id: int) -> int:
post_info = await self.bbs.get_post_info(2, post_id)
post_images = await self.bbs.get_images_by_post_id(2, post_id)
post_data = post_info["post"]["post"]
post_subject = post_data["subject"]
post_soup = BeautifulSoup(post_data["content"], features="html.parser")
- post_text = self.parse_post_text(post_soup, post_subject)
+ post_p = post_soup.find_all("p")
+ post_text = f"*{escape_markdown(post_subject, version=2)}*\n" f"\n"
+ for p in post_p:
+ post_text += f"{escape_markdown(p.get_text(), version=2)}\n"
post_text += f"[source](https://www.miyoushe.com/ys/article/{post_id})"
if len(post_text) >= MessageLimit.CAPTION_LENGTH:
post_text = post_text[: MessageLimit.CAPTION_LENGTH]
await message.reply_text(f"警告!图片字符描述已经超过 {MessageLimit.CAPTION_LENGTH} 个字,已经切割")
try:
if len(post_images) > 1:
- media = [img_info.input_media() for img_info in post_images if img_info.format]
- media[0] = post_images[0].input_media(caption=post_text, parse_mode=ParseMode.MARKDOWN_V2)
+ media = [InputMediaPhoto(img_info.data) for img_info in post_images]
+ media[0] = InputMediaPhoto(post_images[0].data, caption=post_text, parse_mode=ParseMode.MARKDOWN_V2)
if len(media) > 10:
media = media[:10]
await message.reply_text("获取到的图片已经超过10张,为了保证发送成功,已经删除一部分图片")
@@ -235,20 +210,19 @@ class Post(Plugin.Conversation, BasePlugin.Conversation):
return CHECK_COMMAND
@conversation.state(state=CHECK_COMMAND)
- @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
- @error_callable
+ @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=False)
async def check_command(self, update: Update, context: CallbackContext) -> int:
message = update.effective_message
if message.text == "退出":
await message.reply_text("退出任务", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END
- elif message.text == "推送频道":
+ if message.text == "推送频道":
return await self.get_channel(update, context)
- elif message.text == "添加TAG":
+ if message.text == "添加TAG":
return await self.add_tags(update, context)
- elif message.text == "编辑文字":
+ if message.text == "编辑文字":
return await self.edit_text(update, context)
- elif message.text == "删除图片":
+ if message.text == "删除图片":
return await self.delete_photo(update, context)
return ConversationHandler.END
@@ -261,8 +235,7 @@ class Post(Plugin.Conversation, BasePlugin.Conversation):
return GTE_DELETE_PHOTO
@conversation.state(state=GTE_DELETE_PHOTO)
- @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
- @error_callable
+ @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=False)
async def get_delete_photo(self, update: Update, context: CallbackContext) -> int:
post_handler_data: PostHandlerData = context.chat_data.get("post_handler_data")
photo_len = len(post_handler_data.post_images)
@@ -282,14 +255,13 @@ class Post(Plugin.Conversation, BasePlugin.Conversation):
await message.reply_text("请选择你的操作", reply_markup=self.MENU_KEYBOARD)
return CHECK_COMMAND
- @staticmethod
- async def get_channel(update: Update, _: CallbackContext) -> int:
+ async def get_channel(self, update: Update, _: CallbackContext) -> int:
message = update.effective_message
reply_keyboard = []
try:
- for channel_info in bot.config.channels:
- name = channel_info.name
- reply_keyboard.append([f"{name}"])
+ for channel_id in config.channels:
+ chat = await self.get_chat(chat_id=channel_id)
+ reply_keyboard.append([f"{chat.username}"])
except KeyError as error:
logger.error("从配置文件获取频道信息发生错误,退出任务", exc_info=error)
await message.reply_text("从配置文件获取频道信息发生错误,退出任务", reply_markup=ReplyKeyboardRemove())
@@ -298,16 +270,16 @@ class Post(Plugin.Conversation, BasePlugin.Conversation):
return GET_POST_CHANNEL
@conversation.state(state=GET_POST_CHANNEL)
- @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
- @error_callable
+ @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=False)
async def get_post_channel(self, update: Update, context: CallbackContext) -> int:
post_handler_data: PostHandlerData = context.chat_data.get("post_handler_data")
message = update.effective_message
channel_id = -1
try:
- for channel_info in bot.config.channels:
- if message.text == channel_info.name:
- channel_id = channel_info.chat_id
+ for channel_chat_id in config.channels:
+ chat = await self.get_chat(chat_id=channel_id)
+ if message.text == chat.username:
+ channel_id = channel_chat_id
except KeyError as exc:
logger.error("从配置文件获取频道信息发生错误,退出任务", exc_info=exc)
logger.exception(exc)
@@ -328,8 +300,7 @@ class Post(Plugin.Conversation, BasePlugin.Conversation):
return GET_TAGS
@conversation.state(state=GET_TAGS)
- @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
- @error_callable
+ @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=False)
async def get_tags(self, update: Update, context: CallbackContext) -> int:
post_handler_data: PostHandlerData = context.chat_data.get("post_handler_data")
message = update.effective_message
@@ -346,8 +317,7 @@ class Post(Plugin.Conversation, BasePlugin.Conversation):
return GET_TEXT
@conversation.state(state=GET_TEXT)
- @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
- @error_callable
+ @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=False)
async def get_edit_text(self, update: Update, context: CallbackContext) -> int:
post_handler_data: PostHandlerData = context.chat_data.get("post_handler_data")
message = update.effective_message
@@ -357,8 +327,7 @@ class Post(Plugin.Conversation, BasePlugin.Conversation):
return CHECK_COMMAND
@conversation.state(state=SEND_POST)
- @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
- @error_callable
+ @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=False)
async def send_post(self, update: Update, context: CallbackContext) -> int:
post_handler_data: PostHandlerData = context.chat_data.get("post_handler_data")
message = update.effective_message
@@ -369,9 +338,10 @@ class Post(Plugin.Conversation, BasePlugin.Conversation):
channel_id = post_handler_data.channel_id
channel_name = None
try:
- for channel_info in bot.config.channels:
+ for channel_info in config.channels:
if post_handler_data.channel_id == channel_info.chat_id:
- channel_name = channel_info.name
+ chat = await self.get_chat(chat_id=channel_id)
+ channel_name = chat.username
except KeyError as exc:
logger.error("从配置文件获取频道信息发生错误,退出任务")
logger.exception(exc)
@@ -382,13 +352,13 @@ class Post(Plugin.Conversation, BasePlugin.Conversation):
for index, _ in enumerate(post_handler_data.post_images):
if index + 1 not in post_handler_data.delete_photo:
post_images.append(post_handler_data.post_images[index])
- post_text += f" @{escape_markdown(channel_name, version=2)}"
+ post_text += f" @{channel_name}"
for tag in post_handler_data.tags:
post_text += f" \\#{tag}"
try:
if len(post_images) > 1:
- media = [img_info.input_media() for img_info in post_images if img_info.format]
- media[0] = post_images[0].input_media(caption=post_text, parse_mode=ParseMode.MARKDOWN_V2)
+ media = [InputMediaPhoto(img_info.data) for img_info in post_images]
+ media[0] = InputMediaPhoto(post_images[0].data, caption=post_text, parse_mode=ParseMode.MARKDOWN_V2)
await context.bot.send_media_group(channel_id, media=media)
elif len(post_images) == 1:
image = post_images[0]
diff --git a/plugins/system/set_quiz.py b/plugins/admin/quiz.py
similarity index 86%
rename from plugins/system/set_quiz.py
rename to plugins/admin/quiz.py
index f116638..e816fa7 100644
--- a/plugins/system/set_quiz.py
+++ b/plugins/admin/quiz.py
@@ -6,13 +6,9 @@ from telegram import InlineKeyboardButton, InlineKeyboardMarkup, ReplyKeyboardMa
from telegram.ext import CallbackContext, ConversationHandler, filters
from telegram.helpers import escape_markdown
-from core.baseplugin import BasePlugin
from core.plugin import Plugin, conversation, handler
-from core.quiz import QuizService
-from core.quiz.models import Answer, Question
-from utils.decorators.admins import bot_admins_rights_check
-from utils.decorators.error import error_callable
-from utils.decorators.restricts import restricts
+from core.services.quiz.models import Answer, Question
+from core.services.quiz.services import QuizService
from utils.log import logger
(
@@ -35,7 +31,7 @@ class QuizCommandData:
status: int = 0
-class SetQuizPlugin(Plugin.Conversation, BasePlugin.Conversation):
+class SetQuizPlugin(Plugin.Conversation):
"""派蒙的十万个为什么问题修改/添加/删除"""
def __init__(self, quiz_service: QuizService = None):
@@ -43,10 +39,7 @@ class SetQuizPlugin(Plugin.Conversation, BasePlugin.Conversation):
self.time_out = 120
@conversation.entry_point
- @handler.command(command="set_quiz", filters=filters.ChatType.PRIVATE, block=True)
- @restricts()
- @bot_admins_rights_check
- @error_callable
+ @handler.command(command="set_quiz", filters=filters.ChatType.PRIVATE, block=False, admin=True)
async def command_start(self, update: Update, context: CallbackContext) -> int:
user = update.effective_user
message = update.effective_message
@@ -67,42 +60,41 @@ class SetQuizPlugin(Plugin.Conversation, BasePlugin.Conversation):
return CHECK_COMMAND
@conversation.state(state=CHECK_QUESTION)
- @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
+ @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=False)
async def check_question(self, update: Update, _: CallbackContext) -> int:
reply_keyboard = [["删除问题"], ["退出"]]
await update.message.reply_text("请选择你的操作", reply_markup=ReplyKeyboardMarkup(reply_keyboard))
return CHECK_COMMAND
@conversation.state(state=CHECK_COMMAND)
- @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
+ @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=False)
async def check_command(self, update: Update, context: CallbackContext) -> int:
quiz_command_data: QuizCommandData = context.chat_data.get("quiz_command_data")
if update.message.text == "退出":
await update.message.reply_text("退出任务", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END
- elif update.message.text == "查看问题":
+ if update.message.text == "查看问题":
return await self.view_command(update, context)
- elif update.message.text == "添加问题":
+ if update.message.text == "添加问题":
return await self.add_question(update, context)
- elif update.message.text == "删除问题":
+ if update.message.text == "删除问题":
return await self.delete_question(update, context)
# elif update.message.text == "修改问题":
# return await self.edit_question(update, context)
- elif update.message.text == "重载问题":
+ if update.message.text == "重载问题":
return await self.refresh_question(update, context)
- else:
- result = re.findall(r"问题ID (\d+)", update.message.text)
- if len(result) == 1:
- try:
- question_id = int(result[0])
- except ValueError:
- await update.message.reply_text("获取问题ID失败")
- return ConversationHandler.END
- quiz_command_data.question_id = question_id
- await update.message.reply_text("获取问题ID成功")
- return await self.check_question(update, context)
- await update.message.reply_text("命令错误", reply_markup=ReplyKeyboardRemove())
- return ConversationHandler.END
+ result = re.findall(r"问题ID (\d+)", update.message.text)
+ if len(result) == 1:
+ try:
+ question_id = int(result[0])
+ except ValueError:
+ await update.message.reply_text("获取问题ID失败")
+ return ConversationHandler.END
+ quiz_command_data.question_id = question_id
+ await update.message.reply_text("获取问题ID成功")
+ return await self.check_question(update, context)
+ await update.message.reply_text("命令错误", reply_markup=ReplyKeyboardRemove())
+ return ConversationHandler.END
async def refresh_question(self, update: Update, _: CallbackContext) -> int:
try:
@@ -128,7 +120,7 @@ class SetQuizPlugin(Plugin.Conversation, BasePlugin.Conversation):
return GET_NEW_QUESTION
@conversation.state(state=GET_NEW_QUESTION)
- @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
+ @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=False)
async def get_new_question(self, update: Update, context: CallbackContext) -> int:
message = update.effective_message
quiz_command_data: QuizCommandData = context.chat_data.get("quiz_command_data")
@@ -138,7 +130,7 @@ class SetQuizPlugin(Plugin.Conversation, BasePlugin.Conversation):
return GET_NEW_CORRECT_ANSWER
@conversation.state(state=GET_NEW_CORRECT_ANSWER)
- @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
+ @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=False)
async def get_new_correct_answer(self, update: Update, context: CallbackContext) -> int:
quiz_command_data: QuizCommandData = context.chat_data.get("quiz_command_data")
reply_text = f"正确答案:`{escape_markdown(update.message.text, version=2)}`\n" f"请填写错误答案:"
@@ -147,8 +139,8 @@ class SetQuizPlugin(Plugin.Conversation, BasePlugin.Conversation):
return GET_NEW_WRONG_ANSWER
@conversation.state(state=GET_NEW_WRONG_ANSWER)
- @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
- @handler.command(command="finish_edit", block=True)
+ @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=False)
+ @handler.command(command="finish_edit", block=False)
async def get_new_wrong_answer(self, update: Update, context: CallbackContext) -> int:
quiz_command_data: QuizCommandData = context.chat_data.get("quiz_command_data")
reply_text = (
@@ -173,13 +165,13 @@ class SetQuizPlugin(Plugin.Conversation, BasePlugin.Conversation):
return SAVE_QUESTION
@conversation.state(state=SAVE_QUESTION)
- @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
+ @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=False)
async def save_question(self, update: Update, context: CallbackContext):
quiz_command_data: QuizCommandData = context.chat_data.get("quiz_command_data")
if update.message.text == "抛弃修改并退出":
await update.message.reply_text("退出任务", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END
- elif update.message.text == "保存并重载配置":
+ if update.message.text == "保存并重载配置":
if quiz_command_data.status == 1:
answer = [
Answer(text=wrong_answer, is_correct=False) for wrong_answer in quiz_command_data.new_wrong_answer
@@ -197,9 +189,8 @@ class SetQuizPlugin(Plugin.Conversation, BasePlugin.Conversation):
return ConversationHandler.END
await update.message.reply_text("重载配置成功", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END
- else:
- await update.message.reply_text("回复错误,请重新选择")
- return SAVE_QUESTION
+ await update.message.reply_text("回复错误,请重新选择")
+ return SAVE_QUESTION
async def edit_question(self, update: Update, context: CallbackContext) -> int:
_ = self
diff --git a/plugins/system/refresh_metadata.py b/plugins/admin/refresh_metadata.py
similarity index 74%
rename from plugins/system/refresh_metadata.py
rename to plugins/admin/refresh_metadata.py
index 45823a2..a1194a4 100644
--- a/plugins/system/refresh_metadata.py
+++ b/plugins/admin/refresh_metadata.py
@@ -1,21 +1,21 @@
from telegram import Update
+from telegram.ext import CallbackContext
from core.plugin import Plugin, handler
from metadata.scripts.honey import update_honey_metadata
from metadata.scripts.metadatas import update_metadata_from_ambr, update_metadata_from_github
from metadata.scripts.paimon_moe import update_paimon_moe_zh
-from utils.decorators.admins import bot_admins_rights_check
from utils.log import logger
+__all__ = ("MetadataPlugin",)
+
class MetadataPlugin(Plugin):
- @handler.command("refresh_metadata")
- @bot_admins_rights_check
- async def refresh(self, update: Update, _) -> None:
- user = update.effective_user
+ @handler.command("refresh_metadata", admin=True)
+ async def refresh(self, update: Update, _: CallbackContext) -> None:
message = update.effective_message
-
- logger.info(f"用户 {user.full_name}[{user.id}] 刷新[bold]metadata[/]缓存命令", extra={"markup": True})
+ user = update.effective_user
+ logger.info("用户 %s[%s] 刷新[bold]metadata[/]缓存命令", user.full_name, user.id, extra={"markup": True})
msg = await message.reply_text("正在刷新元数据,请耐心等待...")
logger.info("正在从 github 上获取元数据")
diff --git a/plugins/system/search.py b/plugins/admin/search.py
similarity index 76%
rename from plugins/system/search.py
rename to plugins/admin/search.py
index 99f5b8e..0d96eda 100644
--- a/plugins/system/search.py
+++ b/plugins/admin/search.py
@@ -5,23 +5,21 @@ from telegram import Update
from telegram.ext import CallbackContext
from core.plugin import handler, Plugin, job
-from core.search.services import SearchServices
-from utils.decorators.admins import bot_admins_rights_check
-from utils.decorators.restricts import restricts
+from core.services.search.services import SearchServices
from utils.log import logger
-__all__ = []
+__all__ = ("SearchPlugin",)
class SearchPlugin(Plugin):
def __init__(self, search: SearchServices = None):
self.search = search
- self._lock = asyncio.Lock()
+ self.lock = asyncio.Lock()
- async def __async_init__(self):
+ async def initialize(self):
async def load_data():
logger.info("Search 插件模块正在加载搜索条目")
- async with self._lock:
+ async with self.lock:
await self.search.load_data()
logger.success("Search 插件加载模块搜索条目成功")
@@ -29,32 +27,28 @@ class SearchPlugin(Plugin):
@job.run_repeating(interval=datetime.timedelta(hours=1), name="SaveEntryJob")
async def save_entry_job(self, _: CallbackContext):
- if self._lock.locked():
+ if self.lock.locked():
logger.warning("条目数据正在保存 跳过本次定时任务")
else:
- async with self._lock:
+ async with self.lock:
logger.info("条目数据正在自动保存")
await self.search.save_entry()
logger.success("条目数据自动保存成功")
- @handler.command("save_entry", block=False)
- @bot_admins_rights_check
- @restricts()
+ @handler.command("save_entry", block=False, admin=True)
async def save_entry(self, update: Update, _: CallbackContext):
user = update.effective_user
message = update.effective_message
logger.info("用户 %s[%s] 保存条目数据命令请求", user.full_name, user.id)
- if self._lock.locked():
+ if self.lock.locked():
await message.reply_text("条目数据正在保存 请稍后重试")
else:
- async with self._lock:
+ async with self.lock:
reply_text = await message.reply_text("正在保存数据")
await self.search.save_entry()
await reply_text.edit_text("数据保存成功")
- @handler.command("remove_all_entry", block=False)
- @bot_admins_rights_check
- @restricts()
+ @handler.command("remove_all_entry", block=False, admin=True)
async def remove_all_entry(self, update: Update, _: CallbackContext):
user = update.effective_user
message = update.effective_message
diff --git a/plugins/admin/sign_all.py b/plugins/admin/sign_all.py
new file mode 100644
index 0000000..006ded9
--- /dev/null
+++ b/plugins/admin/sign_all.py
@@ -0,0 +1,20 @@
+from telegram import Update
+from telegram.ext import CallbackContext, CommandHandler
+
+from core.plugin import Plugin, handler
+from plugins.tools.sign import SignSystem, SignJobType
+from utils.log import logger
+
+
+class SignAll(Plugin):
+ def __init__(self, sign_system: SignSystem):
+ self.sign_system = sign_system
+
+ @handler(CommandHandler, command="sign_all", block=False, admin=True)
+ async def sign_all(self, update: Update, context: CallbackContext):
+ user = update.effective_user
+ logger.info("用户 %s[%s] sign_all 命令请求", user.full_name, user.id)
+ message = update.effective_message
+ reply = await message.reply_text("正在全部重新签到,请稍后...")
+ await self.sign_system.do_sign_job(context, job_type=SignJobType.START)
+ await reply.edit_text("全部账号重新签到完成")
diff --git a/plugins/system/sign_status.py b/plugins/admin/sign_status.py
similarity index 72%
rename from plugins/system/sign_status.py
rename to plugins/admin/sign_status.py
index 6da5805..8dcfea1 100644
--- a/plugins/system/sign_status.py
+++ b/plugins/admin/sign_status.py
@@ -1,14 +1,13 @@
from telegram import Update
-from telegram.ext import CommandHandler, CallbackContext
+from telegram.ext import CallbackContext, CommandHandler
from core.plugin import Plugin, handler
-from core.sign import SignServices
-from utils.decorators.admins import bot_admins_rights_check
+from core.services.sign.services import SignServices
from utils.log import logger
class SignStatus(Plugin):
- def __init__(self, sign_service: SignServices = None):
+ def __init__(self, sign_service: SignServices):
self.sign_service = sign_service
@staticmethod
@@ -21,11 +20,10 @@ class SignStatus(Plugin):
text = f"自动签到统计信息\n\n总人数:{len(sign_db)}
\n"
return text + "\n".join(f"{name}: {value}
" for name, value in zip(names, values))
- @handler(CommandHandler, command="sign_status", block=False)
- @bot_admins_rights_check
+ @handler(CommandHandler, command="sign_status", block=False, admin=True)
async def sign_status(self, update: Update, _: CallbackContext):
user = update.effective_user
- logger.info(f"用户 {user.full_name}[{user.id}] sign_status 命令请求")
+ logger.info("用户 %s[%s] sign_status 命令请求", user.full_name, user.id)
message = update.effective_message
text = await self.get_sign_status(self.sign_service)
await message.reply_text(text, parse_mode="html", quote=True)
diff --git a/plugins/app/__init__.py b/plugins/app/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/plugins/system/inline.py b/plugins/app/inline.py
similarity index 88%
rename from plugins/system/inline.py
rename to plugins/app/inline.py
index 5a9aae3..a1449e3 100644
--- a/plugins/system/inline.py
+++ b/plugins/app/inline.py
@@ -1,23 +1,22 @@
import asyncio
-from typing import cast, Dict, Awaitable, List
+from typing import Awaitable, Dict, List, cast
from uuid import uuid4
from telegram import (
+ InlineQuery,
InlineQueryResultArticle,
+ InlineQueryResultCachedPhoto,
InputTextMessageContent,
Update,
- InlineQuery,
- InlineQueryResultCachedPhoto,
)
from telegram.constants import ParseMode
from telegram.error import BadRequest
from telegram.ext import CallbackContext, InlineQueryHandler
-from core.base.assets import AssetsService, AssetsCouldNotFound
-from core.plugin import handler, Plugin
-from core.search.services import SearchServices
-from core.wiki import WikiService
-from utils.decorators.error import error_callable
+from core.dependence.assets import AssetsCouldNotFound, AssetsService
+from core.plugin import Plugin, handler
+from core.services.search.services import SearchServices
+from core.services.wiki.services import WikiService
from utils.log import logger
@@ -26,9 +25,9 @@ class Inline(Plugin):
def __init__(
self,
- wiki_service: WikiService = None,
- assets_service: AssetsService = None,
- search_service: SearchServices = None,
+ wiki_service: WikiService,
+ assets_service: AssetsService,
+ search_service: SearchServices,
):
self.assets_service = assets_service
self.wiki_service = wiki_service
@@ -37,7 +36,7 @@ class Inline(Plugin):
self.refresh_task: List[Awaitable] = []
self.search_service = search_service
- async def __async_init__(self):
+ async def initialize(self):
# todo: 整合进 wiki 或者单独模块 从Redis中读取
async def task_weapons():
logger.info("Inline 模块正在获取武器列表")
@@ -73,8 +72,7 @@ class Inline(Plugin):
self.refresh_task.append(asyncio.create_task(task_characters()))
@handler(InlineQueryHandler, block=False)
- @error_callable
- async def inline_query(self, update: Update, context: CallbackContext) -> None:
+ async def inline_query(self, update: Update, _: CallbackContext) -> None:
user = update.effective_user
ilq = cast(InlineQuery, update.inline_query)
query = ilq.query
@@ -100,7 +98,7 @@ class Inline(Plugin):
)
)
else:
- if "查看武器列表并查询" == args[0]:
+ if args[0] == "查看武器列表并查询":
for weapon in self.weapons_list:
name = weapon["name"]
icon = weapon["icon"]
@@ -111,11 +109,11 @@ class Inline(Plugin):
description=f"查看武器列表并查询 {name}",
thumb_url=icon,
input_message_content=InputTextMessageContent(
- f"/weapon@{context.bot.username} {name}", parse_mode=ParseMode.MARKDOWN_V2
+ f"武器查询{name}", parse_mode=ParseMode.MARKDOWN_V2
),
)
)
- elif "查看角色攻略列表并查询" == args[0]:
+ elif args[0] == "查看角色攻略列表并查询":
for character in self.characters_list:
name = character["name"]
icon = character["icon"]
@@ -126,11 +124,11 @@ class Inline(Plugin):
description=f"查看角色攻略列表并查询 {name}",
thumb_url=icon,
input_message_content=InputTextMessageContent(
- f"/strategy@{context.bot.username} {name}", parse_mode=ParseMode.MARKDOWN_V2
+ f"角色攻略查询{name}", parse_mode=ParseMode.MARKDOWN_V2
),
)
)
- elif "查看角色培养素材列表并查询" == args[0]:
+ elif args[0] == "查看角色培养素材列表并查询":
characters_list = await self.wiki_service.get_characters_name_list()
for role_name in characters_list:
results_list.append(
@@ -139,7 +137,7 @@ class Inline(Plugin):
title=role_name,
description=f"查看角色培养素材列表并查询 {role_name}",
input_message_content=InputTextMessageContent(
- f"/material@{context.bot.username} {role_name}", parse_mode=ParseMode.MARKDOWN_V2
+ f"角色培养素材查询{role_name}", parse_mode=ParseMode.MARKDOWN_V2
),
)
)
diff --git a/plugins/system/start.py b/plugins/app/start.py
similarity index 63%
rename from plugins/system/start.py
rename to plugins/app/start.py
index df2afff..d729357 100644
--- a/plugins/system/start.py
+++ b/plugins/app/start.py
@@ -1,38 +1,25 @@
from typing import Optional
-from genshin import Region, GenshinException
from telegram import Update, ReplyKeyboardRemove, Message, User, WebAppInfo, ReplyKeyboardMarkup, KeyboardButton
from telegram.constants import ChatAction
from telegram.ext import CallbackContext, CommandHandler
from telegram.helpers import escape_markdown
-from core.base.redisdb import RedisDB
from core.config import config
-from core.cookies import CookiesService
-from core.cookies.error import CookiesNotFoundError
from core.plugin import handler, Plugin
-from core.user import UserService
-from core.user.error import UserNotFoundError
-from modules.apihelper.client.components.verify import Verify
-from modules.apihelper.error import ResponseException, APIHelperException
-from plugins.genshin.sign import SignSystem, NeedChallenge
-from plugins.genshin.verification import VerificationSystem
-from utils.decorators.error import error_callable
-from utils.decorators.restricts import restricts
-from utils.helpers import get_genshin_client
+from plugins.tools.challenge import ChallengeSystem, ChallengeSystemException
+from plugins.tools.genshin import PlayerNotFoundError, CookiesNotFoundError, GenshinHelper
+from plugins.tools.sign import SignSystem, NeedChallenge
from utils.log import logger
class StartPlugin(Plugin):
- def __init__(self, user_service: UserService = None, cookies_service: CookiesService = None, redis: RedisDB = None):
- self.cookies_service = cookies_service
- self.user_service = user_service
- self.sign_system = SignSystem(redis)
- self.verification_system = VerificationSystem(redis)
+ def __init__(self, sign_system: SignSystem, challenge_system: ChallengeSystem, genshin_helper: GenshinHelper):
+ self.challenge_system = challenge_system
+ self.sign_system = sign_system
+ self.genshin_helper = genshin_helper
@handler.command("start", block=False)
- @error_callable
- @restricts()
async def start(self, update: Update, context: CallbackContext) -> None:
user = update.effective_user
message = update.effective_message
@@ -58,7 +45,7 @@ class StartPlugin(Plugin):
await self.process_validate(message, user, bot_username=context.bot.username)
elif args[0] == "sign":
logger.info("用户 %s[%s] 通过start命令 获取签到信息", user.full_name, user.id)
- await self.gen_sign_button(message, user)
+ await self.get_sign_button(message, user)
elif args[0].startswith("challenge_"):
_data = args[0].split("_")
_command = _data[1]
@@ -73,40 +60,24 @@ class StartPlugin(Plugin):
await message.reply_markdown_v2(f"你好 {user.mention_markdown_v2()} {escape_markdown('!我是派蒙 !')}")
@staticmethod
- @restricts()
async def unknown_command(update: Update, _: CallbackContext) -> None:
await update.effective_message.reply_text("前面的区域,以后再来探索吧!")
@staticmethod
- @restricts()
async def emergency_food(update: Update, _: CallbackContext) -> None:
await update.effective_message.reply_text("派蒙才不是应急食品!")
@handler(CommandHandler, command="ping", block=False)
- @restricts()
async def ping(self, update: Update, _: CallbackContext) -> None:
await update.effective_message.reply_text("online! ヾ(✿゚▽゚)ノ")
@handler(CommandHandler, command="reply_keyboard_remove", block=False)
- @restricts()
async def reply_keyboard_remove(self, update: Update, _: CallbackContext) -> None:
await update.message.reply_text("移除远程键盘成功", reply_markup=ReplyKeyboardRemove())
- async def gen_sign_button(self, message: Message, user: User):
- try:
- client = await get_genshin_client(user.id)
- await message.reply_chat_action(ChatAction.TYPING)
- button = await self.sign_system.get_challenge_button(client.uid, user.id, callback=False)
- if not button:
- await message.reply_text("验证请求已过期。", allow_sending_without_reply=True)
- return
- await message.reply_text("请尽快点击下方按钮进行验证。", allow_sending_without_reply=True, reply_markup=button)
- except (UserNotFoundError, CookiesNotFoundError):
- logger.warning("用户 %s[%s] 账号信息未找到", user.full_name, user.id)
-
async def process_sign_validate(self, message: Message, user: User, validate: str):
try:
- client = await get_genshin_client(user.id)
+ client = await self.genshin_helper.get_genshin_client(user.id)
await message.reply_chat_action(ChatAction.TYPING)
_, challenge = await self.sign_system.get_challenge(client.uid)
if not challenge:
@@ -114,57 +85,29 @@ class StartPlugin(Plugin):
return
sign_text = await self.sign_system.start_sign(client, challenge=challenge, validate=validate)
await message.reply_text(sign_text, allow_sending_without_reply=True)
- except (UserNotFoundError, CookiesNotFoundError):
+ except (PlayerNotFoundError, CookiesNotFoundError):
logger.warning("用户 %s[%s] 账号信息未找到", user.full_name, user.id)
except NeedChallenge:
await message.reply_text("回调错误,请重新签到", allow_sending_without_reply=True)
async def process_validate(self, message: Message, user: User, bot_username: Optional[str] = None):
- try:
- client = await get_genshin_client(user.id)
- if client.region != Region.CHINESE:
- await message.reply_text("非法用户")
- return
- except UserNotFoundError:
- await message.reply_text("用户未找到")
- return
- except CookiesNotFoundError:
- await message.reply_text("检测到用户为UID绑定,无需认证")
- return
- try:
- await client.get_genshin_notes()
- except GenshinException as exc:
- if exc.retcode != 1034:
- raise exc
- else:
- await message.reply_text("账户正常,无需认证")
- return
await message.reply_text(
"由于官方对第三方工具限制以及账户安全的考虑,频繁使用第三方工具会导致账号被风控并要求用过验证才能进行访问。\n"
"如果出现频繁验证请求,建议暂停使用本Bot在内的第三方工具查询功能。\n"
"在暂停使用期间依然出现频繁认证,建议修改密码以保护账号安全。"
)
- verification = Verify(cookies=client.cookie_manager.cookies)
try:
- data = await verification.create()
- challenge = data["challenge"]
- gt = data["gt"]
- logger.success("用户 %s[%s] 创建验证成功\ngt:%s\nchallenge%s", user.full_name, user.id, gt, challenge)
- except ResponseException as exc:
- logger.warning("用户 %s[%s] 创建验证失效 API返回 [%s]%s", user.full_name, user.id, exc.code, exc.message)
- await message.reply_text(f"验证失败 错误信息为 [{exc.code}]{exc.message}")
+ uid, gt, challenge = await self.challenge_system.create_challenge(user.id, ajax=True)
+ except ChallengeSystemException as exc:
+ await message.reply_text(exc.message)
return
- try:
- validate = await verification.ajax(referer="https://webstatic.mihoyo.com/", gt=gt, challenge=challenge)
- if validate:
- await verification.verify(challenge, validate)
- logger.success("用户 %s[%s] 通过 ajax 验证", user.full_name, user.id)
- await message.reply_text("验证成功")
- return
- except APIHelperException as exc:
- logger.warning("用户 %s[%s] ajax 验证失效 错误信息为 %s", user.full_name, user.id, repr(exc))
- await self.verification_system.set_challenge(client.uid, gt, challenge)
- url = f"{config.pass_challenge_user_web}/webapp?username={bot_username}&command=verify>={gt}&challenge={challenge}&uid={client.uid}"
+ if gt == "ajax":
+ await message.reply_text("验证成功")
+ return
+ url = (
+ f"{config.pass_challenge_user_web}/webapp?"
+ f"username={bot_username}&command=verify>={gt}&challenge={challenge}&uid={uid}"
+ )
await message.reply_text(
"请尽快在10秒内完成手动验证\n或发送 /web_cancel 取消操作",
reply_markup=ReplyKeyboardMarkup.from_button(
@@ -174,3 +117,15 @@ class StartPlugin(Plugin):
)
),
)
+
+ async def get_sign_button(self, message: Message, user: User):
+ try:
+ client = await self.genshin_helper.get_genshin_client(user.id)
+ await message.reply_chat_action(ChatAction.TYPING)
+ button = await self.sign_system.get_challenge_button(client.uid, user.id, callback=False)
+ if not button:
+ await message.reply_text("验证请求已过期。", allow_sending_without_reply=True)
+ return
+ await message.reply_text("请尽快点击下方按钮进行验证。", allow_sending_without_reply=True, reply_markup=button)
+ except (PlayerNotFoundError, CookiesNotFoundError):
+ logger.warning("用户 %s[%s] 账号信息未找到", user.full_name, user.id)
diff --git a/plugins/app/webapp.py b/plugins/app/webapp.py
new file mode 100644
index 0000000..376f92f
--- /dev/null
+++ b/plugins/app/webapp.py
@@ -0,0 +1,80 @@
+from typing import Optional
+
+from pydantic import BaseModel
+from telegram import ReplyKeyboardRemove, Update
+from telegram.ext import CallbackContext, filters
+
+from core.plugin import Plugin, handler
+from plugins.tools.challenge import ChallengeSystem, ChallengeSystemException
+from utils.log import logger
+
+
+class WebAppData(BaseModel):
+ path: str
+ data: Optional[dict]
+ code: int
+ message: str
+
+
+class WebAppDataException(Exception):
+ def __init__(self, data):
+ self.data = data
+ super().__init__()
+
+
+class WebApp(Plugin):
+ def __init__(
+ self,
+ challenge_system: ChallengeSystem,
+ ):
+ self.challenge_system = challenge_system
+
+ @staticmethod
+ def de_web_app_data(data: str) -> WebAppData:
+ try:
+ return WebAppData.parse_raw(data)
+ except Exception as exc:
+ raise WebAppDataException(data) from exc
+
+ @handler.message(filters=filters.StatusUpdate.WEB_APP_DATA, block=False)
+ async def app(self, update: Update, _: CallbackContext):
+ user = update.effective_user
+ message = update.effective_message
+ web_app_data = message.web_app_data
+ if web_app_data:
+ logger.info("用户 %s[%s] 触发 WEB_APP_DATA 请求", user.full_name, user.id)
+ result = self.de_web_app_data(web_app_data.data)
+ logger.debug(
+ "path[%s]\ndata[%s]\ncode[%s]\nmessage[%s]", result.path, result.data, result.code, result.message
+ )
+ if result.code == 0:
+ if result.path == "verify":
+ validate = result.data.get("geetest_validate")
+ if validate is not None:
+ try:
+ status = await self.challenge_system.pass_challenge(user.id, validate=validate)
+ except ChallengeSystemException as exc:
+ await message.reply_text(exc.message, reply_markup=ReplyKeyboardRemove())
+ return
+ if status:
+ await message.reply_text("验证通过", reply_markup=ReplyKeyboardRemove())
+ await message.reply_text("非法请求", reply_markup=ReplyKeyboardRemove())
+ return
+ else:
+ logger.warning(
+ "用户 %s[%s] WEB_APP_DATA 请求错误 [%s]%s", user.full_name, user.id, result.code, result.message
+ )
+ if result.path == "verify":
+ await message.reply_text(
+ f"验证过程中出现问题 {result.message}\n如果继续遇到该问题,请打开米游社→我的角色中尝试手动通过验证",
+ reply_markup=ReplyKeyboardRemove(),
+ )
+ else:
+ await message.reply_text(f"WebApp返回错误 {result.message}", reply_markup=ReplyKeyboardRemove())
+ else:
+ logger.warning("用户 %s[%s] WEB_APP_DATA 非法数据", user.full_name, user.id)
+
+ @handler.command("web_cancel", block=False)
+ async def web_cancel(self, update: Update, _: CallbackContext) -> None:
+ message = update.effective_message
+ await message.reply_text("取消操作", reply_markup=ReplyKeyboardRemove())
diff --git a/plugins/genshin/__init__.py b/plugins/genshin/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/plugins/genshin/abyss.py b/plugins/genshin/abyss.py
index 5066680..4a29d1e 100644
--- a/plugins/genshin/abyss.py
+++ b/plugins/genshin/abyss.py
@@ -5,7 +5,6 @@ from datetime import datetime
from functools import lru_cache, partial
from typing import Any, Coroutine, List, Match, Optional, Tuple, Union
-import ujson as json
from arkowrapper import ArkoWrapper
from genshin import Client, GenshinException
from pytz import timezone
@@ -14,21 +13,23 @@ from telegram.constants import ChatAction, ParseMode
from telegram.ext import CallbackContext, filters
from telegram.helpers import create_deep_linked_url
-from core.base.assets import AssetsService
-from core.baseplugin import BasePlugin
-from core.cookies.error import CookiesNotFoundError, TooManyRequestPublicCookies
-from core.cookies.services import CookiesService
+from core.dependence.assets import AssetsService
from core.plugin import Plugin, handler
-from core.template import TemplateService
-from core.template.models import RenderGroupResult, RenderResult
-from core.user import UserService
-from core.user.error import UserNotFoundError
+from core.services.cookies.error import TooManyRequestPublicCookies
+from core.services.template.models import RenderGroupResult, RenderResult
+from core.services.template.services import TemplateService
from metadata.genshin import game_id_to_role_id
-from utils.decorators.error import error_callable
-from utils.decorators.restricts import restricts
-from utils.helpers import async_re_sub, get_genshin_client, get_public_genshin_client
+from plugins.tools.genshin import GenshinHelper, CookiesNotFoundError, PlayerNotFoundError
+from utils.helpers import async_re_sub
from utils.log import logger
+try:
+ import ujson as jsonlib
+
+except ImportError:
+ import json as jsonlib
+
+
TZ = timezone("Asia/Shanghai")
cmd_pattern = r"^/abyss\s*((?:\d+)|(?:all))?\s*(pre)?"
msg_pattern = r"^深渊数据((?:查询)|(?:总览))(上期)?\D?(\d*)?.*?$"
@@ -56,9 +57,8 @@ def get_args(text: str) -> Tuple[int, bool, bool]:
except ValueError:
floor = 0
return floor, result[0] == "all", bool(result[1])
- else:
- result = re.match(msg_pattern, text).groups()
- return int(result[2] or 0), result[0] == "总览", result[1] == "上期"
+ result = re.match(msg_pattern, text).groups()
+ return int(result[2] or 0), result[0] == "总览", result[1] == "上期"
class AbyssUnlocked(Exception):
@@ -73,28 +73,25 @@ class AbyssNotFoundError(Exception):
"""如果查询别人,是无法找到队伍详细,只有数据统计"""
-class Abyss(Plugin, BasePlugin):
+class AbyssPlugin(Plugin):
"""深渊数据查询"""
def __init__(
self,
- user_service: UserService = None,
- cookies_service: CookiesService = None,
- template_service: TemplateService = None,
- assets_service: AssetsService = None,
+ template: TemplateService,
+ helper: GenshinHelper,
+ assets_service: AssetsService,
):
- self.template_service = template_service
- self.cookies_service = cookies_service
- self.user_service = user_service
+ self.template_service = template
+ self.helper = helper
self.assets_service = assets_service
@handler.command("abyss", block=False)
@handler.message(filters.Regex(msg_pattern), block=False)
- @restricts()
- @error_callable
async def command_start(self, update: Update, context: CallbackContext) -> None:
user = update.effective_user
message = update.effective_message
+ uid: Optional[int] = None
# 若查询帮助
if (message.text.startswith("/") and "help" in message.text) or "帮助" in message.text:
@@ -107,7 +104,7 @@ class Abyss(Plugin, BasePlugin):
"深渊数据查询
\n深渊数据查询上期第12层
\n深渊数据总览上期
",
parse_mode=ParseMode.HTML,
)
- logger.info(f"用户 {user.full_name}[{user.id}] 查询[bold]深渊挑战数据[/bold]帮助", extra={"markup": True})
+ logger.info("用户 %s[%s] 查询[bold]深渊挑战数据[/bold]帮助", user.full_name, user.id, extra={"markup": True})
return
# 解析参数
@@ -116,42 +113,45 @@ class Abyss(Plugin, BasePlugin):
if floor > 12 or floor < 0:
reply_msg = await message.reply_text("深渊层数输入错误,请重新输入。支持的参数为: 1-12 或 all")
if filters.ChatType.GROUPS.filter(message):
- self._add_delete_message_job(context, reply_msg.chat_id, reply_msg.message_id, 10)
- self._add_delete_message_job(context, message.chat_id, message.message_id, 10)
+ self.add_delete_message_job(reply_msg)
+ self.add_delete_message_job(message)
return
- elif 0 < floor < 9:
+ if 0 < floor < 9:
previous = False
logger.info(
- f"用户 {user.full_name}[{user.id}] [bold]深渊挑战数据[/bold]请求: "
- f"floor={floor} total={total} previous={previous}",
+ "用户 %s[%s] [bold]深渊挑战数据[/bold]请求: floor=%s total=%s previous=%s",
+ user.full_name,
+ user.id,
+ floor,
+ total,
+ previous,
extra={"markup": True},
)
try:
try:
- client = await get_genshin_client(user.id)
+ client = await self.helper.get_genshin_client(user.id)
await client.get_record_cards()
uid = client.uid
except CookiesNotFoundError:
- client, uid = await get_public_genshin_client(user.id)
- except UserNotFoundError: # 若未找到账号
+ client, uid = await self.helper.get_public_genshin_client(user.id)
+ except PlayerNotFoundError: # 若未找到账号
buttons = [[InlineKeyboardButton("点我绑定账号", url=create_deep_linked_url(context.bot.username, "set_uid"))]]
if filters.ChatType.GROUPS.filter(message):
reply_message = await message.reply_text(
"未查询到您所绑定的账号信息,请先私聊派蒙绑定账号", reply_markup=InlineKeyboardMarkup(buttons)
)
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 30)
-
- self._add_delete_message_job(context, message.chat_id, message.message_id, 30)
+ self.add_delete_message_job(reply_message.chat_id)
+ self.add_delete_message_job(message.chat_id)
else:
await message.reply_text("未查询到您所绑定的账号信息,请先绑定账号", reply_markup=InlineKeyboardMarkup(buttons))
return
except TooManyRequestPublicCookies:
- reply_msg = await message.reply_text("查询次数太多,请您稍后重试")
+ reply_message = await message.reply_text("查询次数太多,请您稍后重试")
if filters.ChatType.GROUPS.filter(message):
- self._add_delete_message_job(context, reply_msg.chat_id, reply_msg.message_id, 10)
- self._add_delete_message_job(context, message.chat_id, message.message_id, 10)
+ self.add_delete_message_job(reply_message.chat_id)
+ self.add_delete_message_job(message.chat_id)
return
async def reply_message_func(content: str) -> None:
@@ -170,10 +170,9 @@ class Abyss(Plugin, BasePlugin):
try:
images = await self.get_rendered_pic(client, uid, floor, total, previous)
except GenshinException as exc:
- if exc.retcode == 1034:
- if client.uid != uid:
- await message.reply_text("出错了呜呜呜 ~ 请稍后重试")
- return
+ if exc.retcode == 1034 and client.uid != uid:
+ await message.reply_text("出错了呜呜呜 ~ 请稍后重试")
+ return
raise exc
except AbyssUnlocked: # 若深渊未解锁
await reply_message_func("还未解锁深渊哦~")
@@ -201,7 +200,7 @@ class Abyss(Plugin, BasePlugin):
if reply_text is not None:
await reply_text.delete()
- logger.info(f"用户 {user.full_name}[{user.id}] [bold]深渊挑战数据[/bold]: 成功发送图片", extra={"markup": True})
+ logger.info("用户 %s[%s] [bold]深渊挑战数据[/bold]: 成功发送图片", user.full_name, user.id, extra={"markup": True})
async def get_rendered_pic(
self, client: Client, uid: int, floor: int, total: bool, previous: bool
@@ -275,7 +274,7 @@ class Abyss(Plugin, BasePlugin):
if total:
avatars = await client.get_genshin_characters(uid, lang="zh-cn")
render_data["avatar_data"] = {i.id: i.constellation for i in avatars}
- data = json.loads(result)
+ data = jsonlib.loads(result)
render_data["data"] = data
render_inputs: List[Tuple[int, Coroutine[Any, Any, RenderResult]]] = []
@@ -312,39 +311,38 @@ class Abyss(Plugin, BasePlugin):
return await asyncio.gather(*render_group_inputs)
- elif floor < 1:
- render_data["data"] = json.loads(result)
+ if floor < 1:
+ render_data["data"] = jsonlib.loads(result)
return [
await self.template_service.render(
"genshin/abyss/overview.html", render_data, viewport={"width": 750, "height": 580}
)
]
+ num_dic = {
+ "0": "",
+ "1": "一",
+ "2": "二",
+ "3": "三",
+ "4": "四",
+ "5": "五",
+ "6": "六",
+ "7": "七",
+ "8": "八",
+ "9": "九",
+ }
+ if num := num_dic.get(str(floor)):
+ render_data["floor-num"] = num
else:
- num_dic = {
- "0": "",
- "1": "一",
- "2": "二",
- "3": "三",
- "4": "四",
- "5": "五",
- "6": "六",
- "7": "七",
- "8": "八",
- "9": "九",
- }
- if num := num_dic.get(str(floor)):
- render_data["floor-num"] = num
- else:
- render_data["floor-num"] = f"十{num_dic.get(str(floor % 10))}"
- floors = json.loads(result)["floors"]
- if (floor_data := list(filter(lambda x: x["floor"] == floor, floors))) is None:
- return None
- avatars = await client.get_genshin_characters(uid, lang="zh-cn")
- render_data["avatar_data"] = {i.id: i.constellation for i in avatars}
- render_data["floor"] = floor_data[0]
- render_data["total_stars"] = f"{floor_data[0]['stars']}/{floor_data[0]['max_stars']}"
- return [
- await self.template_service.render(
- "genshin/abyss/floor.html", render_data, viewport={"width": 690, "height": 500}
- )
- ]
+ render_data["floor-num"] = f"十{num_dic.get(str(floor % 10))}"
+ floors = jsonlib.loads(result)["floors"]
+ if (floor_data := list(filter(lambda x: x["floor"] == floor, floors))) is None:
+ return None
+ avatars = await client.get_genshin_characters(uid, lang="zh-cn")
+ render_data["avatar_data"] = {i.id: i.constellation for i in avatars}
+ render_data["floor"] = floor_data[0]
+ render_data["total_stars"] = f"{floor_data[0]['stars']}/{floor_data[0]['max_stars']}"
+ return [
+ await self.template_service.render(
+ "genshin/abyss/floor.html", render_data, viewport={"width": 690, "height": 500}
+ )
+ ]
diff --git a/plugins/genshin/abyss_team.py b/plugins/genshin/abyss_team.py
index 24f987d..bc05e43 100644
--- a/plugins/genshin/abyss_team.py
+++ b/plugins/genshin/abyss_team.py
@@ -1,54 +1,50 @@
-from telegram import Update, InlineKeyboardMarkup, InlineKeyboardButton
+from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from telegram.constants import ChatAction
-from telegram.ext import CallbackContext, CommandHandler, MessageHandler, filters
+from telegram.ext import CallbackContext, filters
from telegram.helpers import create_deep_linked_url
-from core.base.assets import AssetsService
-from core.baseplugin import BasePlugin
-from core.cookies.error import CookiesNotFoundError
+from core.dependence.assets import AssetsService
from core.plugin import Plugin, handler
-from core.template import TemplateService
-from core.user import UserService
-from core.user.error import UserNotFoundError
+from core.services.template.services import TemplateService
from metadata.shortname import roleToId
from modules.apihelper.client.components.abyss import AbyssTeam as AbyssTeamClient
-from utils.decorators.error import error_callable
-from utils.decorators.restricts import restricts
-from utils.helpers import get_genshin_client
+from plugins.tools.genshin import GenshinHelper, CookiesNotFoundError, PlayerNotFoundError
from utils.log import logger
+__all__ = ("AbyssTeamPlugin",)
-class AbyssTeam(Plugin, BasePlugin):
+
+class AbyssTeamPlugin(Plugin):
"""深境螺旋推荐配队查询"""
def __init__(
- self, user_service: UserService = None, template_service: TemplateService = None, assets: AssetsService = None
+ self,
+ template: TemplateService,
+ helper: GenshinHelper,
+ assets_service: AssetsService,
):
- self.template_service = template_service
- self.user_service = user_service
- self.assets_service = assets
+ self.template_service = template
+ self.helper = helper
self.team_data = AbyssTeamClient()
+ self.assets_service = assets_service
- @handler(CommandHandler, command="abyss_team", block=False)
- @handler(MessageHandler, filters=filters.Regex("^深渊推荐配队(.*)"), block=False)
- @restricts()
- @error_callable
+ @handler.command("abyss_team", block=False)
+ @handler.message(filters.Regex("^深渊推荐配队(.*)"), block=False)
async def command_start(self, update: Update, context: CallbackContext) -> None:
user = update.effective_user
message = update.effective_message
- logger.info(f"用户 {user.full_name}[{user.id}] 查深渊推荐配队命令请求")
+ logger.info("用户 %s[%s] 查深渊推荐配队命令请求", user.full_name, user.id)
try:
- client = await get_genshin_client(user.id)
- except (CookiesNotFoundError, UserNotFoundError):
+ client = await self.helper.get_genshin_client(user.id)
+ except (CookiesNotFoundError, PlayerNotFoundError):
buttons = [[InlineKeyboardButton("点我绑定账号", url=create_deep_linked_url(context.bot.username, "set_cookie"))]]
if filters.ChatType.GROUPS.filter(message):
reply_message = await message.reply_text(
"未查询到您所绑定的账号信息,请先私聊派蒙绑定账号", reply_markup=InlineKeyboardMarkup(buttons)
)
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 30)
-
- self._add_delete_message_job(context, message.chat_id, message.message_id, 30)
+ self.add_delete_message_job(reply_message, delay=30)
+ self.add_delete_message_job(message, delay=30)
else:
await message.reply_text("未查询到您所绑定的账号信息,请先绑定账号", reply_markup=InlineKeyboardMarkup(buttons))
return
diff --git a/plugins/genshin/avatar_list.py b/plugins/genshin/avatar_list.py
index a2d1df0..b251aa0 100644
--- a/plugins/genshin/avatar_list.py
+++ b/plugins/genshin/avatar_list.py
@@ -1,33 +1,30 @@
"""练度统计"""
import asyncio
-from typing import Iterable, List, Optional, Sequence
+from typing import List, Optional, Sequence
from aiohttp import ClientConnectorError
from arkowrapper import ArkoWrapper
from enkanetwork import Assets as EnkaAssets, EnkaNetworkAPI, VaildateUIDError, HTTPException, EnkaPlayerNotFound
from genshin import Client, GenshinException, InvalidCookies
from genshin.models import CalculatorCharacterDetails, CalculatorTalent, Character
-from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Message, Update, User
+from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update, User
from telegram.constants import ChatAction, ParseMode
from telegram.ext import CallbackContext, filters
from telegram.helpers import create_deep_linked_url
-from core.base.assets import AssetsService
-from core.base.redisdb import RedisDB
-from core.baseplugin import BasePlugin
from core.config import config
-from core.cookies.error import CookiesNotFoundError
-from core.cookies.services import CookiesService
+from core.dependence.assets import AssetsService
+from core.dependence.redisdb import RedisDB
from core.plugin import Plugin, handler
-from core.template import TemplateService
-from core.template.models import FileType
-from core.user.error import UserNotFoundError
+from core.services.cookies import CookiesService
+from core.services.players import PlayersService
+from core.services.players.services import PlayerInfoService
+from core.services.template.models import FileType
+from core.services.template.services import TemplateService
from metadata.genshin import AVATAR_DATA, NAMECARD_DATA
from modules.wiki.base import Model
-from utils.decorators.error import error_callable
-from utils.decorators.restricts import restricts
+from plugins.tools.genshin import CookiesNotFoundError, GenshinHelper, PlayerNotFoundError, CharacterDetails
from utils.enkanetwork import RedisCache
-from utils.helpers import get_genshin_client
from utils.log import logger
from utils.patch.aiohttp import AioHttpTimeoutException
@@ -49,18 +46,22 @@ class AvatarData(Model):
def sum_of_skills(self) -> int:
total_level = 0
- for skilldata in self.skills:
- total_level += skilldata.skill.level
+ for skill_data in self.skills:
+ total_level += skill_data.skill.level
return total_level
-class AvatarListPlugin(Plugin, BasePlugin):
+class AvatarListPlugin(Plugin):
def __init__(
self,
+ player_service: PlayersService = None,
cookies_service: CookiesService = None,
assets_service: AssetsService = None,
template_service: TemplateService = None,
redis: RedisDB = None,
+ helper: GenshinHelper = None,
+ character_details: CharacterDetails = None,
+ player_info_service: PlayerInfoService = None,
) -> None:
self.cookies_service = cookies_service
self.assets_service = assets_service
@@ -68,31 +69,36 @@ class AvatarListPlugin(Plugin, BasePlugin):
self.enka_client = EnkaNetworkAPI(lang="chs", user_agent=config.enka_network_api_agent)
self.enka_client.set_cache(RedisCache(redis.client, key="plugin:avatar_list:enka_network", ttl=60 * 60 * 3))
self.enka_assets = EnkaAssets(lang="chs")
+ self.helper = helper
+ self.character_details = character_details
+ self.player_service = player_service
+ self.player_info_service = player_info_service
- async def get_user_client(self, user: User, message: Message, context: CallbackContext) -> Optional[Client]:
+ async def get_user_client(self, update: Update, context: CallbackContext) -> Optional[Client]:
+ message = update.effective_message
+ user = update.effective_user
try:
- return await get_genshin_client(user.id)
- except UserNotFoundError: # 若未找到账号
+ return await self.helper.get_genshin_client(user.id)
+ except PlayerNotFoundError: # 若未找到账号
buttons = [[InlineKeyboardButton("点我绑定账号", url=create_deep_linked_url(context.bot.username, "set_cookie"))]]
if filters.ChatType.GROUPS.filter(message):
reply_message = await message.reply_text(
"未查询到您所绑定的账号信息,请先私聊派蒙绑定账号", reply_markup=InlineKeyboardMarkup(buttons)
)
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 30)
-
- self._add_delete_message_job(context, message.chat_id, message.message_id, 30)
+ self.add_delete_message_job(reply_message, delay=30)
+ self.add_delete_message_job(message, delay=30)
else:
await message.reply_text("未查询到您所绑定的账号信息,请先绑定账号", reply_markup=InlineKeyboardMarkup(buttons))
except CookiesNotFoundError:
buttons = [[InlineKeyboardButton("点我绑定账号", url=create_deep_linked_url(context.bot.username, "set_cookie"))]]
if filters.ChatType.GROUPS.filter(message):
- reply_msg = await message.reply_text(
+ reply_message = await message.reply_text(
"此功能需要绑定cookie
后使用,请先私聊派蒙绑定账号",
reply_markup=InlineKeyboardMarkup(buttons),
parse_mode=ParseMode.HTML,
)
- self._add_delete_message_job(context, reply_msg.chat_id, reply_msg.message_id, 30)
- self._add_delete_message_job(context, message.chat_id, message.message_id, 30)
+ self.add_delete_message_job(reply_message, delay=30)
+ self.add_delete_message_job(message, delay=30)
else:
await message.reply_text(
"此功能需要绑定cookie
后使用,请先私聊派蒙进行绑定",
@@ -101,21 +107,8 @@ class AvatarListPlugin(Plugin, BasePlugin):
)
async def get_avatar_data(self, character: Character, client: Client) -> Optional["AvatarData"]:
- for _ in range(5):
- try:
- detail = await client.get_character_details(character)
- except Exception as exc: # pylint: disable=W0703
- if isinstance(exc, GenshinException) and "Too Many Requests" in exc.msg:
- await asyncio.sleep(0.2)
- continue
- if character.name == "旅行者":
- logger.debug("解析旅行者数据时遇到了错误:%s", str(exc))
- return None
- raise exc
- else:
- break
- else:
- logger.warning("解析[bold]%s[/]的数据时遇到了 Too Many Requests 错误", character.name, extra={"markup": True})
+ detail = await self.character_details.get_character_details(client, character)
+ if detail is None:
return None
if character.id == 10000005: # 针对男草主
talents = []
@@ -197,8 +190,9 @@ class AvatarListPlugin(Plugin, BasePlugin):
ArkoWrapper(choices)
.map(lambda x: next(filter(lambda y: y["name"].split("·")[0] == x.name, NAMECARD_DATA.values()), None))
.filter(lambda x: x)
- .map(lambda x: x["id"])
+ .map(lambda x: int(x["id"]))
)
+ # noinspection PyTypeChecker
name_card = (await self.assets_service.namecard(name_card_choices[0]).navbar()).as_uri()
avatar = (await self.assets_service.avatar(cid := choices[0].id).icon()).as_uri()
nickname = update.effective_user.full_name
@@ -208,21 +202,32 @@ class AvatarListPlugin(Plugin, BasePlugin):
rarity = {k: v["rank"] for k, v in AVATAR_DATA.items()}[str(cid)]
return name_card, avatar, nickname, rarity
- async def get_default_final_data(self, characters: Sequence[Character], update: Update):
- nickname = update.effective_user.full_name
- rarity = 5
- # 须弥·正明
- name_card = (await self.assets_service.namecard(210132).navbar()).as_uri()
- if traveller := next(filter(lambda x: x.id in [10000005, 10000007], characters), None):
- avatar = (await self.assets_service.avatar(traveller.id).icon()).as_uri()
- else:
- avatar = (await self.assets_service.avatar(10000005).icon()).as_uri()
+ async def get_default_final_data(self, player_id: int, characters: Sequence[Character], user: User):
+ player = await self.player_service.get(user.id, player_id)
+ player_info = await self.player_info_service.get(player)
+ nickname = user.full_name
+ name_card: Optional[str] = None
+ avatar: Optional[str] = None
+ rarity: int = 5
+ if player_info is not None:
+ if player_info.nickname is not None:
+ nickname = player_info.nickname
+ if player_info.name_card is not None:
+ name_card = (await self.assets_service.namecard(player_info.name_card).navbar()).as_uri()
+ if player_info.hand_image is not None:
+ avatar = (await self.assets_service.avatar(player_info.hand_image).icon()).as_uri()
+ rarity = {k: v["rank"] for k, v in AVATAR_DATA.items()}[str(player_info.hand_image)]
+ if name_card is not None: # 须弥·正明
+ name_card = (await self.assets_service.namecard(210132).navbar()).as_uri()
+ if avatar is not None:
+ if traveller := next(filter(lambda x: x.id in [10000005, 10000007], characters), None):
+ avatar = (await self.assets_service.avatar(traveller.id).icon()).as_uri()
+ else:
+ avatar = (await self.assets_service.avatar(10000005).icon()).as_uri()
return name_card, avatar, nickname, rarity
@handler.command("avatars", filters.Regex(r"^/avatars\s*(?:(\d+)|(all))?$"), block=False)
@handler.message(filters.Regex(r"^(全部)?练度统计$"), block=False)
- @restricts(30)
- @error_callable
async def avatar_list(self, update: Update, context: CallbackContext):
user = update.effective_user
message = update.effective_message
@@ -233,7 +238,7 @@ class AvatarListPlugin(Plugin, BasePlugin):
logger.info("用户 %s[%s] [bold]练度统计[/bold]: all=%s", user.full_name, user.id, all_avatars, extra={"markup": True})
- client = await self.get_user_client(user, message, context)
+ client = await self.get_user_client(update, context)
if not client:
return
@@ -251,22 +256,22 @@ class AvatarListPlugin(Plugin, BasePlugin):
logger.warning("用户 %s[%s] 无法请求角色数数据 API返回信息为 [%s]%s", user.full_name, user.id, exc.retcode, exc.original)
reply_message = await message.reply_text("出错了呜呜呜 ~ 当前访问令牌无法请求角色数数据,请尝试重新获取Cookie。")
if filters.ChatType.GROUPS.filter(message):
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 30)
- self._add_delete_message_job(context, message.chat_id, message.message_id, 30)
+ self.add_delete_message_job(reply_message, delay=30)
+ self.add_delete_message_job(message, delay=30)
return
except GenshinException as e:
await notice.delete()
if e.retcode == -502002:
reply_message = await message.reply_html("请先在米游社中使用一次养成计算器后再使用此功能~")
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 20)
+ self.add_delete_message_job(reply_message, delay=20)
return
raise e
try:
name_card, avatar, nickname, rarity = await self.get_final_data(client, characters, update)
- except Exception as exc:
+ except Exception as exc: # pylint: disable=W0703
logger.error("卡片信息请求失败 %s", str(exc))
- name_card, avatar, nickname, rarity = await self.get_default_final_data(characters, update)
+ name_card, avatar, nickname, rarity = await self.get_default_final_data(client.uid, characters, user)
render_data = {
"uid": client.uid, # 玩家uid
@@ -291,7 +296,7 @@ class AvatarListPlugin(Plugin, BasePlugin):
file_type=FileType.DOCUMENT if as_document else FileType.PHOTO,
ttl=30 * 24 * 60 * 60,
)
- self._add_delete_message_job(context, notice.chat_id, notice.message_id, 5)
+ self.add_delete_message_job(notice, delay=5)
if as_document:
await image.reply_document(message, filename="练度统计.png")
else:
diff --git a/plugins/genshin/birthday.py b/plugins/genshin/birthday.py
index 000cad9..072d45c 100644
--- a/plugins/genshin/birthday.py
+++ b/plugins/genshin/birthday.py
@@ -5,28 +5,21 @@ from typing import List, Optional
from genshin import Client, GenshinException
from genshin.client.routes import Route
from genshin.utility import recognize_genshin_server
-from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
+from telegram import Update, InlineKeyboardMarkup, InlineKeyboardButton
from telegram.constants import ParseMode
-from telegram.ext import CommandHandler, CallbackContext, MessageHandler
-from telegram.ext import filters
+from telegram.ext import filters, MessageHandler, CommandHandler, CallbackContext
from telegram.helpers import create_deep_linked_url
-from core.baseplugin import BasePlugin
-from core.cookies import CookiesService
-from core.cookies.error import CookiesNotFoundError
+from core.basemodel import RegionEnum
from core.plugin import Plugin, handler
-from core.user import UserService
-from core.user.error import UserNotFoundError
+from core.services.cookies import CookiesService
+from core.services.users.services import UserService
from metadata.genshin import AVATAR_DATA
from metadata.shortname import roleToId, roleToName
from modules.apihelper.client.components.calendar import Calendar
-from utils.bot import get_args
-from utils.decorators.error import error_callable
-from utils.decorators.restricts import restricts
+from plugins.tools.genshin import GenshinHelper
from utils.genshin import fetch_hk4e_token_by_cookie, recognize_genshin_game_biz
-from utils.helpers import get_genshin_client
from utils.log import logger
-from utils.models.base import RegionEnum
BIRTHDAY_URL = Route(
"https://hk4e-api.mihoyo.com/event/birthdaystar/account/post_my_draw",
@@ -40,18 +33,20 @@ def rm_starting_str(string, starting):
return string
-class BirthdayPlugin(Plugin, BasePlugin):
+class BirthdayPlugin(Plugin):
"""Birthday."""
def __init__(
self,
- user_service: UserService = None,
- cookie_service: CookiesService = None,
+ user_service: UserService,
+ helper: GenshinHelper,
+ cookie_service: CookiesService,
):
"""Load Data."""
self.birthday_list = {}
self.user_service = user_service
self.cookie_service = cookie_service
+ self.helper = helper
async def __async_init__(self):
self.birthday_list = await Calendar.async_gen_birthday_list()
@@ -65,10 +60,8 @@ class BirthdayPlugin(Plugin, BasePlugin):
)
return (self.birthday_list.get(key, [])).copy()
- @handler(CommandHandler, command="birthday", block=False)
- @restricts()
- @error_callable
- async def command_start(self, update: Update, context: CallbackContext) -> None:
+ @handler.command(command="birthday", block=False)
+ async def command_start(self, update: Update, _: CallbackContext) -> None:
message = update.effective_message
user = update.effective_user
key = (
@@ -76,10 +69,11 @@ class BirthdayPlugin(Plugin, BasePlugin):
+ "_"
+ rm_starting_str(datetime.now().strftime("%d"), "0")
)
- args = get_args(context)
+ args = self.get_args()
+
if len(args) >= 1:
msg = args[0]
- logger.info(f"用户 {user.full_name}[{user.id}] 查询角色生日命令请求 || 参数 {msg}")
+ logger.info("用户 %s[%s] 查询角色生日命令请求 || 参数 %s", user.full_name, user.id, msg)
if re.match(r"\d{1,2}.\d{1,2}", msg):
try:
month = rm_starting_str(re.findall(r"\d+", msg)[0], "0")
@@ -91,9 +85,7 @@ class BirthdayPlugin(Plugin, BasePlugin):
except IndexError:
text = "请输入正确的日期格式,如1-1,或输入正确的角色名称。"
reply_message = await message.reply_text(text)
- if filters.ChatType.GROUPS.filter(reply_message):
- self._add_delete_message_job(context, message.chat_id, message.message_id)
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id)
+
else:
try:
if msg == "派蒙":
@@ -106,22 +98,19 @@ class BirthdayPlugin(Plugin, BasePlugin):
birthday = AVATAR_DATA[aid]["birthday"]
text = f"{name} 的生日是 {birthday[0]}月{birthday[1]}日 哦~"
reply_message = await message.reply_text(text)
- if filters.ChatType.GROUPS.filter(reply_message):
- self._add_delete_message_job(context, message.chat_id, message.message_id)
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id)
+
except KeyError:
reply_message = await message.reply_text("请输入正确的日期格式,如1-1,或输入正确的角色名称。")
- if filters.ChatType.GROUPS.filter(reply_message):
- self._add_delete_message_job(context, message.chat_id, message.message_id)
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id)
+
else:
- logger.info(f"用户 {user.full_name}[{user.id}] 查询今日角色生日列表")
+ logger.info("用户 %s[%s] 查询今日角色生日列表", user.full_name, user.id)
today_list = await self.get_today_birthday()
text = f"今天是 {'、'.join(today_list)} 的生日哦~" if today_list else "今天没有角色过生日哦~"
reply_message = await message.reply_text(text)
- if filters.ChatType.GROUPS.filter(reply_message):
- self._add_delete_message_job(context, message.chat_id, message.message_id)
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id)
+
+ if filters.ChatType.GROUPS.filter(reply_message):
+ self.add_delete_message_job(message)
+ self.add_delete_message_job(reply_message)
@staticmethod
async def get_card(client: Client, role_id: int) -> None:
@@ -147,8 +136,6 @@ class BirthdayPlugin(Plugin, BasePlugin):
@handler(CommandHandler, command="birthday_card", block=False)
@handler(MessageHandler, filters=filters.Regex("^领取角色生日画片$"), block=False)
- @restricts()
- @error_callable
async def command_birthday_card_start(self, update: Update, context: CallbackContext) -> None:
message = update.effective_message
user = update.effective_user
@@ -157,31 +144,11 @@ class BirthdayPlugin(Plugin, BasePlugin):
if not today_list:
reply_message = await message.reply_text("今天没有角色过生日哦~")
if filters.ChatType.GROUPS.filter(reply_message):
- self._add_delete_message_job(context, message.chat_id, message.message_id)
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id)
+ self.add_delete_message_job(message)
+ self.add_delete_message_job(reply_message)
return
- try:
- client = await get_genshin_client(user.id)
- if client.region == RegionEnum.HOYOLAB:
- text = "此功能当前只支持国服账号哦~"
- else:
- await fetch_hk4e_token_by_cookie(client)
- for name in today_list.copy():
- if role_id := self.role_to_id(name):
- try:
- await self.get_card(client, role_id)
- except GenshinException as e:
- if e.retcode in {-512008, -512009}: # 未过生日、已领取过
- today_list.remove(name)
- if today_list:
- text = f"成功领取了 {'、'.join(today_list)} 的生日画片~"
- else:
- text = "没有领取到生日画片哦 ~ 可能是已经领取过了"
- reply_message = await message.reply_text(text)
- if filters.ChatType.GROUPS.filter(reply_message):
- self._add_delete_message_job(context, message.chat_id, message.message_id)
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id)
- except (UserNotFoundError, CookiesNotFoundError):
+ client = await self.helper.get_genshin_client(user.id)
+ if client is None:
buttons = [[InlineKeyboardButton("点我绑定账号", url=create_deep_linked_url(context.bot.username, "set_cookie"))]]
if filters.ChatType.GROUPS.filter(message):
reply_msg = await message.reply_text(
@@ -189,11 +156,31 @@ class BirthdayPlugin(Plugin, BasePlugin):
reply_markup=InlineKeyboardMarkup(buttons),
parse_mode=ParseMode.HTML,
)
- self._add_delete_message_job(context, reply_msg.chat_id, reply_msg.message_id, 30)
- self._add_delete_message_job(context, message.chat_id, message.message_id, 30)
+ self.add_delete_message_job(reply_msg.chat_id, delay=30)
+ self.add_delete_message_job(message, delay=30)
else:
await message.reply_text(
"此功能需要绑定cookie
后使用,请先私聊派蒙进行绑定",
parse_mode=ParseMode.HTML,
reply_markup=InlineKeyboardMarkup(buttons),
)
+ return
+ if client.region == RegionEnum.HOYOLAB:
+ text = "此功能当前只支持国服账号哦~"
+ else:
+ await fetch_hk4e_token_by_cookie(client)
+ for name in today_list.copy():
+ if role_id := self.role_to_id(name):
+ try:
+ await self.get_card(client, role_id)
+ except GenshinException as e:
+ if e.retcode in {-512008, -512009}: # 未过生日、已领取过
+ today_list.remove(name)
+ if today_list:
+ text = f"成功领取了 {'、'.join(today_list)} 的生日画片~"
+ else:
+ text = "没有领取到生日画片哦 ~ 可能是已经领取过了"
+ reply_message = await message.reply_text(text)
+ if filters.ChatType.GROUPS.filter(reply_message):
+ self.add_delete_message_job(message.chat_id)
+ self.add_delete_message_job(reply_message.chat_id)
diff --git a/plugins/genshin/calendar.py b/plugins/genshin/calendar.py
index 19176ad..3a9d3f7 100644
--- a/plugins/genshin/calendar.py
+++ b/plugins/genshin/calendar.py
@@ -5,14 +5,11 @@ from telegram import Update
from telegram.constants import ChatAction
from telegram.ext import CallbackContext, MessageHandler, filters
-from core.base.assets import AssetsService
-from core.base.redisdb import RedisDB
-from core.baseplugin import BasePlugin
+from core.dependence.assets import AssetsService
+from core.dependence.redisdb import RedisDB
from core.plugin import Plugin, handler
-from core.template import TemplateService
+from core.services.template.services import TemplateService
from modules.apihelper.client.components.calendar import Calendar
-from utils.decorators.error import error_callable
-from utils.decorators.restricts import restricts
from utils.log import logger
try:
@@ -21,14 +18,14 @@ except ImportError:
import json as jsonlib
-class CalendarPlugin(Plugin, BasePlugin):
+class CalendarPlugin(Plugin):
"""活动日历查询"""
def __init__(
self,
- template_service: TemplateService = None,
- assets_service: AssetsService = None,
- redis: RedisDB = None,
+ template_service: TemplateService,
+ assets_service: AssetsService,
+ redis: RedisDB,
):
self.template_service = template_service
self.assets_service = assets_service
@@ -46,8 +43,6 @@ class CalendarPlugin(Plugin, BasePlugin):
@handler.command("calendar", block=False)
@handler(MessageHandler, filters=filters.Regex(r"^(活动)+(日历|日历列表)$"), block=False)
- @restricts()
- @error_callable
async def command_start(self, update: Update, _: CallbackContext) -> None:
user = update.effective_user
message = update.effective_message
diff --git a/plugins/genshin/daily/material.py b/plugins/genshin/daily/material.py
index 311bce2..bec87cc 100644
--- a/plugins/genshin/daily/material.py
+++ b/plugins/genshin/daily/material.py
@@ -24,19 +24,12 @@ from telegram.constants import ChatAction, ParseMode
from telegram.error import RetryAfter, TimedOut
from telegram.ext import CallbackContext
-from core.base.assets import AssetsCouldNotFound, AssetsService, AssetsServiceType
-from core.baseplugin import BasePlugin
-from core.cookies.error import CookiesNotFoundError
+from core.dependence.assets import AssetsCouldNotFound, AssetsService, AssetsServiceType
from core.plugin import Plugin, handler
-from core.template import TemplateService
-from core.template.models import FileType, RenderGroupResult
-from core.user.error import UserNotFoundError
+from core.services.template.models import FileType, RenderGroupResult
+from core.services.template.services import TemplateService
from metadata.genshin import AVATAR_DATA, HONEY_DATA
-from utils.bot import get_args
-from utils.decorators.admins import bot_admins_rights_check
-from utils.decorators.error import error_callable
-from utils.decorators.restricts import restricts
-from utils.helpers import get_genshin_client
+from plugins.tools.genshin import GenshinHelper, PlayerNotFoundError, CookiesNotFoundError, CharacterDetails
from utils.log import logger
INTERVAL = 1
@@ -99,18 +92,26 @@ def get_material_serial_name(names: Iterable[str]) -> str:
return result
-class DailyMaterial(Plugin, BasePlugin):
+class DailyMaterial(Plugin):
"""每日素材表"""
data: DATA_TYPE
locks: Tuple[Lock] = (Lock(), Lock())
- def __init__(self, assets: AssetsService, template_service: TemplateService):
+ def __init__(
+ self,
+ assets: AssetsService,
+ template_service: TemplateService,
+ helper: GenshinHelper,
+ character_details: CharacterDetails,
+ ):
self.assets_service = assets
self.template_service = template_service
+ self.helper = helper
+ self.character_details = character_details
self.client = AsyncClient()
- async def __async_init__(self):
+ async def initialize(self):
"""插件在初始化时,会检查一下本地是否缓存了每日素材的数据"""
data = None
@@ -128,30 +129,10 @@ class DailyMaterial(Plugin, BasePlugin):
data = json.loads(await file.read())
self.data = data
- @staticmethod
- async def _get_skills_data(client: Client, character: Character) -> Optional[List[int]]:
- """获取角色技能的数据"""
- for _ in range(5):
- try:
- detail = await client.get_character_details(character)
- except Exception as e: # pylint: disable=W0703
- if isinstance(e, GenshinException):
- # 如果是 Too Many Requests 异常,则等待一段时间后重试
- if "Too Many Requests" in e.msg:
- await asyncio.sleep(0.2)
- continue
- # 如果是其他异常,则直接抛出
- raise e
- else:
- break
- else:
- # 如果重试了5次都失败了,则直接返回 None
- logger.warning(
- "daily_material 解析角色 id 为 [bold]%s[/]的数据时遇到了 Too Many Requests 错误", character.id, extra={"markup": True}
- )
+ async def _get_skills_data(self, client: Client, character: Character) -> Optional[List[int]]:
+ detail = await self.character_details.get_character_details(client, character)
+ if detail is None:
return None
- # 不用针对旅行者、草主进行特殊处理,因为输入数据不会有旅行者。
- # 不用计算命座加成,因为这个是展示天赋升级情况,10 级为最高。计算命座会引起混淆。
talents = [t for t in detail.talents if t.type in ["attack", "skill", "burst"]]
return [t.level for t in talents]
@@ -160,7 +141,7 @@ class DailyMaterial(Plugin, BasePlugin):
user_data = {"avatar": [], "weapon": []}
try:
logger.debug("尝试获取已绑定的原神账号")
- client = await get_genshin_client(user.id)
+ client = await self.helper.get_genshin_client(user.id)
logger.debug("获取账号数据成功: UID=%s", client.uid)
characters = await client.get_genshin_characters(client.uid)
for character in characters:
@@ -195,7 +176,7 @@ class DailyMaterial(Plugin, BasePlugin):
c_path=(await self.assets_service.avatar(cid).side()).as_uri(),
)
)
- except (UserNotFoundError, CookiesNotFoundError):
+ except (PlayerNotFoundError, CookiesNotFoundError):
logger.info("未查询到用户 %s[%s] 所绑定的账号信息", user.full_name, user.id)
except InvalidCookies:
logger.info("用户 %s[%s] 所绑定的账号信息已失效", user.full_name, user.id)
@@ -206,12 +187,10 @@ class DailyMaterial(Plugin, BasePlugin):
return None, user_data
@handler.command("daily_material", block=False)
- @restricts(restricts_time_of_groups=20, without_overlapping=True)
- @error_callable
async def daily_material(self, update: Update, context: CallbackContext):
user = update.effective_user
message = update.effective_message
- args = get_args(context)
+ args = self.get_args(context)
now = datetime.now()
try:
@@ -235,7 +214,7 @@ class DailyMaterial(Plugin, BasePlugin):
if self.locks[0].locked(): # 若检测到了第一个锁:正在下载每日素材表的数据
notice = await message.reply_text("派蒙正在摘抄每日素材表,以后再来探索吧~")
- self._add_delete_message_job(context, notice.chat_id, notice.message_id, 5)
+ self.add_delete_message_job(notice, delay=5)
return
if self.locks[1].locked(): # 若检测到了第二个锁:正在下载角色、武器、材料的图标
@@ -281,7 +260,7 @@ class DailyMaterial(Plugin, BasePlugin):
except GenshinException as e:
if e.retcode == -502002:
calculator_sync = False # 发现角色养成计算器没启用 设置状态为 False 并防止下次继续获取
- self._add_delete_message_job(context, notice.chat_id, notice.message_id, 5)
+ self.add_delete_message_job(notice, delay=5)
await notice.edit_text(
"获取角色天赋信息失败,如果想要显示角色天赋信息,请先在米游社/HoYoLab中使用一次养成计算器后再使用此功能~",
parse_mode=ParseMode.HTML,
@@ -350,7 +329,7 @@ class DailyMaterial(Plugin, BasePlugin):
),
)
- self._add_delete_message_job(context, notice.chat_id, notice.message_id, 5)
+ self.add_delete_message_job(notice, delay=5)
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
character_img_data.filename = f"{title}可培养角色.png"
@@ -361,7 +340,6 @@ class DailyMaterial(Plugin, BasePlugin):
logger.debug("角色、武器培养素材图发送成功")
@handler.command("refresh_daily_material", block=False)
- @bot_admins_rights_check
async def refresh(self, update: Update, context: CallbackContext):
user = update.effective_user
message = update.effective_message
@@ -369,11 +347,11 @@ class DailyMaterial(Plugin, BasePlugin):
logger.info("用户 {%s}[%s] 刷新[bold]每日素材[/]缓存命令", user.full_name, user.id, extra={"markup": True})
if self.locks[0].locked():
notice = await message.reply_text("派蒙还在抄每日素材表呢,我有在好好工作哦~")
- self._add_delete_message_job(context, notice.chat_id, notice.message_id, 10)
+ self.add_delete_message_job(notice, delay=10)
return
if self.locks[1].locked():
notice = await message.reply_text("派蒙正在搬运每日素材图标,在努力工作呢!")
- self._add_delete_message_job(context, notice.chat_id, notice.message_id, 10)
+ self.add_delete_message_job(notice, delay=10)
return
async with self.locks[1]: # 锁住第二把锁
notice = await message.reply_text("派蒙正在重新摘抄每日素材表,请稍等~", parse_mode=ParseMode.HTML)
diff --git a/plugins/genshin/daily_note.py b/plugins/genshin/daily_note.py
index e88cd16..021fd1a 100644
--- a/plugins/genshin/daily_note.py
+++ b/plugins/genshin/daily_note.py
@@ -1,52 +1,45 @@
import datetime
-import os
+from datetime import datetime
from typing import Optional
+import genshin
from genshin import DataNotPublic
-from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
+from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from telegram.constants import ChatAction
-from telegram.ext import CommandHandler, MessageHandler, ConversationHandler, filters, CallbackContext
+from telegram.ext import ConversationHandler, filters, CallbackContext
from telegram.helpers import create_deep_linked_url
-from core.baseplugin import BasePlugin
-from core.cookies.error import CookiesNotFoundError
-from core.cookies.services import CookiesService
from core.plugin import Plugin, handler
-from core.template.services import RenderResult, TemplateService
-from core.user.error import UserNotFoundError
-from core.user.services import UserService
-from utils.decorators.error import error_callable
-from utils.decorators.restricts import restricts
-from utils.helpers import get_genshin_client
+from core.services.template.models import RenderResult
+from core.services.template.services import TemplateService
+from plugins.tools.genshin import GenshinHelper, CookiesNotFoundError, PlayerNotFoundError
from utils.log import logger
+__all__ = ("DailyNotePlugin",)
-class DailyNote(Plugin, BasePlugin):
+
+class DailyNotePlugin(Plugin):
"""每日便签"""
def __init__(
self,
- user_service: UserService = None,
- cookies_service: CookiesService = None,
- template_service: TemplateService = None,
+ template: TemplateService,
+ helper: GenshinHelper,
):
- self.template_service = template_service
- self.cookies_service = cookies_service
- self.user_service = user_service
- self.current_dir = os.getcwd()
+ self.template_service = template
+ self.helper = helper
- async def _get_daily_note(self, client) -> RenderResult:
+ async def _get_daily_note(self, client: genshin.Client) -> RenderResult:
daily_info = await client.get_genshin_notes(client.uid)
- day = datetime.datetime.now().strftime("%m-%d %H:%M") + " 星期" + "一二三四五六日"[datetime.datetime.now().weekday()]
+
+ day = datetime.now().strftime("%m-%d %H:%M") + " 星期" + "一二三四五六日"[datetime.now().weekday()]
resin_recovery_time = (
daily_info.resin_recovery_time.strftime("%m-%d %H:%M")
if daily_info.max_resin - daily_info.current_resin
else None
)
realm_recovery_time = (
- (datetime.datetime.now().astimezone() + daily_info.remaining_realm_currency_recovery_time).strftime(
- "%m-%d %H:%M"
- )
+ (datetime.now().astimezone() + daily_info.remaining_realm_currency_recovery_time).strftime("%m-%d %H:%M")
if daily_info.max_realm_currency - daily_info.current_realm_currency
else None
)
@@ -58,13 +51,15 @@ class DailyNote(Plugin, BasePlugin):
else:
remained_time = i.remaining_time
if remained_time:
- remained_time = (datetime.datetime.now().astimezone() + remained_time).strftime("%m-%d %H:%M")
+ remained_time = (datetime.now().astimezone() + remained_time).strftime("%m-%d %H:%M")
+
transformer, transformer_ready, transformer_recovery_time = False, None, None
if daily_info.remaining_transformer_recovery_time is not None:
transformer = True
transformer_ready = daily_info.remaining_transformer_recovery_time.total_seconds() == 0
transformer_recovery_time = daily_info.transformer_recovery_time.strftime("%m-%d %H:%M")
- daily_data = {
+
+ render_data = {
"uid": client.uid,
"day": day,
"resin_recovery_time": resin_recovery_time,
@@ -87,38 +82,49 @@ class DailyNote(Plugin, BasePlugin):
"transformer_recovery_time": transformer_recovery_time,
}
render_result = await self.template_service.render(
- "genshin/daily_note/daily_note.html", daily_data, {"width": 600, "height": 548}, full_page=False, ttl=8 * 60
+ "genshin/daily_note/daily_note.html",
+ render_data,
+ {"width": 600, "height": 548},
+ full_page=False,
+ ttl=8 * 60,
)
return render_result
- @handler(CommandHandler, command="dailynote", block=False)
- @handler(MessageHandler, filters=filters.Regex("^当前状态(.*)"), block=False)
- @restricts(30)
- @error_callable
- async def command_start(self, update: Update, context: CallbackContext) -> Optional[int]:
- user = update.effective_user
+ @handler.command("dailynote", block=False)
+ @handler.message(filters.Regex("^当前状态(.*)"), block=False)
+ async def command_start(self, update: Update, _: CallbackContext) -> Optional[int]:
message = update.effective_message
- logger.info(f"用户 {user.full_name}[{user.id}] 查询游戏状态命令请求")
+ user = update.effective_user
+ logger.info("用户 %s[%s] 每日便签命令请求", user.full_name, user.id)
+
try:
- client = await get_genshin_client(user.id)
+ # 获取当前用户的 genshin.Client
+ client = await self.helper.get_genshin_client(user.id)
+ # 渲染
render_result = await self._get_daily_note(client)
- except (UserNotFoundError, CookiesNotFoundError):
- buttons = [[InlineKeyboardButton("点我绑定账号", url=create_deep_linked_url(context.bot.username, "set_cookie"))]]
+ except (CookiesNotFoundError, PlayerNotFoundError):
+ buttons = [
+ [
+ InlineKeyboardButton(
+ "点我绑定账号", url=create_deep_linked_url(self.application.bot.username, "set_cookie")
+ )
+ ]
+ ]
if filters.ChatType.GROUPS.filter(message):
reply_message = await message.reply_text(
"未查询到您所绑定的账号信息,请先私聊派蒙绑定账号", reply_markup=InlineKeyboardMarkup(buttons)
)
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 30)
-
- self._add_delete_message_job(context, message.chat_id, message.message_id, 30)
+ self.add_delete_message_job(reply_message, delay=30)
+ self.add_delete_message_job(message, delay=30)
else:
await message.reply_text("未查询到您所绑定的账号信息,请先绑定账号", reply_markup=InlineKeyboardMarkup(buttons))
return
except DataNotPublic:
reply_message = await message.reply_text("查询失败惹,可能是便签功能被禁用了?请尝试通过米游社或者 hoyolab 获取一次便签信息后重试。")
if filters.ChatType.GROUPS.filter(message):
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 300)
- self._add_delete_message_job(context, message.chat_id, message.message_id, 300)
+ self.add_delete_message_job(reply_message, delay=30)
+ self.add_delete_message_job(message, delay=30)
return ConversationHandler.END
+
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
await render_result.reply_photo(message, filename=f"{client.uid}.png", allow_sending_without_reply=True)
diff --git a/plugins/genshin/help.py b/plugins/genshin/help.py
index cd7edaa..5170383 100644
--- a/plugins/genshin/help.py
+++ b/plugins/genshin/help.py
@@ -1,13 +1,13 @@
from telegram import Update
from telegram.constants import ChatAction
-from telegram.ext import CommandHandler, CallbackContext
+from telegram.ext import CallbackContext
from core.plugin import Plugin, handler
-from core.template import TemplateService
-from utils.decorators.error import error_callable
-from utils.decorators.restricts import restricts
+from core.services.template.services import TemplateService
from utils.log import logger
+__all__ = ("HelpPlugin",)
+
class HelpPlugin(Plugin):
def __init__(self, template_service: TemplateService = None):
@@ -15,17 +15,15 @@ class HelpPlugin(Plugin):
raise ModuleNotFoundError
self.template_service = template_service
- @handler(CommandHandler, command="help", block=False)
- @error_callable
- @restricts()
- async def start(self, update: Update, context: CallbackContext):
- user = update.effective_user
+ @handler.command(command="help", block=False)
+ async def start(self, update: Update, _: CallbackContext):
message = update.effective_message
+ user = update.effective_user
logger.info("用户 %s[%s] 发出help命令", user.full_name, user.id)
await message.reply_chat_action(ChatAction.TYPING)
render_result = await self.template_service.render(
"bot/help/help.html",
- {"bot_username": context.bot.username},
+ {"bot_username": self.application.bot.username},
{"width": 1280, "height": 900},
ttl=30 * 24 * 60 * 60,
)
diff --git a/plugins/genshin/help_raw.py b/plugins/genshin/help_raw.py
new file mode 100644
index 0000000..9121121
--- /dev/null
+++ b/plugins/genshin/help_raw.py
@@ -0,0 +1,38 @@
+import os
+from typing import Optional
+
+import aiofiles
+from bs4 import BeautifulSoup
+from telegram import Update
+from telegram.ext import CallbackContext
+
+from core.plugin import Plugin, handler
+from utils.log import logger
+
+__all__ = ("HelpRawPlugin",)
+
+
+class HelpRawPlugin(Plugin):
+ def __init__(self):
+ self.help_raw: Optional[str] = None
+
+ async def initialize(self):
+ file_path = os.path.join(os.getcwd(), "resources", "bot", "help", "help.html") # resources/bot/help/help.html
+ async with aiofiles.open(file_path, mode="r", encoding="utf-8") as f:
+ html_content = await f.read()
+ soup = BeautifulSoup(html_content, "lxml")
+ command_div = soup.find_all("div", _class="command")
+ for div in command_div:
+ command_name_div = div.find("div", _class="command_name")
+ if command_name_div:
+ command_description_div = div.find("div", _class="command-description")
+ if command_description_div:
+ self.help_raw += f"/{command_name_div.text} - {command_description_div}"
+
+ @handler.command(command="help_raw", block=False)
+ async def start(self, update: Update, _: CallbackContext):
+ if self.help_raw is not None:
+ message = update.effective_message
+ user = update.effective_user
+ logger.info("用户 %s[%s] 发出 help_raw 命令", user.full_name, user.id)
+ await message.reply_text(self.help_raw, allow_sending_without_reply=True)
diff --git a/plugins/genshin/hilichurls.py b/plugins/genshin/hilichurls.py
index dadfa82..3ecd73b 100644
--- a/plugins/genshin/hilichurls.py
+++ b/plugins/genshin/hilichurls.py
@@ -1,14 +1,11 @@
-from os import sep
+from typing import Dict
+from aiofiles import open as async_open
from telegram import Update
-from telegram.ext import CommandHandler, CallbackContext
-from telegram.ext import filters
+from telegram.ext import CallbackContext, filters
-from core.baseplugin import BasePlugin
from core.plugin import Plugin, handler
-from utils.bot import get_args
-from utils.decorators.error import error_callable
-from utils.decorators.restricts import restricts
+from utils.const import RESOURCE_DIR
from utils.log import logger
try:
@@ -17,37 +14,39 @@ try:
except ImportError:
import json as jsonlib
+__all__ = ("HilichurlsPlugin",)
-class HilichurlsPlugin(Plugin, BasePlugin):
+
+class HilichurlsPlugin(Plugin):
"""丘丘语字典."""
- def __init__(self):
- """加载数据文件.数据整理自 https://wiki.biligame.com/ys By @zhxycn."""
- with open(f"resources{sep}json{sep}hilichurls_dictionary.json", "r", encoding="utf8") as f:
- self.hilichurls_dictionary = jsonlib.load(f)
+ hilichurls_dictionary: Dict[str, str]
- @handler(CommandHandler, command="hilichurls", block=False)
- @restricts()
- @error_callable
+ async def initialize(self) -> None:
+ """加载数据文件.数据整理自 https://wiki.biligame.com/ys By @zhxycn."""
+ async with async_open(RESOURCE_DIR / "json/hilichurls_dictionary.json", encoding="utf-8") as file:
+ self.hilichurls_dictionary = jsonlib.loads(await file.read())
+
+ @handler.command(command="hilichurls", block=False)
async def command_start(self, update: Update, context: CallbackContext) -> None:
message = update.effective_message
user = update.effective_user
- args = get_args(context)
+ args = self.get_args(context)
if len(args) >= 1:
msg = args[0]
else:
reply_message = await message.reply_text("请输入要查询的丘丘语。")
if filters.ChatType.GROUPS.filter(reply_message):
- self._add_delete_message_job(context, message.chat_id, message.message_id)
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id)
+ self.add_delete_message_job(message)
+ self.add_delete_message_job(reply_message)
return
search = str.casefold(msg) # 忽略大小写以方便查询
if search not in self.hilichurls_dictionary:
reply_message = await message.reply_text(f"在丘丘语字典中未找到 {msg}。")
if filters.ChatType.GROUPS.filter(reply_message):
- self._add_delete_message_job(context, message.chat_id, message.message_id)
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id)
+ self.add_delete_message_job(message)
+ self.add_delete_message_job(reply_message)
return
- logger.info(f"用户 {user.full_name}[{user.id}] 查询丘丘语字典命令请求 || 参数 {msg}")
+ logger.info("用户 %s[%s] 查询今日角色生日列表 查询丘丘语字典命令请求 || 参数 %s", user.full_name, user.id, msg)
result = self.hilichurls_dictionary[f"{search}"]
await message.reply_markdown_v2(f"丘丘语: `{search}`\n\n`{result}`")
diff --git a/plugins/genshin/ledger.py b/plugins/genshin/ledger.py
index 1336b69..68d5a9c 100644
--- a/plugins/genshin/ledger.py
+++ b/plugins/genshin/ledger.py
@@ -2,75 +2,38 @@ import os
import re
from datetime import datetime, timedelta
-from genshin import GenshinException, DataNotPublic, InvalidCookies
-from telegram import Update, InlineKeyboardMarkup, InlineKeyboardButton
+from genshin import DataNotPublic, InvalidCookies, GenshinException
+from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from telegram.constants import ChatAction
-from telegram.ext import CallbackContext, CommandHandler, MessageHandler, filters
+from telegram.ext import filters, CallbackContext
from telegram.helpers import create_deep_linked_url
-from core.baseplugin import BasePlugin
-from core.cookies.error import CookiesNotFoundError
-from core.cookies.services import CookiesService
from core.plugin import Plugin, handler
-from core.template.services import RenderResult, TemplateService
-from core.user.error import UserNotFoundError
-from core.user.services import UserService
-from utils.bot import get_args
-from utils.decorators.error import error_callable
-from utils.decorators.restricts import restricts
-from utils.helpers import get_genshin_client
+from core.services.cookies import CookiesService
+from core.services.template.models import RenderResult
+from core.services.template.services import TemplateService
+from plugins.tools.genshin import CookiesNotFoundError, GenshinHelper, PlayerNotFoundError
from utils.log import logger
-
-def get_now() -> datetime:
- now = datetime.now()
- return (now - timedelta(days=1)) if now.day == 1 and now.hour <= 4 else now
+__all__ = ("LedgerPlugin",)
-def check_ledger_month(context: CallbackContext) -> int:
- now_time = get_now()
- month = now_time.month
- args = get_args(context)
- if len(args) >= 1:
- month = args[0].replace("月", "")
- if re_data := re.findall(r"\d+", str(month)):
- month = int(re_data[0])
- else:
- num_dict = {"一": 1, "二": 2, "三": 3, "四": 4, "五": 5, "六": 6, "七": 7, "八": 8, "九": 9, "十": 10}
- month = sum(num_dict.get(i, 0) for i in str(month))
- # check right
- allow_month = [now_time.month]
- last_month = now_time.replace(day=1) - timedelta(days=1)
- allow_month.append(last_month.month)
- last_month = last_month.replace(day=1) - timedelta(days=1)
- allow_month.append(last_month.month)
-
- if month in allow_month:
- return month
- elif isinstance(month, int):
- raise IndexError
- return now_time.month
-
-
-class Ledger(Plugin, BasePlugin):
- """旅行札记"""
+class LedgerPlugin(Plugin):
+ """旅行札记查询"""
def __init__(
self,
- user_service: UserService = None,
- cookies_service: CookiesService = None,
- template_service: TemplateService = None,
+ helper: GenshinHelper,
+ cookies_service: CookiesService,
+ template_service: TemplateService,
):
self.template_service = template_service
self.cookies_service = cookies_service
- self.user_service = user_service
self.current_dir = os.getcwd()
+ self.helper = helper
async def _start_get_ledger(self, client, month=None) -> RenderResult:
- try:
- diary_info = await client.get_diary(client.uid, month=month)
- except GenshinException as error:
- raise error
+ diary_info = await client.get_diary(client.uid, month=month)
color = ["#73a9c6", "#d56565", "#70b2b4", "#bd9a5a", "#739970", "#7a6da7", "#597ea0"]
categories = [
{
@@ -104,25 +67,46 @@ class Ledger(Plugin, BasePlugin):
)
return render_result
- @handler(CommandHandler, command="ledger", block=False)
- @handler(MessageHandler, filters=filters.Regex("^旅行札记查询(.*)"), block=False)
- @restricts()
- @error_callable
+ @handler.command(command="ledger", block=False)
+ @handler.message(filters=filters.Regex("^旅行札记查询(.*)"), block=False)
async def command_start(self, update: Update, context: CallbackContext) -> None:
user = update.effective_user
message = update.effective_message
+
+ now = datetime.now()
+ now_time = (now - timedelta(days=1)) if now.day == 1 and now.hour <= 4 else now
+ month = now_time.month
try:
- month = check_ledger_month(context)
+ args = self.get_args(context)
+ if len(args) >= 1:
+ month = args[0].replace("月", "")
+ if re_data := re.findall(r"\d+", str(month)):
+ month = int(re_data[0])
+ else:
+ num_dict = {"一": 1, "二": 2, "三": 3, "四": 4, "五": 5, "六": 6, "七": 7, "八": 8, "九": 9, "十": 10}
+ month = sum(num_dict.get(i, 0) for i in str(month))
+ # check right
+ allow_month = [now_time.month]
+
+ last_month = now_time.replace(day=1) - timedelta(days=1)
+ allow_month.append(last_month.month)
+
+ last_month = last_month.replace(day=1) - timedelta(days=1)
+ allow_month.append(last_month.month)
+
+ if month not in allow_month and isinstance(month, int):
+ raise IndexError
+ month = now_time.month
except IndexError:
reply_message = await message.reply_text("仅可查询最新三月的数据,请重新输入")
if filters.ChatType.GROUPS.filter(message):
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 30)
- self._add_delete_message_job(context, message.chat_id, message.message_id, 30)
+ self.add_delete_message_job(reply_message, delay=30)
+ self.add_delete_message_job(message, delay=30)
return
logger.info("用户 %s[%s] 查询旅行札记", user.full_name, user.id)
await message.reply_chat_action(ChatAction.TYPING)
try:
- client = await get_genshin_client(user.id)
+ client = await self.helper.get_genshin_client(user.id)
try:
render_result = await self._start_get_ledger(client, month)
except InvalidCookies as exc: # 如果抛出InvalidCookies 判断是否真的玄学过期(或权限不足?)
@@ -132,26 +116,31 @@ class Ledger(Plugin, BasePlugin):
)
reply_message = await message.reply_text("出错了呜呜呜 ~ 当前访问令牌无法请求角色数数据,请尝试重新获取Cookie。")
if filters.ChatType.GROUPS.filter(message):
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 30)
- self._add_delete_message_job(context, message.chat_id, message.message_id, 30)
+ self.add_delete_message_job(reply_message, delay=30)
+ self.add_delete_message_job(message, delay=30)
return
- except (UserNotFoundError, CookiesNotFoundError):
- buttons = [[InlineKeyboardButton("点我绑定账号", url=create_deep_linked_url(context.bot.username, "set_cookie"))]]
+ except (PlayerNotFoundError, CookiesNotFoundError):
+ buttons = [
+ [
+ InlineKeyboardButton(
+ "点我绑定账号", url=create_deep_linked_url(self.application.bot.username, "set_cookie")
+ )
+ ]
+ ]
if filters.ChatType.GROUPS.filter(message):
reply_message = await message.reply_text(
"未查询到您所绑定的账号信息,请先私聊派蒙绑定账号", reply_markup=InlineKeyboardMarkup(buttons)
)
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 30)
-
- self._add_delete_message_job(context, message.chat_id, message.message_id, 30)
+ self.add_delete_message_job(reply_message, delay=30)
+ self.add_delete_message_job(message, delay=30)
else:
await message.reply_text("未查询到您所绑定的账号信息,请先绑定账号", reply_markup=InlineKeyboardMarkup(buttons))
return
except DataNotPublic:
reply_message = await message.reply_text("查询失败惹,可能是旅行札记功能被禁用了?请先通过米游社或者 hoyolab 获取一次旅行札记后重试。")
if filters.ChatType.GROUPS.filter(message):
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 30)
- self._add_delete_message_job(context, message.chat_id, message.message_id, 30)
+ self.add_delete_message_job(reply_message, delay=30)
+ self.add_delete_message_job(message, delay=30)
return
except GenshinException as exc:
if exc.retcode == -120:
diff --git a/plugins/genshin/map.py b/plugins/genshin/map.py
index 6a0878e..c74952d 100644
--- a/plugins/genshin/map.py
+++ b/plugins/genshin/map.py
@@ -5,21 +5,17 @@ from telegram import Update, Message, InputMediaDocument, InputMediaPhoto, Inlin
from telegram.constants import ChatAction
from telegram.ext import CommandHandler, MessageHandler, filters, CallbackContext, CallbackQueryHandler
-from core.base.redisdb import RedisDB
-from core.baseplugin import BasePlugin
from core.config import config
+from core.dependence.redisdb import RedisDB
from core.plugin import handler, Plugin
from modules.apihelper.client.components.map import MapHelper, MapException
-from utils.decorators.admins import bot_admins_rights_check
-from utils.decorators.error import error_callable
-from utils.decorators.restricts import restricts
from utils.log import logger
-class Map(Plugin, BasePlugin):
+class Map(Plugin):
"""资源点查询"""
- def __init__(self, redis: RedisDB = None):
+ def __init__(self, redis: RedisDB):
self.cache = redis.client
self.cache_photo_key = "plugin:map:photo:"
self.cache_doc_key = "plugin:map:doc:"
@@ -120,8 +116,6 @@ class Map(Plugin, BasePlugin):
@handler(CommandHandler, command="map", block=False)
@handler(MessageHandler, filters=filters.Regex("^(?Pcookie
后使用,请先私聊派蒙进行绑定",
diff --git a/plugins/genshin/sign.py b/plugins/genshin/sign.py
index 9796ca5..bcb0746 100644
--- a/plugins/genshin/sign.py
+++ b/plugins/genshin/sign.py
@@ -1,325 +1,50 @@
-import asyncio
import datetime
-import random
-import time
-from json import JSONDecodeError
from typing import Optional, Tuple
-from genshin import Game, GenshinException, AlreadyClaimed, Client
-from genshin.utility import recognize_genshin_server
-from httpx import AsyncClient, TimeoutException
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.constants import ChatAction
from telegram.ext import CommandHandler, CallbackContext, CallbackQueryHandler
from telegram.ext import MessageHandler, filters
from telegram.helpers import create_deep_linked_url
-from core.admin.services import BotAdminService
-from core.base.redisdb import RedisDB
-from core.baseplugin import BasePlugin
-from core.bot import bot
from core.config import config
-from core.cookies.error import CookiesNotFoundError
-from core.cookies.services import CookiesService
from core.plugin import Plugin, handler
-from core.sign.models import Sign as SignUser, SignStatusEnum
-from core.sign.services import SignServices
-from core.user.error import UserNotFoundError
-from core.user.services import UserService
-from modules.apihelper.client.components.verify import Verify
-from utils.bot import get_args
-from utils.decorators.error import error_callable
-from utils.decorators.restricts import restricts
-from utils.helpers import get_genshin_client
+from core.services.sign.models import Sign as SignUser, SignStatusEnum
+from core.services.sign.services import SignServices
+from core.services.users.services import UserAdminService
+from plugins.tools.genshin import GenshinHelper, CookiesNotFoundError, PlayerNotFoundError
+from plugins.tools.sign import SignSystem, NeedChallenge
from utils.log import logger
-class NeedChallenge(Exception):
- def __init__(self, uid: int, gt: str = "", challenge: str = ""):
- super().__init__()
- self.uid = uid
- self.gt = gt
- self.challenge = challenge
-
-
-class SignSystem:
- REFERER = (
- "https://webstatic.mihoyo.com/bbs/event/signin-ys/index.html?"
- "bbs_auth_required=true&act_id=e202009291139501&utm_source=bbs&utm_medium=mys&utm_campaign=icon"
- )
-
- def __init__(self, redis: RedisDB):
- self.cache = redis.client
- self.qname = "plugin:sign:"
- self.verify = Verify()
-
- async def get_challenge(self, uid: int) -> Tuple[Optional[str], Optional[str]]:
- data = await self.cache.get(f"{self.qname}{uid}")
- if not data:
- return None, None
- data = data.decode("utf-8").split("|")
- return data[0], data[1]
-
- async def set_challenge(self, uid: int, gt: str, challenge: str):
- await self.cache.set(f"{self.qname}{uid}", f"{gt}|{challenge}")
- await self.cache.expire(f"{self.qname}{uid}", 10 * 60)
-
- async def get_challenge_button(
- self, uid: int, user_id: int, gt: Optional[str] = None, challenge: Optional[str] = None, callback: bool = True
- ) -> Optional[InlineKeyboardMarkup]:
- if not config.pass_challenge_user_web:
- return None
- if challenge and gt:
- await self.set_challenge(uid, gt, challenge)
- if not challenge or not gt:
- gt, challenge = await self.get_challenge(uid)
- if not challenge or not gt:
- return None
- if callback:
- data = f"sign|{user_id}|{uid}"
- return InlineKeyboardMarkup([[InlineKeyboardButton("请尽快点我进行手动验证", callback_data=data)]])
- else:
- url = f"{config.pass_challenge_user_web}?username={bot.app.bot.username}&command=sign>={gt}&challenge={challenge}&uid={uid}"
- return InlineKeyboardMarkup([[InlineKeyboardButton("请尽快点我进行手动验证", url=url)]])
-
- async def recognize(self, gt: str, challenge: str, referer: str = None) -> Optional[str]:
- if not referer:
- referer = self.REFERER
- if not gt or not challenge:
- return None
- pass_challenge_params = {
- "gt": gt,
- "challenge": challenge,
- "referer": referer,
- }
- if config.pass_challenge_app_key:
- pass_challenge_params["appkey"] = config.pass_challenge_app_key
- headers = {
- "Accept": "*/*",
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
- "Chrome/107.0.0.0 Safari/537.36",
- }
- try:
- async with AsyncClient(headers=headers) as client:
- resp = await client.post(
- config.pass_challenge_api,
- params=pass_challenge_params,
- timeout=60,
- )
- logger.debug("recognize 请求返回:%s", resp.text)
- data = resp.json()
- status = data.get("status")
- if status != 0:
- logger.error("recognize 解析错误:[%s]%s", data.get("code"), data.get("msg"))
- if data.get("code", 0) != 0:
- raise RuntimeError
- logger.info("recognize 解析成功")
- return data["data"]["validate"]
- except JSONDecodeError:
- logger.warning("recognize 请求 JSON 解析失败")
- except TimeoutException as exc:
- logger.warning("recognize 请求超时")
- raise exc
- except KeyError:
- logger.warning("recognize 请求数据错误")
- except RuntimeError:
- logger.warning("recognize 请求失败")
- return None
-
- async def start_sign(
- self,
- client: Client,
- challenge: Optional[str] = None,
- validate: Optional[str] = None,
- is_sleep: bool = False,
- is_raise: bool = False,
- title: Optional[str] = "签到结果",
- ) -> str:
- if is_sleep:
- if recognize_genshin_server(client.uid) in ("cn_gf01", "cn_qd01"):
- await asyncio.sleep(random.randint(10, 300)) # nosec
- else:
- await asyncio.sleep(random.randint(0, 3)) # nosec
- try:
- rewards = await client.get_monthly_rewards(game=Game.GENSHIN, lang="zh-cn")
- except GenshinException as error:
- logger.warning("UID[%s] 获取签到信息失败,API返回信息为 %s", client.uid, str(error))
- if is_raise:
- raise error
- return f"获取签到信息失败,API返回信息为 {str(error)}"
- try:
- daily_reward_info = await client.get_reward_info(game=Game.GENSHIN, lang="zh-cn") # 获取签到信息失败
- except GenshinException as error:
- logger.warning("UID[%s] 获取签到状态失败,API返回信息为 %s", client.uid, str(error))
- if is_raise:
- raise error
- return f"获取签到状态失败,API返回信息为 {str(error)}"
- if not daily_reward_info.signed_in:
- try:
- if validate:
- logger.info("UID[%s] 正在尝试通过验证码\nchallenge[%s]\nvalidate[%s]", client.uid, challenge, validate)
- request_daily_reward = await client.request_daily_reward(
- "sign",
- method="POST",
- game=Game.GENSHIN,
- lang="zh-cn",
- challenge=challenge,
- validate=validate,
- )
- logger.debug("request_daily_reward 返回 %s", request_daily_reward)
- if request_daily_reward and request_daily_reward.get("success", 0) == 1:
- # 尝试通过 ajax 请求绕过签到
- gt = request_daily_reward.get("gt", "")
- challenge = request_daily_reward.get("challenge", "")
- logger.warning("UID[%s] 触发验证码\ngt[%s]\nchallenge[%s]", client.uid, gt, challenge)
- validate = await self.verify.ajax(
- referer=self.REFERER,
- gt=gt,
- challenge=challenge,
- )
- if validate:
- logger.success("ajax 通过验证成功\nchallenge[%s]\nvalidate[%s]", challenge, validate)
- request_daily_reward = await client.request_daily_reward(
- "sign",
- method="POST",
- game=Game.GENSHIN,
- lang="zh-cn",
- challenge=challenge,
- validate=validate,
- )
- logger.debug("request_daily_reward 返回 %s", request_daily_reward)
- if request_daily_reward and request_daily_reward.get("success", 0) == 1:
- logger.warning("UID[%s] 触发验证码\nchallenge[%s]", client.uid, challenge)
- raise NeedChallenge(
- uid=client.uid,
- gt=request_daily_reward.get("gt", ""),
- challenge=request_daily_reward.get("challenge", ""),
- )
- elif config.pass_challenge_app_key:
- # 如果无法绕过 检查配置文件是否配置识别 API 尝试请求绕过
- # 注意 需要重新获取没有进行任何请求的 Challenge
- logger.info("UID[%s] 正在使用 recognize 重新请求签到", client.uid)
- _request_daily_reward = await client.request_daily_reward(
- "sign",
- method="POST",
- game=Game.GENSHIN,
- lang="zh-cn",
- )
- logger.debug("request_daily_reward 返回\n%s", _request_daily_reward)
- if _request_daily_reward and _request_daily_reward.get("success", 0) == 1:
- _gt = _request_daily_reward.get("gt", "")
- _challenge = _request_daily_reward.get("challenge", "")
- logger.info("UID[%s] 创建验证码\ngt[%s]\nchallenge[%s]", client.uid, _gt, _challenge)
- _validate = await self.recognize(_gt, _challenge)
- if _validate:
- logger.success("recognize 通过验证成功\nchallenge[%s]\nvalidate[%s]", _challenge, _validate)
- request_daily_reward = await client.request_daily_reward(
- "sign",
- method="POST",
- game=Game.GENSHIN,
- lang="zh-cn",
- challenge=_challenge,
- validate=_validate,
- )
- if request_daily_reward and request_daily_reward.get("success", 0) == 1:
- logger.warning("UID[%s] 触发验证码\nchallenge[%s]", client.uid, _challenge)
- gt = request_daily_reward.get("gt", "")
- challenge = request_daily_reward.get("challenge", "")
- logger.success("UID[%s] 创建验证成功\ngt[%s]\nchallenge[%s]", client.uid, gt, challenge)
- raise NeedChallenge(
- uid=client.uid,
- gt=gt,
- challenge=challenge,
- )
- else:
- logger.success("UID[%s] 通过 recognize 签到成功", client.uid)
- else:
- request_daily_reward = await client.request_daily_reward(
- "sign", method="POST", game=Game.GENSHIN, lang="zh-cn"
- )
- gt = request_daily_reward.get("gt", "")
- challenge = request_daily_reward.get("challenge", "")
- logger.success("UID[%s] 创建验证成功\ngt[%s]\nchallenge[%s]", client.uid, gt, challenge)
- raise NeedChallenge(uid=client.uid, gt=gt, challenge=challenge)
- else:
- request_daily_reward = await client.request_daily_reward(
- "sign", method="POST", game=Game.GENSHIN, lang="zh-cn"
- )
- gt = request_daily_reward.get("gt", "")
- challenge = request_daily_reward.get("challenge", "")
- logger.success("UID[%s] 创建验证成功\ngt[%s]\nchallenge[%s]", client.uid, gt, challenge)
- raise NeedChallenge(uid=client.uid, gt=gt, challenge=challenge)
- else:
- logger.success("UID[%s] 签到成功", client.uid)
- except TimeoutException as error:
- logger.warning("UID[%s] 签到请求超时", client.uid)
- if is_raise:
- raise error
- return "签到失败了呜呜呜 ~ 服务器连接超时 服务器熟啦 ~ "
- except AlreadyClaimed as error:
- logger.warning("UID[%s] 已经签到", client.uid)
- if is_raise:
- raise error
- result = "今天旅行者已经签到过了~"
- except GenshinException as error:
- logger.warning("UID %s 签到失败,API返回信息为 %s", client.uid, str(error))
- if is_raise:
- raise error
- return f"获取签到状态失败,API返回信息为 {str(error)}"
- else:
- result = "OK"
- else:
- logger.info("UID[%s] 已经签到", client.uid)
- result = "今天旅行者已经签到过了~"
- logger.info("UID[%s] 签到结果 %s", client.uid, result)
- reward = rewards[daily_reward_info.claimed_rewards - (1 if daily_reward_info.signed_in else 0)]
- today = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
- cn_timezone = datetime.timezone(datetime.timedelta(hours=8))
- now = datetime.datetime.now(cn_timezone)
- missed_days = now.day - daily_reward_info.claimed_rewards
- if not daily_reward_info.signed_in:
- missed_days -= 1
- message = (
- f"#### {title} ####\n"
- f"时间:{today} (UTC+8)\n"
- f"UID: {client.uid}\n"
- f"今日奖励: {reward.name} × {reward.amount}\n"
- f"本月漏签次数:{missed_days}\n"
- f"签到结果: {result}"
- )
- return message
-
-
-class Sign(Plugin, BasePlugin):
+class Sign(Plugin):
"""每日签到"""
CHECK_SERVER, COMMAND_RESULT = range(10400, 10402)
def __init__(
self,
- redis: RedisDB = None,
- user_service: UserService = None,
- cookies_service: CookiesService = None,
- sign_service: SignServices = None,
- bot_admin_service: BotAdminService = None,
+ genshin_helper: GenshinHelper,
+ sign_service: SignServices,
+ user_admin_service: UserAdminService,
+ sign_system: SignSystem,
):
- self.bot_admin_service = bot_admin_service
- self.cookies_service = cookies_service
- self.user_service = user_service
+ self.user_admin_service = user_admin_service
self.sign_service = sign_service
- self.system = SignSystem(redis)
+ self.sign_system = sign_system
+ self.genshin_helper = genshin_helper
async def _process_auto_sign(self, user_id: int, chat_id: int, method: str) -> str:
try:
- await get_genshin_client(user_id)
- except (UserNotFoundError, CookiesNotFoundError):
+ await self.genshin_helper.get_genshin_client(user_id)
+ except (PlayerNotFoundError, CookiesNotFoundError):
return "未查询到账号信息,请先私聊派蒙绑定账号"
user: SignUser = await self.sign_service.get_by_user_id(user_id)
if user:
if method == "关闭":
await self.sign_service.remove(user)
return "关闭自动签到成功"
- elif method == "开启":
+ if method == "开启":
if user.chat_id == chat_id:
return "自动签到已经开启过了"
user.chat_id = chat_id
@@ -340,18 +65,15 @@ class Sign(Plugin, BasePlugin):
@handler(CommandHandler, command="sign", block=False)
@handler(MessageHandler, filters=filters.Regex("^每日签到(.*)"), block=False)
- @restricts()
- @error_callable
async def command_start(self, update: Update, context: CallbackContext) -> None:
user = update.effective_user
message = update.effective_message
- args = get_args(context)
+ args = self.get_args(context)
validate: Optional[str] = None
if len(args) >= 1:
msg = None
if args[0] == "开启自动签到":
- admin_list = await self.bot_admin_service.get_admin_list()
- if user.id in admin_list:
+ if await self.user_admin_service.is_admin(user.id):
msg = await self._process_auto_sign(user.id, message.chat_id, "开启")
else:
msg = await self._process_auto_sign(user.id, user.id, "开启")
@@ -360,58 +82,61 @@ class Sign(Plugin, BasePlugin):
else:
validate = args[0]
if msg:
- logger.info(f"用户 {user.full_name}[{user.id}] 自动签到命令请求 || 参数 {args[0]}")
+ logger.info("用户 %s[%s] 自动签到命令请求 || 参数 %s", user.full_name, user.id, args[0])
reply_message = await message.reply_text(msg)
if filters.ChatType.GROUPS.filter(message):
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 30)
- self._add_delete_message_job(context, message.chat_id, message.message_id, 30)
+ self.add_delete_message_job(reply_message, delay=30)
+ self.add_delete_message_job(message.chat_id, delay=30)
return
- logger.info(f"用户 {user.full_name}[{user.id}] 每日签到命令请求")
+ logger.info("用户 %s[%s] 每日签到命令请求", user.full_name, user.id)
if filters.ChatType.GROUPS.filter(message):
- self._add_delete_message_job(context, message.chat_id, message.message_id)
+ self.add_delete_message_job(message)
try:
- client = await get_genshin_client(user.id)
+ client = await self.genshin_helper.get_genshin_client(user.id)
await message.reply_chat_action(ChatAction.TYPING)
- _, challenge = await self.system.get_challenge(client.uid)
+ _, challenge = await self.sign_system.get_challenge(client.uid)
if validate:
- _, challenge = await self.system.get_challenge(client.uid)
+ _, challenge = await self.sign_system.get_challenge(client.uid)
if challenge:
- sign_text = await self.system.start_sign(client, challenge=challenge, validate=validate)
+ sign_text = await self.sign_system.start_sign(client, challenge=challenge, validate=validate)
else:
reply_message = await message.reply_text("请求已经过期", allow_sending_without_reply=True)
if filters.ChatType.GROUPS.filter(reply_message):
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id)
+ self.add_delete_message_job(reply_message)
return
else:
- sign_text = await self.system.start_sign(client)
+ sign_text = await self.sign_system.start_sign(client)
reply_message = await message.reply_text(sign_text, allow_sending_without_reply=True)
if filters.ChatType.GROUPS.filter(reply_message):
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id)
- except (UserNotFoundError, CookiesNotFoundError):
+ self.add_delete_message_job(reply_message)
+ except (PlayerNotFoundError, CookiesNotFoundError):
buttons = [[InlineKeyboardButton("点我绑定账号", url=create_deep_linked_url(context.bot.username, "set_cookie"))]]
if filters.ChatType.GROUPS.filter(message):
reply_message = await message.reply_text(
"未查询到您所绑定的账号信息,请先私聊派蒙绑定账号", reply_markup=InlineKeyboardMarkup(buttons)
)
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 30)
+ self.add_delete_message_job(reply_message, delay=30)
- self._add_delete_message_job(context, message.chat_id, message.message_id, 30)
+ self.add_delete_message_job(message.chat_id, delay=30)
else:
await message.reply_text("未查询到您所绑定的账号信息,请先绑定账号", reply_markup=InlineKeyboardMarkup(buttons))
except NeedChallenge as exc:
- button = await self.system.get_challenge_button(
- exc.uid, user.id, exc.gt, exc.challenge, not filters.ChatType.PRIVATE.filter(message)
+ button = await self.sign_system.get_challenge_button(
+ context.bot.username,
+ exc.uid,
+ user.id,
+ exc.gt,
+ exc.challenge,
+ not filters.ChatType.PRIVATE.filter(message),
)
reply_message = await message.reply_text(
f"UID {exc.uid} 签到失败,触发验证码风控,请尝试点击下方按钮重新签到", allow_sending_without_reply=True, reply_markup=button
)
if filters.ChatType.GROUPS.filter(reply_message):
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id)
+ self.add_delete_message_job(reply_message)
@handler(CallbackQueryHandler, pattern=r"^sign\|", block=False)
- @restricts(restricts_time_of_groups=20, without_overlapping=True)
- @error_callable
- async def sign_gen_link(self, update: Update, _: CallbackContext) -> None:
+ async def sign_gen_link(self, update: Update, context: CallbackContext) -> None:
callback_query = update.callback_query
user = callback_query.from_user
@@ -419,15 +144,15 @@ class Sign(Plugin, BasePlugin):
_data = callback_query_data.split("|")
_user_id = int(_data[1])
_uid = int(_data[2])
- logger.debug(f"get_sign_callback 函数返回 user_id[{_user_id}] uid[{_uid}]")
+ logger.debug("get_sign_callback 函数返回 user_id[%s] uid[%s]", _user_id, _uid)
return _user_id, _uid
user_id, uid = await get_sign_callback(callback_query.data)
if user.id != user_id:
await callback_query.answer(text="这不是你的按钮!\n" + config.notice.user_mismatch, show_alert=True)
return
- _, challenge = await self.system.get_challenge(uid)
+ _, challenge = await self.sign_system.get_challenge(uid)
if not challenge:
await callback_query.answer(text="验证请求已经过期,请重新发起签到!", show_alert=True)
return
- await callback_query.answer(url=create_deep_linked_url(bot.app.bot.username, "sign"))
+ await callback_query.answer(url=create_deep_linked_url(context.bot.username, "sign"))
diff --git a/plugins/genshin/userstats.py b/plugins/genshin/stats.py
similarity index 69%
rename from plugins/genshin/userstats.py
rename to plugins/genshin/stats.py
index c92a8ae..89a3ae4 100644
--- a/plugins/genshin/userstats.py
+++ b/plugins/genshin/stats.py
@@ -5,72 +5,66 @@ from genshin import Client, GenshinException
from genshin.models import GenshinUserStats
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.constants import ChatAction
-from telegram.ext import (
- CallbackContext,
- CommandHandler,
- MessageHandler,
- filters,
-)
+from telegram.ext import CallbackContext, filters
from telegram.helpers import create_deep_linked_url
-from core.baseplugin import BasePlugin
-from core.cookies.error import CookiesNotFoundError, TooManyRequestPublicCookies
from core.plugin import Plugin, handler
-from core.template.models import RenderResult
-from core.template.services import TemplateService
-from core.user.error import UserNotFoundError
-from utils.decorators.error import error_callable
-from utils.decorators.restricts import restricts
-from utils.helpers import url_to_file, get_genshin_client, get_public_genshin_client
+from core.services.cookies.error import TooManyRequestPublicCookies
+from core.services.template.models import RenderResult
+from core.services.template.services import TemplateService
+from plugins.tools.genshin import GenshinHelper, PlayerNotFoundError, CookiesNotFoundError
from utils.log import logger
+__all__ = ("PlayerStatsPlugins",)
-class UserStatsPlugins(Plugin, BasePlugin):
+
+class PlayerStatsPlugins(Plugin):
"""玩家统计查询"""
- def __init__(self, template_service: TemplateService = None):
- self.template_service = template_service
+ def __init__(
+ self,
+ template: TemplateService,
+ helper: GenshinHelper,
+ ):
+ self.template_service = template
+ self.helper = helper
- @handler(CommandHandler, command="stats", block=False)
- @handler(MessageHandler, filters=filters.Regex("^玩家统计查询(.*)"), block=False)
- @restricts()
- @error_callable
+ @handler.command("stats", block=False)
+ @handler.message(filters.Regex("^玩家统计查询(.*)"), block=False)
async def command_start(self, update: Update, context: CallbackContext) -> Optional[int]:
user = update.effective_user
message = update.effective_message
- logger.info(f"用户 {user.full_name}[{user.id}] 查询游戏用户命令请求")
+ logger.info("用户 %s[%s] 查询游戏用户命令请求", user.full_name, user.id)
uid: Optional[int] = None
try:
args = context.args
if args is not None and len(args) >= 1:
uid = int(args[0])
except ValueError as exc:
- logger.warning(f"获取 uid 发生错误! 错误信息为 {repr(exc)}")
+ logger.warning("获取 uid 发生错误! 错误信息为 %s", str(exc))
await message.reply_text("输入错误")
return
try:
try:
- client = await get_genshin_client(user.id)
+ client = await self.helper.get_genshin_client(user.id)
except CookiesNotFoundError:
- client, uid = await get_public_genshin_client(user.id)
+ client, uid = await self.helper.get_public_genshin_client(user.id)
render_result = await self.render(client, uid)
- except UserNotFoundError:
- buttons = [[InlineKeyboardButton("点我绑定账号", url=create_deep_linked_url(context.bot.username, "set_uid"))]]
+ except PlayerNotFoundError:
+ buttons = [[InlineKeyboardButton("点我绑定账号", url=create_deep_linked_url(context.bot.username, "set_cookie"))]]
if filters.ChatType.GROUPS.filter(message):
reply_message = await message.reply_text(
"未查询到您所绑定的账号信息,请先私聊派蒙绑定账号", reply_markup=InlineKeyboardMarkup(buttons)
)
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 30)
-
- self._add_delete_message_job(context, message.chat_id, message.message_id, 30)
+ self.add_delete_message_job(reply_message, delay=30)
+ self.add_delete_message_job(message, delay=30)
else:
await message.reply_text("未查询到您所绑定的账号信息,请先绑定账号", reply_markup=InlineKeyboardMarkup(buttons))
return
except GenshinException as exc:
- if exc.retcode == 1034:
- if uid:
- await message.reply_text("出错了呜呜呜 ~ 请稍后重试")
- return
+ if exc.retcode == 1034 and uid:
+ await message.reply_text("出错了呜呜呜 ~ 请稍后重试")
+ return
raise exc
except TooManyRequestPublicCookies:
await message.reply_text("用户查询次数过多 请稍后重试")
@@ -133,13 +127,12 @@ class UserStatsPlugins(Plugin, BasePlugin):
full_page=True,
)
- @staticmethod
- async def cache_images(data: GenshinUserStats) -> None:
+ async def cache_images(self, data: GenshinUserStats) -> None:
"""缓存所有图片到本地"""
# TODO: 并发下载所有资源
# 探索地区
for item in data.explorations:
item.__config__.allow_mutation = True
- item.icon = await url_to_file(item.icon)
- item.cover = await url_to_file(item.cover)
+ item.icon = await self.download_resource(item.icon)
+ item.cover = await self.download_resource(item.cover)
diff --git a/plugins/genshin/strategy.py b/plugins/genshin/strategy.py
index 6424f9d..23f7341 100644
--- a/plugins/genshin/strategy.py
+++ b/plugins/genshin/strategy.py
@@ -1,24 +1,16 @@
-from telegram import InlineKeyboardButton, InlineKeyboardMarkup
-from telegram import Update
-from telegram.constants import ChatAction
-from telegram.constants import ParseMode
-from telegram.ext import CommandHandler, CallbackContext
-from telegram.ext import MessageHandler, filters
+from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
+from telegram.constants import ChatAction, ParseMode
+from telegram.ext import CallbackContext, filters
-from core.baseplugin import BasePlugin
-from core.game.services import GameStrategyService
from core.plugin import Plugin, handler
-from core.search.models import StrategyEntry
-from core.search.services import SearchServices
+from core.services.game.services import GameStrategyService
+from core.services.search.models import StrategyEntry
+from core.services.search.services import SearchServices
from metadata.shortname import roleToName, roleToTag
-from utils.bot import get_args
-from utils.decorators.error import error_callable
-from utils.decorators.restricts import restricts
-from utils.helpers import url_to_file
from utils.log import logger
-class StrategyPlugin(Plugin, BasePlugin):
+class StrategyPlugin(Plugin):
"""角色攻略查询"""
KEYBOARD = [[InlineKeyboardButton(text="查看角色攻略列表并查询", switch_inline_query_current_chat="查看角色攻略列表并查询")]]
@@ -31,21 +23,19 @@ class StrategyPlugin(Plugin, BasePlugin):
self.game_strategy_service = game_strategy_service
self.search_service = search_service
- @handler(CommandHandler, command="strategy", block=False)
- @handler(MessageHandler, filters=filters.Regex("^角色攻略查询(.*)"), block=False)
- @restricts()
- @error_callable
+ @handler.command(command="strategy", block=False)
+ @handler.message(filters=filters.Regex("^角色攻略查询(.*)"), block=False)
async def command_start(self, update: Update, context: CallbackContext) -> None:
message = update.effective_message
user = update.effective_user
- args = get_args(context)
+ args = self.get_args(context)
if len(args) >= 1:
character_name = args[0]
else:
reply_message = await message.reply_text("请回复你要查询的攻略的角色名", reply_markup=InlineKeyboardMarkup(self.KEYBOARD))
if filters.ChatType.GROUPS.filter(reply_message):
- self._add_delete_message_job(context, message.chat_id, message.message_id)
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id)
+ self.add_delete_message_job(message)
+ self.add_delete_message_job(reply_message)
return
character_name = roleToName(character_name)
url = await self.game_strategy_service.get_strategy(character_name)
@@ -54,12 +44,12 @@ class StrategyPlugin(Plugin, BasePlugin):
f"没有找到 {character_name} 的攻略", reply_markup=InlineKeyboardMarkup(self.KEYBOARD)
)
if filters.ChatType.GROUPS.filter(reply_message):
- self._add_delete_message_job(context, message.chat_id, message.message_id)
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id)
+ self.add_delete_message_job(message)
+ self.add_delete_message_job(reply_message)
return
- logger.info(f"用户 {user.full_name}[{user.id}] 查询角色攻略命令请求 || 参数 {character_name}")
+ logger.info("用户 %s[%s] 查询角色攻略命令请求 || 参数 %s", user.full_name, user.id, character_name)
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
- file_path = await url_to_file(url, return_path=True)
+ file_path = await self.download_resource(url, return_path=True)
caption = f"From 米游社 西风驿站 查看原图"
reply_photo = await message.reply_photo(
photo=open(file_path, "rb"),
diff --git a/plugins/genshin/uid.py b/plugins/genshin/uid.py
deleted file mode 100644
index a7863f2..0000000
--- a/plugins/genshin/uid.py
+++ /dev/null
@@ -1,199 +0,0 @@
-from typing import Optional
-
-import genshin
-from genshin import GenshinException, types, DataNotPublic
-from telegram import Update, ReplyKeyboardRemove, ReplyKeyboardMarkup, TelegramObject
-from telegram.ext import CallbackContext, filters, ConversationHandler
-from telegram.helpers import escape_markdown
-
-from core.baseplugin import BasePlugin
-from core.cookies.error import CookiesNotFoundError, TooManyRequestPublicCookies
-from core.cookies.services import CookiesService, PublicCookiesService
-from core.plugin import Plugin, handler, conversation
-from core.user.error import UserNotFoundError
-from core.user.models import User
-from core.user.services import UserService
-from utils.decorators.error import error_callable
-from utils.decorators.restricts import restricts
-from utils.log import logger
-from utils.models.base import RegionEnum
-
-
-class SetUserUidCommandData(TelegramObject):
- user: Optional[User] = None
- region: RegionEnum = RegionEnum.HYPERION
- game_uid: int = 0
-
-
-CHECK_SERVER, CHECK_UID, COMMAND_RESULT = range(10100, 10103)
-
-
-class SetUserUid(Plugin.Conversation, BasePlugin.Conversation):
- """UID用户绑定"""
-
- def __init__(
- self,
- user_service: UserService = None,
- cookies_service: CookiesService = None,
- public_cookies_service: PublicCookiesService = None,
- ):
- self.public_cookies_service = public_cookies_service
- self.cookies_service = cookies_service
- self.user_service = user_service
-
- @conversation.entry_point
- @handler.command(command="setuid", filters=filters.ChatType.PRIVATE, block=True)
- @restricts()
- @error_callable
- async def command_start(self, update: Update, context: CallbackContext) -> int:
- user = update.effective_user
- message = update.effective_message
- logger.info(f"用户 {user.full_name}[{user.id}] 绑定账号命令请求")
- set_user_uid_command_data: SetUserUidCommandData = context.chat_data.get("set_user_uid_command_data")
- if set_user_uid_command_data is None:
- cookies_command_data = SetUserUidCommandData()
- context.chat_data["set_user_uid_command_data"] = cookies_command_data
- text = (
- f"你好 {user.mention_markdown_v2()} "
- f'{escape_markdown("!请输入通行证UID(非游戏UID),BOT将会通过通行证UID查找游戏UID。请选择要绑定的服务器!或回复退出取消操作")}'
- )
- reply_keyboard = [["米游社", "HoYoLab"], ["退出"]]
- await message.reply_markdown_v2(text, reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True))
- return CHECK_SERVER
-
- @conversation.state(state=CHECK_SERVER)
- @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
- @error_callable
- async def check_server(self, update: Update, context: CallbackContext) -> int:
- user = update.effective_user
- message = update.effective_message
- set_user_uid_command_data: SetUserUidCommandData = context.chat_data.get("set_user_uid_command_data")
- if message.text == "退出":
- await message.reply_text("退出任务", reply_markup=ReplyKeyboardRemove())
- return ConversationHandler.END
- elif message.text == "米游社":
- region = set_user_uid_command_data.region = RegionEnum.HYPERION
- elif message.text == "HoYoLab":
- region = set_user_uid_command_data.region = RegionEnum.HOYOLAB
- else:
- await message.reply_text("选择错误,请重新选择")
- return CHECK_SERVER
- try:
- user_info = await self.user_service.get_user_by_id(user.id)
- set_user_uid_command_data.user = user_info
- except UserNotFoundError:
- set_user_uid_command_data.user = None
- user_info = None
- if user_info is not None:
- try:
- await self.cookies_service.get_cookies(user.id, region)
- except CookiesNotFoundError:
- pass
- else:
- await message.reply_text("你已经通过 Cookie 绑定了账号,无法继续下一步")
- return ConversationHandler.END
- await message.reply_text("请输入你的通行证UID(非游戏UID)", reply_markup=ReplyKeyboardRemove())
- return CHECK_UID
-
- @conversation.state(state=CHECK_UID)
- @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
- @error_callable
- async def check_cookies(self, update: Update, context: CallbackContext) -> int:
- user = update.effective_user
- message = update.effective_message
- set_user_uid_command_data: SetUserUidCommandData = context.chat_data.get("set_user_uid_command_data")
- region = set_user_uid_command_data.region
- if message.text == "退出":
- await message.reply_text("退出任务", reply_markup=ReplyKeyboardRemove())
- return ConversationHandler.END
- try:
- hoyolab_uid = int(message.text)
- except ValueError:
- await message.reply_text("UID 格式有误,请检查", reply_markup=ReplyKeyboardRemove())
- return ConversationHandler.END
- try:
- cookies = await self.public_cookies_service.get_cookies(user.id, region)
- except TooManyRequestPublicCookies:
- await message.reply_text("用户查询次数过多,请稍后重试", reply_markup=ReplyKeyboardRemove())
- return ConversationHandler.END
- if region == RegionEnum.HYPERION:
- client = genshin.Client(cookies=cookies.cookies, game=types.Game.GENSHIN, region=types.Region.CHINESE)
- elif region == RegionEnum.HOYOLAB:
- client = genshin.Client(
- cookies=cookies.cookies, game=types.Game.GENSHIN, region=types.Region.OVERSEAS, lang="zh-cn"
- )
- else:
- return ConversationHandler.END
- try:
- user_info = (await client.get_record_cards(hoyolab_uid))[0]
- except DataNotPublic:
- await message.reply_text("角色未公开", reply_markup=ReplyKeyboardRemove())
- logger.warning(f"获取账号信息发生错误 hoyolab_uid[{hoyolab_uid}] 账户信息未公开")
- return ConversationHandler.END
- except GenshinException as exc:
- await message.reply_text("获取账号信息发生错误", reply_markup=ReplyKeyboardRemove())
- logger.error("获取账号信息发生错误")
- logger.exception(exc)
- return ConversationHandler.END
- if user_info.game != types.Game.GENSHIN:
- await message.reply_text("角色信息查询返回非原神游戏信息," "请设置展示主界面为原神", reply_markup=ReplyKeyboardRemove())
- return ConversationHandler.END
- reply_keyboard = [["确认", "退出"]]
- await message.reply_text("获取角色基础信息成功,请检查是否正确!")
- logger.info(f"用户 {user.full_name}[{user.id}] 获取账号 {user_info.nickname}[{user_info.uid}] 信息成功")
- text = (
- f"*角色信息*\n"
- f"角色名称:{escape_markdown(user_info.nickname, version=2)}\n"
- f"角色等级:{user_info.level}\n"
- f"UID:`{user_info.uid}`\n"
- f"服务器名称:`{user_info.server_name}`\n"
- )
- set_user_uid_command_data.game_uid = user_info.uid
- await message.reply_markdown_v2(text, reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True))
- return COMMAND_RESULT
-
- @conversation.state(state=COMMAND_RESULT)
- @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
- @error_callable
- async def command_result(self, update: Update, context: CallbackContext) -> int:
- user = update.effective_user
- message = update.effective_message
- set_user_uid_command_data: SetUserUidCommandData = context.chat_data.get("set_user_uid_command_data")
- if message.text == "退出":
- await message.reply_text("退出任务", reply_markup=ReplyKeyboardRemove())
- return ConversationHandler.END
- elif message.text == "确认":
- if set_user_uid_command_data.user is None:
- if set_user_uid_command_data.region == RegionEnum.HYPERION:
- user_db = User(
- user_id=user.id,
- yuanshen_uid=set_user_uid_command_data.game_uid,
- region=set_user_uid_command_data.region,
- )
- elif set_user_uid_command_data.region == RegionEnum.HOYOLAB:
- user_db = User(
- user_id=user.id,
- genshin_uid=set_user_uid_command_data.game_uid,
- region=set_user_uid_command_data.region,
- )
- else:
- await message.reply_text("数据错误")
- return ConversationHandler.END
- await self.user_service.add_user(user_db)
- else:
- user_db = set_user_uid_command_data.user
- user_db.region = set_user_uid_command_data.region
- if set_user_uid_command_data.region == RegionEnum.HYPERION:
- user_db.yuanshen_uid = set_user_uid_command_data.game_uid
- elif set_user_uid_command_data.region == RegionEnum.HOYOLAB:
- user_db.genshin_uid = set_user_uid_command_data.game_uid
- else:
- await message.reply_text("数据错误")
- return ConversationHandler.END
- await self.user_service.update_user(user_db)
- logger.info(f"用户 {user.full_name}[{user.id}] 绑定UID账号成功")
- await message.reply_text("保存成功", reply_markup=ReplyKeyboardRemove())
- return ConversationHandler.END
- else:
- await message.reply_text("回复错误,请重新输入")
- return COMMAND_RESULT
diff --git a/plugins/genshin/user.py b/plugins/genshin/user.py
deleted file mode 100644
index b7159eb..0000000
--- a/plugins/genshin/user.py
+++ /dev/null
@@ -1,121 +0,0 @@
-from typing import Optional
-
-from telegram import Update, TelegramObject, User, ReplyKeyboardRemove
-from telegram.ext import CallbackContext, filters, ConversationHandler
-
-from core.baseplugin import BasePlugin
-from core.cookies import CookiesService
-from core.cookies.error import CookiesNotFoundError
-from core.cookies.models import Cookies
-from core.plugin import Plugin, handler, conversation
-from core.sign import SignServices
-from core.user import UserService
-from core.user.error import UserNotFoundError
-from utils.decorators.error import error_callable
-from utils.decorators.restricts import restricts
-from utils.log import logger
-from utils.models.base import RegionEnum
-
-
-class DelUserCommandData(TelegramObject):
- user: Optional[User] = None
- region: RegionEnum = RegionEnum.HYPERION
- cookies: Optional[Cookies] = None
-
-
-CHECK_SERVER, DEL_USER = range(10800, 10802)
-
-
-class UserPlugin(Plugin.Conversation, BasePlugin.Conversation):
- def __init__(
- self,
- user_service: UserService = None,
- cookies_service: CookiesService = None,
- sign_service: SignServices = None,
- ):
- self.cookies_service = cookies_service
- self.user_service = user_service
- self.sign_service = sign_service
-
- @conversation.entry_point
- @handler.command(command="deluser", filters=filters.ChatType.PRIVATE, block=True)
- @restricts()
- @error_callable
- async def command_start(self, update: Update, context: CallbackContext) -> int:
- user = update.effective_user
- message = update.effective_message
- logger.info("用户 %s[%s] 删除账号命令请求", user.full_name, user.id)
- del_user_command_data: DelUserCommandData = context.chat_data.get("del_user_command_data")
- if del_user_command_data is None:
- del_user_command_data = DelUserCommandData()
- context.chat_data["del_user_command_data"] = del_user_command_data
- try:
- user_info = await self.user_service.get_user_by_id(user.id)
- del_user_command_data.user = user_info
- except UserNotFoundError:
- await message.reply_text("用户未找到")
- return ConversationHandler.END
- cookies_status: bool = False
- try:
- cookies = await self.cookies_service.get_cookies(user.id, user_info.region)
- del_user_command_data.cookies = cookies
- cookies_status = True
- except CookiesNotFoundError:
- logger.info("用户 %s[%s] Cookies 不存在", user.full_name, user.id)
- if user_info.region == RegionEnum.HYPERION:
- uid = user_info.yuanshen_uid
- region_str = "米游社"
- del_user_command_data.region = RegionEnum.HYPERION
- elif user_info.region == RegionEnum.HOYOLAB:
- uid = user_info.genshin_uid
- region_str = "HoYoLab"
- del_user_command_data.region = RegionEnum.HOYOLAB
- else:
- await message.reply_text("数据非法")
- return ConversationHandler.END
- await message.reply_text("获取用户信息成功")
- text = (
- f"绑定信息\n"
- f"UID:{uid}
\n"
- f"注册:{region_str}
\n"
- f"是否绑定Cookie:{'√' if cookies_status else '×'}
"
- )
- await message.reply_html(text)
- await message.reply_html("请回复确认即可解除绑定并从数据库移除,如绑定Cookies也会跟着一起从数据库删除,删除后操作无法逆转,回复 /cancel 可退出操作")
- return DEL_USER
-
- @conversation.state(state=DEL_USER)
- @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
- @error_callable
- async def command_result(self, update: Update, context: CallbackContext) -> int:
- user = update.effective_user
- message = update.effective_message
- if message.text == "退出":
- await message.reply_text("退出任务", reply_markup=ReplyKeyboardRemove())
- return ConversationHandler.END
- elif message.text == "确认":
- del_user_command_data: DelUserCommandData = context.chat_data.get("del_user_command_data")
- sign = await self.sign_service.get_by_user_id(user.id)
- if sign:
- await self.sign_service.remove(sign)
- logger.success("用户 %s[%s] 从数据库删除定时签到成功", user.full_name, user.id)
- try:
- await self.user_service.del_user_by_id(user.id)
- except UserNotFoundError:
- await message.reply_text("用户未找到")
- return ConversationHandler.END
- else:
- logger.success("用户 %s[%s] 从数据库删除账号成功", user.full_name, user.id)
- cookies = del_user_command_data.cookies
- if cookies:
- try:
- await self.cookies_service.del_cookies(user.id, del_user_command_data.region)
- except CookiesNotFoundError:
- logger.warning("用户 %s[%s] Cookies 不存在", user.full_name, user.id)
- else:
- logger.success("用户 %s[%s] 从数据库删除Cookies成功", user.full_name, user.id)
- await message.reply_text("删除成功")
- return ConversationHandler.END
- else:
- await message.reply_text("回复错误,退出当前会话")
- return ConversationHandler.END
diff --git a/plugins/genshin/verification.py b/plugins/genshin/verification.py
deleted file mode 100644
index a8d1957..0000000
--- a/plugins/genshin/verification.py
+++ /dev/null
@@ -1,96 +0,0 @@
-from typing import Tuple, Optional
-
-from genshin import Region, GenshinException
-from telegram import Update, WebAppInfo, KeyboardButton, ReplyKeyboardMarkup
-from telegram.ext import CallbackContext, filters
-
-from core.base.redisdb import RedisDB
-from core.baseplugin import BasePlugin
-from core.config import config
-from core.cookies import CookiesService
-from core.cookies.error import CookiesNotFoundError
-from core.plugin import Plugin, handler
-from core.user import UserService
-from core.user.error import UserNotFoundError
-from modules.apihelper.client.components.verify import Verify
-from modules.apihelper.error import ResponseException
-from utils.decorators.error import error_callable
-from utils.decorators.restricts import restricts
-from utils.helpers import get_genshin_client
-from utils.log import logger
-
-
-class VerificationSystem:
- def __init__(self, redis: RedisDB = None):
- self.cache = redis.client
- self.qname = "plugin:verification:"
-
- async def get_challenge(self, uid: int) -> Tuple[Optional[str], Optional[str]]:
- data = await self.cache.get(f"{self.qname}{uid}")
- if not data:
- return None, None
- data = data.decode("utf-8").split("|")
- return data[0], data[1]
-
- async def set_challenge(self, uid: int, gt: str, challenge: str):
- await self.cache.set(f"{self.qname}{uid}", f"{gt}|{challenge}")
- await self.cache.expire(f"{self.qname}{uid}", 10 * 60)
-
-
-class VerificationPlugins(Plugin, BasePlugin):
- def __init__(self, user_service: UserService = None, cookies_service: CookiesService = None, redis: RedisDB = None):
- self.cookies_service = cookies_service
- self.user_service = user_service
- self.system = VerificationSystem(redis)
-
- @handler.command("verify", filters=filters.ChatType.PRIVATE, block=False)
- @restricts(restricts_time=60)
- @error_callable
- async def verify(self, update: Update, context: CallbackContext) -> None:
- user = update.effective_user
- message = update.effective_message
- logger.info("用户 %s[%s] 发出verify命令", user.full_name, user.id)
- try:
- client = await get_genshin_client(user.id)
- if client.region != Region.CHINESE:
- await message.reply_text("非法用户")
- return
- except UserNotFoundError:
- await message.reply_text("用户未找到")
- return
- except CookiesNotFoundError:
- await message.reply_text("检测到用户为UID绑定,无需认证")
- return
- is_high: bool = False
- verification = Verify(cookies=client.cookie_manager.cookies)
- if not context.args:
- try:
- await client.get_genshin_notes()
- except GenshinException as exc:
- if exc.retcode == 1034:
- is_high = True
- else:
- raise exc
- else:
- await message.reply_text("账户正常,无需认证")
- return
- try:
- data = await verification.create(is_high=is_high)
- challenge = data["challenge"]
- gt = data["gt"]
- logger.success("用户 %s[%s] 创建验证成功\ngt[%s]\nchallenge[%s]", user.full_name, user.id, gt, challenge)
- except ResponseException as exc:
- logger.warning("用户 %s[%s] 创建验证失效 API返回 [%s]%s", user.full_name, user.id, exc.code, exc.message)
- await message.reply_text(f"创建验证失败 错误信息为 [{exc.code}]{exc.message} 请稍后重试")
- return
- await self.system.set_challenge(client.uid, gt, challenge)
- url = f"{config.pass_challenge_user_web}/webapp?username={context.bot.username}&command=verify>={gt}&challenge={challenge}&uid={client.uid}"
- await message.reply_text(
- "请尽快在10秒内完成手动验证\n或发送 /web_cancel 取消操作",
- reply_markup=ReplyKeyboardMarkup.from_button(
- KeyboardButton(
- text="点我手动验证",
- web_app=WebAppInfo(url=url),
- )
- ),
- )
diff --git a/plugins/genshin/verify.py b/plugins/genshin/verify.py
new file mode 100644
index 0000000..494e37e
--- /dev/null
+++ b/plugins/genshin/verify.py
@@ -0,0 +1,39 @@
+from telegram import KeyboardButton, ReplyKeyboardMarkup, Update, WebAppInfo
+from telegram.ext import CallbackContext, filters
+
+from core.config import config
+from core.plugin import Plugin, handler
+from plugins.tools.challenge import ChallengeSystem, ChallengeSystemException
+from utils.log import logger
+
+
+class VerificationPlugins(Plugin):
+ def __init__(
+ self,
+ challenge_system: ChallengeSystem,
+ ):
+ self.challenge_system = challenge_system
+
+ @handler.command("verify", filters=filters.ChatType.PRIVATE, block=False)
+ async def verify(self, update: Update, context: CallbackContext) -> None:
+ user = update.effective_user
+ message = update.effective_message
+ logger.info("用户 %s[%s] 发出verify命令", user.full_name, user.id)
+ try:
+ uid, gt, challenge = await self.challenge_system.create_challenge(user.id, context.args is not None)
+ except ChallengeSystemException as exc:
+ await message.reply_text(exc.message)
+ return
+ url = (
+ f"{config.pass_challenge_user_web}/webapp?"
+ f"username={context.bot.username}&command=verify>={gt}&challenge={challenge}&uid={uid}"
+ )
+ await message.reply_text(
+ "请尽快在10秒内完成手动验证\n或发送 /web_cancel 取消操作",
+ reply_markup=ReplyKeyboardMarkup.from_button(
+ KeyboardButton(
+ text="点我手动验证",
+ web_app=WebAppInfo(url=url),
+ )
+ ),
+ )
diff --git a/plugins/genshin/weapon.py b/plugins/genshin/weapon.py
index 0629d9c..70c333d 100644
--- a/plugins/genshin/weapon.py
+++ b/plugins/genshin/weapon.py
@@ -2,24 +2,19 @@ from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from telegram.constants import ChatAction
from telegram.ext import CallbackContext, CommandHandler, MessageHandler, filters
-from core.base.assets import AssetsService, AssetsCouldNotFound
-from core.baseplugin import BasePlugin
+from core.dependence.assets import AssetsCouldNotFound, AssetsService
from core.plugin import Plugin, handler
-from core.search.models import WeaponEntry
-from core.search.services import SearchServices
-from core.template import TemplateService
-from core.wiki.services import WikiService
+from core.services.search.models import WeaponEntry
+from core.services.search.services import SearchServices
+from core.services.template.services import TemplateService
+from core.services.wiki.services import WikiService
from metadata.genshin import honey_id_to_game_id
from metadata.shortname import weaponToName, weapons as _weapons_data
from modules.wiki.weapon import Weapon
-from utils.bot import get_args
-from utils.decorators.error import error_callable
-from utils.decorators.restricts import restricts
-from utils.helpers import url_to_file
from utils.log import logger
-class WeaponPlugin(Plugin, BasePlugin):
+class WeaponPlugin(Plugin):
"""武器查询"""
KEYBOARD = [[InlineKeyboardButton(text="查看武器列表并查询", switch_inline_query_current_chat="查看武器列表并查询")]]
@@ -38,22 +33,20 @@ class WeaponPlugin(Plugin, BasePlugin):
@handler(CommandHandler, command="weapon", block=False)
@handler(MessageHandler, filters=filters.Regex("^武器查询(.*)"), block=False)
- @error_callable
- @restricts()
async def command_start(self, update: Update, context: CallbackContext) -> None:
message = update.effective_message
user = update.effective_user
- args = get_args(context)
+ args = self.get_args(context)
if len(args) >= 1:
weapon_name = args[0]
else:
reply_message = await message.reply_text("请回复你要查询的武器", reply_markup=InlineKeyboardMarkup(self.KEYBOARD))
if filters.ChatType.GROUPS.filter(reply_message):
- self._add_delete_message_job(context, message.chat_id, message.message_id)
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id)
+ self.add_delete_message_job(message)
+ self.add_delete_message_job(reply_message)
return
weapon_name = weaponToName(weapon_name)
- logger.info(f"用户 {user.full_name}[{user.id}] 查询武器命令请求 || 参数 weapon_name={weapon_name}")
+ logger.info("用户 %s[%s] 查询角色攻略命令请求 weapon_name[%s]", user.full_name, user.id, weapon_name)
weapons_list = await self.wiki_service.get_weapons_list()
for weapon in weapons_list:
if weapon.name == weapon_name:
@@ -64,8 +57,8 @@ class WeaponPlugin(Plugin, BasePlugin):
f"没有找到 {weapon_name}", reply_markup=InlineKeyboardMarkup(self.KEYBOARD)
)
if filters.ChatType.GROUPS.filter(reply_message):
- self._add_delete_message_job(context, message.chat_id, message.message_id)
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id)
+ self.add_delete_message_job(message)
+ self.add_delete_message_job(reply_message)
return
await message.reply_chat_action(ChatAction.TYPING)
@@ -78,7 +71,7 @@ class WeaponPlugin(Plugin, BasePlugin):
bonus = str(round(float(bonus)))
_template_data = {
"weapon_name": _weapon_data.name,
- "weapon_info_type_img": await url_to_file(_weapon_data.weapon_type.icon_url()),
+ "weapon_info_type_img": await self.download_resource(_weapon_data.weapon_type.icon_url()),
"progression_secondary_stat_value": bonus,
"progression_secondary_stat_name": _weapon_data.attribute.type.value,
"weapon_info_source_img": (
@@ -96,7 +89,7 @@ class WeaponPlugin(Plugin, BasePlugin):
else:
_template_data = {
"weapon_name": _weapon_data.name,
- "weapon_info_type_img": await url_to_file(_weapon_data.weapon_type.icon_url()),
+ "weapon_info_type_img": await self.download_resource(_weapon_data.weapon_type.icon_url()),
"progression_secondary_stat_value": " ",
"progression_secondary_stat_name": "无其它属性加成",
"weapon_info_source_img": (
@@ -119,8 +112,8 @@ class WeaponPlugin(Plugin, BasePlugin):
logger.warning("%s weapon_name[%s]", exc.message, weapon_name)
reply_message = await message.reply_text(f"数据库中没有找到 {weapon_name}")
if filters.ChatType.GROUPS.filter(reply_message):
- self._add_delete_message_job(context, message.chat_id, message.message_id)
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id)
+ self.add_delete_message_job(message)
+ self.add_delete_message_job(reply_message)
return
png_data = await self.template_service.render(
"genshin/weapon/weapon.html", template_data, {"width": 540, "height": 540}, ttl=31 * 24 * 60 * 60
diff --git a/plugins/genshin/wiki.py b/plugins/genshin/wiki.py
deleted file mode 100644
index b2703b3..0000000
--- a/plugins/genshin/wiki.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from telegram import Update
-from telegram.ext import CommandHandler, CallbackContext
-
-from core.plugin import Plugin, handler
-from core.wiki.services import WikiService
-from utils.decorators.admins import bot_admins_rights_check
-
-
-class Wiki(Plugin):
- """有关WIKI操作"""
-
- def __init__(self, wiki_service: WikiService = None):
- self.wiki_service = wiki_service
-
- @handler(CommandHandler, command="refresh_wiki", block=False)
- @bot_admins_rights_check
- async def refresh_wiki(self, update: Update, _: CallbackContext):
- message = update.effective_message
- await message.reply_text("正在刷新Wiki缓存,请稍等")
- await self.wiki_service.refresh_wiki()
- await message.reply_text("刷新Wiki缓存成功")
diff --git a/plugins/genshin/gacha/gacha.py b/plugins/genshin/wish.py
similarity index 84%
rename from plugins/genshin/gacha/gacha.py
rename to plugins/genshin/wish.py
index a072ef6..6ef2f36 100644
--- a/plugins/genshin/gacha/gacha.py
+++ b/plugins/genshin/wish.py
@@ -3,17 +3,15 @@ import re
from datetime import datetime
from typing import Any, List, Optional, Tuple, Union
-import ujson as json
from bs4 import BeautifulSoup
from telegram import Update
from telegram.constants import ChatAction
from telegram.ext import CallbackContext, CommandHandler, MessageHandler, filters
-from core.base.assets import AssetsService
-from core.base.redisdb import RedisDB
-from core.baseplugin import BasePlugin
+from core.dependence.assets import AssetsService
+from core.dependence.redisdb import RedisDB
from core.plugin import Plugin, handler
-from core.template import TemplateService
+from core.services.template.services import TemplateService
from metadata.genshin import AVATAR_DATA, WEAPON_DATA, avatar_to_game_id, weapon_to_game_id
from metadata.shortname import weaponToName
from modules.apihelper.client.components.gacha import Gacha as GachaClient
@@ -21,11 +19,14 @@ from modules.apihelper.models.genshin.gacha import GachaInfo
from modules.gacha.banner import BannerType, GachaBanner
from modules.gacha.player.info import PlayerGachaInfo
from modules.gacha.system import BannerSystem
-from utils.bot import get_args
-from utils.decorators.error import error_callable
-from utils.decorators.restricts import restricts
from utils.log import logger
+try:
+ import ujson as jsonlib
+
+except ImportError:
+ import json as jsonlib
+
class GachaNotFound(Exception):
"""卡池未找到"""
@@ -52,14 +53,14 @@ class GachaRedis:
data = await self.client.get(f"{self.qname}{user_id}")
if data is None:
return PlayerGachaInfo()
- return PlayerGachaInfo(**json.loads(data))
+ return PlayerGachaInfo(**jsonlib.loads(data))
async def set(self, user_id: int, player_gacha_info: PlayerGachaInfo):
value = player_gacha_info.json()
await self.client.set(f"{self.qname}{user_id}", value)
-class GachaHandle:
+class WishSimulatorHandle:
def __init__(self):
self.hyperion = GachaClient()
@@ -119,8 +120,7 @@ class GachaHandle:
else: # pylint: disable=W0120
if default and len(gacha_list_info) > 0:
return gacha_list_info[0]
- else:
- raise GachaNotFound(gacha_name)
+ raise GachaNotFound(gacha_name)
@staticmethod
def de_title(title: str) -> Union[Tuple[str, None], Tuple[str, Any]]:
@@ -134,12 +134,12 @@ class GachaHandle:
return title_html.text, title_html.p
-class Gacha(Plugin, BasePlugin):
+class WishSimulatorPlugin(Plugin):
"""抽卡模拟器(非首模拟器/减寿模拟器)"""
- def __init__(self, assets: AssetsService = None, template_service: TemplateService = None, redis: RedisDB = None):
+ def __init__(self, assets: AssetsService, template_service: TemplateService, redis: RedisDB):
self.gacha_db = GachaRedis(redis)
- self.handle = GachaHandle()
+ self.handle = WishSimulatorHandle()
self.banner_system = BannerSystem()
self.template_service = template_service
self.banner_cache = {}
@@ -157,6 +157,8 @@ class Gacha(Plugin, BasePlugin):
async def de_item_list(self, item_list: List[int]) -> List[dict]:
gacha_item: List[dict] = []
for item_id in item_list:
+ if item_id is None:
+ continue
if 10000 <= item_id <= 100000:
data = WEAPON_DATA.get(str(item_id))
avatar = self.assets_service.weapon(item_id)
@@ -175,15 +177,31 @@ class Gacha(Plugin, BasePlugin):
gacha_item.append(data)
return gacha_item
- @handler(CommandHandler, command="gacha", block=False)
+ async def shutdown(self) -> None:
+ pass
+ # todo 目前清理消息无法执行 因为先停止Job导致无法获取全部信息
+ # logger.info("正在清理消息")
+ # job_queue = self.application.telegram.job_queue
+ # jobs = job_queue.jobs()
+ # for job in jobs:
+ # if "wish_simulator" in job.name and not job.removed:
+ # logger.info("当前Job name %s", job.name)
+ # try:
+ # await job.run(job_queue.application)
+ # except CancelledError:
+ # continue
+ # except Exception as exc:
+ # logger.warning("执行失败 %", str(exc))
+ # else:
+ # logger.info("Jobs为空")
+ # logger.success("清理卡池消息成功")
+
@handler(CommandHandler, command="wish", block=False)
@handler(MessageHandler, filters=filters.Regex("^抽卡模拟器(.*)"), block=False)
- @restricts(restricts_time=3, restricts_time_of_groups=20)
- @error_callable
async def command_start(self, update: Update, context: CallbackContext) -> None:
message = update.effective_message
user = update.effective_user
- args = get_args(context)
+ args = self.get_args(context)
gacha_name = "角色活动"
if len(args) >= 1:
gacha_name = args[0]
@@ -222,8 +240,8 @@ class Gacha(Plugin, BasePlugin):
logger.warning("角色 item_id[%s] 抽卡立绘未找到", exc.item_id)
reply_message = await message.reply_text("出错了呜呜呜 ~ 卡池部分数据未找到!")
if filters.ChatType.GROUPS.filter(message):
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 60)
- self._add_delete_message_job(context, message.chat_id, message.message_id, 60)
+ self.add_delete_message_job(reply_message, name="wish_simulator")
+ self.add_delete_message_job(message.chat_id, name="wish_simulator")
return
player_gacha_banner_info = player_gacha_info.get_banner_info(banner)
template_data = {
@@ -235,10 +253,6 @@ class Gacha(Plugin, BasePlugin):
"items": [],
"wish_name": "",
}
- # logger.debug(f"{banner.banner_id}")
- # logger.debug(f"{banner.banner_type}")
- # logger.debug(f"{banner.rate_up_items5}")
- # logger.debug(f"{banner.fallback_items5_pool1}")
if player_gacha_banner_info.wish_item_id != 0:
weapon = WEAPON_DATA.get(str(player_gacha_banner_info.wish_item_id))
if weapon is not None:
@@ -257,24 +271,22 @@ class Gacha(Plugin, BasePlugin):
reply_message = await message.reply_photo(png_data.photo)
if filters.ChatType.GROUPS.filter(message):
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 300)
- self._add_delete_message_job(context, message.chat_id, message.message_id, 300)
+ self.add_delete_message_job(reply_message, name="wish_simulator")
+ self.add_delete_message_job(message, name="wish_simulator")
@handler(CommandHandler, command="set_wish", block=False)
@handler(MessageHandler, filters=filters.Regex("^非首模拟器定轨(.*)"), block=False)
- @restricts(restricts_time=3, restricts_time_of_groups=20)
- @error_callable
async def set_wish(self, update: Update, context: CallbackContext) -> None:
message = update.effective_message
user = update.effective_user
- args = get_args(context)
+ args = self.get_args(context)
try:
gacha_base_info = await self.handle.gacha_base_info("武器活动")
except GachaNotFound:
reply_message = await message.reply_text("当前还没有武器正在 UP,可能是卡池不存在或者卡池已经结束。")
if filters.ChatType.GROUPS.filter(reply_message):
- self._add_delete_message_job(context, message.chat_id, message.message_id, 10)
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 10)
+ self.add_delete_message_job(message, delay=30)
+ self.add_delete_message_job(reply_message, delay=30)
return
banner = await self.get_banner(gacha_base_info)
up_weapons = {}
@@ -289,8 +301,8 @@ class Gacha(Plugin, BasePlugin):
else:
reply_message = await message.reply_text(f"输入的参数不正确,请输入需要定轨的武器名称。\n{up_weapons_text}")
if filters.ChatType.GROUPS.filter(reply_message):
- self._add_delete_message_job(context, message.chat_id, message.message_id, 10)
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 10)
+ self.add_delete_message_job(message, delay=30)
+ self.add_delete_message_job(reply_message, delay=30)
return
weapon_name = weaponToName(weapon_name)
player_gacha_info = await self.gacha_db.get(user.id)
@@ -302,12 +314,11 @@ class Gacha(Plugin, BasePlugin):
f"输入的参数不正确,可能是没有名为 {weapon_name} 的武器或该武器不存在当前 UP 卡池中\n{up_weapons_text}"
)
if filters.ChatType.GROUPS.filter(reply_message):
- self._add_delete_message_job(context, message.chat_id, message.message_id, 10)
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 10)
+ self.add_delete_message_job(message, delay=30)
+ self.add_delete_message_job(reply_message, delay=30)
return
await self.gacha_db.set(user.id, player_gacha_info)
reply_message = await message.reply_text(f"抽卡模拟器定轨 {weapon_name} 武器成功")
if filters.ChatType.GROUPS.filter(reply_message):
- self._add_delete_message_job(context, message.chat_id, message.message_id, 10)
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 10)
- return
+ self.add_delete_message_job(message, delay=30)
+ self.add_delete_message_job(reply_message, delay=30)
diff --git a/plugins/genshin/gacha/gacha_log.py b/plugins/genshin/wish_log.py
similarity index 74%
rename from plugins/genshin/gacha/gacha_log.py
rename to plugins/genshin/wish_log.py
index c69495d..8f2049a 100644
--- a/plugins/genshin/gacha/gacha_log.py
+++ b/plugins/genshin/wish_log.py
@@ -1,44 +1,35 @@
-import contextlib
from io import BytesIO
import genshin
from aiofiles import open as async_open
from genshin.models import BannerType
-from telegram import Update, User, Message, Document, InlineKeyboardButton, InlineKeyboardMarkup
+from telegram import Document, InlineKeyboardButton, InlineKeyboardMarkup, Message, Update, User
from telegram.constants import ChatAction
-from telegram.ext import CallbackContext, CommandHandler, MessageHandler, filters, ConversationHandler
+from telegram.ext import CallbackContext, CommandHandler, ConversationHandler, MessageHandler, filters
from telegram.helpers import create_deep_linked_url
-from core.base.assets import AssetsService
-from core.baseplugin import BasePlugin
-from core.config import config
-from core.cookies import CookiesService
-from core.cookies.error import CookiesNotFoundError
-from core.plugin import Plugin, handler, conversation
-from core.template import TemplateService
-from core.template.models import FileType
-from core.user import UserService
-from core.user.error import UserNotFoundError
-from metadata.scripts.paimon_moe import update_paimon_moe_zh, GACHA_LOG_PAIMON_MOE_PATH
+from core.basemodel import RegionEnum
+from core.dependence.assets import AssetsService
+from core.plugin import Plugin, conversation, handler
+from core.services.cookies import CookiesService
+from core.services.players import PlayersService
+from core.services.template.models import FileType
+from core.services.template.services import TemplateService
+from metadata.scripts.paimon_moe import GACHA_LOG_PAIMON_MOE_PATH, update_paimon_moe_zh
from modules.gacha_log.error import (
- GachaLogInvalidAuthkey,
- PaimonMoeGachaLogFileError,
- GachaLogFileError,
- GachaLogNotFound,
GachaLogAccountNotFound,
- GachaLogMixedProvider,
GachaLogAuthkeyTimeout,
+ GachaLogFileError,
+ GachaLogInvalidAuthkey,
+ GachaLogMixedProvider,
+ GachaLogNotFound,
+ PaimonMoeGachaLogFileError,
)
from modules.gacha_log.helpers import from_url_get_authkey
from modules.gacha_log.log import GachaLog
-from utils.bot import get_args
-from utils.decorators.admins import bot_admins_rights_check
-from utils.decorators.error import error_callable
-from utils.decorators.restricts import restricts
+from plugins.tools.genshin import PlayerNotFoundError, GenshinHelper
from utils.genshin import get_authkey_by_stoken
-from utils.helpers import get_genshin_client
from utils.log import logger
-from utils.models.base import RegionEnum
try:
import ujson as jsonlib
@@ -49,24 +40,26 @@ except ImportError:
INPUT_URL, INPUT_FILE, CONFIRM_DELETE = range(10100, 10103)
-class GachaLogPlugin(Plugin.Conversation, BasePlugin.Conversation):
+class WishLogPlugin(Plugin.Conversation):
"""抽卡记录导入/导出/分析"""
def __init__(
self,
- template_service: TemplateService = None,
- user_service: UserService = None,
- assets: AssetsService = None,
- cookie_service: CookiesService = None,
+ template_service: TemplateService,
+ players_service: PlayersService,
+ assets: AssetsService,
+ cookie_service: CookiesService,
+ helper: GenshinHelper,
):
self.template_service = template_service
- self.user_service = user_service
+ self.players_service = players_service
self.assets_service = assets
self.cookie_service = cookie_service
self.zh_dict = None
self.gacha_log = GachaLog()
+ self.helper = helper
- async def __async_init__(self):
+ async def initialize(self) -> None:
await update_paimon_moe_zh(False)
async with async_open(GACHA_LOG_PAIMON_MOE_PATH, "r", encoding="utf-8") as load_f:
self.zh_dict = jsonlib.loads(await load_f.read())
@@ -82,7 +75,7 @@ class GachaLogPlugin(Plugin.Conversation, BasePlugin.Conversation):
"""
try:
logger.debug("尝试获取已绑定的原神账号")
- client = await get_genshin_client(user.id, need_cookie=False)
+ client = await self.helper.get_genshin_client(user.id, need_cookie=False)
if authkey:
new_num = await self.gacha_log.get_gacha_log_data(user.id, client, authkey)
return "更新完成,本次没有新增数据" if new_num == 0 else f"更新完成,本次共新增{new_num}条抽卡记录"
@@ -101,7 +94,7 @@ class GachaLogPlugin(Plugin.Conversation, BasePlugin.Conversation):
return "更新数据失败,authkey 已经过期"
except GachaLogMixedProvider:
return "导入失败,你已经通过其他方式导入过抽卡记录了,本次无法导入"
- except UserNotFoundError:
+ except PlayerNotFoundError:
logger.info("未查询到用户 %s[%s] 所绑定的账号信息", user.full_name, user.id)
return "派蒙没有找到您所绑定的账号信息,请先私聊派蒙绑定账号"
@@ -116,8 +109,8 @@ class GachaLogPlugin(Plugin.Conversation, BasePlugin.Conversation):
else:
await message.reply_text("文件格式错误,请发送符合 UIGF 标准的抽卡记录文件或者 paimon.moe、非小酋导出的 xlsx 格式的抽卡记录文件")
return
- if document.file_size > config.plugin.download_file_max_size * 1024 * 1024:
- await message.reply_text(f"文件过大,请发送小于 {config.plugin.download_file_max_size} MB 的文件")
+ if document.file_size > 2 * 1024 * 1024:
+ await message.reply_text("文件过大,请发送小于 2 MB 的文件")
return
try:
out = BytesIO()
@@ -158,44 +151,29 @@ class GachaLogPlugin(Plugin.Conversation, BasePlugin.Conversation):
@conversation.entry_point
@handler(CommandHandler, command="gacha_log_import", filters=filters.ChatType.PRIVATE, block=False)
@handler(MessageHandler, filters=filters.Regex("^导入抽卡记录(.*)") & filters.ChatType.PRIVATE, block=False)
- @restricts()
- @error_callable
async def command_start(self, update: Update, context: CallbackContext) -> int:
message = update.effective_message
user = update.effective_user
- args = get_args(context)
+ args = self.get_args(context)
logger.info("用户 %s[%s] 导入抽卡记录命令请求", user.full_name, user.id)
authkey = from_url_get_authkey(args[0] if args else "")
if not args:
- if message.document:
- await self.import_from_file(user, message)
- return ConversationHandler.END
- elif message.reply_to_message and message.reply_to_message.document:
- await self.import_from_file(user, message, document=message.reply_to_message.document)
- return ConversationHandler.END
- try:
- user_info = await self.user_service.get_user_by_id(user.id)
- except UserNotFoundError:
- user_info = None
- if user_info and user_info.region == RegionEnum.HYPERION:
- try:
- cookies = await self.cookie_service.get_cookies(user_info.user_id, user_info.region)
- except CookiesNotFoundError:
- cookies = None
- if cookies and cookies.cookies and "stoken" in cookies.cookies:
+ player_info = await self.players_service.get_player(user.id, region=RegionEnum.HYPERION)
+ if player_info is not None:
+ cookies = await self.cookie_service.get(user.id, account_id=player_info.account_id)
+ if cookies is not None and cookies.data and "stoken" in cookies.data:
if stuid := next(
- (value for key, value in cookies.cookies.items() if key in ["ltuid", "login_uid"]), None
+ (value for key, value in cookies.data.items() if key in ["ltuid", "login_uid"]), None
):
- cookies.cookies["stuid"] = stuid
+ cookies.data["stuid"] = stuid
client = genshin.Client(
- cookies=cookies.cookies,
+ cookies=cookies.data,
game=genshin.types.Game.GENSHIN,
region=genshin.Region.CHINESE,
lang="zh-cn",
- uid=user_info.yuanshen_uid,
+ uid=player_info.player_id,
)
- with contextlib.suppress(Exception):
- authkey = await get_authkey_by_stoken(client)
+ authkey = await get_authkey_by_stoken(client)
if not authkey:
await message.reply_text(
"开始导入祈愿历史记录:请通过 https://paimon.moe/wish/import 获取抽卡记录链接后发送给我"
@@ -218,17 +196,12 @@ class GachaLogPlugin(Plugin.Conversation, BasePlugin.Conversation):
@conversation.state(state=INPUT_URL)
@handler.message(filters=~filters.COMMAND, block=False)
- @restricts()
- @error_callable
async def import_data_from_message(self, update: Update, _: CallbackContext) -> int:
message = update.effective_message
user = update.effective_user
if message.document:
await self.import_from_file(user, message)
return ConversationHandler.END
- elif not message.text:
- await message.reply_text("请发送正确的抽卡记录链接")
- return INPUT_URL
authkey = from_url_get_authkey(message.text)
reply = await message.reply_text("小派蒙正在从服务器获取数据,请稍后")
await message.reply_chat_action(ChatAction.TYPING)
@@ -239,27 +212,16 @@ class GachaLogPlugin(Plugin.Conversation, BasePlugin.Conversation):
@conversation.entry_point
@handler(CommandHandler, command="gacha_log_delete", filters=filters.ChatType.PRIVATE, block=False)
@handler(MessageHandler, filters=filters.Regex("^删除抽卡记录(.*)") & filters.ChatType.PRIVATE, block=False)
- @restricts()
- @error_callable
async def command_start_delete(self, update: Update, context: CallbackContext) -> int:
message = update.effective_message
user = update.effective_user
logger.info("用户 %s[%s] 删除抽卡记录命令请求", user.full_name, user.id)
try:
- client = await get_genshin_client(user.id, need_cookie=False)
+ client = await self.helper.get_genshin_client(user.id, need_cookie=False)
context.chat_data["uid"] = client.uid
- except UserNotFoundError:
+ except PlayerNotFoundError:
logger.info("未查询到用户 %s[%s] 所绑定的账号信息", user.full_name, user.id)
- buttons = [[InlineKeyboardButton("点我绑定账号", url=create_deep_linked_url(context.bot.username, "set_uid"))]]
- if filters.ChatType.GROUPS.filter(message):
- reply_message = await message.reply_text(
- "未查询到您所绑定的账号信息,请先私聊派蒙绑定账号", reply_markup=InlineKeyboardMarkup(buttons)
- )
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 30)
-
- self._add_delete_message_job(context, message.chat_id, message.message_id, 30)
- else:
- await message.reply_text("未查询到您所绑定的账号信息,请先绑定账号", reply_markup=InlineKeyboardMarkup(buttons))
+ await message.reply_text("未查询到您所绑定的账号信息,请先绑定账号")
return ConversationHandler.END
_, status = await self.gacha_log.load_history_info(str(user.id), str(client.uid), only_status=True)
if not status:
@@ -270,8 +232,6 @@ class GachaLogPlugin(Plugin.Conversation, BasePlugin.Conversation):
@conversation.state(state=CONFIRM_DELETE)
@handler.message(filters=filters.TEXT & ~filters.COMMAND, block=False)
- @restricts()
- @error_callable
async def command_confirm_delete(self, update: Update, context: CallbackContext) -> int:
message = update.effective_message
user = update.effective_user
@@ -282,11 +242,10 @@ class GachaLogPlugin(Plugin.Conversation, BasePlugin.Conversation):
await message.reply_text("已取消")
return ConversationHandler.END
- @handler(CommandHandler, command="gacha_log_force_delete", block=False)
- @bot_admins_rights_check
+ @handler(CommandHandler, command="gacha_log_force_delete", block=False, admin=True)
async def command_gacha_log_force_delete(self, update: Update, context: CallbackContext):
message = update.effective_message
- args = get_args(context)
+ args = self.get_args(context)
if not args:
await message.reply_text("请指定用户ID")
return
@@ -294,7 +253,7 @@ class GachaLogPlugin(Plugin.Conversation, BasePlugin.Conversation):
cid = int(args[0])
if cid < 0:
raise ValueError("Invalid cid")
- client = await get_genshin_client(cid, need_cookie=False)
+ client = await self.helper.get_genshin_client(cid, need_cookie=False)
_, status = await self.gacha_log.load_history_info(str(cid), str(client.uid), only_status=True)
if not status:
await message.reply_text("该用户还没有导入抽卡记录")
@@ -303,21 +262,19 @@ class GachaLogPlugin(Plugin.Conversation, BasePlugin.Conversation):
await message.reply_text("抽卡记录已强制删除" if status else "抽卡记录删除失败")
except GachaLogNotFound:
await message.reply_text("该用户还没有导入抽卡记录")
- except UserNotFoundError:
+ except PlayerNotFoundError:
await message.reply_text("该用户暂未绑定账号")
except (ValueError, IndexError):
await message.reply_text("用户ID 不合法")
@handler(CommandHandler, command="gacha_log_export", filters=filters.ChatType.PRIVATE, block=False)
@handler(MessageHandler, filters=filters.Regex("^导出抽卡记录(.*)") & filters.ChatType.PRIVATE, block=False)
- @restricts()
- @error_callable
async def command_start_export(self, update: Update, context: CallbackContext) -> None:
message = update.effective_message
user = update.effective_user
logger.info("用户 %s[%s] 导出抽卡记录命令请求", user.full_name, user.id)
try:
- client = await get_genshin_client(user.id, need_cookie=False)
+ client = await self.helper.get_genshin_client(user.id, need_cookie=False)
await message.reply_chat_action(ChatAction.TYPING)
path = await self.gacha_log.gacha_log_to_uigf(str(user.id), str(client.uid))
await message.reply_chat_action(ChatAction.UPLOAD_DOCUMENT)
@@ -332,42 +289,31 @@ class GachaLogPlugin(Plugin.Conversation, BasePlugin.Conversation):
await message.reply_text("导入失败,可能文件包含的祈愿记录所属 uid 与你当前绑定的 uid 不同")
except GachaLogFileError:
await message.reply_text("导入失败,数据格式错误")
- except UserNotFoundError:
+ except PlayerNotFoundError:
logger.info("未查询到用户 %s[%s] 所绑定的账号信息", user.full_name, user.id)
- buttons = [[InlineKeyboardButton("点我绑定账号", url=create_deep_linked_url(context.bot.username, "set_uid"))]]
- if filters.ChatType.GROUPS.filter(message):
- reply_message = await message.reply_text(
- "未查询到您所绑定的账号信息,请先私聊派蒙绑定账号", reply_markup=InlineKeyboardMarkup(buttons)
- )
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 30)
-
- self._add_delete_message_job(context, message.chat_id, message.message_id, 30)
- else:
- await message.reply_text("未查询到您所绑定的账号信息,请先绑定账号", reply_markup=InlineKeyboardMarkup(buttons))
+ await message.reply_text("未查询到您所绑定的账号信息,请先绑定账号")
@handler(CommandHandler, command="gacha_log", block=False)
@handler(MessageHandler, filters=filters.Regex("^抽卡记录?(武器|角色|常驻|)$"), block=False)
- @restricts()
- @error_callable
async def command_start_analysis(self, update: Update, context: CallbackContext) -> None:
message = update.effective_message
user = update.effective_user
pool_type = BannerType.CHARACTER1
- if args := get_args(context):
+ if args := self.get_args(context):
if "武器" in args:
pool_type = BannerType.WEAPON
elif "常驻" in args:
pool_type = BannerType.STANDARD
logger.info("用户 %s[%s] 抽卡记录命令请求 || 参数 %s", user.full_name, user.id, pool_type.name)
try:
- client = await get_genshin_client(user.id, need_cookie=False)
+ client = await self.helper.get_genshin_client(user.id, need_cookie=False)
await message.reply_chat_action(ChatAction.TYPING)
data = await self.gacha_log.get_analysis(user.id, client, pool_type, self.assets_service)
if isinstance(data, str):
reply_message = await message.reply_text(data)
if filters.ChatType.GROUPS.filter(message):
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 300)
- self._add_delete_message_job(context, message.chat_id, message.message_id, 300)
+ self.add_delete_message_job(reply_message, delay=300)
+ self.add_delete_message_job(message, delay=300)
else:
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
png_data = await self.template_service.render(
@@ -380,29 +326,26 @@ class GachaLogPlugin(Plugin.Conversation, BasePlugin.Conversation):
[InlineKeyboardButton("点我导入", url=create_deep_linked_url(context.bot.username, "gacha_log_import"))]
]
await message.reply_text("派蒙没有找到你的抽卡记录,快来点击按钮私聊派蒙导入吧~", reply_markup=InlineKeyboardMarkup(buttons))
- except UserNotFoundError:
+ except PlayerNotFoundError:
logger.info("未查询到用户 %s[%s] 所绑定的账号信息", user.full_name, user.id)
buttons = [[InlineKeyboardButton("点我绑定账号", url=create_deep_linked_url(context.bot.username, "set_uid"))]]
if filters.ChatType.GROUPS.filter(message):
reply_message = await message.reply_text(
"未查询到您所绑定的账号信息,请先私聊派蒙绑定账号", reply_markup=InlineKeyboardMarkup(buttons)
)
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 30)
-
- self._add_delete_message_job(context, message.chat_id, message.message_id, 30)
+ self.add_delete_message_job(reply_message, delay=30)
+ self.add_delete_message_job(message, delay=30)
else:
await message.reply_text("未查询到您所绑定的账号信息,请先绑定账号", reply_markup=InlineKeyboardMarkup(buttons))
@handler(CommandHandler, command="gacha_count", block=True)
@handler(MessageHandler, filters=filters.Regex("^抽卡统计?(武器|角色|常驻|仅五星|)$"), block=True)
- @restricts()
- @error_callable
async def command_start_count(self, update: Update, context: CallbackContext) -> None:
message = update.effective_message
user = update.effective_user
pool_type = BannerType.CHARACTER1
all_five = False
- if args := get_args(context):
+ if args := self.get_args(context):
if "武器" in args:
pool_type = BannerType.WEAPON
elif "常驻" in args:
@@ -411,7 +354,7 @@ class GachaLogPlugin(Plugin.Conversation, BasePlugin.Conversation):
all_five = True
logger.info("用户 %s[%s] 抽卡统计命令请求 || 参数 %s || 仅五星 %s", user.full_name, user.id, pool_type.name, all_five)
try:
- client = await get_genshin_client(user.id, need_cookie=False)
+ client = await self.get_genshin_client(user.id, need_cookie=False)
group = filters.ChatType.GROUPS.filter(message)
await message.reply_chat_action(ChatAction.TYPING)
if all_five:
@@ -421,8 +364,8 @@ class GachaLogPlugin(Plugin.Conversation, BasePlugin.Conversation):
if isinstance(data, str):
reply_message = await message.reply_text(data)
if filters.ChatType.GROUPS.filter(message):
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 300)
- self._add_delete_message_job(context, message.chat_id, message.message_id, 300)
+ self.add_delete_message_job(reply_message)
+ self.add_delete_message_job(message)
else:
document = False
if data["hasMore"] and not group:
@@ -446,15 +389,15 @@ class GachaLogPlugin(Plugin.Conversation, BasePlugin.Conversation):
[InlineKeyboardButton("点我导入", url=create_deep_linked_url(context.bot.username, "gacha_log_import"))]
]
await message.reply_text("派蒙没有找到你的抽卡记录,快来私聊派蒙导入吧~", reply_markup=InlineKeyboardMarkup(buttons))
- except UserNotFoundError:
+ except PlayerNotFoundError:
logger.info("未查询到用户 %s[%s] 所绑定的账号信息", user.full_name, user.id)
buttons = [[InlineKeyboardButton("点我绑定账号", url=create_deep_linked_url(context.bot.username, "set_uid"))]]
if filters.ChatType.GROUPS.filter(message):
reply_message = await message.reply_text(
"未查询到您所绑定的账号信息,请先私聊派蒙绑定账号", reply_markup=InlineKeyboardMarkup(buttons)
)
- self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 30)
+ self.add_delete_message_job(reply_message, delay=30)
- self._add_delete_message_job(context, message.chat_id, message.message_id, 30)
+ self.add_delete_message_job(message, delay=30)
else:
await message.reply_text("未查询到您所绑定的账号信息,请先绑定账号", reply_markup=InlineKeyboardMarkup(buttons))
diff --git a/plugins/system/auth.py b/plugins/group/captcha.py
similarity index 84%
rename from plugins/system/auth.py
rename to plugins/group/captcha.py
index ff75c55..b80c8e6 100644
--- a/plugins/system/auth.py
+++ b/plugins/group/captcha.py
@@ -1,23 +1,20 @@
import asyncio
import random
import time
-from typing import Tuple, Union, Dict, List, Optional
+from typing import Tuple, Union, Dict, Optional
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, ChatPermissions, ChatMember, Message, User
from telegram.constants import ParseMode
-from telegram.error import BadRequest, RetryAfter
-from telegram.ext import CallbackContext, CallbackQueryHandler, ChatMemberHandler
+from telegram.error import BadRequest
+from telegram.ext import CallbackContext, CallbackQueryHandler, ChatMemberHandler, filters
from telegram.helpers import escape_markdown
-from core.base.mtproto import MTProto
-from core.base.redisdb import RedisDB
-from core.bot import bot
from core.config import config
+from core.dependence.mtproto import MTProto
+from core.dependence.redisdb import RedisDB
from core.plugin import Plugin, handler
-from core.quiz import QuizService
+from core.services.quiz.services import QuizService
from utils.chatmember import extract_status_change
-from utils.decorators.error import error_callable
-from utils.decorators.restricts import restricts
from utils.log import logger
try:
@@ -47,7 +44,7 @@ FullChatPermissions = ChatPermissions(
)
-class GroupJoiningVerification(Plugin):
+class GroupCaptcha(Plugin):
"""群验证模块"""
def __init__(self, quiz_service: QuizService = None, mtp: MTProto = None, redis: RedisDB = None):
@@ -55,12 +52,12 @@ class GroupJoiningVerification(Plugin):
self.time_out = 120
self.kick_time = 120
self.lock = asyncio.Lock()
- self.chat_administrators_cache: Dict[Union[str, int], Tuple[float, List[ChatMember]]] = {}
+ self.chat_administrators_cache: Dict[Union[str, int], Tuple[float, Tuple[ChatMember]]] = {}
self.is_refresh_quiz = False
self.mtp = mtp.client
self.redis = redis.client
- async def __async_init__(self):
+ async def initialize(self):
logger.info("群验证模块正在刷新问题列表")
await self.refresh_quiz()
logger.success("群验证模块刷新问题列表成功")
@@ -71,7 +68,7 @@ class GroupJoiningVerification(Plugin):
await self.quiz_service.refresh_quiz()
self.is_refresh_quiz = True
- async def get_chat_administrators(self, context: CallbackContext, chat_id: Union[str, int]) -> List[ChatMember]:
+ async def get_chat_administrators(self, context: CallbackContext, chat_id: Union[str, int]) -> Tuple[ChatMember]:
async with self.lock:
cache_data = self.chat_administrators_cache.get(f"{chat_id}")
if cache_data is not None:
@@ -83,43 +80,40 @@ class GroupJoiningVerification(Plugin):
return chat_administrators
@staticmethod
- def is_admin(chat_administrators: List[ChatMember], user_id: int) -> bool:
+ def is_admin(chat_administrators: Tuple[ChatMember], user_id: int) -> bool:
return any(admin.user.id == user_id for admin in chat_administrators)
async def kick_member_job(self, context: CallbackContext):
job = context.job
- logger.info(f"踢出用户 user_id[{job.user_id}] 在 chat_id[{job.chat_id}]")
+ logger.info("踢出用户 user_id[%s] 在 chat_id[%s]", job.user_id, job.chat_id)
try:
await context.bot.ban_chat_member(
chat_id=job.chat_id, user_id=job.user_id, until_date=int(time.time()) + self.kick_time
)
except BadRequest as exc:
- logger.error(f"Auth模块在 chat_id[{job.chat_id}] user_id[{job.user_id}] 执行kick失败")
- logger.exception(exc)
+ logger.error("GroupCaptcha插件在 chat_id[%s] user_id[%s] 执行kick失败", job.chat_id, job.user_id, exc_info=exc)
@staticmethod
async def clean_message_job(context: CallbackContext):
job = context.job
- logger.debug(f"删除消息 chat_id[{job.chat_id}] 的 message_id[{job.data}]")
+ logger.debug("删除消息 chat_id[%s] 的 message_id[%s]", job.chat_id, job.data)
try:
await context.bot.delete_message(chat_id=job.chat_id, message_id=job.data)
except BadRequest as exc:
- if "not found" in str(exc):
- logger.warning(f"Auth模块删除消息 chat_id[{job.chat_id}] message_id[{job.data}]失败 消息不存在")
- elif "Message can't be deleted" in str(exc):
- logger.warning(f"Auth模块删除消息 chat_id[{job.chat_id}] message_id[{job.data}]失败 消息无法删除 可能是没有授权")
+ if "not found" in exc.message:
+ logger.warning("GroupCaptcha插件删除消息 chat_id[%s] message_id[%s]失败 消息不存在", job.chat_id, job.data)
+ elif "Message can't be deleted" in exc.message:
+ logger.warning("GroupCaptcha插件删除消息 chat_id[%s] message_id[%s]失败 消息无法删除 可能是没有授权", job.chat_id, job.data)
else:
- logger.error(f"Auth模块删除消息 chat_id[{job.chat_id}] message_id[{job.data}]失败")
- logger.exception(exc)
+ logger.error("GroupCaptcha插件删除消息 chat_id[%s] message_id[%s]失败", job.chat_id, job.data, exc_info=exc)
@staticmethod
async def restore_member(context: CallbackContext, chat_id: int, user_id: int):
- logger.debug(f"重置用户权限 user_id[{user_id}] 在 chat_id[{chat_id}]")
+ logger.debug("重置用户权限 user_id[%s] 在 chat_id[%s]", chat_id, user_id)
try:
await context.bot.restrict_chat_member(chat_id=chat_id, user_id=user_id, permissions=FullChatPermissions)
except BadRequest as exc:
- logger.error(f"Auth模块在 chat_id[{chat_id}] user_id[{user_id}] 执行restore失败")
- logger.exception(exc)
+ logger.error("GroupCaptcha插件在 chat_id[%s] user_id[%s] 执行restore失败", chat_id, user_id, exc_info=exc)
async def get_new_chat_members_message(self, user: User, context: CallbackContext) -> Optional[Message]:
qname = f"plugin:auth:new_chat_members_message:{user.id}"
@@ -134,31 +128,29 @@ class GroupJoiningVerification(Plugin):
await self.redis.set(qname, message.to_json(), ex=60)
@handler(CallbackQueryHandler, pattern=r"^auth_admin\|", block=False)
- @error_callable
- @restricts(without_overlapping=True)
async def admin(self, update: Update, context: CallbackContext) -> None:
async def admin_callback(callback_query_data: str) -> Tuple[str, int]:
_data = callback_query_data.split("|")
_result = _data[1]
_user_id = int(_data[2])
- logger.debug(f"admin_callback函数返回 result[{_result}] user_id[{_user_id}]")
+ logger.debug("admin_callback函数返回 result[%s] user_id[%s]", _result, _user_id)
return _result, _user_id
callback_query = update.callback_query
user = callback_query.from_user
message = callback_query.message
chat = message.chat
- logger.info(f"用户 {user.full_name}[{user.id}] 在群 {chat.title}[{chat.id}] 点击Auth管理员命令")
+ logger.info("用户 %s[%s] 在群 %s[%s] 点击Auth管理员命令", user.full_name, user.id, chat.title, chat.id)
chat_administrators = await self.get_chat_administrators(context, chat_id=chat.id)
if not self.is_admin(chat_administrators, user.id):
- logger.debug(f"用户 {user.full_name}[{user.id}] 在群 {chat.title}[{chat.id}] 非群管理")
+ logger.debug("用户 %s[%s] 在群 %s[%s] 非群管理", user.full_name, user.id, chat.title, chat.id)
await callback_query.answer(text="你不是管理!\n" + config.notice.user_mismatch, show_alert=True)
return
result, user_id = await admin_callback(callback_query.data)
try:
member_info = await context.bot.get_chat_member(chat.id, user_id)
except BadRequest as error:
- logger.warning(f"获取用户 {user_id} 在群 {chat.title}[{chat.id}] 信息失败 \n", error)
+ logger.warning("获取用户 %s 在群 %s[%s] 信息失败 \n %s", user_id, chat.title, chat.id, error.message)
user_info = f"{user_id}"
else:
user_info = member_info.user.mention_markdown_v2()
@@ -169,12 +161,12 @@ class GroupJoiningVerification(Plugin):
if schedule := context.job_queue.scheduler.get_job(f"{chat.id}|{user_id}|auth_kick"):
schedule.remove()
await message.edit_text(f"{user_info} 被 {user.mention_markdown_v2()} 放行", parse_mode=ParseMode.MARKDOWN_V2)
- logger.info(f"用户 user_id[{user_id}] 在群 {chat.title}[{chat.id}] 被管理放行")
+ logger.info("用户 %s[%s] 在群 %s[%s] 被管理放行", user.full_name, user.id, chat.title, chat.id)
elif result == "kick":
await callback_query.answer(text="驱离", show_alert=False)
await context.bot.ban_chat_member(chat.id, user_id)
await message.edit_text(f"{user_info} 被 {user.mention_markdown_v2()} 驱离", parse_mode=ParseMode.MARKDOWN_V2)
- logger.info(f"用户 user_id[{user_id}] 在群 {chat.title}[{chat.id}] 被管理踢出")
+ logger.info("用户 %s[%s] 在群 %s[%s] 被管理踢出", user.full_name, user.id, chat.title, chat.id)
elif result == "unban":
await callback_query.answer(text="解除驱离", show_alert=False)
await self.restore_member(context, chat.id, user_id)
@@ -183,16 +175,14 @@ class GroupJoiningVerification(Plugin):
await message.edit_text(
f"{user_info} 被 {user.mention_markdown_v2()} 解除驱离", parse_mode=ParseMode.MARKDOWN_V2
)
- logger.info(f"用户 user_id[{user_id}] 在群 {chat.title}[{chat.id}] 被管理解除封禁")
+ logger.info("用户 user_id[%s] 在群 %s[%s] 被管理解除封禁", user_id, chat.title, chat.id)
else:
- logger.warning(f"auth 模块 admin 函数 发现未知命令 result[{result}]")
+ logger.warning("auth 模块 admin 函数 发现未知命令 result[%s]", result)
await context.bot.send_message(chat.id, "派蒙这边收到了错误的消息!请检查详细日记!")
if schedule := context.job_queue.scheduler.get_job(f"{chat.id}|{user_id}|auth_kick"):
schedule.remove()
@handler(CallbackQueryHandler, pattern=r"^auth_challenge\|", block=False)
- @error_callable
- @restricts(without_overlapping=True)
async def query(self, update: Update, context: CallbackContext) -> None:
async def query_callback(callback_query_data: str) -> Tuple[int, bool, str, str]:
_data = callback_query_data.split("|")
@@ -205,8 +195,11 @@ class GroupJoiningVerification(Plugin):
_answer_encode = _answer.text
_question_encode = _question.text
logger.debug(
- f"query_callback函数返回 user_id[{_user_id}] result[{_result}] \n"
- f"question_encode[{_question_encode}] answer_encode[{_answer_encode}]"
+ "query_callback函数返回 user_id[%s] result[%s] \nquestion_encode[%s] answer_encode[%s]",
+ _user_id,
+ _result,
+ _question_encode,
+ _answer_encode,
)
return _user_id, _result, _question_encode, _answer_encode
@@ -215,11 +208,13 @@ class GroupJoiningVerification(Plugin):
message = callback_query.message
chat = message.chat
user_id, result, question, answer = await query_callback(callback_query.data)
- logger.info(f"用户 {user.full_name}[{user.id}] 在群 {chat.title}[{chat.id}] 点击Auth认证命令 ")
+ logger.info("用户 %s[%s] 在群 %s[%s] 点击Auth认证命令", user.full_name, user.id, chat.title, chat.id)
if user.id != user_id:
await callback_query.answer(text="这不是你的验证!\n" + config.notice.user_mismatch, show_alert=True)
return
- logger.info(f"用户 {user.full_name}[{user.id}] 在群 {chat.title}[{chat.id}] 认证结果为 {'通过' if result else '失败'}")
+ logger.info(
+ "用户 %s[%s] 在群 %s[%s] 认证结果为 %s", user.full_name, user.id, chat.title, chat.id, "通过" if result else "失败"
+ )
if result:
buttons = [[InlineKeyboardButton("驱离", callback_data=f"auth_admin|kick|{user.id}")]]
await callback_query.answer(text="验证成功", show_alert=False)
@@ -231,7 +226,7 @@ class GroupJoiningVerification(Plugin):
f"问题:{escape_markdown(question, version=2)} \n"
f"回答:{escape_markdown(answer, version=2)}"
)
- logger.info(f"用户 user_id[{user_id}] 在群 {chat.title}[{chat.id}] 验证成功")
+ logger.info("用户 user_id[%s] 在群 %s[%s] 验证成功", user_id, chat.title, chat.id)
else:
buttons = [
[
@@ -249,24 +244,23 @@ class GroupJoiningVerification(Plugin):
f"问题:{escape_markdown(question, version=2)} \n"
f"回答:{escape_markdown(answer, version=2)}"
)
- logger.info(f"用户 user_id[{user_id}] 在群 {chat.title}[{chat.id}] 验证失败")
+ logger.info("用户 user_id[%s] 在群 %s[%s] 验证失败", user_id, chat.title, chat.id)
try:
await message.edit_text(text, reply_markup=InlineKeyboardMarkup(buttons), parse_mode=ParseMode.MARKDOWN_V2)
except BadRequest as exc:
- if "are exactly the same as " in str(exc):
+ if "are exactly the same as " in exc.message:
logger.warning("编辑消息发生异常,可能为用户点按多次键盘导致")
else:
raise exc
if schedule := context.job_queue.scheduler.get_job(f"{chat.id}|{user.id}|auth_kick"):
schedule.remove()
- @handler.message.new_chat_members(priority=1)
- @error_callable
+ @handler.message(filters=filters.StatusUpdate.NEW_CHAT_MEMBERS, block=False)
async def new_mem(self, update: Update, context: CallbackContext) -> None:
message = update.effective_message
chat = message.chat
- if len(bot.config.verify_groups) >= 1:
- for verify_group in bot.config.verify_groups:
+ if len(config.verify_groups) >= 1:
+ for verify_group in config.verify_groups:
if verify_group == chat.id:
break
else:
@@ -280,11 +274,10 @@ class GroupJoiningVerification(Plugin):
await self.set_new_chat_members_message(user, message)
@handler.chat_member(chat_member_types=ChatMemberHandler.CHAT_MEMBER, block=False)
- @error_callable
async def track_users(self, update: Update, context: CallbackContext) -> None:
chat = update.effective_chat
- if len(bot.config.verify_groups) >= 1:
- for verify_group in bot.config.verify_groups:
+ if len(config.verify_groups) >= 1:
+ for verify_group in config.verify_groups:
if verify_group == chat.id:
break
else:
@@ -301,7 +294,7 @@ class GroupJoiningVerification(Plugin):
if was_member and not is_member:
logger.info("用户 %s[%s] 退出群聊 %s[%s]", user.full_name, user.id, chat.title, chat.id)
return
- elif not was_member and is_member:
+ if not was_member and is_member:
logger.info("用户 %s[%s] 尝试加入群 %s[%s]", user.full_name, user.id, chat.title, chat.id)
if user.is_bot:
return
@@ -323,8 +316,7 @@ class GroupJoiningVerification(Plugin):
parse_mode=ParseMode.HTML,
)
return
- else:
- raise exc
+ raise exc
new_chat_members_message = await self.get_new_chat_members_message(user, context)
question_id = random.choice(question_id_list) # nosec
question = await self.quiz_service.get_question(question_id)
diff --git a/plugins/jobs/public_cookies.py b/plugins/jobs/public_cookies.py
index eacd0a9..80e4bd0 100644
--- a/plugins/jobs/public_cookies.py
+++ b/plugins/jobs/public_cookies.py
@@ -1,25 +1,18 @@
-import asyncio
import datetime
from telegram.ext import CallbackContext
-from core.cookies.services import PublicCookiesService
from core.plugin import Plugin, job
+from core.services.cookies.services import PublicCookiesService
from utils.log import logger
+__all__ = ("PublicCookiesPlugin",)
-class PublicCookies(Plugin):
+
+class PublicCookiesPlugin(Plugin):
def __init__(self, public_cookies_service: PublicCookiesService = None):
self.public_cookies_service = public_cookies_service
- async def __async_init__(self):
- async def _refresh():
- logger.info("正在刷新公共Cookies池")
- await self.public_cookies_service.refresh()
- logger.success("刷新公共Cookies池成功")
-
- asyncio.create_task(_refresh())
-
@job.run_repeating(interval=datetime.timedelta(hours=2), name="PublicCookiesRefresh")
async def refresh(self, _: CallbackContext):
logger.info("正在刷新公共Cookies池")
diff --git a/plugins/jobs/sign.py b/plugins/jobs/sign.py
index bcb06eb..9cfd92f 100644
--- a/plugins/jobs/sign.py
+++ b/plugins/jobs/sign.py
@@ -1,105 +1,25 @@
import datetime
-from aiohttp import ClientConnectorError
-from genshin import GenshinException, AlreadyClaimed, InvalidCookies
-from httpx import TimeoutException
-from telegram.constants import ParseMode
-from telegram.error import BadRequest, Forbidden
from telegram.ext import CallbackContext
-from core.base.redisdb import RedisDB
-from core.cookies import CookiesService
from core.plugin import Plugin, job
-from core.sign.models import SignStatusEnum
-from core.sign.services import SignServices
-from core.user import UserService
-from plugins.genshin.sign import SignSystem, NeedChallenge
-from plugins.system.errorhandler import notice_chat_id
-from plugins.system.sign_status import SignStatus
-from utils.helpers import get_genshin_client
+from plugins.genshin.sign import SignSystem
+from plugins.tools.sign import SignJobType
from utils.log import logger
class SignJob(Plugin):
- def __init__(
- self,
- sign_service: SignServices = None,
- user_service: UserService = None,
- cookies_service: CookiesService = None,
- redis: RedisDB = None,
- ):
- self.sign_service = sign_service
- self.cookies_service = cookies_service
- self.user_service = user_service
- self.sign_system = SignSystem(redis)
+ def __init__(self, sign_system: SignSystem):
+ self.sign_system = sign_system
@job.run_daily(time=datetime.time(hour=0, minute=1, second=0), name="SignJob")
async def sign(self, context: CallbackContext):
- logger.info("正在执行自动签到" if context.job.name == "SignJob" else "正在执行自动重签")
- sign_list = await self.sign_service.get_all()
- for sign_db in sign_list:
- user_id = sign_db.user_id
- if sign_db.status in [
- SignStatusEnum.INVALID_COOKIES,
- SignStatusEnum.FORBIDDEN,
- ]:
- continue
- if context.job.name == "SignJob":
- if sign_db.status not in [SignStatusEnum.STATUS_SUCCESS, SignStatusEnum.ALREADY_CLAIMED]:
- continue
- elif context.job.name == "SignAgainJob" and sign_db.status in [
- SignStatusEnum.STATUS_SUCCESS,
- SignStatusEnum.ALREADY_CLAIMED,
- ]:
- continue
- try:
- client = await get_genshin_client(user_id)
- text = await self.sign_system.start_sign(
- client, is_sleep=True, is_raise=True, title="自动签到" if context.job.name == "SignJob" else "自动重新签到"
- )
- sign_db.status = SignStatusEnum.STATUS_SUCCESS
- except InvalidCookies:
- text = "自动签到执行失败,Cookie无效"
- sign_db.status = SignStatusEnum.INVALID_COOKIES
- except AlreadyClaimed:
- text = "今天旅行者已经签到过了~"
- sign_db.status = SignStatusEnum.ALREADY_CLAIMED
- except GenshinException as exc:
- text = f"自动签到执行失败,API返回信息为 {str(exc)}"
- sign_db.status = SignStatusEnum.GENSHIN_EXCEPTION
- except TimeoutException:
- text = "签到失败了呜呜呜 ~ 服务器连接超时 服务器熟啦 ~ "
- sign_db.status = SignStatusEnum.TIMEOUT_ERROR
- except ClientConnectorError as exc:
- logger.warning("aiohttp 请求错误 %s", str(exc))
- text = "签到失败了呜呜呜 ~ 链接服务器发生错误 服务器熟啦 ~ "
- sign_db.status = SignStatusEnum.TIMEOUT_ERROR
- except NeedChallenge:
- text = "签到失败,触发验证码风控"
- sign_db.status = SignStatusEnum.NEED_CHALLENGE
- except Exception as exc:
- logger.error(f"执行自动签到时发生错误 用户UID[{user_id}]")
- logger.exception(exc)
- text = "签到失败了呜呜呜 ~ 执行自动签到时发生错误"
- if sign_db.chat_id < 0:
- text = f'NOTICE {sign_db.user_id}\n\n{text}'
- try:
- await context.bot.send_message(sign_db.chat_id, text, parse_mode=ParseMode.HTML)
- except BadRequest as exc:
- logger.error("执行自动签到时发生错误 message[%s] user_id[%s]", exc.message, user_id)
- sign_db.status = SignStatusEnum.BAD_REQUEST
- except Forbidden as exc:
- logger.error("执行自动签到时发生错误 message[%s] user_id[%s]", exc.message, user_id)
- sign_db.status = SignStatusEnum.FORBIDDEN
- except Exception as exc:
- logger.error(f"执行自动签到时发生错误 用户UID[{user_id}]")
- logger.exception(exc)
- continue
- sign_db.time_updated = datetime.datetime.now()
- await self.sign_service.update(sign_db)
- logger.info("执行自动签到完成" if context.job.name == "SignJob" else "执行自动重签完成")
- if context.job.name == "SignJob":
- context.job_queue.run_once(self.sign, when=60, name="SignAgainJob")
- elif context.job.name == "SignAgainJob":
- text = await SignStatus.get_sign_status(self.sign_service)
- await context.bot.send_message(notice_chat_id, text, parse_mode=ParseMode.HTML)
+ logger.info("正在执行自动签到")
+ await self.sign_system.do_sign_job(context, job_type=SignJobType.START)
+ logger.success("执行自动签到完成")
+ await self.re_sign(context)
+
+ async def re_sign(self, context: CallbackContext):
+ logger.info("正在执行自动重签")
+ await self.sign_system.do_sign_job(context, job_type=SignJobType.REDO)
+ logger.success("执行自动重签完成")
diff --git a/plugins/system/admin.py b/plugins/system/admin.py
deleted file mode 100644
index 984afdd..0000000
--- a/plugins/system/admin.py
+++ /dev/null
@@ -1,72 +0,0 @@
-import contextlib
-
-from telegram import Update
-from telegram.error import BadRequest, Forbidden
-from telegram.ext import CallbackContext, CommandHandler
-
-from core.admin import BotAdminService
-from core.plugin import handler, Plugin
-from utils.decorators.admins import bot_admins_rights_check
-from utils.log import logger
-
-
-class AdminPlugin(Plugin):
- """有关BOT ADMIN处理"""
-
- def __init__(self, bot_admin_service: BotAdminService = None):
- self.bot_admin_service = bot_admin_service
-
- @handler(CommandHandler, command="add_admin", block=False)
- @bot_admins_rights_check
- async def add_admin(self, update: Update, _: CallbackContext):
- message = update.effective_message
- reply_to_message = message.reply_to_message
- if reply_to_message is None:
- await message.reply_text("请回复对应消息")
- else:
- admin_list = await self.bot_admin_service.get_admin_list()
- if reply_to_message.from_user.id in admin_list:
- await message.reply_text("该用户已经存在管理员列表")
- else:
- await self.bot_admin_service.add_admin(reply_to_message.from_user.id)
- await message.reply_text("添加成功")
-
- @handler(CommandHandler, command="del_admin", block=False)
- @bot_admins_rights_check
- async def del_admin(self, update: Update, _: CallbackContext):
- message = update.effective_message
- reply_to_message = message.reply_to_message
- admin_list = await self.bot_admin_service.get_admin_list()
- if reply_to_message is None:
- await message.reply_text("请回复对应消息")
- else:
- if reply_to_message.from_user.id in admin_list:
- await self.bot_admin_service.delete_admin(reply_to_message.from_user.id)
- await message.reply_text("删除成功")
- else:
- await message.reply_text("该用户不存在管理员列表")
-
- @handler(CommandHandler, command="leave_chat", block=False)
- @bot_admins_rights_check
- async def leave_chat(self, update: Update, context: CallbackContext):
- message = update.effective_message
- try:
- args = message.text.split()
- if len(args) >= 2:
- chat_id = int(args[1])
- else:
- await message.reply_text("输入错误")
- return
- except ValueError as error:
- logger.error("获取 chat_id 发生错误! 错误信息为 \n", exc_info=error)
- await message.reply_text("输入错误")
- return
- try:
- with contextlib.suppress(BadRequest, Forbidden):
- chat = await context.bot.get_chat(chat_id)
- await message.reply_text(f"正在尝试退出群 {chat.title}[{chat.id}]")
- await context.bot.leave_chat(chat_id)
- except (BadRequest, Forbidden) as exc:
- await message.reply_text(f"退出 chat_id[{chat_id}] 发生错误! 错误信息为 {str(exc)}")
- return
- await message.reply_text(f"退出 chat_id[{chat_id}] 成功!")
diff --git a/plugins/system/chat_member.py b/plugins/system/chat_member.py
index 702f7bd..dc9fd4c 100644
--- a/plugins/system/chat_member.py
+++ b/plugins/system/chat_member.py
@@ -1,34 +1,28 @@
-import contextlib
-
-from telegram import Update, Chat, User
-from telegram.error import BadRequest
+from telegram import Chat, Update, User
+from telegram.error import NetworkError
from telegram.ext import CallbackContext, ChatMemberHandler
-from core.admin.services import BotAdminService
-from core.config import config, JoinGroups
-from core.cookies.error import CookiesNotFoundError
-from core.cookies.services import CookiesService
+from core.config import JoinGroups, config
from core.plugin import Plugin, handler
-from core.user.error import UserNotFoundError
-from core.user.services import UserService
+from core.services.cookies import CookiesService
+from core.services.players import PlayersService
+from core.services.users.services import UserAdminService
from utils.chatmember import extract_status_change
-from utils.decorators.error import error_callable
from utils.log import logger
class ChatMember(Plugin):
def __init__(
self,
- bot_admin_service: BotAdminService = None,
- user_service: UserService = None,
+ user_admin_service: UserAdminService = None,
+ players_service: PlayersService = None,
cookies_service: CookiesService = None,
):
self.cookies_service = cookies_service
- self.user_service = user_service
- self.bot_admin_service = bot_admin_service
+ self.players_service = players_service
+ self.user_admin_service = user_admin_service
@handler.chat_member(chat_member_types=ChatMemberHandler.MY_CHAT_MEMBER, block=False)
- @error_callable
async def track_chats(self, update: Update, context: CallbackContext) -> None:
result = extract_status_change(update.my_chat_member)
if result is None:
@@ -57,8 +51,7 @@ class ChatMember(Plugin):
quit_status = True
if config.join_groups == JoinGroups.NO_ALLOW:
try:
- admin_list = await self.bot_admin_service.get_admin_list()
- if user.id in admin_list:
+ if await self.user_admin_service.is_admin(user.id):
quit_status = False
else:
logger.warning("不是管理员邀请!退出群聊")
@@ -66,30 +59,27 @@ class ChatMember(Plugin):
logger.error("获取信息出现错误", exc_info=exc)
elif config.join_groups == JoinGroups.ALLOW_AUTH_USER:
try:
- user_info = await self.user_service.get_user_by_id(user.id)
- await self.cookies_service.get_cookies(user.id, user_info.region)
- except (UserNotFoundError, CookiesNotFoundError):
- logger.warning("用户 %s[%s] 邀请请求被拒绝", user.full_name, user.id)
- except Exception as exc:
+ if await self.cookies_service.get(user.id) is not None:
+ quit_status = False
+ except Exception as exc: # pylint: disable=W0703
logger.error("获取信息出现错误", exc_info=exc)
- else:
- quit_status = False
elif config.join_groups == JoinGroups.ALLOW_USER:
try:
- await self.user_service.get_user_by_id(user.id)
- except UserNotFoundError:
- logger.warning("用户 %s[%s] 邀请请求被拒绝", user.full_name, user.id)
- except Exception as exc:
+ if await self.players_service.get(user.id) is not None:
+ quit_status = False
+ except Exception as exc: # pylint: disable=W0703
logger.error("获取信息出现错误", exc_info=exc)
- else:
- quit_status = False
elif config.join_groups == JoinGroups.ALLOW_ALL:
quit_status = False
else:
quit_status = True
if quit_status:
- with contextlib.suppress(BadRequest):
+ try:
await context.bot.send_message(chat.id, "派蒙不想进去!不是旅行者的邀请!")
+ except NetworkError as exc:
+ logger.info("发送消息失败 %s", exc.message)
+ except Exception as exc:
+ logger.info("发送消息失败", exc_info=exc)
await context.bot.leave_chat(chat.id)
else:
await context.bot.send_message(chat.id, "感谢邀请小派蒙到本群!请使用 /help 查看咱已经学会的功能。")
diff --git a/plugins/system/errorhandler.py b/plugins/system/errorhandler.py
index d4a50de..f11053d 100644
--- a/plugins/system/errorhandler.py
+++ b/plugins/system/errorhandler.py
@@ -1,18 +1,29 @@
import os
import time
import traceback
+from typing import Optional
import aiofiles
-from telegram import ReplyKeyboardRemove, Update
+from aiohttp import ClientError, ClientConnectorError
+from genshin import DataNotPublic, GenshinException, InvalidCookies, TooManyRequests
+from httpx import Timeout as HttpxTimeout, HTTPError
+from telegram import ReplyKeyboardRemove, Update, InlineKeyboardMarkup, InlineKeyboardButton
from telegram.constants import ParseMode
-from telegram.error import BadRequest, Forbidden, NetworkError, TimedOut
-from telegram.ext import CallbackContext
+from telegram.error import BadRequest, Forbidden, TelegramError, TimedOut, NetworkError
+from telegram.ext import CallbackContext, ApplicationHandlerStop
+from telegram.helpers import create_deep_linked_url
-from core.bot import bot
from core.config import config
from core.plugin import Plugin, error_handler
-from modules.errorpush import PbClient, SentryClient, PbClientException, SentryClientException
+from modules.apihelper.error import APIHelperException, APIHelperTimedOut, ResponseException, ReturnCodeError
+from modules.errorpush import (
+ PbClient,
+ PbClientException,
+ SentryClient,
+ SentryClientException,
+)
from utils.log import logger
+from utils.patch.aiohttp import AioHttpTimeoutException
try:
import ujson as jsonlib
@@ -20,34 +31,189 @@ try:
except ImportError:
import json as jsonlib
-notice_chat_id = bot.config.error.notification_chat_id
-current_dir = os.getcwd()
-logs_dir = os.path.join(current_dir, "logs")
-if not os.path.exists(logs_dir):
- os.mkdir(logs_dir)
-report_dir = os.path.join(current_dir, "report")
-if not os.path.exists(report_dir):
- os.mkdir(report_dir)
-pb_client = PbClient(config.error.pb_url, config.error.pb_sunset, config.error.pb_max_lines)
-sentry = SentryClient(config.error.sentry_dsn)
-
class ErrorHandler(Plugin):
- @error_handler(block=False) # pylint: disable=E1123, E1120
- async def error_handler(self, update: object, context: CallbackContext) -> None:
- """记录错误并发送消息通知开发人员。 logger the error and send a telegram message to notify the developer."""
+ ERROR_MSG_PREFIX = "出错了呜呜呜 ~ "
+ SEND_MSG_ERROR_NOTICE = "发送 update_id[%s] 错误信息失败 错误信息为 [%s]"
- if isinstance(context.error, NetworkError):
- logger.error("Bot请求异常 %s", context.error.message)
+ def __init__(self):
+ self.notice_chat_id = config.error.notification_chat_id
+ current_dir = os.getcwd()
+ logs_dir = os.path.join(current_dir, "logs")
+ if not os.path.exists(logs_dir):
+ os.mkdir(logs_dir)
+ self.report_dir = os.path.join(current_dir, "report")
+ if not os.path.exists(self.report_dir):
+ os.mkdir(self.report_dir)
+ self.pb_client = PbClient(config.error.pb_url, config.error.pb_sunset, config.error.pb_max_lines)
+ self.sentry = SentryClient(config.error.sentry_dsn)
+
+ async def notice_user(self, update: object, context: CallbackContext, content: str):
+ if not isinstance(update, Update):
+ logger.warning("错误的消息类型 %s", repr(update))
+ return None
+ if update.inline_query is not None: # 忽略 inline_query
+ return None
+
+ if "重新绑定" in content:
+ buttons = InlineKeyboardMarkup(
+ [[InlineKeyboardButton("点我重新绑定", url=create_deep_linked_url(context.bot.username, "set_cookie"))]]
+ )
+ elif "通过验证" in content:
+ buttons = InlineKeyboardMarkup(
+ [
+ [
+ InlineKeyboardButton(
+ "点我通过验证", url=create_deep_linked_url(context.bot.username, "verify_verification")
+ )
+ ]
+ ]
+ )
+ else:
+ buttons = ReplyKeyboardRemove()
+
+ user = update.effective_user
+ message = update.effective_message
+ chat = update.effective_chat
+
+ if chat.id == user.id:
+ logger.info("尝试通知用户 %s[%s] 错误信息[%s]", user.full_name, user.id, content)
+ else:
+ logger.info("尝试通知用户 %s[%s] 在 %s[%s] 的错误信息[%s]", user.full_name, user.id, chat.title, chat.id, content)
+ try:
+ if update.callback_query:
+ await update.callback_query.answer(content, show_alert=True)
+ return None
+ return await message.reply_text(content, reply_markup=buttons, allow_sending_without_reply=True)
+ except TelegramError as exc:
+ logger.error(self.SEND_MSG_ERROR_NOTICE, update.update_id, exc.message)
+ except Exception as exc:
+ logger.error(self.SEND_MSG_ERROR_NOTICE, update.update_id, repr(exc), exc_info=exc)
+
+ def create_notice_task(self, update: object, context: CallbackContext, content: str):
+ context.application.create_task(self.notice_user(update, context, content), update)
+
+ @error_handler()
+ async def process_genshin_exception(self, update: object, context: CallbackContext):
+ if not isinstance(context.error, GenshinException) or not isinstance(update, Update):
return
+ exc = context.error
+ notice: Optional[str] = None
+ if isinstance(exc, TooManyRequests):
+ notice = self.ERROR_MSG_PREFIX + "Cookie 无效,请尝试重新绑定"
+ elif isinstance(exc, InvalidCookies):
+ if exc.retcode in (10001, -100):
+ notice = self.ERROR_MSG_PREFIX + "Cookie 无效,请尝试重新绑定"
+ elif exc.retcode == 10103:
+ notice = self.ERROR_MSG_PREFIX + "Cookie 有效,但没有绑定到游戏帐户,请尝试登录通行证,在账号管理里面选择账号游戏信息,将原神设置为默认角色。"
+ else:
+ logger.error("未知Cookie错误", exc_info=exc)
+ notice = self.ERROR_MSG_PREFIX + f"Cookie 无效 错误信息为 {exc.original} 请尝试重新绑定"
+ elif isinstance(exc, DataNotPublic):
+ notice = self.ERROR_MSG_PREFIX + "查询的用户数据未公开"
+ else:
+ if exc.retcode == -130:
+ notice = self.ERROR_MSG_PREFIX + "未设置默认角色,请尝试重新绑定"
+ elif exc.retcode == 1034:
+ notice = self.ERROR_MSG_PREFIX + "服务器检测到该账号可能存在异常,请求被拒绝,请尝试通过验证"
+ elif exc.retcode == -500001:
+ notice = self.ERROR_MSG_PREFIX + "网络出小差了,请稍后重试~"
+ elif exc.retcode == -1:
+ notice = self.ERROR_MSG_PREFIX + "系统发生错误,请稍后重试~"
+ elif exc.retcode == -10001: # 参数异常 不应该抛出异常 进入下一步处理
+ pass
+ else:
+ logger.error("GenshinException", exc_info=exc)
+ notice = (
+ self.ERROR_MSG_PREFIX + f"获取账号信息发生错误 错误信息为 {exc.original if exc.original else exc.retcode} ~ 请稍后再试"
+ )
+ if notice:
+ self.create_notice_task(update, context, notice)
+ raise ApplicationHandlerStop
+
+ @error_handler()
+ async def process_telegram_exception(self, update: object, context: CallbackContext):
+ if not isinstance(context.error, TelegramError) or not isinstance(update, Update):
+ return
+ notice: Optional[str] = None
if isinstance(context.error, TimedOut):
- logger.error("Bot请求超时 %s", context.error.message)
+ notice = self.ERROR_MSG_PREFIX + " 连接连接服务器异常"
+ elif isinstance(context.error, BadRequest):
+ if "Replied message not found" in context.error.message:
+ notice = "气死我了!怎么有人喜欢发一个命令就秒删了!"
+ elif "Message is not modified" in context.error.message:
+ logger.warning("编辑消息异常")
+ raise ApplicationHandlerStop
+ elif "Not enough rights" in context.error.message:
+ notice = self.ERROR_MSG_PREFIX + "权限不足,请检查对应权限是否开启"
+ else:
+ logger.error("python-telegram-bot 请求错误", exc_info=context.error)
+ notice = self.ERROR_MSG_PREFIX + "telegram-bot-api请求错误 ~ 请稍后再试"
+ elif isinstance(context.error, Forbidden):
+ logger.error("python-telegram-bot 返回 Forbidden")
+ notice = self.ERROR_MSG_PREFIX + "telegram-bot-api请求错误 ~ 请稍后再试"
+ if notice:
+ self.create_notice_task(update, context, notice)
+ raise ApplicationHandlerStop
+
+ @error_handler()
+ async def process_telegram_update_exception(self, update: object, context: CallbackContext):
+ if update is None and isinstance(context.error, NetworkError):
+ logger.error("python-telegram-bot NetworkError : %s", context.error.message)
+ raise ApplicationHandlerStop
+
+ @error_handler()
+ async def process_apihelper_exception(self, update: object, context: CallbackContext):
+ if not isinstance(context.error, APIHelperException) or not isinstance(update, Update):
return
+ exc = context.error
+ notice: Optional[str] = None
+ if isinstance(exc, APIHelperTimedOut):
+ notice = self.ERROR_MSG_PREFIX + " 连接连接服务器异常"
+ elif isinstance(exc, ReturnCodeError):
+ notice = self.ERROR_MSG_PREFIX + f"API请求错误 错误信息为 {exc.message if exc.message else exc.code} ~ 请稍后再试"
+ elif isinstance(exc, ResponseException):
+ notice = self.ERROR_MSG_PREFIX + f"API请求错误 错误信息为 {exc.message if exc.message else exc.code} ~ 请稍后再试"
+ if notice:
+ self.create_notice_task(update, context, notice)
+ raise ApplicationHandlerStop
+
+ @error_handler()
+ async def process_httpx_exception(self, update: object, context: CallbackContext):
+ if not isinstance(context.error, HTTPError) or not isinstance(update, Update):
+ return
+ exc = context.error
+ notice: Optional[str] = None
+ if isinstance(exc, HttpxTimeout):
+ notice = self.ERROR_MSG_PREFIX + " 连接连接服务器异常"
+ if notice:
+ self.create_notice_task(update, context, notice)
+ raise ApplicationHandlerStop
+
+ @error_handler()
+ async def process_aiohttp_exception(self, update: object, context: CallbackContext):
+ if not isinstance(context.error, ClientError) or not isinstance(update, Update):
+ return
+ exc = context.error
+ notice: Optional[str] = None
+ if isinstance(exc, AioHttpTimeoutException):
+ notice = self.ERROR_MSG_PREFIX + " 连接连接服务器异常"
+ elif isinstance(exc, ClientConnectorError):
+ notice = self.ERROR_MSG_PREFIX + " 连接连接服务器异常"
+ if notice:
+ self.create_notice_task(update, context, notice)
+ raise ApplicationHandlerStop
+
+ @error_handler(block=False)
+ async def process_z_error(self, update: object, context: CallbackContext) -> None:
+ # 必须 `process_` 加上 `z` 保证该函数最后一个注册
+ """记录错误并发送消息通知开发人员。
+ logger the error and send a telegram message to notify the developer."""
logger.error("处理函数时发生异常")
logger.exception(context.error, exc_info=(type(context.error), context.error, context.error.__traceback__))
- if not notice_chat_id:
+ if not self.notice_chat_id:
return
tb_list = traceback.format_exception(None, context.error, context.error.__traceback__)
@@ -65,7 +231,7 @@ class ErrorHandler(Plugin):
f"{tb_string}"
)
file_name = f"error_{update.update_id if isinstance(update, Update) else int(time.time())}.txt"
- log_file = os.path.join(report_dir, file_name)
+ log_file = os.path.join(self.report_dir, file_name)
try:
async with aiofiles.open(log_file, mode="w+", encoding="utf-8") as f:
await f.write(error_text)
@@ -77,11 +243,11 @@ class ErrorHandler(Plugin):
logger.error("其他机器人在运行,请停止!")
return
await context.bot.send_document(
- chat_id=notice_chat_id,
+ chat_id=self.notice_chat_id,
document=open(log_file, "rb"),
caption=f'Error: "{context.error.__class__.__name__}"',
)
- except (BadRequest, Forbidden) as exc:
+ except NetworkError as exc:
logger.error("发送日记失败")
logger.exception(exc)
except FileNotFoundError:
@@ -92,25 +258,27 @@ class ErrorHandler(Plugin):
if effective_message is not None:
chat = effective_message.chat
logger.info(
- f"尝试通知用户 {effective_user.full_name}[{effective_user.id}] "
- f"在 {chat.full_name}[{chat.id}]"
- f"的 update_id[{update.update_id}] 错误信息"
+ "尝试通知用户 %s[%s] 在 %s[%s] 的 update_id[%s] 错误信息",
+ effective_user.full_name,
+ effective_user.id,
+ chat.full_name,
+ chat.id,
+ update.update_id,
)
text = "出错了呜呜呜 ~ 派蒙这边发生了点问题无法处理!"
await context.bot.send_message(
effective_message.chat_id, text, reply_markup=ReplyKeyboardRemove(), parse_mode=ParseMode.HTML
)
- except (BadRequest, Forbidden) as exc:
- logger.error(f"发送 update_id[{update.update_id}] 错误信息失败 错误信息为")
- logger.exception(exc)
- if pb_client.enabled:
+ except NetworkError as exc:
+ logger.error("发送 update_id[%s] 错误信息失败 错误信息为 %s", update.update_id, exc.message)
+ if self.pb_client.enabled:
logger.info("正在上传日记到 pb")
try:
- pb_url = await pb_client.create_pb(error_text)
+ pb_url = await self.pb_client.create_pb(error_text)
if pb_url:
logger.success("上传日记到 pb 成功")
await context.bot.send_message(
- chat_id=notice_chat_id,
+ chat_id=self.notice_chat_id,
text=f"错误信息已上传至 fars 请查看",
parse_mode=ParseMode.HTML,
)
@@ -119,10 +287,10 @@ class ErrorHandler(Plugin):
except Exception as exc:
logger.error("上传错误信息至 fars 失败")
logger.exception(exc)
- if sentry.enabled:
+ if self.sentry.enabled:
logger.info("正在上传日记到 sentry")
try:
- sentry.report_error(update, (type(context.error), context.error, context.error.__traceback__))
+ self.sentry.report_error(update, (type(context.error), context.error, context.error.__traceback__))
logger.success("上传日记到 sentry 成功")
except SentryClientException as exc:
logger.warning("上传错误信息至 sentry 失败", exc_info=exc)
diff --git a/plugins/system/get_chat.py b/plugins/system/get_chat.py
deleted file mode 100644
index ae3eeb0..0000000
--- a/plugins/system/get_chat.py
+++ /dev/null
@@ -1,182 +0,0 @@
-import contextlib
-import html
-import os.path
-from datetime import datetime
-from typing import Tuple
-
-from telegram import Update, Chat, ChatMember, ChatMemberOwner, ChatMemberAdministrator
-from telegram.error import BadRequest, Forbidden
-from telegram.ext import CommandHandler, CallbackContext
-
-from core.cookies import CookiesService
-from core.cookies.error import CookiesNotFoundError
-from core.plugin import Plugin, handler
-from core.sign import SignServices
-from core.user import UserService
-from core.user.error import UserNotFoundError
-from core.user.models import User
-from modules.gacha_log.log import GachaLog
-from modules.pay_log.log import PayLog
-from modules.playercards.file import PlayerCardsFile
-from utils.bot import get_args, get_chat as get_chat_with_cache
-from utils.decorators.admins import bot_admins_rights_check
-from utils.helpers import get_genshin_client
-from utils.log import logger
-from utils.models.base import RegionEnum
-
-
-class GetChat(Plugin):
- def __init__(
- self,
- user_service: UserService = None,
- cookies_service: CookiesService = None,
- sign_service: SignServices = None,
- ):
- self.cookies_service = cookies_service
- self.user_service = user_service
- self.sign_service = sign_service
- self.gacha_log = GachaLog()
- self.pay_log = PayLog()
- self.player_cards_file = PlayerCardsFile()
-
- async def parse_group_chat(self, chat: Chat, admins: Tuple[ChatMember]) -> str:
- text = f"群 ID:{chat.id}
\n群名称:{chat.title}
\n"
- if chat.username:
- text += f"群用户名:@{chat.username}\n"
- sign_info = await self.sign_service.get_by_chat_id(chat.id)
- if sign_info:
- text += f"自动签到推送人数:{len(sign_info)}
\n"
- if chat.description:
- text += f"群简介:{html.escape(chat.description)}
\n"
- if admins:
- for admin in admins:
- text += f'{html.escape(admin.user.full_name)} '
- if isinstance(admin, ChatMemberAdministrator):
- text += "C" if admin.can_change_info else "_"
- text += "D" if admin.can_delete_messages else "_"
- text += "R" if admin.can_restrict_members else "_"
- text += "I" if admin.can_invite_users else "_"
- text += "T" if admin.can_manage_topics else "_"
- text += "P" if admin.can_pin_messages else "_"
- text += "V" if admin.can_manage_video_chats else "_"
- text += "N" if admin.can_promote_members else "_"
- text += "A" if admin.is_anonymous else "_"
- elif isinstance(admin, ChatMemberOwner):
- text += "创建者"
- text += "\n"
- return text
-
- @staticmethod
- async def parse_private_bind(user_info: User, chat_id: int) -> Tuple[str, int]:
- if user_info.region == RegionEnum.HYPERION:
- text = "米游社绑定:"
- uid = user_info.yuanshen_uid
- else:
- text = "原神绑定:"
- uid = user_info.genshin_uid
- temp = "Cookie 绑定"
- try:
- await get_genshin_client(chat_id)
- except CookiesNotFoundError:
- temp = "UID 绑定"
- return f"{text}{temp}
\n游戏 ID:{uid}
", uid
-
- async def parse_private_sign(self, chat_id: int) -> str:
- sign_info = await self.sign_service.get_by_user_id(chat_id)
- if sign_info is not None:
- text = (
- f"\n自动签到:已开启"
- f"\n推送会话:{sign_info.chat_id}
"
- f"\n开启时间:{sign_info.time_created}
"
- f"\n更新时间:{sign_info.time_updated}
"
- f"\n签到状态:{sign_info.status.name}
"
- )
- else:
- text = "\n自动签到:未开启"
- return text
-
- async def parse_private_gacha_log(self, chat_id: int, uid: int) -> str:
- gacha_log, status = await self.gacha_log.load_history_info(str(chat_id), str(uid))
- if status:
- text = "\n抽卡记录:"
- for key, value in gacha_log.item_list.items():
- text += f"\n - {key}:{len(value)} 条"
- text += f"\n - 最后更新:{gacha_log.update_time.strftime('%Y-%m-%d %H:%M:%S')}"
- else:
- text = "\n抽卡记录:未导入
"
- return text
-
- async def parse_private_pay_log(self, chat_id: int, uid: int) -> str:
- pay_log, status = await self.pay_log.load_history_info(str(chat_id), str(uid))
- return (
- f"\n充值记录:\n - 已导入 {len(pay_log.list)} 条\n - 最后更新:{pay_log.info.export_time}"
- if status
- else "\n充值记录:未导入
"
- )
-
- @staticmethod
- def get_file_modify_time(path: str) -> datetime:
- return datetime.fromtimestamp(os.path.getmtime(path))
-
- async def parse_private_player_cards_file(self, uid: int) -> str:
- player_cards = await self.player_cards_file.load_history_info(uid)
- if player_cards is None:
- text = "\n角色卡片:未缓存
"
- else:
- time = self.get_file_modify_time(self.player_cards_file.get_file_path(uid))
- text = (
- f"\n角色卡片:"
- f"\n - 已缓存 {len(player_cards.get('avatarInfoList', []))} 个角色"
- f"\n - 最后更新:{time.strftime('%Y-%m-%d %H:%M:%S')}"
- )
- return text
-
- async def parse_private_chat(self, chat: Chat) -> str:
- text = (
- f'MENTION\n'
- f"用户 ID:{chat.id}
\n"
- f"用户名称:{chat.full_name}
\n"
- )
- if chat.username:
- text += f"用户名:@{chat.username}\n"
- try:
- user_info = await self.user_service.get_user_by_id(chat.id)
- except UserNotFoundError:
- user_info = None
- if user_info is not None:
- temp, uid = await self.parse_private_bind(user_info, chat.id)
- text += temp
- text += await self.parse_private_sign(chat.id)
- with contextlib.suppress(Exception):
- text += await self.parse_private_gacha_log(chat.id, uid)
- with contextlib.suppress(Exception):
- text += await self.parse_private_pay_log(chat.id, uid)
- with contextlib.suppress(Exception):
- text += await self.parse_private_player_cards_file(uid)
- return text
-
- @handler(CommandHandler, command="get_chat", block=False)
- @bot_admins_rights_check
- async def get_chat(self, update: Update, context: CallbackContext):
- user = update.effective_user
- logger.info("用户 %s[%s] get_chat 命令请求", user.full_name, user.id)
- message = update.effective_message
- args = get_args(context)
- if not args:
- await message.reply_text("参数错误,请指定群 id !")
- return
- try:
- chat_id = int(args[0])
- except ValueError:
- await message.reply_text("参数错误,请指定群 id !")
- return
- try:
- chat = await get_chat_with_cache(args[0])
- if chat_id < 0:
- admins = await chat.get_administrators() if chat_id < 0 else None
- text = await self.parse_group_chat(chat, admins)
- else:
- text = await self.parse_private_chat(chat)
- await message.reply_text(text, parse_mode="HTML")
- except (BadRequest, Forbidden) as exc:
- await message.reply_text(f"通过 id 获取会话信息失败,API 返回:{exc.message}")
diff --git a/plugins/system/log.py b/plugins/system/log.py
index 0c4f3ee..09dcb54 100644
--- a/plugins/system/log.py
+++ b/plugins/system/log.py
@@ -2,12 +2,11 @@ import os
from telegram import Update
from telegram.constants import ChatAction
-from telegram.ext import CommandHandler, CallbackContext
+from telegram.ext import CallbackContext
from core.config import config
from core.plugin import Plugin, handler
from modules.errorpush import PbClient, PbClientException
-from utils.decorators.admins import bot_admins_rights_check
from utils.log import logger
current_dir = os.getcwd()
@@ -31,11 +30,10 @@ class Log(Plugin):
logger.exception(exc)
return pb_url
- @handler(CommandHandler, command="send_log", block=False)
- @bot_admins_rights_check
+ @handler.command(command="send_log", block=False, admin=True)
async def send_log(self, update: Update, _: CallbackContext):
user = update.effective_user
- logger.info(f"用户 {user.full_name}[{user.id}] send_log 命令请求")
+ logger.info("用户 %s[%s] send_log 命令请求", user.full_name, user.id)
message = update.effective_message
if os.path.exists(error_log) and os.path.getsize(error_log) > 0:
pb_url = await self.send_to_pb(error_log)
diff --git a/plugins/system/sign_all.py b/plugins/system/sign_all.py
deleted file mode 100644
index e0f8970..0000000
--- a/plugins/system/sign_all.py
+++ /dev/null
@@ -1,92 +0,0 @@
-import datetime
-
-from aiohttp import ClientConnectorError
-from genshin import InvalidCookies, AlreadyClaimed, GenshinException
-from telegram import Update
-from telegram.constants import ParseMode
-from telegram.error import BadRequest, Forbidden
-from telegram.ext import CommandHandler, CallbackContext
-
-from core.base.redisdb import RedisDB
-from core.cookies import CookiesService
-from core.plugin import Plugin, handler
-from core.sign import SignServices
-from core.sign.models import SignStatusEnum
-from core.user import UserService
-from plugins.genshin.sign import SignSystem
-from plugins.jobs.sign import NeedChallenge
-from utils.decorators.admins import bot_admins_rights_check
-from utils.helpers import get_genshin_client
-from utils.log import logger
-
-
-class SignAll(Plugin):
- def __init__(
- self,
- sign_service: SignServices = None,
- user_service: UserService = None,
- cookies_service: CookiesService = None,
- redis: RedisDB = None,
- ):
- self.sign_service = sign_service
- self.cookies_service = cookies_service
- self.user_service = user_service
- self.sign_system = SignSystem(redis)
-
- @handler(CommandHandler, command="sign_all", block=False)
- @bot_admins_rights_check
- async def sign_all(self, update: Update, context: CallbackContext):
- user = update.effective_user
- logger.info(f"用户 {user.full_name}[{user.id}] sign_all 命令请求")
- message = update.effective_message
- reply = await message.reply_text("正在全部重新签到,请稍后...")
- sign_list = await self.sign_service.get_all()
- for sign_db in sign_list:
- user_id = sign_db.user_id
- old_status = sign_db.status
- try:
- client = await get_genshin_client(user_id)
- text = await self.sign_system.start_sign(client, is_sleep=True, is_raise=True, title="自动重新签到")
- except InvalidCookies:
- text = "自动签到执行失败,Cookie无效"
- sign_db.status = SignStatusEnum.INVALID_COOKIES
- except AlreadyClaimed:
- text = "今天旅行者已经签到过了~"
- sign_db.status = SignStatusEnum.ALREADY_CLAIMED
- except GenshinException as exc:
- text = f"自动签到执行失败,API返回信息为 {str(exc)}"
- sign_db.status = SignStatusEnum.GENSHIN_EXCEPTION
- except ClientConnectorError:
- text = "签到失败了呜呜呜 ~ 服务器连接超时 服务器熟啦 ~ "
- sign_db.status = SignStatusEnum.TIMEOUT_ERROR
- except NeedChallenge:
- text = "签到失败,触发验证码风控,自动签到自动关闭"
- sign_db.status = SignStatusEnum.NEED_CHALLENGE
- except Exception as exc:
- logger.error(f"执行自动签到时发生错误 用户UID[{user_id}]")
- logger.exception(exc)
- text = "签到失败了呜呜呜 ~ 执行自动签到时发生错误"
- else:
- sign_db.status = SignStatusEnum.STATUS_SUCCESS
- if sign_db.chat_id < 0:
- text = f'NOTICE {sign_db.user_id}\n\n{text}'
- try:
- await context.bot.send_message(sign_db.chat_id, text, parse_mode=ParseMode.HTML)
- except BadRequest as exc:
- logger.error(f"执行自动签到时发生错误 用户UID[{user_id}]")
- logger.exception(exc)
- sign_db.status = SignStatusEnum.BAD_REQUEST
- except Forbidden as exc:
- logger.error(f"执行自动签到时发生错误 用户UID[{user_id}]")
- logger.exception(exc)
- sign_db.status = SignStatusEnum.FORBIDDEN
- except Exception as exc:
- logger.error(f"执行自动签到时发生错误 用户UID[{user_id}]")
- logger.exception(exc)
- continue
- else:
- sign_db.status = SignStatusEnum.STATUS_SUCCESS
- sign_db.time_updated = datetime.datetime.now()
- if sign_db.status != old_status:
- await self.sign_service.update(sign_db)
- await reply.edit_text("全部账号重新签到完成")
diff --git a/plugins/system/update.py b/plugins/system/update.py
index 5c5f0db..2b76afd 100644
--- a/plugins/system/update.py
+++ b/plugins/system/update.py
@@ -3,14 +3,11 @@ import os
from sys import executable
from aiofiles import open as async_open
-from telegram import Update, Message
+from telegram import Message, Update
from telegram.error import NetworkError
-from telegram.ext import CallbackContext, CommandHandler
+from telegram.ext import CallbackContext
-from core.bot import bot
-from core.plugin import handler, Plugin
-from utils.bot import get_args
-from utils.decorators.admins import bot_admins_rights_check
+from core.plugin import Plugin, handler
from utils.helpers import execute
from utils.log import logger
@@ -27,33 +24,33 @@ UPDATE_DATA = os.path.join(current_dir, "data", "update.json")
class UpdatePlugin(Plugin):
def __init__(self):
- self._lock = asyncio.Lock()
+ self.lock = asyncio.Lock()
- @staticmethod
- async def __async_init__():
+ async def initialize(self) -> None:
if os.path.exists(UPDATE_DATA):
async with async_open(UPDATE_DATA) as file:
data = jsonlib.loads(await file.read())
try:
- reply_text = Message.de_json(data, bot.app.bot)
+ reply_text = Message.de_json(data, self.application.telegram.bot)
await reply_text.edit_text("重启成功")
except NetworkError as exc:
- logger.error("UpdatePlugin 编辑消息出现错误 %s", exc.message)
+ logger.error("编辑消息出现错误 %s", exc.message)
+ except jsonlib.JSONDecodeError:
+ logger.error("JSONDecodeError")
except KeyError as exc:
- logger.error("UpdatePlugin 编辑消息出现错误", exc_info=exc)
+ logger.error("编辑消息出现错误", exc_info=exc)
os.remove(UPDATE_DATA)
- @handler(CommandHandler, command="update", block=False)
- @bot_admins_rights_check
+ @handler.command("update", block=False, admin=True)
async def update(self, update: Update, context: CallbackContext):
user = update.effective_user
message = update.effective_message
- args = get_args(context)
- logger.info(f"用户 {user.full_name}[{user.id}] update命令请求")
- if self._lock.locked():
+ args = self.get_args(context)
+ logger.info("用户 %s[%s] update命令请求", user.full_name, user.id)
+ if self.lock.locked():
await message.reply_text("程序正在更新 请勿重复操作")
return
- async with self._lock:
+ async with self.lock:
reply_text = await message.reply_text("正在更新")
logger.info("正在更新代码")
await execute("git fetch --all")
diff --git a/plugins/system/webapp.py b/plugins/system/webapp.py
deleted file mode 100644
index 212bbdd..0000000
--- a/plugins/system/webapp.py
+++ /dev/null
@@ -1,155 +0,0 @@
-from typing import Optional
-
-from genshin import GenshinException, Region
-from pydantic import BaseModel
-from telegram import KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove, Update, WebAppInfo
-from telegram.ext import CallbackContext, filters
-
-from core.base.redisdb import RedisDB
-from core.config import config
-from core.cookies import CookiesService
-from core.cookies.error import CookiesNotFoundError
-from core.plugin import Plugin, handler
-from core.user import UserService
-from core.user.error import UserNotFoundError
-from modules.apihelper.client.components.verify import Verify
-from modules.apihelper.error import ResponseException
-from plugins.genshin.verification import VerificationSystem
-from utils.decorators.restricts import restricts
-from utils.helpers import get_genshin_client
-from utils.log import logger
-
-
-class WebAppData(BaseModel):
- path: str
- data: Optional[dict]
- code: int
- message: str
-
-
-class WebAppDataException(Exception):
- def __init__(self, data):
- self.data = data
- super().__init__()
-
-
-class WebApp(Plugin):
- def __init__(self, user_service: UserService = None, cookies_service: CookiesService = None, redis: RedisDB = None):
- self.cookies_service = cookies_service
- self.user_service = user_service
- self.verification_system = VerificationSystem(redis)
-
- @staticmethod
- def de_web_app_data(data: str) -> WebAppData:
- try:
- return WebAppData.parse_raw(data)
- except Exception as exc:
- raise WebAppDataException(data) from exc
-
- @handler.message(filters=filters.StatusUpdate.WEB_APP_DATA, block=False)
- @restricts()
- async def app(self, update: Update, context: CallbackContext):
- user = update.effective_user
- message = update.effective_message
- web_app_data = message.web_app_data
- if web_app_data:
- logger.info("用户 %s[%s] 触发 WEB_APP_DATA 请求", user.full_name, user.id)
- result = self.de_web_app_data(web_app_data.data)
- logger.debug(
- "path[%s]\ndata[%s]\ncode[%s]\nmessage[%s]", result.path, result.data, result.code, result.message
- )
- if result.code == 0:
- if result.path == "verify":
- validate = result.data.get("geetest_validate")
- try:
- client = await get_genshin_client(user.id)
- if client.region != Region.CHINESE:
- await message.reply_text("非法用户", reply_markup=ReplyKeyboardRemove())
- return
- except UserNotFoundError:
- await message.reply_text("用户未找到", reply_markup=ReplyKeyboardRemove())
- return
- except CookiesNotFoundError:
- await message.reply_text("检测到用户为UID绑定,无需认证", reply_markup=ReplyKeyboardRemove())
- return
- verify = Verify(cookies=client.cookie_manager.cookies)
- if validate:
- _, challenge = await self.verification_system.get_challenge(client.uid)
- if challenge:
- logger.info(
- "用户 %s[%s] 请求通过认证\nchallenge[%s]\nvalidate[%s]",
- user.full_name,
- user.id,
- challenge,
- validate,
- )
- try:
- await verify.verify(challenge=challenge, validate=validate)
- logger.success("用户 %s[%s] 验证成功", user.full_name, user.id)
- await message.reply_text("验证成功", reply_markup=ReplyKeyboardRemove())
- except ResponseException as exc:
- logger.warning(
- "用户 %s[%s] 验证失效 API返回 [%s]%s", user.full_name, user.id, exc.code, exc.message
- )
- if "拼图已过期" in exc.message:
- await message.reply_text(
- "验证失败,拼图已过期,请稍后重试或更换使用环境进行验证", reply_markup=ReplyKeyboardRemove()
- )
- else:
- await message.reply_text(
- f"验证失败,错误信息为 [{exc.code}]{exc.message},请稍后重试",
- reply_markup=ReplyKeyboardRemove(),
- )
- else:
- logger.warning("用户 %s[%s] 验证失效 请求已经过期", user.full_name, user.id)
- await message.reply_text("验证失效 请求已经过期 请稍后重试", reply_markup=ReplyKeyboardRemove())
- return
- try:
- await client.get_genshin_notes()
- except GenshinException as exc:
- if exc.retcode != 1034:
- raise exc
- else:
- await message.reply_text("账户正常,无需认证")
- return
- try:
- data = await verify.create(is_high=True)
- challenge = data["challenge"]
- gt = data["gt"]
- logger.success("用户 %s[%s] 创建验证成功\ngt:%s\nchallenge%s", user.full_name, user.id, gt, challenge)
- except ResponseException as exc:
- logger.warning("用户 %s[%s] 创建验证失效 API返回 [%s]%s", user.full_name, user.id, exc.code, exc.message)
- await message.reply_text(
- f"创建验证失败 错误信息为 [{exc.code}]{exc.message} 请稍后重试", reply_markup=ReplyKeyboardRemove()
- )
- return
- await self.verification_system.set_challenge(client.uid, gt, challenge)
- url = f"{config.pass_challenge_user_web}/webapp?username={context.bot.username}&command=verify>={gt}&challenge={challenge}&uid={client.uid}"
- await message.reply_text(
- "请尽快点击下方手动验证 或发送 /web_cancel 取消操作",
- reply_markup=ReplyKeyboardMarkup.from_button(
- KeyboardButton(
- text="点我手动验证",
- web_app=WebAppInfo(url=url),
- )
- ),
- )
- else:
- logger.warning(
- "用户 %s[%s] WEB_APP_DATA 请求错误 [%s]%s", user.full_name, user.id, result.code, result.message
- )
- if result.path == "verify":
- await message.reply_text(
- "验证过程中出现问题 %s\n" "如果继续遇到该问题,请打开米游社→我的角色中尝试手动通过验证,或发送 /verify 进行手动验证" % result.message,
- reply_markup=ReplyKeyboardRemove(),
- )
- else:
- await message.reply_text("WebApp返回错误 %s" % result.message, reply_markup=ReplyKeyboardRemove())
- else:
- logger.warning("用户 %s[%s] WEB_APP_DATA 非法数据", user.full_name, user.id)
-
- @handler.command("web_cancel", block=False)
- @restricts()
- async def web_cancel(self, update: Update, _: CallbackContext) -> None:
- message = update.effective_message
- await message.reply_text("取消操作", reply_markup=ReplyKeyboardRemove())
diff --git a/plugins/tools/__init__.py b/plugins/tools/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/plugins/tools/challenge.py b/plugins/tools/challenge.py
new file mode 100644
index 0000000..70cb283
--- /dev/null
+++ b/plugins/tools/challenge.py
@@ -0,0 +1,99 @@
+from typing import Tuple, Optional
+
+from genshin import Region, GenshinException
+
+from core.dependence.redisdb import RedisDB
+from core.plugin import Plugin
+from core.services.cookies import CookiesService
+from modules.apihelper.client.components.verify import Verify
+from modules.apihelper.error import ResponseException, APIHelperException
+from plugins.tools.genshin import GenshinHelper, PlayerNotFoundError
+from utils.log import logger
+
+__all__ = ("ChallengeSystemException", "ChallengeSystem")
+
+
+class ChallengeSystemException(Exception):
+ def __init__(self, message: str):
+ self.message = message
+ super().__init__()
+
+
+class ChallengeSystem(Plugin):
+ def __init__(
+ self,
+ cookies_service: CookiesService,
+ redis: RedisDB,
+ genshin_helper: GenshinHelper,
+ ) -> None:
+ self.cookies_service = cookies_service
+ self.genshin_helper = genshin_helper
+ self.cache = redis.client
+ self.qname = "plugin:challenge:"
+
+ async def get_challenge(self, uid: int) -> Tuple[Optional[str], Optional[str]]:
+ data = await self.cache.get(f"{self.qname}{uid}")
+ if not data:
+ return None, None
+ data = data.decode("utf-8").split("|")
+ return data[0], data[1]
+
+ async def set_challenge(self, uid: int, gt: str, challenge: str):
+ await self.cache.set(f"{self.qname}{uid}", f"{gt}|{challenge}")
+ await self.cache.expire(f"{self.qname}{uid}", 10 * 60)
+
+ async def create_challenge(
+ self, user_id: int, need_verify: bool = True, ajax: bool = False
+ ) -> Tuple[Optional[int], Optional[str], Optional[str]]:
+ try:
+ client = await self.genshin_helper.get_genshin_client(user_id)
+ except PlayerNotFoundError:
+ raise ChallengeSystemException("用户未找到")
+ if client.region != Region.CHINESE:
+ raise ChallengeSystemException("非法用户")
+ if need_verify:
+ try:
+ await client.get_genshin_notes()
+ except GenshinException as exc:
+ if exc.retcode != 1034:
+ raise exc
+ raise ChallengeSystemException("账户正常,无需认证")
+ verify = Verify(cookies=client.cookie_manager.cookies)
+ try:
+ data = await verify.create()
+ challenge = data["challenge"]
+ gt = data["gt"]
+ except ResponseException as exc:
+ logger.warning("用户 %s 创建验证失效 API返回 [%s]%s", user_id, exc.code, exc.message)
+ raise ChallengeSystemException(f"创建验证失败 错误信息为 [{exc.code}]{exc.message} 请稍后重试")
+ if ajax:
+ try:
+ validate = await verify.ajax(referer="https://webstatic.mihoyo.com/", gt=gt, challenge=challenge)
+ if validate:
+ await verify.verify(challenge, validate)
+ return client.uid, "ajax", "ajax"
+ except APIHelperException as exc:
+ logger.warning("用户 %s ajax 验证失效 错误信息为 %s", user_id, str(exc))
+ await self.set_challenge(client.uid, gt, challenge)
+ return client.uid, gt, challenge
+
+ async def pass_challenge(self, user_id: int, validate: str, challenge: Optional[str] = None) -> bool:
+ try:
+ client = await self.genshin_helper.get_genshin_client(user_id)
+ except PlayerNotFoundError:
+ raise ChallengeSystemException("用户未找到")
+ if client.region != Region.CHINESE:
+ raise ChallengeSystemException("非法用户")
+ if challenge is None:
+ _, challenge = await self.get_challenge(client.uid)
+ if challenge is None:
+ raise ChallengeSystemException("验证失效 请求已经过期")
+ verify = Verify(cookies=client.cookie_manager.cookies)
+ try:
+ await verify.verify(challenge=challenge, validate=validate)
+ except ResponseException as exc:
+ logger.warning("用户 %s 验证失效 API返回 [%s]%s", user_id, exc.code, exc.message)
+ if "拼图已过期" in exc.message:
+ raise ChallengeSystemException("验证失败,拼图已过期,请稍后重试或更换使用环境进行验证")
+ raise ChallengeSystemException(f"验证失败,错误信息为 [{exc.code}]{exc.message},请稍后重试")
+ return True
diff --git a/plugins/tools/genshin.py b/plugins/tools/genshin.py
new file mode 100644
index 0000000..a453ab9
--- /dev/null
+++ b/plugins/tools/genshin.py
@@ -0,0 +1,297 @@
+import asyncio
+import random
+import re
+from datetime import datetime, timedelta, time
+from typing import Optional, Tuple, Union, TYPE_CHECKING
+
+import genshin
+from genshin.errors import GenshinException
+from genshin.models import BaseCharacter
+from genshin.models import CalculatorCharacterDetails
+from pydantic import ValidationError
+from sqlalchemy.exc import SQLAlchemyError
+from sqlmodel import SQLModel, Field, String, Column, Integer, BigInteger, select, DateTime, func, delete
+from telegram.ext import ContextTypes
+
+from core.basemodel import RegionEnum
+from core.config import config
+from core.dependence.mysql import MySQL
+from core.dependence.redisdb import RedisDB
+from core.error import ServiceNotFoundError
+from core.plugin import Plugin
+from core.services.cookies.services import CookiesService, PublicCookiesService
+from core.services.players.services import PlayersService
+from core.services.users.services import UserService
+from core.sqlmodel.session import AsyncSession
+from utils.const import REGION_MAP
+from utils.log import logger
+
+if TYPE_CHECKING:
+ from sqlalchemy import Table
+ from genshin import Client as GenshinClient
+
+__all__ = ("GenshinHelper", "PlayerNotFoundError", "CookiesNotFoundError")
+
+
+class PlayerNotFoundError(Exception):
+ def __init__(self, user_id):
+ super().__init__(f"User not found, user_id: {user_id}")
+
+
+class CookiesNotFoundError(Exception):
+ def __init__(self, user_id):
+ super().__init__(f"{user_id} cookies not found")
+
+
+class CharacterDetailsSQLModel(SQLModel, table=True):
+ __tablename__ = "character_details"
+ __table_args__ = (dict(mysql_charset="utf8mb4", mysql_collate="utf8mb4_general_ci"),)
+ id: Optional[int] = Field(default=None, sa_column=Column(Integer, primary_key=True, autoincrement=True))
+ player_id: int = Field(sa_column=Column(BigInteger(), primary_key=True))
+ character_id: int = Field(sa_column=Column(BigInteger(), primary_key=True))
+ data: Optional[str] = Field(sa_column=Column(String(length=4096)))
+ time_updated: Optional[datetime] = Field(sa_column=Column(DateTime, onupdate=func.now())) # pylint: disable=E1102
+
+
+class CharacterDetails(Plugin):
+ def __init__(
+ self,
+ mysql: MySQL,
+ redis: RedisDB,
+ ) -> None:
+ self.mysql = mysql
+ self.redis = redis.client
+ self.ttl = 60 * 60 * 3
+
+ async def initialize(self) -> None:
+ def fetch_and_update_objects(connection):
+ if not self.mysql.engine.dialect.has_table(connection, table_name="character_details"):
+ logger.info("正在创建角色详细信息表")
+ table: "Table" = SQLModel.metadata.tables["character_details"]
+ table.create(connection)
+ logger.success("创建角色详细信息表成功")
+
+ async with self.mysql.engine.begin() as conn:
+ await conn.run_sync(fetch_and_update_objects)
+ asyncio.create_task(self.save_character_details_task(max_ttl=None))
+ self.application.job_queue.run_daily(self.del_old_data_job, time(hour=3, minute=0))
+ self.application.job_queue.run_repeating(self.save_character_details_job, timedelta(hours=1))
+
+ async def save_character_details_job(self, _: ContextTypes.DEFAULT_TYPE):
+ await self.save_character_details()
+
+ async def del_old_data_job(self, _: ContextTypes.DEFAULT_TYPE):
+ await self.del_old_data(timedelta(days=7))
+
+ async def del_old_data(self, expiration_time: timedelta):
+ expire_time = datetime.now() - expiration_time
+ statement = delete(CharacterDetailsSQLModel).where(CharacterDetailsSQLModel.time_updated <= expire_time)
+ async with AsyncSession(self.mysql.engine) as session:
+ await session.execute(statement)
+
+ async def save_character_details_task(self, max_ttl: Optional[int] = 60 * 60):
+ logger.info("正在从Redis中保存角色详细信息")
+ try:
+ await self.save_character_details(max_ttl)
+ except SQLAlchemyError as exc:
+ logger.error("写入到数据库失败 code[%s]", exc.code)
+ logger.debug("写入到数据库失败", exc_info=exc)
+ except Exception as exc:
+ logger.error("save_character_details 执行失败", exc_info=exc)
+ else:
+ logger.success("从Redis中保存角色详细信息成功")
+
+ async def save_character_details(self, max_ttl: Optional[int] = 60 * 60):
+ keys = await self.redis.keys("plugins:character_details:*")
+ for key in keys:
+ key = str(key, encoding="utf-8")
+ ttl = await self.redis.ttl(key)
+ if max_ttl is None or (0 <= ttl <= max_ttl):
+ try:
+ uid, character_id = re.findall(r"\d+", key)
+ except ValueError:
+ logger.warning("非法Key %s", key)
+ continue
+ data = await self.redis.get(key)
+ str_data = str(data, encoding="utf-8")
+ sql_data = CharacterDetailsSQLModel(
+ player_id=uid, character_id=character_id, data=str_data, time_updated=datetime.now()
+ )
+ async with AsyncSession(self.mysql.engine) as session:
+ await session.merge(sql_data)
+ await session.commit()
+
+ @staticmethod
+ def get_qname(uid: int, character: int):
+ return f"plugins:character_details:{uid}:{character}"
+
+ async def get_character_details_for_redis(
+ self,
+ uid: int,
+ character_id: int,
+ ) -> Optional["CalculatorCharacterDetails"]:
+ name = self.get_qname(uid, character_id)
+ data = await self.redis.get(name)
+ if data is None:
+ return None
+ json_data = str(data, encoding="utf-8")
+ return CalculatorCharacterDetails.parse_raw(json_data)
+
+ async def set_character_details_for_redis(self, uid: int, character_id: int, detail: "CalculatorCharacterDetails"):
+ randint = random.randint(1, 30) # nosec
+ await self.redis.set(
+ self.get_qname(uid, character_id), detail.json(), ex=self.ttl + randint * 60 # 使用随机数防止缓存雪崩
+ )
+
+ async def set_character_details_for_mysql(self, uid: int, character_id: int, detail: "CalculatorCharacterDetails"):
+ data = CharacterDetailsSQLModel(player_id=uid, character_id=character_id, data=detail.json())
+ async with AsyncSession(self.mysql.engine) as session:
+ await session.merge(data)
+ await session.commit()
+
+ async def get_character_details_for_mysql(
+ self,
+ uid: int,
+ character_id: int,
+ ) -> Optional["CalculatorCharacterDetails"]:
+ async with AsyncSession(self.mysql.engine) as session:
+ statement = (
+ select(CharacterDetailsSQLModel)
+ .where(CharacterDetailsSQLModel.player_id == uid)
+ .where(CharacterDetailsSQLModel.character_id == character_id)
+ )
+ results = await session.exec(statement)
+ data = results.first()
+ if data is not None:
+ try:
+ return CalculatorCharacterDetails.parse_raw(data.data)
+ except ValidationError as exc:
+ logger.error("解析数据出现异常 ValidationError", exc_info=exc)
+ await session.delete(data)
+ await session.commit()
+ except ValueError as exc:
+ logger.error("解析数据出现异常 ValueError", exc_info=exc)
+ await session.delete(data)
+ await session.commit()
+ return None
+
+ async def get_character_details(
+ self, client: "GenshinClient", character: "Union[int,BaseCharacter]"
+ ) -> Optional["CalculatorCharacterDetails"]:
+ """缓存 character_details 并定时对其进行数据存储 当遇到 Too Many Requests 可以获取以前的数据
+ :param client: genshin.py
+ :param character:
+ :return:
+ """
+ uid = client.uid
+ if uid is not None:
+ if isinstance(character, BaseCharacter):
+ character_id = character.id
+ else:
+ character_id = character
+ detail = await self.get_character_details_for_redis(uid, character_id)
+ if detail is not None:
+ return detail
+ try:
+ detail = await client.get_character_details(character)
+ except GenshinException as exc:
+ if "Too Many Requests" in exc.msg:
+ return await self.get_character_details_for_mysql(uid, character_id)
+ await self.set_character_details_for_redis(uid, character_id, detail)
+ return detail
+ try:
+ return await client.get_character_details(character)
+ except GenshinException as exc:
+ if "Too Many Requests" in exc.msg:
+ logger.warning("Too Many Requests")
+ else:
+ raise exc
+ return None
+
+
+class GenshinHelper(Plugin):
+ def __init__(
+ self,
+ cookies: CookiesService,
+ public_cookies: PublicCookiesService,
+ user: UserService,
+ redis: RedisDB,
+ player: PlayersService,
+ ) -> None:
+ self.cookies_service = cookies
+ self.public_cookies_service = public_cookies
+ self.user_service = user
+ self.redis_db = redis
+ self.players_service = player
+
+ if self.redis_db and config.genshin_ttl:
+ self.genshin_cache = genshin.RedisCache(self.redis_db.client, ttl=config.genshin_ttl)
+ else:
+ self.genshin_cache = None
+
+ if None in (temp := [self.user_service, self.cookies_service, self.players_service]):
+ raise ServiceNotFoundError(*filter(lambda x: x is None, temp))
+
+ @staticmethod
+ def region_server(uid: Union[int, str]) -> RegionEnum:
+ if isinstance(uid, (int, str)):
+ region = REGION_MAP.get(str(uid)[0])
+ else:
+ raise TypeError("UID variable type error")
+ if region:
+ return region
+ raise ValueError(f"UID {uid} isn't associated with any region.")
+
+ async def get_genshin_client(
+ self, user_id: int, region: Optional[RegionEnum] = None, need_cookie: bool = True
+ ) -> Optional[genshin.Client]:
+ """通过 user_id 和 region 获取私有的 `genshin.Client`"""
+ player = await self.players_service.get_player(user_id, region)
+ if player is None:
+ raise PlayerNotFoundError(user_id)
+ cookies = None
+ if need_cookie:
+ cookie_model = await self.cookies_service.get(player.user_id, player.account_id, player.region)
+ if cookie_model is None:
+ raise CookiesNotFoundError(user_id)
+ cookies = cookie_model.data
+
+ uid = player.player_id
+ region = player.region
+ if region == RegionEnum.HYPERION: # 国服
+ game_region = genshin.types.Region.CHINESE
+ elif region == RegionEnum.HOYOLAB: # 国际服
+ game_region = genshin.types.Region.OVERSEAS
+ else:
+ raise TypeError("Region is not None")
+
+ client = genshin.Client(cookies, lang="zh-cn", game=genshin.types.Game.GENSHIN, region=game_region, uid=uid)
+
+ if self.genshin_cache is not None:
+ client.cache = self.genshin_cache
+
+ return client
+
+ async def get_public_genshin_client(self, user_id: int) -> Tuple[genshin.Client, int]:
+ """通过 user_id 获取公共的 `genshin.Client`"""
+ player = await self.players_service.get_player(user_id)
+
+ region = player.region
+ cookies = await self.public_cookies_service.get_cookies(user_id, region)
+
+ uid = player.player_id
+ if region is RegionEnum.HYPERION:
+ game_region = genshin.types.Region.CHINESE
+ elif region is RegionEnum.HOYOLAB:
+ game_region = genshin.types.Region.OVERSEAS
+ else:
+ raise TypeError("Region is not `RegionEnum.NULL`")
+
+ client = genshin.Client(
+ cookies.data, region=game_region, uid=uid, game=genshin.types.Game.GENSHIN, lang="zh-cn"
+ )
+
+ if self.genshin_cache is not None:
+ client.cache = self.genshin_cache
+
+ return client, uid
diff --git a/plugins/tools/sign.py b/plugins/tools/sign.py
new file mode 100644
index 0000000..05fa4da
--- /dev/null
+++ b/plugins/tools/sign.py
@@ -0,0 +1,370 @@
+import asyncio
+import datetime
+import random
+import time
+from enum import Enum
+from json import JSONDecodeError
+from typing import Optional, Tuple, List
+
+from aiohttp import ClientConnectorError
+from genshin import Game, GenshinException, AlreadyClaimed, Client, InvalidCookies
+from genshin.utility import recognize_genshin_server
+from httpx import AsyncClient, TimeoutException
+from telegram import InlineKeyboardButton, InlineKeyboardMarkup
+from telegram.constants import ParseMode
+from telegram.error import Forbidden, BadRequest
+from telegram.ext import CallbackContext
+
+from core.config import config
+from core.dependence.redisdb import RedisDB
+from core.plugin import Plugin
+from core.services.cookies import CookiesService
+from core.services.sign.models import SignStatusEnum
+from core.services.sign.services import SignServices
+from core.services.users.services import UserService
+from modules.apihelper.client.components.verify import Verify
+from plugins.tools.genshin import GenshinHelper
+from utils.log import logger
+
+
+class SignJobType(Enum):
+ START = 1
+ REDO = 2
+
+
+class SignSystemException(Exception):
+ def __init__(self, message: str):
+ self.message = message
+ super().__init__()
+
+
+class NeedChallenge(Exception):
+ def __init__(self, uid: int, gt: str = "", challenge: str = ""):
+ super().__init__()
+ self.uid = uid
+ self.gt = gt
+ self.challenge = challenge
+
+
+class SignSystem(Plugin):
+ REFERER = (
+ "https://webstatic.mihoyo.com/bbs/event/signin-ys/index.html?"
+ "bbs_auth_required=true&act_id=e202009291139501&utm_source=bbs&utm_medium=mys&utm_campaign=icon"
+ )
+
+ def __init__(
+ self,
+ redis: RedisDB,
+ user_service: UserService,
+ cookies_service: CookiesService,
+ sign_service: SignServices,
+ genshin_helper: GenshinHelper,
+ ):
+ self.cookies_service = cookies_service
+ self.user_service = user_service
+ self.sign_service = sign_service
+ self.genshin_helper = genshin_helper
+ self.cache = redis.client
+ self.qname = "plugin:sign:"
+ self.verify = Verify()
+
+ async def get_challenge(self, uid: int) -> Tuple[Optional[str], Optional[str]]:
+ data = await self.cache.get(f"{self.qname}{uid}")
+ if not data:
+ return None, None
+ data = data.decode("utf-8").split("|")
+ return data[0], data[1]
+
+ async def set_challenge(self, uid: int, gt: str, challenge: str):
+ await self.cache.set(f"{self.qname}{uid}", f"{gt}|{challenge}")
+ await self.cache.expire(f"{self.qname}{uid}", 10 * 60)
+
+ async def get_challenge_button(
+ self,
+ bot_username: str,
+ uid: int,
+ user_id: int,
+ gt: Optional[str] = None,
+ challenge: Optional[str] = None,
+ callback: bool = True,
+ ) -> Optional[InlineKeyboardMarkup]:
+ if not config.pass_challenge_user_web:
+ return None
+ if challenge and gt:
+ await self.set_challenge(uid, gt, challenge)
+ if not challenge or not gt:
+ gt, challenge = await self.get_challenge(uid)
+ if not challenge or not gt:
+ return None
+ if callback:
+ data = f"sign|{user_id}|{uid}"
+ return InlineKeyboardMarkup([[InlineKeyboardButton("请尽快点我进行手动验证", callback_data=data)]])
+ url = (
+ f"{config.pass_challenge_user_web}?"
+ f"username={bot_username}&command=sign>={gt}&challenge={challenge}&uid={uid}"
+ )
+ return InlineKeyboardMarkup([[InlineKeyboardButton("请尽快点我进行手动验证", url=url)]])
+
+ async def recognize(self, gt: str, challenge: str, referer: str = None) -> Optional[str]:
+ if not referer:
+ referer = self.REFERER
+ if not gt or not challenge:
+ return None
+ pass_challenge_params = {
+ "gt": gt,
+ "challenge": challenge,
+ "referer": referer,
+ }
+ if config.pass_challenge_app_key:
+ pass_challenge_params["appkey"] = config.pass_challenge_app_key
+ headers = {
+ "Accept": "*/*",
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
+ "Chrome/107.0.0.0 Safari/537.36",
+ }
+ try:
+ async with AsyncClient(headers=headers) as client:
+ resp = await client.post(
+ config.pass_challenge_api,
+ params=pass_challenge_params,
+ timeout=60,
+ )
+ logger.debug("recognize 请求返回:%s", resp.text)
+ data = resp.json()
+ status = data.get("status")
+ if status != 0:
+ logger.error("recognize 解析错误:[%s]%s", data.get("code"), data.get("msg"))
+ if data.get("code", 0) != 0:
+ raise RuntimeError
+ logger.info("recognize 解析成功")
+ return data["data"]["validate"]
+ except JSONDecodeError:
+ logger.warning("recognize 请求 JSON 解析失败")
+ except TimeoutException as exc:
+ logger.warning("recognize 请求超时")
+ raise exc
+ except KeyError:
+ logger.warning("recognize 请求数据错误")
+ except RuntimeError:
+ logger.warning("recognize 请求失败")
+ return None
+
+ async def start_sign(
+ self,
+ client: Client,
+ challenge: Optional[str] = None,
+ validate: Optional[str] = None,
+ is_sleep: bool = False,
+ is_raise: bool = False,
+ title: Optional[str] = "签到结果",
+ ) -> str:
+ if is_sleep:
+ if recognize_genshin_server(client.uid) in ("cn_gf01", "cn_qd01"):
+ await asyncio.sleep(random.randint(10, 300)) # nosec
+ else:
+ await asyncio.sleep(random.randint(0, 3)) # nosec
+ try:
+ rewards = await client.get_monthly_rewards(game=Game.GENSHIN, lang="zh-cn")
+ except GenshinException as error:
+ logger.warning("UID[%s] 获取签到信息失败,API返回信息为 %s", client.uid, str(error))
+ if is_raise:
+ raise error
+ return f"获取签到信息失败,API返回信息为 {str(error)}"
+ try:
+ daily_reward_info = await client.get_reward_info(game=Game.GENSHIN, lang="zh-cn") # 获取签到信息失败
+ except GenshinException as error:
+ logger.warning("UID[%s] 获取签到状态失败,API返回信息为 %s", client.uid, str(error))
+ if is_raise:
+ raise error
+ return f"获取签到状态失败,API返回信息为 {str(error)}"
+ if not daily_reward_info.signed_in:
+ try:
+ if validate:
+ logger.info("UID[%s] 正在尝试通过验证码\nchallenge[%s]\nvalidate[%s]", client.uid, challenge, validate)
+ request_daily_reward = await client.request_daily_reward(
+ "sign",
+ method="POST",
+ game=Game.GENSHIN,
+ lang="zh-cn",
+ challenge=challenge,
+ validate=validate,
+ )
+ logger.debug("request_daily_reward 返回 %s", request_daily_reward)
+ if request_daily_reward and request_daily_reward.get("success", 0) == 1:
+ # 尝试通过 ajax 请求绕过签到
+ gt = request_daily_reward.get("gt", "")
+ challenge = request_daily_reward.get("challenge", "")
+ logger.warning("UID[%s] 触发验证码\ngt[%s]\nchallenge[%s]", client.uid, gt, challenge)
+ validate = await self.verify.ajax(
+ referer=self.REFERER,
+ gt=gt,
+ challenge=challenge,
+ )
+ if validate:
+ logger.success("ajax 通过验证成功\nchallenge[%s]\nvalidate[%s]", challenge, validate)
+ request_daily_reward = await client.request_daily_reward(
+ "sign",
+ method="POST",
+ game=Game.GENSHIN,
+ lang="zh-cn",
+ challenge=challenge,
+ validate=validate,
+ )
+ logger.debug("request_daily_reward 返回 %s", request_daily_reward)
+ if request_daily_reward and request_daily_reward.get("success", 0) == 1:
+ logger.warning("UID[%s] 触发验证码\nchallenge[%s]", client.uid, challenge)
+ raise NeedChallenge(
+ uid=client.uid,
+ gt=request_daily_reward.get("gt", ""),
+ challenge=request_daily_reward.get("challenge", ""),
+ )
+ elif config.pass_challenge_app_key:
+ # 如果无法绕过 检查配置文件是否配置识别 API 尝试请求绕过
+ # 注意 需要重新获取没有进行任何请求的 Challenge
+ logger.info("UID[%s] 正在使用 recognize 重新请求签到", client.uid)
+ _request_daily_reward = await client.request_daily_reward(
+ "sign",
+ method="POST",
+ game=Game.GENSHIN,
+ lang="zh-cn",
+ )
+ logger.debug("request_daily_reward 返回\n%s", _request_daily_reward)
+ if _request_daily_reward and _request_daily_reward.get("success", 0) == 1:
+ _gt = _request_daily_reward.get("gt", "")
+ _challenge = _request_daily_reward.get("challenge", "")
+ logger.info("UID[%s] 创建验证码\ngt[%s]\nchallenge[%s]", client.uid, _gt, _challenge)
+ _validate = await self.recognize(_gt, _challenge)
+ if _validate:
+ logger.success("recognize 通过验证成功\nchallenge[%s]\nvalidate[%s]", _challenge, _validate)
+ request_daily_reward = await client.request_daily_reward(
+ "sign",
+ method="POST",
+ game=Game.GENSHIN,
+ lang="zh-cn",
+ challenge=_challenge,
+ validate=_validate,
+ )
+ if request_daily_reward and request_daily_reward.get("success", 0) == 1:
+ logger.warning("UID[%s] 触发验证码\nchallenge[%s]", client.uid, _challenge)
+ gt = request_daily_reward.get("gt", "")
+ challenge = request_daily_reward.get("challenge", "")
+ logger.success("UID[%s] 创建验证成功\ngt[%s]\nchallenge[%s]", client.uid, gt, challenge)
+ raise NeedChallenge(
+ uid=client.uid,
+ gt=gt,
+ challenge=challenge,
+ )
+ logger.success("UID[%s] 通过 recognize 签到成功", client.uid)
+ else:
+ request_daily_reward = await client.request_daily_reward(
+ "sign", method="POST", game=Game.GENSHIN, lang="zh-cn"
+ )
+ gt = request_daily_reward.get("gt", "")
+ challenge = request_daily_reward.get("challenge", "")
+ logger.success("UID[%s] 创建验证成功\ngt[%s]\nchallenge[%s]", client.uid, gt, challenge)
+ raise NeedChallenge(uid=client.uid, gt=gt, challenge=challenge)
+ else:
+ request_daily_reward = await client.request_daily_reward(
+ "sign", method="POST", game=Game.GENSHIN, lang="zh-cn"
+ )
+ gt = request_daily_reward.get("gt", "")
+ challenge = request_daily_reward.get("challenge", "")
+ logger.success("UID[%s] 创建验证成功\ngt[%s]\nchallenge[%s]", client.uid, gt, challenge)
+ raise NeedChallenge(uid=client.uid, gt=gt, challenge=challenge)
+ else:
+ logger.success("UID[%s] 签到成功", client.uid)
+ except TimeoutException as error:
+ logger.warning("UID[%s] 签到请求超时", client.uid)
+ if is_raise:
+ raise error
+ return "签到失败了呜呜呜 ~ 服务器连接超时 服务器熟啦 ~ "
+ except AlreadyClaimed as error:
+ logger.warning("UID[%s] 已经签到", client.uid)
+ if is_raise:
+ raise error
+ result = "今天旅行者已经签到过了~"
+ except GenshinException as error:
+ logger.warning("UID %s 签到失败,API返回信息为 %s", client.uid, str(error))
+ if is_raise:
+ raise error
+ return f"获取签到状态失败,API返回信息为 {str(error)}"
+ else:
+ result = "OK"
+ else:
+ logger.info("UID[%s] 已经签到", client.uid)
+ result = "今天旅行者已经签到过了~"
+ logger.info("UID[%s] 签到结果 %s", client.uid, result)
+ reward = rewards[daily_reward_info.claimed_rewards - (1 if daily_reward_info.signed_in else 0)]
+ today = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
+ cn_timezone = datetime.timezone(datetime.timedelta(hours=8))
+ now = datetime.datetime.now(cn_timezone)
+ missed_days = now.day - daily_reward_info.claimed_rewards
+ if not daily_reward_info.signed_in:
+ missed_days -= 1
+ message = (
+ f"#### {title} ####\n"
+ f"时间:{today} (UTC+8)\n"
+ f"UID: {client.uid}\n"
+ f"今日奖励: {reward.name} × {reward.amount}\n"
+ f"本月漏签次数:{missed_days}\n"
+ f"签到结果: {result}"
+ )
+ return message
+
+ async def do_sign_job(self, context: CallbackContext, job_type: SignJobType):
+ include_status: List[SignStatusEnum] = [
+ SignStatusEnum.STATUS_SUCCESS,
+ SignStatusEnum.TIMEOUT_ERROR,
+ SignStatusEnum.NEED_CHALLENGE,
+ ]
+ if job_type == SignJobType.START:
+ title = "自动签到"
+ elif job_type == SignJobType.REDO:
+ title = "自动重新签到"
+ else:
+ raise ValueError
+ sign_list = await self.sign_service.get_all()
+ for sign_db in sign_list:
+ if sign_db.status not in include_status:
+ continue
+ user_id = sign_db.user_id
+ try:
+ client = await self.genshin_helper.get_genshin_client(user_id)
+ text = await self.start_sign(client, is_sleep=True, is_raise=True, title=title)
+ except InvalidCookies:
+ text = "自动签到执行失败,Cookie无效"
+ sign_db.status = SignStatusEnum.INVALID_COOKIES
+ except AlreadyClaimed:
+ text = "今天旅行者已经签到过了~"
+ sign_db.status = SignStatusEnum.ALREADY_CLAIMED
+ except GenshinException as exc:
+ text = f"自动签到执行失败,API返回信息为 {str(exc)}"
+ sign_db.status = SignStatusEnum.GENSHIN_EXCEPTION
+ except ClientConnectorError:
+ text = "签到失败了呜呜呜 ~ 服务器连接超时 服务器熟啦 ~ "
+ sign_db.status = SignStatusEnum.TIMEOUT_ERROR
+ except NeedChallenge:
+ text = "签到失败,触发验证码风控,自动签到自动关闭"
+ sign_db.status = SignStatusEnum.NEED_CHALLENGE
+ except Exception as exc:
+ logger.error("执行自动签到时发生错误 user_id[%s] Message[%s]", user_id, exc.message)
+ text = "签到失败了呜呜呜 ~ 执行自动签到时发生错误"
+ else:
+ sign_db.status = SignStatusEnum.STATUS_SUCCESS
+ if sign_db.chat_id < 0:
+ text = f'NOTICE {sign_db.user_id}\n\n{text}'
+ try:
+ await context.bot.send_message(sign_db.chat_id, text, parse_mode=ParseMode.HTML)
+ except BadRequest as exc:
+ logger.error("执行自动签到时发生错误 user_id[%s] Message[%s]", user_id, exc.message)
+ sign_db.status = SignStatusEnum.BAD_REQUEST
+ except Forbidden as exc:
+ logger.error("执行自动签到时发生错误 user_id[%s] message[%s]", user_id, exc.message)
+ sign_db.status = SignStatusEnum.FORBIDDEN
+ except Exception as exc:
+ logger.error("执行自动签到时发生错误 user_id[%s]", user_id, exc_info=exc)
+ continue
+ else:
+ sign_db.status = SignStatusEnum.STATUS_SUCCESS
+ sign_db.time_updated = datetime.datetime.now()
+ await self.sign_service.update(sign_db)
diff --git a/poetry.lock b/poetry.lock
index ddbecd9..070f1cb 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand.
[[package]]
name = "aiofiles"
@@ -1621,6 +1621,97 @@ files = [
{file = "pathspec-0.11.0.tar.gz", hash = "sha256:64d338d4e0914e91c1792321e6907b5a593f1ab1851de7fc269557a21b30ebbc"},
]
+[[package]]
+name = "pillow"
+version = "9.4.0"
+description = "Python Imaging Library (Fork)"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "Pillow-9.4.0-1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b4b4e9dda4f4e4c4e6896f93e84a8f0bcca3b059de9ddf67dac3c334b1195e1"},
+ {file = "Pillow-9.4.0-1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:fb5c1ad6bad98c57482236a21bf985ab0ef42bd51f7ad4e4538e89a997624e12"},
+ {file = "Pillow-9.4.0-1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:f0caf4a5dcf610d96c3bd32932bfac8aee61c96e60481c2a0ea58da435e25acd"},
+ {file = "Pillow-9.4.0-1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:3f4cc516e0b264c8d4ccd6b6cbc69a07c6d582d8337df79be1e15a5056b258c9"},
+ {file = "Pillow-9.4.0-1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b8c2f6eb0df979ee99433d8b3f6d193d9590f735cf12274c108bd954e30ca858"},
+ {file = "Pillow-9.4.0-1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b70756ec9417c34e097f987b4d8c510975216ad26ba6e57ccb53bc758f490dab"},
+ {file = "Pillow-9.4.0-1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:43521ce2c4b865d385e78579a082b6ad1166ebed2b1a2293c3be1d68dd7ca3b9"},
+ {file = "Pillow-9.4.0-2-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:9d9a62576b68cd90f7075876f4e8444487db5eeea0e4df3ba298ee38a8d067b0"},
+ {file = "Pillow-9.4.0-2-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:87708d78a14d56a990fbf4f9cb350b7d89ee8988705e58e39bdf4d82c149210f"},
+ {file = "Pillow-9.4.0-2-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:8a2b5874d17e72dfb80d917213abd55d7e1ed2479f38f001f264f7ce7bae757c"},
+ {file = "Pillow-9.4.0-2-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:83125753a60cfc8c412de5896d10a0a405e0bd88d0470ad82e0869ddf0cb3848"},
+ {file = "Pillow-9.4.0-2-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:9e5f94742033898bfe84c93c831a6f552bb629448d4072dd312306bab3bd96f1"},
+ {file = "Pillow-9.4.0-2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:013016af6b3a12a2f40b704677f8b51f72cb007dac785a9933d5c86a72a7fe33"},
+ {file = "Pillow-9.4.0-2-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:99d92d148dd03fd19d16175b6d355cc1b01faf80dae93c6c3eb4163709edc0a9"},
+ {file = "Pillow-9.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:2968c58feca624bb6c8502f9564dd187d0e1389964898f5e9e1fbc8533169157"},
+ {file = "Pillow-9.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c5c1362c14aee73f50143d74389b2c158707b4abce2cb055b7ad37ce60738d47"},
+ {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd752c5ff1b4a870b7661234694f24b1d2b9076b8bf337321a814c612665f343"},
+ {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a3049a10261d7f2b6514d35bbb7a4dfc3ece4c4de14ef5876c4b7a23a0e566d"},
+ {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16a8df99701f9095bea8a6c4b3197da105df6f74e6176c5b410bc2df2fd29a57"},
+ {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:94cdff45173b1919350601f82d61365e792895e3c3a3443cf99819e6fbf717a5"},
+ {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:ed3e4b4e1e6de75fdc16d3259098de7c6571b1a6cc863b1a49e7d3d53e036070"},
+ {file = "Pillow-9.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5b2f8a31bd43e0f18172d8ac82347c8f37ef3e0b414431157718aa234991b28"},
+ {file = "Pillow-9.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:09b89ddc95c248ee788328528e6a2996e09eaccddeeb82a5356e92645733be35"},
+ {file = "Pillow-9.4.0-cp310-cp310-win32.whl", hash = "sha256:f09598b416ba39a8f489c124447b007fe865f786a89dbfa48bb5cf395693132a"},
+ {file = "Pillow-9.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:f6e78171be3fb7941f9910ea15b4b14ec27725865a73c15277bc39f5ca4f8391"},
+ {file = "Pillow-9.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:3fa1284762aacca6dc97474ee9c16f83990b8eeb6697f2ba17140d54b453e133"},
+ {file = "Pillow-9.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:eaef5d2de3c7e9b21f1e762f289d17b726c2239a42b11e25446abf82b26ac132"},
+ {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4dfdae195335abb4e89cc9762b2edc524f3c6e80d647a9a81bf81e17e3fb6f0"},
+ {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6abfb51a82e919e3933eb137e17c4ae9c0475a25508ea88993bb59faf82f3b35"},
+ {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:451f10ef963918e65b8869e17d67db5e2f4ab40e716ee6ce7129b0cde2876eab"},
+ {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:6663977496d616b618b6cfa43ec86e479ee62b942e1da76a2c3daa1c75933ef4"},
+ {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:60e7da3a3ad1812c128750fc1bc14a7ceeb8d29f77e0a2356a8fb2aa8925287d"},
+ {file = "Pillow-9.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:19005a8e58b7c1796bc0167862b1f54a64d3b44ee5d48152b06bb861458bc0f8"},
+ {file = "Pillow-9.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f715c32e774a60a337b2bb8ad9839b4abf75b267a0f18806f6f4f5f1688c4b5a"},
+ {file = "Pillow-9.4.0-cp311-cp311-win32.whl", hash = "sha256:b222090c455d6d1a64e6b7bb5f4035c4dff479e22455c9eaa1bdd4c75b52c80c"},
+ {file = "Pillow-9.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:ba6612b6548220ff5e9df85261bddc811a057b0b465a1226b39bfb8550616aee"},
+ {file = "Pillow-9.4.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:5f532a2ad4d174eb73494e7397988e22bf427f91acc8e6ebf5bb10597b49c493"},
+ {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dd5a9c3091a0f414a963d427f920368e2b6a4c2f7527fdd82cde8ef0bc7a327"},
+ {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef21af928e807f10bf4141cad4746eee692a0dd3ff56cfb25fce076ec3cc8abe"},
+ {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:847b114580c5cc9ebaf216dd8c8dbc6b00a3b7ab0131e173d7120e6deade1f57"},
+ {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:653d7fb2df65efefbcbf81ef5fe5e5be931f1ee4332c2893ca638c9b11a409c4"},
+ {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:46f39cab8bbf4a384ba7cb0bc8bae7b7062b6a11cfac1ca4bc144dea90d4a9f5"},
+ {file = "Pillow-9.4.0-cp37-cp37m-win32.whl", hash = "sha256:7ac7594397698f77bce84382929747130765f66406dc2cd8b4ab4da68ade4c6e"},
+ {file = "Pillow-9.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:46c259e87199041583658457372a183636ae8cd56dbf3f0755e0f376a7f9d0e6"},
+ {file = "Pillow-9.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:0e51f608da093e5d9038c592b5b575cadc12fd748af1479b5e858045fff955a9"},
+ {file = "Pillow-9.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:765cb54c0b8724a7c12c55146ae4647e0274a839fb6de7bcba841e04298e1011"},
+ {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:519e14e2c49fcf7616d6d2cfc5c70adae95682ae20f0395e9280db85e8d6c4df"},
+ {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d197df5489004db87d90b918033edbeee0bd6df3848a204bca3ff0a903bef837"},
+ {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0845adc64fe9886db00f5ab68c4a8cd933ab749a87747555cec1c95acea64b0b"},
+ {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:e1339790c083c5a4de48f688b4841f18df839eb3c9584a770cbd818b33e26d5d"},
+ {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:a96e6e23f2b79433390273eaf8cc94fec9c6370842e577ab10dabdcc7ea0a66b"},
+ {file = "Pillow-9.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7cfc287da09f9d2a7ec146ee4d72d6ea1342e770d975e49a8621bf54eaa8f30f"},
+ {file = "Pillow-9.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d7081c084ceb58278dd3cf81f836bc818978c0ccc770cbbb202125ddabec6628"},
+ {file = "Pillow-9.4.0-cp38-cp38-win32.whl", hash = "sha256:df41112ccce5d47770a0c13651479fbcd8793f34232a2dd9faeccb75eb5d0d0d"},
+ {file = "Pillow-9.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:7a21222644ab69ddd9967cfe6f2bb420b460dae4289c9d40ff9a4896e7c35c9a"},
+ {file = "Pillow-9.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0f3269304c1a7ce82f1759c12ce731ef9b6e95b6df829dccd9fe42912cc48569"},
+ {file = "Pillow-9.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cb362e3b0976dc994857391b776ddaa8c13c28a16f80ac6522c23d5257156bed"},
+ {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2e0f87144fcbbe54297cae708c5e7f9da21a4646523456b00cc956bd4c65815"},
+ {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28676836c7796805914b76b1837a40f76827ee0d5398f72f7dcc634bae7c6264"},
+ {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0884ba7b515163a1a05440a138adeb722b8a6ae2c2b33aea93ea3118dd3a899e"},
+ {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:53dcb50fbdc3fb2c55431a9b30caeb2f7027fcd2aeb501459464f0214200a503"},
+ {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:e8c5cf126889a4de385c02a2c3d3aba4b00f70234bfddae82a5eaa3ee6d5e3e6"},
+ {file = "Pillow-9.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6c6b1389ed66cdd174d040105123a5a1bc91d0aa7059c7261d20e583b6d8cbd2"},
+ {file = "Pillow-9.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0dd4c681b82214b36273c18ca7ee87065a50e013112eea7d78c7a1b89a739153"},
+ {file = "Pillow-9.4.0-cp39-cp39-win32.whl", hash = "sha256:6d9dfb9959a3b0039ee06c1a1a90dc23bac3b430842dcb97908ddde05870601c"},
+ {file = "Pillow-9.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:54614444887e0d3043557d9dbc697dbb16cfb5a35d672b7a0fcc1ed0cf1c600b"},
+ {file = "Pillow-9.4.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b9b752ab91e78234941e44abdecc07f1f0d8f51fb62941d32995b8161f68cfe5"},
+ {file = "Pillow-9.4.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3b56206244dc8711f7e8b7d6cad4663917cd5b2d950799425076681e8766286"},
+ {file = "Pillow-9.4.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aabdab8ec1e7ca7f1434d042bf8b1e92056245fb179790dc97ed040361f16bfd"},
+ {file = "Pillow-9.4.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:db74f5562c09953b2c5f8ec4b7dfd3f5421f31811e97d1dbc0a7c93d6e3a24df"},
+ {file = "Pillow-9.4.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e9d7747847c53a16a729b6ee5e737cf170f7a16611c143d95aa60a109a59c336"},
+ {file = "Pillow-9.4.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b52ff4f4e002f828ea6483faf4c4e8deea8d743cf801b74910243c58acc6eda3"},
+ {file = "Pillow-9.4.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:575d8912dca808edd9acd6f7795199332696d3469665ef26163cd090fa1f8bfa"},
+ {file = "Pillow-9.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3c4ed2ff6760e98d262e0cc9c9a7f7b8a9f61aa4d47c58835cdaf7b0b8811bb"},
+ {file = "Pillow-9.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e621b0246192d3b9cb1dc62c78cfa4c6f6d2ddc0ec207d43c0dedecb914f152a"},
+ {file = "Pillow-9.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8f127e7b028900421cad64f51f75c051b628db17fb00e099eb148761eed598c9"},
+ {file = "Pillow-9.4.0.tar.gz", hash = "sha256:a1c2d7780448eb93fbcc3789bf3916aa5720d942e37945f4056680317f1cd23e"},
+]
+
+[package.extras]
+docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinxext-opengraph"]
+tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
+
[[package]]
name = "platformdirs"
version = "3.1.0"
@@ -1911,7 +2002,7 @@ files = [
]
[package.dependencies]
-aiolimiter = {version = ">=1.0.0,<1.1.0", optional = true, markers = "extra == \"ext\""}
+aiolimiter = {version = ">=1.0.0,<1.1.0", optional = true, markers = "extra == \"ext\" or extra == \"rate-limiter\""}
APScheduler = {version = ">=3.10.0,<3.11.0", optional = true, markers = "extra == \"ext\""}
cachetools = {version = ">=5.3.0,<5.4.0", optional = true, markers = "extra == \"ext\""}
httpx = {version = ">=0.23.3,<0.24.0", extras = ["http2"]}
@@ -2257,7 +2348,7 @@ files = [
]
[package.dependencies]
-greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"}
+greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and platform_machine == \"aarch64\" or python_version >= \"3\" and platform_machine == \"ppc64le\" or python_version >= \"3\" and platform_machine == \"x86_64\" or python_version >= \"3\" and platform_machine == \"amd64\" or python_version >= \"3\" and platform_machine == \"AMD64\" or python_version >= \"3\" and platform_machine == \"win32\" or python_version >= \"3\" and platform_machine == \"WIN32\""}
[package.extras]
aiomysql = ["aiomysql", "greenlet (!=0.4.17)"]
@@ -2889,11 +2980,11 @@ docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker
testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
[extras]
-all = ["pytest", "pytest-asyncio", "flaky", "Pyrogram", "TgCrypto"]
+all = ["Pyrogram", "TgCrypto", "flaky", "pytest", "pytest-asyncio"]
pyro = ["Pyrogram", "TgCrypto"]
-test = ["pytest", "pytest-asyncio", "flaky"]
+test = ["flaky", "pytest", "pytest-asyncio"]
[metadata]
lock-version = "2.0"
python-versions = "^3.8"
-content-hash = "17a60c0935380268c882c607bbadbf7d3e931615cde1324da7c2515492039732"
+content-hash = "d1846eb4c7be70ecb7c27e528352cdb6a39fd60a9012793671607a68a4871ad3"
diff --git a/pyproject.toml b/pyproject.toml
index 3e74a39..84609dd 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -42,6 +42,7 @@ async-lru = "^2.0.2"
thefuzz = "^0.19.0"
qrcode = "^7.4.2"
cryptography = "^39.0.1"
+pillow = "^9.4.0"
[tool.poetry.extras]
pyro = ["Pyrogram", "TgCrypto"]
diff --git a/requirements.txt b/requirements.txt
index 8fd65d3..68b77df 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,29 +1,109 @@
-httpx~=0.23.3
-ujson~=5.7.0
-git+https://github.com/thesadru/genshin.py
-Jinja2~=3.1.2
-python-telegram-bot[ext, rate-limiter]~=20.1
-sqlmodel~=0.0.8
-colorlog~=6.7.0
-playwright ~= 1.27.1
-fakeredis ~= 2.9.0
-beautifulsoup4 ~= 4.11.2
-asyncmy ~= 0.2.7
-pyppeteer ~= 1.0.2
-aiofiles ~= 23.1.0
-python-dotenv ~= 1.0.0
-alembic ~= 1.10.2
-black ~= 23.1.0
-rich ~= 13.3.1
-git+https://github.com/mrwan200/EnkaNetwork.py
-lxml ~= 4.9.2
-arko-wrapper ~= 0.2.8
-fastapi ~= 0.93.0
-uvicorn[standard] ~= 0.21.0
-sentry-sdk ~= 1.15.0
-GitPython ~= 3.1.30
-openpyxl ~= 3.1.1
-async-lru ~= 2.0.2
-thefuzz ~= 0.19.0
-qrcode ~= 7.4.2
-cryptography ~= 39.0.1
+aiofiles==23.1.0 ; python_version >= "3.8" and python_version < "4.0"
+aiohttp==3.8.4 ; python_version >= "3.8" and python_version < "4.0"
+aiolimiter==1.0.0 ; python_version >= "3.8" and python_version < "4.0"
+aiosignal==1.3.1 ; python_version >= "3.8" and python_version < "4.0"
+alembic==1.10.2 ; python_version >= "3.8" and python_version < "4.0"
+anyio==3.6.2 ; python_version >= "3.8" and python_version < "4.0"
+appdirs==1.4.4 ; python_version >= "3.8" and python_version < "4.0"
+apscheduler==3.10.1 ; python_version >= "3.8" and python_version < "4.0"
+arko-wrapper==0.2.8 ; python_version >= "3.8" and python_version < "4.0"
+async-lru==2.0.2 ; python_version >= "3.8" and python_version < "4.0"
+async-timeout==4.0.2 ; python_version >= "3.8" and python_version < "4.0"
+asyncmy==0.2.7 ; python_version >= "3.8" and python_version < "4.0"
+attrs==22.2.0 ; python_version >= "3.8" and python_version < "4.0"
+backports-zoneinfo==0.2.1 ; python_version >= "3.8" and python_version < "3.9"
+beautifulsoup4==4.11.2 ; python_version >= "3.8" and python_version < "4.0"
+black==23.1.0 ; python_version >= "3.8" and python_version < "4.0"
+cachetools==5.3.0 ; python_version >= "3.8" and python_version < "4.0"
+certifi==2022.12.7 ; python_version >= "3.8" and python_version < "4.0"
+cffi==1.15.1 ; python_version >= "3.8" and python_version < "4.0"
+charset-normalizer==3.1.0 ; python_version >= "3.8" and python_version < "4.0"
+click==8.1.3 ; python_version >= "3.8" and python_version < "4.0"
+colorama==0.4.6 ; python_version >= "3.8" and python_version < "4.0" and sys_platform == "win32" or python_version >= "3.8" and python_version < "4.0" and platform_system == "Windows"
+colorlog==6.7.0 ; python_version >= "3.8" and python_version < "4.0"
+cryptography==39.0.2 ; python_version >= "3.8" and python_version < "4.0"
+enkanetwork-py @ git+https://github.com/mrwan200/EnkaNetwork.py@master ; python_version >= "3.8" and python_version < "4.0"
+et-xmlfile==1.1.0 ; python_version >= "3.8" and python_version < "4.0"
+exceptiongroup==1.1.0 ; python_version >= "3.8" and python_version < "3.11"
+fakeredis==2.10.0 ; python_version >= "3.8" and python_version < "4.0"
+fastapi==0.93.0 ; python_version >= "3.8" and python_version < "4.0"
+flaky==3.7.0 ; python_version >= "3.8" and python_version < "4.0"
+frozenlist==1.3.3 ; python_version >= "3.8" and python_version < "4.0"
+genshin @ git+https://github.com/thesadru/genshin.py@master ; python_version >= "3.8" and python_version < "4.0"
+gitdb==4.0.10 ; python_version >= "3.8" and python_version < "4.0"
+gitpython==3.1.31 ; python_version >= "3.8" and python_version < "4.0"
+greenlet==1.1.3 ; python_version >= "3.8" and python_version < "4.0"
+h11==0.14.0 ; python_version >= "3.8" and python_version < "4.0"
+h2==4.1.0 ; python_version >= "3.8" and python_version < "4.0"
+hpack==4.0.0 ; python_version >= "3.8" and python_version < "4.0"
+httpcore==0.16.3 ; python_version >= "3.8" and python_version < "4.0"
+httptools==0.5.0 ; python_version >= "3.8" and python_version < "4.0"
+httpx==0.23.3 ; python_version >= "3.8" and python_version < "4.0"
+httpx[http2]==0.23.3 ; python_version >= "3.8" and python_version < "4.0"
+hyperframe==6.0.1 ; python_version >= "3.8" and python_version < "4.0"
+idna==3.4 ; python_version >= "3.8" and python_version < "4.0"
+importlib-metadata==6.0.0 ; python_version >= "3.8" and python_version < "4.0"
+importlib-resources==5.12.0 ; python_version >= "3.8" and python_version < "3.9"
+iniconfig==2.0.0 ; python_version >= "3.8" and python_version < "4.0"
+jinja2==3.1.2 ; python_version >= "3.8" and python_version < "4.0"
+lxml==4.9.2 ; python_version >= "3.8" and python_version < "4.0"
+mako==1.2.4 ; python_version >= "3.8" and python_version < "4.0"
+markdown-it-py==2.2.0 ; python_version >= "3.8" and python_version < "4.0"
+markupsafe==2.1.2 ; python_version >= "3.8" and python_version < "4.0"
+mdurl==0.1.2 ; python_version >= "3.8" and python_version < "4.0"
+multidict==6.0.4 ; python_version >= "3.8" and python_version < "4.0"
+mypy-extensions==1.0.0 ; python_version >= "3.8" and python_version < "4.0"
+openpyxl==3.1.1 ; python_version >= "3.8" and python_version < "4.0"
+packaging==23.0 ; python_version >= "3.8" and python_version < "4.0"
+pathspec==0.11.0 ; python_version >= "3.8" and python_version < "4.0"
+pillow==9.4.0 ; python_version >= "3.8" and python_version < "4.0"
+platformdirs==3.1.0 ; python_version >= "3.8" and python_version < "4.0"
+playwright==1.27.1 ; python_version >= "3.8" and python_version < "4.0"
+pluggy==1.0.0 ; python_version >= "3.8" and python_version < "4.0"
+pyaes==1.6.1 ; python_version >= "3.8" and python_version < "4.0"
+pycparser==2.21 ; python_version >= "3.8" and python_version < "4.0"
+pydantic==1.10.5 ; python_version >= "3.8" and python_version < "4.0"
+pyee==8.1.0 ; python_version >= "3.8" and python_version < "4.0"
+pygments==2.14.0 ; python_version >= "3.8" and python_version < "4.0"
+pypng==0.20220715.0 ; python_version >= "3.8" and python_version < "4.0"
+pyppeteer==1.0.2 ; python_version >= "3.8" and python_version < "4.0"
+pyrogram==2.0.100 ; python_version >= "3.8" and python_version < "4.0"
+pysocks==1.7.1 ; python_version >= "3.8" and python_version < "4.0"
+pytest-asyncio==0.20.3 ; python_version >= "3.8" and python_version < "4.0"
+pytest==7.2.2 ; python_version >= "3.8" and python_version < "4.0"
+python-dotenv==1.0.0 ; python_version >= "3.8" and python_version < "4.0"
+python-telegram-bot[ext,rate-limiter]==20.1 ; python_version >= "3.8" and python_version < "4.0"
+pytz-deprecation-shim==0.1.0.post0 ; python_version >= "3.8" and python_version < "4.0"
+pytz==2022.7.1 ; python_version >= "3.8" and python_version < "4.0"
+pyyaml==6.0 ; python_version >= "3.8" and python_version < "4.0"
+qrcode==7.4.2 ; python_version >= "3.8" and python_version < "4.0"
+redis==4.5.1 ; python_version >= "3.8" and python_version < "4.0"
+rfc3986[idna2008]==1.5.0 ; python_version >= "3.8" and python_version < "4.0"
+rich==13.3.2 ; python_version >= "3.8" and python_version < "4.0"
+sentry-sdk==1.16.0 ; python_version >= "3.8" and python_version < "4.0"
+setuptools==67.5.1 ; python_version >= "3.8" and python_version < "4.0"
+six==1.16.0 ; python_version >= "3.8" and python_version < "4.0"
+smmap==5.0.0 ; python_version >= "3.8" and python_version < "4.0"
+sniffio==1.3.0 ; python_version >= "3.8" and python_version < "4.0"
+sortedcontainers==2.4.0 ; python_version >= "3.8" and python_version < "4.0"
+soupsieve==2.4 ; python_version >= "3.8" and python_version < "4.0"
+sqlalchemy2-stubs==0.0.2a32 ; python_version >= "3.8" and python_version < "4.0"
+sqlalchemy==1.4.41 ; python_version >= "3.8" and python_version < "4.0"
+sqlmodel==0.0.8 ; python_version >= "3.8" and python_version < "4.0"
+starlette==0.25.0 ; python_version >= "3.8" and python_version < "4.0"
+tgcrypto==1.2.5 ; python_version >= "3.8" and python_version < "4.0"
+thefuzz==0.19.0 ; python_version >= "3.8" and python_version < "4.0"
+tomli==2.0.1 ; python_version >= "3.8" and python_version < "3.11"
+tornado==6.2 ; python_version >= "3.8" and python_version < "4.0"
+tqdm==4.65.0 ; python_version >= "3.8" and python_version < "4.0"
+typing-extensions==4.5.0 ; python_version >= "3.8" and python_version < "4.0"
+tzdata==2022.7 ; python_version >= "3.8" and python_version < "4.0"
+tzlocal==4.2 ; python_version >= "3.8" and python_version < "4.0"
+ujson==5.7.0 ; python_version >= "3.8" and python_version < "4.0"
+urllib3==1.26.14 ; python_version >= "3.8" and python_version < "4.0"
+uvicorn[standard]==0.21.0 ; python_version >= "3.8" and python_version < "4.0"
+uvloop==0.17.0 ; sys_platform != "win32" and sys_platform != "cygwin" and platform_python_implementation != "PyPy" and python_version >= "3.8" and python_version < "4.0"
+watchfiles==0.18.1 ; python_version >= "3.8" and python_version < "4.0"
+websockets==10.4 ; python_version >= "3.8" and python_version < "4.0"
+yarl==1.8.2 ; python_version >= "3.8" and python_version < "4.0"
+zipp==3.15.0 ; python_version >= "3.8" and python_version < "4.0"
diff --git a/resources/genshin/daily_note/daily_note.html b/resources/genshin/daily_note/daily_note.html
index d57fbad..d67adc6 100644
--- a/resources/genshin/daily_note/daily_note.html
+++ b/resources/genshin/daily_note/daily_note.html
@@ -1,131 +1,131 @@
-
-
-
-
-
-
-