如何用discord.py实现CMD输入消息发送?附代码报错求助
问题解决方案与代码优化建议
一、解决两个核心错误
1. AttributeError: 'NoneType' object has no attribute 'send'
出现这个问题的核心原因是bot.get_channel(channelid)未获取到有效频道对象,常见诱因及修复方式:
- 频道ID格式错误:确保传入的
channelid是整数类型(Discord频道ID是纯数字字符串,需转成int),而非字符串。 - 机器人未完成初始化:必须在
bot.on_ready()事件触发后,再执行获取频道、发送消息的操作——此时机器人已完成与Discord网关的连接,能正确拉取服务器/频道数据。 - 权限或服务器归属问题:检查机器人是否已加入目标频道所在的服务器,且拥有
发送消息权限。
2. RuntimeWarning: coroutine 'reciever' was never awaited
这个警告是因为你在子线程中直接调用了协程函数,但未正确触发其执行。协程必须通过await或asyncio.run()来启动,在子线程中需要单独创建asyncio事件循环:
import asyncio def input_thread(bot, target_channel_id): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) loop.run_until_complete(send_input_messages(bot, target_channel_id)) loop.close() async def send_input_messages(bot, target_channel_id): channel = bot.get_channel(target_channel_id) if not channel: print("无效频道ID,请检查后重试") return while True: msg = input("输入要发送的消息(输入exit退出):") if msg.lower() == "exit": await bot.close() break await channel.send(msg)
二、完整可行代码示例
整合三个模块的功能,同时处理输入与机器人事件:
主模块(main.py)
import discord from discord.ext import commands import threading import asyncio # 初始化机器人 intents = discord.Intents.default() intents.message_content = True bot = commands.Bot(command_prefix="!", intents=intents) # 消息监听逻辑 @bot.event async def on_message(message): # 忽略机器人自身消息 if message.author == bot.user: return print(f"[{message.channel.name}] {message.author.name}: {message.content}") await bot.process_commands(message) @bot.event async def on_ready(): print(f"已登录为 {bot.user.name}") # 启动输入线程 target_channel_id = int(input("输入目标频道ID:")) threading.Thread(target=input_thread, args=(bot, target_channel_id), daemon=True).start() def input_thread(bot, target_channel_id): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) loop.run_until_complete(send_input_messages(bot, target_channel_id)) loop.close() async def send_input_messages(bot, target_channel_id): channel = bot.get_channel(target_channel_id) if not channel: print("错误:无法找到目标频道,请检查ID或机器人权限") await bot.close() return print(f"已绑定频道:{channel.name},输入消息即可发送") while True: try: msg = input() if msg.lower() == "exit": await bot.close() break await channel.send(msg) except Exception as e: print(f"发送失败:{str(e)}") if __name__ == "__main__": # 用户协议确认 print("=== CMD Discord 客户端 ===") agree = input("是否同意使用条款?(y/n):") if agree.lower() != "y": print("程序退出") exit() token = input("输入Discord机器人Token:") bot.run(token)
三、代码优化建议
- 用异步队列替代直接线程调用:避免线程与asyncio事件循环的冲突,通过
asyncio.Queue在输入线程和机器人事件循环间传递消息,更符合discord.py的异步模型:# 在主模块初始化队列 msg_queue = asyncio.Queue() # 输入线程只负责把消息放入队列 def input_thread(): while True: msg = input() if msg.lower() == "exit": msg_queue.put_nowait("exit") break msg_queue.put_nowait(msg) # 在bot.on_ready()中启动消费队列的任务 @bot.event async def on_ready(): print(f"已登录为 {bot.user.name}") target_channel_id = int(input("输入目标频道ID:")) threading.Thread(target=input_thread, daemon=True).start() await process_msg_queue(target_channel_id) async def process_msg_queue(target_channel_id): channel = bot.get_channel(target_channel_id) if not channel: print("错误:无法找到目标频道") await bot.close() return while True: msg = await msg_queue.get() if msg == "exit": await bot.close() break await channel.send(msg) - 参数验证与错误处理:对输入的Token、频道ID做格式校验,捕获网络异常、权限不足等错误并给出明确提示。
- 模块化拆分:将消息监听、发送逻辑拆分为单独的函数/模块,但确保所有异步逻辑都在同一个asyncio事件循环中处理,避免跨线程直接操作bot实例。
- 日志记录:使用
logging模块替代print,方便调试和记录运行状态。 - 弃用sys.addaudithook:该API主要用于审计Python运行时行为,不适合处理用户输入,属于误用场景,建议放弃这种实现方式。
内容的提问来源于stack exchange,提问作者TerminalVelocity
相关产品推荐
相关产品推荐

