You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Discord机器人跨服务器消息转发遇Gateway心跳阻塞断开问题求助

问题分析与解决方案

你的机器人出现discord.gateway: Shard ID None heartbeat blocked for more than 10 seconds警告,核心原因是同步代码阻塞了Discord.py的异步事件循环,导致网关心跳无法按时发送,而非单纯的请求量过大。以下是代码中的关键问题和修复方案:

关键问题点

  1. 同步WebSocket操作阻塞事件循环:你使用的websocket库是同步实现,receive_json_response里的ws.recv()会卡住整个异步事件循环,让Discord.py无法处理心跳和其他事件。
  2. 同步HTTP请求拖慢响应:get_attachment_media里的requests.get是同步请求,会阻塞事件循环,导致消息转发和心跳延迟。
  3. 错误的心跳实现:用time.sleep的同步心跳函数,再通过asyncio.run强行运行,直接阻塞了on_ready后的事件循环启动。
  4. 宽泛的异常捕获:except: pass会掩盖所有错误,包括WebSocket连接失败、事件解析错误等,无法定位问题。

修复后的代码

import asyncio
import json
import discord
import aiohttp
from io import BytesIO
from discord.ext import tasks

# 配置
bot_token = "你的机器人token"
user_token = "你的用户token"
target_channel_id = 你的目标频道ID  # 替换成实际数字ID
client = discord.Client(intents=discord.Intents.all())  # 需开启消息相关意图

# 加载监听频道列表
with open('channels.txt', 'r', encoding='utf-8') as f:
    monitored_channels = set(f.read().strip().split('\n'))  # 用集合提升查询速度

async def send_json_request(ws, request):
    await ws.send(json.dumps(request))

async def receive_json_response(ws):
    response = await ws.recv()
    if response:
        return json.loads(response)

async def get_attachment_media(media):
    media_array = []
    async with aiohttp.ClientSession() as session:
        for item in media:
            async with session.get(item['url']) as resp:
                if resp.status == 200:
                    content = await resp.read()
                    im = BytesIO(content)
                    media_array.append(discord.File(im, filename=item['filename']))
    return media_array

async def heartbeat(ws, interval):
    print("心跳任务启动")
    while True:
        await asyncio.sleep(interval)
        heartbeat_json = {"op": 1, "d": None}
        await send_json_request(ws, heartbeat_json)
        print("发送心跳")

@tasks.loop(seconds=0.1)  # 缩短间隔但用异步接收,不会阻塞
async def message_listener(ws):
    try:
        event = await receive_json_response(ws)
        if not event or 'd' not in event:
            return

        # 忽略自己发送的消息
        if event['d'].get('author', {}).get('id') == '你的用户ID':
            return

        channel_id = event['d'].get('channel_id')
        if channel_id in monitored_channels:
            content = event['d'].get('content', '')
            attachments = event['d'].get('attachments', [])
            media_files = await get_attachment_media(attachments)
            
            target_channel = client.get_channel(target_channel_id)
            if target_channel:
                await target_channel.send(content, files=media_files)
        
        # 处理心跳响应
        if event.get('op') == 11:
            print("收到心跳响应")
    except Exception as e:
        print(f"监听任务出错: {str(e)}")

@client.event
async def on_ready():
    print(f"机器人已登录: {client.user}")
    # 建立异步WebSocket连接
    async with aiohttp.ClientSession() as session:
        async with session.ws_connect("wss://gateway.discord.gg/?v=6&encoding=json") as ws:
            # 接收初始化事件
            init_event = await receive_json_response(ws)
            heartbeat_interval = init_event['d']['heartbeat_interval'] / 1000

            # 发送身份认证
            await send_json_request(ws, {
                "op": 2,
                "d": {
                    "token": user_token,
                    "properties": {
                        "$os": 'linux',
                        "$browser": 'chrome',
                        "$device": 'pc'
                    }
                }
            })

            # 启动心跳任务和消息监听任务
            heartbeat_task = asyncio.create_task(heartbeat(ws, heartbeat_interval))
            listener_task = asyncio.create_task(message_listener(ws))
            
            # 等待任务完成(保持连接)
            await asyncio.gather(heartbeat_task, listener_task)

client.run(bot_token)

核心修改说明

  1. 替换为异步库:用aiohttp的异步WebSocket和HTTP请求,彻底避免阻塞事件循环。
  2. 异步心跳实现:用asyncio.sleep代替time.sleep,心跳任务作为异步协程运行,不会阻塞主循环。
  3. 优化频道查询:将监听频道列表转为set,查询时间从O(n)降到O(1)。
  4. 精准异常捕获:替换宽泛的except: pass,打印具体错误信息方便调试。
  5. 正确的任务管理:用asyncio.create_task启动异步任务,而非asyncio.run,避免阻塞事件循环。
  6. 意图权限:将Intents.default()改为Intents.all()(或按需开启消息相关意图),确保能接收消息事件。

额外注意事项

  • Discord禁止使用用户token运行自动化程序,可能导致账号封禁,建议用机器人token监听目标服务器,而非用户token。
  • 如果转发量较大,建议添加消息发送的延迟(比如await asyncio.sleep(1)),避免触发Discord的速率限制。
  • 确保在Discord开发者后台开启了Message Content Intent权限,否则无法读取消息内容。

内容的提问来源于stack exchange,提问作者Ayvengo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 20:40:29