Skip to content

Commit 2cfc0f3

Browse files
🐛 修复 Gateway WebSocket 空闲超时重连 (#93)
* 🐛 修复 Gateway WebSocket 空闲超时重连 * 🚨 修复 Gateway WebSocket 测试私有导入 * ⬆️ 提升 nonebot2 最低版本 * 🐛 显式禁用 Gateway 接收超时 * 🐛 保留 Gateway 关闭超时 * 🐛 保留 Gateway 连接超时 * 🚨 auto fix by pre-commit hooks --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent a300080 commit 2cfc0f3

4 files changed

Lines changed: 77 additions & 9 deletions

File tree

nonebot/adapters/discord/adapter.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from nonebot.adapters import Adapter as BaseAdapter, Bot as BaseBot
1212

1313
from nonebot.compat import type_validate_json, type_validate_python
14-
from nonebot.drivers import URL, Driver, ForwardDriver, Request, WebSocket
14+
from nonebot.drivers import URL, Driver, ForwardDriver, Request, Timeout, WebSocket
1515
from nonebot.exception import WebSocketClosed
1616
from nonebot.plugin import get_plugin_config
1717
from nonebot.utils import escape_tag
@@ -184,7 +184,11 @@ async def _forward_ws( # noqa: C901
184184
url=ws_url,
185185
headers=headers,
186186
params=params,
187-
timeout=self.discord_config.discord_api_timeout,
187+
timeout=Timeout(
188+
connect=self.discord_config.discord_api_timeout,
189+
read=None,
190+
close=10.0,
191+
),
188192
proxy=self.discord_config.discord_proxy,
189193
)
190194
heartbeat_task: asyncio.Task | None = None

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ license = "MIT"
1313
readme = "README.md"
1414
keywords = ["nonebot", "discord", "bot"]
1515
requires-python = ">=3.10,<4.0.0"
16-
dependencies = ["nonebot2>=2.4.4"]
16+
dependencies = ["nonebot2>=2.5.0"]
1717

1818
[dependency-groups]
1919
dev = [

tests/test_outbound_transport_serialization.py

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import asyncio
34
from collections.abc import Awaitable, Sequence
45
from datetime import datetime, timezone
56
import json
@@ -14,16 +15,18 @@
1415
InteractionResponse,
1516
RecurrenceRule,
1617
Snowflake,
18+
User,
1719
)
1820
from nonebot.adapters.discord.api.types import (
1921
GuildScheduledEventEntityType,
2022
GuildScheduledEventPrivacyLevel,
2123
GuildScheduledEventRecurrenceRuleFrequency,
2224
InteractionCallbackType,
2325
)
26+
from nonebot.adapters.discord.config import BotInfo
2427
from tests.fake.doubles import DummyAdapter, DummyBot
2528

26-
from nonebot.drivers import Request, WebSocket
29+
from nonebot.drivers import URL, Request, Timeout, WebSocket
2730
import pytest
2831

2932

@@ -283,3 +286,64 @@ async def send_bytes(self, data: bytes) -> None:
283286
await adapter._heartbeat(ws, bot) # noqa: SLF001
284287

285288
assert ws.sent == ['{"op":1,"d":42}']
289+
290+
291+
@pytest.mark.asyncio
292+
async def test_gateway_websocket_request_does_not_inherit_api_timeout(
293+
monkeypatch: pytest.MonkeyPatch,
294+
) -> None:
295+
adapter = DummyAdapter()
296+
adapter.discord_config.discord_api_timeout = 30.0
297+
bot_info = BotInfo(token="x" * 10)
298+
captured_request: Request | None = None
299+
300+
class StopForwardError(Exception):
301+
pass
302+
303+
class StopWebSocket:
304+
async def __aenter__(self) -> None:
305+
msg = "websocket request captured"
306+
raise RuntimeError(msg)
307+
308+
async def __aexit__(
309+
self,
310+
_exc_type: object,
311+
_exc: object,
312+
_traceback: object,
313+
) -> None:
314+
return None
315+
316+
async def fake_get_bot_user(_bot_info: BotInfo) -> User:
317+
return User(
318+
id=Snowflake(1),
319+
username="bot",
320+
discriminator="0000",
321+
avatar=None,
322+
)
323+
324+
def fake_websocket(request: Request) -> StopWebSocket:
325+
nonlocal captured_request
326+
captured_request = request
327+
return StopWebSocket()
328+
329+
async def stop_reconnect_sleep(_delay: float) -> None:
330+
raise StopForwardError
331+
332+
monkeypatch.setattr(adapter, "_get_bot_user", fake_get_bot_user)
333+
monkeypatch.setattr(adapter, "websocket", fake_websocket)
334+
monkeypatch.setattr(asyncio, "sleep", stop_reconnect_sleep)
335+
336+
with pytest.raises(StopForwardError):
337+
await adapter._forward_ws( # noqa: SLF001
338+
bot_info,
339+
URL("wss://gateway.discord.gg"),
340+
(0, 1),
341+
)
342+
343+
assert captured_request is not None
344+
assert isinstance(captured_request.timeout, Timeout)
345+
assert (
346+
captured_request.timeout.connect == adapter.discord_config.discord_api_timeout
347+
)
348+
assert captured_request.timeout.read is None
349+
assert captured_request.timeout.close == 10.0

uv.lock

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)