Support mult user task service

This commit is contained in:
omg-xtao 2024-11-18 21:38:50 +08:00 committed by xtaodada
parent 6aa55f21de
commit 990012520a
Signed by: xtaodada
GPG Key ID: 4CBB3F4FA8C85659
5 changed files with 162 additions and 60 deletions

View File

@ -0,0 +1,82 @@
"""tasks_player_id
Revision ID: a9c8cde17cd8
Revises: 1220c5c80757
Create Date: 2024-11-18 15:47:51.218077
"""
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 = "a9c8cde17cd8"
down_revision = "1220c5c80757"
branch_labels = None
depends_on = None
logger = logging.getLogger(__name__)
def update_player_id():
connection = op.get_bind()
try:
statement = "SELECT user_id FROM task;"
task_table_data = connection.execute(text(statement))
need_check_user_id = []
if task_table_data is not None:
for row in task_table_data:
need_check_user_id.append(row[0])
need_check_user_id = list(set(need_check_user_id))
except NoSuchTableError:
logger.warning("Table 'task' doesn't exist")
return # should not happen
try:
statement = "SELECT user_id, player_id, is_chosen FROM players;"
players_table_data = connection.execute(text(statement))
player_id_map = {}
if players_table_data is not None:
for row in players_table_data:
if not row[2]:
continue
uid, pid = row[0], row[1]
if uid not in player_id_map:
player_id_map[uid] = pid
except NoSuchTableError:
logger.warning("Table 'players' doesn't exist")
return # should not happen
update = "UPDATE task SET player_id=:player_id WHERE user_id=:user_id;"
for uid in need_check_user_id:
player_id = player_id_map.get(uid, None)
if player_id is None:
logger.warning("user_id %s doesn't exist player", uid)
continue
try:
with op.get_context().autocommit_block():
connection.execute(text(update), dict(player_id=player_id, user_id=uid))
except Exception as exc: # pylint: disable=W0703
logger.error("Process sign->task Exception", exc_info=exc) # pylint: disable=W0703
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column("task", sa.Column("player_id", sa.BigInteger(), nullable=False))
op.alter_column("task", "user_id", existing_type=mysql.BIGINT(), nullable=False)
op.create_index(op.f("ix_task_player_id"), "task", ["player_id"], unique=False)
update_player_id()
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f("ix_task_player_id"), table_name="task")
op.alter_column("task", "user_id", existing_type=mysql.BIGINT(), nullable=True)
op.drop_column("task", "player_id")
# ### end Alembic commands ###

@ -1 +1 @@
Subproject commit 7129322f6dd9ae82880807a0a5a8579a5f150369
Subproject commit 112b2e92d8492df17dbae23024fa805e3510a56e

View File

@ -52,6 +52,9 @@ class DailyData(TaskDataBase):
class WebAppData(BaseModel):
user_id: int
player_id: int
resin: Optional[ResinData]
expedition: Optional[ExpeditionData]
daily: Optional[DailyData]
@ -61,11 +64,13 @@ class DailyNoteTaskUser:
def __init__(
self,
user_id: int,
player_id: int,
resin_db: Optional[TaskUser] = None,
expedition_db: Optional[TaskUser] = None,
daily_db: Optional[TaskUser] = None,
):
self.user_id = user_id
self.player_id = player_id
self.resin_db = resin_db
self.expedition_db = expedition_db
self.daily_db = daily_db
@ -107,6 +112,8 @@ class DailyNoteTaskUser:
return base64.b64encode(
(
WebAppData(
user_id=self.user_id,
player_id=self.player_id,
resin=self.set_model_noticed(self.resin) if self.resin 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,
@ -136,12 +143,13 @@ class DailyNoteSystem(Plugin):
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)
expedition_db = await self.expedition_service.get_by_user_id(user_id)
daily_db = await self.daily_service.get_by_user_id(user_id)
async def get_single_task_user(self, user_id: int, player_id: int) -> DailyNoteTaskUser:
resin_db = await self.resin_service.get_by_user_id(user_id, player_id)
expedition_db = await self.expedition_service.get_by_user_id(user_id, player_id)
daily_db = await self.daily_service.get_by_user_id(user_id, player_id)
return DailyNoteTaskUser(
user_id=user_id,
player_id=player_id,
resin_db=resin_db,
expedition_db=expedition_db,
daily_db=daily_db,
@ -212,20 +220,17 @@ class DailyNoteSystem(Plugin):
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)
for i in expedition_list:
user_list.add(i.user_id)
for i in daily_list:
user_list.add(i.user_id)
for i in resin_list + expedition_list + daily_list:
user_list.add((i.user_id, i.player_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),
daily_db=next((x for x in daily_list if x.user_id == i), None),
player_id=p,
resin_db=next((x for x in resin_list if x.user_id == i and x.player_id == p), None),
expedition_db=next((x for x in expedition_list if x.user_id == i and x.player_id == p), None),
daily_db=next((x for x in daily_list if x.user_id == i and x.player_id == p), None),
)
for i in user_list
for i, p in user_list
]
async def remove_task_user(self, user: DailyNoteTaskUser):
@ -265,11 +270,12 @@ class DailyNoteSystem(Plugin):
return need_verify
async def import_web_config_resin(self, user: DailyNoteTaskUser, web_config: WebAppData):
user_id = user.user_id
user_id, player_id = user.user_id, user.player_id
if web_config.resin.noticed:
if not user.resin_db:
resin = self.resin_service.create(
user_id,
player_id,
user_id,
status=TaskStatusEnum.STATUS_SUCCESS,
data=ResinData(notice_num=web_config.resin.notice_num).dict(),
@ -286,11 +292,12 @@ class DailyNoteSystem(Plugin):
user.resin = None
async def import_web_config_expedition(self, user: DailyNoteTaskUser, web_config: WebAppData):
user_id = user.user_id
user_id, player_id = user.user_id, user.player_id
if web_config.expedition.noticed:
if not user.expedition_db:
expedition = self.expedition_service.create(
user_id,
player_id,
user_id,
status=TaskStatusEnum.STATUS_SUCCESS,
data=ExpeditionData().dict(),
@ -306,11 +313,12 @@ class DailyNoteSystem(Plugin):
user.expedition = None
async def import_web_config_daily(self, user: DailyNoteTaskUser, web_config: WebAppData):
user_id = user.user_id
user_id, player_id = user.user_id, user.player_id
if web_config.daily.noticed:
if not user.daily_db:
daily = self.daily_service.create(
user_id,
player_id,
user_id,
status=TaskStatusEnum.STATUS_SUCCESS,
data=DailyData(notice_hour=web_config.daily.notice_hour).dict(),
@ -325,8 +333,9 @@ class DailyNoteSystem(Plugin):
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)
async def import_web_config(self, web_config: WebAppData):
user_id, player_id = web_config.user_id, web_config.player_id
user = await self.get_single_task_user(user_id, player_id)
if web_config.resin:
await self.import_web_config_resin(user, web_config)
if web_config.expedition:
@ -346,9 +355,10 @@ class DailyNoteSystem(Plugin):
if task_db.status not in include_status:
continue
user_id = task_db.user_id
logger.debug("自动便签提醒 - 请求便签信息 user_id[%s]", user_id)
player_id = task_db.player_id
logger.debug("自动便签提醒 - 请求便签信息 user_id[%s] player_id[%s]", user_id, player_id)
try:
async with self.genshin_helper.genshin(user_id) as client:
async with self.genshin_helper.genshin(user_id, player_id=player_id) as client:
text = await self.start_get_notes(client, task_db)
except InvalidCookies:
text = "自动便签提醒执行失败Cookie无效"

View File

@ -1,4 +1,4 @@
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Union, Optional
from pydantic import ValidationError
from simnet import Region
@ -13,7 +13,7 @@ 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 plugins.tools.genshin import GenshinHelper
from utils.log import logger
if TYPE_CHECKING:
@ -46,44 +46,50 @@ class DailyNoteTasksPlugin(Plugin.Conversation):
@handler.command(command="daily_note_tasks", filters=filters.ChatType.PRIVATE, block=False)
@handler.command(command="start", filters=filters.Regex(r"daily_note_tasks$"), block=False)
async def command_start(self, update: Update, _: CallbackContext) -> int:
user = update.effective_user
user_id = await self.get_real_user_id(update)
uid, offset = self.get_real_uid_or_offset(update)
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":
self.log_user(update, logger.info, "设置自动便签提醒命令请求")
text = await self.check_genshin_user(user_id, uid, offset, False)
if isinstance(text, str):
await message.reply_text(text, reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END
note_user = await self.note_system.get_single_task_user(user.id)
note_user = await self.note_system.get_single_task_user(user_id, text)
url = f"{config.pass_challenge_user_web}/tasks4?command=tasks&bot_data={note_user.web_config}"
text = (
f'你好 {user.mention_markdown_v2()} {escape_markdown("!请点击下方按钮,开始设置,或者回复退出取消操作")}'
)
text = escape_markdown("请点击下方按钮,开始设置,或者回复退出取消操作")
await message.reply_markdown_v2(
text,
reply_markup=ReplyKeyboardMarkup.from_button(
reply_markup=ReplyKeyboardMarkup(
[
[
KeyboardButton(
text="点我开始设置",
web_app=WebAppInfo(url=url),
)
],
[
"退出",
],
]
),
)
return SET_BY_WEB
async def check_genshin_user(self, user_id: int, request_note: bool) -> str:
async def check_genshin_user(
self, user_id: int, uid: int, offset: Optional[int], request_note: bool
) -> Union[str, int]:
try:
async with self.helper.genshin(user_id) as client:
async with self.helper.genshin(user_id, player_id=uid, offset=offset) as client:
client: "ZZZClient"
if request_note:
await client.get_zzz_notes()
return "ok"
return client.player_id
except ValueError:
return "Cookies 缺少 stoken ,请尝试重新绑定账号。"
except DataNotPublic:
return "查询失败惹,可能是便签功能被禁用了?请尝试通过米游社或者 hoyolab 获取一次便签信息后重试。"
except SimnetBadRequest as e:
return f"获取便签失败,可能遇到验证码风控,请尝试重新绑定账号。{e}"
except (CookiesNotFoundError, PlayerNotFoundError):
return config.notice.user_not_found
@conversation.state(state=SET_BY_WEB)
@handler.message(filters=filters.TEXT & ~filters.COMMAND, block=False)
@ -107,17 +113,19 @@ class DailyNoteTasksPlugin(Plugin.Conversation):
if result.path == "tasks":
try:
validate = WebAppData(**result.data)
if validate.user_id != user.id:
raise ValidationError(None, None)
except ValidationError:
await message.reply_text(
"数据错误\n电量提醒数值必须在 60 ~ 200 之间\n每日任务提醒时间必须在 0 ~ 23 之间",
reply_markup=ReplyKeyboardRemove(),
)
return ConversationHandler.END
need_note = await self.check_genshin_user(user.id, True)
if need_note != "ok":
need_note = await self.check_genshin_user(validate.user_id, validate.player_id, None, True)
if isinstance(need_note, str):
await message.reply_text(need_note, reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END
await self.note_system.import_web_config(user.id, validate)
await self.note_system.import_web_config(validate)
await message.reply_text("修改设置成功", reply_markup=ReplyKeyboardRemove())
else:
logger.warning(

View File

@ -40,37 +40,38 @@ class Sign(Plugin):
self.players_service = player
self.cookies_service = cookies
async def _process_auto_sign(self, user_id: int, chat_id: int, method: str) -> str:
player = await self.players_service.get_player(user_id)
async def _process_auto_sign(self, user_id: int, player_id: int, offset: int, chat_id: int, method: str) -> str:
player = await self.players_service.get_player(user_id, player_id=player_id, offset=offset)
if player is None:
return config.notice.user_not_found
cookie_model = await self.cookies_service.get(player.user_id, player.account_id, player.region)
if cookie_model is None:
return config.notice.user_not_found
user: SignUser = await self.sign_service.get_by_user_id(user_id)
user: SignUser = await self.sign_service.get_by_user_id(user_id, player.player_id)
if user:
if method == "关闭":
await self.sign_service.remove(user)
return "关闭自动签到成功"
return f"UID {player.player_id} 关闭自动签到成功"
if method == "开启":
if user.chat_id == chat_id:
return "自动签到已经开启过了"
return f"UID {player.player_id} 自动签到已经开启过了"
user.chat_id = chat_id
user.status = TaskStatusEnum.STATUS_SUCCESS
await self.sign_service.update(user)
return "修改自动签到通知对话成功"
return f"UID {player.player_id} 修改自动签到通知对话成功"
elif method == "关闭":
return "您还没有开启自动签到"
return f"UID {player.player_id} 您还没有开启自动签到"
elif method == "开启":
user = self.sign_service.create(user_id, chat_id, TaskStatusEnum.STATUS_SUCCESS)
user = self.sign_service.create(user_id, player.player_id, chat_id, TaskStatusEnum.STATUS_SUCCESS)
await self.sign_service.add(user)
return "开启自动签到成功"
return f"UID {player.player_id} 开启自动签到成功"
@handler.command(command="sign", cookie=True, block=False)
@handler.message(filters=filters.Regex("^每日签到(.*)"), cookie=True, block=False)
@handler.command(command="start", filters=filters.Regex("sign$"), block=False)
async def command_start(self, update: Update, context: CallbackContext) -> None:
user_id = await self.get_real_user_id(update)
uid, offset = self.get_real_uid_or_offset(update)
message = update.effective_message
args = self.get_args(context)
validate: Optional[str] = None
@ -78,12 +79,12 @@ class Sign(Plugin):
msg = None
if args[0] == "开启自动签到":
if await self.user_admin_service.is_admin(user_id):
msg = await self._process_auto_sign(user_id, message.chat_id, "开启")
msg = await self._process_auto_sign(user_id, uid, offset, message.chat_id, "开启")
else:
msg = await self._process_auto_sign(user_id, user_id, "开启")
msg = await self._process_auto_sign(user_id, uid, offset, user_id, "开启")
elif args[0] == "关闭自动签到":
msg = await self._process_auto_sign(user_id, message.chat_id, "关闭")
elif args[0] != "sign":
msg = await self._process_auto_sign(user_id, uid, offset, message.chat_id, "关闭")
elif args[0] != "sign" and not args[0].startswith("@"):
validate = args[0]
if msg:
self.log_user(update, logger.info, "自动签到命令请求 || 参数 %s", args[0])
@ -96,7 +97,7 @@ class Sign(Plugin):
if filters.ChatType.GROUPS.filter(message):
self.add_delete_message_job(message)
try:
async with self.genshin_helper.genshin(user_id) as client:
async with self.genshin_helper.genshin(user_id, player_id=uid, offset=offset) as client:
await message.reply_chat_action(ChatAction.TYPING)
_, challenge = await self.sign_system.get_challenge(client.player_id)
if validate:
@ -132,13 +133,14 @@ class Sign(Plugin):
@handler.command(command="start", filters=filters.Regex(r" challenge_sign_(.*)"), block=False)
async def command_challenge(self, update: Update, context: CallbackContext) -> None:
user = update.effective_user
uid, offset = self.get_real_uid_or_offset(update)
message = update.effective_message
args = context.args
_data = args[0].split("_")
validate = _data[2]
logger.info("用户 %s[%s] 通过start命令 进入签到流程", user.full_name, user.id)
try:
async with self.genshin_helper.genshin(user.id) as client:
async with self.genshin_helper.genshin(user.id, player_id=uid, offset=offset) as client:
await message.reply_chat_action(ChatAction.TYPING)
_, challenge = await self.sign_system.get_challenge(client.player_id)
if not challenge: