support task services

Co-authored-by: 洛水居室 <luoshuijs@outlook.com>
This commit is contained in:
omg-xtao 2023-07-23 23:48:06 +08:00 committed by GitHub
parent 98147c4aeb
commit 84f053b70d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 822 additions and 84 deletions

View File

@ -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 ###

View File

@ -1 +0,0 @@
"""SignService"""

View File

@ -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)

View File

@ -0,0 +1 @@
"""TaskService"""

View File

@ -1,15 +1,15 @@
import enum import enum
from datetime import datetime 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 from sqlmodel import Column, DateTime, Enum, Field, SQLModel, Integer
__all__ = ("SignStatusEnum", "Sign") __all__ = ("Task", "TaskStatusEnum", "TaskTypeEnum")
class SignStatusEnum(int, enum.Enum): class TaskStatusEnum(int, enum.Enum):
STATUS_SUCCESS = 0 # 签到成功 STATUS_SUCCESS = 0 # 任务执行成功
INVALID_COOKIES = 1 # Cookie无效 INVALID_COOKIES = 1 # Cookie无效
ALREADY_CLAIMED = 2 # 已经获取奖励 ALREADY_CLAIMED = 2 # 已经获取奖励
NEED_CHALLENGE = 3 # 需要验证码 NEED_CHALLENGE = 3 # 需要验证码
@ -19,15 +19,26 @@ class SignStatusEnum(int, enum.Enum):
FORBIDDEN = 7 # 这错误一般为通知失败 机器人被用户BAN 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") __table_args__ = dict(mysql_charset="utf8mb4", mysql_collate="utf8mb4_general_ci")
id: Optional[int] = Field( id: Optional[int] = Field(
default=None, primary_key=True, sa_column=Column(Integer(), primary_key=True, autoincrement=True) 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)) 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( time_created: Optional[datetime] = Field(
sa_column=Column(DateTime, server_default=func.now()) # pylint: disable=E1102 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 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))

View File

@ -4,47 +4,47 @@ from sqlmodel import select
from core.base_service import BaseService from core.base_service import BaseService
from core.dependence.database import Database 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 from core.sqlmodel.session import AsyncSession
__all__ = ("SignRepository",) __all__ = ("TaskRepository",)
class SignRepository(BaseService.Component): class TaskRepository(BaseService.Component):
def __init__(self, database: Database): def __init__(self, database: Database):
self.engine = database.engine self.engine = database.engine
async def add(self, sign: Sign): async def add(self, task: Task):
async with AsyncSession(self.engine) as session: async with AsyncSession(self.engine) as session:
session.add(sign) session.add(task)
await session.commit() await session.commit()
async def remove(self, sign: Sign): async def remove(self, task: Task):
async with AsyncSession(self.engine) as session: async with AsyncSession(self.engine) as session:
await session.delete(sign) await session.delete(task)
await session.commit() await session.commit()
async def update(self, sign: Sign) -> Sign: async def update(self, task: Task) -> Task:
async with AsyncSession(self.engine) as session: async with AsyncSession(self.engine) as session:
session.add(sign) session.add(task)
await session.commit() await session.commit()
await session.refresh(sign) await session.refresh(task)
return sign 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: 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) results = await session.exec(statement)
return results.first() 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: 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) results = await session.exec(statement)
return results.all() 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: async with AsyncSession(self.engine) as session:
query = select(Sign) query = select(Task).where(Task.type == task_type)
results = await session.exec(query) results = await session.exec(query)
return results.all() return results.all()

View File

@ -0,0 +1,179 @@
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",
"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 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,
)

View File

@ -1,5 +1,5 @@
from telegram import Update from telegram import Update
from telegram.ext import CallbackContext, CommandHandler from telegram.ext import CallbackContext
from core.plugin import Plugin, handler from core.plugin import Plugin, handler
from plugins.tools.sign import SignSystem, SignJobType from plugins.tools.sign import SignSystem, SignJobType
@ -10,7 +10,7 @@ class SignAll(Plugin):
def __init__(self, sign_system: SignSystem): def __init__(self, sign_system: SignSystem):
self.sign_system = sign_system 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): async def sign_all(self, update: Update, context: CallbackContext):
user = update.effective_user user = update.effective_user
logger.info("用户 %s[%s] sign_all 命令请求", user.full_name, user.id) logger.info("用户 %s[%s] sign_all 命令请求", user.full_name, user.id)

View File

@ -2,7 +2,7 @@ from telegram import Update
from telegram.ext import CallbackContext, CommandHandler from telegram.ext import CallbackContext, CommandHandler
from core.plugin import Plugin, handler 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 from utils.log import logger

View File

@ -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("执行自动便签提醒完成")

View File

@ -0,0 +1,130 @@
from pydantic import ValidationError
from typing import TYPE_CHECKING
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
if TYPE_CHECKING:
from simnet import StarRailClient
__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}/tasks2?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: "StarRailClient"
if request_note:
if client.region == Region.CHINESE:
await client.get_starrail_notes_by_stoken()
else:
await client.get_starrail_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
else:
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 ~ 180 之间",
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

View File

@ -1,4 +1,3 @@
import datetime
from typing import Optional, Tuple from typing import Optional, Tuple
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
@ -10,8 +9,8 @@ from telegram.helpers import create_deep_linked_url
from core.config import config from core.config import config
from core.handler.callbackqueryhandler import CallbackQueryHandler from core.handler.callbackqueryhandler import CallbackQueryHandler
from core.plugin import Plugin, handler from core.plugin import Plugin, handler
from core.services.sign.models import Sign as SignUser, SignStatusEnum from core.services.task.models import Task as SignUser, TaskStatusEnum
from core.services.sign.services import SignServices from core.services.task.services import SignServices
from core.services.users.services import UserAdminService from core.services.users.services import UserAdminService
from plugins.tools.genshin import GenshinHelper, CookiesNotFoundError, PlayerNotFoundError from plugins.tools.genshin import GenshinHelper, CookiesNotFoundError, PlayerNotFoundError
from plugins.tools.sign import SignSystem, NeedChallenge from plugins.tools.sign import SignSystem, NeedChallenge
@ -49,18 +48,13 @@ class Sign(Plugin):
if user.chat_id == chat_id: if user.chat_id == chat_id:
return "自动签到已经开启过了" return "自动签到已经开启过了"
user.chat_id = chat_id user.chat_id = chat_id
user.status = SignStatusEnum.STATUS_SUCCESS user.status = TaskStatusEnum.STATUS_SUCCESS
await self.sign_service.update(user) await self.sign_service.update(user)
return "修改自动签到通知对话成功" return "修改自动签到通知对话成功"
elif method == "关闭": elif method == "关闭":
return "您还没有开启自动签到" return "您还没有开启自动签到"
elif method == "开启": elif method == "开启":
user = SignUser( user = self.sign_service.create(user_id, chat_id, TaskStatusEnum.STATUS_SUCCESS)
user_id=user_id,
chat_id=chat_id,
time_created=datetime.datetime.now(),
status=SignStatusEnum.STATUS_SUCCESS,
)
await self.sign_service.add(user) await self.sign_service.add(user)
return "开启自动签到成功" return "开启自动签到成功"

299
plugins/tools/daily_note.py Normal file
View File

@ -0,0 +1,299 @@
import base64
from datetime import datetime
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, TaskExpeditionServices
from plugins.tools.genshin import GenshinHelper, PlayerNotFoundError, CookiesNotFoundError
from utils.log import logger
if TYPE_CHECKING:
from simnet import StarRailClient
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 > 180:
raise ValueError("开拓力提醒数值必须在 100 ~ 180 之间")
return v
class ExpeditionData(TaskDataBase):
pass
class WebAppData(BaseModel):
resin: Optional[ResinData]
expedition: Optional[ExpeditionData]
class DailyNoteTaskUser:
def __init__(
self,
user_id: int,
resin_db: Optional[TaskUser] = None,
expedition_db: Optional[TaskUser] = None,
):
self.user_id = user_id
self.resin_db = resin_db
self.expedition_db = expedition_db
self.resin = ResinData(**self.resin_db.data) if self.resin_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.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.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,
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.expedition_db:
self.expedition_db.data = self.expedition.dict()
class DailyNoteSystem(Plugin):
def __init__(
self,
genshin_helper: GenshinHelper,
resin_service: TaskResinServices,
expedition_service: TaskExpeditionServices,
):
self.genshin_helper = genshin_helper
self.resin_service = resin_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)
expedition_db = await self.expedition_service.get_by_user_id(user_id)
return DailyNoteTaskUser(
user_id=user_id,
resin_db=resin_db,
expedition_db=expedition_db,
)
@staticmethod
async def start_get_notes(
client: "StarRailClient",
user: DailyNoteTaskUser = None,
) -> List[str]:
if client.region == RegionEnum.HOYOLAB:
notes = await client.get_starrail_notes()
else:
notes = await client.get_starrail_notes_by_stoken()
if not user:
return []
notices = []
notice = None
if user.resin_db and notes.max_stamina > 0:
if notes.current_stamina >= user.resin.notice_num:
if not user.resin.noticed:
rec_time = datetime.now().astimezone() + notes.stamina_recover_time
notice = (
f"### 开拓力提示 ####\n\n当前开拓力为 {notes.current_stamina} / {notes.max_stamina} ,记得使用哦~\n"
f"预计全部恢复完成:{rec_time.strftime('%Y-%m-%d %H:%M')}"
)
user.resin.noticed = True
else:
user.resin.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()
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 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),
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.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.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.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.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.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'<a href="tg://user?id={task_user_db.user_id}">'
f"NOTICE {task_user_db.user_id}</a>\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)

View File

@ -17,8 +17,8 @@ from core.config import config
from core.dependence.redisdb import RedisDB from core.dependence.redisdb import RedisDB
from core.plugin import Plugin from core.plugin import Plugin
from core.services.cookies import CookiesService from core.services.cookies import CookiesService
from core.services.sign.models import SignStatusEnum from core.services.task.models import TaskStatusEnum
from core.services.sign.services import SignServices from core.services.task.services import SignServices
from core.services.users.services import UserService from core.services.users.services import UserService
from modules.apihelper.client.components.verify import Verify from modules.apihelper.client.components.verify import Verify
from plugins.tools.genshin import PlayerNotFoundError, CookiesNotFoundError, GenshinHelper from plugins.tools.genshin import PlayerNotFoundError, CookiesNotFoundError, GenshinHelper
@ -269,16 +269,16 @@ class SignSystem(Plugin):
return message return message
async def do_sign_job(self, context: "ContextTypes.DEFAULT_TYPE", job_type: SignJobType): async def do_sign_job(self, context: "ContextTypes.DEFAULT_TYPE", job_type: SignJobType):
include_status: List[SignStatusEnum] = [ include_status: List[TaskStatusEnum] = [
SignStatusEnum.STATUS_SUCCESS, TaskStatusEnum.STATUS_SUCCESS,
SignStatusEnum.TIMEOUT_ERROR, TaskStatusEnum.TIMEOUT_ERROR,
SignStatusEnum.NEED_CHALLENGE, TaskStatusEnum.NEED_CHALLENGE,
] ]
if job_type == SignJobType.START: if job_type == SignJobType.START:
title = "自动签到" title = "自动签到"
elif job_type == SignJobType.REDO: elif job_type == SignJobType.REDO:
title = "自动重新签到" title = "自动重新签到"
include_status.remove(SignStatusEnum.STATUS_SUCCESS) include_status.remove(TaskStatusEnum.STATUS_SUCCESS)
else: else:
raise ValueError raise ValueError
sign_list = await self.sign_service.get_all() 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) text = await self.start_sign(client, is_sleep=True, is_raise=True, title=title)
except InvalidCookies: except InvalidCookies:
text = "自动签到执行失败Cookie无效" text = "自动签到执行失败Cookie无效"
sign_db.status = SignStatusEnum.INVALID_COOKIES sign_db.status = TaskStatusEnum.INVALID_COOKIES
except AlreadyClaimed: except AlreadyClaimed:
text = "今天开拓者已经签到过了~" text = "今天开拓者已经签到过了~"
sign_db.status = SignStatusEnum.ALREADY_CLAIMED sign_db.status = TaskStatusEnum.ALREADY_CLAIMED
except SimnetBadRequest as exc: except SimnetBadRequest as exc:
text = f"自动签到执行失败API返回信息为 {str(exc)}" text = f"自动签到执行失败API返回信息为 {str(exc)}"
sign_db.status = SignStatusEnum.GENSHIN_EXCEPTION sign_db.status = TaskStatusEnum.GENSHIN_EXCEPTION
except SimnetTimedOut: except SimnetTimedOut:
text = "签到失败了呜呜呜 ~ 服务器连接超时 服务器熟啦 ~ " text = "签到失败了呜呜呜 ~ 服务器连接超时 服务器熟啦 ~ "
sign_db.status = SignStatusEnum.TIMEOUT_ERROR sign_db.status = TaskStatusEnum.TIMEOUT_ERROR
except NeedChallenge: except NeedChallenge:
text = "签到失败,触发验证码风控" text = "签到失败,触发验证码风控"
sign_db.status = SignStatusEnum.NEED_CHALLENGE sign_db.status = TaskStatusEnum.NEED_CHALLENGE
except PlayerNotFoundError: except PlayerNotFoundError:
logger.info("用户 user_id[%s] 玩家不存在 关闭并移除自动签到", user_id) logger.info("用户 user_id[%s] 玩家不存在 关闭并移除自动签到", user_id)
await self.sign_service.remove(sign_db) await self.sign_service.remove(sign_db)
@ -316,21 +316,20 @@ class SignSystem(Plugin):
logger.error("执行自动签到时发生错误 user_id[%s]", user_id, exc_info=exc) logger.error("执行自动签到时发生错误 user_id[%s]", user_id, exc_info=exc)
text = "签到失败了呜呜呜 ~ 执行自动签到时发生错误" text = "签到失败了呜呜呜 ~ 执行自动签到时发生错误"
else: else:
sign_db.status = SignStatusEnum.STATUS_SUCCESS sign_db.status = TaskStatusEnum.STATUS_SUCCESS
if sign_db.chat_id < 0: if sign_db.chat_id < 0:
text = f'<a href="tg://user?id={sign_db.user_id}">NOTICE {sign_db.user_id}</a>\n\n{text}' text = f'<a href="tg://user?id={sign_db.user_id}">NOTICE {sign_db.user_id}</a>\n\n{text}'
try: try:
await context.bot.send_message(sign_db.chat_id, text, parse_mode=ParseMode.HTML) await context.bot.send_message(sign_db.chat_id, text, parse_mode=ParseMode.HTML)
except BadRequest as exc: except BadRequest as exc:
logger.error("执行自动签到时发生错误 user_id[%s] Message[%s]", user_id, exc.message) 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: except Forbidden as exc:
logger.error("执行自动签到时发生错误 user_id[%s] message[%s]", user_id, exc.message) 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: except Exception as exc:
logger.error("执行自动签到时发生错误 user_id[%s]", user_id, exc_info=exc) logger.error("执行自动签到时发生错误 user_id[%s]", user_id, exc_info=exc)
continue continue
else: else:
sign_db.status = SignStatusEnum.STATUS_SUCCESS sign_db.status = TaskStatusEnum.STATUS_SUCCESS
sign_db.time_updated = datetime.datetime.now()
await self.sign_service.update(sign_db) await self.sign_service.update(sign_db)