feat: 载入各种服务

This commit is contained in:
DLmaster361
2025-08-05 22:50:07 +08:00
parent 4ca7f9053f
commit 6898e548a5
9 changed files with 290 additions and 527 deletions

View File

@@ -39,6 +39,9 @@ from utils import get_logger
from models.ConfigBase import * from models.ConfigBase import *
logger = get_logger("配置管理")
class GlobalConfig(ConfigBase): class GlobalConfig(ConfigBase):
"""全局配置""" """全局配置"""
@@ -99,8 +102,6 @@ class GlobalConfig(ConfigBase):
Notify_ToAddress = ConfigItem("Notify", "ToAddress", "") Notify_ToAddress = ConfigItem("Notify", "ToAddress", "")
Notify_IfServerChan = ConfigItem("Notify", "IfServerChan", False, BoolValidator()) Notify_IfServerChan = ConfigItem("Notify", "IfServerChan", False, BoolValidator())
Notify_ServerChanKey = ConfigItem("Notify", "ServerChanKey", "") Notify_ServerChanKey = ConfigItem("Notify", "ServerChanKey", "")
Notify_ServerChanChannel = ConfigItem("Notify", "ServerChanChannel", "")
Notify_ServerChanTag = ConfigItem("Notify", "ServerChanTag", "")
Notify_IfCompanyWebHookBot = ConfigItem( Notify_IfCompanyWebHookBot = ConfigItem(
"Notify", "IfCompanyWebHookBot", False, BoolValidator() "Notify", "IfCompanyWebHookBot", False, BoolValidator()
) )
@@ -284,8 +285,6 @@ class MaaUserConfig(ConfigBase):
"Notify", "IfServerChan", False, BoolValidator() "Notify", "IfServerChan", False, BoolValidator()
) )
self.Notify_ServerChanKey = ConfigItem("Notify", "ServerChanKey", "") self.Notify_ServerChanKey = ConfigItem("Notify", "ServerChanKey", "")
self.Notify_ServerChanChannel = ConfigItem("Notify", "ServerChanChannel", "")
self.Notify_ServerChanTag = ConfigItem("Notify", "ServerChanTag", "")
self.Notify_IfCompanyWebHookBot = ConfigItem( self.Notify_IfCompanyWebHookBot = ConfigItem(
"Notify", "IfCompanyWebHookBot", False, BoolValidator() "Notify", "IfCompanyWebHookBot", False, BoolValidator()
) )
@@ -471,8 +470,6 @@ class GeneralUserConfig(ConfigBase):
"Notify", "IfServerChan", False, BoolValidator() "Notify", "IfServerChan", False, BoolValidator()
) )
self.Notify_ServerChanKey = ConfigItem("Notify", "ServerChanKey", "") self.Notify_ServerChanKey = ConfigItem("Notify", "ServerChanKey", "")
self.Notify_ServerChanChannel = ConfigItem("Notify", "ServerChanChannel", "")
self.Notify_ServerChanTag = ConfigItem("Notify", "ServerChanTag", "")
self.Notify_IfCompanyWebHookBot = ConfigItem( self.Notify_IfCompanyWebHookBot = ConfigItem(
"Notify", "IfCompanyWebHookBot", False, BoolValidator() "Notify", "IfCompanyWebHookBot", False, BoolValidator()
) )
@@ -596,13 +593,12 @@ class AppConfig(GlobalConfig):
self.power_sign = "NoAction" self.power_sign = "NoAction"
self.if_ignore_silence = False self.if_ignore_silence = False
self.logger = get_logger("配置管理") logger.info("")
self.logger.info("") logger.info("===================================")
self.logger.info("===================================") logger.info("AUTO_MAA 后端应用程序")
self.logger.info("AUTO_MAA 后端应用程序") logger.info(f"版本号: v{self.VERSION}")
self.logger.info(f"版本号 v{self.VERSION}") logger.info(f"根目录 {self.root_path}")
self.logger.info(f"根目录: {self.root_path}") logger.info("===================================")
self.logger.info("===================================")
# 检查目录 # 检查目录
self.log_path.parent.mkdir(parents=True, exist_ok=True) self.log_path.parent.mkdir(parents=True, exist_ok=True)
@@ -623,21 +619,21 @@ class AppConfig(GlobalConfig):
await self.QueueConfig.connect(self.config_path / "QueueConfig.json") await self.QueueConfig.connect(self.config_path / "QueueConfig.json")
# self.check_data() # self.check_data()
self.logger.info("程序初始化完成") logger.info("程序初始化完成")
async def add_script( async def add_script(
self, script: Literal["MAA", "General"] self, script: Literal["MAA", "General"]
) -> tuple[uuid.UUID, ConfigBase]: ) -> tuple[uuid.UUID, ConfigBase]:
"""添加脚本配置""" """添加脚本配置"""
self.logger.info(f"添加脚本配置:{script}") logger.info(f"添加脚本配置:{script}")
return await self.ScriptConfig.add(self.CLASS_BOOK[script]) return await self.ScriptConfig.add(self.CLASS_BOOK[script])
async def get_script(self, script_id: Optional[str]) -> tuple[list, dict]: async def get_script(self, script_id: Optional[str]) -> tuple[list, dict]:
"""获取脚本配置""" """获取脚本配置"""
self.logger.info(f"获取脚本配置:{script_id}") logger.info(f"获取脚本配置:{script_id}")
if script_id is None: if script_id is None:
data = await self.ScriptConfig.toDict() data = await self.ScriptConfig.toDict()
@@ -653,15 +649,13 @@ class AppConfig(GlobalConfig):
) -> None: ) -> None:
"""更新脚本配置""" """更新脚本配置"""
self.logger.info(f"更新脚本配置:{script_id}") logger.info(f"更新脚本配置:{script_id}")
uid = uuid.UUID(script_id) uid = uuid.UUID(script_id)
for group, items in data.items(): for group, items in data.items():
for name, value in items.items(): for name, value in items.items():
self.logger.debug( logger.debug(f"更新脚本配置:{script_id} - {group}.{name} = {value}")
f"更新脚本配置:{script_id} - {group}.{name} = {value}"
)
await self.ScriptConfig[uid].set(group, name, value) await self.ScriptConfig[uid].set(group, name, value)
await self.ScriptConfig.save() await self.ScriptConfig.save()
@@ -669,21 +663,21 @@ class AppConfig(GlobalConfig):
async def del_script(self, script_id: str) -> None: async def del_script(self, script_id: str) -> None:
"""删除脚本配置""" """删除脚本配置"""
self.logger.info(f"删除脚本配置:{script_id}") logger.info(f"删除脚本配置:{script_id}")
await self.ScriptConfig.remove(uuid.UUID(script_id)) await self.ScriptConfig.remove(uuid.UUID(script_id))
async def reorder_script(self, index_list: list[str]) -> None: async def reorder_script(self, index_list: list[str]) -> None:
"""重新排序脚本""" """重新排序脚本"""
self.logger.info(f"重新排序脚本:{index_list}") logger.info(f"重新排序脚本:{index_list}")
await self.ScriptConfig.setOrder([uuid.UUID(_) for _ in index_list]) await self.ScriptConfig.setOrder([uuid.UUID(_) for _ in index_list])
async def add_user(self, script_id: str) -> tuple[uuid.UUID, ConfigBase]: async def add_user(self, script_id: str) -> tuple[uuid.UUID, ConfigBase]:
"""添加用户配置""" """添加用户配置"""
self.logger.info(f"{script_id} 添加用户配置") logger.info(f"{script_id} 添加用户配置")
script_config = self.ScriptConfig[uuid.UUID(script_id)] script_config = self.ScriptConfig[uuid.UUID(script_id)]
@@ -702,16 +696,14 @@ class AppConfig(GlobalConfig):
) -> None: ) -> None:
"""更新用户配置""" """更新用户配置"""
self.logger.info(f"{script_id} 更新用户配置:{user_id}") logger.info(f"{script_id} 更新用户配置:{user_id}")
script_config = self.ScriptConfig[uuid.UUID(script_id)] script_config = self.ScriptConfig[uuid.UUID(script_id)]
uid = uuid.UUID(user_id) uid = uuid.UUID(user_id)
for group, items in data.items(): for group, items in data.items():
for name, value in items.items(): for name, value in items.items():
self.logger.debug( logger.debug(f"更新脚本配置:{script_id} - {group}.{name} = {value}")
f"更新脚本配置:{script_id} - {group}.{name} = {value}"
)
if isinstance(script_config, (MaaConfig | GeneralConfig)): if isinstance(script_config, (MaaConfig | GeneralConfig)):
await script_config.UserData[uid].set(group, name, value) await script_config.UserData[uid].set(group, name, value)
@@ -720,7 +712,7 @@ class AppConfig(GlobalConfig):
async def del_user(self, script_id: str, user_id: str) -> None: async def del_user(self, script_id: str, user_id: str) -> None:
"""删除用户配置""" """删除用户配置"""
self.logger.info(f"{script_id} 删除用户配置:{user_id}") logger.info(f"{script_id} 删除用户配置:{user_id}")
script_config = self.ScriptConfig[uuid.UUID(script_id)] script_config = self.ScriptConfig[uuid.UUID(script_id)]
uid = uuid.UUID(user_id) uid = uuid.UUID(user_id)
@@ -732,7 +724,7 @@ class AppConfig(GlobalConfig):
async def reorder_user(self, script_id: str, index_list: list[str]) -> None: async def reorder_user(self, script_id: str, index_list: list[str]) -> None:
"""重新排序用户""" """重新排序用户"""
self.logger.info(f"{script_id} 重新排序用户:{index_list}") logger.info(f"{script_id} 重新排序用户:{index_list}")
script_config = self.ScriptConfig[uuid.UUID(script_id)] script_config = self.ScriptConfig[uuid.UUID(script_id)]
@@ -743,14 +735,14 @@ class AppConfig(GlobalConfig):
async def add_queue(self) -> tuple[uuid.UUID, ConfigBase]: async def add_queue(self) -> tuple[uuid.UUID, ConfigBase]:
"""添加调度队列""" """添加调度队列"""
self.logger.info("添加调度队列") logger.info("添加调度队列")
return await self.QueueConfig.add(QueueConfig) return await self.QueueConfig.add(QueueConfig)
async def get_queue(self, queue_id: Optional[str]) -> tuple[list, dict]: async def get_queue(self, queue_id: Optional[str]) -> tuple[list, dict]:
"""获取调度队列配置""" """获取调度队列配置"""
self.logger.info(f"获取调度队列配置:{queue_id}") logger.info(f"获取调度队列配置:{queue_id}")
if queue_id is None: if queue_id is None:
data = await self.QueueConfig.toDict() data = await self.QueueConfig.toDict()
@@ -766,15 +758,13 @@ class AppConfig(GlobalConfig):
) -> None: ) -> None:
"""更新调度队列配置""" """更新调度队列配置"""
self.logger.info(f"更新调度队列配置:{queue_id}") logger.info(f"更新调度队列配置:{queue_id}")
uid = uuid.UUID(queue_id) uid = uuid.UUID(queue_id)
for group, items in data.items(): for group, items in data.items():
for name, value in items.items(): for name, value in items.items():
self.logger.debug( logger.debug(f"更新调度队列配置:{queue_id} - {group}.{name} = {value}")
f"更新调度队列配置:{queue_id} - {group}.{name} = {value}"
)
await self.QueueConfig[uid].set(group, name, value) await self.QueueConfig[uid].set(group, name, value)
await self.QueueConfig.save() await self.QueueConfig.save()
@@ -782,21 +772,21 @@ class AppConfig(GlobalConfig):
async def del_queue(self, queue_id: str) -> None: async def del_queue(self, queue_id: str) -> None:
"""删除调度队列配置""" """删除调度队列配置"""
self.logger.info(f"删除调度队列配置:{queue_id}") logger.info(f"删除调度队列配置:{queue_id}")
await self.QueueConfig.remove(uuid.UUID(queue_id)) await self.QueueConfig.remove(uuid.UUID(queue_id))
async def reorder_queue(self, index_list: list[str]) -> None: async def reorder_queue(self, index_list: list[str]) -> None:
"""重新排序调度队列""" """重新排序调度队列"""
self.logger.info(f"重新排序调度队列:{index_list}") logger.info(f"重新排序调度队列:{index_list}")
await self.QueueConfig.setOrder([uuid.UUID(_) for _ in index_list]) await self.QueueConfig.setOrder([uuid.UUID(_) for _ in index_list])
async def add_time_set(self, queue_id: str) -> tuple[uuid.UUID, ConfigBase]: async def add_time_set(self, queue_id: str) -> tuple[uuid.UUID, ConfigBase]:
"""添加时间设置配置""" """添加时间设置配置"""
self.logger.info(f"{queue_id} 添加时间设置配置") logger.info(f"{queue_id} 添加时间设置配置")
queue_config = self.QueueConfig[uuid.UUID(queue_id)] queue_config = self.QueueConfig[uuid.UUID(queue_id)]
@@ -813,16 +803,14 @@ class AppConfig(GlobalConfig):
) -> None: ) -> None:
"""更新时间设置配置""" """更新时间设置配置"""
self.logger.info(f"{queue_id} 更新时间设置配置:{time_set_id}") logger.info(f"{queue_id} 更新时间设置配置:{time_set_id}")
queue_config = self.QueueConfig[uuid.UUID(queue_id)] queue_config = self.QueueConfig[uuid.UUID(queue_id)]
uid = uuid.UUID(time_set_id) uid = uuid.UUID(time_set_id)
for group, items in data.items(): for group, items in data.items():
for name, value in items.items(): for name, value in items.items():
self.logger.debug( logger.debug(f"更新时间设置配置:{queue_id} - {group}.{name} = {value}")
f"更新时间设置配置:{queue_id} - {group}.{name} = {value}"
)
if isinstance(queue_config, QueueConfig): if isinstance(queue_config, QueueConfig):
await queue_config.TimeSet[uid].set(group, name, value) await queue_config.TimeSet[uid].set(group, name, value)
@@ -831,7 +819,7 @@ class AppConfig(GlobalConfig):
async def del_time_set(self, queue_id: str, time_set_id: str) -> None: async def del_time_set(self, queue_id: str, time_set_id: str) -> None:
"""删除时间设置配置""" """删除时间设置配置"""
self.logger.info(f"{queue_id} 删除时间设置配置:{time_set_id}") logger.info(f"{queue_id} 删除时间设置配置:{time_set_id}")
queue_config = self.QueueConfig[uuid.UUID(queue_id)] queue_config = self.QueueConfig[uuid.UUID(queue_id)]
uid = uuid.UUID(time_set_id) uid = uuid.UUID(time_set_id)
@@ -843,7 +831,7 @@ class AppConfig(GlobalConfig):
async def reorder_time_set(self, queue_id: str, index_list: list[str]) -> None: async def reorder_time_set(self, queue_id: str, index_list: list[str]) -> None:
"""重新排序时间设置""" """重新排序时间设置"""
self.logger.info(f"{queue_id} 重新排序时间设置:{index_list}") logger.info(f"{queue_id} 重新排序时间设置:{index_list}")
queue_config = self.QueueConfig[uuid.UUID(queue_id)] queue_config = self.QueueConfig[uuid.UUID(queue_id)]
@@ -856,14 +844,14 @@ class AppConfig(GlobalConfig):
) -> tuple[uuid.UUID, ConfigBase]: ) -> tuple[uuid.UUID, ConfigBase]:
"""添加计划表""" """添加计划表"""
self.logger.info(f"添加计划表:{script}") logger.info(f"添加计划表:{script}")
return await self.PlanConfig.add(self.CLASS_BOOK[script]) return await self.PlanConfig.add(self.CLASS_BOOK[script])
async def get_plan(self, plan_id: Optional[str]) -> tuple[list, dict]: async def get_plan(self, plan_id: Optional[str]) -> tuple[list, dict]:
"""获取计划表配置""" """获取计划表配置"""
self.logger.info(f"获取计划表配置:{plan_id}") logger.info(f"获取计划表配置:{plan_id}")
if plan_id is None: if plan_id is None:
data = await self.PlanConfig.toDict() data = await self.PlanConfig.toDict()
@@ -877,15 +865,13 @@ class AppConfig(GlobalConfig):
async def update_plan(self, plan_id: str, data: Dict[str, Dict[str, Any]]) -> None: async def update_plan(self, plan_id: str, data: Dict[str, Dict[str, Any]]) -> None:
"""更新计划表配置""" """更新计划表配置"""
self.logger.info(f"更新计划表配置:{plan_id}") logger.info(f"更新计划表配置:{plan_id}")
uid = uuid.UUID(plan_id) uid = uuid.UUID(plan_id)
for group, items in data.items(): for group, items in data.items():
for name, value in items.items(): for name, value in items.items():
self.logger.debug( logger.debug(f"更新计划表配置:{plan_id} - {group}.{name} = {value}")
f"更新计划表配置:{plan_id} - {group}.{name} = {value}"
)
await self.PlanConfig[uid].set(group, name, value) await self.PlanConfig[uid].set(group, name, value)
await self.PlanConfig.save() await self.PlanConfig.save()
@@ -893,21 +879,21 @@ class AppConfig(GlobalConfig):
async def del_plan(self, plan_id: str) -> None: async def del_plan(self, plan_id: str) -> None:
"""删除计划表配置""" """删除计划表配置"""
self.logger.info(f"删除计划表配置:{plan_id}") logger.info(f"删除计划表配置:{plan_id}")
await self.PlanConfig.remove(uuid.UUID(plan_id)) await self.PlanConfig.remove(uuid.UUID(plan_id))
async def reorder_plan(self, index_list: list[str]) -> None: async def reorder_plan(self, index_list: list[str]) -> None:
"""重新排序计划表""" """重新排序计划表"""
self.logger.info(f"重新排序计划表:{index_list}") logger.info(f"重新排序计划表:{index_list}")
await self.PlanConfig.setOrder([uuid.UUID(_) for _ in index_list]) await self.PlanConfig.setOrder([uuid.UUID(_) for _ in index_list])
async def add_queue_item(self, queue_id: str) -> tuple[uuid.UUID, ConfigBase]: async def add_queue_item(self, queue_id: str) -> tuple[uuid.UUID, ConfigBase]:
"""添加队列项配置""" """添加队列项配置"""
self.logger.info(f"{queue_id} 添加队列项配置") logger.info(f"{queue_id} 添加队列项配置")
queue_config = self.QueueConfig[uuid.UUID(queue_id)] queue_config = self.QueueConfig[uuid.UUID(queue_id)]
@@ -924,7 +910,7 @@ class AppConfig(GlobalConfig):
) -> None: ) -> None:
"""更新队列项配置""" """更新队列项配置"""
self.logger.info(f"{queue_id} 更新队列项配置:{queue_item_id}") logger.info(f"{queue_id} 更新队列项配置:{queue_item_id}")
queue_config = self.QueueConfig[uuid.UUID(queue_id)] queue_config = self.QueueConfig[uuid.UUID(queue_id)]
uid = uuid.UUID(queue_item_id) uid = uuid.UUID(queue_item_id)
@@ -933,9 +919,7 @@ class AppConfig(GlobalConfig):
for name, value in items.items(): for name, value in items.items():
if uuid.UUID(value) not in self.ScriptConfig: if uuid.UUID(value) not in self.ScriptConfig:
raise ValueError(f"Script with uid {value} does not exist.") raise ValueError(f"Script with uid {value} does not exist.")
self.logger.debug( logger.debug(f"更新队列项配置:{queue_id} - {group}.{name} = {value}")
f"更新队列项配置:{queue_id} - {group}.{name} = {value}"
)
if isinstance(queue_config, QueueConfig): if isinstance(queue_config, QueueConfig):
await queue_config.QueueItem[uid].set(group, name, value) await queue_config.QueueItem[uid].set(group, name, value)
@@ -944,7 +928,7 @@ class AppConfig(GlobalConfig):
async def del_queue_item(self, queue_id: str, queue_item_id: str) -> None: async def del_queue_item(self, queue_id: str, queue_item_id: str) -> None:
"""删除队列项配置""" """删除队列项配置"""
self.logger.info(f"{queue_id} 删除队列项配置:{queue_item_id}") logger.info(f"{queue_id} 删除队列项配置:{queue_item_id}")
queue_config = self.QueueConfig[uuid.UUID(queue_id)] queue_config = self.QueueConfig[uuid.UUID(queue_id)]
uid = uuid.UUID(queue_item_id) uid = uuid.UUID(queue_item_id)
@@ -956,7 +940,7 @@ class AppConfig(GlobalConfig):
async def reorder_queue_item(self, queue_id: str, index_list: list[str]) -> None: async def reorder_queue_item(self, queue_id: str, index_list: list[str]) -> None:
"""重新排序队列项""" """重新排序队列项"""
self.logger.info(f"{queue_id} 重新排序队列项:{index_list}") logger.info(f"{queue_id} 重新排序队列项:{index_list}")
queue_config = self.QueueConfig[uuid.UUID(queue_id)] queue_config = self.QueueConfig[uuid.UUID(queue_id)]
@@ -967,18 +951,18 @@ class AppConfig(GlobalConfig):
async def get_setting(self) -> Dict[str, Any]: async def get_setting(self) -> Dict[str, Any]:
"""获取全局设置""" """获取全局设置"""
self.logger.info("获取全局设置") logger.info("获取全局设置")
return await self.toDict(ignore_multi_config=True) return await self.toDict(ignore_multi_config=True)
async def update_setting(self, data: Dict[str, Dict[str, Any]]) -> None: async def update_setting(self, data: Dict[str, Dict[str, Any]]) -> None:
"""更新全局设置""" """更新全局设置"""
self.logger.info(f"更新全局设置") logger.info(f"更新全局设置")
for group, items in data.items(): for group, items in data.items():
for name, value in items.items(): for name, value in items.items():
self.logger.debug(f"更新全局设置 - {group}.{name} = {value}") logger.debug(f"更新全局设置 - {group}.{name} = {value}")
await self.set(group, name, value) await self.set(group, name, value)
def server_date(self) -> date: def server_date(self) -> date:
@@ -994,25 +978,29 @@ class AppConfig(GlobalConfig):
dt = dt - timedelta(days=1) dt = dt - timedelta(days=1)
return dt.date() return dt.date()
def get_proxies(self) -> Dict[str, str]:
"""获取代理设置"""
return {
"http": self.get("Update", "ProxyAddress"),
"https": self.get("Update", "ProxyAddress"),
}
async def get_stage(self) -> tuple[bool, Dict[str, Dict[str, list]]]: async def get_stage(self) -> tuple[bool, Dict[str, Dict[str, list]]]:
"""从MAA服务器更新活动关卡信息""" """从MAA服务器更新活动关卡信息"""
self.logger.info("开始获取活动关卡信息") logger.info("开始获取活动关卡信息")
response = requests.get( response = requests.get(
"https://api.maa.plus/MaaAssistantArknights/api/gui/StageActivity.json", "https://api.maa.plus/MaaAssistantArknights/api/gui/StageActivity.json",
timeout=10, timeout=10,
proxies={ proxies=self.get_proxies(),
"http": self.get("Update", "ProxyAddress"),
"https": self.get("Update", "ProxyAddress"),
},
) )
if response.status_code == 200: if response.status_code == 200:
stage_infos = response.json()["Official"]["sideStoryStage"] stage_infos = response.json()["Official"]["sideStoryStage"]
if_get_maa_stage = True if_get_maa_stage = True
else: else:
self.logger.warning(f"无法从MAA服务器获取活动关卡信息:{response.text}") logger.warning(f"无法从MAA服务器获取活动关卡信息:{response.text}")
if_get_maa_stage = False if_get_maa_stage = False
stage_infos = [] stage_infos = []
@@ -1054,23 +1042,18 @@ class AppConfig(GlobalConfig):
async def get_server_info(self, type: str) -> Dict[str, Any]: async def get_server_info(self, type: str) -> Dict[str, Any]:
"""获取公告信息""" """获取公告信息"""
self.logger.info(f"开始从 AUTO_MAA 服务器获取 {type} 信息") logger.info(f"开始从 AUTO_MAA 服务器获取 {type} 信息")
response = requests.get( response = requests.get(
url=f"http://221.236.27.82:10197/d/AUTO_MAA/Server/{type}.json", url=f"http://221.236.27.82:10197/d/AUTO_MAA/Server/{type}.json",
timeout=10, timeout=10,
proxies={ proxies=self.get_proxies(),
"http": self.get("Update", "ProxyAddress"),
"https": self.get("Update", "ProxyAddress"),
},
) )
if response.status_code == 200: if response.status_code == 200:
return response.json() return response.json()
else: else:
self.logger.warning( logger.warning(f"无法从 AUTO_MAA 服务器获取 {type} 信息:{response.text}")
f"无法从 AUTO_MAA 服务器获取 {type} 信息:{response.text}"
)
raise ConnectionError( raise ConnectionError(
"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."
) )

View File

@@ -26,16 +26,17 @@ from utils import get_logger
from .config import Config from .config import Config
logger = get_logger("主业务定时器")
class _MainTimer: class _MainTimer:
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.logger = get_logger("主业务定时器")
async def second_task(self): async def second_task(self):
"""每秒定期任务""" """每秒定期任务"""
self.logger.info("每秒定期任务启动") logger.info("每秒定期任务启动")
while True: while True:

View File

@@ -18,39 +18,28 @@
# Contact: DLmaster_361@163.com # Contact: DLmaster_361@163.com
"""
AUTO_MAA
AUTO_MAA通知服务
v4.4
作者DLmaster_361
"""
import re import re
import smtplib import smtplib
import time import requests
from email.header import Header from email.header import Header
from email.mime.multipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText from email.mime.text import MIMEText
from email.utils import formataddr from email.utils import formataddr
from pathlib import Path from pathlib import Path
from typing import Union
import requests
from PySide6.QtCore import QObject, Signal
from plyer import notification from plyer import notification
from app.core import Config, logger from core import Config
from app.utils.security import Crypto from utils import get_logger, ImageUtils
from app.utils.ImageUtils import ImageUtils
logger = get_logger("通知服务")
class Notification(QObject): class Notification:
push_info_bar = Signal(str, str, str, int) def __init__(self):
super().__init__()
def __init__(self, parent=None):
super().__init__(parent)
def push_plyer(self, title, message, ticker, t) -> bool: def push_plyer(self, title, message, ticker, t) -> bool:
""" """
@@ -63,19 +52,22 @@ class Notification(QObject):
:return: bool :return: bool
""" """
if Config.get(Config.notify_IfPushPlyer): if Config.get("Notify", "IfPushPlyer"):
logger.info(f"推送系统通知:{title}", module="通知服务") logger.info(f"推送系统通知:{title}")
notification.notify( if notification.notify is not None:
title=title, notification.notify(
message=message, title=title,
app_name="AUTO_MAA", message=message,
app_icon=str(Config.app_path / "resources/icons/AUTO_MAA.ico"), app_name="AUTO_MAA",
timeout=t, app_icon=(Path.cwd() / "resources/icons/AUTO_MAA.ico").as_posix(),
ticker=ticker, timeout=t,
toast=True, ticker=ticker,
) toast=True,
)
else:
logger.error("plyer.notification 未正确导入,无法推送系统通知")
return True return True
@@ -90,12 +82,12 @@ class Notification(QObject):
""" """
if ( if (
Config.get(Config.notify_SMTPServerAddress) == "" Config.get("Notify", "SMTPServerAddress") == ""
or Config.get(Config.notify_AuthorizationCode) == "" or Config.get("Notify", "AuthorizationCode") == ""
or not bool( or not bool(
re.match( re.match(
r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
Config.get(Config.notify_FromAddress), Config.get("Notify", "FromAddress"),
) )
) )
or not bool( or not bool(
@@ -106,336 +98,146 @@ class Notification(QObject):
) )
): ):
logger.error( logger.error(
"请正确设置邮件通知的SMTP服务器地址、授权码、发件人地址和收件人地址", "请正确设置邮件通知的SMTP服务器地址、授权码、发件人地址和收件人地址"
module="通知服务",
) )
self.push_info_bar.emit( raise ValueError(
"error", "The SMTP server address, authorization code, sender address, or recipient address is not set correctly."
"邮件通知推送异常",
"请正确设置邮件通知的SMTP服务器地址、授权码、发件人地址和收件人地址",
-1,
) )
return None
try: # 定义邮件正文
# 定义邮件正文 if mode == "文本":
if mode == "文本": message = MIMEText(content, "plain", "utf-8")
message = MIMEText(content, "plain", "utf-8") elif mode == "网页":
elif mode == "网页": message = MIMEMultipart("alternative")
message = MIMEMultipart("alternative") message["From"] = formataddr(
message["From"] = formataddr( (
( Header("AUTO_MAA通知服务", "utf-8").encode(),
Header("AUTO_MAA通知服务", "utf-8").encode(), Config.get("Notify", "FromAddress"),
Config.get(Config.notify_FromAddress),
)
) # 发件人显示的名字
message["To"] = formataddr(
(Header("AUTO_MAA用户", "utf-8").encode(), to_address)
) # 收件人显示的名字
message["Subject"] = Header(title, "utf-8")
if mode == "网页":
message.attach(MIMEText(content, "html", "utf-8"))
smtpObj = smtplib.SMTP_SSL(Config.get(Config.notify_SMTPServerAddress), 465)
smtpObj.login(
Config.get(Config.notify_FromAddress),
Crypto.win_decryptor(Config.get(Config.notify_AuthorizationCode)),
) )
smtpObj.sendmail( ) # 发件人显示的名字
Config.get(Config.notify_FromAddress), to_address, message.as_string() message["To"] = formataddr(
) (Header("AUTO_MAA用户", "utf-8").encode(), to_address)
smtpObj.quit() ) # 收件人显示的名字
logger.success(f"邮件发送成功:{title}", module="通知服务") message["Subject"] = str(Header(title, "utf-8"))
except Exception as e:
logger.exception(f"发送邮件时出错:{e}", module="通知服务")
self.push_info_bar.emit("error", "发送邮件时出错", f"{e}", -1)
def ServerChanPush( if mode == "网页":
self, title, content, send_key, tag, channel message.attach(MIMEText(content, "html", "utf-8"))
) -> Union[bool, str]:
smtpObj = smtplib.SMTP_SSL(Config.get("Notify", "SMTPServerAddress"), 465)
smtpObj.login(
Config.get("Notify", "FromAddress"),
Config.get("Notify", "AuthorizationCode"),
)
smtpObj.sendmail(
Config.get("Notify", "FromAddress"), to_address, message.as_string()
)
smtpObj.quit()
logger.success(f"邮件发送成功:{title}")
def ServerChanPush(self, title, content, send_key) -> None:
""" """
使用Server酱推送通知 使用Server酱推送通知
:param title: 通知标题 :param title: 通知标题
:param content: 通知内容 :param content: 通知内容
:param send_key: Server酱的SendKey :param send_key: Server酱的SendKey
:param tag: 通知标签
:param channel: 通知频道
:return: bool or str
""" """
if not send_key: if not send_key:
logger.error("请正确设置Server酱的SendKey", module="通知服务") raise ValueError("The ServerChan SendKey can not be empty.")
self.push_info_bar.emit(
"error", "Server酱通知推送异常", "请正确设置Server酱的SendKey", -1
)
return None
try: # 构造 URL
# 构造 URL if send_key.startswith("sctp"):
if send_key.startswith("sctp"): match = re.match(r"^sctp(\d+)t", send_key)
match = re.match(r"^sctp(\d+)t", send_key) if match:
if match: url = f"https://{match.group(1)}.push.ft07.com/send/{send_key}.send"
url = f"https://{match.group(1)}.push.ft07.com/send/{send_key}.send"
else:
raise ValueError("SendKey 格式错误sctp")
else: else:
url = f"https://sctapi.ftqq.com/{send_key}.send" raise ValueError("SendKey format is incorrect (sctp<int>).")
else:
url = f"https://sctapi.ftqq.com/{send_key}.send"
# 构建 tags 和 channel # 请求发送
def is_valid(s): params = {"title": title, "desp": content}
return s == "" or ( headers = {"Content-Type": "application/json;charset=utf-8"}
s == "|".join(s.split("|"))
and (s.count("|") == 0 or all(s.split("|")))
)
tags = "|".join(_.strip() for _ in tag.split("|")) response = requests.post(
channels = "|".join(_.strip() for _ in channel.split("|")) url, json=params, headers=headers, timeout=10, proxies=Config.get_proxies()
)
result = response.json()
options = {} if result.get("code") == 0:
if is_valid(tags): logger.success(f"Server酱推送通知成功{title}")
options["tags"] = tags else:
else: raise Exception(f"ServerChan failed to send notification: {response.text}")
logger.warning("Server酱 Tag 配置不正确,将被忽略", module="通知服务")
self.push_info_bar.emit(
"warning",
"Server酱通知推送异常",
"请正确设置 ServerChan 的 Tag",
-1,
)
if is_valid(channels): def WebHookPush(self, title, content, webhook_url) -> None:
options["channel"] = channels
else:
logger.warning(
"Server酱 Channel 配置不正确,将被忽略", module="通知服务"
)
self.push_info_bar.emit(
"warning",
"Server酱通知推送异常",
"请正确设置 ServerChan 的 Channel",
-1,
)
# 请求发送
params = {"title": title, "desp": content, **options}
headers = {"Content-Type": "application/json;charset=utf-8"}
response = requests.post(
url,
json=params,
headers=headers,
timeout=10,
proxies={
"http": Config.get(Config.update_ProxyAddress),
"https": Config.get(Config.update_ProxyAddress),
},
)
result = response.json()
if result.get("code") == 0:
logger.success(f"Server酱推送通知成功{title}", module="通知服务")
return True
else:
error_code = result.get("code", "-1")
logger.exception(
f"Server酱通知推送失败响应码{error_code}", module="通知服务"
)
self.push_info_bar.emit(
"error", "Server酱通知推送失败", f"响应码:{error_code}", -1
)
return f"Server酱通知推送失败{error_code}"
except Exception as e:
logger.exception(f"Server酱通知推送异常{e}", module="通知服务")
self.push_info_bar.emit(
"error",
"Server酱通知推送异常",
"请检查相关设置和网络连接。如全部配置正确,请稍后再试。",
-1,
)
return f"Server酱通知推送异常{str(e)}"
def CompanyWebHookBotPush(self, title, content, webhook_url) -> Union[bool, str]:
""" """
使用企业微信群机器人推送通知 WebHook 推送通知
:param title: 通知标题 :param title: 通知标题
:param content: 通知内容 :param content: 通知内容
:param webhook_url: 企业微信群机器人的WebHook地址 :param webhook_url: WebHook地址
:return: bool or str
""" """
if webhook_url == "": if not webhook_url:
logger.error("请正确设置企业微信群机器人的WebHook地址", module="通知服务") raise ValueError("The webhook URL can not be empty.")
self.push_info_bar.emit(
"error",
"企业微信群机器人通知推送异常",
"请正确设置企业微信群机器人的WebHook地址",
-1,
)
return None
content = f"{title}\n{content}" content = f"{title}\n{content}"
data = {"msgtype": "text", "text": {"content": content}} data = {"msgtype": "text", "text": {"content": content}}
for _ in range(3): response = requests.post(
try: url=webhook_url, json=data, timeout=10, proxies=Config.get_proxies()
response = requests.post( )
url=webhook_url, info = response.json()
json=data,
timeout=10,
proxies={
"http": Config.get(Config.update_ProxyAddress),
"https": Config.get(Config.update_ProxyAddress),
},
)
info = response.json()
break
except Exception as e:
err = e
time.sleep(0.1)
else:
logger.error(f"推送企业微信群机器人时出错:{err}", module="通知服务")
self.push_info_bar.emit(
"error",
"企业微信群机器人通知推送失败",
f"使用企业微信群机器人推送通知时出错:{err}",
-1,
)
return None
if info["errcode"] == 0: if info["errcode"] == 0:
logger.success(f"企业微信群机器人推送通知成功:{title}", module="通知服务") logger.success(f"WebHook 推送通知成功:{title}")
return True
else: else:
logger.error(f"企业微信群机器人推送通知失败:{info}", module="通知服务") raise Exception(f"WebHook failed to send notification: {response.text}")
self.push_info_bar.emit(
"error",
"企业微信群机器人通知推送失败",
f"使用企业微信群机器人推送通知时出错:{err}",
-1,
)
return f"使用企业微信群机器人推送通知时出错:{err}"
def CompanyWebHookBotPushImage(self, image_path: Path, webhook_url: str) -> bool: def CompanyWebHookBotPushImage(self, image_path: Path, webhook_url: str) -> None:
""" """
使用企业微信群机器人推送图片通知 使用企业微信群机器人推送图片通知
:param image_path: 图片文件路径 :param image_path: 图片文件路径
:param webhook_url: 企业微信群机器人的WebHook地址 :param webhook_url: 企业微信群机器人的WebHook地址
:return: bool
""" """
try: if not webhook_url:
# 压缩图片 raise ValueError("The webhook URL can not be empty.")
ImageUtils.compress_image_if_needed(image_path)
# 检查图片是否存在 # 压缩图片
if not image_path.exists(): ImageUtils.compress_image_if_needed(image_path)
logger.error(
"图片推送异常 | 图片不存在或者压缩失败,请检查图片路径是否正确",
module="通知服务",
)
self.push_info_bar.emit(
"error",
"企业微信群机器人通知推送异常",
"图片不存在或者压缩失败,请检查图片路径是否正确",
-1,
)
return False
if not webhook_url: # 检查图片是否存在
logger.error( if not image_path.exists():
"请正确设置企业微信群机器人的WebHook地址", module="通知服务" raise FileNotFoundError(f"File not found: {image_path}")
)
self.push_info_bar.emit(
"error",
"企业微信群机器人通知推送异常",
"请正确设置企业微信群机器人的WebHook地址",
-1,
)
return False
# 获取图片base64和md5 # 获取图片base64和md5
try: image_base64 = ImageUtils.get_base64_from_file(str(image_path))
image_base64 = ImageUtils.get_base64_from_file(str(image_path)) image_md5 = ImageUtils.calculate_md5_from_file(str(image_path))
image_md5 = ImageUtils.calculate_md5_from_file(str(image_path))
except Exception as e:
logger.exception(f"图片编码或MD5计算失败{e}", module="通知服务")
self.push_info_bar.emit(
"error",
"企业微信群机器人通知推送异常",
f"图片编码或MD5计算失败{e}",
-1,
)
return False
data = { data = {
"msgtype": "image", "msgtype": "image",
"image": {"base64": image_base64, "md5": image_md5}, "image": {"base64": image_base64, "md5": image_md5},
} }
for _ in range(3): response = requests.post(
try: url=webhook_url, json=data, timeout=10, proxies=Config.get_proxies()
response = requests.post( )
url=webhook_url, info = response.json()
json=data,
timeout=10,
proxies={
"http": Config.get(Config.update_ProxyAddress),
"https": Config.get(Config.update_ProxyAddress),
},
)
info = response.json()
break
except requests.RequestException as e:
err = e
logger.exception(
f"推送企业微信群机器人图片第{_+1}次失败:{e}", module="通知服务"
)
time.sleep(0.1)
else:
logger.error("推送企业微信群机器人图片时出错", module="通知服务")
self.push_info_bar.emit(
"error",
"企业微信群机器人图片推送失败",
f"使用企业微信群机器人推送图片时出错:{err}",
-1,
)
return False
if info.get("errcode") == 0: if info.get("errcode") == 0:
logger.success( logger.success(f"企业微信群机器人推送图片成功:{image_path.name}")
f"企业微信群机器人推送图片成功:{image_path.name}", else:
module="通知服务", raise Exception(
) f"Company WebHook Bot failed to send image: {response.text}"
return True
else:
logger.error(f"企业微信群机器人推送图片失败:{info}", module="通知服务")
self.push_info_bar.emit(
"error",
"企业微信群机器人图片推送失败",
f"使用企业微信群机器人推送图片时出错:{info}",
-1,
)
return False
except Exception as e:
logger.error(f"推送企业微信群机器人图片时发生未知异常:{e}")
self.push_info_bar.emit(
"error",
"企业微信群机器人图片推送失败",
f"发生未知异常:{e}",
-1,
) )
return False
def send_test_notification(self): def send_test_notification(self) -> None:
"""发送测试通知到所有已启用的通知渠道""" """发送测试通知到所有已启用的通知渠道"""
logger.info("发送测试通知到所有已启用的通知渠道", module="通知服务") logger.info("发送测试通知到所有已启用的通知渠道")
# 发送系统通知 # 发送系统通知
self.push_plyer( self.push_plyer(
@@ -446,39 +248,35 @@ class Notification(QObject):
) )
# 发送邮件通知 # 发送邮件通知
if Config.get(Config.notify_IfSendMail): if Config.get("Notify", "IfSendMail"):
self.send_mail( self.send_mail(
"文本", "文本",
"AUTO_MAA测试通知", "AUTO_MAA测试通知",
"这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!", "这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!",
Config.get(Config.notify_ToAddress), Config.get("Notify", "ToAddress"),
) )
# 发送Server酱通知 # 发送Server酱通知
if Config.get(Config.notify_IfServerChan): if Config.get("Notify", "IfServerChan"):
self.ServerChanPush( self.ServerChanPush(
"AUTO_MAA测试通知", "AUTO_MAA测试通知",
"这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!", "这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!",
Config.get(Config.notify_ServerChanKey), Config.get("Notify", "ServerChanKey"),
Config.get(Config.notify_ServerChanTag),
Config.get(Config.notify_ServerChanChannel),
) )
# 发送企业微信机器人通知 # 发送WebHook通知
if Config.get(Config.notify_IfCompanyWebHookBot): if Config.get("Notify", "IfCompanyWebHookBot"):
self.CompanyWebHookBotPush( self.WebHookPush(
"AUTO_MAA测试通知", "AUTO_MAA测试通知",
"这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!", "这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!",
Config.get(Config.notify_CompanyWebHookBotUrl), Config.get("Notify", "CompanyWebHookBotUrl"),
) )
Notify.CompanyWebHookBotPushImage( Notify.CompanyWebHookBotPushImage(
Config.app_path / "resources/images/notification/test_notify.png", Path.cwd() / "resources/images/notification/test_notify.png",
Config.get(Config.notify_CompanyWebHookBotUrl), Config.get("Notify", "CompanyWebHookBotUrl"),
) )
logger.info("测试通知发送完成", module="通知服务") logger.success("测试通知发送完成")
return True
Notify = Notification() Notify = Notification()

View File

@@ -18,14 +18,7 @@
# Contact: DLmaster_361@163.com # Contact: DLmaster_361@163.com
"""
AUTO_MAA
AUTO_MAA系统服务
v4.4
作者DLmaster_361
"""
from PySide6.QtWidgets import QApplication
import sys import sys
import ctypes import ctypes
import win32gui import win32gui
@@ -37,7 +30,10 @@ import getpass
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from app.core import Config from core import Config
from utils.logger import get_logger
logger = get_logger("系统服务")
class _SystemHandler: class _SystemHandler:
@@ -53,7 +49,7 @@ class _SystemHandler:
def set_Sleep(self) -> None: def set_Sleep(self) -> None:
"""同步系统休眠状态""" """同步系统休眠状态"""
if Config.get(Config.function_IfAllowSleep): if Config.get("Function", "IfAllowSleep"):
# 设置系统电源状态 # 设置系统电源状态
ctypes.windll.kernel32.SetThreadExecutionState( ctypes.windll.kernel32.SetThreadExecutionState(
self.ES_CONTINUOUS | self.ES_SYSTEM_REQUIRED self.ES_CONTINUOUS | self.ES_SYSTEM_REQUIRED
@@ -65,7 +61,9 @@ class _SystemHandler:
def set_SelfStart(self) -> None: def set_SelfStart(self) -> None:
"""同步开机自启""" """同步开机自启"""
if Config.get(Config.start_IfSelfStart) and not self.is_startup(): return None # 目前不支持开机自启
if Config.get("Function", "IfSelfStart") and not self.is_startup():
# 创建任务计划 # 创建任务计划
try: try:
@@ -164,7 +162,7 @@ class _SystemHandler:
pass pass
except Exception as e: except Exception as e:
logger.exception(f"程序自启动任务计划创建失败: {e}", module="系统服务") logger.exception(f"程序自启动任务计划创建失败: {e}")
elif not Config.get(Config.start_IfSelfStart) and self.is_startup(): elif not Config.get(Config.start_IfSelfStart) and self.is_startup():
@@ -179,7 +177,7 @@ class _SystemHandler:
) )
if result.returncode == 0: if result.returncode == 0:
logger.success("程序自启动任务计划已删除", module="系统服务") logger.success("程序自启动任务计划已删除")
else: else:
logger.error( logger.error(
f"程序自启动任务计划删除失败: {result.stderr}", f"程序自启动任务计划删除失败: {result.stderr}",
@@ -187,7 +185,7 @@ class _SystemHandler:
) )
except Exception as e: except Exception as e:
logger.exception(f"程序自启动任务计划删除失败: {e}", module="系统服务") logger.exception(f"程序自启动任务计划删除失败: {e}")
def set_power(self, mode) -> None: def set_power(self, mode) -> None:
""" """
@@ -200,69 +198,65 @@ class _SystemHandler:
if mode == "NoAction": if mode == "NoAction":
logger.info("不执行系统电源操作", module="系统服务") logger.info("不执行系统电源操作")
elif mode == "Shutdown": elif mode == "Shutdown":
self.kill_emulator_processes() self.kill_emulator_processes()
logger.info("执行关机操作", module="系统服务") logger.info("执行关机操作")
subprocess.run(["shutdown", "/s", "/t", "0"]) subprocess.run(["shutdown", "/s", "/t", "0"])
elif mode == "ShutdownForce": elif mode == "ShutdownForce":
logger.info("执行强制关机操作", module="系统服务") logger.info("执行强制关机操作")
subprocess.run(["shutdown", "/s", "/t", "0", "/f"]) subprocess.run(["shutdown", "/s", "/t", "0", "/f"])
elif mode == "Hibernate": elif mode == "Hibernate":
logger.info("执行休眠操作", module="系统服务") logger.info("执行休眠操作")
subprocess.run(["shutdown", "/h"]) subprocess.run(["shutdown", "/h"])
elif mode == "Sleep": elif mode == "Sleep":
logger.info("执行睡眠操作", module="系统服务") logger.info("执行睡眠操作")
subprocess.run( subprocess.run(
["rundll32.exe", "powrprof.dll,SetSuspendState", "0,1,0"] ["rundll32.exe", "powrprof.dll,SetSuspendState", "0,1,0"]
) )
elif mode == "KillSelf": elif mode == "KillSelf":
logger.info("执行退出主程序操作", module="系统服务") logger.info("执行退出主程序操作")
Config.main_window.close()
QApplication.quit()
sys.exit(0) sys.exit(0)
elif sys.platform.startswith("linux"): elif sys.platform.startswith("linux"):
if mode == "NoAction": if mode == "NoAction":
logger.info("不执行系统电源操作", module="系统服务") logger.info("不执行系统电源操作")
elif mode == "Shutdown": elif mode == "Shutdown":
logger.info("执行关机操作", module="系统服务") logger.info("执行关机操作")
subprocess.run(["shutdown", "-h", "now"]) subprocess.run(["shutdown", "-h", "now"])
elif mode == "Hibernate": elif mode == "Hibernate":
logger.info("执行休眠操作", module="系统服务") logger.info("执行休眠操作")
subprocess.run(["systemctl", "hibernate"]) subprocess.run(["systemctl", "hibernate"])
elif mode == "Sleep": elif mode == "Sleep":
logger.info("执行睡眠操作", module="系统服务") logger.info("执行睡眠操作")
subprocess.run(["systemctl", "suspend"]) subprocess.run(["systemctl", "suspend"])
elif mode == "KillSelf": elif mode == "KillSelf":
logger.info("执行退出主程序操作", module="系统服务") logger.info("执行退出主程序操作")
Config.main_window.close()
QApplication.quit()
sys.exit(0) sys.exit(0)
def kill_emulator_processes(self): def kill_emulator_processes(self):
"""这里暂时仅支持 MuMu 模拟器""" """这里暂时仅支持 MuMu 模拟器"""
logger.info("正在清除模拟器进程", module="系统服务") logger.info("正在清除模拟器进程")
keywords = ["Nemu", "nemu", "emulator", "MuMu"] keywords = ["Nemu", "nemu", "emulator", "MuMu"]
for proc in psutil.process_iter(["pid", "name"]): for proc in psutil.process_iter(["pid", "name"]):
@@ -277,7 +271,7 @@ class _SystemHandler:
except (psutil.NoSuchProcess, psutil.AccessDenied): except (psutil.NoSuchProcess, psutil.AccessDenied):
continue continue
logger.success("模拟器进程清除完成", module="系统服务") logger.success("模拟器进程清除完成")
def is_startup(self) -> bool: def is_startup(self) -> bool:
"""判断程序是否已经开机自启""" """判断程序是否已经开机自启"""
@@ -292,7 +286,7 @@ class _SystemHandler:
) )
return result.returncode == 0 return result.returncode == 0
except Exception as e: except Exception as e:
logger.exception(f"检查任务计划程序失败: {e}", module="系统服务") logger.exception(f"检查任务计划程序失败: {e}")
return False return False
def get_window_info(self) -> list: def get_window_info(self) -> list:
@@ -316,7 +310,7 @@ class _SystemHandler:
:param path: 进程路径 :param path: 进程路径
""" """
logger.info(f"开始中止进程: {path}", module="系统服务") logger.info(f"开始中止进程: {path}")
for pid in self.search_pids(path): for pid in self.search_pids(path):
killprocess = subprocess.Popen( killprocess = subprocess.Popen(
@@ -326,7 +320,7 @@ class _SystemHandler:
) )
killprocess.wait() killprocess.wait()
logger.success(f"进程已中止: {path}", module="系统服务") logger.success(f"进程已中止: {path}")
def search_pids(self, path: Path) -> list: def search_pids(self, path: Path) -> list:
""" """
@@ -336,7 +330,7 @@ class _SystemHandler:
:return: 匹配的进程PID列表 :return: 匹配的进程PID列表
""" """
logger.info(f"开始查找进程 PID: {path}", module="系统服务") logger.info(f"开始查找进程 PID: {path}")
pids = [] pids = []
for proc in psutil.process_iter(["pid", "exe"]): for proc in psutil.process_iter(["pid", "exe"]):

View File

@@ -35,14 +35,18 @@ v4.4
import time import time
import json import json
import hmac import hmac
import asyncio
import hashlib import hashlib
import requests import requests
from urllib import parse from urllib import parse
from app.core import Config, logger from core import Config
from utils.logger import get_logger
logger = get_logger("森空岛签到任务")
def skland_sign_in(token) -> dict: async def skland_sign_in(token) -> dict:
"""森空岛签到""" """森空岛签到"""
app_code = "4ca99fa6b56cc2ba" app_code = "4ca99fa6b56cc2ba"
@@ -127,7 +131,7 @@ def skland_sign_in(token) -> dict:
v["cred"] = cred v["cred"] = cred
return v return v
def login_by_token(token_code): async def login_by_token(token_code):
""" """
使用token一步步拿到cred和sign_token 使用token一步步拿到cred和sign_token
@@ -140,10 +144,10 @@ def skland_sign_in(token) -> dict:
token_code = t["data"]["content"] token_code = t["data"]["content"]
except: except:
pass pass
grant_code = get_grant_code(token_code) grant_code = await get_grant_code(token_code)
return get_cred(grant_code) return await get_cred(grant_code)
def get_cred(grant): async def get_cred(grant):
""" """
通过grant code获取cred和sign_token 通过grant code获取cred和sign_token
@@ -155,10 +159,7 @@ def skland_sign_in(token) -> dict:
cred_code_url, cred_code_url,
json={"code": grant, "kind": 1}, json={"code": grant, "kind": 1},
headers=header_login, headers=header_login,
proxies={ proxies=Config.get_proxies(),
"http": Config.get(Config.update_ProxyAddress),
"https": Config.get(Config.update_ProxyAddress),
},
).json() ).json()
if rsp["code"] != 0: if rsp["code"] != 0:
raise Exception(f'获得cred失败{rsp.get("messgae")}') raise Exception(f'获得cred失败{rsp.get("messgae")}')
@@ -166,7 +167,7 @@ def skland_sign_in(token) -> dict:
cred = rsp["data"]["cred"] cred = rsp["data"]["cred"]
return cred, sign_token return cred, sign_token
def get_grant_code(token): async def get_grant_code(token):
""" """
通过token获取grant code 通过token获取grant code
@@ -177,10 +178,7 @@ def skland_sign_in(token) -> dict:
grant_code_url, grant_code_url,
json={"appCode": app_code, "token": token, "type": 0}, json={"appCode": app_code, "token": token, "type": 0},
headers=header_login, headers=header_login,
proxies={ proxies=Config.get_proxies(),
"http": Config.get(Config.update_ProxyAddress),
"https": Config.get(Config.update_ProxyAddress),
},
).json() ).json()
if rsp["status"] != 0: if rsp["status"] != 0:
raise Exception( raise Exception(
@@ -188,7 +186,7 @@ def skland_sign_in(token) -> dict:
) )
return rsp["data"]["code"] return rsp["data"]["code"]
def get_binding_list(cred, sign_token): async def get_binding_list(cred, sign_token):
""" """
查询已绑定的角色列表 查询已绑定的角色列表
@@ -202,21 +200,12 @@ def skland_sign_in(token) -> dict:
headers=get_sign_header( headers=get_sign_header(
binding_url, "get", None, copy_header(cred), sign_token binding_url, "get", None, copy_header(cred), sign_token
), ),
proxies={ proxies=Config.get_proxies(),
"http": Config.get(Config.update_ProxyAddress),
"https": Config.get(Config.update_ProxyAddress),
},
).json() ).json()
if rsp["code"] != 0: if rsp["code"] != 0:
logger.error( logger.error(f"请求角色列表出现问题:{rsp['message']}")
f"森空岛服务 | 请求角色列表出现问题:{rsp['message']}",
module="森空岛签到",
)
if rsp.get("message") == "用户未登录": if rsp.get("message") == "用户未登录":
logger.error( logger.error(f"用户登录可能失效了,请重新登录!")
f"森空岛服务 | 用户登录可能失效了,请重新登录!",
module="森空岛签到",
)
return v return v
# 只取明日方舟arknights的绑定账号 # 只取明日方舟arknights的绑定账号
for i in rsp["data"]["list"]: for i in rsp["data"]["list"]:
@@ -225,7 +214,7 @@ def skland_sign_in(token) -> dict:
v.extend(i.get("bindingList")) v.extend(i.get("bindingList"))
return v return v
def do_sign(cred, sign_token) -> dict: async def do_sign(cred, sign_token) -> dict:
""" """
对所有绑定的角色进行签到 对所有绑定的角色进行签到
@@ -234,7 +223,7 @@ def skland_sign_in(token) -> dict:
:return: 签到结果字典 :return: 签到结果字典
""" """
characters = get_binding_list(cred, sign_token) characters = await get_binding_list(cred, sign_token)
result = {"成功": [], "重复": [], "失败": [], "总计": len(characters)} result = {"成功": [], "重复": [], "失败": [], "总计": len(characters)}
for character in characters: for character in characters:
@@ -249,10 +238,7 @@ def skland_sign_in(token) -> dict:
sign_url, "post", body, copy_header(cred), sign_token sign_url, "post", body, copy_header(cred), sign_token
), ),
json=body, json=body,
proxies={ proxies=Config.get_proxies(),
"http": Config.get(Config.update_ProxyAddress),
"https": Config.get(Config.update_ProxyAddress),
},
).json() ).json()
if rsp["code"] != 0: if rsp["code"] != 0:
@@ -276,10 +262,10 @@ def skland_sign_in(token) -> dict:
# 主流程 # 主流程
try: try:
# 拿到cred和sign_token # 拿到cred和sign_token
cred, sign_token = login_by_token(token) cred, sign_token = await login_by_token(token)
time.sleep(1) await asyncio.sleep(1)
# 依次签到 # 依次签到
return do_sign(cred, sign_token) return await do_sign(cred, sign_token)
except Exception as e: except Exception as e:
logger.exception(f"森空岛服务 | 森空岛签到失败: {e}", module="森空岛签到") logger.exception(f"森空岛签到失败: {e}")
return {"成功": [], "重复": [], "失败": [], "总计": 0} return {"成功": [], "重复": [], "失败": [], "总计": 0}

View File

@@ -18,12 +18,6 @@
# Contact: DLmaster_361@163.com # Contact: DLmaster_361@163.com
"""
AUTO_MAA
AUTO_MAA图像组件
v4.4
作者ClozyA
"""
import base64 import base64
import hashlib import hashlib
@@ -56,10 +50,8 @@ class ImageUtils:
""" """
如果图片大于max_size_mb则压缩并覆盖原文件返回原始路径Path对象 如果图片大于max_size_mb则压缩并覆盖原文件返回原始路径Path对象
""" """
if hasattr(Image, "Resampling"): # Pillow 9.1.0及以后
RESAMPLE = Image.Resampling.LANCZOS RESAMPLE = Image.Resampling.LANCZOS # Pillow 9.1.0及以后
else:
RESAMPLE = Image.ANTIALIAS
max_size = max_size_mb * 1024 * 1024 max_size = max_size_mb * 1024 * 1024
if image_path.stat().st_size <= max_size: if image_path.stat().st_size <= max_size:
@@ -70,7 +62,7 @@ class ImageUtils:
quality = 90 if suffix in [".jpg", ".jpeg"] else None quality = 90 if suffix in [".jpg", ".jpeg"] else None
step = 5 step = 5
if suffix in [".jpg", ".jpeg"]: if quality is not None:
while True: while True:
img.save(image_path, quality=quality, optimize=True) img.save(image_path, quality=quality, optimize=True)
if image_path.stat().st_size <= max_size or quality <= 10: if image_path.stat().st_size <= max_size or quality <= 10:

View File

@@ -19,36 +19,33 @@
# Contact: DLmaster_361@163.com # Contact: DLmaster_361@163.com
import asyncio
import psutil import psutil
import subprocess import subprocess
from pathlib import Path from pathlib import Path
from datetime import datetime
from PySide6.QtCore import QTimer, QObject, Signal
class ProcessManager(QObject): class ProcessManager:
"""进程监视器类,用于跟踪主进程及其所有子进程的状态""" """进程监视器类,用于跟踪主进程及其所有子进程的状态"""
processClosed = Signal()
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.main_pid = None self.main_pid = None
self.tracked_pids = set() self.tracked_pids = set()
self.check_task = None
self.check_timer = QTimer() async def open_process(
self.check_timer.timeout.connect(self.check_processes) self, path: Path, args: list = [], tracking_time: int = 60
) -> None:
def open_process(self, path: Path, args: list = [], tracking_time: int = 60) -> int:
""" """
启动一个新进程并返回其pid并开始监视该进程 启动一个新进程并返回其pid并开始监视该进程
:param path: 可执行文件的路径 Parameters
:param args: 启动参数列表 ----------
:param tracking_time: 子进程追踪持续时间(秒) path: 可执行文件的路径
:return: 新进程的PID args: 启动参数列表
tracking_time: 子进程追踪持续时间(秒)
""" """
process = subprocess.Popen( process = subprocess.Popen(
@@ -60,9 +57,9 @@ class ProcessManager(QObject):
stderr=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
) )
self.start_monitoring(process.pid, tracking_time) await self.start_monitoring(process.pid, tracking_time)
def start_monitoring(self, pid: int, tracking_time: int = 60) -> None: async def start_monitoring(self, pid: int, tracking_time: int = 60) -> None:
""" """
启动进程监视器,跟踪指定的主进程及其子进程 启动进程监视器,跟踪指定的主进程及其子进程
@@ -70,7 +67,7 @@ class ProcessManager(QObject):
:param tracking_time: 子进程追踪持续时间(秒) :param tracking_time: 子进程追踪持续时间(秒)
""" """
self.clear() await self.clear()
self.main_pid = pid self.main_pid = pid
self.tracking_time = tracking_time self.tracking_time = tracking_time
@@ -89,16 +86,16 @@ class ProcessManager(QObject):
except psutil.NoSuchProcess: except psutil.NoSuchProcess:
pass pass
# 启动持续追踪机制 # 启动持续追踪任务
self.start_time = datetime.now() if tracking_time > 0:
self.check_timer.start(100) self.check_task = asyncio.create_task(self.track_processes())
await asyncio.sleep(tracking_time)
await self.stop_tracking()
def check_processes(self) -> None: async def track_processes(self) -> None:
"""检查跟踪的进程是否仍在运行,并更新子进程列表""" """更新子进程列表"""
# 仅在时限内持续更新跟踪的进程列表,发现新的子进程
if (datetime.now() - self.start_time).total_seconds() < self.tracking_time:
while True:
current_pids = set(self.tracked_pids) current_pids = set(self.tracked_pids)
for pid in current_pids: for pid in current_pids:
try: try:
@@ -109,12 +106,19 @@ class ProcessManager(QObject):
self.tracked_pids.add(child.pid) self.tracked_pids.add(child.pid)
except psutil.NoSuchProcess: except psutil.NoSuchProcess:
continue continue
await asyncio.sleep(0.1)
if not self.is_running(): async def stop_tracking(self) -> None:
self.clear() """停止更新子进程列表"""
self.processClosed.emit()
def is_running(self) -> bool: if self.check_task and not self.check_task.done():
self.check_task.cancel()
try:
await self.check_task
except asyncio.CancelledError:
pass
async def is_running(self) -> bool:
"""检查所有跟踪的进程是否还在运行""" """检查所有跟踪的进程是否还在运行"""
for pid in self.tracked_pids: for pid in self.tracked_pids:
@@ -127,11 +131,9 @@ class ProcessManager(QObject):
return False return False
def kill(self, if_force: bool = False) -> None: async def kill(self, if_force: bool = False) -> None:
"""停止监视器并中止所有跟踪的进程""" """停止监视器并中止所有跟踪的进程"""
self.check_timer.stop()
for pid in self.tracked_pids: for pid in self.tracked_pids:
try: try:
proc = psutil.Process(pid) proc = psutil.Process(pid)
@@ -145,13 +147,11 @@ class ProcessManager(QObject):
except psutil.NoSuchProcess: except psutil.NoSuchProcess:
continue continue
if self.main_pid: await self.clear()
self.processClosed.emit()
self.clear()
def clear(self) -> None: async def clear(self) -> None:
"""清空跟踪的进程列表""" """清空跟踪的进程列表"""
await self.stop_tracking()
self.main_pid = None self.main_pid = None
self.check_timer.stop()
self.tracked_pids.clear() self.tracked_pids.clear()

View File

@@ -25,6 +25,14 @@ __license__ = "GPL-3.0 license"
from .logger import get_logger from .logger import get_logger
from .ImageUtils import ImageUtils
from .ProcessManager import ProcessManager
from .security import dpapi_encrypt, dpapi_decrypt from .security import dpapi_encrypt, dpapi_decrypt
__all__ = ["get_logger", "dpapi_encrypt", "dpapi_decrypt"] __all__ = [
"get_logger",
"ImageUtils",
"ProcessManager",
"dpapi_encrypt",
"dpapi_decrypt",
]

View File

@@ -59,6 +59,7 @@ _logger = _logger.patch(lambda record: record["extra"].setdefault("module", "未
def get_logger(module_name: str): def get_logger(module_name: str):
""" """
获取一个绑定 module 名的日志器 获取一个绑定 module 名的日志器
:param module_name: 模块名称,如 "用户管理" :param module_name: 模块名称,如 "用户管理"
:return: 绑定后的 logger :return: 绑定后的 logger
""" """