简体   繁体   English

将 discord 机器人暴露给 API(Flask,FASTAPI)

[英]Expose discord bot to API (Flask, FASTAPI)

I'm building a discord bot to take commands from multiple systems and programs.我正在构建一个 discord 机器人来接收来自多个系统和程序的命令。 I'm wanting to expose certain actions of my discord bot to REST endpoints and then execute said actions in one spot.我想将我的 discord 机器人的某些操作暴露给 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')

When I try to run this, I'm only seeing the bot active.当我尝试运行它时,我只看到机器人处于活动状态。 I'm a little newer to python but a veteran programmer.我对 python 有点新,但我是一名资深程序员。 Is this due to python's "lack" of multithreading?这是由于python“缺乏”多线程吗? Or port usage?还是端口使用?

The end goal is to call the "/items/" endpoint and see a message on discord sent to me最终目标是调用“/items/”端点并在 discord 上查看发送给我的消息

EDIT编辑

I tried all the answers and coming up with some of my own.我尝试了所有的答案并想出了一些我自己的答案。 The problem is multi-threading.问题是多线程。 I got frustrated with it and ended up just moving this piece to Node.js.我对此感到沮丧,最后只是将这件作品移至 Node.js。 It doesn't technically fulfill this question but was far easier than navigating python multithreading.它在技术上并不能解决这个问题,但比导航 python 多线程要容易得多。

server.js: 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');
})

According to the discord.py docs bot.run() is "A blocking call that abstracts away the event loop initialisation from you."根据 discord.py 文档bot.run()是“从您那里抽象出事件循环初始化的阻塞调用”。 and further they said if we want more control over the loop we could use start() coroutine instead of run().他们还说,如果我们想要更多地控制循环,我们可以使用 start() 协程而不是 run()。 So now we should create a task for calling this coroutine and we know discord.py and FastAPI all are asynchronous applications.所以现在我们应该创建一个任务来调用这个协程,我们知道discord.pyFastAPI都是异步应用程序。 For starting a FastAPI app you need an ASGI server to handle it.要启动 FastAPI 应用程序,您需要一个 ASGI 服务器来处理它。 In this case, we're using Uvicorn .在这种情况下,我们使用Uvicorn So far we have run FastAPI app, now we need to start our discord bot.到目前为止,我们已经运行了 FastAPI 应用程序,现在我们需要启动我们的 discord 机器人。 According to FastAPI docs we could use startup/shutdown event , for calling bot.start() coroutine before the main API starts.根据 FastAPI 文档,我们可以使用启动/关闭事件,在主 API 启动之前调用 bot.start() 协程。

Here is an example of an app which has an API endpoint for sending a message to a discord's user:这是一个应用程序示例,该应用程序具有 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)

Tested with Python 3.7.4用 Python 3.7.4 测试

You are not returning anything from your send_message function.您没有从您的send_message function 中返回任何内容。 Something like this should do good.像这样的事情应该做得很好。

@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('👀')

Code bot.run(...) runs all time and it blocks next line which starts API.代码bot.run(...)一直运行,它会阻止下一行开始 API。 You would have to run one of them in separated thread or process.您必须在单独的线程或进程中运行其中之一。

I tried to run bot in thread我试图在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')

but it gives me message that bot should run in main thread.但它给我的信息是bot应该在主线程中运行。

But I found question Discord bot and bottle in the same time in Python and base on it I create code which works for me但是我在Python 中同时发现了问题 Discord bot 和 bottle并基于它创建了适合我的代码

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')

But I'm not sure if this is ellegant method because uvicorn runs ayncio indirectly.但我不确定这是否是优雅的方法,因为uvicorn间接运行ayncio


Full version完整版本

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')

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM