diff --git a/README.md b/README.md index 4a1a8ea..2f2c5e1 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,11 @@ 本软件是明日方舟第三方软件`MAA`的第三方工具,即第33方软件。旨在优化MAA多账号功能体验,并通过一些方法解决MAA项目未能解决的部分问题,提高代理的稳定性。 +- **集中管理**:一站式管理多个MAA脚本与多个用户配置,和凌乱的散装脚本窗口说再见! +- **无人值守**:自动处理MAA相关报错,再也不用为代理任务卡死时自己不在电脑旁烦恼啦! +- **配置灵活**:通过调度队列与脚本的组合,自由实现您能想到的所有调度需求! +- **信息统计**:自动统计用户的公招与关卡掉落物,看看这个月您的收益是多少! + ### 原理 本软件可以存储多个明日方舟账号数据,并通过以下流程实现代理功能: @@ -32,11 +37,9 @@ ### 优势 -- **节省运行开销:** 只需要一份MAA软件与一个模拟器,无需多开就能完成多账号代理,羸弱的电脑也能代理日常。 -- **自定义空间大:** 依靠高级用户配置模式,支持MAA几乎所有设置选项自定义,支持模拟器多开。 -- **调度方法自由:** 通过调度队列功能,自由实现MAA多开等多种调度方式。 -- **一键代理无忧:** 无须中途手动修改MAA配置,将繁琐交给AUTO_MAA,把游戏留给自己。 -- **代理结果复核:** 通过人工排查功能核实各用户代理情况,堵住自动代理的最后一丝风险。 +- **高效稳定**:通过日志监测、异常处理等机制,保障代理任务顺利完成。 +- **简洁易用**:无需手动修改配置文件,实现自动化调度与多开管理。 +- **兼容扩展**:支持 MAA 几乎所有的配置选项,满足不同用户需求。 ## 重要声明 diff --git a/app/core/config.py b/app/core/config.py index e28c9e9..6070b52 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -517,7 +517,7 @@ class MaaPlanConfig(LQConfig): class AppConfig(GlobalConfig): - VERSION = "4.3.8.3" + VERSION = "4.3.8.4" gameid_refreshed = Signal() PASSWORD_refreshed = Signal() diff --git a/app/models/MAA.py b/app/models/MAA.py index a2bd23e..5b1ceef 100644 --- a/app/models/MAA.py +++ b/app/models/MAA.py @@ -608,13 +608,14 @@ class MaaManager(QObject): Config.app_path / f"history/{curdate}/{user_data["Info"]["Name"]}/{start_time.strftime("%H-%M-%S")}.json", ) - - if Config.get(Config.notify_IfSendSixStar) and if_six_star: - + if if_six_star: self.push_notification( "公招六星", f"喜报:用户 {user[0]} 公招出六星啦!", - {"user_name": user_data["Info"]["Name"]}, + { + "user_name": user_data["Info"]["Name"], + }, + user_data, ) # 执行MAA解压更新动作 @@ -640,26 +641,23 @@ class MaaManager(QObject): logger.info(f"{self.name} | 更新动作结束") - if Config.get(Config.notify_IfSendStatistic): - - statistics = Config.merge_maa_logs("指定项", user_logs_list) - statistics["user_info"] = user[0] - 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["Annihilation"] and run_book["Routine"]) - else "代理任务未全部完成" - ) - self.push_notification( - "统计信息", - f"{current_date} | 用户 {user[0]} 的自动代理统计报告", - statistics, - ) + # 发送统计信息 + statistics = Config.merge_maa_logs("指定项", user_logs_list) + statistics["user_index"] = user[2] + statistics["user_info"] = user[0] + 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["Annihilation"] and run_book["Routine"]) + else "代理任务未全部完成" + ) + self.push_notification( + "统计信息", + f"{current_date} | 用户 {user[0]} 的自动代理统计报告", + statistics, + user_data, + ) if run_book["Annihilation"] and run_book["Routine"]: # 成功完成代理的用户修改相关参数 @@ -852,6 +850,17 @@ class MaaManager(QObject): self.data[_]["Config"]["Info"]["Name"] for _ in wait_index ], } + + # 生成结果文本 + result_text = ( + f"任务开始时间:{result["start_time"]},结束时间:{result["end_time"]}\n" + f"已完成数:{result["completed_count"]},未完成数:{result["uncompleted_count"]}\n\n" + ) + if len(result["failed_user"]) > 0: + result_text += f"{self.mode[2:4]}未成功的用户:\n{"\n".join(result["failed_user"])}\n" + if len(result["waiting_user"]) > 0: + result_text += f"\n未开始{self.mode[2:4]}的用户:\n{"\n".join(result["waiting_user"])}\n" + # 推送代理结果通知 Notify.push_plyer( title.replace("报告", "已完成!"), @@ -859,15 +868,7 @@ class MaaManager(QObject): f"已完成用户数:{len(over_index)},未完成用户数:{len(error_index) + len(wait_index)}", 10, ) - if Config.get(Config.notify_SendTaskResultTime) == "任何时刻" or ( - Config.get(Config.notify_SendTaskResultTime) == "仅失败时" - and len(error_index) + len(wait_index) != 0 - ): - result_text = self.push_notification("代理结果", title, result) - else: - result_text = self.push_notification( - "代理结果", title, result, if_get_text_only=True - ) + self.push_notification("代理结果", title, result) self.agree_bilibili(False) self.log_monitor.deleteLater() @@ -1741,15 +1742,21 @@ class MaaManager(QObject): mode: str, title: str, message: Union[str, dict], - if_get_text_only: bool = False, - ) -> str: + user_data: Dict[str, Dict[str, Union[str, int, bool]]] = None, + ) -> None: """通过所有渠道推送通知""" env = Environment( loader=FileSystemLoader(str(Config.app_path / "resources/html")) ) - if mode == "代理结果": + if mode == "代理结果" and ( + Config.get(Config.notify_SendTaskResultTime) == "任何时刻" + or ( + Config.get(Config.notify_SendTaskResultTime) == "仅失败时" + and message["uncompleted_count"] != 0 + ) + ): # 生成文本通知内容 message_text = ( f"任务开始时间:{message["start_time"]},结束时间:{message["end_time"]}\n" @@ -1761,9 +1768,6 @@ class MaaManager(QObject): if len(message["waiting_user"]) > 0: message_text += f"\n未开始{self.mode[2:4]}的用户:\n{"\n".join(message["waiting_user"])}\n" - if if_get_text_only: - return message_text - # 生成HTML通知内容 message["failed_user"] = "、".join(message["failed_user"]) message["waiting_user"] = "、".join(message["waiting_user"]) @@ -1771,27 +1775,34 @@ class MaaManager(QObject): template = env.get_template("MAA_result.html") message_html = template.render(message) - # 发送全局通知 - Notify.send_mail("网页", title, message_html,Config.get(Config.notify_ToAddress)) + # ServerChan的换行是两个换行符。故而将\n替换为\n\n serverchan_message = message_text.replace("\n", "\n\n") - Notify.ServerChanPush(title, f"{serverchan_message}\n\nAUTO_MAA 敬上",Config.get(Config.notify_ServerChanKey),Config.get(Config.notify_ServerChanTag),Config.get(Config.notify_ServerChanChannel)) - Notify.CompanyWebHookBotPush(title, f"{message_text}\n\nAUTO_MAA 敬上",Config.get(Config.notify_CompanyWebHookBotUrl)) - # # 发送用户单独通知 - # for user_name in message["failed_user"].split("、") + message["waiting_user"].split("、"): - # if not user_name: # 跳过空字符串 - # continue - # user_config = Config.member_dict.get(user_name) - # if user_config and user_config.get(user_config.Notify_Enable): - # user_notify = UserNotification(user_config) - # user_notify.send_notification( - # f"{self.mode[2:4]}任务未完成通知", - # f"您的{self.mode[2:4]}任务未完成,请检查相关设置。\n\n{message_text}" - # ) + # 发送全局通知 - return message_text + if Config.get(Config.notify_IfSendMail): + Notify.send_mail( + "网页", title, message_html, Config.get(Config.notify_ToAddress) + ) + + if Config.get(Config.notify_IfServerChan): + Notify.ServerChanPush( + title, + f"{serverchan_message}\n\nAUTO_MAA 敬上", + Config.get(Config.notify_ServerChanKey), + Config.get(Config.notify_ServerChanTag), + Config.get(Config.notify_ServerChanChannel), + ) + + if Config.get(Config.notify_IfCompanyWebHookBot): + Notify.CompanyWebHookBotPush( + title, + f"{message_text}\n\nAUTO_MAA 敬上", + Config.get(Config.notify_CompanyWebHookBotUrl), + ) elif mode == "统计信息": + # 生成文本通知内容 formatted = [] for stage, items in message["drop_statistics"].items(): @@ -1817,41 +1828,155 @@ class MaaManager(QObject): template = env.get_template("MAA_statistics.html") message_html = template.render(message) - # 发送全局通知 - Notify.send_mail("网页", title, message_html,Config.get(Config.notify_ToAddress)) # ServerChan的换行是两个换行符。故而将\n替换为\n\n serverchan_message = message_text.replace("\n", "\n\n") - Notify.ServerChanPush(title, f"{serverchan_message}\n\nAUTO_MAA 敬上",Config.get(Config.notify_ServerChanKey),Config.get(Config.notify_ServerChanTag),Config.get(Config.notify_ServerChanChannel)) - Notify.CompanyWebHookBotPush(title, f"{message_text}\n\nAUTO_MAA 敬上",Config.get(Config.notify_CompanyWebHookBotUrl)) - # # 发送用户单独通知 - # user_name = message.get("user_info") - # if user_name: - # user_config = Config.member_dict.get(user_name) - # if user_config and user_config.get(user_config.Notify_Enable): - # user_notify = UserNotification(user_config) - # user_notify.send_notification( - # f"{self.mode[2:4]}任务统计报告", - # f"您的{self.mode[2:4]}任务统计报告如下:\n\n{message_text}" - # ) + # 发送全局通知 + if Config.get(Config.notify_IfSendStatistic): + + if Config.get(Config.notify_IfSendMail): + Notify.send_mail( + "网页", title, message_html, Config.get(Config.notify_ToAddress) + ) + + if Config.get(Config.notify_IfServerChan): + Notify.ServerChanPush( + title, + f"{serverchan_message}\n\nAUTO_MAA 敬上", + Config.get(Config.notify_ServerChanKey), + Config.get(Config.notify_ServerChanTag), + Config.get(Config.notify_ServerChanChannel), + ) + + if Config.get(Config.notify_IfCompanyWebHookBot): + Notify.CompanyWebHookBotPush( + title, + f"{message_text}\n\nAUTO_MAA 敬上", + Config.get(Config.notify_CompanyWebHookBotUrl), + ) + + # 发送用户单独通知 + if ( + user_data["Notify"]["Enabled"] + and user_data["Notify"]["IfSendStatistic"] + ): + + # 发送邮件通知 + if user_data["Notify"]["IfSendMail"]: + if user_data["Notify"]["ToAddress"]: + Notify.send_mail( + "网页", + title, + message_html, + user_data["Notify"]["ToAddress"], + ) + else: + logger.error( + f"{self.name} | 用户邮箱地址为空,无法发送用户单独的邮件通知" + ) + + # 发送ServerChan通知 + if user_data["Notify"]["IfServerChan"]: + if user_data["Notify"]["ServerChanKey"]: + Notify.ServerChanPush( + title, + f"{serverchan_message}\n\nAUTO_MAA 敬上", + user_data["Notify"]["ServerChanKey"], + user_data["Notify"]["ServerChanTag"], + user_data["Notify"]["ServerChanChannel"], + ) + else: + logger.error( + f"{self.name} |用户ServerChan密钥为空,无法发送用户单独的ServerChan通知" + ) + + # 推送CompanyWebHookBot通知 + if user_data["Notify"]["IfCompanyWebHookBot"]: + if user_data["Notify"]["CompanyWebHookBotUrl"]: + Notify.CompanyWebHookBotPush( + title, + f"{message_text}\n\nAUTO_MAA 敬上", + user_data["Notify"]["CompanyWebHookBotUrl"], + ) + else: + logger.error( + f"{self.name} |用户CompanyWebHookBot密钥为空,无法发送用户单独的CompanyWebHookBot通知" + ) elif mode == "公招六星": + # 生成HTML通知内容 template = env.get_template("MAA_six_star.html") + message_html = template.render(message) # 发送全局通知 - Notify.send_mail("网页", title, message_html,Config.get(Config.notify_ToAddress)) - Notify.ServerChanPush(title, "好羡慕~\n\nAUTO_MAA 敬上",Config.get(Config.notify_ServerChanKey),Config.get(Config.notify_ServerChanTag),Config.get(Config.notify_ServerChanChannel)) - Notify.CompanyWebHookBotPush(title, "好羡慕~\n\nAUTO_MAA 敬上",Config.get(Config.notify_CompanyWebHookBotUrl)) + if Config.get(Config.notify_IfSendSixStar): - # # 发送用户单独通知 - # user_name = message.get("user_name") - # if user_name: - # user_config = Config.member_dict.get(user_name) - # if user_config and user_config.get(user_config.Notify_Enable): - # user_notify = UserNotification(user_config) - # user_notify.send_notification( - # "公招六星通知", - # "恭喜您在公招中获得了六星干员!" - # ) + if Config.get(Config.notify_IfSendMail): + Notify.send_mail( + "网页", title, message_html, Config.get(Config.notify_ToAddress) + ) + + if Config.get(Config.notify_IfServerChan): + Notify.ServerChanPush( + title, + "好羡慕~\n\nAUTO_MAA 敬上", + Config.get(Config.notify_ServerChanKey), + Config.get(Config.notify_ServerChanTag), + Config.get(Config.notify_ServerChanChannel), + ) + + if Config.get(Config.notify_IfCompanyWebHookBot): + Notify.CompanyWebHookBotPush( + title, + "好羡慕~\n\nAUTO_MAA 敬上", + Config.get(Config.notify_CompanyWebHookBotUrl), + ) + + # 发送用户单独通知 + if user_data["Notify"]["Enabled"] and user_data["Notify"]["IfSendSixStar"]: + + # 发送邮件通知 + if user_data["Notify"]["IfSendMail"]: + if user_data["Notify"]["ToAddress"]: + Notify.send_mail( + "网页", + title, + message_html, + user_data["Notify"]["ToAddress"], + ) + else: + logger.error( + f"{self.name} | 用户邮箱地址为空,无法发送用户单独的邮件通知" + ) + + # 发送ServerChan通知 + if user_data["Notify"]["IfServerChan"]: + + if user_data["Notify"]["ServerChanKey"]: + Notify.ServerChanPush( + title, + "好羡慕~\n\nAUTO_MAA 敬上", + user_data["Notify"]["ServerChanKey"], + user_data["Notify"]["ServerChanTag"], + user_data["Notify"]["ServerChanChannel"], + ) + else: + logger.error( + f"{self.name} |用户ServerChan密钥为空,无法发送用户单独的ServerChan通知" + ) + + # 推送CompanyWebHookBot通知 + if user_data["Notify"]["IfCompanyWebHookBot"]: + if user_data["Notify"]["CompanyWebHookBotUrl"]: + Notify.CompanyWebHookBotPush( + title, + "好羡慕~\n\nAUTO_MAA 敬上", + user_data["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 639e3ac..4cb60bb 100644 --- a/app/services/notification.py +++ b/app/services/notification.py @@ -69,219 +69,208 @@ class Notification(QWidget): def send_mail(self, mode, title, content, to_address) -> None: """推送邮件通知""" - - if Config.get(Config.notify_IfSendMail): - - if ( - Config.get(Config.notify_SMTPServerAddress) == "" - or Config.get(Config.notify_AuthorizationCode) == "" - or not bool( - re.match( - r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", - Config.get(Config.notify_FromAddress), - ) - ) - or not bool( - re.match( - r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", - to_address, - ) - ) - ): - logger.error( - "请正确设置邮件通知的SMTP服务器地址、授权码、发件人地址和收件人地址" - ) - self.push_info_bar.emit( - "error", - "邮件通知推送异常", - "请正确设置邮件通知的SMTP服务器地址、授权码、发件人地址和收件人地址", - -1, - ) - return None - - try: - # 定义邮件正文 - if mode == "文本": - message = MIMEText(content, "plain", "utf-8") - elif mode == "网页": - message = MIMEMultipart("alternative") - message["From"] = formataddr( - ( - Header("AUTO_MAA通知服务", "utf-8").encode(), - 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( + if ( + Config.get(Config.notify_SMTPServerAddress) == "" + or Config.get(Config.notify_AuthorizationCode) == "" + or not bool( + re.match( + r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", Config.get(Config.notify_FromAddress), - Crypto.win_decryptor(Config.get(Config.notify_AuthorizationCode)), ) - smtpObj.sendmail( - Config.get(Config.notify_FromAddress), + ) + or not bool( + re.match( + r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", to_address, - message.as_string(), ) - smtpObj.quit() - logger.success("邮件发送成功") - return None - except Exception as e: - logger.error(f"发送邮件时出错:\n{e}") - self.push_info_bar.emit("error", "发送邮件时出错", f"{e}", -1) - return None + ) + ): + logger.error( + "请正确设置邮件通知的SMTP服务器地址、授权码、发件人地址和收件人地址" + ) + self.push_info_bar.emit( + "error", + "邮件通知推送异常", + "请正确设置邮件通知的SMTP服务器地址、授权码、发件人地址和收件人地址", + -1, + ) + return None + + try: + # 定义邮件正文 + if mode == "文本": + message = MIMEText(content, "plain", "utf-8") + elif mode == "网页": + message = MIMEMultipart("alternative") + message["From"] = formataddr( + ( + Header("AUTO_MAA通知服务", "utf-8").encode(), + 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(), + ) + smtpObj.quit() + logger.success("邮件发送成功") + return None + except Exception as e: + logger.error(f"发送邮件时出错:\n{e}") + self.push_info_bar.emit("error", "发送邮件时出错", f"{e}", -1) + return None return None def ServerChanPush(self, title, content, send_key, tag, channel): """使用Server酱推送通知""" - if Config.get(Config.notify_IfServerChan): - if not send_key: - logger.error("请正确设置Server酱的SendKey") - self.push_info_bar.emit( - "error", "Server酱通知推送异常", "请正确设置Server酱的SendKey", -1 - ) - return None + if not send_key: + logger.error("请正确设置Server酱的SendKey") + self.push_info_bar.emit( + "error", "Server酱通知推送异常", "请正确设置Server酱的SendKey", -1 + ) + return None - try: - # 构造 URL - if send_key.startswith("sctp"): - match = re.match(r"^sctp(\d+)t", send_key) - if match: - url = f"https://{match.group(1)}.push.ft07.com/send/{send_key}.send" - else: - raise ValueError("SendKey 格式错误(sctp)") + try: + # 构造 URL + if send_key.startswith("sctp"): + match = re.match(r"^sctp(\d+)t", send_key) + if match: + url = f"https://{match.group(1)}.push.ft07.com/send/{send_key}.send" else: - url = f"https://sctapi.ftqq.com/{send_key}.send" - - # 构建 tags 和 channel - def is_valid(s): - return s == "" or ( - s == "|".join(s.split("|")) - and (s.count("|") == 0 or all(s.split("|"))) - ) - - tags = "|".join( - _.strip() - for _ in tag.split("|") - ) - channels = "|".join( - _.strip() - for _ in channel.split("|") - ) - - options = {} - if is_valid(tags): - options["tags"] = tags - else: - logger.warning("Server酱 Tag 配置不正确,将被忽略") - self.push_info_bar.emit( - "warning", - "Server酱通知推送异常", - "请正确设置 ServerChan 的 Tag", - -1, - ) - - if is_valid(channels): - options["channel"] = channels - else: - logger.warning("Server酱 Channel 配置不正确,将被忽略") - 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) - result = response.json() - - if result.get("code") == 0: - logger.info("Server酱推送通知成功") - return True - else: - error_code = result.get("code", "-1") - logger.error(f"Server酱通知推送失败:响应码:{error_code}") - self.push_info_bar.emit( - "error", "Server酱通知推送失败", f"响应码:{error_code}", -1 - ) - return f"Server酱通知推送失败:{error_code}" - - except Exception as e: - logger.exception("Server酱通知推送异常") - self.push_info_bar.emit( - "error", "Server酱通知推送异常", f"请检查相关设置,如还有问题可联系开发者", -1 - ) - return f"Server酱通知推送异常:{str(e)}" - return None - - def CompanyWebHookBotPush(self, title, content,webhook_url): - """使用企业微信群机器人推送通知""" - if Config.get(Config.notify_IfCompanyWebHookBot): - - if webhook_url == "": - logger.error("请正确设置企业微信群机器人的WebHook地址") - self.push_info_bar.emit( - "error", - "企业微信群机器人通知推送异常", - "请正确设置企业微信群机器人的WebHook地址", - -1, - ) - return None - - content = f"{title}\n{content}" - data = {"msgtype": "text", "text": {"content": content}} - - for _ in range(3): - try: - response = requests.post( - url=webhook_url, - json=data, - timeout=10, - ) - info = response.json() - break - except Exception as e: - err = e - time.sleep(0.1) + raise ValueError("SendKey 格式错误(sctp)") else: - logger.error(f"推送企业微信群机器人时出错:{err}") + url = f"https://sctapi.ftqq.com/{send_key}.send" + + # 构建 tags 和 channel + def is_valid(s): + return s == "" or ( + s == "|".join(s.split("|")) + and (s.count("|") == 0 or all(s.split("|"))) + ) + + tags = "|".join(_.strip() for _ in tag.split("|")) + channels = "|".join(_.strip() for _ in channel.split("|")) + + options = {} + if is_valid(tags): + options["tags"] = tags + else: + logger.warning("Server酱 Tag 配置不正确,将被忽略") self.push_info_bar.emit( - "error", - "企业微信群机器人通知推送失败", - f'使用企业微信群机器人推送通知时出错:{info["errmsg"]}', + "warning", + "Server酱通知推送异常", + "请正确设置 ServerChan 的 Tag", -1, ) - return None - if info["errcode"] == 0: - logger.info("企业微信群机器人推送通知成功") + if is_valid(channels): + options["channel"] = channels + else: + logger.warning("Server酱 Channel 配置不正确,将被忽略") + 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) + result = response.json() + + if result.get("code") == 0: + logger.info("Server酱推送通知成功") return True else: - logger.error(f"企业微信群机器人推送通知失败:{info}") + error_code = result.get("code", "-1") + logger.error(f"Server酱通知推送失败:响应码:{error_code}") self.push_info_bar.emit( - "error", - "企业微信群机器人通知推送失败", - f'使用企业微信群机器人推送通知时出错:{info["errmsg"]}', - -1, + "error", "Server酱通知推送失败", f"响应码:{error_code}", -1 ) - return f'使用企业微信群机器人推送通知时出错:{info["errmsg"]}' - return None + return f"Server酱通知推送失败:{error_code}" + + except Exception as e: + logger.exception("Server酱通知推送异常") + self.push_info_bar.emit( + "error", + "Server酱通知推送异常", + "请检查相关设置和网络连接。如全部配置正确,请稍后再试。", + -1, + ) + return f"Server酱通知推送异常:{str(e)}" + + def CompanyWebHookBotPush(self, title, content, webhook_url): + """使用企业微信群机器人推送通知""" + if webhook_url == "": + logger.error("请正确设置企业微信群机器人的WebHook地址") + self.push_info_bar.emit( + "error", + "企业微信群机器人通知推送异常", + "请正确设置企业微信群机器人的WebHook地址", + -1, + ) + return None + + content = f"{title}\n{content}" + data = {"msgtype": "text", "text": {"content": content}} + + for _ in range(3): + try: + response = requests.post( + url=webhook_url, + json=data, + timeout=10, + ) + info = response.json() + break + except Exception as e: + err = e + time.sleep(0.1) + else: + logger.error(f"推送企业微信群机器人时出错:{err}") + self.push_info_bar.emit( + "error", + "企业微信群机器人通知推送失败", + f'使用企业微信群机器人推送通知时出错:{info["errmsg"]}', + -1, + ) + return None + + if info["errcode"] == 0: + logger.info("企业微信群机器人推送通知成功") + return True + else: + logger.error(f"企业微信群机器人推送通知失败:{info}") + self.push_info_bar.emit( + "error", + "企业微信群机器人通知推送失败", + f'使用企业微信群机器人推送通知时出错:{info["errmsg"]}', + -1, + ) + return f'使用企业微信群机器人推送通知时出错:{info["errmsg"]}' def send_test_notification(self): """发送测试通知到所有已启用的通知渠道""" diff --git a/app/ui/Widget.py b/app/ui/Widget.py index e308046..ea5f8d0 100644 --- a/app/ui/Widget.py +++ b/app/ui/Widget.py @@ -25,17 +25,24 @@ v4.3 作者:DLmaster_361 """ +import os +import re +from datetime import datetime +from functools import partial +from typing import Optional, Union, List, Dict +from urllib.parse import urlparse + +import markdown +from PySide6.QtCore import Qt, QTime, QTimer, QEvent, QSize +from PySide6.QtGui import QIcon, QPixmap, QPainter, QPainterPath from PySide6.QtWidgets import ( QApplication, QWidget, - QWidget, QLabel, QHBoxLayout, QVBoxLayout, QSizePolicy, ) -from PySide6.QtCore import Qt, QTime, QTimer, QEvent, QSize -from PySide6.QtGui import QIcon, QPixmap, QPainter, QPainterPath from qfluentwidgets import ( LineEdit, PasswordLineEdit, @@ -77,13 +84,6 @@ from qfluentwidgets import ( FlyoutViewBase, ) from qfluentwidgets.common.overload import singledispatchmethod -import os -import re -import markdown -from datetime import datetime -from urllib.parse import urlparse -from functools import partial -from typing import Optional, Union, List, Dict from app.core import Config from app.services import Crypto @@ -109,6 +109,8 @@ class LineEditMessageBox(MessageBoxBase): self.viewLayout.addWidget(self.title) self.viewLayout.addWidget(self.input) + self.input.setFocus() + class ComboBoxMessageBox(MessageBoxBase): """选择对话框""" @@ -465,24 +467,27 @@ class LineEditSettingCard(SettingCard): self.LineEdit.setMinimumWidth(250) self.LineEdit.setPlaceholderText(text) - if configItem: - self.setValue(self.qconfig.get(configItem)) - configItem.valueChanged.connect(self.setValue) - self.hBoxLayout.addWidget(self.LineEdit, 0, Qt.AlignRight) self.hBoxLayout.addSpacing(16) + self.configItem.valueChanged.connect(self.setValue) self.LineEdit.textChanged.connect(self.__textChanged) + self.setValue(self.qconfig.get(configItem)) + def __textChanged(self, content: str): - self.setValue(content.strip()) + + self.configItem.valueChanged.disconnect() + self.qconfig.set(self.configItem, content.strip()) + self.configItem.valueChanged.connect(self.setValue) + self.textChanged.emit(content.strip()) def setValue(self, content: str): - if self.configItem: - self.qconfig.set(self.configItem, content.strip()) + self.LineEdit.textChanged.disconnect() self.LineEdit.setText(content.strip()) + self.LineEdit.textChanged.connect(self.__textChanged) class PasswordLineEditSettingCard(SettingCard): @@ -511,35 +516,29 @@ class PasswordLineEditSettingCard(SettingCard): self.LineEdit.setPlaceholderText(text) if algorithm == "AUTO": self.LineEdit.setViewPasswordButtonVisible(False) - self.if_setValue = False - - if configItem: - self.setValue(self.qconfig.get(configItem)) - configItem.valueChanged.connect(self.setValue) self.hBoxLayout.addWidget(self.LineEdit, 0, Qt.AlignRight) self.hBoxLayout.addSpacing(16) + self.configItem.valueChanged.connect(self.setValue) self.LineEdit.textChanged.connect(self.__textChanged) + self.setValue(self.qconfig.get(configItem)) + def __textChanged(self, content: str): - if self.if_setValue: - return None - + self.configItem.valueChanged.disconnect() if self.algorithm == "DPAPI": - self.setValue(Crypto.win_encryptor(content)) + self.qconfig.set(self.configItem, Crypto.win_encryptor(content)) elif self.algorithm == "AUTO": - self.setValue(Crypto.AUTO_encryptor(content)) + self.qconfig.set(self.configItem, Crypto.AUTO_encryptor(content)) + self.configItem.valueChanged.connect(self.setValue) + self.textChanged.emit() def setValue(self, content: str): - self.if_setValue = True - - if self.configItem: - self.qconfig.set(self.configItem, content) - + self.LineEdit.textChanged.disconnect() if self.algorithm == "DPAPI": self.LineEdit.setText(Crypto.win_decryptor(content)) elif self.algorithm == "AUTO": @@ -555,8 +554,7 @@ class PasswordLineEditSettingCard(SettingCard): self.LineEdit.setText("************") self.LineEdit.setPasswordVisible(False) self.LineEdit.setReadOnly(True) - - self.if_setValue = False + self.LineEdit.textChanged.connect(self.__textChanged) class UserLableSettingCard(SettingCard): @@ -998,9 +996,30 @@ class UserNoticeSettingCard(PushAndSwitchButtonSettingCard): def setValues(self): def short_str(s: str) -> str: - if len(s) <= 10: - return s - return s[:10] + "..." + if s.startswith(("SC", "sc")): + # SendKey:首4 + 末4 + return f"{s[:4]}***{s[-4:]}" if len(s) > 8 else s + + elif s.startswith(("http://", "https://")): + # Webhook URL:域名 + 路径尾3 + parsed_url = urlparse(s) + domain = parsed_url.netloc + path_tail = ( + parsed_url.path[-3:] + if len(parsed_url.path) > 3 + else parsed_url.path + ) + return f"{domain}***{path_tail}" + + elif "@" in s: + # 邮箱:@前3/6 + 域名 + username, domain = s.split("@", 1) + displayed_name = f"{username[:3]}***" if len(username) > 6 else username + return f"{displayed_name}@{domain}" + + else: + # 普通字符串:末尾3字符 + return f"***{s[-3:]}" if len(s) > 3 else s content_list = [] diff --git a/resources/version.json b/resources/version.json index f1dea9d..622187a 100644 --- a/resources/version.json +++ b/resources/version.json @@ -1,6 +1,12 @@ { - "main_version": "4.3.8.3", + "main_version": "4.3.8.4", "version_info": { + "4.3.8.4": { + "新增功能": [ + "支持为每一个用户执行独立通知", + "输入文本框适配文本插入操作" + ] + }, "4.3.8.3": { "新增功能": [ "用户仪表盘支持直接控制用户状态"