feat(feishu): add global group mention policy
- Add group_policy config: 'open' (default) or 'mention' - 'open': Respond to all group messages (backward compatible) - 'mention': Only respond when @mentioned in any group - Auto-detect bot mentions by pattern matching: * If open_id configured: match against mentions * Otherwise: detect bot by empty user_id + ou_ open_id pattern - Support @_all mentions - Private chats unaffected (always respond) - Clean implementation with minimal logging docs: update Feishu README with group policy documentation
This commit is contained in:
15
README.md
15
README.md
@@ -482,7 +482,8 @@ Uses **WebSocket** long connection — no public IP required.
|
|||||||
"appSecret": "xxx",
|
"appSecret": "xxx",
|
||||||
"encryptKey": "",
|
"encryptKey": "",
|
||||||
"verificationToken": "",
|
"verificationToken": "",
|
||||||
"allowFrom": ["ou_YOUR_OPEN_ID"]
|
"allowFrom": ["ou_YOUR_OPEN_ID"],
|
||||||
|
"groupPolicy": "open"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -491,6 +492,18 @@ Uses **WebSocket** long connection — no public IP required.
|
|||||||
> `encryptKey` and `verificationToken` are optional for Long Connection mode.
|
> `encryptKey` and `verificationToken` are optional for Long Connection mode.
|
||||||
> `allowFrom`: Add your open_id (find it in nanobot logs when you message the bot). Use `["*"]` to allow all users.
|
> `allowFrom`: Add your open_id (find it in nanobot logs when you message the bot). Use `["*"]` to allow all users.
|
||||||
|
|
||||||
|
**Group Chat Policy** (optional):
|
||||||
|
|
||||||
|
| Option | Values | Default | Description |
|
||||||
|
|--------|--------|---------|-------------|
|
||||||
|
| `groupPolicy` | `"open"` | `"open"` | Respond to all group messages (backward compatible) |
|
||||||
|
| | `"mention"` | | Only respond when @mentioned |
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> - `"open"`: Respond to all messages in all groups
|
||||||
|
> - `"mention"`: Only respond when @mentioned in any group
|
||||||
|
> - Private chats are unaffected (always respond)
|
||||||
|
|
||||||
**3. Run**
|
**3. Run**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -352,6 +352,74 @@ class FeishuChannel(BaseChannel):
|
|||||||
self._running = False
|
self._running = False
|
||||||
logger.info("Feishu bot stopped")
|
logger.info("Feishu bot stopped")
|
||||||
|
|
||||||
|
def _get_bot_open_id_sync(self) -> str | None:
|
||||||
|
"""Get bot's own open_id for mention detection.
|
||||||
|
|
||||||
|
飞书 SDK 没有直接的 bot info API,从配置或缓存获取。
|
||||||
|
"""
|
||||||
|
# 尝试从配置获取 open_id(用户可以在配置中指定)
|
||||||
|
if hasattr(self.config, 'open_id') and self.config.open_id:
|
||||||
|
return self.config.open_id
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _is_bot_mentioned(self, message: Any, bot_open_id: str | None) -> bool:
|
||||||
|
"""Check if bot is mentioned in the message.
|
||||||
|
|
||||||
|
飞书 mentions 数组包含被@的对象。匹配策略:
|
||||||
|
1. 如果配置了 bot_open_id,则匹配 open_id
|
||||||
|
2. 否则,检查 mentions 中是否有空的 user_id(bot 的特征)
|
||||||
|
|
||||||
|
Handles:
|
||||||
|
- Direct mentions in message.mentions
|
||||||
|
- @all mentions
|
||||||
|
"""
|
||||||
|
# Check @all
|
||||||
|
raw_content = message.content or ""
|
||||||
|
if "@_all" in raw_content:
|
||||||
|
logger.debug("Feishu: @_all mention detected")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Check mentions array
|
||||||
|
mentions = message.mentions if hasattr(message, 'mentions') and message.mentions else []
|
||||||
|
if mentions:
|
||||||
|
if bot_open_id:
|
||||||
|
# 策略 1: 匹配配置的 open_id
|
||||||
|
for mention in mentions:
|
||||||
|
if mention.id:
|
||||||
|
open_id = getattr(mention.id, 'open_id', None)
|
||||||
|
if open_id == bot_open_id:
|
||||||
|
logger.debug("Feishu: bot mention matched")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
# 策略 2: 检查 bot 特征 - user_id 为空且 open_id 存在
|
||||||
|
for mention in mentions:
|
||||||
|
if mention.id:
|
||||||
|
user_id = getattr(mention.id, 'user_id', None)
|
||||||
|
open_id = getattr(mention.id, 'open_id', None)
|
||||||
|
# Bot 的特征:user_id 为空字符串,open_id 存在
|
||||||
|
if user_id == '' and open_id and open_id.startswith('ou_'):
|
||||||
|
logger.debug("Feishu: bot mention matched")
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _should_respond_in_group(
|
||||||
|
self,
|
||||||
|
chat_id: str,
|
||||||
|
mentioned: bool
|
||||||
|
) -> tuple[bool, str]:
|
||||||
|
"""Determine if bot should respond in a group chat.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(should_respond, reason)
|
||||||
|
"""
|
||||||
|
# Check mention requirement
|
||||||
|
if self.config.group_policy == "mention" and not mentioned:
|
||||||
|
return False, "not mentioned in group"
|
||||||
|
|
||||||
|
return True, ""
|
||||||
|
|
||||||
def _add_reaction_sync(self, message_id: str, emoji_type: str) -> None:
|
def _add_reaction_sync(self, message_id: str, emoji_type: str) -> None:
|
||||||
"""Sync helper for adding reaction (runs in thread pool)."""
|
"""Sync helper for adding reaction (runs in thread pool)."""
|
||||||
from lark_oapi.api.im.v1 import CreateMessageReactionRequest, CreateMessageReactionRequestBody, Emoji
|
from lark_oapi.api.im.v1 import CreateMessageReactionRequest, CreateMessageReactionRequestBody, Emoji
|
||||||
@@ -892,6 +960,16 @@ class FeishuChannel(BaseChannel):
|
|||||||
chat_type = message.chat_type
|
chat_type = message.chat_type
|
||||||
msg_type = message.message_type
|
msg_type = message.message_type
|
||||||
|
|
||||||
|
# Check group policy and mention requirement
|
||||||
|
if chat_type == "group":
|
||||||
|
bot_open_id = self._get_bot_open_id_sync()
|
||||||
|
mentioned = self._is_bot_mentioned(message, bot_open_id)
|
||||||
|
should_respond, reason = self._should_respond_in_group(chat_id, mentioned)
|
||||||
|
|
||||||
|
if not should_respond:
|
||||||
|
logger.debug("Feishu: ignoring group message - {}", reason)
|
||||||
|
return
|
||||||
|
|
||||||
# Add reaction
|
# Add reaction
|
||||||
await self._add_reaction(message_id, self.config.react_emoji)
|
await self._add_reaction(message_id, self.config.react_emoji)
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ class FeishuConfig(Base):
|
|||||||
react_emoji: str = (
|
react_emoji: str = (
|
||||||
"THUMBSUP" # Emoji type for message reactions (e.g. THUMBSUP, OK, DONE, SMILE)
|
"THUMBSUP" # Emoji type for message reactions (e.g. THUMBSUP, OK, DONE, SMILE)
|
||||||
)
|
)
|
||||||
|
# Group chat settings
|
||||||
|
group_policy: Literal["open", "mention"] = "open" # Group response policy (default: open for backward compatibility)
|
||||||
|
|
||||||
|
|
||||||
class DingTalkConfig(Base):
|
class DingTalkConfig(Base):
|
||||||
|
|||||||
Reference in New Issue
Block a user