feat: 没测过的MAA调度方案
@@ -20,19 +20,25 @@
|
|||||||
# Contact: DLmaster_361@163.com
|
# Contact: DLmaster_361@163.com
|
||||||
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Body
|
import uuid
|
||||||
|
from fastapi import APIRouter, WebSocket, Body, Path
|
||||||
|
|
||||||
from app.core import Config
|
from app.core import Config, TaskManager
|
||||||
from app.models.schema import *
|
from app.models.schema import *
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/dispatch", tags=["任务调度"])
|
router = APIRouter(prefix="/api/dispatch", tags=["任务调度"])
|
||||||
|
|
||||||
|
|
||||||
@router.post("/add", summary="添加任务", response_model=OutBase, status_code=200)
|
@router.post(
|
||||||
async def add_plan(plan: DispatchIn = Body(...)) -> OutBase:
|
"/start", summary="添加任务", response_model=TaskCreateOut, status_code=200
|
||||||
|
)
|
||||||
|
async def add_plan(task: TaskCreateIn = Body(...)) -> TaskCreateOut:
|
||||||
|
|
||||||
uid, config = await Config.add_plan(plan.type)
|
try:
|
||||||
return OutBase(code=200, status="success", message="任务添加成功")
|
task_id = await TaskManager.add_task(task.mode, task.taskId)
|
||||||
|
except Exception as e:
|
||||||
|
return TaskCreateOut(code=500, status="error", message=str(e), taskId="")
|
||||||
|
return TaskCreateOut(taskId=str(task_id))
|
||||||
|
|
||||||
|
|
||||||
@router.post("/stop", summary="中止任务", response_model=OutBase, status_code=200)
|
@router.post("/stop", summary="中止任务", response_model=OutBase, status_code=200)
|
||||||
@@ -45,13 +51,18 @@ async def stop_plan(plan: DispatchIn = Body(...)) -> OutBase:
|
|||||||
return OutBase()
|
return OutBase()
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.websocket("/ws/{taskId}")
|
||||||
"/order", summary="重新排序计划表", response_model=OutBase, status_code=200
|
async def websocket_endpoint(
|
||||||
)
|
websocket: WebSocket, taskId: str = Path(..., description="要连接的任务ID")
|
||||||
async def reorder_plan(plan: PlanReorderIn = Body(...)) -> OutBase:
|
):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await Config.reorder_plan(plan.indexList)
|
uid = uuid.UUID(taskId)
|
||||||
except Exception as e:
|
except ValueError:
|
||||||
return OutBase(code=500, status="error", message=str(e))
|
await websocket.close(code=1008, reason="无效的任务ID")
|
||||||
return OutBase()
|
return
|
||||||
|
|
||||||
|
if uid in TaskManager.connection_events:
|
||||||
|
TaskManager.websocket_dict[uid] = websocket
|
||||||
|
TaskManager.connection_events[uid].set()
|
||||||
|
else:
|
||||||
|
await websocket.close(code=1008, reason="任务不存在或已结束")
|
||||||
|
|||||||
@@ -23,7 +23,15 @@ __version__ = "5.0.0"
|
|||||||
__author__ = "DLmaster361 <DLmaster_361@163.com>"
|
__author__ = "DLmaster361 <DLmaster_361@163.com>"
|
||||||
__license__ = "GPL-3.0 license"
|
__license__ = "GPL-3.0 license"
|
||||||
|
|
||||||
from .config import Config
|
from .config import Config, MaaConfig, GeneralConfig, MaaUserConfig
|
||||||
from .timer import MainTimer
|
from .timer import MainTimer
|
||||||
|
from .task_manager import TaskManager
|
||||||
|
|
||||||
__all__ = ["Config", "MainTimer"]
|
__all__ = [
|
||||||
|
"Config",
|
||||||
|
"MaaConfig",
|
||||||
|
"GeneralConfig",
|
||||||
|
"MainTimer",
|
||||||
|
"TaskManager",
|
||||||
|
"MaaUserConfig",
|
||||||
|
]
|
||||||
|
|||||||
@@ -20,20 +20,18 @@
|
|||||||
# Contact: DLmaster_361@163.com
|
# Contact: DLmaster_361@163.com
|
||||||
|
|
||||||
|
|
||||||
import sqlite3
|
|
||||||
import json
|
|
||||||
import sys
|
|
||||||
import shutil
|
|
||||||
import re
|
import re
|
||||||
import base64
|
import shutil
|
||||||
|
import asyncio
|
||||||
import requests
|
import requests
|
||||||
import truststore
|
import truststore
|
||||||
import calendar
|
import calendar
|
||||||
from datetime import datetime, timedelta, date
|
from datetime import datetime, timedelta, date
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
|
||||||
from typing import Union, Dict, List, Literal, Optional, Any, Tuple, Callable, TypeVar
|
from typing import Dict, List, Literal, Optional, Any
|
||||||
|
|
||||||
from app.utils import get_logger
|
from app.utils import get_logger
|
||||||
from app.models.ConfigBase import *
|
from app.models.ConfigBase import *
|
||||||
@@ -546,7 +544,7 @@ class GeneralConfig(ConfigBase):
|
|||||||
|
|
||||||
class AppConfig(GlobalConfig):
|
class AppConfig(GlobalConfig):
|
||||||
|
|
||||||
VERSION = "4.5.0.1"
|
VERSION = "5.0.0.1"
|
||||||
|
|
||||||
CLASS_BOOK = {
|
CLASS_BOOK = {
|
||||||
"MAA": MaaConfig,
|
"MAA": MaaConfig,
|
||||||
@@ -580,15 +578,11 @@ class AppConfig(GlobalConfig):
|
|||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__(if_save_multi_config=False)
|
super().__init__(if_save_multi_config=False)
|
||||||
|
|
||||||
self.root_path = Path.cwd()
|
self.log_path = Path.cwd() / "debug/app.log"
|
||||||
|
self.database_path = Path.cwd() / "data/data.db"
|
||||||
|
self.config_path = Path.cwd() / "config"
|
||||||
|
self.key_path = Path.cwd() / "data/key"
|
||||||
|
|
||||||
self.log_path = self.root_path / "debug/app.log"
|
|
||||||
self.database_path = self.root_path / "data/data.db"
|
|
||||||
self.config_path = self.root_path / "config"
|
|
||||||
self.key_path = self.root_path / "data/key"
|
|
||||||
|
|
||||||
# self.PASSWORD = ""
|
|
||||||
self.running_list = []
|
|
||||||
self.silence_dict: Dict[Path, datetime] = {}
|
self.silence_dict: Dict[Path, datetime] = {}
|
||||||
self.power_sign = "NoAction"
|
self.power_sign = "NoAction"
|
||||||
self.if_ignore_silence = False
|
self.if_ignore_silence = False
|
||||||
@@ -597,7 +591,7 @@ class AppConfig(GlobalConfig):
|
|||||||
logger.info("===================================")
|
logger.info("===================================")
|
||||||
logger.info("AUTO_MAA 后端应用程序")
|
logger.info("AUTO_MAA 后端应用程序")
|
||||||
logger.info(f"版本号: v{self.VERSION}")
|
logger.info(f"版本号: v{self.VERSION}")
|
||||||
logger.info(f"根目录: {self.root_path}")
|
logger.info(f"工作目录: {Path.cwd()}")
|
||||||
logger.info("===================================")
|
logger.info("===================================")
|
||||||
|
|
||||||
# 检查目录
|
# 检查目录
|
||||||
@@ -618,6 +612,10 @@ class AppConfig(GlobalConfig):
|
|||||||
await self.PlanConfig.connect(self.config_path / "PlanConfig.json")
|
await self.PlanConfig.connect(self.config_path / "PlanConfig.json")
|
||||||
await self.QueueConfig.connect(self.config_path / "QueueConfig.json")
|
await self.QueueConfig.connect(self.config_path / "QueueConfig.json")
|
||||||
|
|
||||||
|
from .task_manager import TaskManager
|
||||||
|
|
||||||
|
self.task_dict = TaskManager.task_dict
|
||||||
|
|
||||||
# self.check_data()
|
# self.check_data()
|
||||||
logger.info("程序初始化完成")
|
logger.info("程序初始化完成")
|
||||||
|
|
||||||
@@ -653,6 +651,11 @@ class AppConfig(GlobalConfig):
|
|||||||
|
|
||||||
uid = uuid.UUID(script_id)
|
uid = uuid.UUID(script_id)
|
||||||
|
|
||||||
|
if uid in self.task_dict:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Cannot update script {script_id} while tasks are running."
|
||||||
|
)
|
||||||
|
|
||||||
for group, items in data.items():
|
for group, items in data.items():
|
||||||
for name, value in items.items():
|
for name, value in items.items():
|
||||||
logger.debug(f"更新脚本配置:{script_id} - {group}.{name} = {value}")
|
logger.debug(f"更新脚本配置:{script_id} - {group}.{name} = {value}")
|
||||||
@@ -665,7 +668,14 @@ class AppConfig(GlobalConfig):
|
|||||||
|
|
||||||
logger.info(f"删除脚本配置:{script_id}")
|
logger.info(f"删除脚本配置:{script_id}")
|
||||||
|
|
||||||
await self.ScriptConfig.remove(uuid.UUID(script_id))
|
uid = uuid.UUID(script_id)
|
||||||
|
|
||||||
|
if uid in self.task_dict:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Cannot delete script {script_id} while tasks are running."
|
||||||
|
)
|
||||||
|
|
||||||
|
await self.ScriptConfig.remove(uid)
|
||||||
|
|
||||||
async def reorder_script(self, index_list: list[str]) -> None:
|
async def reorder_script(self, index_list: list[str]) -> None:
|
||||||
"""重新排序脚本"""
|
"""重新排序脚本"""
|
||||||
@@ -1058,5 +1068,343 @@ class AppConfig(GlobalConfig):
|
|||||||
"Cannot connect to the notice server. Please check your network connection or try again later."
|
"Cannot connect to the notice server. Please check your network connection or try again later."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def save_maa_log(self, log_path: Path, logs: list, maa_result: str) -> bool:
|
||||||
|
"""
|
||||||
|
保存MAA日志并生成对应统计数据
|
||||||
|
|
||||||
|
:param log_path: 日志文件保存路径
|
||||||
|
:type log_path: Path
|
||||||
|
:param logs: 日志内容列表
|
||||||
|
:type logs: list
|
||||||
|
:param maa_result: MAA 结果
|
||||||
|
:type maa_result: str
|
||||||
|
:return: 是否包含6★招募
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
|
|
||||||
|
logger.info(f"开始处理 MAA 日志,日志长度: {len(logs)},日志标记:{maa_result}")
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"recruit_statistics": defaultdict(int),
|
||||||
|
"drop_statistics": defaultdict(dict),
|
||||||
|
"maa_result": maa_result,
|
||||||
|
}
|
||||||
|
|
||||||
|
if_six_star = False
|
||||||
|
|
||||||
|
# 公招统计(仅统计招募到的)
|
||||||
|
confirmed_recruit = False
|
||||||
|
current_star_level = None
|
||||||
|
i = 0
|
||||||
|
while i < len(logs):
|
||||||
|
if "公招识别结果:" in logs[i]:
|
||||||
|
current_star_level = None # 每次识别公招时清空之前的星级
|
||||||
|
i += 1
|
||||||
|
while i < len(logs) and "Tags" not in logs[i]: # 读取所有公招标签
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
if i < len(logs) and "Tags" in logs[i]: # 识别星级
|
||||||
|
star_match = re.search(r"(\d+)\s*★ Tags", logs[i])
|
||||||
|
if star_match:
|
||||||
|
current_star_level = f"{star_match.group(1)}★"
|
||||||
|
if current_star_level == "6★":
|
||||||
|
if_six_star = True
|
||||||
|
|
||||||
|
if "已确认招募" in logs[i]: # 只有确认招募后才统计
|
||||||
|
confirmed_recruit = True
|
||||||
|
|
||||||
|
if confirmed_recruit and current_star_level:
|
||||||
|
data["recruit_statistics"][current_star_level] += 1
|
||||||
|
confirmed_recruit = False # 重置,等待下一次公招
|
||||||
|
current_star_level = None # 清空已处理的星级
|
||||||
|
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
# 掉落统计
|
||||||
|
# 存储所有关卡的掉落统计
|
||||||
|
all_stage_drops = {}
|
||||||
|
|
||||||
|
# 查找所有Fight任务的开始和结束位置
|
||||||
|
fight_tasks = []
|
||||||
|
for i, line in enumerate(logs):
|
||||||
|
if "开始任务: Fight" in line or "开始任务: 刷理智" in line:
|
||||||
|
# 查找对应的任务结束位置
|
||||||
|
end_index = -1
|
||||||
|
for j in range(i + 1, len(logs)):
|
||||||
|
if "完成任务: Fight" in logs[j] or "完成任务: 刷理智" in logs[j]:
|
||||||
|
end_index = j
|
||||||
|
break
|
||||||
|
# 如果遇到新的Fight任务开始,则当前任务没有正常结束
|
||||||
|
if j < len(logs) and (
|
||||||
|
"开始任务: Fight" in logs[j] or "开始任务: 刷理智" in logs[j]
|
||||||
|
):
|
||||||
|
break
|
||||||
|
|
||||||
|
# 如果找到了结束位置,记录这个任务的范围
|
||||||
|
if end_index != -1:
|
||||||
|
fight_tasks.append((i, end_index))
|
||||||
|
|
||||||
|
# 处理每个Fight任务
|
||||||
|
for start_idx, end_idx in fight_tasks:
|
||||||
|
# 提取当前任务的日志
|
||||||
|
task_logs = logs[start_idx : end_idx + 1]
|
||||||
|
|
||||||
|
# 查找任务中的最后一次掉落统计
|
||||||
|
last_drop_stats = {}
|
||||||
|
current_stage = None
|
||||||
|
|
||||||
|
for line in task_logs:
|
||||||
|
# 匹配掉落统计行,如"1-7 掉落统计:"
|
||||||
|
drop_match = re.search(r"([A-Za-z0-9\-]+) 掉落统计:", line)
|
||||||
|
if drop_match:
|
||||||
|
# 发现新的掉落统计,重置当前关卡的掉落数据
|
||||||
|
current_stage = drop_match.group(1)
|
||||||
|
last_drop_stats = {}
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 如果已经找到了关卡,处理掉落物
|
||||||
|
if current_stage:
|
||||||
|
item_match: List[str] = re.findall(
|
||||||
|
r"^(?!\[)(\S+?)\s*:\s*([\d,]+)(?:\s*\(\+[\d,]+\))?",
|
||||||
|
line,
|
||||||
|
re.M,
|
||||||
|
)
|
||||||
|
for item, total in item_match:
|
||||||
|
# 解析数值时去掉逗号 (如 2,160 -> 2160)
|
||||||
|
total = int(total.replace(",", ""))
|
||||||
|
|
||||||
|
# 黑名单
|
||||||
|
if item not in [
|
||||||
|
"当前次数",
|
||||||
|
"理智",
|
||||||
|
"最快截图耗时",
|
||||||
|
"专精等级",
|
||||||
|
"剩余时间",
|
||||||
|
]:
|
||||||
|
last_drop_stats[item] = total
|
||||||
|
|
||||||
|
# 如果任务中有掉落统计,更新总统计
|
||||||
|
if current_stage and last_drop_stats:
|
||||||
|
if current_stage not in all_stage_drops:
|
||||||
|
all_stage_drops[current_stage] = {}
|
||||||
|
|
||||||
|
# 累加掉落数据
|
||||||
|
for item, count in last_drop_stats.items():
|
||||||
|
all_stage_drops[current_stage].setdefault(item, 0)
|
||||||
|
all_stage_drops[current_stage][item] += count
|
||||||
|
|
||||||
|
# 将累加后的掉落数据保存到结果中
|
||||||
|
data["drop_statistics"] = all_stage_drops
|
||||||
|
|
||||||
|
# 保存日志
|
||||||
|
log_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with log_path.open("w", encoding="utf-8") as f:
|
||||||
|
f.writelines(logs)
|
||||||
|
with log_path.with_suffix(".json").open("w", encoding="utf-8") as f:
|
||||||
|
json.dump(data, f, ensure_ascii=False, indent=4)
|
||||||
|
|
||||||
|
logger.success(
|
||||||
|
f"MAA 日志统计完成,日志路径:{log_path}",
|
||||||
|
)
|
||||||
|
|
||||||
|
return if_six_star
|
||||||
|
|
||||||
|
async def save_general_log(
|
||||||
|
self, log_path: Path, logs: list, general_result: str
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
保存通用日志并生成对应统计数据
|
||||||
|
|
||||||
|
:param log_path: 日志文件保存路径
|
||||||
|
:param logs: 日志内容列表
|
||||||
|
:param general_result: 待保存的日志结果信息
|
||||||
|
"""
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"开始处理通用日志,日志长度: {len(logs)},日志标记:{general_result}"
|
||||||
|
)
|
||||||
|
|
||||||
|
data: Dict[str, str] = {"general_result": general_result}
|
||||||
|
|
||||||
|
# 保存日志
|
||||||
|
log_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with log_path.with_suffix(".log").open("w", encoding="utf-8") as f:
|
||||||
|
f.writelines(logs)
|
||||||
|
with log_path.with_suffix(".json").open("w", encoding="utf-8") as f:
|
||||||
|
json.dump(data, f, ensure_ascii=False, indent=4)
|
||||||
|
|
||||||
|
logger.success(f"通用日志统计完成,日志路径:{log_path.with_suffix('.log')}")
|
||||||
|
|
||||||
|
def merge_statistic_info(self, statistic_path_list: List[Path]) -> dict:
|
||||||
|
"""
|
||||||
|
合并指定数据统计信息文件
|
||||||
|
|
||||||
|
:param statistic_path_list: 需要合并的统计信息文件路径列表
|
||||||
|
:return: 合并后的统计信息字典
|
||||||
|
"""
|
||||||
|
|
||||||
|
logger.info(f"开始合并统计信息文件,共计 {len(statistic_path_list)} 个文件")
|
||||||
|
|
||||||
|
data: Dict[str, Any] = {"index": {}}
|
||||||
|
|
||||||
|
for json_file in statistic_path_list:
|
||||||
|
|
||||||
|
with json_file.open("r", encoding="utf-8") as f:
|
||||||
|
single_data = json.load(f)
|
||||||
|
|
||||||
|
for key in single_data.keys():
|
||||||
|
|
||||||
|
if key not in data:
|
||||||
|
data[key] = {}
|
||||||
|
|
||||||
|
# 合并公招统计
|
||||||
|
if key == "recruit_statistics":
|
||||||
|
|
||||||
|
for star_level, count in single_data[key].items():
|
||||||
|
if star_level not in data[key]:
|
||||||
|
data[key][star_level] = 0
|
||||||
|
data[key][star_level] += count
|
||||||
|
|
||||||
|
# 合并掉落统计
|
||||||
|
elif key == "drop_statistics":
|
||||||
|
|
||||||
|
for stage, drops in single_data[key].items():
|
||||||
|
if stage not in data[key]:
|
||||||
|
data[key][stage] = {} # 初始化关卡
|
||||||
|
|
||||||
|
for item, count in drops.items():
|
||||||
|
|
||||||
|
if item not in data[key][stage]:
|
||||||
|
data[key][stage][item] = 0
|
||||||
|
data[key][stage][item] += count
|
||||||
|
|
||||||
|
# 录入运行结果
|
||||||
|
elif key in ["maa_result", "general_result"]:
|
||||||
|
|
||||||
|
actual_date = datetime.strptime(
|
||||||
|
f"{json_file.parent.parent.name} {json_file.stem}",
|
||||||
|
"%Y-%m-%d %H-%M-%S",
|
||||||
|
) + timedelta(
|
||||||
|
days=(
|
||||||
|
1
|
||||||
|
if datetime.strptime(json_file.stem, "%H-%M-%S").time()
|
||||||
|
< datetime.min.time().replace(hour=4)
|
||||||
|
else 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if single_data[key] != "Success!":
|
||||||
|
if "error_info" not in data:
|
||||||
|
data["error_info"] = {}
|
||||||
|
data["error_info"][actual_date.strftime("%d日 %H:%M:%S")] = (
|
||||||
|
single_data[key]
|
||||||
|
)
|
||||||
|
|
||||||
|
data["index"][actual_date] = [
|
||||||
|
actual_date.strftime("%d日 %H:%M:%S"),
|
||||||
|
("完成" if single_data[key] == "Success!" else "异常"),
|
||||||
|
json_file,
|
||||||
|
]
|
||||||
|
|
||||||
|
data["index"] = [data["index"][_] for _ in sorted(data["index"])]
|
||||||
|
|
||||||
|
logger.success(
|
||||||
|
f"统计信息合并完成,共计 {len(data['index'])} 条记录",
|
||||||
|
)
|
||||||
|
|
||||||
|
return {k: v for k, v in data.items() if v}
|
||||||
|
|
||||||
|
def search_history(
|
||||||
|
self, mode: str, start_date: datetime, end_date: datetime
|
||||||
|
) -> dict:
|
||||||
|
"""
|
||||||
|
搜索指定范围内的历史记录
|
||||||
|
|
||||||
|
:param mode: 合并模式(按日合并、按周合并、按月合并)
|
||||||
|
:param start_date: 开始日期
|
||||||
|
:param end_date: 结束日期
|
||||||
|
:return: 搜索到的历史记录字典
|
||||||
|
"""
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"开始搜索历史记录,合并模式:{mode},日期范围:{start_date} 至 {end_date}"
|
||||||
|
)
|
||||||
|
|
||||||
|
history_dict = {}
|
||||||
|
|
||||||
|
for date_folder in (Path.cwd() / "history").iterdir():
|
||||||
|
if not date_folder.is_dir():
|
||||||
|
continue # 只处理日期文件夹
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
date = datetime.strptime(date_folder.name, "%Y-%m-%d")
|
||||||
|
|
||||||
|
if not (start_date <= date <= end_date):
|
||||||
|
continue # 只统计在范围内的日期
|
||||||
|
|
||||||
|
if mode == "按日合并":
|
||||||
|
date_name = date.strftime("%Y年 %m月 %d日")
|
||||||
|
elif mode == "按周合并":
|
||||||
|
year, week, _ = date.isocalendar()
|
||||||
|
date_name = f"{year}年 第{week}周"
|
||||||
|
elif mode == "按月合并":
|
||||||
|
date_name = date.strftime("%Y年 %m月")
|
||||||
|
|
||||||
|
if date_name not in history_dict:
|
||||||
|
history_dict[date_name] = {}
|
||||||
|
|
||||||
|
for user_folder in date_folder.iterdir():
|
||||||
|
if not user_folder.is_dir():
|
||||||
|
continue # 只处理用户文件夹
|
||||||
|
|
||||||
|
if user_folder.stem not in history_dict[date_name]:
|
||||||
|
history_dict[date_name][user_folder.stem] = list(
|
||||||
|
user_folder.with_suffix("").glob("*.json")
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
history_dict[date_name][user_folder.stem] += list(
|
||||||
|
user_folder.with_suffix("").glob("*.json")
|
||||||
|
)
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
logger.warning(f"非日期格式的目录: {date_folder}")
|
||||||
|
|
||||||
|
logger.success(f"历史记录搜索完成,共计 {len(history_dict)} 条记录")
|
||||||
|
|
||||||
|
return {
|
||||||
|
k: v
|
||||||
|
for k, v in sorted(history_dict.items(), key=lambda x: x[0], reverse=True)
|
||||||
|
}
|
||||||
|
|
||||||
|
def clean_old_history(self):
|
||||||
|
"""删除超过用户设定天数的历史记录文件(基于目录日期)"""
|
||||||
|
|
||||||
|
if self.get("Function", "HistoryRetentionTime") == 0:
|
||||||
|
logger.info("历史记录永久保留,跳过历史记录清理")
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.info("开始清理超过设定天数的历史记录")
|
||||||
|
|
||||||
|
deleted_count = 0
|
||||||
|
|
||||||
|
for date_folder in (Path.cwd() / "history").iterdir():
|
||||||
|
if not date_folder.is_dir():
|
||||||
|
continue # 只处理日期文件夹
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 只检查 `YYYY-MM-DD` 格式的文件夹
|
||||||
|
folder_date = datetime.strptime(date_folder.name, "%Y-%m-%d")
|
||||||
|
if datetime.now() - folder_date > timedelta(
|
||||||
|
days=self.get("Function", "HistoryRetentionTime")
|
||||||
|
):
|
||||||
|
shutil.rmtree(date_folder, ignore_errors=True)
|
||||||
|
deleted_count += 1
|
||||||
|
logger.info(f"已删除超期日志目录: {date_folder}")
|
||||||
|
except ValueError:
|
||||||
|
logger.warning(f"非日期格式的目录: {date_folder}")
|
||||||
|
|
||||||
|
logger.success(f"清理完成: {deleted_count} 个日期目录")
|
||||||
|
|
||||||
|
|
||||||
Config = AppConfig()
|
Config = AppConfig()
|
||||||
|
|||||||
@@ -20,242 +20,30 @@
|
|||||||
|
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
import asyncio
|
||||||
from packaging import version
|
from fastapi import WebSocket
|
||||||
from typing import Dict, Union
|
from typing import Dict, Optional
|
||||||
|
|
||||||
from .config import Config, MaaConfig, GeneralConfig
|
from .config import Config, MaaConfig, GeneralConfig, QueueConfig
|
||||||
from utils import get_logger
|
from app.models.schema import TaskMessage
|
||||||
from task import *
|
from app.utils import get_logger
|
||||||
|
from app.task import *
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger("业务调度")
|
logger = get_logger("业务调度")
|
||||||
|
|
||||||
|
|
||||||
class Task:
|
|
||||||
"""业务线程"""
|
|
||||||
|
|
||||||
check_maa_version = Signal(str)
|
|
||||||
push_info_bar = Signal(str, str, str, int)
|
|
||||||
play_sound = Signal(str)
|
|
||||||
question = Signal(str, str)
|
|
||||||
question_response = Signal(bool)
|
|
||||||
update_maa_user_info = Signal(str, dict)
|
|
||||||
update_general_sub_info = Signal(str, dict)
|
|
||||||
create_task_list = Signal(list)
|
|
||||||
create_user_list = Signal(list)
|
|
||||||
update_task_list = Signal(list)
|
|
||||||
update_user_list = Signal(list)
|
|
||||||
update_log_text = Signal(str)
|
|
||||||
accomplish = Signal(list)
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self, mode: str, name: str, info: Dict[str, Dict[str, Union[str, int, bool]]]
|
|
||||||
):
|
|
||||||
super(Task, self).__init__()
|
|
||||||
|
|
||||||
self.setObjectName(f"Task-{mode}-{name}")
|
|
||||||
|
|
||||||
self.mode = mode
|
|
||||||
self.name = name
|
|
||||||
self.info = info
|
|
||||||
|
|
||||||
self.logs = []
|
|
||||||
|
|
||||||
self.question_response.connect(lambda: print("response"))
|
|
||||||
|
|
||||||
@logger.catch
|
|
||||||
def run(self):
|
|
||||||
|
|
||||||
if "设置MAA" in self.mode:
|
|
||||||
|
|
||||||
logger.info(f"任务开始:设置{self.name}", module=f"业务 {self.name}")
|
|
||||||
self.push_info_bar.emit("info", "设置MAA", self.name, 3000)
|
|
||||||
|
|
||||||
self.task = MaaManager(
|
|
||||||
self.mode,
|
|
||||||
Config.script_dict[self.name],
|
|
||||||
(None if "全局" in self.mode else self.info["SetMaaInfo"]["Path"]),
|
|
||||||
)
|
|
||||||
self.task.check_maa_version.connect(self.check_maa_version.emit)
|
|
||||||
self.task.push_info_bar.connect(self.push_info_bar.emit)
|
|
||||||
self.task.play_sound.connect(self.play_sound.emit)
|
|
||||||
self.task.accomplish.connect(lambda: self.accomplish.emit([]))
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.task.run()
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception(
|
|
||||||
f"任务异常:{self.name},错误信息:{e}", module=f"业务 {self.name}"
|
|
||||||
)
|
|
||||||
self.push_info_bar.emit("error", "任务异常", self.name, -1)
|
|
||||||
|
|
||||||
elif self.mode == "设置通用脚本":
|
|
||||||
|
|
||||||
logger.info(f"任务开始:设置{self.name}", module=f"业务 {self.name}")
|
|
||||||
self.push_info_bar.emit("info", "设置通用脚本", self.name, 3000)
|
|
||||||
|
|
||||||
self.task = GeneralManager(
|
|
||||||
self.mode,
|
|
||||||
Config.script_dict[self.name],
|
|
||||||
self.info["SetSubInfo"]["Path"],
|
|
||||||
)
|
|
||||||
self.task.push_info_bar.connect(self.push_info_bar.emit)
|
|
||||||
self.task.play_sound.connect(self.play_sound.emit)
|
|
||||||
self.task.accomplish.connect(lambda: self.accomplish.emit([]))
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.task.run()
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception(
|
|
||||||
f"任务异常:{self.name},错误信息:{e}", module=f"业务 {self.name}"
|
|
||||||
)
|
|
||||||
self.push_info_bar.emit("error", "任务异常", self.name, -1)
|
|
||||||
|
|
||||||
else:
|
|
||||||
|
|
||||||
logger.info(f"任务开始:{self.name}", module=f"业务 {self.name}")
|
|
||||||
self.task_list = [
|
|
||||||
[
|
|
||||||
(
|
|
||||||
value
|
|
||||||
if Config.script_dict[value]["Config"].get_name() == ""
|
|
||||||
else f"{value} - {Config.script_dict[value]["Config"].get_name()}"
|
|
||||||
),
|
|
||||||
"等待",
|
|
||||||
value,
|
|
||||||
]
|
|
||||||
for _, value in sorted(
|
|
||||||
self.info["Queue"].items(), key=lambda x: int(x[0][7:])
|
|
||||||
)
|
|
||||||
if value != "禁用"
|
|
||||||
]
|
|
||||||
|
|
||||||
self.create_task_list.emit(self.task_list)
|
|
||||||
|
|
||||||
for task in self.task_list:
|
|
||||||
|
|
||||||
if self.isInterruptionRequested():
|
|
||||||
break
|
|
||||||
|
|
||||||
task[1] = "运行"
|
|
||||||
self.update_task_list.emit(self.task_list)
|
|
||||||
|
|
||||||
# 检查任务是否在运行列表中
|
|
||||||
if task[2] in Config.running_list:
|
|
||||||
|
|
||||||
task[1] = "跳过"
|
|
||||||
self.update_task_list.emit(self.task_list)
|
|
||||||
logger.info(
|
|
||||||
f"跳过任务:{task[0]},该任务已在运行列表中",
|
|
||||||
module=f"业务 {self.name}",
|
|
||||||
)
|
|
||||||
self.push_info_bar.emit("info", "跳过任务", task[0], 3000)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 标记为运行中
|
|
||||||
Config.running_list.append(task[2])
|
|
||||||
logger.info(f"任务开始:{task[0]}", module=f"业务 {self.name}")
|
|
||||||
self.push_info_bar.emit("info", "任务开始", task[0], 3000)
|
|
||||||
|
|
||||||
if Config.script_dict[task[2]]["Type"] == "Maa":
|
|
||||||
|
|
||||||
self.task = MaaManager(
|
|
||||||
self.mode[0:4],
|
|
||||||
Config.script_dict[task[2]],
|
|
||||||
)
|
|
||||||
|
|
||||||
self.task.check_maa_version.connect(self.check_maa_version.emit)
|
|
||||||
self.task.question.connect(self.question.emit)
|
|
||||||
self.question_response.disconnect()
|
|
||||||
self.question_response.connect(self.task.question_response.emit)
|
|
||||||
self.task.push_info_bar.connect(self.push_info_bar.emit)
|
|
||||||
self.task.play_sound.connect(self.play_sound.emit)
|
|
||||||
self.task.create_user_list.connect(self.create_user_list.emit)
|
|
||||||
self.task.update_user_list.connect(self.update_user_list.emit)
|
|
||||||
self.task.update_log_text.connect(self.update_log_text.emit)
|
|
||||||
self.task.update_user_info.connect(self.update_maa_user_info.emit)
|
|
||||||
self.task.accomplish.connect(
|
|
||||||
lambda log: self.task_accomplish(task[2], log)
|
|
||||||
)
|
|
||||||
|
|
||||||
elif Config.script_dict[task[2]]["Type"] == "General":
|
|
||||||
|
|
||||||
self.task = GeneralManager(
|
|
||||||
self.mode[0:4],
|
|
||||||
Config.script_dict[task[2]],
|
|
||||||
)
|
|
||||||
|
|
||||||
self.task.question.connect(self.question.emit)
|
|
||||||
self.question_response.disconnect()
|
|
||||||
self.question_response.connect(self.task.question_response.emit)
|
|
||||||
self.task.push_info_bar.connect(self.push_info_bar.emit)
|
|
||||||
self.task.play_sound.connect(self.play_sound.emit)
|
|
||||||
self.task.create_user_list.connect(self.create_user_list.emit)
|
|
||||||
self.task.update_user_list.connect(self.update_user_list.emit)
|
|
||||||
self.task.update_log_text.connect(self.update_log_text.emit)
|
|
||||||
self.task.update_sub_info.connect(self.update_general_sub_info.emit)
|
|
||||||
self.task.accomplish.connect(
|
|
||||||
lambda log: self.task_accomplish(task[2], log)
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.task.run() # 运行任务业务
|
|
||||||
|
|
||||||
task[1] = "完成"
|
|
||||||
self.update_task_list.emit(self.task_list)
|
|
||||||
logger.info(f"任务完成:{task[0]}", module=f"业务 {self.name}")
|
|
||||||
self.push_info_bar.emit("info", "任务完成", task[0], 3000)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
|
|
||||||
self.task_accomplish(
|
|
||||||
task[2],
|
|
||||||
{
|
|
||||||
"Time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
||||||
"History": f"任务异常,异常简报:{e}",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
task[1] = "异常"
|
|
||||||
self.update_task_list.emit(self.task_list)
|
|
||||||
logger.exception(
|
|
||||||
f"任务异常:{task[0]},错误信息:{e}",
|
|
||||||
module=f"业务 {self.name}",
|
|
||||||
)
|
|
||||||
self.push_info_bar.emit("error", "任务异常", task[0], -1)
|
|
||||||
|
|
||||||
# 任务结束后从运行列表中移除
|
|
||||||
Config.running_list.remove(task[2])
|
|
||||||
|
|
||||||
self.accomplish.emit(self.logs)
|
|
||||||
|
|
||||||
def task_accomplish(self, name: str, log: dict):
|
|
||||||
"""
|
|
||||||
销毁任务线程并保存任务结果
|
|
||||||
|
|
||||||
:param name: 任务名称
|
|
||||||
:param log: 任务日志记录
|
|
||||||
"""
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
f"任务完成:{name},日志记录:{list(log.values())}",
|
|
||||||
module=f"业务 {self.name}",
|
|
||||||
)
|
|
||||||
|
|
||||||
self.logs.append([name, log])
|
|
||||||
self.task.deleteLater()
|
|
||||||
|
|
||||||
|
|
||||||
class _TaskManager:
|
class _TaskManager:
|
||||||
"""业务调度器"""
|
"""业务调度器"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(_TaskManager, self).__init__()
|
super().__init__()
|
||||||
|
|
||||||
self.task_dict: Dict[str, Task] = {}
|
self.task_dict: Dict[uuid.UUID, asyncio.Task] = {}
|
||||||
|
self.connection_events: Dict[uuid.UUID, asyncio.Event] = {}
|
||||||
|
self.websocket_dict: Dict[uuid.UUID, WebSocket] = {}
|
||||||
|
|
||||||
def add_task(self, mode: str, uid: str):
|
async def add_task(self, mode: str, uid: str) -> uuid.UUID:
|
||||||
"""
|
"""
|
||||||
添加任务
|
添加任务
|
||||||
|
|
||||||
@@ -268,6 +56,7 @@ class _TaskManager:
|
|||||||
if mode == "设置脚本":
|
if mode == "设置脚本":
|
||||||
if actual_id in Config.ScriptConfig:
|
if actual_id in Config.ScriptConfig:
|
||||||
task_id = actual_id
|
task_id = actual_id
|
||||||
|
actual_id = None
|
||||||
else:
|
else:
|
||||||
for script_id, script in Config.ScriptConfig.items():
|
for script_id, script in Config.ScriptConfig.items():
|
||||||
if (
|
if (
|
||||||
@@ -280,128 +69,192 @@ class _TaskManager:
|
|||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"The task corresponding to UID {uid} could not be found."
|
f"The task corresponding to UID {uid} could not be found."
|
||||||
)
|
)
|
||||||
elif actual_id in Config.QueueConfig or actual_id in Config.ScriptConfig:
|
elif actual_id in Config.QueueConfig:
|
||||||
task_id = actual_id
|
task_id = actual_id
|
||||||
|
actual_id = None
|
||||||
|
elif actual_id in Config.ScriptConfig:
|
||||||
|
task_id = uuid.uuid4()
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"The task corresponding to UID {uid} could not be found.")
|
raise ValueError(f"The task corresponding to UID {uid} could not be found.")
|
||||||
|
|
||||||
if name in Config.running_list or name in self.task_dict:
|
if task_id in self.task_dict:
|
||||||
|
|
||||||
logger.warning(f"任务已存在:{name}")
|
raise RuntimeError(f"The task {task_id} is already running.")
|
||||||
MainInfoBar.push_info_bar("warning", "任务已存在", name, 5000)
|
|
||||||
return None
|
|
||||||
|
|
||||||
logger.info(f"任务开始:{name},模式:{mode}", module="业务调度")
|
logger.info(f"创建任务:{task_id},模式:{mode}")
|
||||||
MainInfoBar.push_info_bar("info", "任务开始", name, 3000)
|
|
||||||
SoundPlayer.play("任务开始")
|
|
||||||
|
|
||||||
# 标记任务为运行中
|
|
||||||
Config.running_list.append(name)
|
|
||||||
|
|
||||||
# 创建任务实例并连接信号
|
# 创建任务实例并连接信号
|
||||||
self.task_dict[name] = Task(mode, name, info)
|
if task_id in self.connection_events:
|
||||||
self.task_dict[name].check_maa_version.connect(self.check_maa_version)
|
self.connection_events[task_id].clear()
|
||||||
self.task_dict[name].question.connect(
|
else:
|
||||||
lambda title, content: self.push_dialog(name, title, content)
|
self.connection_events[task_id] = asyncio.Event()
|
||||||
|
|
||||||
|
await self.connection_events[task_id].wait()
|
||||||
|
|
||||||
|
if task_id not in self.websocket_dict:
|
||||||
|
raise RuntimeError(f"The task {task_id} is not connected to a WebSocket.")
|
||||||
|
|
||||||
|
logger.info(f"开始运行任务:{task_id},模式:{mode}")
|
||||||
|
|
||||||
|
self.task_dict[task_id] = asyncio.create_task(
|
||||||
|
self.run_task(mode, task_id, actual_id)
|
||||||
)
|
)
|
||||||
self.task_dict[name].push_info_bar.connect(MainInfoBar.push_info_bar)
|
self.task_dict[task_id].add_done_callback(
|
||||||
self.task_dict[name].play_sound.connect(SoundPlayer.play)
|
lambda t: asyncio.create_task(self.remove_task(t, mode, task_id))
|
||||||
self.task_dict[name].update_maa_user_info.connect(Config.change_maa_user_info)
|
|
||||||
self.task_dict[name].update_general_sub_info.connect(
|
|
||||||
Config.change_general_sub_info
|
|
||||||
)
|
|
||||||
self.task_dict[name].accomplish.connect(
|
|
||||||
lambda logs: self.remove_task(mode, name, logs)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# 向UI发送信号以创建或连接GUI
|
return task_id
|
||||||
if "新调度台" in mode:
|
|
||||||
self.create_gui.emit(self.task_dict[name])
|
|
||||||
|
|
||||||
elif "主调度台" in mode:
|
@logger.catch
|
||||||
self.connect_gui.emit(self.task_dict[name])
|
async def run_task(
|
||||||
|
self, mode: str, task_id: uuid.UUID, actual_id: Optional[uuid.UUID]
|
||||||
|
):
|
||||||
|
|
||||||
# 启动任务线程
|
websocket = self.websocket_dict[task_id]
|
||||||
self.task_dict[name].start()
|
|
||||||
|
|
||||||
def stop_task(self, name: str) -> None:
|
if mode == "设置脚本":
|
||||||
|
|
||||||
|
if isinstance(Config.ScriptConfig[task_id], MaaConfig):
|
||||||
|
task_item = MaaManager(mode, task_id, actual_id, websocket)
|
||||||
|
# elif isinstance(Config.ScriptConfig[task_id], GeneralConfig):
|
||||||
|
# task_item = GeneralManager(mode, task_id, actual_id, websocket)
|
||||||
|
else:
|
||||||
|
logger.error(
|
||||||
|
f"不支持的脚本类型:{Config.ScriptConfig[task_id].__class__.__name__}"
|
||||||
|
)
|
||||||
|
await websocket.send_json(
|
||||||
|
TaskMessage(
|
||||||
|
type="Info", data={"Error": "脚本类型不支持"}
|
||||||
|
).model_dump()
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
task = asyncio.create_task(task_item.run())
|
||||||
|
task.add_done_callback(
|
||||||
|
lambda t: asyncio.create_task(task_item.final_task(t))
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
if task_id in Config.QueueConfig:
|
||||||
|
|
||||||
|
queue = Config.QueueConfig[task_id]
|
||||||
|
if not isinstance(queue, QueueConfig):
|
||||||
|
logger.error(
|
||||||
|
f"不支持的队列类型:{Config.QueueConfig[task_id].__class__.__name__}"
|
||||||
|
)
|
||||||
|
await websocket.send_json(
|
||||||
|
TaskMessage(
|
||||||
|
type="Info", data={"Error": "队列类型不支持"}
|
||||||
|
).model_dump()
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
task_list = []
|
||||||
|
for queue_item in queue.QueueItem.values():
|
||||||
|
uid = uuid.UUID(queue_item.get("Info", "ScriptId"))
|
||||||
|
task_list.append(
|
||||||
|
{
|
||||||
|
"script_id": str(uid),
|
||||||
|
"status": "等待",
|
||||||
|
"name": Config.ScriptConfig[uid].get("Info", "Name"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
elif actual_id is not None and actual_id in Config.ScriptConfig:
|
||||||
|
|
||||||
|
task_list = [{"script_id": str(actual_id), "status": "等待"}]
|
||||||
|
|
||||||
|
for task in task_list:
|
||||||
|
|
||||||
|
script_id = uuid.UUID(task["script_id"])
|
||||||
|
|
||||||
|
# 检查任务是否在运行列表中
|
||||||
|
if script_id in self.task_dict:
|
||||||
|
|
||||||
|
task["status"] = "跳过"
|
||||||
|
await websocket.send_json(
|
||||||
|
TaskMessage(
|
||||||
|
type="Update", data={"task_list": task_list}
|
||||||
|
).model_dump()
|
||||||
|
)
|
||||||
|
logger.info(f"跳过任务:{script_id},该任务已在运行列表中")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 标记为运行中
|
||||||
|
task["status"] = "运行"
|
||||||
|
await websocket.send_json(
|
||||||
|
TaskMessage(
|
||||||
|
type="Update", data={"task_list": task_list}
|
||||||
|
).model_dump()
|
||||||
|
)
|
||||||
|
logger.info(f"任务开始:{script_id}")
|
||||||
|
|
||||||
|
if isinstance(Config.ScriptConfig[script_id], MaaConfig):
|
||||||
|
task_item = MaaManager(mode, script_id, None, websocket)
|
||||||
|
# elif isinstance(Config.ScriptConfig[task_id], GeneralConfig):
|
||||||
|
# task_item = GeneralManager(mode, task_id, actual_id, websocket)
|
||||||
|
else:
|
||||||
|
logger.error(
|
||||||
|
f"不支持的脚本类型:{Config.ScriptConfig[script_id].__class__.__name__}"
|
||||||
|
)
|
||||||
|
await websocket.send_json(
|
||||||
|
TaskMessage(
|
||||||
|
type="Info", data={"Error": "脚本类型不支持"}
|
||||||
|
).model_dump()
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
task = asyncio.create_task(task_item.run())
|
||||||
|
task.add_done_callback(
|
||||||
|
lambda t: asyncio.create_task(task_item.final_task(t))
|
||||||
|
)
|
||||||
|
|
||||||
|
async def stop_task(self, task_id: uuid.UUID) -> None:
|
||||||
"""
|
"""
|
||||||
中止任务
|
中止任务
|
||||||
|
|
||||||
:param name: 任务名称
|
:param task_id: 任务ID
|
||||||
"""
|
"""
|
||||||
|
|
||||||
logger.info(f"中止任务:{name}", module="业务调度")
|
logger.info(f"中止任务:{task_id}")
|
||||||
MainInfoBar.push_info_bar("info", "中止任务", name, 3000)
|
|
||||||
|
|
||||||
if name == "ALL":
|
if task_id not in self.task_dict:
|
||||||
|
raise ValueError(f"The task {task_id} is not running.")
|
||||||
|
|
||||||
for name in self.task_dict:
|
self.task_dict[task_id].cancel()
|
||||||
|
|
||||||
self.task_dict[name].task.requestInterruption()
|
async def remove_task(
|
||||||
self.task_dict[name].requestInterruption()
|
self, task: asyncio.Task, mode: str, task_id: uuid.UUID
|
||||||
self.task_dict[name].quit()
|
) -> None:
|
||||||
self.task_dict[name].wait()
|
|
||||||
|
|
||||||
elif name in self.task_dict:
|
|
||||||
|
|
||||||
self.task_dict[name].task.requestInterruption()
|
|
||||||
self.task_dict[name].requestInterruption()
|
|
||||||
self.task_dict[name].quit()
|
|
||||||
self.task_dict[name].wait()
|
|
||||||
|
|
||||||
def remove_task(self, mode: str, name: str, logs: list) -> None:
|
|
||||||
"""
|
"""
|
||||||
处理任务结束后的收尾工作
|
处理任务结束后的收尾工作
|
||||||
|
|
||||||
:param mode: 任务模式
|
Parameters
|
||||||
:param name: 任务名称
|
----------
|
||||||
:param logs: 任务日志
|
task : asyncio.Task
|
||||||
|
任务对象
|
||||||
|
mode : str
|
||||||
|
任务模式
|
||||||
|
task_id : uuid.UUID
|
||||||
|
任务ID
|
||||||
"""
|
"""
|
||||||
|
|
||||||
logger.info(f"任务结束:{name}", module="业务调度")
|
logger.info(f"任务结束:{task_id}")
|
||||||
MainInfoBar.push_info_bar("info", "任务结束", name, 3000)
|
|
||||||
SoundPlayer.play("任务结束")
|
|
||||||
|
|
||||||
# 删除任务线程,移除运行中标记
|
# 从任务字典中移除任务
|
||||||
self.task_dict[name].deleteLater()
|
try:
|
||||||
self.task_dict.pop(name)
|
await task
|
||||||
Config.running_list.remove(name)
|
except asyncio.CancelledError:
|
||||||
|
logger.info(f"任务 {task_id} 已结束")
|
||||||
|
self.task_dict.pop(task_id)
|
||||||
|
|
||||||
if "调度队列" in name and "人工排查" not in mode:
|
websocket = self.websocket_dict.pop(task_id, None)
|
||||||
|
if websocket:
|
||||||
# 保存调度队列历史记录
|
await websocket.send_json(
|
||||||
if len(logs) > 0:
|
TaskMessage(type="Signal", data={"Accomplish": "无描述"}).model_dump()
|
||||||
time = logs[0][1]["Time"]
|
)
|
||||||
history = ""
|
await websocket.close()
|
||||||
for log in logs:
|
|
||||||
history += f"任务名称:{log[0]},{log[1]["History"].replace("\n","\n ")}\n"
|
|
||||||
Config.save_history(name, {"Time": time, "History": history})
|
|
||||||
else:
|
|
||||||
Config.save_history(
|
|
||||||
name,
|
|
||||||
{
|
|
||||||
"Time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
||||||
"History": "没有任务被执行",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
# 根据调度队列情况设置电源状态
|
|
||||||
if (
|
|
||||||
Config.queue_dict[name]["Config"].get(
|
|
||||||
Config.queue_dict[name]["Config"].QueueSet_AfterAccomplish
|
|
||||||
)
|
|
||||||
!= "NoAction"
|
|
||||||
and Config.power_sign == "NoAction"
|
|
||||||
):
|
|
||||||
Config.set_power_sign(
|
|
||||||
Config.queue_dict[name]["Config"].get(
|
|
||||||
Config.queue_dict[name]["Config"].QueueSet_AfterAccomplish
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if Config.args.mode == "cli" and Config.power_sign == "NoAction":
|
|
||||||
Config.set_power_sign("KillSelf")
|
|
||||||
|
|
||||||
|
|
||||||
TaskManager = _TaskManager()
|
TaskManager = _TaskManager()
|
||||||
|
|||||||
@@ -193,6 +193,9 @@ class ConfigItem:
|
|||||||
self.name = name
|
self.name = name
|
||||||
self.value: Any = default
|
self.value: Any = default
|
||||||
self.validator = validator or ConfigValidator()
|
self.validator = validator or ConfigValidator()
|
||||||
|
self.is_locked = False
|
||||||
|
|
||||||
|
self.setValue(default)
|
||||||
|
|
||||||
def setValue(self, value: Any):
|
def setValue(self, value: Any):
|
||||||
"""
|
"""
|
||||||
@@ -211,6 +214,11 @@ class ConfigItem:
|
|||||||
) == value:
|
) == value:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if self.is_locked:
|
||||||
|
raise ValueError(
|
||||||
|
f"Config item '{self.group}.{self.name}' is locked and cannot be modified."
|
||||||
|
)
|
||||||
|
|
||||||
# deepcopy new value
|
# deepcopy new value
|
||||||
try:
|
try:
|
||||||
self.value = deepcopy(value)
|
self.value = deepcopy(value)
|
||||||
@@ -232,6 +240,18 @@ class ConfigItem:
|
|||||||
return dpapi_decrypt(self.value)
|
return dpapi_decrypt(self.value)
|
||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
|
def lock(self):
|
||||||
|
"""
|
||||||
|
锁定配置项,锁定后无法修改配置项值
|
||||||
|
"""
|
||||||
|
self.is_locked = True
|
||||||
|
|
||||||
|
def unlock(self):
|
||||||
|
"""
|
||||||
|
解锁配置项,解锁后可以修改配置项值
|
||||||
|
"""
|
||||||
|
self.is_locked = False
|
||||||
|
|
||||||
|
|
||||||
class ConfigBase:
|
class ConfigBase:
|
||||||
"""
|
"""
|
||||||
@@ -249,6 +269,7 @@ class ConfigBase:
|
|||||||
|
|
||||||
self.file: None | Path = None
|
self.file: None | Path = None
|
||||||
self.if_save_multi_config = if_save_multi_config
|
self.if_save_multi_config = if_save_multi_config
|
||||||
|
self.is_locked = False
|
||||||
|
|
||||||
async def connect(self, path: Path):
|
async def connect(self, path: Path):
|
||||||
"""
|
"""
|
||||||
@@ -265,6 +286,9 @@ class ConfigBase:
|
|||||||
"The config file must be a JSON file with '.json' extension."
|
"The config file must be a JSON file with '.json' extension."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.is_locked:
|
||||||
|
raise ValueError("Config is locked and cannot be modified.")
|
||||||
|
|
||||||
self.file = path
|
self.file = path
|
||||||
|
|
||||||
if not self.file.exists():
|
if not self.file.exists():
|
||||||
@@ -292,6 +316,9 @@ class ConfigBase:
|
|||||||
配置数据字典
|
配置数据字典
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if self.is_locked:
|
||||||
|
raise ValueError("Config is locked and cannot be modified.")
|
||||||
|
|
||||||
# update the value of config item
|
# update the value of config item
|
||||||
if data.get("SubConfigsInfo"):
|
if data.get("SubConfigsInfo"):
|
||||||
for k, v in data["SubConfigsInfo"].items():
|
for k, v in data["SubConfigsInfo"].items():
|
||||||
@@ -395,6 +422,32 @@ class ConfigBase:
|
|||||||
indent=4,
|
indent=4,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def lock(self):
|
||||||
|
"""
|
||||||
|
锁定配置项,锁定后无法修改配置项值
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.is_locked = True
|
||||||
|
|
||||||
|
for name in dir(self):
|
||||||
|
item = getattr(self, name)
|
||||||
|
if isinstance(item, ConfigItem | MultipleConfig):
|
||||||
|
item.lock()
|
||||||
|
|
||||||
|
async def unlock(self):
|
||||||
|
"""
|
||||||
|
解锁配置项,解锁后可以修改配置项值
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.is_locked = False
|
||||||
|
|
||||||
|
for name in dir(self):
|
||||||
|
item = getattr(self, name)
|
||||||
|
if isinstance(item, ConfigItem):
|
||||||
|
item.unlock()
|
||||||
|
elif isinstance(item, MultipleConfig):
|
||||||
|
await item.unlock()
|
||||||
|
|
||||||
|
|
||||||
class MultipleConfig:
|
class MultipleConfig:
|
||||||
"""
|
"""
|
||||||
@@ -424,6 +477,7 @@ class MultipleConfig:
|
|||||||
self.file: None | Path = None
|
self.file: None | Path = None
|
||||||
self.order: List[uuid.UUID] = []
|
self.order: List[uuid.UUID] = []
|
||||||
self.data: Dict[uuid.UUID, ConfigBase] = {}
|
self.data: Dict[uuid.UUID, ConfigBase] = {}
|
||||||
|
self.is_locked = False
|
||||||
|
|
||||||
def __getitem__(self, key: uuid.UUID) -> ConfigBase:
|
def __getitem__(self, key: uuid.UUID) -> ConfigBase:
|
||||||
"""允许通过 config[uuid] 访问配置项"""
|
"""允许通过 config[uuid] 访问配置项"""
|
||||||
@@ -462,6 +516,9 @@ class MultipleConfig:
|
|||||||
"The config file must be a JSON file with '.json' extension."
|
"The config file must be a JSON file with '.json' extension."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.is_locked:
|
||||||
|
raise ValueError("Config is locked and cannot be modified.")
|
||||||
|
|
||||||
self.file = path
|
self.file = path
|
||||||
|
|
||||||
if not self.file.exists():
|
if not self.file.exists():
|
||||||
@@ -490,6 +547,9 @@ class MultipleConfig:
|
|||||||
配置数据字典
|
配置数据字典
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if self.is_locked:
|
||||||
|
raise ValueError("Config is locked and cannot be modified.")
|
||||||
|
|
||||||
if not data.get("instances"):
|
if not data.get("instances"):
|
||||||
self.order = []
|
self.order = []
|
||||||
self.data = {}
|
self.data = {}
|
||||||
@@ -614,9 +674,17 @@ class MultipleConfig:
|
|||||||
要移除的配置项的唯一标识符
|
要移除的配置项的唯一标识符
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if self.is_locked:
|
||||||
|
raise ValueError("Config is locked and cannot be modified.")
|
||||||
|
|
||||||
if uid not in self.data:
|
if uid not in self.data:
|
||||||
raise ValueError(f"Config item with uid {uid} does not exist.")
|
raise ValueError(f"Config item with uid {uid} does not exist.")
|
||||||
|
|
||||||
|
if self.data[uid].is_locked:
|
||||||
|
raise ValueError(
|
||||||
|
f"Config item with uid {uid} is locked and cannot be removed."
|
||||||
|
)
|
||||||
|
|
||||||
self.data.pop(uid)
|
self.data.pop(uid)
|
||||||
self.order.remove(uid)
|
self.order.remove(uid)
|
||||||
|
|
||||||
@@ -641,6 +709,26 @@ class MultipleConfig:
|
|||||||
if self.file:
|
if self.file:
|
||||||
await self.save()
|
await self.save()
|
||||||
|
|
||||||
|
async def lock(self):
|
||||||
|
"""
|
||||||
|
锁定配置项,锁定后无法修改配置项值
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.is_locked = True
|
||||||
|
|
||||||
|
for item in self.values():
|
||||||
|
await item.lock()
|
||||||
|
|
||||||
|
async def unlock(self):
|
||||||
|
"""
|
||||||
|
解锁配置项,解锁后可以修改配置项值
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.is_locked = False
|
||||||
|
|
||||||
|
for item in self.values():
|
||||||
|
await item.unlock()
|
||||||
|
|
||||||
def keys(self):
|
def keys(self):
|
||||||
"""返回配置项的所有唯一标识符"""
|
"""返回配置项的所有唯一标识符"""
|
||||||
|
|
||||||
|
|||||||
@@ -192,12 +192,24 @@ class DispatchIn(BaseModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class DispatchCreateIn(DispatchIn):
|
class TaskCreateIn(DispatchIn):
|
||||||
mode: Literal["自动代理", "人工排查", "设置脚本"] = Field(
|
mode: Literal["自动代理", "人工排查", "设置脚本"] = Field(
|
||||||
..., description="任务模式"
|
..., description="任务模式"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TaskCreateOut(OutBase):
|
||||||
|
taskId: str = Field(..., description="新创建的任务ID")
|
||||||
|
|
||||||
|
|
||||||
|
class TaskMessage(BaseModel):
|
||||||
|
type: Literal["Update", "Message", "Info", "Signal"] = Field(
|
||||||
|
...,
|
||||||
|
description="消息类型 Update: 更新数据, Message: 请求弹出对话框, Info: 需要在UI显示的消息, Signal: 程序信号",
|
||||||
|
)
|
||||||
|
data: Dict[str, Any] = Field(..., description="消息数据,具体内容根据type类型而定")
|
||||||
|
|
||||||
|
|
||||||
class SettingGetOut(OutBase):
|
class SettingGetOut(OutBase):
|
||||||
data: Dict[str, Dict[str, Any]] = Field(..., description="全局设置数据")
|
data: Dict[str, Dict[str, Any]] = Field(..., description="全局设置数据")
|
||||||
|
|
||||||
|
|||||||
@@ -303,7 +303,7 @@ class _SystemHandler:
|
|||||||
win32gui.EnumWindows(callback, window_info)
|
win32gui.EnumWindows(callback, window_info)
|
||||||
return window_info
|
return window_info
|
||||||
|
|
||||||
def kill_process(self, path: Path) -> None:
|
async def kill_process(self, path: Path) -> None:
|
||||||
"""
|
"""
|
||||||
根据路径中止进程
|
根据路径中止进程
|
||||||
|
|
||||||
@@ -312,7 +312,7 @@ class _SystemHandler:
|
|||||||
|
|
||||||
logger.info(f"开始中止进程: {path}")
|
logger.info(f"开始中止进程: {path}")
|
||||||
|
|
||||||
for pid in self.search_pids(path):
|
for pid in await self.search_pids(path):
|
||||||
killprocess = subprocess.Popen(
|
killprocess = subprocess.Popen(
|
||||||
f"taskkill /F /T /PID {pid}",
|
f"taskkill /F /T /PID {pid}",
|
||||||
shell=True,
|
shell=True,
|
||||||
@@ -322,7 +322,7 @@ class _SystemHandler:
|
|||||||
|
|
||||||
logger.success(f"进程已中止: {path}")
|
logger.success(f"进程已中止: {path}")
|
||||||
|
|
||||||
def search_pids(self, path: Path) -> list:
|
async def search_pids(self, path: Path) -> list:
|
||||||
"""
|
"""
|
||||||
根据路径查找进程PID
|
根据路径查找进程PID
|
||||||
|
|
||||||
|
|||||||
1998
app/task/MAA.py
Normal file
@@ -25,5 +25,6 @@ __license__ = "GPL-3.0 license"
|
|||||||
|
|
||||||
|
|
||||||
from .skland import skland_sign_in
|
from .skland import skland_sign_in
|
||||||
|
from .MAA import MaaManager
|
||||||
|
|
||||||
__all__ = ["skland_sign_in"]
|
__all__ = ["skland_sign_in", "MaaManager"]
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ import hashlib
|
|||||||
import requests
|
import requests
|
||||||
from urllib import parse
|
from urllib import parse
|
||||||
|
|
||||||
from core import Config
|
from app.core import Config
|
||||||
from utils.logger import get_logger
|
from app.utils.logger import get_logger
|
||||||
|
|
||||||
logger = get_logger("森空岛签到任务")
|
logger = get_logger("森空岛签到任务")
|
||||||
|
|
||||||
|
|||||||
89
app/utils/LogMonitor.py
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import asyncio
|
||||||
|
import aiofiles
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Callable, Optional, List, Awaitable
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
|
||||||
|
class LogMonitor:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
time_stamp_range: tuple[int, int],
|
||||||
|
time_format: str,
|
||||||
|
callback: Callable[[List[str]], Awaitable[None]],
|
||||||
|
encoding: str = "utf-8",
|
||||||
|
):
|
||||||
|
self.time_stamp_range = time_stamp_range
|
||||||
|
self.time_format = time_format
|
||||||
|
self.callback = callback
|
||||||
|
self.encoding = encoding
|
||||||
|
self.log_file_path: Optional[Path] = None
|
||||||
|
self.log_start_time: datetime = datetime.now()
|
||||||
|
self.log_contents: List[str] = []
|
||||||
|
self.task: Optional[asyncio.Task] = None
|
||||||
|
|
||||||
|
async def monitor_log(self):
|
||||||
|
|
||||||
|
if self.log_file_path is None or not self.log_file_path.exists():
|
||||||
|
raise ValueError("Log file path is not set or does not exist.")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
|
||||||
|
log_contents = []
|
||||||
|
if_log_start = False
|
||||||
|
|
||||||
|
async with aiofiles.open(
|
||||||
|
self.log_file_path, "r", encoding=self.encoding
|
||||||
|
) as f:
|
||||||
|
|
||||||
|
async for line in f:
|
||||||
|
if not if_log_start:
|
||||||
|
try:
|
||||||
|
entry_time = datetime.strptime(
|
||||||
|
line[
|
||||||
|
self.time_stamp_range[0] : self.time_stamp_range[1]
|
||||||
|
],
|
||||||
|
self.time_format,
|
||||||
|
)
|
||||||
|
if entry_time > self.log_start_time:
|
||||||
|
if_log_start = True
|
||||||
|
log_contents.append(line)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
log_contents.append(line)
|
||||||
|
|
||||||
|
# 调用回调
|
||||||
|
if log_contents != self.log_contents:
|
||||||
|
self.log_contents = log_contents
|
||||||
|
await self.callback(log_contents)
|
||||||
|
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
|
async def start(self, log_file_path: Path, start_time: datetime) -> None:
|
||||||
|
"""启动监控"""
|
||||||
|
|
||||||
|
if log_file_path.is_dir():
|
||||||
|
raise ValueError(f"Log file cannot be a directory: {log_file_path}")
|
||||||
|
|
||||||
|
if self.task is not None and not self.task.done():
|
||||||
|
await self.stop()
|
||||||
|
|
||||||
|
self.log_file_path = log_file_path
|
||||||
|
self.log_start_time = start_time
|
||||||
|
self.task = asyncio.create_task(self.monitor_log())
|
||||||
|
|
||||||
|
logger.info(f"开始监控文件: {self.log_file_path}")
|
||||||
|
|
||||||
|
async def stop(self):
|
||||||
|
"""停止监控"""
|
||||||
|
|
||||||
|
if self.task is not None and not self.task.done():
|
||||||
|
self.task.cancel()
|
||||||
|
|
||||||
|
self.log_contents = []
|
||||||
|
logger.info(f"停止监控文件: {self.log_file_path}")
|
||||||
|
self.log_file_path = None
|
||||||
@@ -26,12 +26,14 @@ __license__ = "GPL-3.0 license"
|
|||||||
|
|
||||||
from .logger import get_logger
|
from .logger import get_logger
|
||||||
from .ImageUtils import ImageUtils
|
from .ImageUtils import ImageUtils
|
||||||
|
from .LogMonitor import LogMonitor
|
||||||
from .ProcessManager import ProcessManager
|
from .ProcessManager import ProcessManager
|
||||||
from .security import dpapi_encrypt, dpapi_decrypt
|
from .security import dpapi_encrypt, dpapi_decrypt
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"get_logger",
|
"get_logger",
|
||||||
"ImageUtils",
|
"ImageUtils",
|
||||||
|
"LogMonitor",
|
||||||
"ProcessManager",
|
"ProcessManager",
|
||||||
"dpapi_encrypt",
|
"dpapi_encrypt",
|
||||||
"dpapi_decrypt",
|
"dpapi_decrypt",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ dependencies = [
|
|||||||
"uvicorn==0.35.0",
|
"uvicorn==0.35.0",
|
||||||
"plyer==2.1.0",
|
"plyer==2.1.0",
|
||||||
"psutil==7.0.0",
|
"psutil==7.0.0",
|
||||||
|
"jinja2==3.1.6",
|
||||||
"pywin32==310",
|
"pywin32==310",
|
||||||
"keyboard==0.13.5",
|
"keyboard==0.13.5",
|
||||||
"truststore==0.10.1",
|
"truststore==0.10.1",
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ loguru==0.7.3
|
|||||||
fastapi==0.116.1
|
fastapi==0.116.1
|
||||||
pydantic==2.11.7
|
pydantic==2.11.7
|
||||||
uvicorn==0.35.0
|
uvicorn==0.35.0
|
||||||
|
aiofiles==24.1.0
|
||||||
plyer==2.1.0
|
plyer==2.1.0
|
||||||
psutil==7.0.0
|
psutil==7.0.0
|
||||||
|
jinja2==3.1.6
|
||||||
pywin32==310
|
pywin32==310
|
||||||
keyboard==0.13.5
|
keyboard==0.13.5
|
||||||
truststore==0.10.1
|
truststore==0.10.1
|
||||||
|
|||||||
403
resources/docs/ChineseSimplified.isl
Normal file
@@ -0,0 +1,403 @@
|
|||||||
|
; *** Inno Setup version 6.4.0+ Chinese Simplified messages ***
|
||||||
|
;
|
||||||
|
; To download user-contributed translations of this file, go to:
|
||||||
|
; https://jrsoftware.org/files/istrans/
|
||||||
|
;
|
||||||
|
; Note: When translating this text, do not add periods (.) to the end of
|
||||||
|
; messages that didn't have them already, because on those messages Inno
|
||||||
|
; Setup adds the periods automatically (appending a period would result in
|
||||||
|
; two periods being displayed).
|
||||||
|
;
|
||||||
|
; Maintained by Zhenghan Yang
|
||||||
|
; Email: 847320916@QQ.com
|
||||||
|
; Translation based on network resource
|
||||||
|
; The latest Translation is on https://github.com/kira-96/Inno-Setup-Chinese-Simplified-Translation
|
||||||
|
;
|
||||||
|
|
||||||
|
[LangOptions]
|
||||||
|
; The following three entries are very important. Be sure to read and
|
||||||
|
; understand the '[LangOptions] section' topic in the help file.
|
||||||
|
LanguageName=简体中文
|
||||||
|
; If Language Name display incorrect, uncomment next line
|
||||||
|
; LanguageName=<7B80><4F53><4E2D><6587>
|
||||||
|
; About LanguageID, to reference link:
|
||||||
|
; https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/a9eac961-e77d-41a6-90a5-ce1a8b0cdb9c
|
||||||
|
LanguageID=$0804
|
||||||
|
; About CodePage, to reference link:
|
||||||
|
; https://docs.microsoft.com/en-us/windows/win32/intl/code-page-identifiers
|
||||||
|
LanguageCodePage=936
|
||||||
|
; If the language you are translating to requires special font faces or
|
||||||
|
; sizes, uncomment any of the following entries and change them accordingly.
|
||||||
|
;DialogFontName=
|
||||||
|
;DialogFontSize=8
|
||||||
|
;WelcomeFontName=Verdana
|
||||||
|
;WelcomeFontSize=12
|
||||||
|
;TitleFontName=Arial
|
||||||
|
;TitleFontSize=29
|
||||||
|
;CopyrightFontName=Arial
|
||||||
|
;CopyrightFontSize=8
|
||||||
|
|
||||||
|
[Messages]
|
||||||
|
|
||||||
|
; *** 应用程序标题
|
||||||
|
SetupAppTitle=安装
|
||||||
|
SetupWindowTitle=安装 - %1
|
||||||
|
UninstallAppTitle=卸载
|
||||||
|
UninstallAppFullTitle=%1 卸载
|
||||||
|
|
||||||
|
; *** Misc. common
|
||||||
|
InformationTitle=信息
|
||||||
|
ConfirmTitle=确认
|
||||||
|
ErrorTitle=错误
|
||||||
|
|
||||||
|
; *** SetupLdr messages
|
||||||
|
SetupLdrStartupMessage=现在将安装 %1。您想要继续吗?
|
||||||
|
LdrCannotCreateTemp=无法创建临时文件。安装程序已中止
|
||||||
|
LdrCannotExecTemp=无法执行临时目录中的文件。安装程序已中止
|
||||||
|
HelpTextNote=
|
||||||
|
|
||||||
|
; *** 启动错误消息
|
||||||
|
LastErrorMessage=%1。%n%n错误 %2: %3
|
||||||
|
SetupFileMissing=安装目录中缺少文件 %1。请修正这个问题或者获取程序的新副本。
|
||||||
|
SetupFileCorrupt=安装文件已损坏。请获取程序的新副本。
|
||||||
|
SetupFileCorruptOrWrongVer=安装文件已损坏,或是与这个安装程序的版本不兼容。请修正这个问题或获取新的程序副本。
|
||||||
|
InvalidParameter=无效的命令行参数:%n%n%1
|
||||||
|
SetupAlreadyRunning=安装程序正在运行。
|
||||||
|
WindowsVersionNotSupported=此程序不支持当前计算机运行的 Windows 版本。
|
||||||
|
WindowsServicePackRequired=此程序需要 %1 服务包 %2 或更高版本。
|
||||||
|
NotOnThisPlatform=此程序不能在 %1 上运行。
|
||||||
|
OnlyOnThisPlatform=此程序只能在 %1 上运行。
|
||||||
|
OnlyOnTheseArchitectures=此程序只能安装到为下列处理器架构设计的 Windows 版本中:%n%n%1
|
||||||
|
WinVersionTooLowError=此程序需要 %1 版本 %2 或更高。
|
||||||
|
WinVersionTooHighError=此程序不能安装于 %1 版本 %2 或更高。
|
||||||
|
AdminPrivilegesRequired=在安装此程序时您必须以管理员身份登录。
|
||||||
|
PowerUserPrivilegesRequired=在安装此程序时您必须以管理员身份或有权限的用户组身份登录。
|
||||||
|
SetupAppRunningError=安装程序发现 %1 当前正在运行。%n%n请先关闭正在运行的程序,然后点击“确定”继续,或点击“取消”退出。
|
||||||
|
UninstallAppRunningError=卸载程序发现 %1 当前正在运行。%n%n请先关闭正在运行的程序,然后点击“确定”继续,或点击“取消”退出。
|
||||||
|
|
||||||
|
; *** 启动问题
|
||||||
|
PrivilegesRequiredOverrideTitle=选择安装程序模式
|
||||||
|
PrivilegesRequiredOverrideInstruction=选择安装模式
|
||||||
|
PrivilegesRequiredOverrideText1=%1 可以为所有用户安装(需要管理员权限),或仅为您安装。
|
||||||
|
PrivilegesRequiredOverrideText2=%1 只能为您安装,或为所有用户安装(需要管理员权限)。
|
||||||
|
PrivilegesRequiredOverrideAllUsers=为所有用户安装(&A)
|
||||||
|
PrivilegesRequiredOverrideAllUsersRecommended=为所有用户安装(&A) (建议选项)
|
||||||
|
PrivilegesRequiredOverrideCurrentUser=只为我安装(&M)
|
||||||
|
PrivilegesRequiredOverrideCurrentUserRecommended=只为我安装(&M) (建议选项)
|
||||||
|
|
||||||
|
; *** 其他错误
|
||||||
|
ErrorCreatingDir=安装程序无法创建目录“%1”
|
||||||
|
ErrorTooManyFilesInDir=无法在目录“%1”中创建文件,因为里面包含太多文件
|
||||||
|
|
||||||
|
; *** 安装程序公共消息
|
||||||
|
ExitSetupTitle=退出安装程序
|
||||||
|
ExitSetupMessage=安装程序尚未完成。如果现在退出,将不会安装该程序。%n%n您之后可以再次运行安装程序完成安装。%n%n现在退出安装程序吗?
|
||||||
|
AboutSetupMenuItem=关于安装程序(&A)...
|
||||||
|
AboutSetupTitle=关于安装程序
|
||||||
|
AboutSetupMessage=%1 版本 %2%n%3%n%n%1 主页:%n%4
|
||||||
|
AboutSetupNote=
|
||||||
|
TranslatorNote=简体中文翻译由Kira(847320916@qq.com)维护。项目地址:https://github.com/kira-96/Inno-Setup-Chinese-Simplified-Translation
|
||||||
|
|
||||||
|
; *** 按钮
|
||||||
|
ButtonBack=< 上一步(&B)
|
||||||
|
ButtonNext=下一步(&N) >
|
||||||
|
ButtonInstall=安装(&I)
|
||||||
|
ButtonOK=确定
|
||||||
|
ButtonCancel=取消
|
||||||
|
ButtonYes=是(&Y)
|
||||||
|
ButtonYesToAll=全是(&A)
|
||||||
|
ButtonNo=否(&N)
|
||||||
|
ButtonNoToAll=全否(&O)
|
||||||
|
ButtonFinish=完成(&F)
|
||||||
|
ButtonBrowse=浏览(&B)...
|
||||||
|
ButtonWizardBrowse=浏览(&R)...
|
||||||
|
ButtonNewFolder=新建文件夹(&M)
|
||||||
|
|
||||||
|
; *** “选择语言”对话框消息
|
||||||
|
SelectLanguageTitle=选择安装语言
|
||||||
|
SelectLanguageLabel=选择安装时使用的语言。
|
||||||
|
|
||||||
|
; *** 公共向导文字
|
||||||
|
ClickNext=点击“下一步”继续,或点击“取消”退出安装程序。
|
||||||
|
BeveledLabel=
|
||||||
|
BrowseDialogTitle=浏览文件夹
|
||||||
|
BrowseDialogLabel=在下面的列表中选择一个文件夹,然后点击“确定”。
|
||||||
|
NewFolderName=新建文件夹
|
||||||
|
|
||||||
|
; *** “欢迎”向导页
|
||||||
|
WelcomeLabel1=欢迎使用 [name] 安装向导
|
||||||
|
WelcomeLabel2=现在将安装 [name/ver] 到您的电脑中。%n%n建议您在继续安装前关闭所有其他应用程序。
|
||||||
|
|
||||||
|
; *** “密码”向导页
|
||||||
|
WizardPassword=密码
|
||||||
|
PasswordLabel1=这个安装程序有密码保护。
|
||||||
|
PasswordLabel3=请输入密码,然后点击“下一步”继续。密码区分大小写。
|
||||||
|
PasswordEditLabel=密码(&P):
|
||||||
|
IncorrectPassword=您输入的密码不正确,请重新输入。
|
||||||
|
|
||||||
|
; *** “许可协议”向导页
|
||||||
|
WizardLicense=许可协议
|
||||||
|
LicenseLabel=请在继续安装前阅读以下重要信息。
|
||||||
|
LicenseLabel3=请仔细阅读下列许可协议。在继续安装前您必须同意这些协议条款。
|
||||||
|
LicenseAccepted=我同意此协议(&A)
|
||||||
|
LicenseNotAccepted=我不同意此协议(&D)
|
||||||
|
|
||||||
|
; *** “信息”向导页
|
||||||
|
WizardInfoBefore=信息
|
||||||
|
InfoBeforeLabel=请在继续安装前阅读以下重要信息。
|
||||||
|
InfoBeforeClickLabel=准备好继续安装后,点击“下一步”。
|
||||||
|
WizardInfoAfter=信息
|
||||||
|
InfoAfterLabel=请在继续安装前阅读以下重要信息。
|
||||||
|
InfoAfterClickLabel=准备好继续安装后,点击“下一步”。
|
||||||
|
|
||||||
|
; *** “用户信息”向导页
|
||||||
|
WizardUserInfo=用户信息
|
||||||
|
UserInfoDesc=请输入您的信息。
|
||||||
|
UserInfoName=用户名(&U):
|
||||||
|
UserInfoOrg=组织(&O):
|
||||||
|
UserInfoSerial=序列号(&S):
|
||||||
|
UserInfoNameRequired=您必须输入用户名。
|
||||||
|
|
||||||
|
; *** “选择目标目录”向导页
|
||||||
|
WizardSelectDir=选择目标位置
|
||||||
|
SelectDirDesc=您想将 [name] 安装在哪里?
|
||||||
|
SelectDirLabel3=安装程序将安装 [name] 到下面的文件夹中。
|
||||||
|
SelectDirBrowseLabel=点击“下一步”继续。如果您想选择其他文件夹,点击“浏览”。
|
||||||
|
DiskSpaceGBLabel=至少需要有 [gb] GB 的可用磁盘空间。
|
||||||
|
DiskSpaceMBLabel=至少需要有 [mb] MB 的可用磁盘空间。
|
||||||
|
CannotInstallToNetworkDrive=安装程序无法安装到一个网络驱动器。
|
||||||
|
CannotInstallToUNCPath=安装程序无法安装到一个 UNC 路径。
|
||||||
|
InvalidPath=您必须输入一个带驱动器卷标的完整路径,例如:%n%nC:\APP%n%n或UNC路径:%n%n\\server\share
|
||||||
|
InvalidDrive=您选定的驱动器或 UNC 共享不存在或不能访问。请选择其他位置。
|
||||||
|
DiskSpaceWarningTitle=磁盘空间不足
|
||||||
|
DiskSpaceWarning=安装程序至少需要 %1 KB 的可用空间才能安装,但选定驱动器只有 %2 KB 的可用空间。%n%n您一定要继续吗?
|
||||||
|
DirNameTooLong=文件夹名称或路径太长。
|
||||||
|
InvalidDirName=文件夹名称无效。
|
||||||
|
BadDirName32=文件夹名称不能包含下列任何字符:%n%n%1
|
||||||
|
DirExistsTitle=文件夹已存在
|
||||||
|
DirExists=文件夹:%n%n%1%n%n已经存在。您一定要安装到这个文件夹中吗?
|
||||||
|
DirDoesntExistTitle=文件夹不存在
|
||||||
|
DirDoesntExist=文件夹:%n%n%1%n%n不存在。您想要创建此文件夹吗?
|
||||||
|
|
||||||
|
; *** “选择组件”向导页
|
||||||
|
WizardSelectComponents=选择组件
|
||||||
|
SelectComponentsDesc=您想安装哪些程序组件?
|
||||||
|
SelectComponentsLabel2=选中您想安装的组件;取消您不想安装的组件。然后点击“下一步”继续。
|
||||||
|
FullInstallation=完全安装
|
||||||
|
; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language)
|
||||||
|
CompactInstallation=简洁安装
|
||||||
|
CustomInstallation=自定义安装
|
||||||
|
NoUninstallWarningTitle=组件已存在
|
||||||
|
NoUninstallWarning=安装程序检测到下列组件已安装在您的电脑中:%n%n%1%n%n取消选中这些组件不会卸载它们。%n%n确定要继续吗?
|
||||||
|
ComponentSize1=%1 KB
|
||||||
|
ComponentSize2=%1 MB
|
||||||
|
ComponentsDiskSpaceGBLabel=当前选择的组件需要至少 [gb] GB 的磁盘空间。
|
||||||
|
ComponentsDiskSpaceMBLabel=当前选择的组件需要至少 [mb] MB 的磁盘空间。
|
||||||
|
|
||||||
|
; *** “选择附加任务”向导页
|
||||||
|
WizardSelectTasks=选择附加任务
|
||||||
|
SelectTasksDesc=您想要安装程序执行哪些附加任务?
|
||||||
|
SelectTasksLabel2=选择您想要安装程序在安装 [name] 时执行的附加任务,然后点击“下一步”。
|
||||||
|
|
||||||
|
; *** “选择开始菜单文件夹”向导页
|
||||||
|
WizardSelectProgramGroup=选择开始菜单文件夹
|
||||||
|
SelectStartMenuFolderDesc=安装程序应该在哪里放置程序的快捷方式?
|
||||||
|
SelectStartMenuFolderLabel3=安装程序将在下列“开始”菜单文件夹中创建程序的快捷方式。
|
||||||
|
SelectStartMenuFolderBrowseLabel=点击“下一步”继续。如果您想选择其他文件夹,点击“浏览”。
|
||||||
|
MustEnterGroupName=您必须输入一个文件夹名。
|
||||||
|
GroupNameTooLong=文件夹名或路径太长。
|
||||||
|
InvalidGroupName=无效的文件夹名字。
|
||||||
|
BadGroupName=文件夹名不能包含下列任何字符:%n%n%1
|
||||||
|
NoProgramGroupCheck2=不创建开始菜单文件夹(&D)
|
||||||
|
|
||||||
|
; *** “准备安装”向导页
|
||||||
|
WizardReady=准备安装
|
||||||
|
ReadyLabel1=安装程序准备就绪,现在可以开始安装 [name] 到您的电脑。
|
||||||
|
ReadyLabel2a=点击“安装”继续此安装程序。如果您想重新考虑或修改任何设置,点击“上一步”。
|
||||||
|
ReadyLabel2b=点击“安装”继续此安装程序。
|
||||||
|
ReadyMemoUserInfo=用户信息:
|
||||||
|
ReadyMemoDir=目标位置:
|
||||||
|
ReadyMemoType=安装类型:
|
||||||
|
ReadyMemoComponents=已选择组件:
|
||||||
|
ReadyMemoGroup=开始菜单文件夹:
|
||||||
|
ReadyMemoTasks=附加任务:
|
||||||
|
|
||||||
|
; *** TExtractionWizardPage wizard page and Extract7ZipArchive
|
||||||
|
ExtractionLabel=正在提取附加文件...
|
||||||
|
ButtonStopExtraction=停止提取(&S)
|
||||||
|
StopExtraction=您确定要停止提取吗?
|
||||||
|
ErrorExtractionAborted=提取已中止
|
||||||
|
ErrorExtractionFailed=提取失败:%1
|
||||||
|
|
||||||
|
; *** TDownloadWizardPage wizard page and DownloadTemporaryFile
|
||||||
|
DownloadingLabel=正在下载附加文件...
|
||||||
|
ButtonStopDownload=停止下载(&S)
|
||||||
|
StopDownload=您确定要停止下载吗?
|
||||||
|
ErrorDownloadAborted=下载已中止
|
||||||
|
ErrorDownloadFailed=下载失败:%1 %2
|
||||||
|
ErrorDownloadSizeFailed=获取下载大小失败:%1 %2
|
||||||
|
ErrorFileHash1=校验文件哈希失败:%1
|
||||||
|
ErrorFileHash2=无效的文件哈希:预期 %1,实际 %2
|
||||||
|
ErrorProgress=无效的进度:%1 / %2
|
||||||
|
ErrorFileSize=文件大小错误:预期 %1,实际 %2
|
||||||
|
|
||||||
|
; *** “正在准备安装”向导页
|
||||||
|
WizardPreparing=正在准备安装
|
||||||
|
PreparingDesc=安装程序正在准备安装 [name] 到您的电脑。
|
||||||
|
PreviousInstallNotCompleted=先前的程序安装或卸载未完成,您需要重启您的电脑以完成。%n%n在重启电脑后,再次运行安装程序以完成 [name] 的安装。
|
||||||
|
CannotContinue=安装程序不能继续。请点击“取消”退出。
|
||||||
|
ApplicationsFound=以下应用程序正在使用将由安装程序更新的文件。建议您允许安装程序自动关闭这些应用程序。
|
||||||
|
ApplicationsFound2=以下应用程序正在使用将由安装程序更新的文件。建议您允许安装程序自动关闭这些应用程序。安装完成后,安装程序将尝试重新启动这些应用程序。
|
||||||
|
CloseApplications=自动关闭应用程序(&A)
|
||||||
|
DontCloseApplications=不要关闭应用程序(&D)
|
||||||
|
ErrorCloseApplications=安装程序无法自动关闭所有应用程序。建议您在继续之前,关闭所有在使用需要由安装程序更新的文件的应用程序。
|
||||||
|
PrepareToInstallNeedsRestart=安装程序必须重启您的计算机。计算机重启后,请再次运行安装程序以完成 [name] 的安装。%n%n是否立即重新启动?
|
||||||
|
|
||||||
|
; *** “正在安装”向导页
|
||||||
|
WizardInstalling=正在安装
|
||||||
|
InstallingLabel=安装程序正在安装 [name] 到您的电脑,请稍候。
|
||||||
|
|
||||||
|
; *** “安装完成”向导页
|
||||||
|
FinishedHeadingLabel=[name] 安装完成
|
||||||
|
FinishedLabelNoIcons=安装程序已在您的电脑中安装了 [name]。
|
||||||
|
FinishedLabel=安装程序已在您的电脑中安装了 [name]。您可以通过已安装的快捷方式运行此应用程序。
|
||||||
|
ClickFinish=点击“完成”退出安装程序。
|
||||||
|
FinishedRestartLabel=为完成 [name] 的安装,安装程序必须重新启动您的电脑。要立即重启吗?
|
||||||
|
FinishedRestartMessage=为完成 [name] 的安装,安装程序必须重新启动您的电脑。%n%n要立即重启吗?
|
||||||
|
ShowReadmeCheck=是,我想查阅自述文件
|
||||||
|
YesRadio=是,立即重启电脑(&Y)
|
||||||
|
NoRadio=否,稍后重启电脑(&N)
|
||||||
|
; used for example as 'Run MyProg.exe'
|
||||||
|
RunEntryExec=运行 %1
|
||||||
|
; used for example as 'View Readme.txt'
|
||||||
|
RunEntryShellExec=查阅 %1
|
||||||
|
|
||||||
|
; *** “安装程序需要下一张磁盘”提示
|
||||||
|
ChangeDiskTitle=安装程序需要下一张磁盘
|
||||||
|
SelectDiskLabel2=请插入磁盘 %1 并点击“确定”。%n%n如果这个磁盘中的文件可以在下列文件夹之外的文件夹中找到,请输入正确的路径或点击“浏览”。
|
||||||
|
PathLabel=路径(&P):
|
||||||
|
FileNotInDir2=“%2”中找不到文件“%1”。请插入正确的磁盘或选择其他文件夹。
|
||||||
|
SelectDirectoryLabel=请指定下一张磁盘的位置。
|
||||||
|
|
||||||
|
; *** 安装状态消息
|
||||||
|
SetupAborted=安装程序未完成安装。%n%n请修正这个问题并重新运行安装程序。
|
||||||
|
AbortRetryIgnoreSelectAction=选择操作
|
||||||
|
AbortRetryIgnoreRetry=重试(&T)
|
||||||
|
AbortRetryIgnoreIgnore=忽略错误并继续(&I)
|
||||||
|
AbortRetryIgnoreCancel=关闭安装程序
|
||||||
|
|
||||||
|
; *** 安装状态消息
|
||||||
|
StatusClosingApplications=正在关闭应用程序...
|
||||||
|
StatusCreateDirs=正在创建目录...
|
||||||
|
StatusExtractFiles=正在解压缩文件...
|
||||||
|
StatusCreateIcons=正在创建快捷方式...
|
||||||
|
StatusCreateIniEntries=正在创建 INI 条目...
|
||||||
|
StatusCreateRegistryEntries=正在创建注册表条目...
|
||||||
|
StatusRegisterFiles=正在注册文件...
|
||||||
|
StatusSavingUninstall=正在保存卸载信息...
|
||||||
|
StatusRunProgram=正在完成安装...
|
||||||
|
StatusRestartingApplications=正在重启应用程序...
|
||||||
|
StatusRollback=正在撤销更改...
|
||||||
|
|
||||||
|
; *** 其他错误
|
||||||
|
ErrorInternal2=内部错误:%1
|
||||||
|
ErrorFunctionFailedNoCode=%1 失败
|
||||||
|
ErrorFunctionFailed=%1 失败;错误代码 %2
|
||||||
|
ErrorFunctionFailedWithMessage=%1 失败;错误代码 %2.%n%3
|
||||||
|
ErrorExecutingProgram=无法执行文件:%n%1
|
||||||
|
|
||||||
|
; *** 注册表错误
|
||||||
|
ErrorRegOpenKey=打开注册表项时出错:%n%1\%2
|
||||||
|
ErrorRegCreateKey=创建注册表项时出错:%n%1\%2
|
||||||
|
ErrorRegWriteKey=写入注册表项时出错:%n%1\%2
|
||||||
|
|
||||||
|
; *** INI 错误
|
||||||
|
ErrorIniEntry=在文件“%1”中创建 INI 条目时出错。
|
||||||
|
|
||||||
|
; *** 文件复制错误
|
||||||
|
FileAbortRetryIgnoreSkipNotRecommended=跳过此文件(&S) (不推荐)
|
||||||
|
FileAbortRetryIgnoreIgnoreNotRecommended=忽略错误并继续(&I) (不推荐)
|
||||||
|
SourceIsCorrupted=源文件已损坏
|
||||||
|
SourceDoesntExist=源文件“%1”不存在
|
||||||
|
ExistingFileReadOnly2=无法替换现有文件,它是只读的。
|
||||||
|
ExistingFileReadOnlyRetry=移除只读属性并重试(&R)
|
||||||
|
ExistingFileReadOnlyKeepExisting=保留现有文件(&K)
|
||||||
|
ErrorReadingExistingDest=尝试读取现有文件时出错:
|
||||||
|
FileExistsSelectAction=选择操作
|
||||||
|
FileExists2=文件已经存在。
|
||||||
|
FileExistsOverwriteExisting=覆盖已存在的文件(&O)
|
||||||
|
FileExistsKeepExisting=保留现有的文件(&K)
|
||||||
|
FileExistsOverwriteOrKeepAll=为所有冲突文件执行此操作(&D)
|
||||||
|
ExistingFileNewerSelectAction=选择操作
|
||||||
|
ExistingFileNewer2=现有的文件比安装程序将要安装的文件还要新。
|
||||||
|
ExistingFileNewerOverwriteExisting=覆盖已存在的文件(&O)
|
||||||
|
ExistingFileNewerKeepExisting=保留现有的文件(&K) (推荐)
|
||||||
|
ExistingFileNewerOverwriteOrKeepAll=为所有冲突文件执行此操作(&D)
|
||||||
|
ErrorChangingAttr=尝试更改下列现有文件的属性时出错:
|
||||||
|
ErrorCreatingTemp=尝试在目标目录创建文件时出错:
|
||||||
|
ErrorReadingSource=尝试读取下列源文件时出错:
|
||||||
|
ErrorCopying=尝试复制下列文件时出错:
|
||||||
|
ErrorReplacingExistingFile=尝试替换现有文件时出错:
|
||||||
|
ErrorRestartReplace=重启并替换失败:
|
||||||
|
ErrorRenamingTemp=尝试重命名下列目标目录中的一个文件时出错:
|
||||||
|
ErrorRegisterServer=无法注册 DLL/OCX:%1
|
||||||
|
ErrorRegSvr32Failed=RegSvr32 失败;退出代码 %1
|
||||||
|
ErrorRegisterTypeLib=无法注册类库:%1
|
||||||
|
|
||||||
|
; *** 卸载显示名字标记
|
||||||
|
; used for example as 'My Program (32-bit)'
|
||||||
|
UninstallDisplayNameMark=%1 (%2)
|
||||||
|
; used for example as 'My Program (32-bit, All users)'
|
||||||
|
UninstallDisplayNameMarks=%1 (%2, %3)
|
||||||
|
UninstallDisplayNameMark32Bit=32 位
|
||||||
|
UninstallDisplayNameMark64Bit=64 位
|
||||||
|
UninstallDisplayNameMarkAllUsers=所有用户
|
||||||
|
UninstallDisplayNameMarkCurrentUser=当前用户
|
||||||
|
|
||||||
|
; *** 安装后错误
|
||||||
|
ErrorOpeningReadme=尝试打开自述文件时出错。
|
||||||
|
ErrorRestartingComputer=安装程序无法重启电脑,请手动重启。
|
||||||
|
|
||||||
|
; *** 卸载消息
|
||||||
|
UninstallNotFound=文件“%1”不存在。无法卸载。
|
||||||
|
UninstallOpenError=文件“%1”不能被打开。无法卸载。
|
||||||
|
UninstallUnsupportedVer=此版本的卸载程序无法识别卸载日志文件“%1”的格式。无法卸载
|
||||||
|
UninstallUnknownEntry=卸载日志中遇到一个未知条目 (%1)
|
||||||
|
ConfirmUninstall=您确认要完全移除 %1 及其所有组件吗?
|
||||||
|
UninstallOnlyOnWin64=仅允许在 64 位 Windows 中卸载此程序。
|
||||||
|
OnlyAdminCanUninstall=仅使用管理员权限的用户能完成此卸载。
|
||||||
|
UninstallStatusLabel=正在从您的电脑中移除 %1,请稍候。
|
||||||
|
UninstalledAll=已顺利从您的电脑中移除 %1。
|
||||||
|
UninstalledMost=%1 卸载完成。%n%n有部分内容未能被删除,但您可以手动删除它们。
|
||||||
|
UninstalledAndNeedsRestart=为完成 %1 的卸载,需要重启您的电脑。%n%n立即重启电脑吗?
|
||||||
|
UninstallDataCorrupted=文件“%1”已损坏。无法卸载
|
||||||
|
|
||||||
|
; *** 卸载状态消息
|
||||||
|
ConfirmDeleteSharedFileTitle=删除共享的文件吗?
|
||||||
|
ConfirmDeleteSharedFile2=系统表示下列共享的文件已不有其他程序使用。您希望卸载程序删除这些共享的文件吗?%n%n如果删除这些文件,但仍有程序在使用这些文件,则这些程序可能出现异常。如果您不能确定,请选择“否”,在系统中保留这些文件以免引发问题。
|
||||||
|
SharedFileNameLabel=文件名:
|
||||||
|
SharedFileLocationLabel=位置:
|
||||||
|
WizardUninstalling=卸载状态
|
||||||
|
StatusUninstalling=正在卸载 %1...
|
||||||
|
|
||||||
|
; *** Shutdown block reasons
|
||||||
|
ShutdownBlockReasonInstallingApp=正在安装 %1。
|
||||||
|
ShutdownBlockReasonUninstallingApp=正在卸载 %1。
|
||||||
|
|
||||||
|
; The custom messages below aren't used by Setup itself, but if you make
|
||||||
|
; use of them in your scripts, you'll want to translate them.
|
||||||
|
|
||||||
|
[CustomMessages]
|
||||||
|
|
||||||
|
NameAndVersion=%1 版本 %2
|
||||||
|
AdditionalIcons=附加快捷方式:
|
||||||
|
CreateDesktopIcon=创建桌面快捷方式(&D)
|
||||||
|
CreateQuickLaunchIcon=创建快速启动栏快捷方式(&Q)
|
||||||
|
ProgramOnTheWeb=%1 网站
|
||||||
|
UninstallProgram=卸载 %1
|
||||||
|
LaunchProgram=运行 %1
|
||||||
|
AssocFileExtension=将 %2 文件扩展名与 %1 建立关联(&A)
|
||||||
|
AssocingFileExtension=正在将 %2 文件扩展名与 %1 建立关联...
|
||||||
|
AutoStartProgramGroupDescription=启动:
|
||||||
|
AutoStartProgram=自动启动 %1
|
||||||
|
AddonHostProgramNotFound=您选择的文件夹中无法找到 %1。%n%n您要继续吗?
|
||||||
65
resources/docs/MAA_config_info.txt
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
#主界面
|
||||||
|
"MainFunction.PostActions": "8" #完成后退出MAA
|
||||||
|
"MainFunction.PostActions": "9" #完成后退出MAA和游戏
|
||||||
|
"MainFunction.PostActions": "12" #完成后退出MAA和模拟器
|
||||||
|
"TaskQueue.WakeUp.IsChecked": "True" #开始唤醒
|
||||||
|
"TaskQueue.Recruiting.IsChecked": "True" #自动公招
|
||||||
|
"TaskQueue.Base.IsChecked": "True" #基建换班
|
||||||
|
"TaskQueue.Combat.IsChecked": "True" #刷理智
|
||||||
|
"TaskQueue.Mall.IsChecked": "True" #获取信用及购物
|
||||||
|
"TaskQueue.Mission.IsChecked": "True" #领取奖励
|
||||||
|
"TaskQueue.AutoRoguelike.IsChecked": "False" #自动肉鸽
|
||||||
|
"TaskQueue.Reclamation.IsChecked": "False" #生息演算
|
||||||
|
"TaskQueue.Order.WakeUp": "0"
|
||||||
|
"TaskQueue.Order.Recruiting": "1"
|
||||||
|
"TaskQueue.Order.Base": "2"
|
||||||
|
"TaskQueue.Order.Combat": "3"
|
||||||
|
"TaskQueue.Order.Mall": "4"
|
||||||
|
"TaskQueue.Order.Mission": "5"
|
||||||
|
"TaskQueue.Order.AutoRoguelike": "6"
|
||||||
|
"TaskQueue.Order.Reclamation": "7"
|
||||||
|
#刷理智
|
||||||
|
"MainFunction.UseMedicine": "True" #吃理智药
|
||||||
|
"MainFunction.UseMedicine.Quantity": "999" #吃理智药数量
|
||||||
|
"MainFunction.Stage1": "" #主关卡
|
||||||
|
"MainFunction.Stage2": "" #备选关卡1
|
||||||
|
"MainFunction.Stage3": "" #备选关卡2
|
||||||
|
"MainFunction.Stage4": "" #备选关卡3
|
||||||
|
"Fight.RemainingSanityStage": "Annihilation" #剩余理智关卡
|
||||||
|
"MainFunction.Series.Quantity": "1" #连战次数
|
||||||
|
"MainFunction.Annihilation.UseCustom": "True" #自定义剿灭关卡
|
||||||
|
"MainFunction.Annihilation.Stage": "Annihilation"、"Chernobog@Annihilation"、"LungmenOutskirts@Annihilation"、"LungmenDowntown@Annihilation" #自定义剿灭关卡号
|
||||||
|
"Penguin.IsDrGrandet": "True" #博朗台模式
|
||||||
|
"GUI.CustomStageCode": "False" #手动输入关卡名
|
||||||
|
"GUI.UseAlternateStage": "False" #使用备选关卡
|
||||||
|
"Fight.UseRemainingSanityStage": "True" #使用剩余理智
|
||||||
|
"GUI.AllowUseStoneSave": "False" #允许吃源石保持状态
|
||||||
|
"Fight.UseExpiringMedicine": "False" #无限吃48小时内过期的理智药
|
||||||
|
"GUI.HideUnavailableStage": "False" #隐藏当日不开放关卡
|
||||||
|
"GUI.HideSeries": "False" #隐藏连战次数
|
||||||
|
"Infrast.CustomInfrastPlanShowInFightSettings": "False" #显示基建计划
|
||||||
|
"Penguin.EnablePenguin": "True" #上报企鹅物流
|
||||||
|
"Yituliu.EnableYituliu": "True" #上报一图流
|
||||||
|
#基建换班
|
||||||
|
"Infrast.InfrastMode": "Normal"、"Rotation"、"Custom" #基建模式
|
||||||
|
"Infrast.CustomInfrastPlanIndex": "1" #自定义基建配置索引号
|
||||||
|
"Infrast.DefaultInfrast": "user_defined" #内置配置
|
||||||
|
"Infrast.IsCustomInfrastFileReadOnly": "False" #自定义基建配置文件只读
|
||||||
|
"Infrast.CustomInfrastFile": "" #自定义基建配置文件地址
|
||||||
|
#设置
|
||||||
|
"Start.ClientType": "Official"、"Bilibili"、"YoStarEN"、"YoStarJP"、"YoStarKR"、"txwy" #服务器
|
||||||
|
G"Timer.Timer1": "False" #时间设置1
|
||||||
|
"Connect.AdbPath" #ADB路径
|
||||||
|
"Connect.Address": "127.0.0.1:16448" #连接地址
|
||||||
|
G"VersionUpdate.ScheduledUpdateCheck": "True" #定时检查更新
|
||||||
|
G"VersionUpdate.AutoDownloadUpdatePackage": "True" #自动下载更新包
|
||||||
|
G"VersionUpdate.AutoInstallUpdatePackage": "True" #自动安装更新包
|
||||||
|
G"Start.MinimizeDirectly": "True" #启动MAA后直接最小化
|
||||||
|
"Start.RunDirectly": "True" #启动MAA后直接运行
|
||||||
|
"Start.OpenEmulatorAfterLaunch": "True" #启动MAA后自动开启模拟器
|
||||||
|
G"GUI.UseTray": "True" #显示托盘图标
|
||||||
|
G"GUI.MinimizeToTray": "False" #最小化时隐藏至托盘
|
||||||
|
"Start.EmulatorPath" #模拟器路径
|
||||||
|
"Start.EmulatorAddCommand": "-v 2" #附加命令
|
||||||
|
"Start.EmulatorWaitSeconds": "10" #等待模拟器启动时间
|
||||||
|
G"VersionUpdate.package": "MirrorChyanAppv5.15.6.zip" #更新包标识
|
||||||
160
resources/html/MAA_result.html
Normal file
26
resources/html/MAA_six_star.html
Normal file
233
resources/html/MAA_statistics.html
Normal file
160
resources/html/general_result.html
Normal file
200
resources/html/general_statistics.html
Normal file
BIN
resources/icons/AUTO_MAA.ico
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
resources/icons/AUTO_MAA_Updater.ico
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
resources/icons/MirrorChyan.ico
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
resources/images/AUTO_MAA.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
resources/images/Home/BannerDefault.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
resources/images/README/payid.png
Normal file
|
After Width: | Height: | Size: 309 KiB |
BIN
resources/images/notification/six_star.png
Normal file
|
After Width: | Height: | Size: 222 KiB |
BIN
resources/images/notification/test_notify.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
resources/sounds/both/删除用户.wav
Normal file
BIN
resources/sounds/both/删除脚本实例.wav
Normal file
BIN
resources/sounds/both/删除计划表.wav
Normal file
BIN
resources/sounds/both/删除调度队列.wav
Normal file
BIN
resources/sounds/both/欢迎回来.wav
Normal file
BIN
resources/sounds/both/添加用户.wav
Normal file
BIN
resources/sounds/both/添加脚本实例.wav
Normal file
BIN
resources/sounds/both/添加计划表.wav
Normal file
BIN
resources/sounds/both/添加调度队列.wav
Normal file
BIN
resources/sounds/noisy/ADB失败.wav
Normal file
BIN
resources/sounds/noisy/ADB成功.wav
Normal file
BIN
resources/sounds/noisy/MAA在完成任务前中止.wav
Normal file
BIN
resources/sounds/noisy/MAA在完成任务前退出.wav
Normal file
BIN
resources/sounds/noisy/MAA更新.wav
Normal file
BIN
resources/sounds/noisy/MAA未检测到任何模拟器.wav
Normal file
BIN
resources/sounds/noisy/MAA未能正确登录PRTS.wav
Normal file
BIN
resources/sounds/noisy/MAA的ADB连接异常.wav
Normal file
BIN
resources/sounds/noisy/MAA进程超时.wav
Normal file
BIN
resources/sounds/noisy/MAA部分任务执行失败.wav
Normal file
BIN
resources/sounds/noisy/任务开始.wav
Normal file
BIN
resources/sounds/noisy/任务结束.wav
Normal file
BIN
resources/sounds/noisy/公告展示.wav
Normal file
BIN
resources/sounds/noisy/公告通知.wav
Normal file
BIN
resources/sounds/noisy/六星喜报.wav
Normal file
BIN
resources/sounds/noisy/历史记录查询.wav
Normal file
BIN
resources/sounds/noisy/发生异常.wav
Normal file
BIN
resources/sounds/noisy/发生错误.wav
Normal file
BIN
resources/sounds/noisy/子任务失败.wav
Normal file
BIN
resources/sounds/noisy/排查录入.wav
Normal file
BIN
resources/sounds/noisy/排查重试.wav
Normal file
BIN
resources/sounds/noisy/无新版本.wav
Normal file
BIN
resources/sounds/noisy/有新版本.wav
Normal file
BIN
resources/sounds/noisy/森空岛签到失败.wav
Normal file
BIN
resources/sounds/noisy/森空岛签到成功.wav
Normal file
BIN
resources/sounds/simple/任务开始.wav
Normal file
BIN
resources/sounds/simple/任务结束.wav
Normal file
BIN
resources/sounds/simple/公告展示.wav
Normal file
BIN
resources/sounds/simple/公告通知.wav
Normal file
BIN
resources/sounds/simple/历史记录查询.wav
Normal file
BIN
resources/sounds/simple/发生异常.wav
Normal file
BIN
resources/sounds/simple/发生错误.wav
Normal file
BIN
resources/sounds/simple/无新版本.wav
Normal file
BIN
resources/sounds/simple/有新版本.wav
Normal file
63
resources/version.json
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
"main_version": "4.4.1.0",
|
||||||
|
"version_info": {
|
||||||
|
"4.4.1.0": {
|
||||||
|
"新增功能": [
|
||||||
|
"启动时支持直接运行复数调度队列"
|
||||||
|
],
|
||||||
|
"修复BUG": [
|
||||||
|
"修复计划表未能按照鹰历获取关卡号的问题"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"4.4.1.5": {
|
||||||
|
"新增功能": [
|
||||||
|
"适配 MAA 长期开放剿灭关卡",
|
||||||
|
"新增完成任务后自动复原脚本配置",
|
||||||
|
"通用脚本启动附加命令添加额外的语法以适应UI可执行文件与任务可执行文件不同的情况",
|
||||||
|
"新增 Go_Updater 独立更新器"
|
||||||
|
],
|
||||||
|
"程序优化": [
|
||||||
|
"优化调度队列配置逻辑",
|
||||||
|
"优化静默进程标记逻辑,避免未能及时移除导致相关功能持续开启",
|
||||||
|
"MAA 代理时更新改为强制开启",
|
||||||
|
"移除 MAA 详细配置模式中的剿灭项"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"4.4.1.4": {
|
||||||
|
"修复BUG": [
|
||||||
|
"添加强制关机功能并优化关机流程"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"4.4.1.3": {
|
||||||
|
"修复BUG": [
|
||||||
|
"移除崩溃弹窗机制"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"4.4.1.2": {
|
||||||
|
"新增功能": [
|
||||||
|
"AUTO_MAA 配置分享中心上线"
|
||||||
|
],
|
||||||
|
"修复BUG": [
|
||||||
|
"日志读取添加兜底机制",
|
||||||
|
"修复 QTimer.singleShot 参数问题"
|
||||||
|
],
|
||||||
|
"程序优化": [
|
||||||
|
"小文件配置信息转移至AUTO_MAA自建服务"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"4.4.1.1": {
|
||||||
|
"新增功能": [
|
||||||
|
"通用脚本支持在选定的时机自动更新配置文件"
|
||||||
|
],
|
||||||
|
"修复BUG": [
|
||||||
|
"修复MAA掉落物统计功能",
|
||||||
|
"修复模拟器界面被异常关闭且无法重新打开的问题"
|
||||||
|
],
|
||||||
|
"程序优化": [
|
||||||
|
"重构日志记录,载入更多日志记录项",
|
||||||
|
"优化日志监看启停逻辑",
|
||||||
|
"SpinBox和TimeEdit组件忽视滚轮事件"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||