Skip to content

Conversation

@ShadowLemoon
Copy link
Collaborator

@ShadowLemoon ShadowLemoon commented Oct 31, 2025

Summary by CodeRabbit

发版说明

  • New Features

    • 重构推送通知系统,采用模块化架构支持更多推送服务通道
    • 新增推送服务管理器,统一管理多个推送渠道配置与发送
    • 扩展推送通道支持,包含邮箱、Webhook、机器人等多种通知方式
  • Refactor

    • 完全重新设计推送配置和通知分发流程

@coderabbitai
Copy link

coderabbitai bot commented Oct 31, 2025

Walkthrough

推送系统完全重构。移除旧的单体式 Push 类和配置模块,建立模块化的 PushChannel 抽象基类架构,新增 20+ 个具体推送通道实现(如 Bark、钉钉、Telegram 等),引入 PushService 协调器、新的 PushConfig 管理系统和 CurlGenerator 工具类。

Changes

Cohort / File(s) 变更摘要
已移除的旧推送系统
src/one_dragon/base/config/push_config.py, src/one_dragon/base/notify/push.py, src/one_dragon/base/notify/push_cards.py
删除了旧的 NotifyMethodEnum、PushConfig、PushConfigNames 类及整个 Push 通知枢纽类,包括所有通道实现方法(bark、dingding_bot、discord_bot 等)和 WeCom 嵌套类
推送通道基础架构
src/one_dragon/base/push/push_channel.py, src/one_dragon/base/push/push_channel_config.py
新增 PushChannel 抽象基类,定义标准推送接口,包含图像处理和代理工具方法;新增 FieldTypeEnum 和 PushChannelConfigField 数据模型用于配置元数据
推送通道实现 - 基础通道
src/one_dragon/base/push/channel/fake.py, src/one_dragon/base/push/channel/webhook.py, src/one_dragon/base/push/channel/smtp.py
新增 FakePushChannel(占位符)、Webhook(通用 Webhook 接口,支持动态变量替换和模板)、Smtp(邮件推送)
推送通道实现 - 即时通讯
src/one_dragon/base/push/channel/dingding.py, src/one_dragon/base/push/channel/feishu.py, src/one_dragon/base/push/channel/work_weixin.py
新增钉钉机器人、飞书/Lark 机器人、企业微信机器人通道,包含签名认证、图像上传等
推送通道实现 - 机器人/Bot
src/one_dragon/base/push/channel/one_bot.py, src/one_dragon/base/push/channel/chronocat.py, src/one_dragon/base/push/channel/ai_botk.py
新增 OneBot、Chronocat(QQ 消息)、AiBotK(智能微秘书)通道
推送通道实现 - 社交/通讯平台
src/one_dragon/base/push/channel/discord.py, src/one_dragon/base/push/channel/telegram.py
新增 Discord、Telegram 通道,支持文本和图像传输
推送通道实现 - 推送服务
src/one_dragon/base/push/channel/bark.py, src/one_dragon/base/push/channel/gotify.py, src/one_dragon/base/push/channel/ntfy.py, src/one_dragon/base/push/channel/server_chan.py
新增 Bark、Gotify、ntfy、Server酱推送服务通道
推送通道实现 - 第三方推送平台
src/one_dragon/base/push/channel/push_plus.py, src/one_dragon/base/push/channel/push_deer.py, src/one_dragon/base/push/channel/push_me.py, src/one_dragon/base/push/channel/we_plus_bot.py, src/one_dragon/base/push/channel/wx_pusher.py, src/one_dragon/base/push/channel/q_msg.py, src/one_dragon/base/push/channel/synology_chat.py, src/one_dragon/base/push/channel/i_got.py
新增 PushPlus、PushDeer、PushMe、WePlusBot、WxPusher、QMsg、Synology Chat、iGot 通道实现
推送系统新核心
src/one_dragon/base/push/push_config.py, src/one_dragon/base/push/push_service.py
新增 PushProxy 枚举和 PushConfig 类用于管理推送配置(包含动态属性生成);新增 PushService 协调器类,管理通道注册、异步推送、配置访问
工具类
src/one_dragon/base/push/curl_generator.py
新增 CurlGenerator,生成兼容 PowerShell 和 Unix 的 curl 命令,支持模板变量替换、自定义头部和请求体
配置文件修复
src/one_dragon/base/push/push_email_services.py
仅修复空白符和 EOF 格式
上下文整合
src/script_chainer/context/script_chainer_context.py, src/script_chainer/win_exe/script_runner.py
ScriptChainerContext 新增 push_service 属性和 init 方法;script_runner 用 PushService 替换全局 Push 实例

Sequence Diagram(s)

sequenceDiagram
    participant App as 应用
    participant PushService as PushService
    participant Config as PushConfig
    participant Channel as PushChannel<br/>(具体实现)
    participant External as 外部服务<br/>(钉钉/Discord等)

    Note over App,External: 初始化流程
    App->>PushService: 创建实例
    PushService->>PushService: init_push_channels()
    PushService->>PushService: 注册所有通道实例
    PushService->>Config: 生成通道配置字段

    Note over App,External: 同步推送流程
    App->>PushService: push(content, title, channel_id)
    PushService->>Config: push_config (获取/缓存)
    PushService->>PushService: get_channel_config(channel_id)
    PushService->>Channel: validate_config(config)
    alt 配置有效
        PushService->>Channel: push(config, title, content, image)
        Channel->>External: HTTP 请求
        External-->>Channel: 响应
        Channel-->>PushService: (success, message)
    else 配置无效
        Channel-->>PushService: (False, 错误信息)
    end
    PushService-->>App: (success, message)

    Note over App,External: 异步推送流程
    App->>PushService: push_async(content, title)
    PushService->>PushService: 线程池执行推送
    PushService->>Channel: push(...) [后台]
    Channel->>External: HTTP 请求 [后台]
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

重点审核区域:

  • push_service.py — 通道注册、异步处理、配置管理的核心逻辑和线程安全性
  • push_channel.py — 抽象基类的设计、图像压缩算法(二分查找)和代理处理
  • curl_generator.py — 复杂的字符串替换、JSON 解析、跨平台转义逻辑
  • push_config.py — 动态属性生成闭包、配置键生成策略
  • 各推送通道实现 — 20+ 个新文件,需验证配置验证、签名生成(钉钉/飞书)、图像处理、错误处理的一致性
  • script_runner.py — 推送调用点替换、上下文生命周期管理

Poem

🐰 旧日 Push 已落幕,二十通道齐争鸣,
MVC 模式巧分工,配置灵动生属性,
异步推送如春风,遍布钉钉与飞书,
一条龙架新通衢,消息四海任我行! 🚀

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.04% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed 标题"feat: 同步最新的通知模块"指向真实的变更内容——拉取请求确实对通知模块进行了大规模更新和重构。然而,标题使用"同步"一词不够准确地反映变更的核心性质。实际变更包括删除旧的推送实现、添加多个新推送渠道、引入模块化架构和新的配置系统,这是一个全面的重构,而非单纯的"同步"。标题虽然相关,但表述宽泛且不够具体,难以让扫描历史的开发者快速理解此次变更的重要性和范围。
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

🧹 Nitpick comments (10)
src/script_chainer/context/script_chainer_context.py (1)

20-32: 初始化逻辑正确,考虑使用 logging.exception

使用非阻塞锁模式正确防止了死锁,PushService 集成也很合理。不过在第 30 行,可以考虑使用 log.exception('初始化出错') 替代 log.error('初始化出错', exc_info=True),这是更简洁的写法。

可选的改进:

         try:
             self.push_service.init_push_channels()
-        except Exception:
-            log.error('初始化出错', exc_info=True)
+        except Exception:
+            log.exception('初始化出错')
         finally:
             self._init_lock.release()
src/one_dragon/base/push/channel/fake.py (1)

5-47: 占位符设计合理

FakePushChannel 作为占位符明确表示不支持的推送方法,设计合理。不过建议考虑在类文档字符串中明确说明此通道的用途,避免开发者误用。

可选改进:在类定义后添加文档字符串说明用途:

 class FakePushChannel(PushChannel):
+    """
+    占位符推送通道,用于标记不再维护的推送方法。
+    此通道的所有操作都会返回失败状态。
+    """
 
     def __init__(self):
src/script_chainer/win_exe/script_runner.py (2)

229-237: 上下文初始化逻辑正确,建议使用 logging.exception

基于上下文的推送服务集成设计合理,错误处理也较为完善。建议在第 236 行使用 log.exception 替代 log.error 以简化代码。

可选改进:

     try:
         ctx = ScriptChainerContext()
         ctx.init()
-    except Exception as e:
-        log.error(f'初始化上下文实例失败: {e}')
+    except Exception as e:
+        log.exception(f'初始化上下文实例失败: {e}')

269-274: 资源清理逻辑合理,建议使用 logging.exception

在 finally 块中清理资源的设计正确。建议在第 274 行使用 log.exception 简化错误日志记录。

可选改进:

         if ctx is not None:
             try:
                 ctx.after_app_shutdown()
-            except Exception as e:
-                log.error(f'清理资源失败: {e}')
+            except Exception as e:
+                log.exception(f'清理资源失败: {e}')
src/one_dragon/base/push/channel/ai_botk.py (1)

96-98: 简化 JSON 数据发送

当前实现手动序列化 JSON 并编码,requests 库可以通过 json 参数自动处理。

应用此差异简化代码:

-            # 发送请求
-            headers = {"Content-Type": "application/json"}
-            response = requests.post(url, data=json.dumps(data).encode("utf-8"), headers=headers, timeout=15)
+            # 发送请求
+            response = requests.post(url, json=data, timeout=15)
src/one_dragon/base/push/channel/server_chan.py (1)

90-93: 建议使用一致的空字符串检查方式

当前使用 len(push_key) == 0 检查空字符串。为与其他渠道保持一致并处理仅包含空格的情况,建议使用 .strip()

-        if len(push_key) == 0:
+        if not push_key.strip():
             return False, "PUSH_KEY 不能为空"
src/one_dragon/base/push/channel/push_deer.py (1)

89-94: 错误消息可能过于冗长

Line 94 将完整的响应 JSON 对象转换为字符串返回给用户。这可能包含大量技术细节,建议仅提取关键错误信息。

             if len(content_result) > 0:
                 return True, "推送成功"
             else:
-                return False, f"推送失败:{response_json}"
+                error_msg = response_json.get("content", {}).get("error", "未知错误")
+                return False, f"推送失败:{error_msg}"
src/one_dragon/base/push/channel/gotify.py (1)

109-112: 内部异常处理冗余

Lines 109-112 的外层异常处理块捕获内层已处理的异常,这是冗余的。内层的 except Exception 已经覆盖所有异常。

考虑移除外层的异常处理:

-        except Exception as e:
-            return False, f"gotify 推送系统异常: {str(e)}"
src/one_dragon/base/push/channel/smtp.py (2)

146-153: 建议重构成功返回位置

Line 146 的 return 语句在 try 块内,而 Ruff 建议将其移至 else 块以提高代码清晰度。

                 smtp_server.sendmail(
                     email,
                     email,
                     message.as_bytes()
                 )
-
-                return True, "SMTP邮件推送成功"
-
             except Exception as e:
                 log.error('SMTP邮件推送异常', exc_info=True)
                 return False, f"SMTP邮件推送异常: {str(e)}"
-
+            else:
+                return True, "SMTP邮件推送成功"
             finally:
                 smtp_server.close()

198-205: 未使用的变量

Line 199 解析得到的 host 变量未被使用(仅用于验证端口)。可以使用下划线前缀表示这是一个占位变量。

         try:
-            host, port = server.split(":")
+            _host, port = server.split(":")
             port_int = int(port)
             if port_int < 1 or port_int > 65535:
                 return False, "端口号必须在1-65535之间"
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3037da4 and 10794f9.

📒 Files selected for processing (34)
  • src/one_dragon/base/config/push_config.py (0 hunks)
  • src/one_dragon/base/notify/push.py (0 hunks)
  • src/one_dragon/base/notify/push_cards.py (0 hunks)
  • src/one_dragon/base/push/channel/ai_botk.py (1 hunks)
  • src/one_dragon/base/push/channel/bark.py (1 hunks)
  • src/one_dragon/base/push/channel/chronocat.py (1 hunks)
  • src/one_dragon/base/push/channel/dingding.py (1 hunks)
  • src/one_dragon/base/push/channel/discord.py (1 hunks)
  • src/one_dragon/base/push/channel/fake.py (1 hunks)
  • src/one_dragon/base/push/channel/feishu.py (1 hunks)
  • src/one_dragon/base/push/channel/gotify.py (1 hunks)
  • src/one_dragon/base/push/channel/i_got.py (1 hunks)
  • src/one_dragon/base/push/channel/ntfy.py (1 hunks)
  • src/one_dragon/base/push/channel/one_bot.py (1 hunks)
  • src/one_dragon/base/push/channel/push_deer.py (1 hunks)
  • src/one_dragon/base/push/channel/push_me.py (1 hunks)
  • src/one_dragon/base/push/channel/push_plus.py (1 hunks)
  • src/one_dragon/base/push/channel/q_msg.py (1 hunks)
  • src/one_dragon/base/push/channel/server_chan.py (1 hunks)
  • src/one_dragon/base/push/channel/smtp.py (1 hunks)
  • src/one_dragon/base/push/channel/synology_chat.py (1 hunks)
  • src/one_dragon/base/push/channel/telegram.py (1 hunks)
  • src/one_dragon/base/push/channel/we_plus_bot.py (1 hunks)
  • src/one_dragon/base/push/channel/webhook.py (1 hunks)
  • src/one_dragon/base/push/channel/work_weixin.py (1 hunks)
  • src/one_dragon/base/push/channel/wx_pusher.py (1 hunks)
  • src/one_dragon/base/push/curl_generator.py (1 hunks)
  • src/one_dragon/base/push/push_channel.py (1 hunks)
  • src/one_dragon/base/push/push_channel_config.py (1 hunks)
  • src/one_dragon/base/push/push_config.py (1 hunks)
  • src/one_dragon/base/push/push_email_services.py (1 hunks)
  • src/one_dragon/base/push/push_service.py (1 hunks)
  • src/script_chainer/context/script_chainer_context.py (4 hunks)
  • src/script_chainer/win_exe/script_runner.py (3 hunks)
💤 Files with no reviewable changes (3)
  • src/one_dragon/base/notify/push_cards.py
  • src/one_dragon/base/config/push_config.py
  • src/one_dragon/base/notify/push.py
🧰 Additional context used
🧬 Code graph analysis (28)
src/one_dragon/base/push/channel/telegram.py (2)
src/one_dragon/base/push/push_channel.py (3)
  • PushChannel (10-151)
  • get_proxy (138-151)
  • image_to_bytes (59-83)
src/one_dragon/base/push/push_channel_config.py (2)
  • PushChannelConfigField (21-35)
  • FieldTypeEnum (12-17)
src/one_dragon/base/push/channel/server_chan.py (2)
src/one_dragon/base/push/push_channel.py (1)
  • PushChannel (10-151)
src/one_dragon/base/push/push_channel_config.py (2)
  • PushChannelConfigField (21-35)
  • FieldTypeEnum (12-17)
src/one_dragon/base/push/channel/synology_chat.py (2)
src/one_dragon/base/push/push_channel.py (1)
  • PushChannel (10-151)
src/one_dragon/base/push/push_channel_config.py (2)
  • PushChannelConfigField (21-35)
  • FieldTypeEnum (12-17)
src/one_dragon/base/push/channel/discord.py (2)
src/one_dragon/base/push/push_channel.py (3)
  • PushChannel (10-151)
  • get_proxy (138-151)
  • image_to_bytes (59-83)
src/one_dragon/base/push/push_channel_config.py (2)
  • PushChannelConfigField (21-35)
  • FieldTypeEnum (12-17)
src/one_dragon/base/push/channel/dingding.py (2)
src/one_dragon/base/push/push_channel.py (1)
  • PushChannel (10-151)
src/one_dragon/base/push/push_channel_config.py (2)
  • PushChannelConfigField (21-35)
  • FieldTypeEnum (12-17)
src/one_dragon/base/push/channel/ai_botk.py (2)
src/one_dragon/base/push/push_channel.py (1)
  • PushChannel (10-151)
src/one_dragon/base/push/push_channel_config.py (2)
  • PushChannelConfigField (21-35)
  • FieldTypeEnum (12-17)
src/one_dragon/base/push/channel/wx_pusher.py (2)
src/one_dragon/base/push/push_channel.py (1)
  • PushChannel (10-151)
src/one_dragon/base/push/push_channel_config.py (2)
  • PushChannelConfigField (21-35)
  • FieldTypeEnum (12-17)
src/one_dragon/base/push/channel/one_bot.py (2)
src/one_dragon/base/push/push_channel.py (2)
  • PushChannel (10-151)
  • image_to_base64 (121-136)
src/one_dragon/base/push/push_channel_config.py (2)
  • PushChannelConfigField (21-35)
  • FieldTypeEnum (12-17)
src/one_dragon/base/push/channel/fake.py (1)
src/one_dragon/base/push/push_channel.py (1)
  • PushChannel (10-151)
src/one_dragon/base/push/channel/gotify.py (2)
src/one_dragon/base/push/push_channel.py (1)
  • PushChannel (10-151)
src/one_dragon/base/push/push_channel_config.py (2)
  • PushChannelConfigField (21-35)
  • FieldTypeEnum (12-17)
src/one_dragon/base/push/channel/webhook.py (2)
src/one_dragon/base/push/push_channel.py (3)
  • PushChannel (10-151)
  • get_proxy (138-151)
  • image_to_bytes (59-83)
src/one_dragon/base/push/push_channel_config.py (2)
  • PushChannelConfigField (21-35)
  • FieldTypeEnum (12-17)
src/script_chainer/context/script_chainer_context.py (1)
src/one_dragon/base/push/push_service.py (2)
  • PushService (42-255)
  • init_push_channels (57-94)
src/one_dragon/base/push/channel/we_plus_bot.py (2)
src/one_dragon/base/push/push_channel.py (1)
  • PushChannel (10-151)
src/one_dragon/base/push/push_channel_config.py (2)
  • PushChannelConfigField (21-35)
  • FieldTypeEnum (12-17)
src/one_dragon/base/push/channel/smtp.py (2)
src/one_dragon/base/push/push_channel.py (1)
  • PushChannel (10-151)
src/one_dragon/base/push/push_channel_config.py (2)
  • PushChannelConfigField (21-35)
  • FieldTypeEnum (12-17)
src/one_dragon/base/push/channel/i_got.py (2)
src/one_dragon/base/push/push_channel.py (1)
  • PushChannel (10-151)
src/one_dragon/base/push/push_channel_config.py (2)
  • PushChannelConfigField (21-35)
  • FieldTypeEnum (12-17)
src/one_dragon/base/push/channel/bark.py (2)
src/one_dragon/base/push/push_channel.py (1)
  • PushChannel (10-151)
src/one_dragon/base/push/push_channel_config.py (2)
  • PushChannelConfigField (21-35)
  • FieldTypeEnum (12-17)
src/one_dragon/base/push/push_service.py (3)
src/one_dragon/base/push/push_channel.py (2)
  • PushChannel (10-151)
  • get_proxy (138-151)
src/one_dragon/base/push/push_channel_config.py (1)
  • PushChannelConfigField (21-35)
src/one_dragon/base/push/push_config.py (13)
  • PushConfig (17-166)
  • PushProxy (11-14)
  • generate_channel_fields (83-116)
  • custom_push_title (39-40)
  • custom_push_title (43-44)
  • send_image (47-49)
  • send_image (52-53)
  • get_channel_config_value (118-136)
  • proxy (56-57)
  • proxy (60-61)
  • is_personal_proxy (64-65)
  • personal_proxy (68-73)
  • personal_proxy (76-81)
src/one_dragon/base/push/channel/q_msg.py (2)
src/one_dragon/base/push/push_channel.py (1)
  • PushChannel (10-151)
src/one_dragon/base/push/push_channel_config.py (2)
  • PushChannelConfigField (21-35)
  • FieldTypeEnum (12-17)
src/one_dragon/base/push/channel/chronocat.py (2)
src/one_dragon/base/push/push_channel.py (1)
  • PushChannel (10-151)
src/one_dragon/base/push/push_channel_config.py (2)
  • PushChannelConfigField (21-35)
  • FieldTypeEnum (12-17)
src/one_dragon/base/push/channel/work_weixin.py (2)
src/one_dragon/base/push/push_channel.py (2)
  • PushChannel (10-151)
  • image_to_bytes (59-83)
src/one_dragon/base/push/push_channel_config.py (2)
  • PushChannelConfigField (21-35)
  • FieldTypeEnum (12-17)
src/script_chainer/win_exe/script_runner.py (4)
src/script_chainer/context/script_chainer_context.py (3)
  • ScriptChainerContext (15-103)
  • init (23-32)
  • after_app_shutdown (98-103)
src/one_dragon/base/config/yaml_operator.py (1)
  • is_file_exists (97-102)
src/one_dragon/base/push/push_service.py (1)
  • push_async (220-243)
src/script_chainer/config/script_config.py (1)
  • script_display_name (58-59)
src/one_dragon/base/push/channel/push_plus.py (2)
src/one_dragon/base/push/push_channel.py (1)
  • PushChannel (10-151)
src/one_dragon/base/push/push_channel_config.py (2)
  • PushChannelConfigField (21-35)
  • FieldTypeEnum (12-17)
src/one_dragon/base/push/push_config.py (2)
src/one_dragon/base/push/push_channel_config.py (1)
  • PushChannelConfigField (21-35)
src/one_dragon/base/config/yaml_operator.py (2)
  • get (77-78)
  • update (80-87)
src/one_dragon/base/push/channel/feishu.py (2)
src/one_dragon/base/push/push_channel.py (2)
  • PushChannel (10-151)
  • image_to_bytes (59-83)
src/one_dragon/base/push/push_channel_config.py (2)
  • PushChannelConfigField (21-35)
  • FieldTypeEnum (12-17)
src/one_dragon/base/push/channel/ntfy.py (2)
src/one_dragon/base/push/push_channel.py (2)
  • PushChannel (10-151)
  • image_to_bytes (59-83)
src/one_dragon/base/push/push_channel_config.py (2)
  • PushChannelConfigField (21-35)
  • FieldTypeEnum (12-17)
src/one_dragon/base/push/channel/push_deer.py (2)
src/one_dragon/base/push/push_channel.py (1)
  • PushChannel (10-151)
src/one_dragon/base/push/push_channel_config.py (2)
  • PushChannelConfigField (21-35)
  • FieldTypeEnum (12-17)
src/one_dragon/base/push/channel/push_me.py (2)
src/one_dragon/base/push/push_channel.py (1)
  • PushChannel (10-151)
src/one_dragon/base/push/push_channel_config.py (2)
  • PushChannelConfigField (21-35)
  • FieldTypeEnum (12-17)
src/one_dragon/base/push/push_channel.py (4)
src/one_dragon/base/push/channel/discord.py (2)
  • push (55-140)
  • validate_config (142-161)
src/one_dragon/base/push/channel/ntfy.py (2)
  • push (78-170)
  • validate_config (173-188)
src/one_dragon/base/push/channel/one_bot.py (2)
  • push (53-156)
  • validate_config (158-178)
src/one_dragon/base/push/push_channel_config.py (1)
  • PushChannelConfigField (21-35)
🪛 Ruff (0.14.2)
src/one_dragon/base/push/channel/telegram.py

59-59: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


62-62: Docstring contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF002)


62-62: Docstring contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF002)


104-104: Local variable headers is assigned to but never used

Remove assignment to unused variable headers

(F841)


122-122: Use explicit conversion flag

Replace with conversion flag

(RUF010)


125-125: Do not catch blind exception: Exception

(BLE001)


126-126: Use explicit conversion flag

Replace with conversion flag

(RUF010)


130-130: Do not catch blind exception: Exception

(BLE001)


131-131: Use explicit conversion flag

Replace with conversion flag

(RUF010)

src/one_dragon/base/push/channel/server_chan.py

32-32: Unused method argument: image

(ARG002)


33-33: Unused method argument: proxy_url

(ARG002)


39-39: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


42-42: Docstring contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF002)


42-42: Docstring contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF002)


75-75: String contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF001)


77-77: Do not catch blind exception: Exception

(BLE001)


78-78: Use explicit conversion flag

Replace with conversion flag

(RUF010)

src/one_dragon/base/push/channel/synology_chat.py

52-52: Unused method argument: image

(ARG002)


53-53: Unused method argument: proxy_url

(ARG002)


59-59: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


62-62: Docstring contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF002)


62-62: Docstring contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF002)


91-91: String contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF001)


91-91: String contains ambiguous (FULLWIDTH COLON). Did you mean : (COLON)?

(RUF001)


93-93: Do not catch blind exception: Exception

(BLE001)


95-95: Use explicit conversion flag

Replace with conversion flag

(RUF010)

src/one_dragon/base/push/push_channel_config.py

4-4: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


13-13: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


25-25: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)

src/one_dragon/base/push/channel/discord.py

4-4: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


67-67: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


70-70: Docstring contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF002)


70-70: Docstring contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF002)


125-125: Use pop instead of key in dict followed by del dict[key]

Replace if statement with .pop(..., None)

(RUF051)


136-136: Consider moving this statement to an else block

(TRY300)


138-138: Do not catch blind exception: Exception

(BLE001)


140-140: Use explicit conversion flag

Replace with conversion flag

(RUF010)

src/one_dragon/base/push/channel/dingding.py

62-62: Unused method argument: image

(ARG002)


63-63: Unused method argument: proxy_url

(ARG002)


69-69: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


72-72: Docstring contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF002)


72-72: Docstring contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF002)


102-102: Comment contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF003)


102-102: Comment contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF003)


124-124: String contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF001)


126-126: Do not catch blind exception: Exception

(BLE001)


127-127: Use explicit conversion flag

Replace with conversion flag

(RUF010)

src/one_dragon/base/push/channel/ai_botk.py

37-37: String contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF001)


54-54: Unused method argument: image

(ARG002)


55-55: Unused method argument: proxy_url

(ARG002)


61-61: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


64-64: Docstring contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF002)


64-64: Docstring contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF002)


107-107: String contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF001)


110-110: Use explicit conversion flag

Replace with conversion flag

(RUF010)


111-111: Do not catch blind exception: Exception

(BLE001)


112-112: Use explicit conversion flag

Replace with conversion flag

(RUF010)

src/one_dragon/base/push/channel/wx_pusher.py

51-51: Unused method argument: image

(ARG002)


52-52: Unused method argument: proxy_url

(ARG002)


58-58: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


61-61: Docstring contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF002)


61-61: Docstring contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF002)


77-77: Comment contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF003)


121-121: String contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF001)


124-124: Use explicit conversion flag

Replace with conversion flag

(RUF010)


125-125: Do not catch blind exception: Exception

(BLE001)


126-126: Use explicit conversion flag

Replace with conversion flag

(RUF010)


148-148: Comment contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF003)


148-148: Comment contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF003)

src/one_dragon/base/push/channel/one_bot.py

59-59: Unused method argument: proxy_url

(ARG002)


65-65: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


68-68: Docstring contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF002)


68-68: Docstring contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF002)


116-116: String contains ambiguous (FULLWIDTH EXCLAMATION MARK). Did you mean ! (EXCLAMATION MARK)?

(RUF001)


121-121: Do not catch blind exception: Exception

(BLE001)


122-122: Use explicit conversion flag

Replace with conversion flag

(RUF010)


137-137: String contains ambiguous (FULLWIDTH EXCLAMATION MARK). Did you mean ! (EXCLAMATION MARK)?

(RUF001)


142-142: Do not catch blind exception: Exception

(BLE001)


143-143: Use explicit conversion flag

Replace with conversion flag

(RUF010)


155-155: Do not catch blind exception: Exception

(BLE001)


156-156: Use explicit conversion flag

Replace with conversion flag

(RUF010)

src/one_dragon/base/push/channel/fake.py

12-12: String contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF001)


18-18: Unused method argument: config

(ARG002)


19-19: Unused method argument: title

(ARG002)


20-20: Unused method argument: content

(ARG002)


21-21: Unused method argument: image

(ARG002)


22-22: Unused method argument: proxy_url

(ARG002)


38-38: Unused method argument: config

(ARG002)

src/one_dragon/base/push/channel/gotify.py

54-54: Unused method argument: proxy_url

(ARG002)


60-60: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


63-63: Docstring contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF002)


63-63: Docstring contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF002)


78-78: Comment contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF003)


80-80: String contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF001)


98-98: String contains ambiguous (FULLWIDTH EXCLAMATION MARK). Did you mean ! (EXCLAMATION MARK)?

(RUF001)


101-101: f-string without any placeholders

Remove extraneous f prefix

(F541)


101-101: String contains ambiguous (FULLWIDTH EXCLAMATION MARK). Did you mean ! (EXCLAMATION MARK)?

(RUF001)


106-106: Use explicit conversion flag

Replace with conversion flag

(RUF010)


109-109: Do not catch blind exception: Exception

(BLE001)


110-110: Use explicit conversion flag

Replace with conversion flag

(RUF010)


114-114: Do not catch blind exception: Exception

(BLE001)


115-115: Use explicit conversion flag

Replace with conversion flag

(RUF010)

src/one_dragon/base/push/channel/webhook.py

83-83: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


86-86: Docstring contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF002)


86-86: Docstring contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF002)


121-121: Comment contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF003)


121-121: Comment contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF003)


126-126: Comment contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF003)


126-126: Comment contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF003)


134-134: Comment contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF003)


134-134: Comment contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF003)


140-140: Do not catch blind exception: Exception

(BLE001)


150-150: Comment contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF003)


176-176: Consider moving this statement to an else block

(TRY300)


176-176: String contains ambiguous (FULLWIDTH EXCLAMATION MARK). Did you mean ! (EXCLAMATION MARK)?

(RUF001)


180-180: Use explicit conversion flag

Replace with conversion flag

(RUF010)


181-181: Do not catch blind exception: Exception

(BLE001)


183-183: Use explicit conversion flag

Replace with conversion flag

(RUF010)

src/script_chainer/context/script_chainer_context.py

29-29: Do not catch blind exception: Exception

(BLE001)


85-85: Avoid specifying long messages outside the exception class

(TRY003)

src/one_dragon/base/push/channel/we_plus_bot.py

4-4: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


58-58: Unused method argument: image

(ARG002)


59-59: Unused method argument: proxy_url

(ARG002)


65-65: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


68-68: Docstring contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF002)


68-68: Docstring contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF002)


111-111: Do not catch blind exception: Exception

(BLE001)


113-113: String contains ambiguous (FULLWIDTH COLON). Did you mean : (COLON)?

(RUF001)


113-113: Use explicit conversion flag

Replace with conversion flag

(RUF010)

src/one_dragon/base/push/channel/smtp.py

57-57: String contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF001)


82-82: Unused method argument: image

(ARG002)


83-83: Unused method argument: proxy_url

(ARG002)


89-89: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


92-92: Docstring contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF002)


92-92: Docstring contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF002)


132-132: Comment contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF003)


132-132: Comment contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF003)


146-146: Consider moving this statement to an else block

(TRY300)


148-148: Do not catch blind exception: Exception

(BLE001)


150-150: Use explicit conversion flag

Replace with conversion flag

(RUF010)


155-155: Do not catch blind exception: Exception

(BLE001)


157-157: Use explicit conversion flag

Replace with conversion flag

(RUF010)


196-196: String contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF001)


196-196: String contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF001)


196-196: String contains ambiguous (FULLWIDTH COLON). Did you mean : (COLON)?

(RUF001)


196-196: String contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF001)


199-199: Unpacked variable host is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)

src/one_dragon/base/push/channel/i_got.py

42-42: Unused method argument: image

(ARG002)


43-43: Unused method argument: proxy_url

(ARG002)


49-49: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


52-52: Docstring contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF002)


52-52: Docstring contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF002)


80-80: String contains ambiguous (FULLWIDTH COLON). Did you mean : (COLON)?

(RUF001)


82-82: Do not catch blind exception: Exception

(BLE001)


84-84: Use explicit conversion flag

Replace with conversion flag

(RUF010)

src/one_dragon/base/push/channel/bark.py

84-84: Unused method argument: image

(ARG002)


85-85: Unused method argument: proxy_url

(ARG002)


91-91: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


94-94: Docstring contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF002)


94-94: Docstring contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF002)


157-157: Do not catch blind exception: Exception

(BLE001)


158-158: Use explicit conversion flag

Replace with conversion flag

(RUF010)

src/one_dragon/base/push/push_service.py

114-114: Create your own exception

(TRY002)

src/one_dragon/base/push/channel/q_msg.py

4-4: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


50-50: Unused method argument: image

(ARG002)


51-51: Unused method argument: proxy_url

(ARG002)


57-57: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


60-60: Docstring contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF002)


60-60: Docstring contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF002)


77-77: Comment contains ambiguous (FULLWIDTH COLON). Did you mean : (COLON)?

(RUF003)


77-77: Comment contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF003)


92-92: String contains ambiguous (FULLWIDTH COLON). Did you mean : (COLON)?

(RUF001)


94-94: Do not catch blind exception: Exception

(BLE001)


96-96: String contains ambiguous (FULLWIDTH COLON). Did you mean : (COLON)?

(RUF001)


96-96: Use explicit conversion flag

Replace with conversion flag

(RUF010)

src/one_dragon/base/push/channel/chronocat.py

4-4: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


62-62: Unused method argument: image

(ARG002)


63-63: Unused method argument: proxy_url

(ARG002)


69-69: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


72-72: Docstring contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF002)


72-72: Docstring contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF002)


150-150: Do not catch blind exception: Exception

(BLE001)


152-152: Use explicit conversion flag

Replace with conversion flag

(RUF010)


182-182: String contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF001)


224-224: Consider moving this statement to an else block

(TRY300)


225-225: Do not catch blind exception: Exception

(BLE001)

src/one_dragon/base/push/channel/work_weixin.py

53-53: Unused method argument: proxy_url

(ARG002)


59-59: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


62-62: Docstring contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF002)


62-62: Docstring contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF002)


97-97: Do not catch blind exception: Exception

(BLE001)


104-104: String contains ambiguous (FULLWIDTH EXCLAMATION MARK). Did you mean ! (EXCLAMATION MARK)?

(RUF001)


111-111: Do not catch blind exception: Exception

(BLE001)


130-130: Do not catch blind exception: Exception

(BLE001)


131-131: Use explicit conversion flag

Replace with conversion flag

(RUF010)


133-133: Undefined name MatLike

(F821)


157-157: Probable use of insecure hash functions in hashlib: md5

(S324)


169-169: Do not catch blind exception: Exception

(BLE001)


176-176: String contains ambiguous (FULLWIDTH EXCLAMATION MARK). Did you mean ! (EXCLAMATION MARK)?

(RUF001)

src/script_chainer/win_exe/script_runner.py

235-235: Do not catch blind exception: Exception

(BLE001)


236-236: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


273-273: Do not catch blind exception: Exception

(BLE001)


274-274: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

src/one_dragon/base/push/channel/push_plus.py

48-48: String contains ambiguous (FULLWIDTH COLON). Did you mean : (COLON)?

(RUF001)


48-48: String contains ambiguous (FULLWIDTH SEMICOLON). Did you mean ; (SEMICOLON)?

(RUF001)


48-48: String contains ambiguous (FULLWIDTH COLON). Did you mean : (COLON)?

(RUF001)


78-78: Unused method argument: image

(ARG002)


79-79: Unused method argument: proxy_url

(ARG002)


85-85: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


88-88: Docstring contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF002)


88-88: Docstring contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF002)


121-121: String contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF001)


131-131: String contains ambiguous (FULLWIDTH EXCLAMATION MARK). Did you mean ! (EXCLAMATION MARK)?

(RUF001)


135-135: Do not catch blind exception: Exception

(BLE001)


136-136: Use explicit conversion flag

Replace with conversion flag

(RUF010)

src/one_dragon/base/push/push_config.py

98-98: Comment contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF003)

src/one_dragon/base/push/channel/feishu.py

57-57: String contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF001)


65-65: String contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF001)


83-83: Unused method argument: proxy_url

(ARG002)


89-89: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


92-92: Docstring contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF002)


92-92: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


92-92: Docstring contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF002)


113-113: Comment contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF003)


121-121: Comment contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF003)


121-121: Comment contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF003)


164-164: Do not catch blind exception: Exception

(BLE001)


165-165: Use explicit conversion flag

Replace with conversion flag

(RUF010)


178-178: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


234-234: Consider moving this statement to an else block

(TRY300)


236-236: Do not catch blind exception: Exception

(BLE001)

src/one_dragon/base/push/channel/ntfy.py

67-67: String contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF001)


84-84: Unused method argument: proxy_url

(ARG002)


90-90: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


93-93: Docstring contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF002)


93-93: Docstring contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF002)


157-157: String contains ambiguous (FULLWIDTH EXCLAMATION MARK). Did you mean ! (EXCLAMATION MARK)?

(RUF001)


157-157: String contains ambiguous (FULLWIDTH COLON). Did you mean : (COLON)?

(RUF001)


161-161: String contains ambiguous (FULLWIDTH EXCLAMATION MARK). Did you mean ! (EXCLAMATION MARK)?

(RUF001)


163-163: String contains ambiguous (FULLWIDTH EXCLAMATION MARK). Did you mean ! (EXCLAMATION MARK)?

(RUF001)


165-165: String contains ambiguous (FULLWIDTH EXCLAMATION MARK). Did you mean ! (EXCLAMATION MARK)?

(RUF001)


167-167: Do not catch blind exception: Exception

(BLE001)


168-168: Use explicit conversion flag

Replace with conversion flag

(RUF010)

src/one_dragon/base/push/curl_generator.py

22-22: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


25-25: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


51-51: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


63-63: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


131-131: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)


132-132: Comment contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF003)


155-155: Docstring contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF002)


155-155: Docstring contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF002)


187-187: Comment contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF003)


189-189: Comment contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF003)


193-193: Comment contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF003)


198-198: Comment contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF003)


210-210: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


230-230: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


246-246: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


251-251: Comment contains ambiguous (FULLWIDTH COLON). Did you mean : (COLON)?

(RUF003)


260-260: Unused method argument: style

(ARG002)


262-262: Docstring contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF002)


262-262: Docstring contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF002)


266-266: Docstring contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF002)


266-266: Docstring contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF002)

src/one_dragon/base/push/channel/push_deer.py

4-4: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


49-49: Unused method argument: image

(ARG002)


50-50: Unused method argument: proxy_url

(ARG002)


56-56: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


59-59: Docstring contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF002)


59-59: Docstring contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF002)


94-94: String contains ambiguous (FULLWIDTH COLON). Did you mean : (COLON)?

(RUF001)


96-96: Do not catch blind exception: Exception

(BLE001)


98-98: Use explicit conversion flag

Replace with conversion flag

(RUF010)

src/one_dragon/base/push/channel/push_me.py

4-4: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


50-50: Unused method argument: image

(ARG002)


51-51: Unused method argument: proxy_url

(ARG002)


57-57: Docstring contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF002)


60-60: Docstring contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF002)


60-60: Docstring contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF002)


93-93: String contains ambiguous (FULLWIDTH COLON). Did you mean : (COLON)?

(RUF001)


93-93: String contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF001)


95-95: Do not catch blind exception: Exception

(BLE001)


97-97: String contains ambiguous (FULLWIDTH COLON). Did you mean : (COLON)?

(RUF001)


97-97: Use explicit conversion flag

Replace with conversion flag

(RUF010)

src/one_dragon/base/push/push_channel.py

59-59: Undefined name MatLike

(F821)


85-85: Undefined name MatLike

(F821)


100-100: Comment contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF003)


121-121: Undefined name MatLike

(F821)

🔇 Additional comments (20)
src/one_dragon/base/push/push_email_services.py (1)

426-433: 代码变更正确

仅包含格式调整(添加空行和文件末尾换行符),无功能性变更。

src/script_chainer/win_exe/script_runner.py (1)

245-254: 推送调用正确添加了空值检查

在调用 ctx.push_service.push_async 前正确检查了 ctx is not None,避免了潜在的空指针异常。

src/one_dragon/base/push/channel/we_plus_bot.py (3)

17-51: 配置架构定义清晰

TOKEN、RECEIVER、VERSION 字段的配置架构定义合理,必填项标记正确。


53-113: 推送逻辑实现正确

实现要点:

  • 推送前验证配置(第 79-81 行)
  • 根据内容长度自动选择模板(800 字符阈值)
  • 15 秒超时设置合理
  • 响应状态检查正确

异常捕获用于提高推送操作的容错性,符合设计预期。


115-134: 配置验证逻辑完善

正确验证了必填字段 TOKEN 和 RECEIVER 的非空性。

src/one_dragon/base/push/channel/synology_chat.py (2)

47-95: 推送逻辑基本结构正确

配置验证、超时设置(15秒)、状态码检查和异常处理的整体结构合理。但请确保 API 调用格式正确(见前一条评论)。


97-116: 配置验证逻辑完善

正确验证了 URL 和 TOKEN 的非空性。

src/one_dragon/base/push/channel/q_msg.py (2)

16-43: 配置架构定义合理

KEY 和 TYPE 字段定义清晰,TYPE 使用 COMBO 类型并限定选项为 "send" 和 "group",设计合理。


98-120: 配置验证逻辑完善

正确验证了 KEY 和 TYPE 的非空性,并强制要求 TYPE 必须是 "send" 或 "group",与配置架构中的 options 定义一致。

src/one_dragon/base/push/push_channel_config.py (2)

12-17: 字段类型枚举定义完整

FieldTypeEnum 涵盖了常见的 UI 组件类型(TEXT、COMBO、KEY_VALUE、CODE_EDITOR),设计合理。


20-35: 数据类设计规范

PushChannelConfigField 使用 dataclass 定义清晰,要点:

  • 正确使用 field(default_factory=list) 避免可变默认值问题
  • 字段类型和默认值设置合理
  • Optional[str] 用于可选的 language 字段符合设计需求
src/one_dragon/base/push/channel/ai_botk.py (2)

12-47: 配置架构定义清晰

初始化方法正确定义了所需的配置字段,字段类型和验证规则设置合理。


114-140: 配置验证逻辑完善

验证方法正确检查了所有必需字段,并对目标类型进行了枚举值验证。

src/one_dragon/base/push/channel/server_chan.py (1)

27-78: 推送方法实现正确

使用 requests.postjson 参数发送数据是正确的做法,错误处理也较为完善。

src/one_dragon/base/push/channel/dingding.py (2)

39-55: 签名生成方法实现正确

HMAC-SHA256 签名逻辑符合钉钉机器人的签名要求。


90-97: 注意消息数据结构

Line 93 将标题和内容都放入 title 字段,Line 95 的 text 字段格式化为 Markdown。建议验证这是否符合实际需求,通常 title 应仅包含标题。

请确认钉钉通知的实际显示效果是否符合预期。如果系统通知栏显示异常,可能需要调整数据结构。

src/one_dragon/base/push/channel/push_deer.py (1)

100-115: 配置验证实现正确

验证逻辑正确检查必需的 KEY 字段。

src/one_dragon/base/push/channel/gotify.py (1)

82-95: 验证 Gotify API 数据格式

Line 93 使用 data=data 发送表单数据。请确认 Gotify API 是否接受 application/x-www-form-urlencoded 格式,还是需要 JSON 格式(json=data)。

根据 Gotify 官方文档,消息推送端点通常接受 JSON 格式。如果 API 期望 JSON,请应用以下更改:

             try:
-                response = requests.post(full_url, data=data, timeout=15)
+                response = requests.post(full_url, json=data, timeout=15)
src/one_dragon/base/push/channel/wx_pusher.py (2)

77-96: ID 解析逻辑实现良好

正确处理分号分隔的 ID 字符串,并验证至少配置一个推送目标。由于在 Line 69 已调用 validate_config,格式验证已完成,此处的类型转换是安全的。


98-107: HTML 内容格式化合理

使用 HTML 格式化消息内容,white-space: pre-wrap 样式保留了换行符,提供良好的显示效果。

Comment on lines +113 to +142
data = {
"title": title,
"body": content,
}

# 添加可选参数
archive = config.get('ARCHIVE')
if archive:
data["isArchive"] = archive

group = config.get('GROUP')
if group:
data["group"] = group

sound = config.get('SOUND')
if sound:
data["sound"] = sound

icon = config.get('ICON')
if icon:
data["icon"] = icon

level = config.get('LEVEL')
if level:
data["level"] = level

url_param = config.get('URL')
if url_param:
data["url"] = url_param

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

补充 DEVICE_KEY 的请求负载

Line 118 附近虽然给出了 DEVICE_KEY 配置项,但在构造 data 时没有带上它,导致填写的设备码被忽略,无法按设备定向推送。请补上该字段写入逻辑,恢复这一功能。

@@
             data = {
                 "title": title,
                 "body": content,
             }
+            device_key = config.get('DEVICE_KEY')
+            if device_key:
+                data["device_key"] = device_key
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
data = {
"title": title,
"body": content,
}
# 添加可选参数
archive = config.get('ARCHIVE')
if archive:
data["isArchive"] = archive
group = config.get('GROUP')
if group:
data["group"] = group
sound = config.get('SOUND')
if sound:
data["sound"] = sound
icon = config.get('ICON')
if icon:
data["icon"] = icon
level = config.get('LEVEL')
if level:
data["level"] = level
url_param = config.get('URL')
if url_param:
data["url"] = url_param
data = {
"title": title,
"body": content,
}
device_key = config.get('DEVICE_KEY')
if device_key:
data["device_key"] = device_key
# 添加可选参数
archive = config.get('ARCHIVE')
if archive:
data["isArchive"] = archive
group = config.get('GROUP')
if group:
data["group"] = group
sound = config.get('SOUND')
if sound:
data["sound"] = sound
icon = config.get('ICON')
if icon:
data["icon"] = icon
level = config.get('LEVEL')
if level:
data["level"] = level
url_param = config.get('URL')
if url_param:
data["url"] = url_param
🤖 Prompt for AI Agents
In src/one_dragon/base/push/channel/bark.py around lines 113 to 142, the payload
construction omits the DEVICE_KEY config so device-specific pushes are ignored;
read config.get('DEVICE_KEY') (or 'DEVICE') as used elsewhere and, if present,
add it into the data dict (e.g., data["device_key"] or the expected Bark field
name) so the request includes the device target; keep the same conditional
pattern used for other optional fields and ensure the key name matches the API
contract.

Comment on lines +144 to +150
headers = {"Content-Type": "application/json;charset=utf-8"}
response = requests.post(
url=url,
data=json.dumps(data),
headers=headers,
timeout=15
).json()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

尊重 proxy_url 参数以支持受限网络

Line 146 发起请求时没有带上 proxy_url,导致在需要代理的环境中该通道完全不可用。请复用基类的 get_proxy 并把结果传给 requests.post

@@
-            response = requests.post(
-                url=url,
-                data=json.dumps(data),
-                headers=headers,
-                timeout=15
-            ).json()
+            proxies = self.get_proxy(proxy_url)
+            response = requests.post(
+                url=url,
+                data=json.dumps(data),
+                headers=headers,
+                timeout=15,
+                proxies=proxies,
+            ).json()
🤖 Prompt for AI Agents
In src/one_dragon/base/push/channel/bark.py around lines 144 to 150, the
requests.post call doesn't use the proxy_url, so the channel fails in restricted
networks; call the class/base get_proxy method to obtain the proxy dict (if any)
and pass it to requests.post via the proxies parameter (or omit if None),
ensuring the proxy dict is reused and the request includes timeout, headers and
data as before.

Comment on lines +100 to +224
api_url = f'{url}/api/message/send'
message_content = f"{title}\n{content}"
success_count = 0
total_count = 0

# 发送个人消息 (chatType=1)
for user_id in user_ids:
total_count += 1
data = {
"peer": {"chatType": 1, "peerUin": user_id},
"elements": [
{
"elementType": 1,
"textElement": {"content": message_content},
}
],
}

if self._send_message(api_url, headers, data):
success_count += 1
log.info(f"Chronocat QQ个人消息:{user_id} 推送成功")
else:
log.error(f"Chronocat QQ个人消息:{user_id} 推送失败")

# 发送群消息 (chatType=2)
for group_id in group_ids:
total_count += 1
data = {
"peer": {"chatType": 2, "peerUin": group_id},
"elements": [
{
"elementType": 1,
"textElement": {"content": message_content},
}
],
}

if self._send_message(api_url, headers, data):
success_count += 1
log.info(f"Chronocat QQ群消息:{group_id} 推送成功")
else:
log.error(f"Chronocat QQ群消息:{group_id} 推送失败")

if success_count == 0:
return False, "所有消息推送失败"
elif success_count < total_count:
return True, f"部分推送成功 ({success_count}/{total_count})"
else:
return True, "推送成功"

except Exception as e:
log.error("Chronocat 推送异常", exc_info=True)
return False, f"Chronocat 推送异常: {str(e)}"

def validate_config(self, config: dict[str, str]) -> tuple[bool, str]:
"""
验证 Chronocat 配置

Args:
config: 配置字典

Returns:
tuple[bool, str]: 验证是否通过、错误信息
"""
url = config.get('URL', '')
token = config.get('TOKEN', '')
qq_config = config.get('QQ', '')

if len(url) == 0:
return False, "服务地址不能为空"

if len(token) == 0:
return False, "访问令牌不能为空"

if len(qq_config) == 0:
return False, "QQ 配置不能为空"

# 验证 QQ 配置格式
user_ids = self._extract_user_ids(qq_config)
group_ids = self._extract_group_ids(qq_config)

if not user_ids and not group_ids:
return False, "QQ 配置格式错误,需要包含 user_id 或 group_id"

return True, "配置验证通过"

def _extract_user_ids(self, qq_config: str) -> List[str]:
"""
从 QQ 配置中提取用户 ID

Args:
qq_config: QQ 配置字符串

Returns:
List[str]: 用户 ID 列表
"""
return re.findall(r"user_id=(\d+)", qq_config)

def _extract_group_ids(self, qq_config: str) -> List[str]:
"""
从 QQ 配置中提取群 ID

Args:
qq_config: QQ 配置字符串

Returns:
List[str]: 群 ID 列表
"""
return re.findall(r"group_id=(\d+)", qq_config)

def _send_message(self, url: str, headers: dict[str, str], data: dict) -> bool:
"""
发送单条消息

Args:
url: API 地址
headers: 请求头
data: 请求数据

Returns:
bool: 是否发送成功
"""
try:
response = requests.post(url, headers=headers, data=json.dumps(data), timeout=15)
return response.status_code == 200
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

为 Chronocat 渠道补充代理能力

push 接收了 proxy_url,但 _send_message 内的请求没有使用代理,部署在需要代理才能访问 Chronocat 服务的网络时会完全失败。

             headers = {
                 "Content-Type": "application/json",
                 "Authorization": f'Bearer {token}',
             }
 
             api_url = f'{url}/api/message/send'
             message_content = f"{title}\n{content}"
+            proxies = self.get_proxy(proxy_url)
@@
-                if self._send_message(api_url, headers, data):
+                if self._send_message(api_url, headers, data, proxies):
@@
-                if self._send_message(api_url, headers, data):
+                if self._send_message(api_url, headers, data, proxies):
@@
-    def _send_message(self, url: str, headers: dict[str, str], data: dict) -> bool:
+    def _send_message(
+        self,
+        url: str,
+        headers: dict[str, str],
+        data: dict,
+        proxies: dict | None,
+    ) -> bool:
         """
         发送单条消息
         """
         try:
-            response = requests.post(url, headers=headers, data=json.dumps(data), timeout=15)
+            response = requests.post(
+                url,
+                headers=headers,
+                data=json.dumps(data),
+                timeout=15,
+                proxies=proxies,
+            )
             return response.status_code == 200

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Ruff (0.14.2)

150-150: Do not catch blind exception: Exception

(BLE001)


152-152: Use explicit conversion flag

Replace with conversion flag

(RUF010)


182-182: String contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF001)


224-224: Consider moving this statement to an else block

(TRY300)

Comment on lines +118 to +125
if image is not None:
image_data = self.image_to_bytes(image)
if image_data is not None:
image_data.seek(0)
files = {'file': ('image.png', image_data, 'image/png')}
data = {'payload_json': json.dumps(message_payload)}
if "Content-Type" in headers:
del headers["Content-Type"]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

修正上传图片的文件类型声明

image_to_bytes 最终产出的是 JPEG 数据,但这里把文件名/类型写成了 PNG,Discord 会根据头信息解析,可能导致上传失败或显示异常。请调整为正确的 JPEG 标识。

-                    files = {'file': ('image.png', image_data, 'image/png')}
+                    files = {'file': ('image.jpg', image_data, 'image/jpeg')}
🧰 Tools
🪛 Ruff (0.14.2)

125-125: Use pop instead of key in dict followed by del dict[key]

Replace if statement with .pop(..., None)

(RUF051)

🤖 Prompt for AI Agents
In src/one_dragon/base/push/channel/discord.py around lines 118 to 125, the
uploaded image is actually JPEG data but the code labels it as PNG; change the
files entry to use a .jpg/.jpeg filename and the MIME type image/jpeg (e.g.
('image.jpg', image_data, 'image/jpeg')) so Discord parses the upload correctly,
keeping the existing payload_json handling and removal of the Content-Type
header.

Comment on lines +133 to +134
response = requests.post(message_url, headers=headers, data=data, files=files, timeout=30)
response.raise_for_status()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

请确保消息发送同样走代理链路

创建私聊频道时已走 proxies,但真正发送消息的请求忽略了代理配置,在需要代理才能访问 Discord 的环境中会直接失败。请把相同的 proxies 参数传入消息发送调用。

-            response = requests.post(message_url, headers=headers, data=data, files=files, timeout=30)
+            response = requests.post(
+                message_url,
+                headers=headers,
+                data=data,
+                files=files,
+                timeout=30,
+                proxies=proxies,
+            )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
response = requests.post(message_url, headers=headers, data=data, files=files, timeout=30)
response.raise_for_status()
response = requests.post(
message_url,
headers=headers,
data=data,
files=files,
timeout=30,
proxies=proxies,
)
response.raise_for_status()
🤖 Prompt for AI Agents
In src/one_dragon/base/push/channel/discord.py around lines 133 to 134, the POST
that sends the actual Discord message omits the proxies argument so it bypasses
the proxy chain used when creating the DM channel; update the requests.post call
to pass the same proxies variable (proxies=proxies) used earlier so the message
send uses the configured proxy and include it in the call signature along with
the existing headers, data, files and timeout.

Comment on lines +122 to +129
# 解析服务器地址和端口
host, port = server.split(":")
port_int = int(port) if port else None
# 连接SMTP服务器并发送邮件
smtp_server = (
smtplib.SMTP_SSL(host, port_int) if use_ssl
else smtplib.SMTP(host, port_int)
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

端口解析可能失败

如果服务器配置为 host: 格式(有冒号但端口为空),Line 124 的 int(port) 将抛出 ValueError。虽然 validate_config 检查了冒号的存在,但没有验证端口部分非空。

建议在 push 方法中添加错误处理,或在验证方法中增强检查:

         # 检查服务器格式
         if ":" not in server:
             return False, "邮件服务器格式不正确,应包含端口号(如:smtp.exmail.qq.com:465)"

         try:
             host, port = server.split(":")
+            if not port.strip():
+                return False, "端口号不能为空"
             port_int = int(port)
             if port_int < 1 or port_int > 65535:
                 return False, "端口号必须在1-65535之间"

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/one_dragon/base/push/channel/smtp.py around lines 122-129, the code calls
int(port) directly which will raise ValueError for inputs like "host:" where the
port substring is empty; modify the parsing to check that the port part is
non-empty (e.g., if port and port.strip(): port_int = int(port) else port_int =
None) and either fallback to a sensible default or raise a clear configuration
error; alternatively strengthen validate_config to reject empty port segments so
this code can assume a valid numeric port — ensure any ValueError is caught and
reraised or logged with a descriptive message referencing the invalid server
string.

Comment on lines +76 to +82
# 构建完整的请求URL
full_url = url + token

# 构建请求数据
message_text = f"{title}\n{content}"
payload_data = {"text": message_text}
data = "payload=" + json.dumps(payload_data)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

请验证 URL 拼接格式

第 77 行直接拼接 URL 和 Token(full_url = url + token)可能存在问题。通常 API 需要路径分隔符或查询参数格式。请确认 Synology Chat API 的正确 URL 格式。

第 82 行的 payload 格式("payload=" + json.dumps(...))也较为特殊,请确保这符合 Synology Chat 的 API 规范。

建议验证 Synology Chat 官方文档中的 API 调用格式:


🌐 Web query:

Synology Chat webhook API URL format and payload structure

💡 Result:

Summary — URL format

  • Incoming webhook endpoint:
    https://:/webapi/entry.cgi?api=SYNO.Chat.External&method=incoming&version=&token=<YOUR_TOKEN>. [1][2]

HTTP method and payload

  • Use POST. Synology accepts the message as either:
    1. form-encoded with a single field named payload whose value is a JSON string (payload={"text":"..."}), or
    2. a raw JSON body containing the payload (same JSON shape). Many examples use application/x-www-form-urlencoded with --data-urlencode 'payload=...'. You may also include the token in the POST body instead of the URL. [1][2]

Common payload fields (JSON)

  • text: string (message text).
  • file_url: URL to an image/file to attach (must be a reachable HTTP/HTTPS URL).
  • user_ids: array of user IDs to target (depends on API version/usage).
  • other fields (source, etc.) are used by some clients/libraries — follow what your integration UI shows. Example JSON: {"text":"This is a test","file_url":"https://example.com/img.jpg"}. [1][2][3]

Example curl (working)

Notes / tips

  • Don't wrap the token in URL-encoded quotes — token is the raw token string. Some DSM versions accept version=1 or version=2; version=2 is commonly used. If you get parameter errors, ensure api/method/version/token are present and payload is properly encoded. [1][2][3]

Sources
[1] Stack Overflow — “bash - Synology chat incoming webhooks” (includes Synology KB excerpt and curl examples).
[2] GitHub synochat (incoming webhook examples).
[3] Synology Chat integration / technical docs (overview of incoming/outgoing webhooks).


修复第 77 行 URL 拼接格式

Synology Chat API 的 Token 应作为 URL 查询参数(token=<YOUR_TOKEN>),而非直接字符串拼接。

第 77 行当前代码 full_url = url + token 会生成格式错误的 URL。根据 Synology 官方文档,正确的 URL 格式应为:

https://<HOST>:<PORT>/webapi/entry.cgi?api=SYNO.Chat.External&method=incoming&version=<N>&token=<YOUR_TOKEN>

需要验证 url 变量是否已包含完整的 API 端点和 token= 前缀。若 url 为基础端点,应改为 full_url = url + "&token=" + token 或类似格式。

第 82 行的 payload 格式 "payload=" + json.dumps(...) 是正确的,符合 Synology Chat 的 application/x-www-form-urlencoded 规范。

🤖 Prompt for AI Agents
In src/one_dragon/base/push/channel/synology_chat.py around lines 76 to 82, the
code concatenates the token directly (full_url = url + token) producing an
invalid URL; change the construction to ensure the token is appended as a query
parameter (token=<YOUR_TOKEN>) by checking whether url already contains a '?'
and then appending either '?token=' or '&token=' followed by the token (or
explicitly append '&token=' if url already includes other query params); keep
the payload construction as-is ("payload=" + json.dumps(...)) for
application/x-www-form-urlencoded.

Comment on lines +85 to +183
# 1. 先发文字
text_data = {"msgtype": "text", "text": {"content": f"{title}\n{content}"}}
try:
resp_obj = requests.post(url, data=json.dumps(text_data), headers=headers, timeout=15)
resp_obj.raise_for_status()

status = resp_obj.status_code
body_snip = (resp_obj.text or "")[:300] if hasattr(resp_obj, "text") else ""
resp = None

try:
resp = resp_obj.json()
except Exception as je:
error_msg = f"企业微信机器人文字响应解析失败: {type(je).__name__}: {je}; status={status}; body_snip={body_snip}"
error_messages.append(error_msg)
log.error(error_msg)

if resp and resp.get("errcode") == 0:
text_success = True
log.info("企业微信机器人文字推送成功!")
else:
errcode = resp.get("errcode") if resp else None
errmsg = resp.get("errmsg") if resp else None
error_msg = f"企业微信机器人文字推送失败! status={status}; errcode={errcode}; errmsg={errmsg}; body_snip={body_snip}"
error_messages.append(error_msg)
log.error(error_msg)
except Exception as e:
error_msg = f"企业微信机器人文字推送请求异常: {type(e).__name__}: {e}"
error_messages.append(error_msg)
log.error(error_msg)

# 2. 再发图片
if image is not None:
image_success = self._send_image(url, headers, image, error_messages)
else:
image_success = True

# 判断整体结果
if text_success and image_success:
return True, "推送成功"
elif text_success or image_success:
return True, f"部分推送成功: {'; '.join(error_messages)}" if error_messages else "部分推送成功"
else:
return False, f"推送失败: {'; '.join(error_messages)}" if error_messages else "推送失败"

except Exception as e:
return False, f"企业微信推送异常: {str(e)}"

def _send_image(self, url: str, headers: dict[str, str], image: MatLike, error_messages: list[str]) -> bool:
"""
发送图片到企业微信机器人

Args:
url: 机器人URL
headers: 请求头
image: 图片数据
error_messages: 错误消息列表

Returns:
bool: 是否发送成功
"""
# 企业微信机器人图片最大支持2MB
TARGET_SIZE = 2 * 1024 * 1024
img_bytes = self.image_to_bytes(image, max_bytes=TARGET_SIZE)
if img_bytes is None:
error_msg = "图片转换失败"
error_messages.append(error_msg)
log.error(error_msg)
return False
img_bytes = img_bytes.getvalue()
img_size = len(img_bytes)
img_base64 = base64.b64encode(img_bytes).decode('utf-8')
img_md5 = hashlib.md5(img_bytes).hexdigest()
img_data = {
"msgtype": "image",
"image": {"base64": img_base64, "md5": img_md5}
}

resp_obj = requests.post(url, data=json.dumps(img_data), headers=headers, timeout=15)
status = resp_obj.status_code
body_snip = (resp_obj.text or "")[:300] if hasattr(resp_obj, "text") else ""

try:
resp = resp_obj.json()
except Exception as je:
error_msg = f"企业微信机器人图片响应解析失败: {type(je).__name__}: {je}; status={status}; body_snip={body_snip}"
error_messages.append(error_msg)
log.error(error_msg)
return False

if resp and resp.get("errcode") == 0:
log.info("企业微信机器人图片推送成功!")
return True
else:
errcode = resp.get("errcode") if resp else None
errmsg = resp.get("errmsg") if resp else None
error_msg = f"企业微信机器人图片推送失败! status={status}; errcode={errcode}; errmsg={errmsg}; size={img_size}B; body_snip={body_snip}"
error_messages.append(error_msg)
log.error(error_msg)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

补齐企业微信渠道的代理支持

文本和图片请求都没有把 proxy_url 传入,代理网络下两种消息都会失败。同时 _send_image 需要能接收同一个代理配置。

         headers = {"Content-Type": "application/json;charset=utf-8"}
 
+        proxies = self.get_proxy(proxy_url)
         text_success = False
         image_success = False
         error_messages = []
 
         # 1. 先发文字
@@
-                resp_obj = requests.post(url, data=json.dumps(text_data), headers=headers, timeout=15)
+                resp_obj = requests.post(
+                    url,
+                    data=json.dumps(text_data),
+                    headers=headers,
+                    timeout=15,
+                    proxies=proxies,
+                )
@@
-                image_success = self._send_image(url, headers, image, error_messages)
+                image_success = self._send_image(url, headers, image, error_messages, proxies)
@@
-    def _send_image(self, url: str, headers: dict[str, str], image: MatLike, error_messages: list[str]) -> bool:
+    def _send_image(
+        self,
+        url: str,
+        headers: dict[str, str],
+        image: MatLike,
+        error_messages: list[str],
+        proxies: dict | None,
+    ) -> bool:
@@
-        resp_obj = requests.post(url, data=json.dumps(img_data), headers=headers, timeout=15)
+        resp_obj = requests.post(
+            url,
+            data=json.dumps(img_data),
+            headers=headers,
+            timeout=15,
+            proxies=proxies,
+        )

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Ruff (0.14.2)

97-97: Do not catch blind exception: Exception

(BLE001)


104-104: String contains ambiguous (FULLWIDTH EXCLAMATION MARK). Did you mean ! (EXCLAMATION MARK)?

(RUF001)


111-111: Do not catch blind exception: Exception

(BLE001)


130-130: Do not catch blind exception: Exception

(BLE001)


131-131: Use explicit conversion flag

Replace with conversion flag

(RUF010)


133-133: Undefined name MatLike

(F821)


157-157: Probable use of insecure hash functions in hashlib: md5

(S324)


169-169: Do not catch blind exception: Exception

(BLE001)


176-176: String contains ambiguous (FULLWIDTH EXCLAMATION MARK). Did you mean ! (EXCLAMATION MARK)?

(RUF001)

Comment on lines +133 to +185
def _send_image(self, url: str, headers: dict[str, str], image: MatLike, error_messages: list[str]) -> bool:
"""
发送图片到企业微信机器人

Args:
url: 机器人URL
headers: 请求头
image: 图片数据
error_messages: 错误消息列表

Returns:
bool: 是否发送成功
"""
# 企业微信机器人图片最大支持2MB
TARGET_SIZE = 2 * 1024 * 1024
img_bytes = self.image_to_bytes(image, max_bytes=TARGET_SIZE)
if img_bytes is None:
error_msg = "图片转换失败"
error_messages.append(error_msg)
log.error(error_msg)
return False
img_bytes = img_bytes.getvalue()
img_size = len(img_bytes)
img_base64 = base64.b64encode(img_bytes).decode('utf-8')
img_md5 = hashlib.md5(img_bytes).hexdigest()
img_data = {
"msgtype": "image",
"image": {"base64": img_base64, "md5": img_md5}
}

resp_obj = requests.post(url, data=json.dumps(img_data), headers=headers, timeout=15)
status = resp_obj.status_code
body_snip = (resp_obj.text or "")[:300] if hasattr(resp_obj, "text") else ""

try:
resp = resp_obj.json()
except Exception as je:
error_msg = f"企业微信机器人图片响应解析失败: {type(je).__name__}: {je}; status={status}; body_snip={body_snip}"
error_messages.append(error_msg)
log.error(error_msg)
return False

if resp and resp.get("errcode") == 0:
log.info("企业微信机器人图片推送成功!")
return True
else:
errcode = resp.get("errcode") if resp else None
errmsg = resp.get("errmsg") if resp else None
error_msg = f"企业微信机器人图片推送失败! status={status}; errcode={errcode}; errmsg={errmsg}; size={img_size}B; body_snip={body_snip}"
error_messages.append(error_msg)
log.error(error_msg)
return False

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

MatLike 未定义会导致模块导入失败

_send_image 的类型注解直接使用了 MatLike,但本文件并未导入该名称,在没有 from __future__ import annotations 的情况下会在模块加载阶段抛出 NameError。请补充正确的引用。

 import hashlib
 import json
 
 import requests
+from cv2.typing import MatLike
 
 from one_dragon.base.push.push_channel import PushChannel

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Ruff (0.14.2)

133-133: Undefined name MatLike

(F821)


157-157: Probable use of insecure hash functions in hashlib: md5

(S324)


169-169: Do not catch blind exception: Exception

(BLE001)


176-176: String contains ambiguous (FULLWIDTH EXCLAMATION MARK). Did you mean ! (EXCLAMATION MARK)?

(RUF001)

🤖 Prompt for AI Agents
In src/one_dragon/base/push/channel/work_weixin.py around lines 133 to 185, the
type annotation MatLike is used but not defined or imported which will raise
NameError at module import time; fix by adding "from __future__ import
annotations" at the top of the file so forward references are allowed (or
alternatively import MatLike from its defining module if you know its source)
and run linters/tests to confirm the module imports correctly.

Comment on lines +59 to +136
def image_to_bytes(self, image: MatLike, max_bytes: int | None = None) -> BytesIO | None:
"""
将图片转换为字节数组

Args:
image: 图片 RGB格式
max_bytes: 图片最大字节数 超过时压缩

Returns:
BytesIO: 图片数据 统一jpeg格式
"""
bgr_image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
retval, buffer = cv2.imencode('.jpg', bgr_image)

if retval:
image_bytes = BytesIO(buffer.tobytes())
if max_bytes is not None:
img_bytes = image_bytes.getvalue()
orig_size = len(img_bytes)
if orig_size > max_bytes:
return self._compress_image_bytes(bgr_image, max_bytes)

return image_bytes
else:
return None

def _compress_image_bytes(self, bgr_image: MatLike, max_bytes: int) -> BytesIO | None:
"""
自动将图片压缩为渐进式 JPG,使用二分搜索质量,尽量贴近 2MB 上限

Args:
bgr_image: 图片数据
max_bytes: 最大字节数

Returns:
BytesIO: 压缩后的图片数据 统一jpeg格式
"""

import cv2
best: BytesIO | None = None

# 二分搜索质量,尽量贴近 2MB
lo, hi = 30, 90
while lo <= hi:
q = (lo + hi) // 2
params = [
int(cv2.IMWRITE_JPEG_QUALITY), int(q),
int(cv2.IMWRITE_JPEG_OPTIMIZE), 1,
int(cv2.IMWRITE_JPEG_PROGRESSIVE), 1,
]
ok, enc = cv2.imencode('.jpg', bgr_image, params)
if not ok:
break
size = enc.nbytes
if size <= max_bytes:
best = BytesIO(enc.tobytes())
lo = q + 1 # 尝试更高质量
else:
hi = q - 1 # 降低质量

return best

def image_to_base64(self, image: MatLike, max_bytes: int | None = None) -> str | None:
"""
将图片转换为 base64 字符串

Args:
image: 图片
max_bytes: 图片最大字节数 超过时压缩

Returns:
str: 图片 base64 字符串
"""
image_bytes = self.image_to_bytes(image, max_bytes=max_bytes)
if image_bytes is None:
return None
image_bytes.seek(0)
return base64.b64encode(image_bytes.getvalue()).decode('utf-8')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

阻止模块导入失败:补齐 MatLike 依赖

MatLike 在多个函数注解中使用,但当前文件既没有导入也没有定义。Python 会在函数定义阶段解析这些注解,实际加载模块时会直接抛出 NameError: name 'MatLike' is not defined,导致整个推送模块无法初始化。请显式引入 MatLike,并为不支持该类型提示的旧版 OpenCV 提供回退,确保模块始终能被导入。

@@
-import base64
-from abc import abstractmethod, ABC
-from io import BytesIO
-
-import cv2
-
-from one_dragon.base.push.push_channel_config import PushChannelConfigField
+import base64
+from abc import abstractmethod, ABC
+from io import BytesIO
+from typing import Any
+
+import cv2
+
+try:
+    from cv2.typing import MatLike
+except (ImportError, AttributeError):
+    MatLike = Any
+
+from one_dragon.base.push.push_channel_config import PushChannelConfigField

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Ruff (0.14.2)

59-59: Undefined name MatLike

(F821)


85-85: Undefined name MatLike

(F821)


100-100: Comment contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF003)


121-121: Undefined name MatLike

(F821)

🤖 Prompt for AI Agents
In src/one_dragon/base/push/push_channel.py around lines 59 to 136, the type
name MatLike used in function annotations is not imported or defined which
raises NameError at import time; fix by adding a robust import/fallback at the
top of the module: try to import MatLike (preferably from numpy.typing or OpenCV
typing if available), and if that fails define MatLike = typing.Any (or use
typing.TYPE_CHECKING guard and conditional imports) so annotations remain valid
and the module can always be imported; update imports to include
typing.Any/TYPE_CHECKING and ensure no runtime dependency on MatLike resolution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant