diff --git a/.github/workflows/build-app.yml b/.github/workflows/build-app.yml index 8bacf40..eb1cd4b 100644 --- a/.github/workflows/build-app.yml +++ b/.github/workflows/build-app.yml @@ -108,7 +108,7 @@ jobs: api-token: '${{ secrets.SIGNPATH_API_TOKEN }}' organization-id: '787a1d5f-6177-4f30-9559-d2646473584a' project-slug: 'AUTO_MAA_' - signing-policy-slug: 'test-signing' + signing-policy-slug: 'release-signing' artifact-configuration-slug: "AUTO_MAA" github-artifact-id: '${{ steps.upload-unsigned-main-program.outputs.artifact-id }}' wait-for-completion: true @@ -156,7 +156,7 @@ jobs: api-token: '${{ secrets.SIGNPATH_API_TOKEN }}' organization-id: '787a1d5f-6177-4f30-9559-d2646473584a' project-slug: 'AUTO_MAA_' - signing-policy-slug: 'test-signing' + signing-policy-slug: 'release-signing' artifact-configuration-slug: "AUTO_MAA-Setup" github-artifact-id: '${{ steps.upload-unsigned-setup-program.outputs.artifact-id }}' wait-for-completion: true diff --git a/README.md b/README.md index e8343ed..d81c21b 100644 --- a/README.md +++ b/README.md @@ -52,13 +52,10 @@ - **传播:** AUTO_MAA原则上允许传播者自由传播本软件,但无论在何种传播过程中,不得删除项目作者与开发者所留版权声明,不得隐瞒项目作者与相关开发者的存在。由于软件性质,项目组不希望发现任何人在明日方舟官方媒体(包括官方媒体账号与森空岛社区等)或明日方舟游戏相关内容(包括同好群、线下活动与游戏内容讨论等)下提及AUTO_MAA或MAA,希望各位理解。 - **衍生:** AUTO_MAA允许任何人对软件本体或软件部分代码进行二次开发或利用。但依据GPL,相关成果再次分发时也必须使用GPL或兼容的协议开源。 - **贡献:** 不论是直接参与软件的维护编写,或是撰写文档、测试、反馈BUG、给出建议、参与讨论,都为AUTO_MAA项目的发展完善做出了不可忽视的贡献。项目组提倡各位贡献者遵照GitHub开源社区惯例,发布Issues参与项目。避免私信或私发邮件(安全性漏洞或敏感问题除外),以帮助更多用户。 +- **图像:** `AUTO_MAA主页默认图像` 并不适用开源协议,著作权归 [NARINpopo](https://space.bilibili.com/1877154) 画师所有,商业使用权归 [DLmaster (@DLmaster361)](https://github.com/DLmaster361) 所有,软件用户仅拥有非商业使用权。不得以开源协议已授权为由在未经授权的情况下使用 `AUTO_MAA主页默认图像`,不得在未经授权的情况下将 `AUTO_MAA主页默认图像` 用于任何商业用途。 以上细则是本项目对GPL的相关补充与强调。未提及的以GPL为准,发生冲突的以本细则为准。如有不清楚的部分,请发Issues询问。若发生纠纷,相关内容也没有在Issues上提及的,项目组拥有最终解释权。 -**注意** - -- 由于本软件有修改其它目录JSON文件等行为,使用前请将AUTO_MAA添加入Windows Defender信任区以及防病毒软件的信任区或开发者目录,避免被误杀。 - --- # 使用方法 diff --git a/app/core/config.py b/app/core/config.py index f0b386a..fc15c7e 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -576,7 +576,7 @@ class MaaPlanConfig(LQConfig): class AppConfig(GlobalConfig): - VERSION = "4.3.10.3" + VERSION = "4.3.10.0" gameid_refreshed = Signal() PASSWORD_refreshed = Signal() diff --git a/app/core/timer.py b/app/core/timer.py index daa164b..9aed618 100644 --- a/app/core/timer.py +++ b/app/core/timer.py @@ -96,17 +96,9 @@ class _MainTimer(QObject): windows = System.get_window_info() - # 排除雷电名为新通知的窗口 - windows = [ - window - for window in windows - if not ( - window[0] == "新通知" and Path(window[1]) in Config.silence_list - ) - ] - + # 此处排除雷电名为新通知的窗口 if any( - str(emulator_path) in window + str(emulator_path) in window and window[0] != "新通知" for window in windows for emulator_path in Config.silence_list ): diff --git a/app/models/MAA.py b/app/models/MAA.py index b29fca1..ded0b51 100644 --- a/app/models/MAA.py +++ b/app/models/MAA.py @@ -32,6 +32,7 @@ import subprocess import shutil import re import win32com.client +from functools import partial from datetime import datetime, timedelta from pathlib import Path from jinja2 import Environment, FileSystemLoader @@ -39,6 +40,7 @@ from typing import Union, List, Dict from app.core import Config, MaaConfig, MaaUserConfig from app.services import Notify, Crypto, System, skland_sign_in +from app.utils.ImageUtils import ImageUtils class MaaManager(QObject): @@ -242,9 +244,17 @@ class MaaManager(QObject): "info", f"森空岛签到{type}", "、".join(user_list), - -1, + -1 if type == "失败" else 5000, ) + if skland_result["总计"] == 0: + self.push_info_bar.emit( + "info", + "森空岛签到失败", + user[0], + -1, + ) + if ( skland_result["总计"] > 0 and len(skland_result["失败"]) == 0 @@ -959,8 +969,10 @@ class MaaManager(QObject): if self.isInterruptionRequested: return None - # 移除静默进程标记 - Config.silence_list.remove(self.emulator_path) + # 10s后移除静默进程标记 + QTimer.singleShot( + 10000, partial(Config.silence_list.remove, self.emulator_path) + ) if "-" in self.ADB_address: ADB_ip = f"{self.ADB_address.split("-")[0]}-" @@ -1269,10 +1281,15 @@ class MaaManager(QObject): else: self.agree_bilibili(False) + # 切换配置 + if data["Current"] != "Default": + + data["Configurations"]["Default"] = data["Configurations"][data["Current"]] + data["Current"] = "Default" + # 自动代理配置 if "自动代理" in mode: - data["Current"] = "Default" # 切换配置 for i in range(1, 9): data["Global"][f"Timer.Timer{i}"] = "False" # 时间设置 @@ -1578,7 +1595,6 @@ class MaaManager(QObject): # 人工排查配置 elif "人工排查" in mode: - data["Current"] = "Default" # 切换配置 for i in range(1, 9): data["Global"][f"Timer.Timer{i}"] = "False" # 时间设置 data["Configurations"]["Default"][ @@ -1651,7 +1667,6 @@ class MaaManager(QObject): # 设置MAA配置 elif "设置MAA" in mode: - data["Current"] = "Default" # 切换配置 for i in range(1, 9): data["Global"][f"Timer.Timer{i}"] = "False" # 时间设置 data["Configurations"]["Default"][ @@ -1707,7 +1722,6 @@ class MaaManager(QObject): elif mode == "更新MAA": - data["Current"] = "Default" # 切换配置 for i in range(1, 9): data["Global"][f"Timer.Timer{i}"] = "False" # 时间设置 data["Configurations"]["Default"][ @@ -1995,6 +2009,10 @@ class MaaManager(QObject): "好羡慕~\n\nAUTO_MAA 敬上", Config.get(Config.notify_CompanyWebHookBotUrl), ) + Notify.CompanyWebHookBotPushImage( + Config.app_path / "resources/images/notification/six_star.png", + Config.get(Config.notify_CompanyWebHookBotUrl), + ) # 发送用户单独通知 if user_data["Notify"]["Enabled"] and user_data["Notify"]["IfSendSixStar"]: @@ -2037,8 +2055,14 @@ class MaaManager(QObject): "好羡慕~\n\nAUTO_MAA 敬上", user_data["Notify"]["CompanyWebHookBotUrl"], ) + Notify.CompanyWebHookBotPushImage( + Config.app_path + / "resources/images/notification/six_star.png", + Config.get(Config.notify_CompanyWebHookBotUrl), + ) else: logger.error( f"{self.name} |用户CompanyWebHookBot密钥为空,无法发送用户单独的CompanyWebHookBot通知" ) + return None diff --git a/app/services/notification.py b/app/services/notification.py index 21486d5..f433c0b 100644 --- a/app/services/notification.py +++ b/app/services/notification.py @@ -32,6 +32,7 @@ from email.header import Header from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.utils import formataddr +from pathlib import Path import requests from PySide6.QtCore import QObject, Signal @@ -40,6 +41,7 @@ from plyer import notification from app.core import Config from app.services.security import Crypto +from app.utils.ImageUtils import ImageUtils class Notification(QObject): @@ -271,6 +273,100 @@ class Notification(QObject): ) return f"使用企业微信群机器人推送通知时出错:{err}" + def CompanyWebHookBotPushImage(self, image_path: Path, webhook_url: str) -> bool: + """使用企业微信群机器人推送图片通知""" + try: + # 压缩图片 + ImageUtils.compress_image_if_needed(image_path) + + # 检查图片是否存在 + if not image_path.exists(): + logger.error( + "图片推送异常 | 图片不存在或者压缩失败,请检查图片路径是否正确" + ) + self.push_info_bar.emit( + "error", + "企业微信群机器人通知推送异常", + "图片不存在或者压缩失败,请检查图片路径是否正确", + -1, + ) + return False + + if not webhook_url: + logger.error("请正确设置企业微信群机器人的WebHook地址") + self.push_info_bar.emit( + "error", + "企业微信群机器人通知推送异常", + "请正确设置企业微信群机器人的WebHook地址", + -1, + ) + return False + + # 获取图片base64和md5 + try: + image_base64 = ImageUtils.get_base64_from_file(str(image_path)) + image_md5 = ImageUtils.calculate_md5_from_file(str(image_path)) + except Exception as e: + logger.error(f"图片编码或MD5计算失败:{e}") + self.push_info_bar.emit( + "error", + "企业微信群机器人通知推送异常", + f"图片编码或MD5计算失败:{e}", + -1, + ) + return False + + data = { + "msgtype": "image", + "image": {"base64": image_base64, "md5": image_md5}, + } + + for _ in range(3): + try: + response = requests.post( + url=webhook_url, + json=data, + timeout=10, + ) + info = response.json() + break + except requests.RequestException as e: + err = e + logger.warning(f"推送企业微信群机器人图片第{_+1}次失败:{e}") + time.sleep(0.1) + else: + logger.error(f"推送企业微信群机器人图片时出错:{err}") + self.push_info_bar.emit( + "error", + "企业微信群机器人图片推送失败", + f"使用企业微信群机器人推送图片时出错:{err}", + -1, + ) + return False + + if info.get("errcode") == 0: + logger.info("企业微信群机器人推送图片成功") + return True + else: + logger.error(f"企业微信群机器人推送图片失败:{info}") + 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): """发送测试通知到所有已启用的通知渠道""" # 发送系统通知 @@ -307,6 +403,10 @@ class Notification(QObject): "这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!", Config.get(Config.notify_CompanyWebHookBotUrl), ) + Notify.CompanyWebHookBotPushImage( + Config.app_path / "resources/images/notification/test_notify.png", + Config.get(Config.notify_CompanyWebHookBotUrl), + ) return True diff --git a/app/services/skland.py b/app/services/skland.py index ff59014..192f589 100644 --- a/app/services/skland.py +++ b/app/services/skland.py @@ -153,7 +153,9 @@ def skland_sign_in(token) -> dict: headers=header_login, ).json() if rsp["status"] != 0: - raise Exception(f'使用token: {token} 获得认证代码失败:{rsp.get("msg")}') + raise Exception( + f'使用token: {token[:3]}******{token[-3:]} 获得认证代码失败:{rsp.get("msg")}' + ) return rsp["data"]["code"] # 获取已绑定的角色列表 diff --git a/app/utils/ImageUtils.py b/app/utils/ImageUtils.py new file mode 100644 index 0000000..64b2447 --- /dev/null +++ b/app/utils/ImageUtils.py @@ -0,0 +1,67 @@ +import base64 +import hashlib +from pathlib import Path + +from PIL import Image + +class ImageUtils: + @staticmethod + def get_base64_from_file(image_path): + """从本地文件读取并返回base64编码字符串""" + with open(image_path, "rb") as f: + return base64.b64encode(f.read()).decode("utf-8") + + @staticmethod + def calculate_md5_from_file(image_path): + """从本地文件读取并返回md5值(hex字符串)""" + with open(image_path, "rb") as f: + return hashlib.md5(f.read()).hexdigest() + + @staticmethod + def calculate_md5_from_base64(base64_content): + """从base64字符串计算md5""" + image_data = base64.b64decode(base64_content) + return hashlib.md5(image_data).hexdigest() + + @staticmethod + def compress_image_if_needed(image_path: Path, max_size_mb=2) -> Path: + """ + 如果图片大于max_size_mb,则压缩并覆盖原文件,返回原始路径(Path对象) + """ + if hasattr(Image, "Resampling"): # Pillow 9.1.0及以后 + RESAMPLE = Image.Resampling.LANCZOS + else: + RESAMPLE = Image.ANTIALIAS + + max_size = max_size_mb * 1024 * 1024 + if image_path.stat().st_size <= max_size: + return image_path + + img = Image.open(image_path) + suffix = image_path.suffix.lower() + quality = 90 if suffix in [".jpg", ".jpeg"] else None + step = 5 + + if suffix in [".jpg", ".jpeg"]: + while True: + img.save(image_path, quality=quality, optimize=True) + if image_path.stat().st_size <= max_size or quality <= 10: + break + quality -= step + elif suffix == ".png": + width, height = img.size + while True: + img.save(image_path, optimize=True) + if ( + image_path.stat().st_size <= max_size + or width <= 200 + or height <= 200 + ): + break + width = int(width * 0.95) + height = int(height * 0.95) + img = img.resize((width, height), RESAMPLE) + else: + raise ValueError("仅支持JPG/JPEG和PNG格式图片的压缩。") + + return image_path diff --git a/requirements.txt b/requirements.txt index e32d6b5..0c2cbd1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,4 +9,5 @@ pycryptodome requests markdown Jinja2 -nuitka \ No newline at end of file +nuitka +pillow \ No newline at end of file diff --git a/resources/images/Home/BannerDefault.png b/resources/images/Home/BannerDefault.png index 0795101..ac4c489 100644 Binary files a/resources/images/Home/BannerDefault.png and b/resources/images/Home/BannerDefault.png differ diff --git a/resources/images/notification/six_star.png b/resources/images/notification/six_star.png new file mode 100644 index 0000000..2ef8c38 Binary files /dev/null and b/resources/images/notification/six_star.png differ diff --git a/resources/images/notification/test_notify.png b/resources/images/notification/test_notify.png new file mode 100644 index 0000000..6429431 Binary files /dev/null and b/resources/images/notification/test_notify.png differ diff --git a/resources/version.json b/resources/version.json index fff5c59..d4e489d 100644 --- a/resources/version.json +++ b/resources/version.json @@ -1,6 +1,15 @@ { - "main_version": "4.3.10.3", + "main_version": "4.3.10.0", "version_info": { + "4.3.10.0": { + "新增功能": [ + "更换全新默认主页图", + "适配 MAA 无`Default`配置情况 #52" + ], + "程序优化": [ + "静默模式控制时段延长至模拟器完成启动的10s后" + ] + }, "4.3.10.3": { "程序优化": [ "使用 keyboard 模块替代 pyautogui 模块"