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 sqlmodel import select
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 import Update
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 @@ class BirthdayPlugin(Plugin):
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 @@ class BirthdayPlugin(Plugin):
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 @@ class BirthdayPlugin(Plugin):
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 @@ class BirthdayPlugin(Plugin):
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.handler.callbackqueryhandler import CallbackQueryHandler
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 @@ class Sign(Plugin):
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.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.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 @@ class SignSystem(Plugin):
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 @@ class SignSystem(Plugin):
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 @@ class SignSystem(Plugin):
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)