feat(core): 添加统计信息通知功能

This commit is contained in:
DLmaster
2025-03-03 20:40:28 +08:00
parent d1c8f98408
commit 175d6860a3
6 changed files with 240 additions and 29 deletions

View File

@@ -31,7 +31,7 @@ import json
import sys import sys
import shutil import shutil
import re import re
from datetime import datetime, timedelta from datetime import datetime
from collections import defaultdict from collections import defaultdict
from pathlib import Path from pathlib import Path
from qfluentwidgets import ( from qfluentwidgets import (
@@ -45,7 +45,7 @@ from qfluentwidgets import (
OptionsValidator, OptionsValidator,
qconfig, qconfig,
) )
from typing import Union, Dict, List from typing import Union, Dict, List, Tuple
class AppConfig: class AppConfig:
@@ -469,8 +469,6 @@ class AppConfig:
def save_maa_log(self, log_path: Path, logs: list, maa_result: str) -> None: def save_maa_log(self, log_path: Path, logs: list, maa_result: str) -> None:
"""保存MAA日志""" """保存MAA日志"""
log_path.parent.mkdir(parents=True, exist_ok=True)
data: Dict[str, Union[str, Dict[str, Union[int, dict]]]] = { data: Dict[str, Union[str, Dict[str, Union[int, dict]]]] = {
"recruit_statistics": defaultdict(int), "recruit_statistics": defaultdict(int),
"drop_statistics": defaultdict(dict), "drop_statistics": defaultdict(dict),
@@ -545,6 +543,7 @@ class AppConfig:
data["drop_statistics"][current_stage] = stage_drops data["drop_statistics"][current_stage] = stage_drops
# 保存日志 # 保存日志
log_path.parent.mkdir(parents=True, exist_ok=True)
with log_path.open("w", encoding="utf-8") as f: with log_path.open("w", encoding="utf-8") as f:
f.writelines(logs) f.writelines(logs)
with log_path.with_suffix(".json").open("w", encoding="utf-8") as f: with log_path.with_suffix(".json").open("w", encoding="utf-8") as f:
@@ -552,10 +551,10 @@ class AppConfig:
logger.info(f"处理完成:{log_path}") logger.info(f"处理完成:{log_path}")
self.merge_maa_logs(log_path.parent) self.merge_maa_logs("所有项", log_path.parent)
def merge_maa_logs(self, logs_path: Path) -> None: def merge_maa_logs(self, mode: str, logs_path: Union[Path, List[Path]]) -> dict:
"""合并所有 .log 文件""" """合并指定数据统计信息文件"""
data = { data = {
"recruit_statistics": defaultdict(int), "recruit_statistics": defaultdict(int),
@@ -563,7 +562,12 @@ class AppConfig:
"maa_result": defaultdict(str), "maa_result": defaultdict(str),
} }
for json_file in logs_path.glob("*.json"): if mode == "所有项":
logs_path_list = list(logs_path.glob("*.json"))
elif mode == "指定项":
logs_path_list = logs_path
for json_file in logs_path_list:
with json_file.open("r", encoding="utf-8") as f: with json_file.open("r", encoding="utf-8") as f:
single_data: Dict[str, Union[str, Dict[str, Union[int, dict]]]] = ( single_data: Dict[str, Union[str, Dict[str, Union[int, dict]]]] = (
@@ -592,10 +596,14 @@ class AppConfig:
] = single_data["maa_result"] ] = single_data["maa_result"]
# 生成汇总 JSON 文件 # 生成汇总 JSON 文件
with logs_path.with_suffix(".json").open("w", encoding="utf-8") as f: if mode == "所有项":
json.dump(data, f, ensure_ascii=False, indent=4)
logger.info(f"统计完成:{logs_path.with_suffix(".json")}") with logs_path.with_suffix(".json").open("w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=4)
logger.info(f"统计完成:{logs_path.with_suffix(".json")}")
return data
def load_maa_logs( def load_maa_logs(
self, mode: str, json_path: Path self, mode: str, json_path: Path
@@ -779,11 +787,14 @@ class GlobalConfig(QConfig):
ui_location = ConfigItem("UI", "location", "100x100") ui_location = ConfigItem("UI", "location", "100x100")
ui_maximized = ConfigItem("UI", "maximized", False, BoolValidator()) ui_maximized = ConfigItem("UI", "maximized", False, BoolValidator())
notify_IfPushPlyer = ConfigItem("Notify", "IfPushPlyer", False, BoolValidator())
notify_IfSendMail = ConfigItem("Notify", "IfSendMail", False, BoolValidator())
notify_IfSendErrorOnly = ConfigItem( notify_IfSendErrorOnly = ConfigItem(
"Notify", "IfSendErrorOnly", False, BoolValidator() "Notify", "IfSendErrorOnly", False, BoolValidator()
) )
notify_IfSendStatistic = ConfigItem(
"Notify", "IfSendStatistic", False, BoolValidator()
)
notify_IfPushPlyer = ConfigItem("Notify", "IfPushPlyer", False, BoolValidator())
notify_IfSendMail = ConfigItem("Notify", "IfSendMail", False, BoolValidator())
notify_SMTPServerAddress = ConfigItem("Notify", "SMTPServerAddress", "") notify_SMTPServerAddress = ConfigItem("Notify", "SMTPServerAddress", "")
notify_AuthorizationCode = ConfigItem("Notify", "AuthorizationCode", "") notify_AuthorizationCode = ConfigItem("Notify", "AuthorizationCode", "")
notify_FromAddress = ConfigItem("Notify", "FromAddress", "") notify_FromAddress = ConfigItem("Notify", "FromAddress", "")

View File

@@ -34,7 +34,7 @@ import subprocess
import shutil import shutil
import time import time
from pathlib import Path from pathlib import Path
from typing import List from typing import Union, List
from app.core import Config from app.core import Config
from app.services import Notify, System from app.services import Notify, System
@@ -176,6 +176,9 @@ class MaaManager(QObject):
[True, "routine", "日常"], [True, "routine", "日常"],
] ]
user_logs_list = []
user_start_time = datetime.now()
# 尝试次数循环 # 尝试次数循环
for i in range(self.set["RunSet"]["RunTimesLimit"]): for i in range(self.set["RunSet"]["RunTimesLimit"]):
@@ -273,7 +276,7 @@ class MaaManager(QObject):
System.kill_process(self.maa_exe_path) System.kill_process(self.maa_exe_path)
self.if_open_emulator = True self.if_open_emulator = True
# 推送异常通知 # 推送异常通知
Notify.push_notification( Notify.push_plyer(
"用户自动代理出现异常!", "用户自动代理出现异常!",
f"用户 {user[0].replace("_", " 今天的")}{mode_book[j][5:7]}部分出现一次异常", f"用户 {user[0].replace("_", " 今天的")}{mode_book[j][5:7]}部分出现一次异常",
f"{user[0].replace("_", " ")}{mode_book[j][5:7]}出现异常", f"{user[0].replace("_", " ")}{mode_book[j][5:7]}出现异常",
@@ -287,13 +290,17 @@ class MaaManager(QObject):
# 移除静默进程标记 # 移除静默进程标记
Config.silence_list.remove(self.emulator_path) Config.silence_list.remove(self.emulator_path)
# 保存运行日志 # 保存运行日志以及统计信息
Config.save_maa_log( Config.save_maa_log(
Config.app_path Config.app_path
/ f"history/{curdate}/{self.data[user[2]][0]}/{start_time.strftime("%H-%M-%S")}.log", / f"history/{curdate}/{self.data[user[2]][0]}/{start_time.strftime("%H-%M-%S")}.log",
self.check_maa_log(start_time, mode_book[j]), self.check_maa_log(start_time, mode_book[j]),
self.maa_result, self.maa_result,
) )
user_logs_list.append(
Config.app_path
/ f"history/{curdate}/{self.data[user[2]][0]}/{start_time.strftime("%H-%M-%S")}.json",
)
# 成功完成代理的用户修改相关参数 # 成功完成代理的用户修改相关参数
if run_book[0] and run_book[1]: if run_book[0] and run_book[1]:
@@ -301,7 +308,7 @@ class MaaManager(QObject):
self.data[user[2]][3] -= 1 self.data[user[2]][3] -= 1
self.data[user[2]][14] += 1 self.data[user[2]][14] += 1
user[1] = "完成" user[1] = "完成"
Notify.push_notification( Notify.push_plyer(
"成功完成一个自动代理任务!", "成功完成一个自动代理任务!",
f"已完成用户 {user[0].replace("_", " 今天的")}任务", f"已完成用户 {user[0].replace("_", " 今天的")}任务",
f"已完成 {user[0].replace("_", "")}", f"已完成 {user[0].replace("_", "")}",
@@ -309,6 +316,26 @@ class MaaManager(QObject):
) )
break break
if Config.global_config.get(
Config.global_config.notify_IfSendStatistic
):
statistics = Config.merge_maa_logs("指定项", user_logs_list)
statistics["start_time"] = user_start_time.strftime(
"%Y-%m-%d %H:%M:%S"
)
statistics["end_time"] = datetime.now().strftime(
"%Y-%m-%d %H:%M:%S"
)
statistics["maa_result"] = (
"代理任务全部完成"
if (run_book[0] and run_book[1])
else "代理任务未全部完成"
)
self.push_notification(
"统计信息", f"用户 {user[0]} 的自动代理统计报告", statistics
)
# 录入代理失败的用户 # 录入代理失败的用户
if not (run_book[0] and run_book[1]): if not (run_book[0] and run_book[1]):
user[1] = "异常" user[1] = "异常"
@@ -487,7 +514,7 @@ class MaaManager(QObject):
else f"{self.mode[:4]}任务报告" else f"{self.mode[:4]}任务报告"
) )
# 推送代理结果通知 # 推送代理结果通知
Notify.push_notification( Notify.push_plyer(
title.replace("报告", "已完成!"), title.replace("报告", "已完成!"),
f"已完成用户数:{len(over_index)},未完成用户数:{len(error_index) + len(wait_index)}", f"已完成用户数:{len(over_index)},未完成用户数:{len(error_index) + len(wait_index)}",
f"已完成用户数:{len(over_index)},未完成用户数:{len(error_index) + len(wait_index)}", f"已完成用户数:{len(over_index)},未完成用户数:{len(error_index) + len(wait_index)}",
@@ -499,12 +526,7 @@ class MaaManager(QObject):
Config.global_config.get(Config.global_config.notify_IfSendErrorOnly) Config.global_config.get(Config.global_config.notify_IfSendErrorOnly)
and len(error_index) + len(wait_index) != 0 and len(error_index) + len(wait_index) != 0
): ):
Notify.send_mail( self.push_notification("代理结果", title, end_log)
title,
f"{end_log}\n\nAUTO_MAA 敬上\n\n我们根据您在 AUTO_MAA 中的设置发送了这封电子邮件,本邮件无需回复\n",
)
Notify.ServerChanPush(title, f"{end_log}\n\nAUTO_MAA 敬上")
Notify.CompanyWebHookBotPush(title, f"{end_log}AUTO_MAA 敬上")
self.agree_bilibili(False) self.agree_bilibili(False)
self.log_monitor.deleteLater() self.log_monitor.deleteLater()
@@ -1130,3 +1152,165 @@ class MaaManager(QObject):
if dt.time() < datetime.min.time().replace(hour=4): if dt.time() < datetime.min.time().replace(hour=4):
dt = dt - timedelta(days=1) dt = dt - timedelta(days=1)
return dt.strftime("%Y-%m-%d") return dt.strftime("%Y-%m-%d")
def push_notification(
self, mode: str, title: str, message: Union[str, dict]
) -> None:
"""通过所有渠道推送通知"""
if mode == "代理结果":
Notify.send_mail(
"文本",
title,
f"{message}\n\nAUTO_MAA 敬上\n\n我们根据您在 AUTO_MAA 中的设置发送了这封电子邮件,本邮件无需回复\n",
)
Notify.ServerChanPush(title, f"{message}\n\nAUTO_MAA 敬上")
Notify.CompanyWebHookBotPush(title, f"{message}AUTO_MAA 敬上")
elif mode == "统计信息":
# HTML模板
html_template = """
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{title}</title>
<style>
body {{
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f4f4f4;
}}
.container {{
width: 80%;
margin: auto;
overflow: hidden;
padding: 20px;
background-color: white;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}}
h1.main-title {{
text-align: center;
color: #333;
font-size: 2em;
margin-bottom: 20px;
}}
p {{
font-size: 16px;
color: #555;
}}
table {{
width: 100%;
border-collapse: collapse;
margin: 25px 0;
font-size: 18px;
text-align: left;
}}
th, td {{
padding: 12px 15px;
}}
th {{
background-color: #007BFF;
color: white;
}}
tr:nth-child(even) {{
background-color: #f3f3f3;
}}
tr:hover {{
background-color: #ddd;
}}
</style>
</head>
<body>
<div class="container">
<h1 class="main-title">{title}</h1>
<p><strong>开始时间:</strong> {start_time}</p>
<p><strong>结束时间:</strong> {end_time}</p>
<p><strong>MAA执行结果:</strong> {maa_result}</p>
<h2>招募统计</h2>
<table>
<thead>
<tr>
<th>星级</th>
<th>数量</th>
</tr>
</thead>
<tbody>
{recruit_rows}
</tbody>
</table>
{drop_tables}
</div>
</body>
</html>
"""
# 构建招募统计表格行
recruit_rows = "".join(
f"<tr><td>{star}</td><td>{count}</td></tr>"
for star, count in message["recruit_statistics"].items()
)
# 构建掉落统计数据表格
drop_tables_html = ""
for stage, items in message["drop_statistics"].items():
drop_rows = "".join(
f"<tr><td>{item}</td><td>{quantity}</td></tr>"
for item, quantity in items.items()
)
drop_table = f"""
<h2>掉落统计 ({stage})</h2>
<table>
<thead>
<tr>
<th>物品</th>
<th>数量</th>
</tr>
</thead>
<tbody>
{drop_rows}
</tbody>
</table>
"""
drop_tables_html += drop_table
# 填充HTML模板
html_content = html_template.format(
title=title,
start_time=message["start_time"],
end_time=message["end_time"],
maa_result=message["maa_result"],
recruit_rows=recruit_rows,
drop_tables=drop_tables_html,
)
formatted = []
for stage, items in message["drop_statistics"].items():
formatted.append(f"掉落统计({stage}:")
for item, quantity in items.items():
formatted.append(f" {item}: {quantity}")
drop_text = "\n".join(formatted)
formatted = ["招募统计:"]
for star, count in message["recruit_statistics"].items():
formatted.append(f" {star}: {count}")
recruit_text = "\n".join(formatted)
# 构建通知消息
message_text = (
f"开始时间: {message['start_time']}\n"
f"结束时间: {message['end_time']}\n"
f"MAA执行结果: {message['maa_result']}\n\n"
f"{recruit_text}\n"
f"{drop_text}"
)
Notify.send_mail("网页", title, html_content)
Notify.ServerChanPush(title, f"{message_text}\n\nAUTO_MAA 敬上")
Notify.CompanyWebHookBotPush(title, f"{message_text}AUTO_MAA 敬上")

View File

@@ -29,6 +29,7 @@ from loguru import logger
from plyer import notification from plyer import notification
import smtplib import smtplib
from email.mime.text import MIMEText from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header from email.header import Header
from email.utils import formataddr from email.utils import formataddr
@@ -40,7 +41,7 @@ from app.services.security import Crypto
class Notification: class Notification:
def push_notification(self, title, message, ticker, t): def push_plyer(self, title, message, ticker, t):
"""推送系统通知""" """推送系统通知"""
if Config.global_config.get(Config.global_config.notify_IfPushPlyer): if Config.global_config.get(Config.global_config.notify_IfPushPlyer):
@@ -57,14 +58,17 @@ class Notification:
return True return True
def send_mail(self, title, content): def send_mail(self, mode, title, content):
"""推送邮件通知""" """推送邮件通知"""
if Config.global_config.get(Config.global_config.notify_IfSendMail): if Config.global_config.get(Config.global_config.notify_IfSendMail):
try: try:
# 定义邮件正文 # 定义邮件正文
message = MIMEText(content, "plain", "utf-8") if mode == "文本":
message = MIMEText(content, "plain", "utf-8")
elif mode == "网页":
message = MIMEMultipart("alternative")
message["From"] = formataddr( message["From"] = formataddr(
( (
Header("AUTO_MAA通知服务", "utf-8").encode(), Header("AUTO_MAA通知服务", "utf-8").encode(),
@@ -81,6 +85,9 @@ class Notification:
) # 收件人显示的名字 ) # 收件人显示的名字
message["Subject"] = Header(title, "utf-8") message["Subject"] = Header(title, "utf-8")
if mode == "网页":
message.attach(MIMEText(content, "html", "utf-8"))
smtpObj = smtplib.SMTP_SSL( smtpObj = smtplib.SMTP_SSL(
Config.global_config.get( Config.global_config.get(
Config.global_config.notify_SMTPServerAddress Config.global_config.notify_SMTPServerAddress

View File

@@ -69,6 +69,8 @@ class Home(QWidget):
Layout.addWidget(self.banner) Layout.addWidget(self.banner)
Layout.addWidget(self.banner_text) Layout.addWidget(self.banner_text)
Layout.setStretch(0, 2)
Layout.setStretch(1, 3)
v_layout = QVBoxLayout(self.banner) v_layout = QVBoxLayout(self.banner)
v_layout.setContentsMargins(0, 0, 0, 15) v_layout.setContentsMargins(0, 0, 0, 15)

View File

@@ -548,6 +548,12 @@ class NotifySettingCard(HeaderCardWidget):
content="仅在任务出现异常时推送通知", content="仅在任务出现异常时推送通知",
configItem=Config.global_config.notify_IfSendErrorOnly, configItem=Config.global_config.notify_IfSendErrorOnly,
) )
self.caer_IfSendStatistic = SwitchSettingCard(
icon=FluentIcon.PAGE_RIGHT,
title="推送统计信息",
content="推送自动代理统计信息的通知",
configItem=Config.global_config.notify_IfSendStatistic,
)
self.card_IfPushPlyer = SwitchSettingCard( self.card_IfPushPlyer = SwitchSettingCard(
icon=FluentIcon.PAGE_RIGHT, icon=FluentIcon.PAGE_RIGHT,
title="推送系统通知", title="推送系统通知",
@@ -560,6 +566,7 @@ class NotifySettingCard(HeaderCardWidget):
Layout = QVBoxLayout() Layout = QVBoxLayout()
Layout.addWidget(self.card_IfSendErrorOnly) Layout.addWidget(self.card_IfSendErrorOnly)
Layout.addWidget(self.caer_IfSendStatistic)
Layout.addWidget(self.card_IfPushPlyer) Layout.addWidget(self.card_IfPushPlyer)
Layout.addWidget(self.card_SendMail) Layout.addWidget(self.card_SendMail)
Layout.addWidget(self.card_ServerChan) Layout.addWidget(self.card_ServerChan)

View File

@@ -1,7 +1,7 @@
{ {
"main_version": "4.2.4.6", "main_version": "4.2.4.7",
"updater_version": "1.1.2.1", "updater_version": "1.1.2.1",
"announcement": "\n## 新增功能\n- 历史记录统计功能上线\n- 添加软件主页\n- 添加启动时直接最小化功能\n- 更新器拥有多网址测速功能\n## 修复BUG\n- RMA70-12不能正确统计的问题\n- 更新器修正`channel`\n## 程序优化\n- 添加MAA监测字段`未检测到任何模拟器`\n- 取消MAA运行中自动更新", "announcement": "\n## 新增功能\n- 历史记录统计功能上线\n- 添加软件主页\n- 添加启动时直接最小化功能\n- 更新器拥有多网址测速功能\n- 添加统计信息通知功能\n## 修复BUG\n- RMA70-12不能正确统计的问题\n- 更新器修正`channel`\n## 程序优化\n- 添加MAA监测字段`未检测到任何模拟器`\n- 取消MAA运行中自动更新",
"proxy_list": [ "proxy_list": [
"", "",
"https://gitproxy.click/", "https://gitproxy.click/",