【问题标题】:Expose discord bot to API (Flask, FASTAPI)将 discord bot 暴露给 API (Flask, FASTAPI)
【发布时间】:2021-02-13 19:19:29
【问题描述】:

我正在构建一个不和谐的机器人来接收来自多个系统和程序的命令。我想将我的不和谐机器人的某些操作公开给 REST 端点,然后在一个地方执行所述操作。

import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional
from discord.ext import commands

app = FastAPI()

TOKEN = 'MY_TOKEN'

bot = commands.Bot(command_prefix='>')

class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: Optional[float] = None

@app.get("/")
def hello():
    return {"message":"Hello"}

@app.post("/items/")
async def create_item(item: Item):
    await send_message()
    return item

@bot.event
async def on_ready():
    print(f'{bot.user.name} has connected to Discord!')

async def send_message():
    user = await bot.fetch_user(USER_ID)
    await user.send('????')

if __name__ == "__main__":
    bot.run('BOT_TOKEN')
    uvicorn.run(app, host='0.0.0.0')

当我尝试运行它时,我只看到机器人处于活动状态。我对python有点新,但我是一名资深程序员。这是由于python“缺乏”多线程吗?还是端口使用情况?

最终目标是调用“/items/”端点并查看发送给我的关于不和谐的消息

编辑

我尝试了所有答案并想出了一些我自己的答案。问题是多线程。我对此感到很沮丧,最后只是将这部分移到了 Node.js。它在技术上并不能解决这个问题,但比导航 python 多线程要容易得多。

server.js:

var express = require('express');
var app = express();
const Discord = require('discord.js');
const client = new Discord.Client();

app.get('/listUsers', function (req, res) {

    dm_user();

    res.send('hello');
})

client.on('ready', () => {
  console.log(`Logged in as ${client.user.tag}!`);
});

client.on('message', msg => {
  if (msg.content === 'ping') {
    msg.reply('pong');
  }
});

async function dm_user(id){
    var my_user = await client.users.fetch('USER_ID');
    console.log(my_user);
}

var server = app.listen(8081, function () {
   var host = server.address().address
   var port = server.address().port
   console.log("Example app listening at http://%s:%s", host, port)
   client.login('TOKEN');
})

【问题讨论】:

  • 所以,用浏览器导航到localhost:8000/docs(或您分配给您机器的任何 IP 地址)不会返回任何内容,对吧?
  • @isabi 是正确的
  • 当你运行 bot.run('BOT_TOKEN') 时,它会运行这个函数,直到你停止 bot 并在关闭 bot 后执行 uvicorn.run()。所以uvicorn.run() 没有被执行,你无法连接到网页。您必须在单独的线程中启动机器人。

标签: python discord.py fastapi


【解决方案1】:

根据 discord.py 文档 bot.run() 是“从您那里抽象出事件循环初始化的阻塞调用。”他们还说,如果我们想要更多地控制循环,我们可以使用 start() 协程而不是 run()。所以现在我们应该创建一个任务来调用这个协程,我们知道discord.pyFastAPI 都是asynchronous 应用程序。要启动 FastAPI 应用程序,您需要一个 ASGI 服务器来处理它。在这种情况下,我们使用Uvicorn。到目前为止,我们已经运行了 FastAPI 应用程序,现在我们需要启动我们的 discord 机器人。根据 FastAPI 文档,我们可以使用startup/shutdown event,在主 API 启动之前调用 bot.start() 协程。

这是一个应用程序示例,该应用程序具有用于向不和谐用户发送消息的 API 端点:

import asyncio
import discord
import uvicorn

from config import TOKEN, USER_ID
from fastapi import FastAPI

app = FastAPI()
bot = discord.Client()

@app.on_event("startup")
async def startup_event(): #this fucntion will run before the main API starts
    asyncio.create_task(bot.start(TOKEN))
    await asyncio.sleep(4) #optional sleep for established connection with discord
    print(f"{bot.user} has connected to Discord!")

@app.get("/")
async def root(msg: str): #API endpoint for sending a message to a discord's user
    user = await send_message(msg)
    return {"Message": f"'{msg}' sent to {user}"}

async def send_message(message):
    user = await bot.fetch_user(USER_ID)
    await user.send(message)
    return user #for optional log in the response of endpoint

if __name__ == "__main__":
    uvicorn.run(app, host="localhost", port=5000)

使用 Python 3.7.4 测试

【讨论】:

  • 放弃投票,因为这仅在您不使用 cogs/extensions 的情况下有效。如果整个机器人是单个文件,那么它可以工作。但是,如果您有 cogs/extensions,则只有主 cog 可以工作,其他的根本不会执行。
【解决方案2】:

您没有从 send_message 函数返回任何内容。这样的事情应该会很好。

@app.post("/items/")
async def create_item(item: Item):
    msg = await send_message()
    return msg


async def send_message():
    user = await bot.fetch_user(USER_ID)
    return await user.send('?')

【讨论】:

  • 不确定您是否尝试过此解决方案。我在我的身上试了一下,并没有接近工作。请发布您为我接受的完整代码集
【解决方案3】:

代码bot.run(...) 一直在运行,它会阻止启动 API 的下一行。您必须在单独的线程或进程中运行其中一个。

我试图在thread 中运行bot

if __name__ == "__main__":
    import threading 

    print('Starting bot')
    t = threading.Thread(target=bot.start, args=(TOKEN,))
    t.start()
    
    print('Starting API')
    uvicorn.run(app, host='0.0.0.0')

但它告诉我bot 应该在主线程中运行。

但我发现了问题Discord bot and bottle in the same time in Python 并基于它创建了适合我的代码

if __name__ == "__main__":
    import asyncio
    
    print('Starting bot')
    bot_app = bot.start(TOKEN)
    bot_task = asyncio.ensure_future(bot_app)
    
    print('Starting API')
    uvicorn.run(app, host='0.0.0.0')

但我不确定这是否是优雅的方法,因为 uvicorn 间接运行 ayncio


完整版

import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional
from discord.ext import commands

app = FastAPI()

#import os
#TOKEN = os.getenv("DISCORD_TOKEN")
TOKEN = 'MY_TOKEN'

bot = commands.Bot(command_prefix='>')

class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: Optional[float] = None

@app.get("/")
def hello():
    return {"message":"Hello"}

@app.post("/items/")
async def create_item(item: Item):
    await send_message()
    return item

@bot.event
async def on_ready():
    print(f'{bot.user.name} has connected to Discord!')

async def send_message():
    user = await bot.fetch_user(USER_ID)
    await user.send('?')

if __name__ == "__main__":
    import asyncio
        
    print('Starting bot')
    bot_app = bot.start(TOKEN)
    bot_task = asyncio.ensure_future(bot_app)
        
    print('Starting API')
    uvicorn.run(app, host='0.0.0.0')

【讨论】:

  • 我想你很接近了。我试过你的 sn-ps 并不能让它工作。您能否使用完整的代码集编辑您的回复,以便我知道我使用的是相同的东西?
  • 完全一样,但我放了完整的代码。
  • 这不起作用,因为 uvicorn 永远运行它自己的应用程序(或直到完成,除非被杀死、中断或自行退出,否则永远不会发生)。所以 ensure_future() 永远不会执行。
  • @Fanatique 你对我的代码或你自己的代码有问题吗?我的代码对我有用。它需要使用ensure_future(bot_app) 而不是bot.run(),并且必须在uvicorn.run() 之前使用。如果您自己的代码有问题,那么最好在新页面上创建新问题,您将有更多空间来显示您的代码和描述。也许您的代码可能需要完全不同的解决方案。
  • @furas 您的代码不起作用。自己试试。当机器人准备好时,它应该会收到on_ready() 事件,你已经编写好了代码。您是否在控制台中的任何位置获得print(f'{bot.user.name} has connected to Discord!')?我刚刚说明了它不起作用的原因。 ensure_future 不会在 API 之前运行机器人。即使发生了也没关系。如果它在 API 之前运行:API 将无法工作。如果它在 API 之后运行:机器人将永远无法工作。 bot 和 API 都有独立的阻塞事件循环,只有其中一个可以工作。在您的示例中,只有 API 有效。
猜你喜欢
  • 2020-09-05
  • 1970-01-01
  • 2021-08-11
  • 2011-09-07
  • 1970-01-01
  • 2019-04-13
  • 2011-04-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多