fix(agent): bind channel admin identity (#6294)

This commit is contained in:
InfinityPacer
2026-08-13 14:53:59 +08:00
committed by GitHub
parent eeec40cce3
commit 3aed078bbb
19 changed files with 752 additions and 74 deletions

View File

@@ -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

View File

@@ -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)}")

View File

@@ -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,

View File

@@ -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 前端可识别的按钮行。

View File

@@ -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,

View File

@@ -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"),

View File

@@ -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,

View File

@@ -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,

View File

@@ -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)}")

View File

@@ -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,

View File

@@ -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)}")

View File

@@ -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,

View File

@@ -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,

View File

@@ -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
# 来源(渠道名称)