mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-21 16:23:34 +08:00
fix(agent): bind channel admin identity (#6294)
This commit is contained in:
@@ -373,6 +373,7 @@ class MoviePilotAgent:
|
||||
channel: str = None,
|
||||
source: str = None,
|
||||
username: str = None,
|
||||
is_channel_admin: Optional[bool] = None,
|
||||
original_message_id: Optional[str] = None,
|
||||
original_chat_id: Optional[str] = None,
|
||||
replay_mode: ReplyMode = ReplyMode.DISPATCH,
|
||||
@@ -385,6 +386,7 @@ class MoviePilotAgent:
|
||||
self.channel = channel
|
||||
self.source = source
|
||||
self.username = username
|
||||
self.is_channel_admin = is_channel_admin
|
||||
self.original_message_id = original_message_id
|
||||
self.original_chat_id = original_chat_id
|
||||
self.reply_mode = replay_mode
|
||||
@@ -934,6 +936,8 @@ class MoviePilotAgent:
|
||||
"anthropic",
|
||||
}:
|
||||
return True
|
||||
if self.channel and self.channel != MessageChannel.Web.value:
|
||||
return self.is_channel_admin is True
|
||||
if not self.username:
|
||||
return False
|
||||
try:
|
||||
@@ -2429,6 +2433,7 @@ class _MessageTask:
|
||||
channel: Optional[str] = None
|
||||
source: Optional[str] = None
|
||||
username: Optional[str] = None
|
||||
is_channel_admin: Optional[bool] = None
|
||||
original_message_id: Optional[str] = None
|
||||
original_chat_id: Optional[str] = None
|
||||
processing_status: Optional[dict] = None
|
||||
@@ -2596,6 +2601,7 @@ class AgentManager:
|
||||
channel: str = None,
|
||||
source: str = None,
|
||||
username: str = None,
|
||||
is_channel_admin: Optional[bool] = None,
|
||||
original_message_id: Optional[str] = None,
|
||||
original_chat_id: Optional[str] = None,
|
||||
reply_mode: ReplyMode = ReplyMode.DISPATCH,
|
||||
@@ -2623,6 +2629,7 @@ class AgentManager:
|
||||
channel=channel,
|
||||
source=source,
|
||||
username=username,
|
||||
is_channel_admin=is_channel_admin,
|
||||
original_message_id=original_message_id,
|
||||
original_chat_id=original_chat_id,
|
||||
reply_mode=reply_mode,
|
||||
@@ -2773,6 +2780,7 @@ class AgentManager:
|
||||
"channel": task.channel,
|
||||
"source": task.source,
|
||||
"username": task.username,
|
||||
"is_channel_admin": task.is_channel_admin,
|
||||
"original_message_id": task.original_message_id,
|
||||
"original_chat_id": task.original_chat_id,
|
||||
"replay_mode": task.reply_mode,
|
||||
@@ -2792,6 +2800,7 @@ class AgentManager:
|
||||
agent.channel = task.channel
|
||||
agent.source = task.source
|
||||
agent.username = task.username
|
||||
agent.is_channel_admin = task.is_channel_admin
|
||||
agent.original_message_id = task.original_message_id
|
||||
agent.original_chat_id = task.original_chat_id
|
||||
agent.reply_mode = task.reply_mode
|
||||
|
||||
@@ -19,7 +19,7 @@ from app.agent.policy.sanitizer import (
|
||||
from app.agent.tools.tags import ToolTag
|
||||
from app.chain import ChainBase
|
||||
from app.core.config import settings
|
||||
from app.db.user_oper import UserOper
|
||||
from app.helper.agent import matches_channel_admin
|
||||
from app.helper.service import ServiceConfigHelper
|
||||
from app.log import logger
|
||||
from app.schemas import Notification
|
||||
@@ -403,8 +403,8 @@ class MoviePilotTool(BaseTool, metaclass=ABCMeta):
|
||||
|
||||
:return: 当前调用者是系统管理员、渠道管理员或显式管理员上下文时返回 True
|
||||
"""
|
||||
if bool(self._agent_context.get("is_admin")):
|
||||
return True
|
||||
if "is_admin" in self._agent_context:
|
||||
return self._agent_context.get("is_admin") is True
|
||||
|
||||
if not self._channel or not self._source:
|
||||
return False
|
||||
@@ -509,12 +509,10 @@ class MoviePilotTool(BaseTool, metaclass=ABCMeta):
|
||||
|
||||
async def _check_permission(self) -> Optional[str]:
|
||||
"""
|
||||
检查用户权限:
|
||||
1. 首先检查工具是否需要管理员权限
|
||||
2. 如果需要管理员权限,则检查用户是否是渠道管理员
|
||||
3. 如果渠道没有设置管理员名单,则检查用户是否是系统管理员
|
||||
4. 如果都不是系统管理员,检查用户ID是否等于渠道配置的用户ID
|
||||
5. 如果都不是,返回权限拒绝消息
|
||||
检查管理员工具权限。
|
||||
|
||||
Agent 共享上下文中的显式管理员事实优先;没有该事实的旧调用才按渠道
|
||||
管理员名单回查,并保留无消息渠道内部调用的兼容行为。
|
||||
"""
|
||||
if not self._require_admin:
|
||||
return None
|
||||
@@ -522,7 +520,9 @@ class MoviePilotTool(BaseTool, metaclass=ABCMeta):
|
||||
if await self.is_admin_user():
|
||||
return None
|
||||
|
||||
if not self._channel or not self._source:
|
||||
if "is_admin" not in self._agent_context and (
|
||||
not self._channel or not self._source
|
||||
):
|
||||
return None
|
||||
|
||||
return (
|
||||
@@ -536,13 +536,11 @@ class MoviePilotTool(BaseTool, metaclass=ABCMeta):
|
||||
"""
|
||||
检查当前消息渠道身份是否具备管理员权限。
|
||||
|
||||
:return: 当前渠道用户是渠道管理员、系统管理员或默认接收人时返回 True
|
||||
:return: 当前渠道稳定用户 ID 位于显式管理员名单时返回 True
|
||||
"""
|
||||
if not self._channel or not self._source:
|
||||
return False
|
||||
|
||||
# 渠道配置来自 SystemConfigOper 内存缓存,可以直接读取;
|
||||
# 只有用户信息需要走异步数据库查询。
|
||||
user_id_str = str(self._user_id) if self._user_id else None
|
||||
|
||||
channel_type_map = {
|
||||
@@ -578,58 +576,17 @@ class MoviePilotTool(BaseTool, metaclass=ABCMeta):
|
||||
"qqbot": "QQBOT_ADMINS",
|
||||
}
|
||||
|
||||
user_id_key_map = {
|
||||
"telegram": "TELEGRAM_CHAT_ID",
|
||||
"vocechat": "VOCECHAT_CHANNEL_ID",
|
||||
"wechat": "WECHAT_BOT_CHAT_ID",
|
||||
"feishu": "FEISHU_OPEN_ID",
|
||||
"wechatclawbot": "WECHATCLAWBOT_DEFAULT_TARGET",
|
||||
"discord": "DISCORD_CHANNEL_ID",
|
||||
"slack": "SLACK_CHANNEL",
|
||||
"qqbot": "QQ_OPENID",
|
||||
}
|
||||
|
||||
admin_key = admin_key_map.get(channel_type)
|
||||
user_id_key = user_id_key_map.get(channel_type)
|
||||
|
||||
try:
|
||||
configs = ServiceConfigHelper.get_notification_configs()
|
||||
for config in configs:
|
||||
if config.name == self._source and config.config:
|
||||
channel_admins = config.config.get(admin_key) if admin_key else None
|
||||
if channel_admins:
|
||||
admin_list = [
|
||||
aid.strip()
|
||||
for aid in str(channel_admins).split(",")
|
||||
if aid.strip()
|
||||
]
|
||||
if user_id_str and user_id_str in admin_list:
|
||||
return True
|
||||
|
||||
user = (
|
||||
await UserOper().async_get_by_name(self._username)
|
||||
if self._username
|
||||
else None
|
||||
)
|
||||
if user and user.is_superuser:
|
||||
return True
|
||||
|
||||
return False
|
||||
else:
|
||||
user = (
|
||||
await UserOper().async_get_by_name(self._username)
|
||||
if self._username
|
||||
else None
|
||||
)
|
||||
if user and user.is_superuser:
|
||||
return True
|
||||
|
||||
if user_id_key:
|
||||
config_user_id = config.config.get(user_id_key)
|
||||
if config_user_id and str(config_user_id) == user_id_str:
|
||||
return True
|
||||
|
||||
return False
|
||||
return matches_channel_admin(
|
||||
config.config,
|
||||
admin_key,
|
||||
user_id_str,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"检查权限失败: {summarize_error(e)}")
|
||||
|
||||
|
||||
@@ -152,6 +152,7 @@ class MessageChain(ChainBase):
|
||||
source=source,
|
||||
userid=userid,
|
||||
username=username,
|
||||
is_channel_admin=info.is_channel_admin,
|
||||
text=text,
|
||||
original_message_id=original_message_id,
|
||||
original_chat_id=original_chat_id,
|
||||
@@ -174,6 +175,7 @@ class MessageChain(ChainBase):
|
||||
audio_refs: Optional[List[str]] = None,
|
||||
files: Optional[List[CommingMessage.MessageAttachment]] = None,
|
||||
reply_to_message_id: Optional[Union[str, int]] = None,
|
||||
is_channel_admin: Optional[bool] = None,
|
||||
) -> None:
|
||||
"""
|
||||
识别消息内容,执行操作
|
||||
@@ -214,6 +216,7 @@ class MessageChain(ChainBase):
|
||||
source=source,
|
||||
userid=userid,
|
||||
username=username,
|
||||
is_channel_admin=is_channel_admin,
|
||||
text=text,
|
||||
original_message_id=original_message_id,
|
||||
original_chat_id=original_chat_id,
|
||||
@@ -271,6 +274,7 @@ class MessageChain(ChainBase):
|
||||
source=source,
|
||||
userid=userid,
|
||||
username=username,
|
||||
is_channel_admin=is_channel_admin,
|
||||
text=text,
|
||||
original_message_id=original_message_id,
|
||||
original_chat_id=original_chat_id,
|
||||
@@ -305,6 +309,7 @@ class MessageChain(ChainBase):
|
||||
audio_refs: Optional[List[str]] = None,
|
||||
files: Optional[List[CommingMessage.MessageAttachment]] = None,
|
||||
has_audio_input: bool = False,
|
||||
is_channel_admin: Optional[bool] = None,
|
||||
) -> bool:
|
||||
"""将 TG/飞书中的确认控制文本交回所属 Agent 会话。"""
|
||||
if channel not in {MessageChannel.Telegram, MessageChannel.Feishu}:
|
||||
@@ -331,6 +336,7 @@ class MessageChain(ChainBase):
|
||||
source=source,
|
||||
userid=userid,
|
||||
username=username,
|
||||
is_channel_admin=is_channel_admin,
|
||||
original_message_id=original_message_id,
|
||||
original_chat_id=original_chat_id,
|
||||
images=images,
|
||||
@@ -354,6 +360,7 @@ class MessageChain(ChainBase):
|
||||
has_audio_input: bool = False,
|
||||
processing_status: Optional[_ProcessingStatus] = None,
|
||||
reply_to_message_id: Optional[Union[str, int]] = None,
|
||||
is_channel_admin: Optional[bool] = None,
|
||||
) -> bool:
|
||||
"""执行实际消息路由,便于统一包裹处理中状态。"""
|
||||
|
||||
@@ -365,6 +372,7 @@ class MessageChain(ChainBase):
|
||||
source=source,
|
||||
userid=userid,
|
||||
username=username,
|
||||
is_channel_admin=is_channel_admin,
|
||||
original_message_id=original_message_id,
|
||||
original_chat_id=original_chat_id,
|
||||
processing_status=processing_status,
|
||||
@@ -430,6 +438,7 @@ class MessageChain(ChainBase):
|
||||
source=source,
|
||||
userid=userid,
|
||||
username=username,
|
||||
is_channel_admin=is_channel_admin,
|
||||
original_message_id=original_message_id,
|
||||
original_chat_id=original_chat_id,
|
||||
images=images,
|
||||
@@ -490,6 +499,7 @@ class MessageChain(ChainBase):
|
||||
source=source,
|
||||
userid=userid,
|
||||
username=username,
|
||||
is_channel_admin=is_channel_admin,
|
||||
original_message_id=original_message_id,
|
||||
original_chat_id=original_chat_id,
|
||||
images=images,
|
||||
@@ -747,6 +757,7 @@ class MessageChain(ChainBase):
|
||||
original_message_id: Optional[Union[str, int]] = None,
|
||||
original_chat_id: Optional[str] = None,
|
||||
processing_status: Optional[_ProcessingStatus] = None,
|
||||
is_channel_admin: Optional[bool] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
处理按钮回调
|
||||
@@ -815,6 +826,7 @@ class MessageChain(ChainBase):
|
||||
source=source,
|
||||
userid=userid,
|
||||
username=username,
|
||||
is_channel_admin=is_channel_admin,
|
||||
original_message_id=original_message_id,
|
||||
original_chat_id=original_chat_id,
|
||||
):
|
||||
@@ -954,6 +966,7 @@ class MessageChain(ChainBase):
|
||||
username: str,
|
||||
original_message_id: Optional[Union[str, int]] = None,
|
||||
original_chat_id: Optional[str] = None,
|
||||
is_channel_admin: Optional[bool] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
将 Agent 按钮选择回传为同一会话中的下一条用户消息。
|
||||
@@ -999,6 +1012,7 @@ class MessageChain(ChainBase):
|
||||
source=source,
|
||||
userid=userid,
|
||||
username=username,
|
||||
is_channel_admin=is_channel_admin,
|
||||
session_id=request.session_id,
|
||||
)
|
||||
|
||||
@@ -1527,6 +1541,7 @@ class MessageChain(ChainBase):
|
||||
files: Optional[List[CommingMessage.MessageAttachment]] = None,
|
||||
session_id: Optional[str] = None,
|
||||
has_audio_input: bool = False,
|
||||
is_channel_admin: Optional[bool] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
处理AI智能体消息
|
||||
@@ -1644,6 +1659,7 @@ class MessageChain(ChainBase):
|
||||
"channel": channel.value if channel else None,
|
||||
"source": source,
|
||||
"username": username,
|
||||
"is_channel_admin": is_channel_admin,
|
||||
"original_message_id": str(original_message_id)
|
||||
if original_message_id
|
||||
else None,
|
||||
|
||||
@@ -7,6 +7,27 @@ _WEB_AGENT_EDIT_QUEUES: dict[str, list[Queue[dict]]] = {}
|
||||
_WEB_AGENT_EDIT_LOCK = Lock()
|
||||
|
||||
|
||||
def matches_channel_admin(
|
||||
config: Optional[dict],
|
||||
admin_key: str,
|
||||
*principal_ids: Optional[Union[str, int]],
|
||||
) -> bool:
|
||||
"""按渠道配置中的稳定主体 ID 判断管理员身份。"""
|
||||
admins = {
|
||||
item.strip()
|
||||
for item in str((config or {}).get(admin_key) or "").split(",")
|
||||
if item.strip()
|
||||
}
|
||||
if not admins:
|
||||
return False
|
||||
candidates = {
|
||||
str(principal_id).strip()
|
||||
for principal_id in principal_ids
|
||||
if principal_id is not None and str(principal_id).strip()
|
||||
}
|
||||
return bool(admins.intersection(candidates))
|
||||
|
||||
|
||||
def normalize_web_agent_button_rows(buttons: Optional[list[list[dict]]]) -> list[list[dict]]:
|
||||
"""
|
||||
将消息按钮转换为 WebAgent 前端可识别的按钮行。
|
||||
|
||||
@@ -5,6 +5,7 @@ from urllib.parse import quote, unquote
|
||||
|
||||
from app.core.context import MediaInfo, Context
|
||||
from app.core.event import eventmanager
|
||||
from app.helper.agent import matches_channel_admin
|
||||
from app.log import logger
|
||||
from app.modules import _ModuleBase, _MessageBase
|
||||
from app.schemas import (
|
||||
@@ -194,7 +195,7 @@ class DiscordModule(_ModuleBase, _MessageBase[Discord]):
|
||||
chat_id = msg_json.get("chat_id")
|
||||
if callback_data and userid:
|
||||
if str(callback_data).strip().startswith("/") and self._should_reject_admin_command(
|
||||
client_config.config, userid, username
|
||||
client_config.config, userid
|
||||
):
|
||||
self._send_admin_denied(client, userid, chat_id)
|
||||
return None
|
||||
@@ -207,6 +208,9 @@ class DiscordModule(_ModuleBase, _MessageBase[Discord]):
|
||||
source=client_config.name,
|
||||
userid=userid,
|
||||
username=username,
|
||||
is_channel_admin=matches_channel_admin(
|
||||
client_config.config, "DISCORD_ADMINS", userid
|
||||
),
|
||||
text=f"CALLBACK:{callback_data}",
|
||||
is_callback=True,
|
||||
callback_data=callback_data,
|
||||
@@ -223,7 +227,7 @@ class DiscordModule(_ModuleBase, _MessageBase[Discord]):
|
||||
files = self._extract_files(msg_json)
|
||||
if (text or images or audio_refs or files) and userid:
|
||||
if text and text.startswith("/") and self._should_reject_admin_command(
|
||||
client_config.config, userid, username
|
||||
client_config.config, userid
|
||||
):
|
||||
self._send_admin_denied(client, userid, chat_id)
|
||||
return None
|
||||
@@ -238,6 +242,9 @@ class DiscordModule(_ModuleBase, _MessageBase[Discord]):
|
||||
source=client_config.name,
|
||||
userid=userid,
|
||||
username=username,
|
||||
is_channel_admin=matches_channel_admin(
|
||||
client_config.config, "DISCORD_ADMINS", userid
|
||||
),
|
||||
text=text,
|
||||
chat_id=str(chat_id) if chat_id else None,
|
||||
images=images,
|
||||
|
||||
@@ -53,6 +53,7 @@ from lark_oapi.event.callback.model.p2_card_action_trigger import (
|
||||
from app.core.config import settings
|
||||
from app.core.context import Context, MediaInfo
|
||||
from app.db.user_oper import UserOper
|
||||
from app.helper.agent import matches_channel_admin
|
||||
from app.log import logger
|
||||
from app.schemas import CommingMessage, Notification
|
||||
from app.schemas.types import MessageChannel, NotificationType
|
||||
@@ -687,6 +688,12 @@ class Feishu:
|
||||
source=self._name,
|
||||
userid=userid,
|
||||
username=username,
|
||||
is_channel_admin=matches_channel_admin(
|
||||
{"FEISHU_ADMINS": ",".join(self._admins)},
|
||||
"FEISHU_ADMINS",
|
||||
open_id,
|
||||
user_id,
|
||||
),
|
||||
text=f"CALLBACK:{callback_data}",
|
||||
is_callback=True,
|
||||
callback_data=callback_data,
|
||||
@@ -724,6 +731,12 @@ class Feishu:
|
||||
source=self._name,
|
||||
userid=userid,
|
||||
username=username,
|
||||
is_channel_admin=matches_channel_admin(
|
||||
{"FEISHU_ADMINS": ",".join(self._admins)},
|
||||
"FEISHU_ADMINS",
|
||||
open_id,
|
||||
user_id,
|
||||
),
|
||||
text=text,
|
||||
message_id=message.get("message_id"),
|
||||
chat_id=message.get("chat_id"),
|
||||
|
||||
@@ -9,6 +9,7 @@ from urllib.parse import quote, unquote
|
||||
from typing import Optional, List, Tuple, Union, Any
|
||||
|
||||
from app.core.context import MediaInfo, Context
|
||||
from app.helper.agent import matches_channel_admin
|
||||
from app.log import logger
|
||||
from app.modules import _ModuleBase, _MessageBase
|
||||
from app.modules.qqbot.qqbot import QQBot
|
||||
@@ -174,6 +175,9 @@ class QQBotModule(_ModuleBase, _MessageBase[QQBot]):
|
||||
source=client_config.name,
|
||||
userid=user_openid,
|
||||
username=user_openid,
|
||||
is_channel_admin=matches_channel_admin(
|
||||
client_config.config, "QQBOT_ADMINS", user_openid
|
||||
),
|
||||
text=content,
|
||||
images=images,
|
||||
audio_refs=audio_refs,
|
||||
@@ -186,7 +190,7 @@ class QQBotModule(_ModuleBase, _MessageBase[QQBot]):
|
||||
# 群聊用 group:group_openid 作为 userid,便于回复时识别
|
||||
userid = f"group:{group_openid}" if group_openid else member_openid
|
||||
if content.startswith("/") and self._should_reject_admin_command(
|
||||
client_config.config, member_openid, userid
|
||||
client_config.config, member_openid
|
||||
):
|
||||
self._send_admin_denied(client, userid)
|
||||
return None
|
||||
@@ -200,6 +204,9 @@ class QQBotModule(_ModuleBase, _MessageBase[QQBot]):
|
||||
source=client_config.name,
|
||||
userid=userid,
|
||||
username=member_openid or group_openid,
|
||||
is_channel_admin=matches_channel_admin(
|
||||
client_config.config, "QQBOT_ADMINS", member_openid
|
||||
),
|
||||
text=content,
|
||||
images=images,
|
||||
audio_refs=audio_refs,
|
||||
|
||||
@@ -6,6 +6,7 @@ from urllib.parse import quote, unquote
|
||||
|
||||
from app.core.context import MediaInfo, Context
|
||||
from app.core.event import eventmanager
|
||||
from app.helper.agent import matches_channel_admin
|
||||
from app.log import logger
|
||||
from app.modules import _ModuleBase, _MessageBase
|
||||
from app.modules.slack.slack import Slack
|
||||
@@ -279,7 +280,7 @@ class SlackModule(_ModuleBase, _MessageBase[Slack]):
|
||||
text = msg_json.get("text")
|
||||
username = msg_json.get("user")
|
||||
if text and text.startswith("/") and self._should_reject_admin_command(
|
||||
client_config.config, userid, username
|
||||
client_config.config, userid
|
||||
):
|
||||
self._send_admin_denied(client, userid)
|
||||
return None
|
||||
@@ -295,7 +296,7 @@ class SlackModule(_ModuleBase, _MessageBase[Slack]):
|
||||
text = f"CALLBACK:{callback_data}"
|
||||
username = msg_json.get("user", {}).get("name")
|
||||
if str(callback_data).strip().startswith("/") and self._should_reject_admin_command(
|
||||
client_config.config, userid, username
|
||||
client_config.config, userid
|
||||
):
|
||||
self._send_admin_denied(client, userid)
|
||||
return None
|
||||
@@ -319,6 +320,9 @@ class SlackModule(_ModuleBase, _MessageBase[Slack]):
|
||||
source=client_config.name,
|
||||
userid=userid,
|
||||
username=username,
|
||||
is_channel_admin=matches_channel_admin(
|
||||
client_config.config, "SLACK_ADMINS", userid
|
||||
),
|
||||
text=text,
|
||||
is_callback=True,
|
||||
callback_data=callback_data,
|
||||
@@ -349,7 +353,7 @@ class SlackModule(_ModuleBase, _MessageBase[Slack]):
|
||||
text = msg_json.get("callback_id")
|
||||
username = msg_json.get("user", {}).get("username")
|
||||
if text and text.startswith("/") and self._should_reject_admin_command(
|
||||
client_config.config, userid, username
|
||||
client_config.config, userid
|
||||
):
|
||||
self._send_admin_denied(client, userid)
|
||||
return None
|
||||
@@ -358,7 +362,7 @@ class SlackModule(_ModuleBase, _MessageBase[Slack]):
|
||||
text = msg_json.get("command")
|
||||
username = msg_json.get("user_name")
|
||||
chat_id = msg_json.get("channel_id")
|
||||
if self._should_reject_admin_command(client_config.config, userid, username):
|
||||
if self._should_reject_admin_command(client_config.config, userid):
|
||||
self._send_admin_denied(client, userid)
|
||||
return None
|
||||
else:
|
||||
@@ -373,6 +377,9 @@ class SlackModule(_ModuleBase, _MessageBase[Slack]):
|
||||
source=client_config.name,
|
||||
userid=userid,
|
||||
username=username,
|
||||
is_channel_admin=matches_channel_admin(
|
||||
client_config.config, "SLACK_ADMINS", userid
|
||||
),
|
||||
text=text,
|
||||
message_id=message_id,
|
||||
chat_id=chat_id,
|
||||
|
||||
@@ -3,6 +3,7 @@ from typing import Optional, Union, List, Tuple, Any
|
||||
from urllib.parse import quote, unquote
|
||||
|
||||
from app.core.context import MediaInfo, Context
|
||||
from app.helper.agent import matches_channel_admin
|
||||
from app.log import logger
|
||||
from app.modules import _ModuleBase, _MessageBase
|
||||
from app.modules.synologychat.synologychat import SynologyChat
|
||||
@@ -168,7 +169,7 @@ class SynologyChatModule(_ModuleBase, _MessageBase[SynologyChat]):
|
||||
files = self._extract_files(message)
|
||||
if (text or images or audio_refs or files) and user_id:
|
||||
if text and text.startswith("/") and self._should_reject_admin_command(
|
||||
client_config.config, user_id, user_name
|
||||
client_config.config, user_id
|
||||
):
|
||||
self._send_admin_denied(client, user_id)
|
||||
return None
|
||||
@@ -179,7 +180,10 @@ class SynologyChatModule(_ModuleBase, _MessageBase[SynologyChat]):
|
||||
f"files={len(files) if files else 0}"
|
||||
)
|
||||
return CommingMessage(channel=MessageChannel.SynologyChat, source=client_config.name,
|
||||
userid=user_id, username=user_name, text=text or "",
|
||||
userid=user_id, username=user_name,
|
||||
is_channel_admin=matches_channel_admin(
|
||||
client_config.config, "SYNOLOGYCHAT_ADMINS", user_id
|
||||
), text=text or "",
|
||||
images=images, audio_refs=audio_refs, files=files)
|
||||
except Exception as err:
|
||||
logger.debug(f"解析SynologyChat消息失败:{str(err)}")
|
||||
|
||||
@@ -5,6 +5,7 @@ from typing import Dict, Optional, Union, List, Tuple, Any
|
||||
|
||||
from app.core.context import MediaInfo, Context
|
||||
from app.core.event import eventmanager
|
||||
from app.helper.agent import matches_channel_admin
|
||||
from app.log import logger
|
||||
from app.modules import _ModuleBase, _MessageBase
|
||||
from app.modules.telegram.telegram import Telegram
|
||||
@@ -211,7 +212,7 @@ class TelegramModule(_ModuleBase, _MessageBase[Telegram]):
|
||||
|
||||
if callback_data and user_id:
|
||||
if str(callback_data).strip().startswith("/") and self._should_reject_admin_command(
|
||||
client_config.config, user_id, user_name
|
||||
client_config.config, user_id
|
||||
):
|
||||
if client:
|
||||
client.answer_callback_query(
|
||||
@@ -235,6 +236,9 @@ class TelegramModule(_ModuleBase, _MessageBase[Telegram]):
|
||||
source=client_config.name,
|
||||
userid=user_id,
|
||||
username=user_name,
|
||||
is_channel_admin=matches_channel_admin(
|
||||
client_config.config, "TELEGRAM_ADMINS", user_id
|
||||
),
|
||||
text=callback_text,
|
||||
is_callback=True,
|
||||
callback_data=callback_data,
|
||||
@@ -293,7 +297,7 @@ class TelegramModule(_ModuleBase, _MessageBase[Telegram]):
|
||||
user_list = client_config.config.get("TELEGRAM_USERS")
|
||||
|
||||
if cleaned_text and cleaned_text.startswith("/"):
|
||||
if self._should_reject_admin_command(client_config.config, user_id, user_name):
|
||||
if self._should_reject_admin_command(client_config.config, user_id):
|
||||
client.send_msg(
|
||||
title="只有管理员才有权限执行此命令", userid=user_id
|
||||
)
|
||||
@@ -311,6 +315,9 @@ class TelegramModule(_ModuleBase, _MessageBase[Telegram]):
|
||||
source=client_config.name,
|
||||
userid=user_id,
|
||||
username=user_name,
|
||||
is_channel_admin=matches_channel_admin(
|
||||
client_config.config, "TELEGRAM_ADMINS", user_id
|
||||
),
|
||||
text=cleaned_text,
|
||||
message_id=message_id,
|
||||
chat_id=str(chat_id) if chat_id else None,
|
||||
|
||||
@@ -3,6 +3,7 @@ from urllib.parse import quote, unquote
|
||||
from typing import Optional, Union, List, Tuple, Any, Dict
|
||||
|
||||
from app.core.context import Context, MediaInfo
|
||||
from app.helper.agent import matches_channel_admin
|
||||
from app.log import logger
|
||||
from app.modules import _ModuleBase, _MessageBase
|
||||
from app.modules.vocechat.vocechat import VoceChat
|
||||
@@ -180,6 +181,10 @@ class VoceChatModule(_ModuleBase, _MessageBase[VoceChat]):
|
||||
text = content
|
||||
# 用户ID
|
||||
gid = msg_body.get("target", {}).get("gid")
|
||||
from_uid = msg_body.get("from_uid")
|
||||
if from_uid is None:
|
||||
return None
|
||||
actor_userid = f"UID#{from_uid}"
|
||||
channel_id = client_config.config.get("channel_id")
|
||||
if gid and str(gid) == str(channel_id):
|
||||
# 来自监听频道的消息
|
||||
@@ -191,7 +196,7 @@ class VoceChatModule(_ModuleBase, _MessageBase[VoceChat]):
|
||||
# 处理消息内容
|
||||
if (text or images or audio_refs or files) and userid:
|
||||
if text and text.startswith("/") and self._should_reject_admin_command(
|
||||
client_config.config, msg_body.get("from_uid"), userid
|
||||
client_config.config, from_uid, actor_userid
|
||||
):
|
||||
self._send_admin_denied(client, userid)
|
||||
return None
|
||||
@@ -201,7 +206,11 @@ class VoceChatModule(_ModuleBase, _MessageBase[VoceChat]):
|
||||
f"audios={len(audio_refs) if audio_refs else 0}, files={len(files) if files else 0}"
|
||||
)
|
||||
return CommingMessage(channel=MessageChannel.VoceChat, source=client_config.name,
|
||||
userid=userid, username=userid, text=text or "",
|
||||
userid=userid, username=userid,
|
||||
is_channel_admin=matches_channel_admin(
|
||||
client_config.config, "VOCECHAT_ADMINS",
|
||||
from_uid, actor_userid,
|
||||
), text=text or "",
|
||||
images=images, audio_refs=audio_refs, files=files)
|
||||
except Exception as err:
|
||||
logger.error(f"VoceChat消息处理发生错误:{str(err)}")
|
||||
|
||||
@@ -7,6 +7,7 @@ from urllib.parse import quote
|
||||
|
||||
from app.core.context import Context, MediaInfo
|
||||
from app.core.event import eventmanager
|
||||
from app.helper.agent import matches_channel_admin
|
||||
from app.log import logger
|
||||
from app.modules import _ModuleBase, _MessageBase
|
||||
from app.modules.wechat.WXBizMsgCrypt3 import WXBizMsgCrypt
|
||||
@@ -250,7 +251,10 @@ class WechatModule(_ModuleBase, _MessageBase[WeChat]):
|
||||
if content or images or audio_refs or files:
|
||||
# 处理消息内容
|
||||
return CommingMessage(channel=MessageChannel.Wechat, source=client_config.name,
|
||||
userid=user_id, username=user_id, text=content or "",
|
||||
userid=user_id, username=user_id,
|
||||
is_channel_admin=matches_channel_admin(
|
||||
client_config.config, "WECHAT_ADMINS", user_id
|
||||
), text=content or "",
|
||||
images=images, audio_refs=audio_refs, files=files)
|
||||
except Exception as err:
|
||||
logger.error(f"微信消息处理发生错误:{str(err)}")
|
||||
@@ -320,6 +324,9 @@ class WechatModule(_ModuleBase, _MessageBase[WeChat]):
|
||||
source=client_config.name,
|
||||
userid=sender,
|
||||
username=sender,
|
||||
is_channel_admin=matches_channel_admin(
|
||||
client_config.config, "WECHAT_ADMINS", sender
|
||||
),
|
||||
text=text or "",
|
||||
images=images,
|
||||
audio_refs=audio_refs,
|
||||
|
||||
@@ -3,6 +3,7 @@ from typing import Any, List, Optional, Tuple, Union
|
||||
|
||||
from app.core.cache import TTLCache
|
||||
from app.core.context import Context, MediaInfo
|
||||
from app.helper.agent import matches_channel_admin
|
||||
from app.log import logger
|
||||
from app.modules import _MessageBase, _ModuleBase
|
||||
from app.modules.wechatclawbot.wechatclawbot import WechatClawBot
|
||||
@@ -198,6 +199,9 @@ class WechatClawBotModule(_ModuleBase, _MessageBase[WechatClawBot]):
|
||||
source=client_config.name,
|
||||
userid=user_id,
|
||||
username=username,
|
||||
is_channel_admin=matches_channel_admin(
|
||||
client_config.config, "WECHATCLAWBOT_ADMINS", user_id
|
||||
),
|
||||
text=text,
|
||||
message_id=message_id,
|
||||
chat_id=str(message.get("chat_id") or "") or None,
|
||||
|
||||
@@ -168,6 +168,8 @@ class CommingMessage(BaseModel):
|
||||
userid: Optional[Union[str, int]] = None
|
||||
# 用户名称
|
||||
username: Optional[Union[str, int]] = None
|
||||
# 渠道适配器依据稳定用户 ID 与当前实例管理员名单生成的授权事实
|
||||
is_channel_admin: Optional[bool] = None
|
||||
# 消息渠道
|
||||
channel: Optional[MessageChannel] = None
|
||||
# 来源(渠道名称)
|
||||
|
||||
Reference in New Issue
Block a user