Multitasks in discord py bot - python

I'm trying to multitask with discord py, but I have a problem
Code:
#tasks.loop(seconds=10)
async def taskLoop(ctx, something):
await ctx.send(something)
#client.command()
async def startX(ctx, something):
taskLoop.start(ctx, something)
#client.command()
async def endX(ctx):
taskLoop.cancel()
taskLoop.stop()
In discord I start the command like: -startX zzzzzzzzzz
So it works, every 10 seconds the bot sends "zzzzzzzzzz"
When I try to create a new task (while the previous one is still running), for example: -startX yyyyyyyy
I get the error:
Command raised an exception: RuntimeError: Task is already launched and is not completed.
Obviously I understand it's because the other task is still working, but I looked in the documentation and couldn't find a way to create multiple tasks.
Are there any solutions for this? Thread maybe?

You can't really start the same task more than once. You can create a "task generator" which will generate and start the tasks
started_tasks = []
async def task_loop(ctx, something): # the function that will "loop"
await ctx.send(something)
def task_generator(ctx, something):
t = tasks.loop(seconds=10)(task_loop)
started_tasks.append(t)
t.start(ctx, something)
#bot.command()
async def start(ctx, something):
task_generator(ctx, something)
#bot.command()
async def stop(ctx):
for t in started_tasks:
t.cancel()

Related

How do I use aioschedule in cog class in discord.py

I have a cog which is ment to handel events and loops. I am using aioschedule to run ping every minute as a test if the aioschedule works. But now the problem is that when I put it inside the class it asks for self but when i give self it gives an error.pls help with this
import discord
import os
import asyncio
import time
from discord.ext import commands,tasks
import aioschedule as schedule
class Events(commands.Cog):
def __init__(self, client):
self.client = client
async def bot_test_clear(self):
channel_bot_test = self.client.get_channel(os.getenv('bot-test-text'))
messages = await channel_bot_test.history(limit=100).flatten()
if not messages:
return
embed = discord.Embed(description='It has been 1 hour, clearing chats...', color=0xff0000)
await channel_bot_test.send(embed=embed)
await asyncio.sleep(10)
await channel_bot_test.purge(limit=None)
async def ping(self):
self.client.send("pong")
schedule.every(5).seconds.do(ping)
#commands.Cog.listener()
async def on_ready(self):
self.bot_text_clear.start()
print(f'{self.client.user} is now online')
def setup(client):
client.add_cog(Events(client))
loop = asyncio.get_event_loop()
while True:
loop.run_until_complete(schedule.run_pending())
time.sleep(0.1)
img of the error
So I answered your earlier question and suggested you use aioschedule so I will show you how I use it.
def _run_scheduler(self):
""" Create the scheduler task. """
async def _run_scheduler():
self._scheduler_running = True
print("Created 'Scheduler' task")
while self._scheduler_running:
await aioschedule.run_pending()
await asyncio.sleep(0.25)
if not self._scheduler_running:
asyncio.create_task(_run_scheduler())
The above method is attached to my bot, and is called once when the bot is created. The above code will process all the jobs you schedule. I use create_task so I can call the method from a non-async method.
#commands.Cog.listener()
async def on_startup(self):
aioschedule.every().monday.at("21:15").do(self.announce_winners)
aioschedule.every().day.at("21:30").do(self.start_quiz)
I add jobs, which are then processed in that event loop in the earlier code block. I use on_startup (a custom event) which is called after on_ready since on_ready can be called multiple times if the bot disconnects (stop duplicate jobs)
Your error (which should be text, not an image) is due to schedule.every(5).seconds.do(ping) being called and it is expecting self which you do not give it.

(discord.py) functions in a cog

I've been trying to develop a discord bot in python, and I want to make a bunch of "hidden" commands that don't show up in help. I want to make it so that whenever someone activates a hidden command, the bot sends them a pm. I tried to make a function to do it, but so far it doesn't work. Here is the code in the cog file:
import discord
from discord.ext import commands
class Hidden(commands.Cog):
def __init__(self, client):
self.client = client
def hidden_message(ctx):
ctx.author.send('You have found one of several hidden commands! :shushing_face:\nCan you find them all? :thinking:')
#commands.command()
async def example(self, ctx):
await ctx.send('yes')
hidden_message(ctx)
def setup(client):
client.add_cog(Hidden(client))
When the example command is run, the bot responds normally, but the function isn't called. There are no error messages in the console. I'm still pretty new to python so could someone tell me what I'm doing wrong?
You need to use await when calling async functions like ctx.author.send, and so the function you're wrapping that with needs to be async too
async def hidden_message(self, ctx):
await ctx.author.send('You have found one of several hidden commands! :shushing_face:\nCan you find them all? :thinking:')
And then
#commands.command()
async def example(self, ctx):
await ctx.send('yes')
await self.hidden_message(ctx)
Finally, to make a command hidden from the default help command, you can do
#commands.command(hidden=True)
In order to send a message, hidden_message would have to be a courotine, i.e. it uses async def instead of just def.
However, there is a second issue that arises because of how hidden_message is called. Calling hidden_message as hidden_message(ctx) would require the function to be defined in the global scope. Since it is a method of class Hidden, it needs to be called as such.
Highlighting the edits:
class Hidden(commands.Cog):
...
async def hidden_message(self, ctx):
...
#commands.command()
async def example(self, ctx):
await ctx.send("yes")
await self.hidden_message(ctx)

How can I break a loop in a discord.py command upon execution of another command?

#client.command()
async def activate(ctx, quote, delay: int):
await ctx.send("Successfully activated SpamMode")
global a
a = 1
while a == 1:
await ctx.send(quote)
time.sleep(delay)
So I have this command in my discord bot (made with discord.py), what it does is basically send a message quote with a delay between each message delay (in seconds) I made it happen with a while loop, is there a way to break that loop upon executing a different command without having to wait for time.sleep(delay) to finish?
i.e.
#client.command()
async def deactivate(ctx):
global a
a = 2
await ctx.send("Successfully deactivated SpamMode")
So basically what is currently happening is that if I try to use this command which stops the previous command from working I have to wait for time.sleep() to finish which is what I don't want. Any help at all would be appreciated.
Use tasks.loop. You can stop them immediately by using the cancel() method
Example:
from discord.ext import tasks
#client.command()
async def activate(ctx, quote, delay: int):
global spam_loop
#tasks.loop(seconds=delay)
async def spam_loop(q):
await ctx.send(q)
spam_loop.start(quote)
await ctx.send("Successfully activated SpamMode")
#client.command()
async def deactivate(ctx):
spam_loop.cancel()
await ctx.send("Successfully deactivated SpamMode")

How to queue requests until the function returns a response for a previous request?

When the user sends !bot in a special channel, the code runs a function that runs for 20-30 seconds (depends on some_var). My problem is that if several people write !bot , the code will reverse this in multiple threads. How do I queue for these requests?
I tried to understand asyncio, discord.ext.tasks, but can't figure out how it works
#client.command()
async def test(ctx, *args):
data = args
if data:
answer = some_function(data[0]) #works from 5 seconds to 1 minute or more
await ctx.send(answer)
Everything works well, but I just don't want to load the system so much, I need a loop to process requests first in - first out
You can use an asyncio.Queue to queue tasks, then process them sequentially in a background loop:
import asyncio
from discord.ext import commands, tasks
queue = asyncio.Queue()
bot = commands.Bot('!')
#tasks.loop(seconds=1.0)
async def executor():
task = await queue.get()
await task
queue.task_done()
#executor.before_loop
async def before():
await bot.wait_until_ready()
#bot.command()
async def example(ctx, num: int):
await queue.put(helper(ctx, num))
async def helper(ctx, num):
await asyncio.sleep(num)
await ctx.send(num)
executor.start()
bot.run('token')
Make some_function() to async then await it. Then all test command will be processed simultaneously.
async some_function(...):
# something to do
#client.command()
async def test(ctx, *args):
if args:
answer = await some_function(data[0])
await ctx.send(answer)

Usage of threading.Timer with async based functions

I'm working on a Discord bot with Python and it queues music from YouTube, i'm working on something to autoqueue songs when the player is stopped, while all of the code works perfectly, the only problem is me not being able to check if the player is playing or not every 15 seconds
async def cmd_autoqueue(self,message, player,channel,author, permissions, leftover_args):
print("autoq ran")
if started == True:
if player.is_stopped:
await self.cmd_autoqadd(player, channel, author, permissions,leftover_args,song_url=last_url)
threading.Timer(15.0,await self.cmd_autoqueue(message, player,channel,author, permissions, leftover_args)).start()
i did realise that
threading.Timer(15.0,await self.cmd_autoqueue(message, player,channel,author, permissions, leftover_args)).start()
calls the function, and if i wanted to pass it as something that would be called later i would use lambda: but , async lambda?
also started boolean is managed by other stuff so well its there for the sake of the 'if', here in this question
Solution:
async def cmd_autoqueue(self,message, player,channel,author, permissions, leftover_args):
global started
print("loop")
if started == True:
await asyncio.sleep(15)
if player.is_stopped:
await self.cmd_autoqadd(player, channel, author, permissions,leftover_args,song_url=last_url)
await self.cmd_autoqueue(message, player,channel,author, permissions, leftover_args)
You could use loop.call_later, that returns you an asyncio-compatible handler to cancel the task.
class YourClass:
def init(self):
self._loop = asyncio.get_event_loop()
self._check_handler = None
def schedule_check(self):
self._check_handler = loop.call_later(
15 * 60, # Every 15 minutes
self._loop.create_task, self._periodic_check())
def stop_checking(self):
self._check_handler.cancel()
async def start_player(self):
await do something()
if not check_handler:
schedule_check()
async def stop_player(self):
await do something()
self.stop_checking()
async def _periodic_check(self):
await do_something()
self._schedule_check()
To pass a function (that needs some parameters) as a parameter to another function, that doesn't allow you to pass the parameters, you can bind the parameters using functools.partial.

Categories

Resources