Skip to content

解决wechatpadpro接收不到消息的问题! #2042

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

yigehaozi
Copy link

@yigehaozi yigehaozi commented Jul 6, 2025

解决了 #1977

Motivation

Modifications

Check

  • 😊 我的 Commit Message 符合良好的规范
  • 👀 我的更改经过良好的测试
  • 🤓 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到了 requirements.txtpyproject.toml 文件相应位置。
  • 😮 我的更改没有引入恶意代码

好的,这是翻译成中文的 pull request 总结:

Sourcery 总结

在 WeChatPadPro 适配器中使用 aiohttp 客户端进行 WebSocket 连接,以确保可靠的消息接收,并简化连接和错误处理。

Bug 修复:

  • websockets.connect 切换到 aiohttp.ws_connect,以修复 WeChatPadPro 中消息丢失的问题。

增强功能:

  • 使用 asyncio 替换手动接收循环和基于超时的重新连接,以处理 WebSocket 消息流和错误。
Original summary in English

Summary by Sourcery

Use aiohttp client for WebSocket connections in the WeChatPadPro adapter to ensure reliable message reception and simplify connection and error handling

Bug Fixes:

  • Switch from websockets.connect to aiohttp.ws_connect to fix missing messages in WeChatPadPro

Enhancements:

  • Replace manual receive loop and timeout-based reconnection with asyncio for handling WebSocket message stream and errors

Copy link
Contributor

sourcery-ai bot commented Jul 6, 2025

## 审查者指南

将之前的基于 websockets 的连接和手动轮询循环替换为 aiohttp 的 ClientSession.ws_connect 和 async-for 消息循环,从而提高可靠性并简化错误处理。

### 文件级别更改

| 变更 | 详情 | 文件 |
| ------ | ------- | ----- |
| 切换到 aiohttp 以进行 WebSocket 连接和消息处理 | <ul><li>将 websockets.connect 替换为 aiohttp.ClientSession.ws_connect</li><li>删除手动基于超时的 recv 循环和相关的异常分支</li><li>引入了处理 TEXT、ERROR 和 CLOSED 类型的 ws 消息的 async for 循环</li><li>在 ERROR 和 CLOSED 消息类型上引发异常以触发重新连接</li></ul> | `astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py` |

### 可能相关的 issue

- **#1977**: 此 PR 使用 aiohttp 替换 websockets 客户端以修复该问题。

---

<details>
<summary>提示和命令</summary>

#### 与 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` 以随时(重新)生成摘要。
- **生成审查者指南:** 在 pull request 上评论 `@sourcery-ai guide` 以随时(重新)生成审查者指南。
- **解决所有 Sourcery 评论:** 在 pull request 上评论 `@sourcery-ai resolve` 以解决所有 Sourcery 评论。 如果您已经解决了所有评论并且不想再看到它们,这将非常有用。
- **驳回所有 Sourcery 审查:** 在 pull request 上评论 `@sourcery-ai dismiss` 以驳回所有现有的 Sourcery 审查。 如果你想用一个新的审查重新开始,这尤其有用 - 不要忘记评论 `@sourcery-ai review` 以触发一个新的审查!

#### 自定义您的体验

访问您的 [仪表板](https://app.sourcery.ai) 以:
- 启用或禁用审查功能,例如 Sourcery 生成的 pull request 摘要、审查者指南等。
- 更改审查语言。
- 添加、删除或编辑自定义审查说明。
- 调整其他审查设置。

#### 获得帮助

- [联系我们的支持团队](mailto:[email protected]) 提出问题或反馈。
- 访问我们的[文档](https://docs.sourcery.ai) 以获取详细的指南和信息。
- 通过在 [X/Twitter](https://x.com/SourceryAI)[LinkedIn](https://www.linkedin.com/company/sourcery-ai/)[GitHub](https://github.com/sourcery-ai) 上关注我们,与 Sourcery 团队保持联系。

</details>
Original review guide in English

Reviewer's Guide

Replaces the previous websockets-based connection and manual polling loop with aiohttp’s ClientSession.ws_connect and an async-for message loop, improving reliability and simplifying error handling.

File-Level Changes

Change Details Files
Switch to aiohttp for WebSocket connection and message processing
  • Replaced websockets.connect with aiohttp.ClientSession.ws_connect
  • Removed manual timeout-based recv loop and related exception branches
  • Introduced async for loop over ws messages handling TEXT, ERROR, and CLOSED types
  • Raised exceptions on ERROR and CLOSED message types to trigger reconnection
astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py

Possibly linked issues


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

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

@yigehaozi - 我已经查看了你的更改 - 这里有一些反馈:

  • 为了避免在持久性故障时出现紧密的重新连接循环,请在重试 WebSocket 连接之前添加一个小的退避延迟。
  • 使用更具体的自定义异常(例如,WebSocketClosedError)而不是通用的 Exception,以区分正常关闭和实际错误。
  • 如果服务可能会发送二进制帧,请考虑处理 WSMsgType.BINARY(或记录意外的消息类型)以实现更强大的处理。
AI 代理的提示
请解决此代码审查中的评论:
## 总体评论
- 为了避免在持久性故障时出现紧密的重新连接循环,请在重试 WebSocket 连接之前添加一个小的退避延迟。
- 使用更具体的自定义异常(例如,WebSocketClosedError)而不是通用的 Exception,以区分正常关闭和实际错误。
- 如果服务可能会发送二进制帧,请考虑处理 WSMsgType.BINARY(或记录意外的消息类型)以实现更强大的处理。

## 单独评论

### 评论 1
<location> `astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py:372` </location>
<code_context>
+                async with aiohttp.ClientSession() as session:
+                    async with session.ws_connect(ws_url) as ws:
+                        logger.info("WebSocket 连接成功。")
+                        async for msg in ws:
+                            if msg.type == aiohttp.WSMsgType.TEXT:
+                                asyncio.create_task(self.handle_websocket_message(msg.data))
</code_context>

<issue_to_address>
新的循环中没有空闲超时或重新连接逻辑。

如果没有空闲超时或心跳,客户端可能会在服务器无响应时挂起。请添加一种机制来检测和处理空闲连接。
</issue_to_address>

### 评论 2
<location> `astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py:375` </location>
<code_context>
+                        async for msg in ws:
+                            if msg.type == aiohttp.WSMsgType.TEXT:
+                                asyncio.create_task(self.handle_websocket_message(msg.data))
+                            elif msg.type == aiohttp.WSMsgType.ERROR:
+                                message = f"WebSocket 链接错误: {ws.exception()}"
+                                logger.info(message)
+                                raise Exception(message)
+                            elif msg.type == aiohttp.WSMsgType.CLOSED:
+                                message = f"WebSocket 链接正常关闭。"
</code_context>

<issue_to_address>
在 WebSocket 错误或关闭时引发通用 Exception 可能会掩盖原始原因。

在此处引发通用 Exception 会丢弃原始异常上下文。使用 'raise ws.exception()' 或 'raise Exception(message) from ws.exception()' 来保留堆栈跟踪并帮助调试。
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
                            elif msg.type == aiohttp.WSMsgType.ERROR:
                                message = f"WebSocket 链接错误: {ws.exception()}"
                                logger.info(message)
                                raise Exception(message)
                            elif msg.type == aiohttp.WSMsgType.CLOSED:
                                message = f"WebSocket 链接正常关闭。"
                                logger.info(message)
                                raise Exception(message)
=======
                            elif msg.type == aiohttp.WSMsgType.ERROR:
                                message = f"WebSocket 链接错误: {ws.exception()}"
                                logger.info(message)
                                raise Exception(message) from ws.exception()
                            elif msg.type == aiohttp.WSMsgType.CLOSED:
                                message = f"WebSocket 链接正常关闭。"
                                logger.info(message)
                                if ws.exception() is not None:
                                    raise Exception(message) from ws.exception()
                                else:
                                    raise Exception(message)
>>>>>>> REPLACE

</suggested_fix>

Sourcery 对开源项目是免费的 - 如果您喜欢我们的评论,请考虑分享它们 ✨
帮助我更有用!请点击每个评论上的 👍 或 👎,我将使用反馈来改进您的评论。
Original comment in English

Hey @yigehaozi - I've reviewed your changes - here's some feedback:

  • To avoid tight reconnection loops on persistent failures, add a small backoff delay before retrying the WebSocket connection.
  • Use a more specific custom exception (e.g., WebSocketClosedError) instead of generic Exception to distinguish between normal closures and actual errors.
  • If the service might send binary frames, consider handling WSMsgType.BINARY (or logging unexpected message types) for more robust handling.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- To avoid tight reconnection loops on persistent failures, add a small backoff delay before retrying the WebSocket connection.
- Use a more specific custom exception (e.g., WebSocketClosedError) instead of generic Exception to distinguish between normal closures and actual errors.
- If the service might send binary frames, consider handling WSMsgType.BINARY (or logging unexpected message types) for more robust handling.

## Individual Comments

### Comment 1
<location> `astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py:372` </location>
<code_context>
+                async with aiohttp.ClientSession() as session:
+                    async with session.ws_connect(ws_url) as ws:
+                        logger.info("WebSocket 连接成功。")
+                        async for msg in ws:
+                            if msg.type == aiohttp.WSMsgType.TEXT:
+                                asyncio.create_task(self.handle_websocket_message(msg.data))
</code_context>

<issue_to_address>
No idle timeout or reconnection logic is present in the new loop.

Without an idle timeout or heartbeat, the client may hang if the server becomes unresponsive. Please add a mechanism to detect and handle idle connections.
</issue_to_address>

### Comment 2
<location> `astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py:375` </location>
<code_context>
+                        async for msg in ws:
+                            if msg.type == aiohttp.WSMsgType.TEXT:
+                                asyncio.create_task(self.handle_websocket_message(msg.data))
+                            elif msg.type == aiohttp.WSMsgType.ERROR:
+                                message = f"WebSocket 链接错误: {ws.exception()}"
+                                logger.info(message)
+                                raise Exception(message)
+                            elif msg.type == aiohttp.WSMsgType.CLOSED:
+                                message = f"WebSocket 链接正常关闭。"
</code_context>

<issue_to_address>
Raising a generic Exception on WebSocket error or close may obscure the original cause.

Raising a generic Exception here discards the original exception context. Use 'raise ws.exception()' or 'raise Exception(message) from ws.exception()' to retain the stack trace and aid debugging.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
                            elif msg.type == aiohttp.WSMsgType.ERROR:
                                message = f"WebSocket 链接错误: {ws.exception()}"
                                logger.info(message)
                                raise Exception(message)
                            elif msg.type == aiohttp.WSMsgType.CLOSED:
                                message = f"WebSocket 链接正常关闭。"
                                logger.info(message)
                                raise Exception(message)
=======
                            elif msg.type == aiohttp.WSMsgType.ERROR:
                                message = f"WebSocket 链接错误: {ws.exception()}"
                                logger.info(message)
                                raise Exception(message) from ws.exception()
                            elif msg.type == aiohttp.WSMsgType.CLOSED:
                                message = f"WebSocket 链接正常关闭。"
                                logger.info(message)
                                if ws.exception() is not None:
                                    raise Exception(message) from ws.exception()
                                else:
                                    raise Exception(message)
>>>>>>> REPLACE

</suggested_fix>

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 on lines +375 to +382
elif msg.type == aiohttp.WSMsgType.ERROR:
message = f"WebSocket 链接错误: {ws.exception()}"
logger.info(message)
raise Exception(message)
elif msg.type == aiohttp.WSMsgType.CLOSED:
message = f"WebSocket 链接正常关闭。"
logger.info(message)
raise Exception(message)
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (bug_risk): 在 WebSocket 错误或关闭时引发通用 Exception 可能会掩盖原始原因。

在此处引发通用 Exception 会丢弃原始异常上下文。使用 'raise ws.exception()' 或 'raise Exception(message) from ws.exception()' 来保留堆栈跟踪并帮助调试。

Suggested change
elif msg.type == aiohttp.WSMsgType.ERROR:
message = f"WebSocket 链接错误: {ws.exception()}"
logger.info(message)
raise Exception(message)
elif msg.type == aiohttp.WSMsgType.CLOSED:
message = f"WebSocket 链接正常关闭。"
logger.info(message)
raise Exception(message)
elif msg.type == aiohttp.WSMsgType.ERROR:
message = f"WebSocket 链接错误: {ws.exception()}"
logger.info(message)
raise Exception(message) from ws.exception()
elif msg.type == aiohttp.WSMsgType.CLOSED:
message = f"WebSocket 链接正常关闭。"
logger.info(message)
if ws.exception() is not None:
raise Exception(message) from ws.exception()
else:
raise Exception(message)
Original comment in English

suggestion (bug_risk): Raising a generic Exception on WebSocket error or close may obscure the original cause.

Raising a generic Exception here discards the original exception context. Use 'raise ws.exception()' or 'raise Exception(message) from ws.exception()' to retain the stack trace and aid debugging.

Suggested change
elif msg.type == aiohttp.WSMsgType.ERROR:
message = f"WebSocket 链接错误: {ws.exception()}"
logger.info(message)
raise Exception(message)
elif msg.type == aiohttp.WSMsgType.CLOSED:
message = f"WebSocket 链接正常关闭。"
logger.info(message)
raise Exception(message)
elif msg.type == aiohttp.WSMsgType.ERROR:
message = f"WebSocket 链接错误: {ws.exception()}"
logger.info(message)
raise Exception(message) from ws.exception()
elif msg.type == aiohttp.WSMsgType.CLOSED:
message = f"WebSocket 链接正常关闭。"
logger.info(message)
if ws.exception() is not None:
raise Exception(message) from ws.exception()
else:
raise Exception(message)

elif msg.type == aiohttp.WSMsgType.ERROR:
message = f"WebSocket 链接错误: {ws.exception()}"
logger.info(message)
raise Exception(message)
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (code-quality): 引发特定的错误,而不是通用的 ExceptionBaseException (raise-specific-error)

Explanation如果一段代码引发一个特定的异常类型,而不是通用的 [`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException) 或 [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception),则调用代码可以:
  • 获取关于错误类型的更多信息
  • 为其定义特定的异常处理

这样,代码的调用者可以适当地处理错误。

您如何解决这个问题?

因此,与其让代码引发 ExceptionBaseException,例如:

if incorrect_input(value):
    raise Exception("The input is incorrect")

您可以让代码引发一个特定的错误,例如:

if incorrect_input(value):
    raise ValueError("The input is incorrect")

或者:

class IncorrectInputError(Exception):
    pass


if incorrect_input(value):
    raise IncorrectInputError("The input is incorrect")
Original comment in English

issue (code-quality): Raise a specific error instead of the general Exception or BaseException (raise-specific-error)

ExplanationIf a piece of code raises a specific exception type rather than the generic [`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException) or [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception), the calling code can:
  • get more information about what type of error it is
  • define specific exception handling for it

This way, callers of the code can handle the error appropriately.

How can you solve this?

So instead of having code raising Exception or BaseException like

if incorrect_input(value):
    raise Exception("The input is incorrect")

you can have code raising a specific error like

if incorrect_input(value):
    raise ValueError("The input is incorrect")

or

class IncorrectInputError(Exception):
    pass


if incorrect_input(value):
    raise IncorrectInputError("The input is incorrect")

elif msg.type == aiohttp.WSMsgType.CLOSED:
message = f"WebSocket 链接正常关闭。"
logger.info(message)
raise Exception(message)
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (code-quality): 引发特定的错误,而不是通用的 ExceptionBaseException (raise-specific-error)

Explanation如果一段代码引发一个特定的异常类型,而不是通用的 [`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException) 或 [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception),则调用代码可以:
  • 获取关于错误类型的更多信息
  • 为其定义特定的异常处理

这样,代码的调用者可以适当地处理错误。

您如何解决这个问题?

因此,与其让代码引发 ExceptionBaseException,例如:

if incorrect_input(value):
    raise Exception("The input is incorrect")

您可以让代码引发一个特定的错误,例如:

if incorrect_input(value):
    raise ValueError("The input is incorrect")

或者:

class IncorrectInputError(Exception):
    pass


if incorrect_input(value):
    raise IncorrectInputError("The input is incorrect")
Original comment in English

issue (code-quality): Raise a specific error instead of the general Exception or BaseException (raise-specific-error)

ExplanationIf a piece of code raises a specific exception type rather than the generic [`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException) or [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception), the calling code can:
  • get more information about what type of error it is
  • define specific exception handling for it

This way, callers of the code can handle the error appropriately.

How can you solve this?

So instead of having code raising Exception or BaseException like

if incorrect_input(value):
    raise Exception("The input is incorrect")

you can have code raising a specific error like

if incorrect_input(value):
    raise ValueError("The input is incorrect")

or

class IncorrectInputError(Exception):
    pass


if incorrect_input(value):
    raise IncorrectInputError("The input is incorrect")

logger.info(message)
raise Exception(message)
elif msg.type == aiohttp.WSMsgType.CLOSED:
message = f"WebSocket 链接正常关闭。"
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (code-quality): 将没有插值的 f-string 替换为字符串 (remove-redundant-fstring)

Suggested change
message = f"WebSocket 链接正常关闭。"
message = "WebSocket 链接正常关闭。"
Original comment in English

suggestion (code-quality): Replace f-string with no interpolated values with string (remove-redundant-fstring)

Suggested change
message = f"WebSocket 链接正常关闭。"
message = "WebSocket 链接正常关闭。"

@Soulter
Copy link
Member

Soulter commented Jul 20, 2025

为什么这样改可以解决这个问题呢

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.

2 participants