diff --git a/app/models/MAA.py b/app/models/MAA.py index 954b3cb..da96f8f 100644 --- a/app/models/MAA.py +++ b/app/models/MAA.py @@ -40,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): @@ -1998,6 +1999,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"]: @@ -2040,8 +2045,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/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/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