From 055406d2077862ab9fe1c028246bbc2809990ca9 Mon Sep 17 00:00:00 2001 From: liuwei Date: Tue, 21 Apr 2026 16:37:23 +0800 Subject: [PATCH] =?UTF-8?q?feat(trendradar):=20=E6=96=B0=E5=A2=9E=E7=A9=BA?= =?UTF-8?q?=E6=9D=83=E9=99=90=E6=8F=92=E4=BB=B6=E5=B9=B6=E6=8E=A5=E5=85=A5?= =?UTF-8?q?webhook=E7=BE=A4=E6=9D=83=E9=99=90=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新建 trendradar_permission 空插件,仅注册 TRENDRADAR_WEBHOOK 功能用于后台群级开关 - webhook 发送前强制校验群权限,未开启群加入 blocked_groups 并拦截 - 更新对接文档,补充权限开关的启用步骤与返回字段说明 --- .../blueprints/trendradar_webhook.py | 38 ++++++++- plugins/trendradar_permission/__init__.py | 8 ++ plugins/trendradar_permission/config.toml | 4 + plugins/trendradar_permission/main.py | 85 +++++++++++++++++++ plugins/trendradar_webhook/README.md | 16 +++- 5 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 plugins/trendradar_permission/__init__.py create mode 100644 plugins/trendradar_permission/config.toml create mode 100644 plugins/trendradar_permission/main.py diff --git a/admin/dashboard/blueprints/trendradar_webhook.py b/admin/dashboard/blueprints/trendradar_webhook.py index 94796c5..0dcc23c 100644 --- a/admin/dashboard/blueprints/trendradar_webhook.py +++ b/admin/dashboard/blueprints/trendradar_webhook.py @@ -14,6 +14,7 @@ from typing import Any, Dict, List, Tuple from flask import Blueprint, current_app, jsonify, request from loguru import logger +from utils.robot_cmd.robot_command import Feature, GroupBotManager, PermissionStatus # 独立 webhook 路由,避免和后台管理接口混在一起。 @@ -140,6 +141,30 @@ def _build_wechat_text(title: str, content: str, payload: Dict[str, Any]) -> str return "\n".join(lines).strip() +def _filter_groups_by_permission(groups: List[str]) -> Tuple[List[str], List[str]]: + """按 TrendRadar 功能权限过滤目标群。 + + 返回: + 1. allowed_groups: 有权限可发送的群 + 2. blocked_groups: 未开启权限被拦截的群 + """ + feature = Feature.get_feature("TRENDRADAR_WEBHOOK") + # 若功能尚未注册,按“全部拦截”处理,避免误发;同时给出明确告警。 + if not feature: + logger.warning("[TrendRadarWebhook] 未发现 TRENDRADAR_WEBHOOK 功能注册,当前请求将被拦截") + return [], list(groups) + + allowed: List[str] = [] + blocked: List[str] = [] + for gid in groups: + status = GroupBotManager.get_group_permission(gid, feature) + if status == PermissionStatus.ENABLED: + allowed.append(gid) + else: + blocked.append(gid) + return allowed, blocked + + def _check_token(cfg: Dict[str, Any], payload: Dict[str, Any]) -> bool: """校验 webhook token。""" expected = str(cfg.get("token", "") or "").strip() @@ -172,6 +197,15 @@ def trendradar_webhook(): target_groups = _extract_target_groups(cfg, payload) if not target_groups: return jsonify({"success": False, "message": "未配置目标群"}), 400 + allowed_groups, blocked_groups = _filter_groups_by_permission(target_groups) + if not allowed_groups: + return jsonify( + { + "success": False, + "message": "目标群未开启 TrendRadar webhook 权限", + "blocked_groups": blocked_groups, + } + ), 403 text = _build_wechat_text(title, content, payload) loop = _get_or_create_loop() @@ -183,7 +217,7 @@ def trendradar_webhook(): await dashboard_server.client.send_text_message(group_id, text, "") timeout_seconds = int(cfg.get("send_timeout_seconds", 20)) - for group_id in target_groups: + for group_id in allowed_groups: try: fut = asyncio.run_coroutine_threadsafe(_send_once(group_id), loop) fut.result(timeout=max(timeout_seconds, 5)) @@ -200,10 +234,10 @@ def trendradar_webhook(): "success": len(failed_groups) == 0, "title": title, "sent_groups": sent_groups, + "blocked_groups": blocked_groups, "failed_groups": failed_groups, } ) except Exception as e: logger.error(f"[TrendRadarWebhook] 处理失败: {e}") return jsonify({"success": False, "message": str(e)}), 500 - diff --git a/plugins/trendradar_permission/__init__.py b/plugins/trendradar_permission/__init__.py new file mode 100644 index 0000000..1a6dd8e --- /dev/null +++ b/plugins/trendradar_permission/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +from .main import TrendRadarPermissionPlugin + + +def get_plugin(): + """返回插件实例。""" + return TrendRadarPermissionPlugin() + diff --git a/plugins/trendradar_permission/config.toml b/plugins/trendradar_permission/config.toml new file mode 100644 index 0000000..82f6285 --- /dev/null +++ b/plugins/trendradar_permission/config.toml @@ -0,0 +1,4 @@ +[TrendRadarPermission] +# 该插件只用于注册群权限开关,不处理消息。 +enable = true + diff --git a/plugins/trendradar_permission/main.py b/plugins/trendradar_permission/main.py new file mode 100644 index 0000000..9dd130b --- /dev/null +++ b/plugins/trendradar_permission/main.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +from typing import Any, Dict, List, Optional, Tuple + +from loguru import logger + +from base.plugin_common.message_plugin_interface import MessagePluginInterface +from base.plugin_common.plugin_interface import PluginStatus + + +class TrendRadarPermissionPlugin(MessagePluginInterface): + """TrendRadar Webhook 权限占位插件。 + + 设计说明: + 1. 本插件不处理任何聊天消息,也不提供命令; + 2. 唯一职责是注册 Feature:TRENDRADAR_WEBHOOK; + 3. 该 Feature 会出现在群插件权限配置页,便于按群启停 webhook 下发。 + """ + + FEATURE_KEY = "TRENDRADAR_WEBHOOK" + FEATURE_DESCRIPTION = "📡 TrendRadar Webhook推送 [群级开关]" + + @property + def name(self) -> str: + return "TrendRadar权限开关" + + @property + def version(self) -> str: + return "1.0.0" + + @property + def description(self) -> str: + return "仅用于注册TrendRadar webhook群权限,不处理消息。" + + @property + def author(self) -> str: + return "ABOT Team" + + @property + def command_prefix(self) -> Optional[str]: + return "" + + @property + def commands(self) -> List[str]: + return [] + + @property + def feature_key(self) -> Optional[str]: + return self.FEATURE_KEY + + @property + def feature_description(self) -> Optional[str]: + return self.FEATURE_DESCRIPTION + + def __init__(self): + super().__init__() + self.feature = self.register_feature() + self.enable = True + + def initialize(self, context: Dict[str, Any]) -> bool: + """初始化插件。 + + 这里只读取 enable 配置,默认启用。 + """ + self.LOG = logger + cfg = self._config.get("TrendRadarPermission", {}) + self.enable = bool(cfg.get("enable", True)) + self.LOG.info(f"[{self.name}] 初始化完成,enable={self.enable}, feature={self.feature_key}") + return True + + def start(self) -> bool: + self.status = PluginStatus.RUNNING + return True + + def stop(self) -> bool: + self.status = PluginStatus.STOPPED + return True + + def can_process(self, message: Dict[str, Any]) -> bool: + """该插件不处理任何消息。""" + return False + + async def process_message(self, message: Dict[str, Any]) -> Tuple[bool, Optional[str]]: + """空实现:永远不处理。""" + return False, None + diff --git a/plugins/trendradar_webhook/README.md b/plugins/trendradar_webhook/README.md index 573c483..2526621 100644 --- a/plugins/trendradar_webhook/README.md +++ b/plugins/trendradar_webhook/README.md @@ -23,6 +23,20 @@ allow_payload_target_groups = false send_timeout_seconds = 20 ``` +## 2.1 群权限开关(必做) + +已新增空插件用于权限管理: + +- [main.py](/D:/learn/abot/plugins/trendradar_permission/main.py) + +请在后台给目标群开启: + +`📡 TrendRadar Webhook推送 [群级开关]` + +说明: +1. webhook 在发送前会强制校验该群是否开启权限; +2. 未开启的群会被拦截,并在接口返回里出现在 `blocked_groups`。 + ## 3. TrendRadar 配置(Generic Webhook) 在 TrendRadar 里设置: @@ -55,5 +69,5 @@ send_timeout_seconds = 20 1. `success` 2. `sent_groups` +3. `blocked_groups` 3. `failed_groups` -