diff --git a/alembic/versions/89dcb9109475_tasks_daily.py b/alembic/versions/89dcb9109475_tasks_daily.py new file mode 100644 index 0000000..a7e65d6 --- /dev/null +++ b/alembic/versions/89dcb9109475_tasks_daily.py @@ -0,0 +1,59 @@ +"""tasks_daily + +Revision ID: 89dcb9109475 +Revises: 27aaa52f9d4a +Create Date: 2024-03-10 10:48:10.227236 + +""" +from alembic import op +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = "89dcb9109475" +down_revision = "27aaa52f9d4a" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column( + "task", + "type", + existing_type=mysql.ENUM( + "SIGN", + "RESIN", + "REALM", + "EXPEDITION", + "TRANSFORMER", + "CARD", + "DAILY", + collation="utf8mb4_general_ci", + ), + nullable=False, + ) + op.drop_index("task_1", table_name="task") + op.create_index(op.f("ix_task_user_id"), "task", ["user_id"], unique=False) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f("ix_task_user_id"), table_name="task") + op.create_index("task_1", "task", ["user_id"], unique=False) + op.alter_column( + "task", + "type", + existing_type=mysql.ENUM( + "SIGN", + "RESIN", + "REALM", + "EXPEDITION", + "TRANSFORMER", + "CARD", + collation="utf8mb4_general_ci", + ), + nullable=True, + ) + op.alter_column("task", "user_id", existing_type=mysql.BIGINT(), nullable=True) + # ### end Alembic commands ### diff --git a/core/services/task/services.py b/core/services/task/services.py index e4328c2..63a4f47 100644 --- a/core/services/task/services.py +++ b/core/services/task/services.py @@ -5,6 +5,7 @@ from gram_core.services.task.services import ( TaskResinServices, TaskRealmServices, TaskExpeditionServices, + TaskDailyServices, ) __all__ = [ @@ -14,4 +15,5 @@ __all__ = [ "TaskResinServices", "TaskRealmServices", "TaskExpeditionServices", + "TaskDailyServices", ] diff --git a/gram_core b/gram_core index 909c81b..b8260c2 160000 --- a/gram_core +++ b/gram_core @@ -1 +1 @@ -Subproject commit 909c81b14035f97dae6fa0b7ea474576ecc25fd7 +Subproject commit b8260c296496b2352ed71c489ea19b1fc57125af diff --git a/plugins/genshin/daily_note_tasks.py b/plugins/genshin/daily_note_tasks.py index 42d9d86..093952b 100644 --- a/plugins/genshin/daily_note_tasks.py +++ b/plugins/genshin/daily_note_tasks.py @@ -1,3 +1,5 @@ +from typing import TYPE_CHECKING + from pydantic import ValidationError from simnet import Region from simnet.errors import DataNotPublic, BadRequest as SimnetBadRequest @@ -14,8 +16,10 @@ from plugins.tools.daily_note import DailyNoteSystem, WebAppData from plugins.tools.genshin import GenshinHelper, CookiesNotFoundError, PlayerNotFoundError from utils.log import logger -__all__ = ("DailyNoteTasksPlugin",) +if TYPE_CHECKING: + from simnet import GenshinClient +__all__ = ("DailyNoteTasksPlugin",) SET_BY_WEB = 10100 @@ -106,7 +110,7 @@ class DailyNoteTasksPlugin(Plugin.Conversation): validate = WebAppData(**result.data) except ValidationError: await message.reply_text( - "数据错误\n" "树脂提醒数值必须在 100 ~ 160 之间\n" "洞天宝钱提醒数值必须在 100 ~ 2400 之间", + "数据错误\n树脂提醒数值必须在 100 ~ 160 之间\n洞天宝钱提醒数值必须在 100 ~ 2400 之间\n每日任务提醒时间必须在 0 ~ 23 之间", reply_markup=ReplyKeyboardRemove(), ) return ConversationHandler.END diff --git a/plugins/tools/daily_note.py b/plugins/tools/daily_note.py index 0a180d0..3ab7f17 100644 --- a/plugins/tools/daily_note.py +++ b/plugins/tools/daily_note.py @@ -1,5 +1,6 @@ import base64 -from typing import TYPE_CHECKING, List, Optional +from datetime import datetime +from typing import TYPE_CHECKING, List, Optional, Union from pydantic import BaseModel, validator from simnet import Region @@ -10,13 +11,14 @@ from telegram.error import BadRequest, Forbidden 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 core.services.task.services import TaskResinServices, TaskRealmServices, TaskExpeditionServices, TaskDailyServices from gram_core.plugin.methods.migrate_data import IMigrateData, MigrateDataException from plugins.tools.genshin import GenshinHelper, PlayerNotFoundError, CookiesNotFoundError from utils.log import logger if TYPE_CHECKING: from simnet import GenshinClient + from simnet.models.genshin.chronicle.notes import Notes, NotesWidget from telegram.ext import ContextTypes from gram_core.services.task.services import TaskServices @@ -49,10 +51,21 @@ class ExpeditionData(TaskDataBase): pass +class DailyData(TaskDataBase): + notice_hour: Optional[int] = 22 + + @validator("notice_hour") + def notice_hour_validator(cls, v): + if v < 0 or v > 23: + raise ValueError("每日任务提醒时间必须在 0 ~ 23 之间") + return v + + class WebAppData(BaseModel): resin: Optional[ResinData] realm: Optional[RealmData] expedition: Optional[ExpeditionData] + daily: Optional[DailyData] class DailyNoteTaskUser: @@ -62,14 +75,17 @@ class DailyNoteTaskUser: resin_db: Optional[TaskUser] = None, realm_db: Optional[TaskUser] = None, expedition_db: Optional[TaskUser] = None, + daily_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.daily_db = daily_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 + self.daily = DailyData(**self.daily_db.data) if self.daily_db else None @property def status(self) -> TaskStatusEnum: @@ -78,6 +94,7 @@ class DailyNoteTaskUser: 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, + self.daily_db.status if self.daily_db else TaskStatusEnum.STATUS_SUCCESS, ] ) @@ -89,6 +106,8 @@ class DailyNoteTaskUser: self.realm_db.status = value if self.expedition_db: self.expedition_db.status = value + if self.daily: + self.daily_db.status = value @staticmethod def js_bool(value: bool) -> str: @@ -108,6 +127,7 @@ class DailyNoteTaskUser: 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, + daily=self.set_model_noticed(self.daily) if self.daily else None, ).json() ).encode() ).decode() @@ -119,6 +139,8 @@ class DailyNoteTaskUser: self.realm_db.data = self.realm.dict() if self.expedition_db: self.expedition_db.data = self.expedition.dict() + if self.daily_db: + self.daily_db.data = self.daily.dict() class DailyNoteSystem(Plugin): @@ -128,23 +150,89 @@ class DailyNoteSystem(Plugin): resin_service: TaskResinServices, realm_service: TaskRealmServices, expedition_service: TaskExpeditionServices, + daily_service: TaskDailyServices, ): self.genshin_helper = genshin_helper self.resin_service = resin_service self.realm_service = realm_service self.expedition_service = expedition_service + self.daily_service = daily_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) + daily_db = await self.daily_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, + daily_db=daily_db, ) + @staticmethod + def get_resin_notice(user: DailyNoteTaskUser, notes: Union["Notes", "NotesWidget"]) -> str: + 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 + return notice + + @staticmethod + def get_realm_notice(user: DailyNoteTaskUser, notes: Union["Notes", "NotesWidget"]) -> str: + 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 + return notice + + @staticmethod + def get_expedition_notice(user: DailyNoteTaskUser, notes: Union["Notes", "NotesWidget"]) -> str: + 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 + return notice + + @staticmethod + def get_daily_notice(user: DailyNoteTaskUser, notes: Union["Notes", "NotesWidget"]) -> str: + notice = None + now_hour = datetime.now().hour + if user.daily_db: + if now_hour == user.daily.notice_hour: + if (notes.completed_commissions != notes.max_commissions or (not notes.claimed_commission_reward)) and ( + not user.daily.noticed + ): + notice_ = "已领取奖励" if notes.claimed_commission_reward else "未领取奖励" + notice = ( + f"### 每日任务提示 ####\n\n" + f"当前进度为 {notes.completed_commissions} / {notes.max_commissions} ({notice_}) ,记得完成哦~" + ) + user.daily.noticed = True + else: + user.daily.noticed = False + return notice + @staticmethod async def start_get_notes( client: "GenshinClient", @@ -156,41 +244,12 @@ class DailyNoteSystem(Plugin): 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) + notices = [ + DailyNoteSystem.get_resin_notice(user, notes), + DailyNoteSystem.get_realm_notice(user, notes), + DailyNoteSystem.get_expedition_notice(user, notes), + DailyNoteSystem.get_daily_notice(user, notes), + ] user.save() return notices @@ -198,6 +257,7 @@ class DailyNoteSystem(Plugin): resin_list = await self.resin_service.get_all() realm_list = await self.realm_service.get_all() expedition_list = await self.expedition_service.get_all() + daily_list = await self.daily_service.get_all() user_list = set() for i in resin_list: user_list.add(i.user_id) @@ -205,12 +265,15 @@ class DailyNoteSystem(Plugin): user_list.add(i.user_id) for i in expedition_list: user_list.add(i.user_id) + for i in daily_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), + daily_db=next((x for x in daily_list if x.user_id == i), None), ) for i in user_list ] @@ -222,6 +285,8 @@ class DailyNoteSystem(Plugin): await self.realm_service.remove(user.realm_db) if user.expedition_db: await self.expedition_service.remove(user.expedition_db) + if user.daily_db: + await self.daily_service.remove(user.daily_db) async def update_task_user(self, user: DailyNoteTaskUser): if user.resin_db: @@ -239,6 +304,11 @@ class DailyNoteSystem(Plugin): await self.expedition_service.update(user.expedition_db) except StaleDataError: logger.warning("用户 user_id[%s] 自动便签提醒 - 探索派遣数据过期,跳过更新数据", user.user_id) + if user.daily_db: + try: + await self.daily_service.update(user.daily_db) + except StaleDataError: + logger.warning("用户 user_id[%s] 自动便签提醒 - 每日任务数据过期,跳过更新数据", user.user_id) @staticmethod async def check_need_note(web_config: WebAppData) -> bool: @@ -249,65 +319,101 @@ class DailyNoteSystem(Plugin): need_verify = True if web_config.expedition and web_config.expedition.noticed: need_verify = True + if web_config.daily and web_config.daily.noticed: + need_verify = True return need_verify + async def import_web_config_resin(self, user: DailyNoteTaskUser, web_config: WebAppData): + user_id = user.user_id + 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 + user.resin_db.status = TaskStatusEnum.STATUS_SUCCESS + else: + if user.resin_db: + await self.resin_service.remove(user.resin_db) + user.resin_db = None + user.resin = None + + async def import_web_config_realm(self, user: DailyNoteTaskUser, web_config: WebAppData): + user_id = user.user_id + 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 + + async def import_web_config_expedition(self, user: DailyNoteTaskUser, web_config: WebAppData): + user_id = user.user_id + 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 + user.expedition_db.status = TaskStatusEnum.STATUS_SUCCESS + else: + if user.expedition_db: + await self.expedition_service.remove(user.expedition_db) + user.expedition_db = None + user.expedition = None + + async def import_web_config_daily(self, user: DailyNoteTaskUser, web_config: WebAppData): + user_id = user.user_id + if web_config.daily.noticed: + if not user.daily_db: + daily = self.daily_service.create( + user_id, + user_id, + status=TaskStatusEnum.STATUS_SUCCESS, + data=DailyData(notice_hour=web_config.daily.notice_hour).dict(), + ) + await self.daily_service.add(daily) + else: + user.daily.noticed = False + user.daily_db.status = TaskStatusEnum.STATUS_SUCCESS + else: + if user.daily_db: + await self.daily_service.remove(user.daily_db) + user.daily_db = None + user.daily = None + 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 - user.resin_db.status = TaskStatusEnum.STATUS_SUCCESS - else: - if user.resin_db: - await self.resin_service.remove(user.resin_db) - user.resin_db = None - user.resin = None + await self.import_web_config_resin(user, web_config) 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 + await self.import_web_config_realm(user, web_config) 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 - user.expedition_db.status = TaskStatusEnum.STATUS_SUCCESS - else: - if user.expedition_db: - await self.expedition_service.remove(user.expedition_db) - user.expedition_db = None - user.expedition = None + await self.import_web_config_expedition(user, web_config) + if web_config.daily: + await self.import_web_config_daily(user, web_config) user.save() await self.update_task_user(user) @@ -347,7 +453,9 @@ class DailyNoteSystem(Plugin): 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]): + for idx, task_user_db in enumerate( + [task_db.resin_db, task_db.realm_db, task_db.expedition_db, task_db.daily_db] + ): if task_user_db is None: continue notice_text = text[idx] if isinstance(text, list) else text