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'))
Related
I'm having some trouble trying to continuously run an async function while my discord bot is running at the same time.
This is a slimmed down version of my code which produces the same error in the same manner.
import discord.member
from discord.ext import commands, tasks
import asyncio
from apscheduler.schedulers.asyncio import AsyncIOScheduler
intents = discord.Intents().all()
intents.members = True
client = commands.Bot(command_prefix="$", intents=intents)
async def function():
channel = client.get_channel(910690310022107156)
await channel.send(f'Hello World!')
scheduler = AsyncIOScheduler()
scheduler.add_job(function, 'interval', minutes=.1)
scheduler.start()
asyncio.get_event_loop().run_forever()
client.run('TOKEN')
If I run asyncio.get_event_loop().run_forever(), line 21 client.run('TOKEN') never runs which means the bot is never online and can't await channel.send(f'Hello World!') . If I put client.run('TOKEN') before the asyncio loop, the loop never runs and the function is never executed. Is there a way to run the bot and continuously schedule/ execute functions at the same time?
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!'
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')
So, I'm making a Discord bot. It works fine and the commands work too. What I'm trying to do is make the status, aka rich presence, constantly changing. For example, /h > 1 sec later > /he > 1 sec later > /hel > one sec later > /help and then just keep repeating that. So, I thought I would put it in a while True: loop. But, that would obviously stop the rest of the bot from functioning. Then, I thought I would use threading, but that also doesn't work(or maybe I'm doing it wrong). Then, I put the forever loop in the on_ready() function, and the status works, but it pauses for 15 seconds after it goes through a few characters. Also, it stops the rest of the bot meaning commands wouldn't work either. I'm not really sure what else to do.
Code:
import os
import random
import discord
from dotenv import load_dotenv
import time
from threading import Thread
from website import main
load_dotenv()
TOKEN= os.getenv('DISCORD_TOKEN')
GUILD = os.getenv('GUILD_NAME')
print(TOKEN)
client = discord.Client()
async def update_presence():
while True:
await client.change_presence(activity=discord.Game(name='/h'))
time.sleep(1)
await client.change_presence(activity=discord.Game(name='/he'))
time.sleep(1)
await client.change_presence(activity=discord.Game(name='/hel'))
time.sleep(1)
await client.change_presence(activity=discord.Game(name='/help'))
time.sleep(1)
#client.event
async def on_ready():
await client.change_presence(activity=discord.Game('/help'))
print(f'{client.user} has connected to Discord!')
main()
t = Thread(target=update_presence)
t.start()
client.run(TOKEN)
Mixing coroutines and Threading is a bad idea, also time.sleep is a blocking function, use asyncio.sleep instead:
import asyncio
async def update_presence():
while True:
await client.change_presence(activity=discord.Game(name='/h'))
await asyncio.sleep(1)
await client.change_presence(activity=discord.Game(name='/he'))
await asyncio.sleep(1)
await client.change_presence(activity=discord.Game(name='/hel'))
await asyncio.sleep(1)
await client.change_presence(activity=discord.Game(name='/help'))
await asyncio.sleep(1)
And then use loop.create_task to create a background task
bot.loop.create_task(update_presence())
Another option would be to use the discord.py extension tasks
from discord.ext import tasks
#tasks.loop(seconds=1)
async def update_presence():
await client.change_presence(activity=discord.Game(name='/h'))
await asyncio.sleep(1)
await client.change_presence(activity=discord.Game(name='/he'))
await asyncio.sleep(1)
await client.change_presence(activity=discord.Game(name='/hel'))
await asyncio.sleep(1)
await client.change_presence(activity=discord.Game(name='/help'))
update_presence.start()
References:
asyncio.sleep
loop.create_task
tasks.loop
Loop.start
I want to make the bot send a message every day at 1pm. Here's my code:
#tasks.loop(hours=24)
async def called_every_day():
channel = client.get_channel(800476409587171369)
print(f"Got channel {channel}")
await channel.send("Your message")
#called_every_day.before_loop
async def before():
await client.wait_until_ready()
print("Finished waiting")
called_every_day.start()
This works, if I start up the bot at 1pm. However, any time I edit the code and restart the bot, it restarts the loop. I want to prevent this from happening, how would I go about doing so? I'm new to programming, so any insight would be greatly appreciated.
You can use APScheduler and Cron to schedule your commands to be sent at a specific time, like 12:00 PM
Docs: https://apscheduler.readthedocs.io/en/stable/, https://apscheduler.readthedocs.io/en/stable/modules/triggers/cron.html
Here is an example:
# Async scheduler so it does not block other events
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger
from discord.ext import commands
import discord
bot = commands.Bot(command_prefix="!")
async def func():
await bot.wait_until_ready()
c = bot.get_channel(800476409587171369)
await c.send("Your Message")
#bot.event
async def on_ready():
print("Ready")
# Initializing scheduler
scheduler = AsyncIOScheduler()
# Executes your function at 24:00 (Local Time)
scheduler.add_job(func, CronTrigger(hour="24", minute="0", second="0"))
# Starting the scheduler
scheduler.start()