Discord.py - Sending a message within a synchronous function - python

I am currently working on a Python program using Flask and Discord.py. The program will take parameters from an HTTP GET Request, and then the bot will send them in a message to a specific channel in my Discord server. However, I cannot figure out how to send a message every time a new GET Request is sent since it uses an asynchronous function. My current code is
import os
import discord
from discord.ext.commands import Bot
from flask import Flask, request
from threading import Thread
bot = Bot("!")
messageToSend = ""
recievedMessage = ""
app = Flask('')
#app.route('/')
def home():
return messageToSend
#app.route('/login',methods = ['POST', 'GET'])
def login():
global recievedMessage
if request.method == 'GET':
user = request.args.get('nm')
message = request.args.get('msg')
recievedMessage = f"[{user}]: {message}"
print(recievedMessage)
return user
def run ():
app.run(host='0.0.0.0',port=8080)
def keep_alive():
t = Thread(target=run)
t.start()
#bot.command()
async def send():
message = send_message()
channel = bot.get_channel(852280470384410654)
await channel.send(message)
keep_alive()
bot.run(os.getenv('TOKEN'))
I have tried other methods, such as using Asyncio, but none of them have been able to work. Is it possible to do what I am attempting?

Related

running discord.py bot at the same time with a flask application

i was working on a discord bot that let you interact from outside discord.
i used a simple flask application as a test, so i wanna make a bot that uses a list array of users objects so the user can sign in from the browser flask application with his discord account so the flask application identify him with his discord id and so on shows him the collected information from the discord bot.
so i am using this code.
import discord
import os
from discord.ext import commands, tasks
from flask import Flask, redirect, url_for
from flask_discord import DiscordOAuth2Session, requires_authorization, Unauthorized
class p:
def __init__(self, name, id):
self.name = name
self.id = id
self.age = 17
intents = discord.Intents.default()
intents.members = True
bot = commands.Bot(intents=intents, command_prefix="-", case_insensitive=True)
#bot.event
async def on_ready():
globals()["server"] = bot.guilds[0]
globals()["users"] = [] # the array of users that the bot adn the flask app should use.
for member in server.members: # looping trough users adding each users to the users array
globals()["U_" + str(member.id)] = p(member.name, member.id)
users.append(globals()["U_" + str(member.id)])
print("Bot is Online !")
#bot.command()
async def get_data(ctx):
u = ctx.author.id
usr = globals()["U_" + str(u)]
await ctx.send(f"age = {usr.age}")
#bot.command()
async def edit_data(ctx, arg1):
u = ctx.author.id
usr = globals()["U_" + str(u)]
usr.age = int(arg1)
await ctx.send(f"age = {usr.age}")
app = Flask(__name__) # the flask app
app.secret_key = b"secret key"
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "true"
app.config["DISCORD_CLIENT_ID"] = 0
app.config["DISCORD_CLIENT_SECRET"] = ""
app.config["DISCORD_REDIRECT_URI"] = "http://127.0.0.1/callback"
app.config["DISCORD_BOT_TOKEN"] = ""
disc = DiscordOAuth2Session(app)
#app.route("/login/")
def login():
return disc.create_session()
#app.errorhandler(Unauthorized)
def redirect_unauthorized(e):
return redirect(url_for("login"))
#app.route("/callback/")
def callback():
disc.callback()
return redirect(url_for(".me"))
#app.route("/me/")
#requires_authorization
def me():
user = disc.fetch_user()
return f"""
<html>
<head>
<title>{user.name}</title>
</head>
<body>
<img src='{user.avatar_url}' />
<p>user age = {globals()[f"U_{user.id}"]}
</body>
</html>"""
app.run(port=80, host="127.0.0.1") # to run the flask app
bot.run("") # to run the discord bot
so i want the flask app and the discord bot to be running at the same time and to use the Users list.
any help ?

How restart dicord bot after client.close(). Discord.py

I make disocord bot with api in flask. Now it just needs to send message to channel. But if I start bot with flask all flask code stops. I tried to use client.close and I had exception RuntimeError: Event loop is closed. I tried to use client.clean in different places. But I still had this exception.
Now my code looks so :
from flask import Flask, request
import json
import discord
global data
app = Flask(__name__)
client = discord.Client()
CHANNEL = <id>
#client.event
async def on_ready():
global data
channel = client.get_channel(id=CHANNEL)
await channel.send(f"{data['products'][0]['custom_fields'][0]['discord']}\nТовар : {data['products'][0]['name']}")
await client.close()
client.clear()
#app.route('/purchase', methods=['POST'])
def purchase():
global data
data = json.loads(request.data)
client.run('token')
return data['products'][0]['custom_fields'][0]['discord']
I can recommend to run it in the thread. I had the same issue when I had not to block the main thread of the game-client (I believe this should work with Flask too). Example can be found here:
import discord
import asyncio
discord_loop = asyncio.get_event_loop()
client = discord.Client(loop=discord_loop, heartbeat_timeout=20, intents=discord.Intents.all())
def init():
try:
asyncio.get_child_watcher()
global discord_loop
discord_loop = asyncio.get_event_loop()
thread = threading.Thread(target=discord_loop.run_forever)
thread.start()
asyncio.run_coroutine_threadsafe(client.connect(reconnect=True), discord_loop)
asyncio.run_coroutine_threadsafe(client.login(token=DiscordSettings.TOKEN, bot=True), discord_loop)
except:
utils.get_and_log_exception_info()
def stop():
asyncio.run_coroutine_threadsafe(client.logout(), discord_loop)
discord_loop.call_soon_threadsafe(discord_loop.stop)
def reconnect():
client.clear()
asyncio.run_coroutine_threadsafe(client.connect(reconnect=True), discord_loop)

Expose discord bot to API (Flask, FASTAPI)

I'm building a discord bot to take commands from multiple systems and programs. I'm wanting to expose certain actions of my discord bot to REST endpoints and then execute said actions in one spot.
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. Is this due to python's "lack" of multithreading? Or port usage?
The end goal is to call the "/items/" endpoint and see a message on discord sent to me
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. It doesn't technically fulfill this question but was far easier than navigating python multithreading.
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." and further they said if we want more control over the loop we could use start() coroutine instead of run(). So now we should create a task for calling this coroutine and we know discord.py and FastAPI all are asynchronous applications. For starting a FastAPI app you need an ASGI server to handle it. In this case, we're using Uvicorn. So far we have run FastAPI app, now we need to start our discord bot. According to FastAPI docs we could use startup/shutdown event, for calling bot.start() coroutine before the main API starts.
Here is an example of an app which has an API endpoint for sending a message to a discord's user:
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
You are not returning anything from your 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. You would have to run one of them in separated thread or process.
I tried to run bot in thread
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.
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
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.
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')

How to combine Discord.py and Flask in one file?

I am trying to make an unblocked version of Discord using my own web-app using Flask, and have it show messages from a discord server. I have the following code:
from flask import Flask, render_template, redirect
import discord
from discord.ext import commands
import multiprocessing
app = Flask(__name__)
prefix = '%'
token = 'MyBotTokenHere'
intents = discord.Intents().all()
bot = commands.Bot(command_prefix=prefix, intents=intents)
text_channels = {}
# Discord Bot Part
async def replace_nick(id, guild_id=0):
member2 = bot.guilds[guild_id].get_member(id)
nick = ''
try:
return member2.display_name
except AttributeError:
return 'No Nickname'
async def clean_up_messages(messages: list):
output = []
for i in messages:
nick = await replace_nick(i.author.id)
output.append((nick, i.content))
return output
async def get_channel1(given_id=None, guild_id=0):
channel = discord.utils.get(bot.guilds[guild_id].channels, id=given_id)
return channel
async def send_message1(channel: int, message, guild=0):
channel = await get_channel1(channel, guild)
await channel.send(message)
#bot.event
async def on_ready():
print('Ready')
for guild in bot.guilds:
for channel in guild.text_channels:
messages = await channel.history(limit=50).flatten()
messages = await clean_up_messages(messages)
text_channels[channel.id] = [channel.name, messages]
#bot.event
async def on_message(message: discord.Message):
messages = await message.channel.history(limit=50).flatten()
messages = await clean_up_messages(messages)
text_channels[message.channel.id] = [message.channel.name, messages]
# Flask Part
#app.route('/<int:channel_id>/')
def channel(channel_id):
global channels
from static.bot import text_channels
channels = text_channels.copy()
name = channels[channel_id][0]
mess = text_channels[channel_id][1]
return render_template('channel.html', channel_name=name, messages=mess)
#app.route('/menu/')
def menu():
return render_template('menu.html', channels=text_channels)
#app.route('/')
def main():
return render_template('main.html')
if __name__ == '__main__':
y = multiprocessing.Process(target=app.run)
y.start()
bot.run(token)
When I run the code, the flask app starts up and works, but the bot is offline. How do I fix this?
P.S. Answers from here don't work Running Flask & a Discord bot in the same application.

Python + Flask + Discord: How to send a message through discord through a flask endpoint?

I'm trying to send a message with discord, activated through a Flask endpoint
I get the following error message when I call http://127.0.0.1:5000/send
RuntimeError: There is no current event loop in thread 'Thread-4'.
I have the following (minimal) code
import discord
from flask import Flask, jsonify
async def my_background_task():
for message in ['a', 'b']:
await client.wait_until_ready()
channel = client.get_channel(CHANNEL_ID)
await channel.send(message)
await client.close()
def sendMessages():
client = discord.Client()
client.loop.create_task(my_background_task())
client.run('SECRET')
app = Flask(__name__)
#app.route('/send')
def send():
sendMessages()
Maybe you should consider using webhooks instead of a bot. Here is a simple example you should implement flask to it.
import requests #dependency
url = "<your url>" #webhook url, from here: https://i.imgur.com/aT3AThK.png
data = {}
#for all params, see https://discordapp.com/developers/docs/resources/webhook#execute-webhook
data["content"] = "message content"
data["username"] = "custom username"
#leave this out if you dont want an embed
data["embeds"] = []
embed = {}
#for all params, see https://discordapp.com/developers/docs/resources/channel#embed-object
embed["description"] = "text in embed"
embed["title"] = "embed title"
data["embeds"].append(embed)
result = requests.post(url, json=data, headers={"Content-Type": "application/json"})
try:
result.raise_for_status()
except requests.exceptions.HTTPError as err:
print(err)
else:
print("Payload delivered successfully, code {}.".format(result.status_code))
#result: https://i.imgur.com/DRqXQzA.png
Sometimes using webhooks cannot provide the required functionality.
If it is possible for you to switch from flask to quart, you can use the direct support for async methods to gain controll over the event loop as you intented to do in your given example.
The following snippet starts the discord bot within the same event loop as the quart server.
#app.before_serving
async def before_serving():
loop = asyncio.get_event_loop()
await client.login(TOKEN) # this could be done outside of this method
# do not use client.run
loop.create_task(client.connect())
A complete minimal working example would look like follows
import discord
import asyncio
from quart import Quart
app = Quart(__name__)
client = discord.Client()
#app.before_serving
async def before_serving():
loop = asyncio.get_event_loop()
await client.login(TOKEN)
loop.create_task(client.connect())
#app.route("/send", methods=["GET"])
async def send_message():
# wait_until_ready and check for valid connection is missing here
channel = client.get_channel(CH_ID)
await channel.send('XYZ')
return 'OK', 200
app.run()
The connect of the client could be triggered by the /send method itself, however it is important to not create a new task at every request.

Categories