feat(trendradar): 新增空权限插件并接入webhook群权限校验
- 新建 trendradar_permission 空插件,仅注册 TRENDRADAR_WEBHOOK 功能用于后台群级开关 - webhook 发送前强制校验群权限,未开启群加入 blocked_groups 并拦截 - 更新对接文档,补充权限开关的启用步骤与返回字段说明
This commit is contained in:
@@ -14,6 +14,7 @@ from typing import Any, Dict, List, Tuple
|
|||||||
|
|
||||||
from flask import Blueprint, current_app, jsonify, request
|
from flask import Blueprint, current_app, jsonify, request
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
from utils.robot_cmd.robot_command import Feature, GroupBotManager, PermissionStatus
|
||||||
|
|
||||||
|
|
||||||
# 独立 webhook 路由,避免和后台管理接口混在一起。
|
# 独立 webhook 路由,避免和后台管理接口混在一起。
|
||||||
@@ -140,6 +141,30 @@ def _build_wechat_text(title: str, content: str, payload: Dict[str, Any]) -> str
|
|||||||
return "\n".join(lines).strip()
|
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:
|
def _check_token(cfg: Dict[str, Any], payload: Dict[str, Any]) -> bool:
|
||||||
"""校验 webhook token。"""
|
"""校验 webhook token。"""
|
||||||
expected = str(cfg.get("token", "") or "").strip()
|
expected = str(cfg.get("token", "") or "").strip()
|
||||||
@@ -172,6 +197,15 @@ def trendradar_webhook():
|
|||||||
target_groups = _extract_target_groups(cfg, payload)
|
target_groups = _extract_target_groups(cfg, payload)
|
||||||
if not target_groups:
|
if not target_groups:
|
||||||
return jsonify({"success": False, "message": "未配置目标群"}), 400
|
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)
|
text = _build_wechat_text(title, content, payload)
|
||||||
loop = _get_or_create_loop()
|
loop = _get_or_create_loop()
|
||||||
@@ -183,7 +217,7 @@ def trendradar_webhook():
|
|||||||
await dashboard_server.client.send_text_message(group_id, text, "")
|
await dashboard_server.client.send_text_message(group_id, text, "")
|
||||||
|
|
||||||
timeout_seconds = int(cfg.get("send_timeout_seconds", 20))
|
timeout_seconds = int(cfg.get("send_timeout_seconds", 20))
|
||||||
for group_id in target_groups:
|
for group_id in allowed_groups:
|
||||||
try:
|
try:
|
||||||
fut = asyncio.run_coroutine_threadsafe(_send_once(group_id), loop)
|
fut = asyncio.run_coroutine_threadsafe(_send_once(group_id), loop)
|
||||||
fut.result(timeout=max(timeout_seconds, 5))
|
fut.result(timeout=max(timeout_seconds, 5))
|
||||||
@@ -200,10 +234,10 @@ def trendradar_webhook():
|
|||||||
"success": len(failed_groups) == 0,
|
"success": len(failed_groups) == 0,
|
||||||
"title": title,
|
"title": title,
|
||||||
"sent_groups": sent_groups,
|
"sent_groups": sent_groups,
|
||||||
|
"blocked_groups": blocked_groups,
|
||||||
"failed_groups": failed_groups,
|
"failed_groups": failed_groups,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[TrendRadarWebhook] 处理失败: {e}")
|
logger.error(f"[TrendRadarWebhook] 处理失败: {e}")
|
||||||
return jsonify({"success": False, "message": str(e)}), 500
|
return jsonify({"success": False, "message": str(e)}), 500
|
||||||
|
|
||||||
|
|||||||
8
plugins/trendradar_permission/__init__.py
Normal file
8
plugins/trendradar_permission/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from .main import TrendRadarPermissionPlugin
|
||||||
|
|
||||||
|
|
||||||
|
def get_plugin():
|
||||||
|
"""返回插件实例。"""
|
||||||
|
return TrendRadarPermissionPlugin()
|
||||||
|
|
||||||
4
plugins/trendradar_permission/config.toml
Normal file
4
plugins/trendradar_permission/config.toml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[TrendRadarPermission]
|
||||||
|
# 该插件只用于注册群权限开关,不处理消息。
|
||||||
|
enable = true
|
||||||
|
|
||||||
85
plugins/trendradar_permission/main.py
Normal file
85
plugins/trendradar_permission/main.py
Normal file
@@ -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
|
||||||
|
|
||||||
@@ -23,6 +23,20 @@ allow_payload_target_groups = false
|
|||||||
send_timeout_seconds = 20
|
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)
|
## 3. TrendRadar 配置(Generic Webhook)
|
||||||
|
|
||||||
在 TrendRadar 里设置:
|
在 TrendRadar 里设置:
|
||||||
@@ -55,5 +69,5 @@ send_timeout_seconds = 20
|
|||||||
|
|
||||||
1. `success`
|
1. `success`
|
||||||
2. `sent_groups`
|
2. `sent_groups`
|
||||||
|
3. `blocked_groups`
|
||||||
3. `failed_groups`
|
3. `failed_groups`
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user