From 29efa649a352e69b5edc302c33506d7b2edf3e31 Mon Sep 17 00:00:00 2001 From: omg-xtao <100690902+omg-xtao@users.noreply.github.com> Date: Sun, 23 Jul 2023 23:46:11 +0800 Subject: [PATCH] :sparkles: support task services --- alembic/versions/1df05b897d3f_tasks.py | 134 +++++++ core/services/sign/__init__.py | 1 - core/services/sign/services.py | 28 -- core/services/task/__init__.py | 1 + core/services/{sign => task}/models.py | 27 +- core/services/{sign => task}/repositories.py | 34 +- core/services/task/services.py | 213 +++++++++++ plugins/admin/sign_all.py | 4 +- plugins/admin/sign_status.py | 2 +- plugins/genshin/birthday.py | 156 ++++---- plugins/genshin/daily_note_tasks.py | 124 +++++++ plugins/genshin/sign.py | 14 +- plugins/jobs/birthday_card.py | 20 + plugins/jobs/daily_note.py | 20 + plugins/tools/birthday_card.py | 182 ++++++++++ plugins/tools/daily_note.py | 361 +++++++++++++++++++ plugins/tools/sign.py | 33 +- 17 files changed, 1183 insertions(+), 171 deletions(-) create mode 100644 alembic/versions/1df05b897d3f_tasks.py delete mode 100644 core/services/sign/__init__.py delete mode 100644 core/services/sign/services.py create mode 100644 core/services/task/__init__.py rename core/services/{sign => task}/models.py (56%) rename core/services/{sign => task}/repositories.py (51%) create mode 100644 core/services/task/services.py create mode 100644 plugins/genshin/daily_note_tasks.py create mode 100644 plugins/jobs/birthday_card.py create mode 100644 plugins/jobs/daily_note.py create mode 100644 plugins/tools/birthday_card.py create mode 100644 plugins/tools/daily_note.py diff --git a/alembic/versions/1df05b897d3f_tasks.py b/alembic/versions/1df05b897d3f_tasks.py new file mode 100644 index 00000000..118aed35 --- /dev/null +++ b/alembic/versions/1df05b897d3f_tasks.py @@ -0,0 +1,134 @@ +"""tasks + +Revision ID: 1df05b897d3f +Revises: a1c10da5704b +Create Date: 2023-07-23 14:44:59.592519 + +""" +import logging + +from alembic import op +import sqlalchemy as sa +from sqlalchemy import text +from sqlalchemy.dialects import mysql +from sqlalchemy.exc import NoSuchTableError + +# revision identifiers, used by Alembic. +revision = "1df05b897d3f" +down_revision = "a1c10da5704b" +branch_labels = None +depends_on = None + +logger = logging.getLogger(__name__) + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + connection = op.get_bind() + task_table = op.create_table( + "task", + sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), + sa.Column("user_id", sa.BigInteger(), nullable=True), + sa.Column("chat_id", sa.BigInteger(), nullable=True), + sa.Column( + "time_created", + sa.DateTime(), + server_default=sa.text("now()"), + nullable=True, + ), + sa.Column("time_updated", sa.DateTime(), nullable=True), + sa.Column( + "type", + sa.Enum( + "SIGN", + "RESIN", + "REALM", + "EXPEDITION", + "TRANSFORMER", + "CARD", + name="tasktypeenum", + ), + nullable=True, + ), + sa.Column( + "status", + sa.Enum( + "STATUS_SUCCESS", + "INVALID_COOKIES", + "ALREADY_CLAIMED", + "NEED_CHALLENGE", + "GENSHIN_EXCEPTION", + "TIMEOUT_ERROR", + "BAD_REQUEST", + "FORBIDDEN", + name="taskstatusenum", + ), + nullable=True, + ), + sa.Column("data", sa.JSON(), nullable=True), + sa.PrimaryKeyConstraint("id"), + mysql_charset="utf8mb4", + mysql_collate="utf8mb4_general_ci", + ) + op.create_index("task_1", "task", ["user_id"], unique=False) + try: + statement = "SELECT * FROM sign;" + old_sign_table_data = connection.execute(text(statement)) + except NoSuchTableError: + logger.warning("Table 'sign' doesn't exist") + return # should not happen + if old_sign_table_data is not None: + for row in old_sign_table_data: + try: + user_id = row["user_id"] + chat_id = row["chat_id"] + time_created = row["time_created"] + time_updated = row["time_updated"] + status = row["status"] + task_type = "SIGN" + insert = task_table.insert().values( + user_id=int(user_id), + chat_id=int(chat_id), + time_created=time_created, + time_updated=time_updated, + type=task_type, + status=status, + ) + with op.get_context().autocommit_block(): + connection.execute(insert) + except Exception as exc: # pylint: disable=W0703 + logger.error("Process sign->task Exception", exc_info=exc) # pylint: disable=W0703 + op.drop_table("sign") + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "sign", + sa.Column("id", mysql.INTEGER(), autoincrement=False, nullable=False), + sa.Column("user_id", mysql.BIGINT(), autoincrement=False, nullable=False), + sa.Column("chat_id", mysql.BIGINT(), autoincrement=False, nullable=True), + sa.Column("time_created", mysql.DATETIME(), nullable=True), + sa.Column("time_updated", mysql.DATETIME(), nullable=True), + sa.Column( + "status", + mysql.ENUM( + "STATUS_SUCCESS", + "INVALID_COOKIES", + "ALREADY_CLAIMED", + "GENSHIN_EXCEPTION", + "TIMEOUT_ERROR", + "BAD_REQUEST", + "FORBIDDEN", + ), + nullable=True, + ), + sa.PrimaryKeyConstraint("id", "user_id"), + mysql_collate="utf8mb4_general_ci", + mysql_default_charset="utf8mb4", + mysql_engine="InnoDB", + ) + op.drop_index("task_1", table_name="task") + op.drop_table("task") + # ### end Alembic commands ### diff --git a/core/services/sign/__init__.py b/core/services/sign/__init__.py deleted file mode 100644 index 9b51e2f5..00000000 --- a/core/services/sign/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""SignService""" diff --git a/core/services/sign/services.py b/core/services/sign/services.py deleted file mode 100644 index 74de7aa8..00000000 --- a/core/services/sign/services.py +++ /dev/null @@ -1,28 +0,0 @@ -from core.base_service import BaseService -from core.services.sign.models import Sign -from core.services.sign.repositories import SignRepository - -__all__ = ["SignServices"] - - -class SignServices(BaseService): - def __init__(self, sign_repository: SignRepository) -> None: - self._repository: SignRepository = sign_repository - - async def get_all(self): - return await self._repository.get_all() - - async def add(self, sign: Sign): - return await self._repository.add(sign) - - async def remove(self, sign: Sign): - return await self._repository.remove(sign) - - async def update(self, sign: Sign): - return await self._repository.update(sign) - - async def get_by_user_id(self, user_id: int): - return await self._repository.get_by_user_id(user_id) - - async def get_by_chat_id(self, chat_id: int): - return await self._repository.get_by_chat_id(chat_id) diff --git a/core/services/task/__init__.py b/core/services/task/__init__.py new file mode 100644 index 00000000..0835fdfa --- /dev/null +++ b/core/services/task/__init__.py @@ -0,0 +1 @@ +"""TaskService""" diff --git a/core/services/sign/models.py b/core/services/task/models.py similarity index 56% rename from core/services/sign/models.py rename to core/services/task/models.py index dd62239d..5387ea43 100644 --- a/core/services/sign/models.py +++ b/core/services/task/models.py @@ -1,15 +1,15 @@ import enum from datetime import datetime -from typing import Optional +from typing import Optional, Dict, Any -from sqlalchemy import func, BigInteger +from sqlalchemy import func, BigInteger, JSON from sqlmodel import Column, DateTime, Enum, Field, SQLModel, Integer -__all__ = ("SignStatusEnum", "Sign") +__all__ = ("Task", "TaskStatusEnum", "TaskTypeEnum") -class SignStatusEnum(int, enum.Enum): - STATUS_SUCCESS = 0 # 签到成功 +class TaskStatusEnum(int, enum.Enum): + STATUS_SUCCESS = 0 # 任务执行成功 INVALID_COOKIES = 1 # Cookie无效 ALREADY_CLAIMED = 2 # 已经获取奖励 NEED_CHALLENGE = 3 # 需要验证码 @@ -19,15 +19,26 @@ class SignStatusEnum(int, enum.Enum): FORBIDDEN = 7 # 这错误一般为通知失败 机器人被用户BAN -class Sign(SQLModel, table=True): +class TaskTypeEnum(int, enum.Enum): + SIGN = 0 # 签到 + RESIN = 1 # 体力 + REALM = 2 # 洞天宝钱 + EXPEDITION = 3 # 委托 + TRANSFORMER = 4 # 参量质变仪 + CARD = 5 # 生日画片 + + +class Task(SQLModel, table=True): __table_args__ = dict(mysql_charset="utf8mb4", mysql_collate="utf8mb4_general_ci") id: Optional[int] = Field( default=None, primary_key=True, sa_column=Column(Integer(), primary_key=True, autoincrement=True) ) user_id: int = Field(primary_key=True, sa_column=Column(BigInteger(), index=True)) - chat_id: Optional[int] = Field(default=None) + chat_id: Optional[int] = Field(default=None, sa_column=Column(BigInteger())) time_created: Optional[datetime] = Field( sa_column=Column(DateTime, server_default=func.now()) # pylint: disable=E1102 ) time_updated: Optional[datetime] = Field(sa_column=Column(DateTime, onupdate=func.now())) # pylint: disable=E1102 - status: Optional[SignStatusEnum] = Field(sa_column=Column(Enum(SignStatusEnum))) + type: TaskTypeEnum = Field(primary_key=True, sa_column=Column(Enum(TaskTypeEnum))) + status: Optional[TaskStatusEnum] = Field(sa_column=Column(Enum(TaskStatusEnum))) + data: Optional[Dict[str, Any]] = Field(sa_column=Column(JSON)) diff --git a/core/services/sign/repositories.py b/core/services/task/repositories.py similarity index 51% rename from core/services/sign/repositories.py rename to core/services/task/repositories.py index 9eccbc16..c509836e 100644 --- a/core/services/sign/repositories.py +++ b/core/services/task/repositories.py @@ -4,47 +4,47 @@ from core.base_service import BaseService from core.dependence.database import Database -from core.services.sign.models import Sign +from core.services.task.models import Task, TaskTypeEnum from core.sqlmodel.session import AsyncSession -__all__ = ("SignRepository",) +__all__ = ("TaskRepository",) -class SignRepository(BaseService.Component): +class TaskRepository(BaseService.Component): def __init__(self, database: Database): self.engine = database.engine - async def add(self, sign: Sign): + async def add(self, task: Task): async with AsyncSession(self.engine) as session: - session.add(sign) + session.add(task) await session.commit() - async def remove(self, sign: Sign): + async def remove(self, task: Task): async with AsyncSession(self.engine) as session: - await session.delete(sign) + await session.delete(task) await session.commit() - async def update(self, sign: Sign) -> Sign: + async def update(self, task: Task) -> Task: async with AsyncSession(self.engine) as session: - session.add(sign) + session.add(task) await session.commit() - await session.refresh(sign) - return sign + await session.refresh(task) + return task - async def get_by_user_id(self, user_id: int) -> Optional[Sign]: + async def get_by_user_id(self, user_id: int, task_type: TaskTypeEnum) -> Optional[Task]: async with AsyncSession(self.engine) as session: - statement = select(Sign).where(Sign.user_id == user_id) + statement = select(Task).where(Task.user_id == user_id).where(Task.type == task_type) results = await session.exec(statement) return results.first() - async def get_by_chat_id(self, chat_id: int) -> Optional[List[Sign]]: + async def get_by_chat_id(self, chat_id: int, task_type: TaskTypeEnum) -> Optional[List[Task]]: async with AsyncSession(self.engine) as session: - statement = select(Sign).where(Sign.chat_id == chat_id) + statement = select(Task).where(Task.chat_id == chat_id).where(Task.type == task_type) results = await session.exec(statement) return results.all() - async def get_all(self) -> List[Sign]: + async def get_all(self, task_type: TaskTypeEnum) -> List[Task]: async with AsyncSession(self.engine) as session: - query = select(Sign) + query = select(Task).where(Task.type == task_type) results = await session.exec(query) return results.all() diff --git a/core/services/task/services.py b/core/services/task/services.py new file mode 100644 index 00000000..74435fe2 --- /dev/null +++ b/core/services/task/services.py @@ -0,0 +1,213 @@ +import datetime +from typing import Optional, Dict, Any + +from core.base_service import BaseService +from core.services.task.models import Task, TaskTypeEnum +from core.services.task.repositories import TaskRepository + +__all__ = [ + "TaskServices", + "SignServices", + "TaskCardServices", + "TaskResinServices", + "TaskRealmServices", + "TaskExpeditionServices", +] + + +class TaskServices(BaseService): + TASK_TYPE: TaskTypeEnum + + def __init__(self, task_repository: TaskRepository) -> None: + self._repository: TaskRepository = task_repository + + async def add(self, task: Task): + return await self._repository.add(task) + + async def remove(self, task: Task): + return await self._repository.remove(task) + + async def update(self, task: Task): + task.time_updated = datetime.datetime.now() + return await self._repository.update(task) + + async def get_by_user_id(self, user_id: int): + return await self._repository.get_by_user_id(user_id, self.TASK_TYPE) + + async def get_all(self): + return await self._repository.get_all(self.TASK_TYPE) + + def create(self, user_id: int, chat_id: int, status: int, data: Optional[Dict[str, Any]] = None): + return Task( + user_id=user_id, + chat_id=chat_id, + time_created=datetime.datetime.now(), + status=status, + type=self.TASK_TYPE, + data=data, + ) + + +class SignServices(BaseService): + TASK_TYPE = TaskTypeEnum.SIGN + + def __init__(self, task_repository: TaskRepository) -> None: + self._repository: TaskRepository = task_repository + + async def add(self, task: Task): + return await self._repository.add(task) + + async def remove(self, task: Task): + return await self._repository.remove(task) + + async def update(self, task: Task): + task.time_updated = datetime.datetime.now() + return await self._repository.update(task) + + async def get_by_user_id(self, user_id: int): + return await self._repository.get_by_user_id(user_id, self.TASK_TYPE) + + async def get_all(self): + return await self._repository.get_all(self.TASK_TYPE) + + def create(self, user_id: int, chat_id: int, status: int, data: Optional[Dict[str, Any]] = None): + return Task( + user_id=user_id, + chat_id=chat_id, + time_created=datetime.datetime.now(), + status=status, + type=self.TASK_TYPE, + data=data, + ) + + +class TaskCardServices(BaseService): + TASK_TYPE = TaskTypeEnum.CARD + + def __init__(self, task_repository: TaskRepository) -> None: + self._repository: TaskRepository = task_repository + + async def add(self, task: Task): + return await self._repository.add(task) + + async def remove(self, task: Task): + return await self._repository.remove(task) + + async def update(self, task: Task): + task.time_updated = datetime.datetime.now() + return await self._repository.update(task) + + async def get_by_user_id(self, user_id: int): + return await self._repository.get_by_user_id(user_id, self.TASK_TYPE) + + async def get_all(self): + return await self._repository.get_all(self.TASK_TYPE) + + def create(self, user_id: int, chat_id: int, status: int, data: Optional[Dict[str, Any]] = None): + return Task( + user_id=user_id, + chat_id=chat_id, + time_created=datetime.datetime.now(), + status=status, + type=self.TASK_TYPE, + data=data, + ) + + +class TaskResinServices(BaseService): + TASK_TYPE = TaskTypeEnum.RESIN + + def __init__(self, task_repository: TaskRepository) -> None: + self._repository: TaskRepository = task_repository + + async def add(self, task: Task): + return await self._repository.add(task) + + async def remove(self, task: Task): + return await self._repository.remove(task) + + async def update(self, task: Task): + task.time_updated = datetime.datetime.now() + return await self._repository.update(task) + + async def get_by_user_id(self, user_id: int): + return await self._repository.get_by_user_id(user_id, self.TASK_TYPE) + + async def get_all(self): + return await self._repository.get_all(self.TASK_TYPE) + + def create(self, user_id: int, chat_id: int, status: int, data: Optional[Dict[str, Any]] = None): + return Task( + user_id=user_id, + chat_id=chat_id, + time_created=datetime.datetime.now(), + status=status, + type=self.TASK_TYPE, + data=data, + ) + + +class TaskRealmServices(BaseService): + TASK_TYPE = TaskTypeEnum.REALM + + def __init__(self, task_repository: TaskRepository) -> None: + self._repository: TaskRepository = task_repository + + async def add(self, task: Task): + return await self._repository.add(task) + + async def remove(self, task: Task): + return await self._repository.remove(task) + + async def update(self, task: Task): + task.time_updated = datetime.datetime.now() + return await self._repository.update(task) + + async def get_by_user_id(self, user_id: int): + return await self._repository.get_by_user_id(user_id, self.TASK_TYPE) + + async def get_all(self): + return await self._repository.get_all(self.TASK_TYPE) + + def create(self, user_id: int, chat_id: int, status: int, data: Optional[Dict[str, Any]] = None): + return Task( + user_id=user_id, + chat_id=chat_id, + time_created=datetime.datetime.now(), + status=status, + type=self.TASK_TYPE, + data=data, + ) + + +class TaskExpeditionServices(BaseService): + TASK_TYPE = TaskTypeEnum.EXPEDITION + + def __init__(self, task_repository: TaskRepository) -> None: + self._repository: TaskRepository = task_repository + + async def add(self, task: Task): + return await self._repository.add(task) + + async def remove(self, task: Task): + return await self._repository.remove(task) + + async def update(self, task: Task): + task.time_updated = datetime.datetime.now() + return await self._repository.update(task) + + async def get_by_user_id(self, user_id: int): + return await self._repository.get_by_user_id(user_id, self.TASK_TYPE) + + async def get_all(self): + return await self._repository.get_all(self.TASK_TYPE) + + def create(self, user_id: int, chat_id: int, status: int, data: Optional[Dict[str, Any]] = None): + return Task( + user_id=user_id, + chat_id=chat_id, + time_created=datetime.datetime.now(), + status=status, + type=self.TASK_TYPE, + data=data, + ) diff --git a/plugins/admin/sign_all.py b/plugins/admin/sign_all.py index 006ded94..57064038 100644 --- a/plugins/admin/sign_all.py +++ b/plugins/admin/sign_all.py @@ -1,5 +1,5 @@ from telegram import Update -from telegram.ext import CallbackContext, CommandHandler +from telegram.ext import CallbackContext from core.plugin import Plugin, handler from plugins.tools.sign import SignSystem, SignJobType @@ -10,7 +10,7 @@ class SignAll(Plugin): def __init__(self, sign_system: SignSystem): self.sign_system = sign_system - @handler(CommandHandler, command="sign_all", block=False, admin=True) + @handler.command(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) diff --git a/plugins/admin/sign_status.py b/plugins/admin/sign_status.py index 8dcfea15..ca1be954 100644 --- a/plugins/admin/sign_status.py +++ b/plugins/admin/sign_status.py @@ -2,7 +2,7 @@ from telegram.ext import CallbackContext, CommandHandler from core.plugin import Plugin, handler -from core.services.sign.services import SignServices +from core.services.task.services import SignServices from utils.log import logger diff --git a/plugins/genshin/birthday.py b/plugins/genshin/birthday.py index 183a9239..49fb5900 100644 --- a/plugins/genshin/birthday.py +++ b/plugins/genshin/birthday.py @@ -1,41 +1,33 @@ import re -from datetime import datetime -from typing import List, Optional, TYPE_CHECKING +from typing import TYPE_CHECKING -from simnet.client.routes import Route -from simnet.errors import BadRequest as SimnetBadRequest -from simnet.utils.player import recognize_genshin_server, recognize_genshin_game_biz +from simnet import Region +from simnet.errors import RegionNotSupported from telegram import InlineKeyboardMarkup, InlineKeyboardButton from telegram.constants import ParseMode from telegram.ext import filters, MessageHandler, CommandHandler from telegram.helpers import create_deep_linked_url -from core.basemodel import RegionEnum from core.plugin import Plugin, handler from core.services.cookies import CookiesService -from core.services.users.services import UserService +from core.services.task.models import Task as TaskUser, TaskStatusEnum +from core.services.task.services import TaskCardServices +from core.services.users.services import UserService, UserAdminService from metadata.genshin import AVATAR_DATA from metadata.shortname import roleToId, roleToName -from modules.apihelper.client.components.calendar import Calendar +from plugins.tools.birthday_card import ( + BirthdayCardSystem, + rm_starting_str, + BirthdayCardNoBirthdayError, + BirthdayCardAlreadyClaimedError, +) from plugins.tools.genshin import PlayerNotFoundError, CookiesNotFoundError, GenshinHelper from utils.log import logger if TYPE_CHECKING: - from simnet import GenshinClient from telegram import Update from telegram.ext import ContextTypes -BIRTHDAY_URL = Route( - "https://hk4e-api.mihoyo.com/event/birthdaystar/account/post_my_draw", -) - - -def rm_starting_str(string, starting): - """Remove the starting character from a string.""" - while string[0] == str(starting): - string = string[1:] - return string - class BirthdayPlugin(Plugin): """Birthday.""" @@ -45,36 +37,23 @@ def __init__( user_service: UserService, helper: GenshinHelper, cookie_service: CookiesService, + card_system: BirthdayCardSystem, + user_admin_service: UserAdminService, + card_service: TaskCardServices, ): """Load Data.""" - self.birthday_list = {} self.user_service = user_service self.cookie_service = cookie_service self.helper = helper - - async def initialize(self): - self.birthday_list = await Calendar.async_gen_birthday_list() - self.birthday_list.get("6_1", []).append("派蒙") - - async def get_today_birthday(self) -> List[str]: - key = ( - rm_starting_str(datetime.now().strftime("%m"), "0") - + "_" - + rm_starting_str(datetime.now().strftime("%d"), "0") - ) - return (self.birthday_list.get(key, [])).copy() + self.card_system = card_system + self.user_admin_service = user_admin_service + self.card_service = card_service @handler.command(command="birthday", block=False) async def command_start(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE") -> None: message = update.effective_message user = update.effective_user - key = ( - rm_starting_str(datetime.now().strftime("%m"), "0") - + "_" - + rm_starting_str(datetime.now().strftime("%d"), "0") - ) args = self.get_args(context) - if len(args) >= 1: msg = args[0] logger.info("用户 %s[%s] 查询角色生日命令请求 || 参数 %s", user.full_name, user.id, msg) @@ -83,7 +62,7 @@ async def command_start(self, update: "Update", context: "ContextTypes.DEFAULT_T month = rm_starting_str(re.findall(r"\d+", msg)[0], "0") day = rm_starting_str(re.findall(r"\d+", msg)[1], "0") key = f"{month}_{day}" - day_list = self.birthday_list.get(key, []) + day_list = self.card_system.birthday_list.get(key, []) date = f"{month}月{day}日" text = f"{date} 是 {'、'.join(day_list)} 的生日哦~" if day_list else f"{date} 没有角色过生日哦~" except IndexError: @@ -102,13 +81,11 @@ async def command_start(self, update: "Update", context: "ContextTypes.DEFAULT_T birthday = AVATAR_DATA[aid]["birthday"] text = f"{name} 的生日是 {birthday[0]}月{birthday[1]}日 哦~" reply_message = await message.reply_text(text) - except KeyError: reply_message = await message.reply_text("请输入正确的日期格式,如1-1,或输入正确的角色名称。") - else: logger.info("用户 %s[%s] 查询今日角色生日列表", user.full_name, user.id) - today_list = await self.get_today_birthday() + today_list = self.card_system.get_today_birthday() text = f"今天是 {'、'.join(today_list)} 的生日哦~" if today_list else "今天没有角色过生日哦~" reply_message = await message.reply_text(text) @@ -116,60 +93,65 @@ async def command_start(self, update: "Update", context: "ContextTypes.DEFAULT_T self.add_delete_message_job(message) self.add_delete_message_job(reply_message) - @staticmethod - async def get_card(client: "GenshinClient", role_id: int) -> None: - """领取画片""" - url = BIRTHDAY_URL.get_url() - params = { - "game_biz": recognize_genshin_game_biz(client.player_id), - "lang": "zh-cn", - "badge_uid": client.player_id, - "badge_region": recognize_genshin_server(client.player_id), - "activity_id": "20220301153521", - } - json = { - "role_id": role_id, - } - await client.request_lab(url, method="POST", params=params, data=json) - - @staticmethod - def role_to_id(name: str) -> Optional[int]: - if name == "派蒙": - return -1 - return roleToId(name) + async def _process_auto_birthday_card(self, user_id: int, chat_id: int, method: str) -> str: + try: + async with self.helper.genshin(user_id) as client: + if client.region != Region.CHINESE: + return "此功能当前只支持国服账号哦~" + except (PlayerNotFoundError, CookiesNotFoundError): + return "未查询到账号信息,请先私聊派蒙绑定账号" + user: TaskUser = await self.card_service.get_by_user_id(user_id) + if user: + if method == "关闭": + await self.card_service.remove(user) + return "关闭自动领取生日画片成功" + if method == "开启": + if user.chat_id == chat_id: + return "自动领取生日画片已经开启过了" + user.chat_id = chat_id + user.status = TaskStatusEnum.STATUS_SUCCESS + await self.card_service.update(user) + return "修改自动领取生日画片对话成功" + elif method == "关闭": + return "您还没有开启自动领取生日画片" + elif method == "开启": + user = self.card_service.create(user_id, chat_id, TaskStatusEnum.STATUS_SUCCESS) + await self.card_service.add(user) + return "开启自动领取生日画片成功" @handler(CommandHandler, command="birthday_card", block=False) @handler(MessageHandler, filters=filters.Regex("^领取角色生日画片$"), block=False) async def command_birthday_card_start(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE") -> None: message = update.effective_message user = update.effective_user + args = self.get_args(context) + if len(args) >= 1: + msg = None + if args[0] == "开启自动领取": + if await self.user_admin_service.is_admin(user.id): + msg = await self._process_auto_birthday_card(user.id, message.chat_id, "开启") + else: + msg = await self._process_auto_birthday_card(user.id, user.id, "开启") + elif args[0] == "关闭自动领取": + msg = await self._process_auto_birthday_card(user.id, message.chat_id, "关闭") + if msg: + 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(reply_message, delay=30) + self.add_delete_message_job(message, delay=30) + return logger.info("用户 %s[%s] 领取生日画片命令请求", user.full_name, user.id) - today_list = await self.get_today_birthday() - if not today_list: - reply_message = await message.reply_text("今天没有角色过生日哦~") - if filters.ChatType.GROUPS.filter(reply_message): - self.add_delete_message_job(message) - self.add_delete_message_job(reply_message) - return try: async with self.helper.genshin(user.id) as client: - if client.region == RegionEnum.HOYOLAB: + try: + text = await self.card_system.start_get_card(client) + except RegionNotSupported: text = "此功能当前只支持国服账号哦~" - else: - game_biz = recognize_genshin_game_biz(client.player_id) - region = recognize_genshin_server(client.player_id) - await client.get_hk4e_token_by_cookie_token(game_biz, region) - for name in today_list.copy(): - if role_id := self.role_to_id(name): - try: - await self.get_card(client, role_id) - except SimnetBadRequest as e: - if e.ret_code in {-512008, -512009}: # 未过生日、已领取过 - today_list.remove(name) - if today_list: - text = f"成功领取了 {'、'.join(today_list)} 的生日画片~" - else: - text = "没有领取到生日画片哦 ~ 可能是已经领取过了" + except BirthdayCardNoBirthdayError: + text = "今天没有角色过生日哦~" + except BirthdayCardAlreadyClaimedError: + text = "没有领取到生日画片哦 ~ 可能是已经领取过了" reply_message = await message.reply_text(text) if filters.ChatType.GROUPS.filter(reply_message): self.add_delete_message_job(message) diff --git a/plugins/genshin/daily_note_tasks.py b/plugins/genshin/daily_note_tasks.py new file mode 100644 index 00000000..9c75201e --- /dev/null +++ b/plugins/genshin/daily_note_tasks.py @@ -0,0 +1,124 @@ +from pydantic import ValidationError +from simnet import Region +from simnet.errors import DataNotPublic, BadRequest as SimnetBadRequest +from telegram import ReplyKeyboardMarkup, ReplyKeyboardRemove, Update, KeyboardButton, WebAppInfo +from telegram.ext import CallbackContext, ConversationHandler, filters +from telegram.helpers import escape_markdown + +from core.config import config +from core.plugin import Plugin, conversation, handler +from core.services.cookies.services import CookiesService +from core.services.players.services import PlayersService, PlayerInfoService +from plugins.app.webapp import WebApp +from plugins.tools.daily_note import DailyNoteSystem, WebAppData +from plugins.tools.genshin import GenshinHelper, CookiesNotFoundError, PlayerNotFoundError +from utils.log import logger + +__all__ = ("DailyNoteTasksPlugin",) + + +SET_BY_WEB = 10100 + + +class DailyNoteTasksPlugin(Plugin.Conversation): + """自动便签提醒任务""" + + def __init__( + self, + players_service: PlayersService, + cookies_service: CookiesService, + player_info_service: PlayerInfoService, + helper: GenshinHelper, + note_system: DailyNoteSystem, + ): + self.cookies_service = cookies_service + self.players_service = players_service + self.player_info_service = player_info_service + self.helper = helper + self.note_system = note_system + + @conversation.entry_point + @handler.command(command="daily_note_tasks", filters=filters.ChatType.PRIVATE, block=False) + async def command_start(self, update: Update, _: CallbackContext) -> int: + user = update.effective_user + message = update.effective_message + logger.info("用户 %s[%s] 设置自动便签提醒命令请求", user.full_name, user.id) + text = await self.check_genshin_user(user.id, False) + if text != "ok": + await message.reply_text(text, reply_markup=ReplyKeyboardRemove()) + return ConversationHandler.END + note_user = await self.note_system.get_single_task_user(user.id) + url = f"{config.pass_challenge_user_web}/tasks1?command=tasks&bot_data={note_user.web_config}" + text = f'你好 {user.mention_markdown_v2()} {escape_markdown("!请点击下方按钮,开始设置,或者回复退出取消操作")}' + await message.reply_markdown_v2( + text, + reply_markup=ReplyKeyboardMarkup.from_button( + KeyboardButton( + text="点我开始设置", + web_app=WebAppInfo(url=url), + ) + ), + ) + return SET_BY_WEB + + async def check_genshin_user(self, user_id: int, request_note: bool) -> str: + try: + async with self.helper.genshin(user_id) as client: + client: "GenshinClient" + if request_note: + if client.region == Region.CHINESE: + await client.get_genshin_notes_by_stoken() + else: + await client.get_genshin_notes() + return "ok" + except ValueError: + return "Cookies 缺少 stoken ,请尝试重新绑定账号。" + except DataNotPublic: + return "查询失败惹,可能是便签功能被禁用了?请尝试通过米游社或者 hoyolab 获取一次便签信息后重试。" + except SimnetBadRequest as e: + return f"获取便签失败,可能遇到验证码风控,请尝试重新绑定账号。{e}" + except (CookiesNotFoundError, PlayerNotFoundError): + return "未查询到您所绑定的账号信息,请先私聊派蒙绑定账号" + + @conversation.state(state=SET_BY_WEB) + @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=False) + async def set_by_web_text(self, update: Update, _: CallbackContext) -> int: + message = update.effective_message + if message.text == "退出": + await message.reply_text("退出任务", reply_markup=ReplyKeyboardRemove()) + return ConversationHandler.END + await message.reply_text("输入错误,请重新输入") + return SET_BY_WEB + + @conversation.state(state=SET_BY_WEB) + @handler.message(filters=filters.StatusUpdate.WEB_APP_DATA, block=False) + async def set_by_web(self, update: Update, _: CallbackContext) -> int: + user = update.effective_user + message = update.effective_message + web_app_data = message.web_app_data + if web_app_data: + result = WebApp.de_web_app_data(web_app_data.data) + if result.code == 0: + if result.path == "tasks": + try: + validate = WebAppData(**result.data) + except ValidationError: + await message.reply_text( + "数据错误\n" "树脂提醒数值必须在 100 ~ 160 之间\n" "洞天宝钱提醒数值必须在 100 ~ 2400 之间", + reply_markup=ReplyKeyboardRemove(), + ) + return ConversationHandler.END + need_note = await self.check_genshin_user(user.id, True) + if need_note != "ok": + await message.reply_text(need_note, reply_markup=ReplyKeyboardRemove()) + return ConversationHandler.END + await self.note_system.import_web_config(user.id, validate) + await message.reply_text("修改设置成功", reply_markup=ReplyKeyboardRemove()) + else: + logger.warning( + "用户 %s[%s] WEB_APP_DATA 请求错误 [%s]%s", user.full_name, user.id, result.code, result.message + ) + await message.reply_text(f"WebApp返回错误 {result.message}", reply_markup=ReplyKeyboardRemove()) + else: + logger.warning("用户 %s[%s] WEB_APP_DATA 非法数据", user.full_name, user.id) + return ConversationHandler.END diff --git a/plugins/genshin/sign.py b/plugins/genshin/sign.py index a6fd2c31..024ec1a5 100644 --- a/plugins/genshin/sign.py +++ b/plugins/genshin/sign.py @@ -1,4 +1,3 @@ -import datetime from typing import Optional, Tuple from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup @@ -12,8 +11,8 @@ from core.plugin import Plugin, handler from core.services.cookies import CookiesService from core.services.players import PlayersService -from core.services.sign.models import Sign as SignUser, SignStatusEnum -from core.services.sign.services import SignServices +from core.services.task.models import Task as SignUser, TaskStatusEnum +from core.services.task.services import SignServices from core.services.users.services import UserAdminService from plugins.tools.genshin import PlayerNotFoundError, CookiesNotFoundError, GenshinHelper from plugins.tools.sign import SignSystem, NeedChallenge @@ -57,18 +56,13 @@ async def _process_auto_sign(self, user_id: int, chat_id: int, method: str) -> s if user.chat_id == chat_id: return "自动签到已经开启过了" user.chat_id = chat_id - user.status = SignStatusEnum.STATUS_SUCCESS + user.status = TaskStatusEnum.STATUS_SUCCESS await self.sign_service.update(user) return "修改自动签到通知对话成功" elif method == "关闭": return "您还没有开启自动签到" elif method == "开启": - user = SignUser( - user_id=user_id, - chat_id=chat_id, - time_created=datetime.datetime.now(), - status=SignStatusEnum.STATUS_SUCCESS, - ) + user = self.sign_service.create(user_id, chat_id, TaskStatusEnum.STATUS_SUCCESS) await self.sign_service.add(user) return "开启自动签到成功" diff --git a/plugins/jobs/birthday_card.py b/plugins/jobs/birthday_card.py new file mode 100644 index 00000000..6322b66b --- /dev/null +++ b/plugins/jobs/birthday_card.py @@ -0,0 +1,20 @@ +import datetime +from typing import TYPE_CHECKING + +from core.plugin import Plugin, job +from plugins.tools.birthday_card import BirthdayCardSystem +from utils.log import logger + +if TYPE_CHECKING: + from telegram.ext import ContextTypes + + +class CardJob(Plugin): + def __init__(self, card_system: BirthdayCardSystem): + self.card_system = card_system + + @job.run_daily(time=datetime.time(hour=0, minute=1, second=0), name="CardJob") + async def card(self, context: "ContextTypes.DEFAULT_TYPE"): + logger.info("正在执行自动领取生日画片") + await self.card_system.do_get_card_job(context) + logger.success("执行自动领取生日画片完成") diff --git a/plugins/jobs/daily_note.py b/plugins/jobs/daily_note.py new file mode 100644 index 00000000..4e2b7eed --- /dev/null +++ b/plugins/jobs/daily_note.py @@ -0,0 +1,20 @@ +import datetime +from typing import TYPE_CHECKING + +from core.plugin import Plugin, job +from plugins.tools.daily_note import DailyNoteSystem +from utils.log import logger + +if TYPE_CHECKING: + from telegram.ext import ContextTypes + + +class NotesJob(Plugin): + def __init__(self, daily_note_system: DailyNoteSystem): + self.daily_note_system = daily_note_system + + @job.run_repeating(interval=datetime.timedelta(minutes=20), name="NotesJob") + async def card(self, context: "ContextTypes.DEFAULT_TYPE"): + logger.info("正在执行自动便签提醒") + await self.daily_note_system.do_get_notes_job(context) + logger.success("执行自动便签提醒完成") diff --git a/plugins/tools/birthday_card.py b/plugins/tools/birthday_card.py new file mode 100644 index 00000000..bc8e9163 --- /dev/null +++ b/plugins/tools/birthday_card.py @@ -0,0 +1,182 @@ +from datetime import datetime +from typing import TYPE_CHECKING, List, Optional + +from simnet.errors import BadRequest as SimnetBadRequest, RegionNotSupported, InvalidCookies, TimedOut as SimnetTimedOut +from simnet.client.routes import Route +from simnet.utils.player import recognize_genshin_game_biz, recognize_genshin_server +from telegram.constants import ParseMode +from telegram.error import BadRequest, Forbidden + +from core.basemodel import RegionEnum +from core.plugin import Plugin +from core.services.task.models import TaskStatusEnum +from core.services.task.services import TaskCardServices +from metadata.shortname import roleToId +from modules.apihelper.client.components.calendar import Calendar +from plugins.tools.genshin import GenshinHelper, PlayerNotFoundError, CookiesNotFoundError +from utils.log import logger + +if TYPE_CHECKING: + from simnet import GenshinClient + from telegram.ext import ContextTypes + +BIRTHDAY_URL = Route( + "https://hk4e-api.mihoyo.com/event/birthdaystar/account/post_my_draw", +) + + +def rm_starting_str(string, starting): + """Remove the starting character from a string.""" + while string[0] == str(starting): + string = string[1:] + return string + + +class BirthdayCardNoBirthdayError(Exception): + pass + + +class BirthdayCardAlreadyClaimedError(Exception): + pass + + +class BirthdayCardSystem(Plugin): + def __init__( + self, + card_service: TaskCardServices, + genshin_helper: GenshinHelper, + ): + self.birthday_list = {} + self.card_service = card_service + self.genshin_helper = genshin_helper + + async def initialize(self): + self.birthday_list = await Calendar.async_gen_birthday_list() + self.birthday_list.get("6_1", []).append("派蒙") + + @property + def key(self): + return ( + rm_starting_str(datetime.now().strftime("%m"), "0") + + "_" + + rm_starting_str(datetime.now().strftime("%d"), "0") + ) + + def get_today_birthday(self) -> List[str]: + key = self.key + return (self.birthday_list.get(key, [])).copy() + + @staticmethod + def role_to_id(name: str) -> Optional[int]: + if name == "派蒙": + return -1 + return roleToId(name) + + @staticmethod + async def get_card(client: "GenshinClient", role_id: int) -> None: + """领取画片""" + url = BIRTHDAY_URL.get_url() + params = { + "game_biz": recognize_genshin_game_biz(client.player_id), + "lang": "zh-cn", + "badge_uid": client.player_id, + "badge_region": recognize_genshin_server(client.player_id), + "activity_id": "20220301153521", + } + json = { + "role_id": role_id, + } + try: + await client.request_lab(url, method="POST", params=params, data=json) + except SimnetBadRequest as e: + if e.retcode == -512008: + raise BirthdayCardNoBirthdayError from e # 未过生日 + if e.retcode == -512009: + raise BirthdayCardAlreadyClaimedError from e # 已领取过 + raise e + + async def start_get_card( + self, + client: "GenshinClient", + ) -> str: + if client.region == RegionEnum.HOYOLAB: + raise RegionNotSupported + today_list = self.get_today_birthday() + if not today_list: + raise BirthdayCardNoBirthdayError + game_biz = recognize_genshin_game_biz(client.player_id) + region = recognize_genshin_server(client.player_id) + await client.get_hk4e_token_by_cookie_token(game_biz, region) + for name in today_list.copy(): + if role_id := self.role_to_id(name): + try: + await self.get_card(client, role_id) + except BirthdayCardAlreadyClaimedError: + today_list.remove(name) + if today_list: + text = f"成功领取了 {'、'.join(today_list)} 的生日画片~" + else: + raise BirthdayCardAlreadyClaimedError + return text + + async def do_get_card_job(self, context: "ContextTypes.DEFAULT_TYPE"): + if not self.get_today_birthday(): + logger.info("今天没有角色过生日,跳过自动领取生日画片") + return + include_status: List[TaskStatusEnum] = [ + TaskStatusEnum.STATUS_SUCCESS, + TaskStatusEnum.TIMEOUT_ERROR, + ] + task_list = await self.card_service.get_all() + for task_db in task_list: + if task_db.status not in include_status: + continue + user_id = task_db.user_id + try: + async with self.genshin_helper.genshin(user_id) as client: + text = await self.start_get_card(client) + except InvalidCookies: + text = "自动领取生日画片执行失败,Cookie无效" + task_db.status = TaskStatusEnum.INVALID_COOKIES + except BirthdayCardAlreadyClaimedError: + text = "今天旅行者已经领取过了~" + task_db.status = TaskStatusEnum.ALREADY_CLAIMED + except SimnetBadRequest as exc: + text = f"自动领取生日画片执行失败,API返回信息为 {str(exc)}" + task_db.status = TaskStatusEnum.GENSHIN_EXCEPTION + except SimnetTimedOut: + text = "领取失败了呜呜呜 ~ 服务器连接超时 服务器熟啦 ~ " + task_db.status = TaskStatusEnum.TIMEOUT_ERROR + except PlayerNotFoundError: + logger.info("用户 user_id[%s] 玩家不存在 关闭并移除自动领取生日画片", user_id) + await self.card_service.remove(task_db) + continue + except CookiesNotFoundError: + logger.info("用户 user_id[%s] cookie 不存在 关闭并移除自动领取生日画片", user_id) + await self.card_service.remove(task_db) + continue + except RegionNotSupported: + logger.info("用户 user_id[%s] 不支持的服务器 关闭并移除自动领取生日画片", user_id) + await self.card_service.remove(task_db) + continue + except Exception as exc: + logger.error("执行自动领取生日画片时发生错误 user_id[%s]", user_id, exc_info=exc) + text = "自动领取生日画片失败了呜呜呜 ~ 执行自动领取生日画片时发生错误" + else: + task_db.status = TaskStatusEnum.STATUS_SUCCESS + if task_db.chat_id < 0: + text = f'NOTICE {task_db.user_id}\n\n{text}' + try: + await context.bot.send_message(task_db.chat_id, text, parse_mode=ParseMode.HTML) + except BadRequest as exc: + logger.error("执行自动领取生日画片时发生错误 user_id[%s] Message[%s]", user_id, exc.message) + task_db.status = TaskStatusEnum.BAD_REQUEST + except Forbidden as exc: + logger.error("执行自动领取生日画片时发生错误 user_id[%s] message[%s]", user_id, exc.message) + task_db.status = TaskStatusEnum.FORBIDDEN + except Exception as exc: + logger.error("执行自动领取生日画片时发生错误 user_id[%s]", user_id, exc_info=exc) + continue + else: + task_db.status = TaskStatusEnum.STATUS_SUCCESS + await self.card_service.update(task_db) diff --git a/plugins/tools/daily_note.py b/plugins/tools/daily_note.py new file mode 100644 index 00000000..31fa9038 --- /dev/null +++ b/plugins/tools/daily_note.py @@ -0,0 +1,361 @@ +import base64 +from typing import TYPE_CHECKING, List, Optional + +from pydantic import BaseModel, validator +from simnet.errors import BadRequest as SimnetBadRequest, InvalidCookies, TimedOut as SimnetTimedOut +from telegram.constants import ParseMode +from telegram.error import BadRequest, Forbidden + +from core.basemodel import RegionEnum +from core.plugin import Plugin +from core.services.task.models import Task as TaskUser, TaskStatusEnum +from core.services.task.services import TaskResinServices, TaskRealmServices, TaskExpeditionServices +from plugins.tools.genshin import GenshinHelper, PlayerNotFoundError, CookiesNotFoundError +from utils.log import logger + +if TYPE_CHECKING: + from simnet import GenshinClient + from telegram.ext import ContextTypes + + +class TaskDataBase(BaseModel): + noticed: Optional[bool] = False + + +class ResinData(TaskDataBase): + notice_num: Optional[int] = 140 + + @validator("notice_num") + def notice_num_validator(cls, v): + if v < 100 or v > 160: + raise ValueError("树脂提醒数值必须在 100 ~ 160 之间") + return v + + +class RealmData(TaskDataBase): + notice_num: Optional[int] = 2000 + + @validator("notice_num") + def notice_num_validator(cls, v): + if v < 100 or v > 2400: + raise ValueError("洞天宝钱提醒数值必须在 100 ~ 2400 之间") + return v + + +class ExpeditionData(TaskDataBase): + pass + + +class WebAppData(BaseModel): + resin: Optional[ResinData] + realm: Optional[RealmData] + expedition: Optional[ExpeditionData] + + +class DailyNoteTaskUser: + def __init__( + self, + user_id: int, + resin_db: Optional[TaskUser] = None, + realm_db: Optional[TaskUser] = None, + expedition_db: Optional[TaskUser] = None, + ): + self.user_id = user_id + self.resin_db = resin_db + self.realm_db = realm_db + self.expedition_db = expedition_db + self.resin = ResinData(**self.resin_db.data) if self.resin_db else None + self.realm = RealmData(**self.realm_db.data) if self.realm_db else None + self.expedition = ExpeditionData(**self.expedition_db.data) if self.expedition_db else None + + @property + def status(self) -> TaskStatusEnum: + return max( + [ + self.resin_db.status if self.resin_db else TaskStatusEnum.STATUS_SUCCESS, + self.realm_db.status if self.realm_db else TaskStatusEnum.STATUS_SUCCESS, + self.expedition_db.status if self.expedition_db else TaskStatusEnum.STATUS_SUCCESS, + ] + ) + + @status.setter + def status(self, value: TaskStatusEnum): + if self.resin_db: + self.resin_db.status = value + if self.realm_db: + self.realm_db.status = value + if self.expedition_db: + self.expedition_db.status = value + + @staticmethod + def js_bool(value: bool) -> str: + return "true" if value else "false" + + @staticmethod + def set_model_noticed(model: TaskDataBase): + data = model.copy(deep=True) + data.noticed = True + return data + + @property + def web_config(self) -> str: + return base64.b64encode( + ( + WebAppData( + resin=self.set_model_noticed(self.resin) if self.resin else None, + realm=self.set_model_noticed(self.realm) if self.realm else None, + expedition=self.set_model_noticed(self.expedition) if self.expedition else None, + ).json() + ).encode() + ).decode() + + def save(self): + if self.resin_db: + self.resin_db.data = self.resin.dict() + if self.realm_db: + self.realm_db.data = self.realm.dict() + if self.expedition_db: + self.expedition_db.data = self.expedition.dict() + + +class DailyNoteSystem(Plugin): + def __init__( + self, + genshin_helper: GenshinHelper, + resin_service: TaskResinServices, + realm_service: TaskRealmServices, + expedition_service: TaskExpeditionServices, + ): + self.genshin_helper = genshin_helper + self.resin_service = resin_service + self.realm_service = realm_service + self.expedition_service = expedition_service + + async def get_single_task_user(self, user_id: int) -> DailyNoteTaskUser: + resin_db = await self.resin_service.get_by_user_id(user_id) + realm_db = await self.realm_service.get_by_user_id(user_id) + expedition_db = await self.expedition_service.get_by_user_id(user_id) + return DailyNoteTaskUser( + user_id=user_id, + resin_db=resin_db, + realm_db=realm_db, + expedition_db=expedition_db, + ) + + @staticmethod + async def start_get_notes( + client: "GenshinClient", + user: DailyNoteTaskUser = None, + ) -> List[str]: + if client.region == RegionEnum.HOYOLAB: + notes = await client.get_genshin_notes() + else: + notes = await client.get_genshin_notes_by_stoken() + if not user: + return [] + notices = [] + notice = None + if user.resin_db and notes.max_resin > 0: + if notes.current_resin >= user.resin.notice_num: + if not user.resin.noticed: + notice = ( + f"### 树脂提示 ####\n\n当前树脂为 {notes.current_resin} / {notes.max_resin} ,记得使用哦~\n" + f"预计全部恢复完成:{notes.resin_recovery_time.strftime('%Y-%m-%d %H:%M')}" + ) + user.resin.noticed = True + else: + user.resin.noticed = False + notices.append(notice) + notice = None + if user.realm_db and notes.max_realm_currency > 0: + if notes.current_realm_currency >= user.realm.notice_num: + if not user.realm.noticed: + notice = ( + f"### 洞天宝钱提示 ####\n\n" + f"当前存储为 {notes.current_realm_currency} / {notes.max_realm_currency} ,记得领取哦~" + ) + user.realm.noticed = True + else: + user.realm.noticed = False + notices.append(notice) + notice = None + if user.expedition_db and len(notes.expeditions) > 0: + all_finished = all(i.status == "Finished" for i in notes.expeditions) + if all_finished: + if not user.expedition.noticed: + notice = "### 探索派遣提示 ####\n\n所有探索派遣已完成,记得重新派遣哦~" + user.expedition.noticed = True + else: + user.expedition.noticed = False + notices.append(notice) + user.save() + return notices + + async def get_all_task_users(self) -> List[DailyNoteTaskUser]: + resin_list = await self.resin_service.get_all() + realm_list = await self.realm_service.get_all() + expedition_list = await self.expedition_service.get_all() + user_list = set() + for i in resin_list: + user_list.add(i.user_id) + for i in realm_list: + user_list.add(i.user_id) + for i in expedition_list: + user_list.add(i.user_id) + return [ + DailyNoteTaskUser( + user_id=i, + resin_db=next((x for x in resin_list if x.user_id == i), None), + realm_db=next((x for x in realm_list if x.user_id == i), None), + expedition_db=next((x for x in expedition_list if x.user_id == i), None), + ) + for i in user_list + ] + + async def remove_task_user(self, user: DailyNoteTaskUser): + if user.resin_db: + await self.resin_service.remove(user.resin_db) + if user.realm_db: + await self.realm_service.remove(user.realm_db) + if user.expedition_db: + await self.expedition_service.remove(user.expedition_db) + + async def update_task_user(self, user: DailyNoteTaskUser): + if user.resin_db: + await self.resin_service.update(user.resin_db) + if user.realm_db: + await self.realm_service.update(user.realm_db) + if user.expedition_db: + await self.expedition_service.update(user.expedition_db) + + @staticmethod + async def check_need_note(web_config: WebAppData) -> bool: + need_verify = False + if web_config.resin and web_config.resin.noticed: + need_verify = True + if web_config.realm and web_config.realm.noticed: + need_verify = True + if web_config.expedition and web_config.expedition.noticed: + need_verify = True + return need_verify + + async def import_web_config(self, user_id: int, web_config: WebAppData): + user = await self.get_single_task_user(user_id) + if web_config.resin: + if web_config.resin.noticed: + if not user.resin_db: + resin = self.resin_service.create( + user_id, + user_id, + status=TaskStatusEnum.STATUS_SUCCESS, + data=ResinData(notice_num=web_config.resin.notice_num).dict(), + ) + await self.resin_service.add(resin) + else: + user.resin.notice_num = web_config.resin.notice_num + user.resin.noticed = False + else: + if user.resin_db: + await self.resin_service.remove(user.resin_db) + user.resin_db = None + user.resin = None + if web_config.realm: + if web_config.realm.noticed: + if not user.realm_db: + realm = self.realm_service.create( + user_id, + user_id, + status=TaskStatusEnum.STATUS_SUCCESS, + data=RealmData(notice_num=web_config.realm.notice_num).dict(), + ) + await self.realm_service.add(realm) + else: + user.realm.notice_num = web_config.realm.notice_num + user.realm.noticed = False + else: + if user.realm_db: + await self.realm_service.remove(user.realm_db) + user.realm_db = None + user.realm = None + if web_config.expedition: + if web_config.expedition.noticed: + if not user.expedition_db: + expedition = self.expedition_service.create( + user_id, + user_id, + status=TaskStatusEnum.STATUS_SUCCESS, + data=ExpeditionData().dict(), + ) + await self.expedition_service.add(expedition) + else: + user.expedition.noticed = False + else: + if user.expedition_db: + await self.expedition_service.remove(user.expedition_db) + user.expedition_db = None + user.expedition = None + user.save() + await self.update_task_user(user) + + async def do_get_notes_job(self, context: "ContextTypes.DEFAULT_TYPE"): + include_status: List[TaskStatusEnum] = [ + TaskStatusEnum.STATUS_SUCCESS, + TaskStatusEnum.TIMEOUT_ERROR, + ] + task_list = await self.get_all_task_users() + for task_db in task_list: + if task_db.status not in include_status: + continue + user_id = task_db.user_id + try: + async with self.genshin_helper.genshin(user_id) as client: + text = await self.start_get_notes(client, task_db) + if all(not i for i in text): + continue + except InvalidCookies: + text = "自动便签提醒执行失败,Cookie无效" + task_db.status = TaskStatusEnum.INVALID_COOKIES + except SimnetBadRequest as exc: + text = f"自动便签提醒执行失败,API返回信息为 {str(exc)}" + task_db.status = TaskStatusEnum.GENSHIN_EXCEPTION + except SimnetTimedOut: + text = "便签获取失败了呜呜呜 ~ 服务器连接超时 服务器熟啦 ~ " + task_db.status = TaskStatusEnum.TIMEOUT_ERROR + except PlayerNotFoundError: + logger.info("用户 user_id[%s] 玩家不存在 关闭并移除自动便签提醒", user_id) + await self.remove_task_user(task_db) + continue + except CookiesNotFoundError: + logger.info("用户 user_id[%s] cookie 不存在 关闭并移除自动便签提醒", user_id) + await self.remove_task_user(task_db) + continue + except Exception as exc: + logger.error("执行自动便签提醒时发生错误 user_id[%s]", user_id, exc_info=exc) + text = "获取便签失败了呜呜呜 ~ 执行自动便签提醒时发生错误" + else: + task_db.status = TaskStatusEnum.STATUS_SUCCESS + for idx, task_user_db in enumerate([task_db.resin_db, task_db.realm_db, task_db.expedition_db]): + if task_user_db is None: + continue + notice_text = text[idx] if isinstance(text, list) else text + if not notice_text: + continue + if task_user_db.chat_id < 0: + notice_text = ( + f'' + f"NOTICE {task_user_db.user_id}\n\n{notice_text}" + ) + try: + await context.bot.send_message(task_user_db.chat_id, notice_text, parse_mode=ParseMode.HTML) + except BadRequest as exc: + logger.error("执行自动便签提醒时发生错误 user_id[%s] Message[%s]", user_id, exc.message) + task_user_db.status = TaskStatusEnum.BAD_REQUEST + except Forbidden as exc: + logger.error("执行自动便签提醒时发生错误 user_id[%s] message[%s]", user_id, exc.message) + task_user_db.status = TaskStatusEnum.FORBIDDEN + except Exception as exc: + logger.error("执行自动便签提醒时发生错误 user_id[%s]", user_id, exc_info=exc) + continue + else: + task_user_db.status = TaskStatusEnum.STATUS_SUCCESS + await self.update_task_user(task_db) diff --git a/plugins/tools/sign.py b/plugins/tools/sign.py index 8ccb73f6..bd8c6492 100644 --- a/plugins/tools/sign.py +++ b/plugins/tools/sign.py @@ -17,8 +17,8 @@ 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.task.models import TaskStatusEnum +from core.services.task.services import SignServices from core.services.users.services import UserService from modules.apihelper.client.components.verify import Verify from plugins.tools.genshin import PlayerNotFoundError, CookiesNotFoundError, GenshinHelper @@ -269,16 +269,16 @@ async def start_sign( return message async def do_sign_job(self, context: "ContextTypes.DEFAULT_TYPE", job_type: SignJobType): - include_status: List[SignStatusEnum] = [ - SignStatusEnum.STATUS_SUCCESS, - SignStatusEnum.TIMEOUT_ERROR, - SignStatusEnum.NEED_CHALLENGE, + include_status: List[TaskStatusEnum] = [ + TaskStatusEnum.STATUS_SUCCESS, + TaskStatusEnum.TIMEOUT_ERROR, + TaskStatusEnum.NEED_CHALLENGE, ] if job_type == SignJobType.START: title = "自动签到" elif job_type == SignJobType.REDO: title = "自动重新签到" - include_status.remove(SignStatusEnum.STATUS_SUCCESS) + include_status.remove(TaskStatusEnum.STATUS_SUCCESS) else: raise ValueError sign_list = await self.sign_service.get_all() @@ -291,19 +291,19 @@ async def do_sign_job(self, context: "ContextTypes.DEFAULT_TYPE", job_type: Sign text = await self.start_sign(client, is_sleep=True, is_raise=True, title=title) except InvalidCookies: text = "自动签到执行失败,Cookie无效" - sign_db.status = SignStatusEnum.INVALID_COOKIES + sign_db.status = TaskStatusEnum.INVALID_COOKIES except AlreadyClaimed: text = "今天旅行者已经签到过了~" - sign_db.status = SignStatusEnum.ALREADY_CLAIMED + sign_db.status = TaskStatusEnum.ALREADY_CLAIMED except SimnetBadRequest as exc: text = f"自动签到执行失败,API返回信息为 {str(exc)}" - sign_db.status = SignStatusEnum.GENSHIN_EXCEPTION + sign_db.status = TaskStatusEnum.GENSHIN_EXCEPTION except SimnetTimedOut: text = "签到失败了呜呜呜 ~ 服务器连接超时 服务器熟啦 ~ " - sign_db.status = SignStatusEnum.TIMEOUT_ERROR + sign_db.status = TaskStatusEnum.TIMEOUT_ERROR except NeedChallenge: text = "签到失败,触发验证码风控" - sign_db.status = SignStatusEnum.NEED_CHALLENGE + sign_db.status = TaskStatusEnum.NEED_CHALLENGE except PlayerNotFoundError: logger.info("用户 user_id[%s] 玩家不存在 关闭并移除自动签到", user_id) await self.sign_service.remove(sign_db) @@ -316,21 +316,20 @@ async def do_sign_job(self, context: "ContextTypes.DEFAULT_TYPE", job_type: Sign logger.error("执行自动签到时发生错误 user_id[%s]", user_id, exc_info=exc) text = "签到失败了呜呜呜 ~ 执行自动签到时发生错误" else: - sign_db.status = SignStatusEnum.STATUS_SUCCESS + sign_db.status = TaskStatusEnum.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 + sign_db.status = TaskStatusEnum.BAD_REQUEST except Forbidden as exc: logger.error("执行自动签到时发生错误 user_id[%s] message[%s]", user_id, exc.message) - sign_db.status = SignStatusEnum.FORBIDDEN + sign_db.status = TaskStatusEnum.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() + sign_db.status = TaskStatusEnum.STATUS_SUCCESS await self.sign_service.update(sign_db)