Skip to content

入群欢迎词支持@和图片的cq码 - #98

Open
god-what-is-that wants to merge 5 commits into
Zhalslar:mainfrom
god-what-is-that:main
Open

入群欢迎词支持@和图片的cq码#98
god-what-is-that wants to merge 5 commits into
Zhalslar:mainfrom
god-what-is-that:main

Conversation

@god-what-is-that

@god-what-is-that god-what-is-that commented Mar 29, 2026

Copy link
Copy Markdown

1、如题
2、增加了uid替换{qq},format改成了replace避免不存在“{nickname}”时报错
#94

Summary by Sourcery

通过将格式化文本转换为消息链,在群欢迎消息中支持基于 CQ 码的提及和图片。

新功能:

  • 在欢迎消息中添加 CQ 码解析,以渲染 @ 提及和图片,并支持 URL 和本地文件路径。

增强:

  • 优化入群欢迎消息的发送方式,改为使用构造的 MessageChain 而非纯文本消息,以支持更丰富的内容。
Original summary in English

Summary by Sourcery

Support CQ code-based mentions and images in group join welcome messages by converting formatted text into a message chain.

New Features:

  • Add parsing of CQ codes in welcome messages to render @ mentions and images, including support for both URLs and local file paths.

Enhancements:

  • Refine join welcome sending to use a constructed MessageChain instead of plain text messages for richer content.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@sourcery-ai

sourcery-ai Bot commented Mar 29, 2026

Copy link
Copy Markdown

Reviewer's Guide

此 PR 更改了进群欢迎消息的发送逻辑,改为构造并发送由 CQ 码解析得到的 MessageChain,同时引入了一个 CQ 码解析器,用于将包含 @ 和图片 CQ 码(包括本地文件路径)的文本转换为 AstrBot 消息组件。

解析 CQ 并发送进群欢迎消息的时序图

sequenceDiagram
    actor GroupMember
    participant BotEventHandler as Bot_event_monitoring
    participant Database as DB
    participant CQParser as parse_cq_to_chain
    participant MessageChain as MessageChain
    participant MessageAPI as AiocqhttpMessageEvent

    GroupMember->>BotEventHandler: member_joins_group(event)
    BotEventHandler->>DB: get(gid, join_welcome)
    DB-->>BotEventHandler: join_welcome_template
    BotEventHandler->>DB: get_nickname(event, uid)
    DB-->>BotEventHandler: nickname
    BotEventHandler->>BotEventHandler: welcome = join_welcome_template.format(nickname)
    BotEventHandler->>CQParser: parse_cq_to_chain(welcome)
    CQParser-->>BotEventHandler: components_list
    BotEventHandler->>MessageChain: MessageChain(chain=components_list)
    MessageChain-->>BotEventHandler: chain_instance
    BotEventHandler->>MessageAPI: send(chain_instance)
    MessageAPI-->>GroupMember: welcome_message_with_at_and_images
Loading

CQ 解析与消息组件的类图

classDiagram
    class ParseCQToChainModule {
        +parse_cq_to_chain(text str) list
    }

    class Plain {
        +text str
    }

    class At {
        +qq str
        +name str
    }

    class Image {
        +file str
        +url str
        +fromURL(url str) Image
        +fromFileSystem(path str) Image
    }

    class MessageChain {
        +chain list
    }

    class AiocqhttpMessageEvent {
        +send(message MessageChain) None
        +plain_result(text str) str
    }

    ParseCQToChainModule ..> Plain : creates
    ParseCQToChainModule ..> At : creates
    ParseCQToChainModule ..> Image : creates
    MessageChain o-- Plain : contains
    MessageChain o-- At : contains
    MessageChain o-- Image : contains
    AiocqhttpMessageEvent ..> MessageChain : sends

    class JoinHandleEventMonitoring {
        +event_monitoring(event AiocqhttpMessageEvent) None
    }

    JoinHandleEventMonitoring ..> ParseCQToChainModule : uses
    JoinHandleEventMonitoring ..> MessageChain : constructs
    JoinHandleEventMonitoring ..> AiocqhttpMessageEvent : sends_message_via_event
Loading

文件级改动

Change Details Files
将进群欢迎消息从发送纯文本改为发送解析后的 MessageChain,以支持 CQ @ 和图片代码。
  • 用新的 CQ 解析器构造 MessageChain,替换直接发送纯文本欢迎消息的方式
  • 保留昵称格式化逻辑,但在发送前将格式化后的欢迎文本先通过 CQ 解析步骤
core/join_handle.py
添加一个将 CQ 码转为 MessageChain 的解析器,支持来自 URL 或本地文件系统路径的 at 提及和图片。
  • 实现 parse_cq_to_chain,扫描文本中的 [CQ:...] 片段并拆分为 Plain/AstrBot 组件
  • 将 CQ 参数解析为键值字典,支持 qq、file 和 url 参数
  • 将 CQ 类型 at 映射为 At 组件,将 image 映射为 Image 组件,并根据情况调用 fromURL 或 fromFileSystem
  • 使用 os.path.exists 校验本地图片路径,如果文件缺失则回退为一个 Plain 的错误占位文本
  • 确保所有非 CQ 部分的消息按顺序保留为 Plain 组件
core/parse_cq_to_chain.py

可能关联的 Issues

  • #(unknown): PR通过parse_cq_to_chain使进群欢迎词可解析CQ码图片和@,完全满足Issue需求

Tips and commands

Interacting with Sourcery

  • 触发新一次代码评审: 在 pull request 中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的评审评论。
  • 从评审评论生成 GitHub Issue: 在回复 Sourcery 的评审评论时请求其从该评论创建 Issue。你也可以直接回复评审评论 @sourcery-ai issue 来从中创建 Issue。
  • 生成 pull request 标题: 在 pull request 标题的任意位置写上 @sourcery-ai,即可随时生成标题。也可以在 pull request 中评论 @sourcery-ai title 来(重新)生成标题。
  • 生成 pull request 总结: 在 pull request 正文任意位置写上 @sourcery-ai summary,即可在对应位置生成 PR 总结。也可以在 pull request 中评论 @sourcery-ai summary 来(重新)生成总结。
  • 生成 Reviewer 指南: 在 pull request 中评论 @sourcery-ai guide,即可随时(重新)生成 Reviewer 指南。
  • 一次性解决所有 Sourcery 评论: 在 pull request 中评论 @sourcery-ai resolve 来标记所有 Sourcery 评论为已解决。如果你已经处理完所有评论且不想再看到它们,这会非常有用。
  • 一次性忽略所有 Sourcery 评审: 在 pull request 中评论 @sourcery-ai dismiss 来忽略所有已有的 Sourcery 评审。如果你想从一次全新的评审开始,这尤其有用——别忘了再评论 @sourcery-ai review 来触发新的评审!

Customizing Your Experience

访问你的 dashboard 来:

  • 启用或禁用诸如 Sourcery 自动生成的 pull request 总结、Reviewer 指南等评审功能。
  • 更改评审语言。
  • 添加、删除或编辑自定义评审指令。
  • 调整其他评审设置。

Getting Help

Original review guide in English

Reviewer's Guide

This PR changes the join-welcome sending logic to construct and send a MessageChain parsed from CQ codes, and introduces a CQ-code parser that converts text with @ and image CQ codes (including local file paths) into AstrBot message components.

Sequence diagram for sending join welcome with CQ parsing

sequenceDiagram
    actor GroupMember
    participant BotEventHandler as Bot_event_monitoring
    participant Database as DB
    participant CQParser as parse_cq_to_chain
    participant MessageChain as MessageChain
    participant MessageAPI as AiocqhttpMessageEvent

    GroupMember->>BotEventHandler: member_joins_group(event)
    BotEventHandler->>DB: get(gid, join_welcome)
    DB-->>BotEventHandler: join_welcome_template
    BotEventHandler->>DB: get_nickname(event, uid)
    DB-->>BotEventHandler: nickname
    BotEventHandler->>BotEventHandler: welcome = join_welcome_template.format(nickname)
    BotEventHandler->>CQParser: parse_cq_to_chain(welcome)
    CQParser-->>BotEventHandler: components_list
    BotEventHandler->>MessageChain: MessageChain(chain=components_list)
    MessageChain-->>BotEventHandler: chain_instance
    BotEventHandler->>MessageAPI: send(chain_instance)
    MessageAPI-->>GroupMember: welcome_message_with_at_and_images
Loading

Class diagram for CQ parsing and message components

classDiagram
    class ParseCQToChainModule {
        +parse_cq_to_chain(text str) list
    }

    class Plain {
        +text str
    }

    class At {
        +qq str
        +name str
    }

    class Image {
        +file str
        +url str
        +fromURL(url str) Image
        +fromFileSystem(path str) Image
    }

    class MessageChain {
        +chain list
    }

    class AiocqhttpMessageEvent {
        +send(message MessageChain) None
        +plain_result(text str) str
    }

    ParseCQToChainModule ..> Plain : creates
    ParseCQToChainModule ..> At : creates
    ParseCQToChainModule ..> Image : creates
    MessageChain o-- Plain : contains
    MessageChain o-- At : contains
    MessageChain o-- Image : contains
    AiocqhttpMessageEvent ..> MessageChain : sends

    class JoinHandleEventMonitoring {
        +event_monitoring(event AiocqhttpMessageEvent) None
    }

    JoinHandleEventMonitoring ..> ParseCQToChainModule : uses
    JoinHandleEventMonitoring ..> MessageChain : constructs
    JoinHandleEventMonitoring ..> AiocqhttpMessageEvent : sends_message_via_event
Loading

File-Level Changes

Change Details Files
Send join welcome messages as a parsed MessageChain instead of plain text to support CQ @ and image codes.
  • Replace direct sending of plain text welcome message with construction of a MessageChain using a new CQ parser
  • Preserve nickname formatting but route the formatted welcome text through the CQ parsing step before sending
core/join_handle.py
Add a CQ code to MessageChain parser that supports at-mentions and images from URLs or local filesystem paths.
  • Implement parse_cq_to_chain to scan text for [CQ:...] segments and split into Plain/AstrBot components
  • Parse CQ parameters into a key-value dict, supporting qq, file, and url parameters
  • Map CQ type at to At components, and image to Image components using either fromURL or fromFileSystem
  • Validate local image paths with os.path.exists and fall back to a Plain error placeholder if the file is missing
  • Ensure all non-CQ portions of the message are preserved as Plain components in order
core/parse_cq_to_chain.py

Possibly linked issues

  • #(unknown): PR通过parse_cq_to_chain使进群欢迎词可解析CQ码图片和@,完全满足Issue需求

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了两个问题,并给出了一些整体性的反馈:

  • 目前 CQ 解析只处理 atimage 类型,并且会静默丢弃其他任何 [CQ:...] 段;建议把未知的 CQ 代码以纯文本的形式保留下来,这样在出现新的 CQ 类型时就不会丢失内容。
  • 当图片文件路径不存在时,当前行为是向用户发送可见的 [图片读取失败: path] 消息;可能更安全的做法是只在内部记录日志,并保留原始 CQ 文本或直接省略损坏的图片,而不是在消息中暴露文件系统路径。
给 AI Agent 的提示词
Please address the comments from this code review:

## Overall Comments
- 目前 CQ 解析只处理 `at``image` 类型,并且会静默丢弃其他任何 `[CQ:...]` 段;建议把未知的 CQ 代码以纯文本的形式保留下来,这样在出现新的 CQ 类型时就不会丢失内容。
- 当图片文件路径不存在时,当前行为是向用户发送可见的 `[图片读取失败: path]` 消息;可能更安全的做法是只在内部记录日志,并保留原始 CQ 文本或直接省略损坏的图片,而不是在消息中暴露文件系统路径。

## Individual Comments

### Comment 1
<location path="core/parse_cq_to_chain.py" line_range="18-19" />
<code_context>
+        if plain_text:
+            chain.append(Plain(plain_text))
+        
+        cq_type = match.group(1)
+        params_str = match.group(2)
+        
+        # 解析参数:file=D:\xxx...
</code_context>
<issue_to_address>
**issue (bug_risk):** 未识别的 CQ 类型会被静默丢弃,而不是保留为文本。

当 `cq_type` 不是 `at``image` 时,这个分支只会向前移动 `last_pos`,而不会追加任何内容,因此该 CQ 段会丢失。为避免丢弃未知或未来可能出现的 CQ 类型,建议在类型未被识别时,将原始片段 `text[match.start():match.end()]` 作为一个 `Plain` 段追加到链中。
</issue_to_address>

### Comment 2
<location path="core/parse_cq_to_chain.py" line_range="29-30" />
<code_context>
+                params[k.strip()] = v.strip()
+        
+        # 根据类型构造组件
+        if cq_type == "at":
+            chain.append(At(qq=params.get("qq", ""), name=""))
+        elif cq_type == "image":
+            file_path = params.get("file") or params.get("url")
</code_context>
<issue_to_address>
**suggestion:** 当缺少 `qq` 时创建 `At` 会退回到空字符串,这可能比较容易出错。

当 `qq` 缺失或格式不正确时,我们会得到 `At(qq="")`,它可能会静默失效或产生意外行为。建议将缺少 `qq` 的情况视为无效——要么将该段落保留为 `Plain`,要么跳过并插入一个可见的占位符,以便更容易发现配置问题。

Suggested implementation:

```python
        # 根据类型构造组件
        if cq_type == "at":
            qq = (params.get("qq") or "").strip()
            # qq 缺失或格式异常时,不创建空的 At,而是退回为原始文本,便于发现配置问题
            if qq and qq.isdigit():
                chain.append(At(qq=qq, name=""))
            else:
                chain.append(Plain(match.group(0)))
        elif cq_type == "image":

```

This change assumes that `Plain` is already imported and available in this module (similar to `At` and `Image`). If not, you should import `Plain` from the same package where other segment classes (like `At`/`Image`) come from.
</issue_to_address>

Sourcery 对开源项目免费——如果你觉得我们的 Review 有帮助,欢迎分享 ✨
帮我变得更有用!请对每条评论点 👍 或 👎,我会根据这些反馈改进后续的 Review。
Original comment in English

Hey - I've found 2 issues, and left some high level feedback:

  • The CQ parsing only handles at and image types and silently drops any other [CQ:...] segments; consider preserving unknown CQ codes as plain text so content is not lost if new types appear.
  • When an image file path does not exist, the current behavior sends a user-visible [图片读取失败: path] message; it may be safer to log this internally and either keep the original CQ text or omit the broken image rather than exposing filesystem paths in the message.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The CQ parsing only handles `at` and `image` types and silently drops any other `[CQ:...]` segments; consider preserving unknown CQ codes as plain text so content is not lost if new types appear.
- When an image file path does not exist, the current behavior sends a user-visible `[图片读取失败: path]` message; it may be safer to log this internally and either keep the original CQ text or omit the broken image rather than exposing filesystem paths in the message.

## Individual Comments

### Comment 1
<location path="core/parse_cq_to_chain.py" line_range="18-19" />
<code_context>
+        if plain_text:
+            chain.append(Plain(plain_text))
+        
+        cq_type = match.group(1)
+        params_str = match.group(2)
+        
+        # 解析参数:file=D:\xxx...
</code_context>
<issue_to_address>
**issue (bug_risk):** Unrecognized CQ types are silently dropped instead of being preserved as text.

When `cq_type` is not `at` or `image`, this branch only moves `last_pos` forward and never appends anything, so that CQ segment is lost. To avoid dropping unknown or future CQ types, consider appending the raw slice `text[match.start():match.end()]` as a `Plain` segment when the type isn’t recognized.
</issue_to_address>

### Comment 2
<location path="core/parse_cq_to_chain.py" line_range="29-30" />
<code_context>
+                params[k.strip()] = v.strip()
+        
+        # 根据类型构造组件
+        if cq_type == "at":
+            chain.append(At(qq=params.get("qq", ""), name=""))
+        elif cq_type == "image":
+            file_path = params.get("file") or params.get("url")
</code_context>
<issue_to_address>
**suggestion:** Creating an `At` with a missing `qq` falls back to an empty string, which may be error-prone.

When `qq` is missing or malformed, we end up with `At(qq="")`, which may silently do nothing or behave unexpectedly. Consider instead treating missing `qq` as invalid—either leave the segment as `Plain`, or skip it while inserting a visible placeholder so misconfigurations are easier to spot.

Suggested implementation:

```python
        # 根据类型构造组件
        if cq_type == "at":
            qq = (params.get("qq") or "").strip()
            # qq 缺失或格式异常时,不创建空的 At,而是退回为原始文本,便于发现配置问题
            if qq and qq.isdigit():
                chain.append(At(qq=qq, name=""))
            else:
                chain.append(Plain(match.group(0)))
        elif cq_type == "image":

```

This change assumes that `Plain` is already imported and available in this module (similar to `At` and `Image`). If not, you should import `Plain` from the same package where other segment classes (like `At`/`Image`) come from.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread core/parse_cq_to_chain.py
Comment on lines +18 to +19
cq_type = match.group(1)
params_str = match.group(2)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): 未识别的 CQ 类型会被静默丢弃,而不是保留为文本。

cq_type 不是 atimage 时,这个分支只会向前移动 last_pos,而不会追加任何内容,因此该 CQ 段会丢失。为避免丢弃未知或未来可能出现的 CQ 类型,建议在类型未被识别时,将原始片段 text[match.start():match.end()] 作为一个 Plain 段追加到链中。

Original comment in English

issue (bug_risk): Unrecognized CQ types are silently dropped instead of being preserved as text.

When cq_type is not at or image, this branch only moves last_pos forward and never appends anything, so that CQ segment is lost. To avoid dropping unknown or future CQ types, consider appending the raw slice text[match.start():match.end()] as a Plain segment when the type isn’t recognized.

Comment thread core/parse_cq_to_chain.py
Comment on lines +29 to +30
if cq_type == "at":
chain.append(At(qq=params.get("qq", ""), name=""))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: 当缺少 qq 时创建 At 会退回到空字符串,这可能比较容易出错。

qq 缺失或格式不正确时,我们会得到 At(qq=""),它可能会静默失效或产生意外行为。建议将缺少 qq 的情况视为无效——要么将该段落保留为 Plain,要么跳过并插入一个可见的占位符,以便更容易发现配置问题。

Suggested implementation:

        # 根据类型构造组件
        if cq_type == "at":
            qq = (params.get("qq") or "").strip()
            # qq 缺失或格式异常时,不创建空的 At,而是退回为原始文本,便于发现配置问题
            if qq and qq.isdigit():
                chain.append(At(qq=qq, name=""))
            else:
                chain.append(Plain(match.group(0)))
        elif cq_type == "image":

This change assumes that Plain is already imported and available in this module (similar to At and Image). If not, you should import Plain from the same package where other segment classes (like At/Image) come from.

Original comment in English

suggestion: Creating an At with a missing qq falls back to an empty string, which may be error-prone.

When qq is missing or malformed, we end up with At(qq=""), which may silently do nothing or behave unexpectedly. Consider instead treating missing qq as invalid—either leave the segment as Plain, or skip it while inserting a visible placeholder so misconfigurations are easier to spot.

Suggested implementation:

        # 根据类型构造组件
        if cq_type == "at":
            qq = (params.get("qq") or "").strip()
            # qq 缺失或格式异常时,不创建空的 At,而是退回为原始文本,便于发现配置问题
            if qq and qq.isdigit():
                chain.append(At(qq=qq, name=""))
            else:
                chain.append(Plain(match.group(0)))
        elif cq_type == "image":

This change assumes that Plain is already imported and available in this module (similar to At and Image). If not, you should import Plain from the same package where other segment classes (like At/Image) come from.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant