diff --git a/app/card/timepickersettingcard1.py b/app/card/timepickersettingcard1.py new file mode 100644 index 00000000..fba68871 --- /dev/null +++ b/app/card/timepickersettingcard1.py @@ -0,0 +1,26 @@ +from qfluentwidgets import (TimePicker, SettingCard, FluentIconBase) +from typing import Union +from PyQt5.QtCore import Qt, QTime +from PyQt5.QtGui import QIcon + +from module.config import cfg + + +class TimePickerSettingCard1(SettingCard): + """ Setting card with a TimePicker """ + + def __init__(self, configname: str, icon: Union[str, QIcon, FluentIconBase], title, content=None, texts=None, parent=None): + super().__init__(icon, title, content, parent) + self.configname = configname + self.timePicker = TimePicker(self) + self.hBoxLayout.addWidget(self.timePicker, 0, Qt.AlignRight) + + time_str = cfg.get_value(configname) + time_parts = list(map(int, time_str.split(":"))) # 分割小时和分钟 + qtime = QTime(*time_parts) # 创建 QTime 对象 + self.timePicker.setTime(qtime) + + self.timePicker.timeChanged.connect(self._onTimeChanged) + + def _onTimeChanged(self, time: QTime): + cfg.set_value(self.configname, time.toString(Qt.DefaultLocaleShortDate)) diff --git a/app/setting_interface.py b/app/setting_interface.py index e5f3fd48..a765811a 100644 --- a/app/setting_interface.py +++ b/app/setting_interface.py @@ -10,6 +10,7 @@ from .card.switchsettingcard1 import SwitchSettingCard1, SwitchSettingCardTeam, SwitchSettingCardImmersifier, SwitchSettingCardGardenofplenty from .card.rangesettingcard1 import RangeSettingCard1 from .card.pushsettingcard1 import PushSettingCardInstance, PushSettingCardNotifyTemplate, PushSettingCardEval, PushSettingCardDate, PushSettingCardKey, PushSettingCardTeam, PushSettingCardFriends +from .card.timepickersettingcard1 import TimePickerSettingCard1 from module.config import cfg from tasks.base.tasks import start_task from .tools.check_update import checkUpdate @@ -486,10 +487,22 @@ def __initCard(self): "after_finish", FIF.POWER_BUTTON, self.tr('任务完成后'), - self.tr('其中“退出”指退出游戏,“循环”指根据开拓力7×24小时无人值守循环运行程序(仅限完整运行生效)'), + self.tr('其中“退出”指退出游戏,“循环”指7×24小时无人值守循环运行程序(仅限完整运行生效)'), texts={'无': 'None', '退出': 'Exit', '循环': 'Loop', '关机': 'Shutdown', '睡眠': 'Sleep', '休眠': 'Hibernate', '重启': 'Restart', '注销': 'Logoff'} ) + self.loopModeCard = ComboBoxSettingCard2( + "loop_mode", + FIF.COMMAND_PROMPT, + self.tr('循环模式'), + '', + texts={'根据开拓力': 'power', '定时任务': 'scheduled'} + ) + self.scheduledCard = TimePickerSettingCard1( + "scheduled_time", + FIF.DATE_TIME, + "定时任务时间", + ) self.playAudioCard = SwitchSettingCard1( FIF.ALBUM, self.tr('声音提示'), @@ -678,6 +691,8 @@ def __initLayout(self): self.ProgramGroup.addSettingCard(self.importConfigCard) self.ProgramGroup.addSettingCard(self.checkUpdateCard) self.ProgramGroup.addSettingCard(self.afterFinishCard) + self.ProgramGroup.addSettingCard(self.loopModeCard) + self.ProgramGroup.addSettingCard(self.scheduledCard) self.ProgramGroup.addSettingCard(self.playAudioCard) self.ProgramGroup.addSettingCard(self.powerLimitCard) self.ProgramGroup.addSettingCard(self.refreshHourEnableCard) diff --git a/assets/config/config.example.yaml b/assets/config/config.example.yaml index a9da9065..e5eb92d3 100644 --- a/assets/config/config.example.yaml +++ b/assets/config/config.example.yaml @@ -203,6 +203,12 @@ auto_set_resolution_enable: true # 是否启用自动修改分辨率功能。tru # 自动配置游戏路径 auto_set_game_path_enable: true # 是否启用自动配置游戏路径。true 开启,false 关闭。 +# 循环模式 +loop_mode: power # 可选值:"power"(根据开拓力), "scheduled"(定时任务) + +# 指定运行时间 +scheduled_time: 4:00 + # 游戏刷新时间设置 refresh_hour: 4 # 设置游戏每日刷新的时间(24小时制)。 diff --git a/assets/docs/Changelog.md b/assets/docs/Changelog.md index 699a68c6..4a3c1f78 100644 --- a/assets/docs/Changelog.md +++ b/assets/docs/Changelog.md @@ -3,17 +3,18 @@ ## v2.6.1 ### 新功能 -- 支持 2.6 版本新增关卡 +- 支持 2.6 版本新增关卡和角色(乱破) - “副本名称” 配置项支持手动输入 +- 阵亡导致挑战失败后支持自动重试 [#385](https://github.com/moesnow/March7thAssistant/pull/385) - 支持自动批量使用兑换码(工具箱) - “抽卡记录” 支持 “更新完整数据”(用于修复错误的抽卡数据) +- 循环模式支持 “根据开拓力”(原有模式) 和 “定时任务”(指定时间) - 支持 “Server酱3” 推送方式 [#377](https://github.com/moesnow/March7thAssistant/pull/377) -- 支持 “乱破” ### 修复 - 更换抽卡记录 API - 手动修改配置文件会被图形界面覆盖 [#341](https://github.com/moesnow/March7thAssistant/issues/341) [#379](https://github.com/moesnow/March7thAssistant/issues/379) -- 游戏窗口位于多显示器副屏时截图内容全黑 [#378](https://github.com/moesnow/March7thAssistant/pull/378) +- 游戏窗口位于多显示器副屏时截图内容全黑或坐标偏移 [#378](https://github.com/moesnow/March7thAssistant/pull/378) [#384](https://github.com/moesnow/March7thAssistant/pull/384) ## v2.5.4 diff --git a/tasks/game/__init__.py b/tasks/game/__init__.py index 6820e866..15fc4e9a 100644 --- a/tasks/game/__init__.py +++ b/tasks/game/__init__.py @@ -2,7 +2,6 @@ import sys import time import psutil -import random from app.tools.account_manager import load_acc_and_pwd @@ -159,26 +158,34 @@ def get_wait_time(current_power): # 距离体力到达配置文件指定的上限剩余秒数 wait_time_power_limit = (cfg.power_limit - current_power) * 6 * 60 # 距离第二天凌晨4点剩余秒数,+30避免显示3点59分不美观,#7 - wait_time_next_day = Date.get_time_next_x_am(cfg.refresh_hour) + random.randint(30, 600) + wait_time_next_day = Date.get_time_next_x_am(cfg.refresh_hour) + 30 # 取最小值 wait_time = min(wait_time_power_limit, wait_time_next_day) return wait_time - current_power = Power.get() - if current_power >= cfg.power_limit: - log.info(f"🟣开拓力 >= {cfg.power_limit}") - log.info("即将再次运行") - log.hr("完成", 2) + if cfg.loop_mode == "power": + current_power = Power.get() + if current_power >= cfg.power_limit: + log.info(f"🟣开拓力 >= {cfg.power_limit}") + log.info("即将再次运行") + log.hr("完成", 2) + return + else: + starrail.stop_game() + wait_time = get_wait_time(current_power) + future_time = Date.calculate_future_time(wait_time) else: starrail.stop_game() - wait_time = get_wait_time(current_power) - future_time = Date.calculate_future_time(wait_time) - log.info(cfg.notify_template['ContinueTime'].format(time=future_time)) - notif.notify(cfg.notify_template['ContinueTime'].format(time=future_time)) - log.hr("完成", 2) - # 等待状态退出OCR避免内存占用 - ocr.exit_ocr() - time.sleep(wait_time) + scheduled_time = cfg.scheduled_time + wait_time = Date.time_to_seconds(scheduled_time) + future_time = Date.calculate_future_time(scheduled_time) + + log.info(cfg.notify_template['ContinueTime'].format(time=future_time)) + notif.notify(cfg.notify_template['ContinueTime'].format(time=future_time)) + log.hr("完成", 2) + # 等待状态退出OCR避免内存占用 + ocr.exit_ocr() + time.sleep(wait_time) def notify_after_finish_not_loop(): diff --git a/utils/date.py b/utils/date.py index 02f20015..f6a34896 100644 --- a/utils/date.py +++ b/utils/date.py @@ -46,13 +46,53 @@ def get_time_next_x_am(hour=4): return int(time_until_next_x_am.total_seconds()) @staticmethod - def calculate_future_time(seconds): + def calculate_future_time(input_data): current_time = datetime.now() - future_time = current_time + timedelta(seconds=seconds) + # 如果输入是整数(表示秒数) + if isinstance(input_data, int): + future_time = current_time + timedelta(seconds=input_data) + # 如果输入是字符串(格式如 "HH:MM") + elif isinstance(input_data, str): + try: + # 提取时分并构造当天的目标时间 + input_time = datetime.strptime(input_data, "%H:%M").time() + future_time = datetime.combine(current_time.date(), input_time) + # 如果目标时间已经过了今天,则认为是明天的时间 + if future_time <= current_time: + future_time += timedelta(days=1) + except ValueError: + return "输入的时间格式不合法,应为 'HH:MM'" + else: + return "输入数据类型不支持,请输入秒数或时间字符串" + + # 判断并返回对应的时间描述 if future_time.date() == current_time.date(): return f"今天{future_time.hour}时{future_time.minute}分" elif future_time.date() == current_time.date() + timedelta(days=1): return f"明天{future_time.hour}时{future_time.minute}分" else: - return "输入秒数不合法" + return "输入秒数或时间超出范围" + + @staticmethod + def time_to_seconds(time_str): + try: + # 获取当前时间 + current_time = datetime.now() + + # 将输入时间字符串解析为时间对象 + target_time = datetime.strptime(time_str, "%H:%M").time() + + # 构造当天的目标时间 + target_datetime = datetime.combine(current_time.date(), target_time) + + # 如果目标时间已经过了今天,则调整为明天的时间 + if target_datetime <= current_time: + target_datetime += timedelta(days=1) + + # 计算剩余秒数 + remaining_seconds = int((target_datetime - current_time).total_seconds()) + + return remaining_seconds + except ValueError: + return "输入的时间格式不合法,应为 'HH:MM'"