Discord.py: Restarting the event loop after the event loop already closes due to bot.close() - python

I have a discord bot that sends a message every once in a while based on a web scraping application (won't show that here because it is 500 lines long and someone else could compete for something with it) This is the code that sends the message:
import discord
import time
import asyncio
#the reason it is in while true is so it sends more frequently than once every 30 minutes for testing
while True:
bot = discord.Client()
#bot.event
async def on_ready():
channel = bot.get_channel(866363006974820355)
await channel.send("Test")
print("Sent")
await bot.close()
print("started")
bot.run('hiddentoken')
After the bot closes the loop it goes back to the bot.run() and gives the following exception: Event loop is closed. How do I reopen the event loop before I do bot.run()? Do I need to or is there a workaround I can use.
Note: I tried just keeping the bot open all of the time but it logs out of discord after a bit.

This is not my response, this is #Benjin. This is where he answered.
praw relies on the requests library, which is synchronous meaning that the code is blocking. This can cause your bot to freeze if the blocking code takes too long to execute.
To get around this, a separate thread can be created that handles the blocking code. Below is an example of this. Note how blocking_function will use time.sleep to block for 10 minutes (600 seconds). This should be more than enough to freeze and eventually crash the bot. However, since the function is in it's own thread using run_in_executor, the bot continues to operate as normal.
import time
import asyncio
from discord.ext import commands
from concurrent.futures import ThreadPoolExecutor
def blocking_function():
print('entering blocking function')
time.sleep(600)
print('sleep has been completed')
return 'Pong'
client = commands.Bot(command_prefix='!')
#client.event
async def on_ready():
print('client ready')
#client.command()
async def ping():
loop = asyncio.get_event_loop()
block_return = await loop.run_in_executor(ThreadPoolExecutor(), blocking_function)
await client.say(block_return)
client.run('token')

Related

discord.py running inside subprocess

Hi I want to discord bot that would be controlled by multiple cpu cores at once. So I would be able to use more computational power from my cpu (servers will have assigned different servers to respond to).
Code:
import asyncio
from aiomultiprocess import Pool
import discord
from discord.ext import commands
async def c(core):
print('core assigned' + str(core))
client = commands.Bot(command_prefix='!', intents=discord.Intents().all())
client.remove_command('prefix')
client.remove_command('help')
#client.event
async def on_ready():
print("ready")
#client.event
async def on_message(ctx):
if ctx.author == client.user:
return
await ctx.channel.send("something")
client.run('token')
async def main():
async with Pool() as pool:
await pool.map(c, core)
if __name__ == "__main__":
core = [1, 2...]
asyncio.run(main())
Error is pretty long and I dont want to blank out every personal information.
Is there any way to do that or I am trying to do impossible?
It would be impractical to run two discord bots from one program (in the way above) as they'd share the same IP address with the code above - because ratelimits are done by ip address, and since both the bots would be connecting from the same IP, you could surpass the ratelimits and get temporarily banned from the discord API.
Since you want the discord bots to be controlled by 1 program, you could theoretically proxy the traffic or use a vpn on one bot (although this might be impractical).
Alternatively, if you're happy with two programs on separate machines, you could build a mechanism to communicate between them, and get similar functionality to running both the discord bots in one script.
You could put the other bots in the event loop of the main one by doing something like this.
import asyncio
import discord
from discord.ext import commands
loop = asyncio.get_event_loop()
mainbot = commands.Bot(loop=loop, command_prefix="!")
bot_one = commands.Bot(loop=loop, command_prefix="1>")
bot_two = commands.Bot(loop=loop, command_prefix="2>")
#mainbot.command()
async def ping1(ctx):
await ctx.send("Pong. I'm main bot.")
#bot_one.command()
async def ping2(ctx):
await ctx.send("Pong. I'm bot one.")
#bot_two.command()
async def ping3(ctx):
await ctx.send("Pong. I'm bot two.")
loop.create_task(bot_one.start(bot1token))
loop.create_task(bot_two.start(bot2token))
mainbot.run(mainbottoken)

Single action script for discord bot with python discord.py

I understand that usually the discord bots are in a listening (blocking) loop, but how can I create a function that connects, send a message or perform any action and disconnect in a non blocking flow?
I'm using discord.py and I'm looking for something like:
import discord
TOKEN = "mYtOkEn"
discord.connect(TOKEN)
discord.send("I'm sending this message")
discord.disconnect()
I already tryied playing with the async but have problems with the threading, so was wondering if there is something more simple.
It is for a button that when clicked, perform that action but after that it can continue working on other tasks
Thanks beforehand
One way you could accomplish this is by using a custom event loop.
Example:
import discord
import asyncio
from threading import Thread
TOKEN = "secret"
client = discord.Client()
def init():
loop = asyncio.get_event_loop()
loop.create_task(client.start(TOKEN))
Thread(target=loop.run_forever).start()
#client.event
async def on_message(message):
if message.author == client.user:
return
await message.channel.send('Hello!')
#client.event
async def on_ready():
print("Discord bot logged in as: %s, %s" % (client.user.name, client.user.id))
init()
print("Non-blocking")
Take a look at this for more info: C-Python asyncio: running discord.py in a thread
Thank you for your help and support. With the SleepyStew answer I could find the path to solve it and went this way:
import discord
import asyncio
def discord_single_task():
# Define Coroutine
async def coroutine_to_run():
TOKEN = "Secret"
# Instantiate the Client Class
client = discord.Client()
# # Start (We won't use connect because we don't want to open a websocket, it will start a blocking loop and it is what we are avoiding)
await client.login(TOKEN)
# Do what you have to do
print("We are doing what we want to do")
# Close
await client.close()
# Create Loop to run coroutine
loop = asyncio.new_event_loop()
llll = loop.create_task(coroutine_to_run())
loop.run_until_complete(llll)
return 'Action performed successfully without a blocking loop!'

#tasks.loop() stopping commands from running until the loop is finished

I have a background loop involving selenium, so it takes a long time to finish executing. I noticed that the bot had a delay when responding to commands, and I found out that the processes inside #tasks.loop() needs to finish before the commands execute. For example:
from discord.ext import commands, tasks
import time
bot = commands.Bot(command_prefix='-')
#bot.command()
async def test(ctx):
await ctx.send('hi')
#tasks.loop(seconds=30)
async def loop():
print('h')
time.sleep(20)
print('i')
#bot.event
async def on_ready():
loop.start()
bot.run()
Here, if you do -test after it prints the letter h and before it prints the letter i, the bot will not respond until it prints the i and the loop finishes.
How would I make it so that the commands will be able to execute along with the loop? FYI my code doesn't have a time.sleep(), it was just an example.
If you have long running code then you should move it into separated function and run it with threading or `multiprocessing.
Here basic example with threading. It runs new thread in every loop. For something more complex it may need different menthod. It may need to run single thread before discord and use queue in loop to send information to thread.
from discord.ext import commands, tasks
import time
import threading
import os
bot = commands.Bot(command_prefix='-')
#bot.command()
async def test(ctx):
await ctx.send('hi')
def long_running_function():
print('long_running_function: start')
time.sleep(10)
print('long_running_function: end')
#tasks.loop(seconds=30)
async def loop():
print('h')
t = threading.Thread(target=long_running_function)
t.start()
print('i')
#bot.event
async def on_ready():
loop.start()
bot.run(os.getenv('DISCORD_TOKEN'))

discord.py way to use the bot in a new thread

I'm making my discord.py bot, and I want a way to send custom messages. I tried using on_message but kept having error about threading.
#bot.event
async def on_ready():
print(f'{bot.user.name} is now on Discord!')
#Here I want a loop that asks for input, then, if it gets it, the bot sends it.
I've tried using Thread's, but I can't await in a thread.
#I want to do somthing like:
channel = bot.get_channel(my_channel_id)
while True:
msg = input("Bot: ")
await channel.send(msg)
Thanks for all your answers!
EDIT:
I'm having trouble getting your solutions to work, and I'm pretty sure it's my fault. Is there any way for the bot to run normally, but while it does, there is a loop asking for input and sending it to discord as the bot when it gets it.
Like a working version of this?:
c = bot.get_channel(my_channel_id)
while True:
message = input("Bot: ")
await c.send(message)
AFAIK There is no async equivalent of input() in standard library. There are some workarounds for it, here is my suggestion that I think is the cleanest:
Fire up a thread when your program starts that you can run the blocking input() call in it. I used an executor because asyncio has a handy function to communicate with executor of any kind. Then from async code schedule a new job in executor and wait for it.
import asyncio
from concurrent.futures.thread import ThreadPoolExecutor
async def main():
loop = asyncio.get_event_loop()
while True:
line = await loop.run_in_executor(executor, input)
print('Got text:', line)
executor = ThreadPoolExecutor(max_workers=1)
asyncio.run(main())

How to add a function to discord.py event loop?

I am using Python with discord.py. Documentation here
I've got a bot that is running on a Discord server that links the server with a subreddit. Users have various commands that do things like getting the top submissions, getting the latest submissions, and so on.
I want to add some features to the bot, with one of them being a keyword notifier. The bot should search the subreddit for keywords in the title, and then notify users if they are on the list for that keyword. I know how to do this, I've done it plenty of times, but I don't know how to do it with a Discord bot. I have no experience with asynchio or any kind of asynchronous programming.
The way I've tried to do it works, but it is very janky and definitely not good. At the top of the on message() function, I just add a call to the search_submissions() function, so that whenever someone puts sends a new message on the server, the bot will scan the Reddit submissions. The server is busy enough that this would work relatively okay, but I really want to do it the "proper" way.
I don't know how to call the search_submissions() function without putting it inside of on_message().
Edit for extra code:
import discord
TOKEN = "redacted"
client = discord.Client()
#client.event
async def reddit_search():
print("Searching")
#client.event
async def on_message(message):
if message.content.startswith("reddit!hot"):
# Get hot
# Do other things.
#client.event
async def on_ready():
print("Connected to Discord as {}.".format(client.user.name))
client.run(TOKEN)
You can add a function to the bot event loop with Client.loop.create_task(search_submissions()) like this:
async def search_submissions():
pass
client = discord.Client()
client.loop.create_task(search_submissions())
client.run(TOKEN)
Update:
If you want your function to continue working you can put it in a while loop with some sleeping in between:
async def search_submissions():
while(true):
# do your stuff
await asyncio.sleep(1)
The other answers here don't take into account discord.py's helpful tasks.loop decorator.
To make an event occur every 5 seconds, you would use
from discord.ext import tasks, commands
class MyCog(commands.Cog):
def __init__(self):
self.foo.start()
def cog_unload(self):
self.printer.cancel()
#tasks.loop(seconds=5.0)
async def foo(self):
print('bar')
More can be found here: https://discordpy.readthedocs.io/en/latest/ext/tasks/
You want your search_submissions() function to be async so other functions of your bot can still be invoked and your bot stays responsive. Define it to be def async and use aiohttp to send async HTTP requests to reddit -- what this does is send off the request, relinquish control to the event loop, and then take back control once the results have been transmitted back. If you use a standard HTTP library here instead then your whole bot will be blocked until the result comes back. This of course only makes sense if the task is mainly I/O-bound and less CPU-bound.
Then call search_submissions() in on_message(message) -- but call it asynchronously using result = await search_submissions(). This will resume execution of on_message once the result of search_submissions is ready.
If you truly want to do something else in the same context while waiting on search_submissions (which I think is unlikely), dispatch it as task = asyncio.create_task(search_submissions()). This will start the task immediately and allow you to do something else within the same function. Once you need the result you will have to result = await task.
async def search_submissions():
async with aiohttp.ClientSession() as session:
async with session.get(some_reddit_url) as response:
return await response.read()
#client.event
async def on_message(message):
if message.content.startswith("reddit!hot"):
result = await search_submissions()
await message.channel.send(result)

Categories