feat(task): 竞赛未编成时支持暂停与通知
This commit is contained in:
parent
a167cbfbe1
commit
3be8485795
|
@ -95,7 +95,7 @@ class FlowController:
|
||||||
logger.info('Interrupt requested.')
|
logger.info('Interrupt requested.')
|
||||||
self.interrupt_event.set()
|
self.interrupt_event.set()
|
||||||
|
|
||||||
def request_pause(self) -> None:
|
def request_pause(self, *, wait_resume: bool = False) -> None:
|
||||||
"""
|
"""
|
||||||
请求暂停任务。
|
请求暂停任务。
|
||||||
|
|
||||||
|
@ -106,6 +106,8 @@ class FlowController:
|
||||||
if not self.paused:
|
if not self.paused:
|
||||||
logger.info('Pause requested.')
|
logger.info('Pause requested.')
|
||||||
self.paused = True
|
self.paused = True
|
||||||
|
if wait_resume:
|
||||||
|
self.check()
|
||||||
|
|
||||||
def request_resume(self) -> None:
|
def request_resume(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -64,6 +64,8 @@ class ContestConfig(ConfigBaseModel):
|
||||||
select_which_contestant: Literal[1, 2, 3] = 1
|
select_which_contestant: Literal[1, 2, 3] = 1
|
||||||
"""选择第几个挑战者"""
|
"""选择第几个挑战者"""
|
||||||
|
|
||||||
|
when_no_set: Literal['remind', 'wait', 'auto_set', 'auto_set_silent'] = 'remind'
|
||||||
|
"""竞赛队伍未编成时应该:remind=通知我并跳过竞赛,wait=提醒我并等待手动编成,auto_set=使用自动编成并提醒,auto_set_silent=使用自动编成不提醒"""
|
||||||
|
|
||||||
|
|
||||||
class ProduceConfig(ConfigBaseModel):
|
class ProduceConfig(ConfigBaseModel):
|
||||||
|
|
|
@ -51,7 +51,7 @@ ConfigKey = Literal[
|
||||||
'mini_live_reassign', 'mini_live_duration',
|
'mini_live_reassign', 'mini_live_duration',
|
||||||
'online_live_reassign', 'online_live_duration',
|
'online_live_reassign', 'online_live_duration',
|
||||||
'contest_enabled',
|
'contest_enabled',
|
||||||
'select_which_contestant',
|
'select_which_contestant', 'when_no_set',
|
||||||
|
|
||||||
# produce
|
# produce
|
||||||
'produce_enabled', 'selected_solution_id', 'produce_count',
|
'produce_enabled', 'selected_solution_id', 'produce_count',
|
||||||
|
@ -1250,6 +1250,20 @@ class KotoneBotUI:
|
||||||
interactive=True,
|
interactive=True,
|
||||||
info=ContestConfig.model_fields['select_which_contestant'].description
|
info=ContestConfig.model_fields['select_which_contestant'].description
|
||||||
)
|
)
|
||||||
|
|
||||||
|
when_no_set_choices = [
|
||||||
|
("通知我并跳过竞赛", "remind"),
|
||||||
|
("提醒我并等待手动编成", "wait"),
|
||||||
|
("使用自动编成并提醒我", "auto_set"),
|
||||||
|
("使用自动编成", "auto_set_silent")
|
||||||
|
]
|
||||||
|
when_no_set = gr.Dropdown(
|
||||||
|
choices=when_no_set_choices,
|
||||||
|
value=self.current_config.options.contest.when_no_set,
|
||||||
|
label="竞赛队伍未编成时",
|
||||||
|
interactive=True,
|
||||||
|
info=ContestConfig.model_fields['when_no_set'].description
|
||||||
|
)
|
||||||
contest_enabled.change(
|
contest_enabled.change(
|
||||||
fn=lambda x: gr.Group(visible=x),
|
fn=lambda x: gr.Group(visible=x),
|
||||||
inputs=[contest_enabled],
|
inputs=[contest_enabled],
|
||||||
|
@ -1259,10 +1273,12 @@ class KotoneBotUI:
|
||||||
def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
|
def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
|
||||||
config.contest.enabled = data['contest_enabled']
|
config.contest.enabled = data['contest_enabled']
|
||||||
config.contest.select_which_contestant = data['select_which_contestant']
|
config.contest.select_which_contestant = data['select_which_contestant']
|
||||||
|
config.contest.when_no_set = data['when_no_set']
|
||||||
|
|
||||||
return set_config, {
|
return set_config, {
|
||||||
'contest_enabled': contest_enabled,
|
'contest_enabled': contest_enabled,
|
||||||
'select_which_contestant': select_which_contestant
|
'select_which_contestant': select_which_contestant,
|
||||||
|
'when_no_set': when_no_set
|
||||||
}
|
}
|
||||||
|
|
||||||
def _create_produce_settings(self) -> ConfigBuilderReturnValue:
|
def _create_produce_settings(self) -> ConfigBuilderReturnValue:
|
||||||
|
|
|
@ -2,12 +2,15 @@
|
||||||
import logging
|
import logging
|
||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
|
|
||||||
|
from kotonebot.errors import StopCurrentTask
|
||||||
from kotonebot.kaa.tasks import R
|
from kotonebot.kaa.tasks import R
|
||||||
from kotonebot.kaa.config import conf
|
from kotonebot.kaa.config import conf
|
||||||
from kotonebot.kaa.game_ui import WhiteFilter
|
from kotonebot.kaa.game_ui import WhiteFilter, dialog
|
||||||
from ..actions.scenes import at_home, goto_home
|
from ..actions.scenes import at_home, goto_home
|
||||||
from ..actions.loading import wait_loading_end
|
from ..actions.loading import wait_loading_end
|
||||||
from kotonebot import device, image, ocr, color, action, task, user, rect_expand, sleep, contains, Interval
|
from kotonebot import device, image, ocr, color, action, task, rect_expand, sleep, contains, Interval
|
||||||
|
from kotonebot.backend.context.context import vars
|
||||||
|
from kotonebot.ui import user as ui_user
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -70,11 +73,39 @@ def handle_challenge() -> bool:
|
||||||
|
|
||||||
# 记忆未编成 [screenshots/contest/no_memo.png]
|
# 记忆未编成 [screenshots/contest/no_memo.png]
|
||||||
if image.find(R.Daily.TextContestNoMemory):
|
if image.find(R.Daily.TextContestNoMemory):
|
||||||
logger.debug('Memory not set. Using auto-compilation.')
|
logger.debug('Memory not set.')
|
||||||
user.warning('竞赛未编成', _('记忆未编成。将使用自动编成。'), once=True)
|
when_no_set = conf().contest.when_no_set
|
||||||
if image.find(R.Daily.ButtonContestChallenge):
|
|
||||||
device.click()
|
auto_compilation = False
|
||||||
return True
|
match when_no_set:
|
||||||
|
case 'remind':
|
||||||
|
# 关闭编成提示弹窗
|
||||||
|
dialog.expect_no(msg='Closed memory not set dialog.')
|
||||||
|
ui_user.warning('竞赛未编成', '已跳过此次竞赛任务。')
|
||||||
|
logger.info('Contest skipped due to memory not set (remind mode).')
|
||||||
|
raise StopCurrentTask
|
||||||
|
case 'wait':
|
||||||
|
dialog.expect_no(msg='Closed memory not set dialog.')
|
||||||
|
ui_user.warning('竞赛未编成', '已自动暂停,请手动编成后返回至挑战开始页,并点击网页上「恢复」按钮或使用快捷键继续执行。')
|
||||||
|
vars.flow.request_pause(wait_resume=True)
|
||||||
|
logger.info('Contest paused due to memory not set (wait mode).')
|
||||||
|
return True
|
||||||
|
case 'auto_set' | 'auto_set_silent':
|
||||||
|
if when_no_set == 'auto_set':
|
||||||
|
ui_user.warning('竞赛未编成', '将使用自动编成。', once=True)
|
||||||
|
logger.debug('Using auto-compilation with notification.')
|
||||||
|
else: # auto_set_silent
|
||||||
|
logger.debug('Using auto-compilation silently.')
|
||||||
|
auto_compilation = True
|
||||||
|
case _:
|
||||||
|
logger.warning(f'Unknown value for contest.when_no_set: {when_no_set}, fallback to auto.')
|
||||||
|
logger.debug('Using auto-compilation silently.')
|
||||||
|
auto_compilation = True
|
||||||
|
|
||||||
|
if auto_compilation:
|
||||||
|
if image.find(R.Daily.ButtonContestChallenge):
|
||||||
|
device.click()
|
||||||
|
return True
|
||||||
|
|
||||||
# 勾选跳过所有
|
# 勾选跳过所有
|
||||||
# [screenshots/contest/contest2.png]
|
# [screenshots/contest/contest2.png]
|
||||||
|
|
|
@ -4,6 +4,7 @@ import time
|
||||||
|
|
||||||
import cv2
|
import cv2
|
||||||
from cv2.typing import MatLike
|
from cv2.typing import MatLike
|
||||||
|
from win11toast import toast
|
||||||
|
|
||||||
from .pushkit import Wxpusher
|
from .pushkit import Wxpusher
|
||||||
from .. import logging
|
from .. import logging
|
||||||
|
@ -25,17 +26,6 @@ def retry(func):
|
||||||
continue
|
continue
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
def ask(
|
|
||||||
question: str,
|
|
||||||
options: list[str],
|
|
||||||
*,
|
|
||||||
timeout: float = -1,
|
|
||||||
) -> bool:
|
|
||||||
"""
|
|
||||||
询问用户
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def _save_local(
|
def _save_local(
|
||||||
title: str,
|
title: str,
|
||||||
message: str,
|
message: str,
|
||||||
|
@ -74,6 +64,43 @@ def push(
|
||||||
logger.warning('push remote message failed: %s', e)
|
logger.warning('push remote message failed: %s', e)
|
||||||
_save_local(title, message, images)
|
_save_local(title, message, images)
|
||||||
|
|
||||||
|
def _show_toast(title: str, message: str | None = None, buttons: list[str] | None = None):
|
||||||
|
"""
|
||||||
|
统一的 Toast 通知函数
|
||||||
|
|
||||||
|
:param title: 通知标题
|
||||||
|
:param message: 通知消息内容
|
||||||
|
:param buttons: 按钮列表,如果提供则显示带按钮的通知
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if buttons:
|
||||||
|
logger.verbose('showing toast notification with buttons: %s - %s', title, message or '')
|
||||||
|
toast(title, message or '', buttons=buttons)
|
||||||
|
else:
|
||||||
|
# 如果没有 message,只显示 title
|
||||||
|
if message:
|
||||||
|
logger.verbose('showing toast notification: %s - %s', title, message)
|
||||||
|
toast(title, message)
|
||||||
|
else:
|
||||||
|
logger.verbose('showing toast notification: %s', title)
|
||||||
|
toast(title)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning('toast notification failed: %s', e)
|
||||||
|
|
||||||
|
def ask(
|
||||||
|
question: str,
|
||||||
|
options: list[tuple[str, str]],
|
||||||
|
*,
|
||||||
|
timeout: float = -1,
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
询问用户
|
||||||
|
"""
|
||||||
|
# 将选项转换为按钮列表
|
||||||
|
buttons = [option[1] for option in options]
|
||||||
|
_show_toast("琴音小助手询问", question, buttons=buttons)
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
def info(
|
def info(
|
||||||
title: str,
|
title: str,
|
||||||
message: str | None = None,
|
message: str | None = None,
|
||||||
|
@ -83,6 +110,7 @@ def info(
|
||||||
):
|
):
|
||||||
logger.info('user.info: %s', message)
|
logger.info('user.info: %s', message)
|
||||||
push('KAA:' + title, message, images=images)
|
push('KAA:' + title, message, images=images)
|
||||||
|
_show_toast('KAA:' + title, message)
|
||||||
|
|
||||||
def warning(
|
def warning(
|
||||||
title: str,
|
title: str,
|
||||||
|
@ -98,7 +126,8 @@ def warning(
|
||||||
:param once: 每次运行是否只显示一次。
|
:param once: 每次运行是否只显示一次。
|
||||||
"""
|
"""
|
||||||
logger.warning('user.warning: %s', message)
|
logger.warning('user.warning: %s', message)
|
||||||
push("KAA 警告:" + title, message, images=images)
|
push("琴音小助手警告:" + title, message, images=images)
|
||||||
|
_show_toast("琴音小助手警告:" + title, message)
|
||||||
|
|
||||||
def error(
|
def error(
|
||||||
title: str,
|
title: str,
|
||||||
|
@ -111,4 +140,5 @@ def error(
|
||||||
错误信息。
|
错误信息。
|
||||||
"""
|
"""
|
||||||
logger.error('user.error: %s', message)
|
logger.error('user.error: %s', message)
|
||||||
push("KAA 错误:" + title, message, images=images)
|
push("琴音小助手错误:" + title, message, images=images)
|
||||||
|
_show_toast("琴音小助手错误:" + title, message)
|
|
@ -41,6 +41,7 @@ dependencies = [
|
||||||
# TODO: move these dependencies to optional-dependencies
|
# TODO: move these dependencies to optional-dependencies
|
||||||
"pywin32==310",
|
"pywin32==310",
|
||||||
"ahk==1.8.3",
|
"ahk==1.8.3",
|
||||||
|
"win11toast==0.35", # For Windows Toast Notification
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
-r requirements.txt
|
-r requirements.txt
|
||||||
|
|
||||||
pywin32==310
|
pywin32==310
|
||||||
ahk==1.8.3
|
ahk==1.8.3
|
||||||
|
win11toast==0.35
|
Loading…
Reference in New Issue