Skip to content

[Python lark-oapi 1.6.9] InboundMessage.mentioned_all is always False for post (rich-text) @all messages #138

Description

@wuzhenhua24

Summary

For post (rich-text) messages that mention everyone (@所有人 / @all),
InboundMessage.mentioned_all is always False. As a result PolicyGate
never emits policy_mention_all_blocked, so a bot configured with
respond_to_mention_all=False still responds to broadcast @all posts.

Plain text @all messages are handled correctly — only the post code
path is affected.

Root cause

In lark_oapi/channel/normalize/pipeline.py, the PostContent branch of
InboundPipeline.process derives mentioned_all only from:

  1. ext.mentioned_all (the event mentions[] array), and
  2. text_has_mention_all(content.text) / parse_at_tags(content.text)
    (the rendered text).

But for a post:

  • Feishu does not populate mentions[] for @all (already acknowledged in the
    code comments on the text branch).
  • The @all signal lives in the post AST as an at node with
    user_id == "all" ({"tag":"at","user_id":"all"}). converters/post.py
    renders that node to the literal string @所有人 / @<user_name>not
    the @_all placeholder that text_has_mention_all looks for, and not an
    <at> tag that parse_at_tags matches.

So neither probe fires and mentioned_all stays False. The post AST is never
inspected for the mention-all node.

Reproduction (confirmed on 1.6.9, also reproduces on 1.6.6)

import asyncio, json
from lark_oapi.channel.normalize.pipeline import InboundPipeline, PipelineConfig, PipelineDeps
from lark_oapi.channel.config import PolicyConfig
from lark_oapi.channel.safety.policy_gate import PolicyGate

async def main():
    pipe = InboundPipeline(PipelineConfig(), PipelineDeps())
    # A post that @-mentions everyone, like a broadcast notice
    content = {"content": [[
        {"tag": "at", "user_id": "all", "user_name": "Everyone"},
        {"tag": "text", "text": " heads up everyone"},
    ]]}
    event = {"message_id": "om", "chat_id": "oc", "chat_type": "group",
             "message_type": "post", "content": json.dumps(content), "mentions": []}
    sender = {"sender_id": {"open_id": "ou_asker"}, "sender_type": "user"}
    inbound = await pipe.process("e", event, sender)
    print("post  mentioned_all =", inbound.mentioned_all)

    gate = PolicyGate(PolicyConfig(require_mention=False, respond_to_mention_all=False))
    gate.set_bot_open_id("ou_bot")
    print("post  decision      =", gate.evaluate(inbound))

    # same intent as plain text — handled correctly
    tevent = dict(event, message_type="text", content=json.dumps({"text": "@_all heads up"}))
    tin = await pipe.process("e2", tevent, sender)
    print("text  mentioned_all =", tin.mentioned_all)
    print("text  decision      =", gate.evaluate(tin))

asyncio.run(main())

Output:

post  mentioned_all = False
post  decision      = PolicyDecision(allowed=True, reason=None)
text  mentioned_all = True
text  decision      = PolicyDecision(allowed=False, reason='policy_mention_all_blocked')

Expected: the post case should match the text case
(mentioned_all = True, decision policy_mention_all_blocked).

Suggested fix

In the PostContent branch, probe the raw post AST for an at node with
user_id == "all" (mirroring is_mention_all):

elif isinstance(content, PostContent):
    if not mentioned_all and text_has_mention_all(content.text):
        mentioned_all = True
    # post @all is an `at` node (user_id == "all") in the AST, not an
    # @_all placeholder in the rendered text — probe the raw post.
    if not mentioned_all and _post_has_mention_all(content.post):
        mentioned_all = True
    ...

where _post_has_mention_all walks the post documents/paragraphs for any at
element whose user_id (or open_id) equals "all".

Environment

  • lark-oapi 1.6.9 (also 1.6.6)
  • Python 3.11

Possibly related: #134 (InboundMessage.mentioned_bot always False for IM
messages) — same family of post/IM mention-resolution gaps.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions