I'm making a Discord bot that plays music and found the following code online (which has been modified by me):
async def play(self, inter: disnake.CmdInter, url):
"""Plays a song from a given YouTube URL."""
async with inter.channel.typing():
self.queue.append([inter.channel, url])
player = await YTDLSource.from_url(
url, loop=self.bot.loop, stream=True
)
try:
self.bot.voice_clients[0].play(
player,
after=...
)
except disnake.errors.ClientException: # Already playing a song
self.queue.append([inter.channel, url])
await inter.send(
f"Added `{player.title}` to queue "
f"(Nr. {self.queue.index([inter.channel, url]) + 1})"
)
return
await inter.send(f"Now playing: `{player.title}`")
The after kwarg allows me to specify a closure that is executed after the playback stops. To add a queue system, I wanted to have after lead to a function that recursively plays the next song in the queue until the queue is empty.
My problem is that I can't figure out how to use closures of coroutines (which the recursive function has to be since the playback requires the use of coroutines). after=await foo, after=foo, after=lambda: await foo, after=lambda: foo and some more combinations don't work and I haven't found anything about coroutine closures on the internet.
Maybe they just don't work like that, I'm not familiar with asynchronous programming, so I wouldn't know.
Related
I watched a video where a man used dictionary to run a music bot on few different discord servers and the code looks like this:
#.... __init__..... self.vc = {}
async def join_VC(self, ctx, channel):
id = int(ctx.guild.id)
if self.vc[id] == None or not self.vc[id].is_connected():
self.vc[id] = await channel.connect()
if self.vc[id] == None:
await ctx.send("Could not connect to the vouce channel.")
return
else:
await self.vc[id].move_to(channel)
My question is: Is it possible to use threading or multiprocessing in replacement to dictionary? If yes then what do you guys reccomend?
full code: https://github.com/TheRealDulanOoga/GeraldTheRobot/tree/6aeec689b9a533527abfea54298ae88a0bd4d691
I don't know if I should use multiprocessing on whole class (is it possible?) or just on few functions. I'm a bit confused I need an advice.
your function is already async, means it can run parallel. So there is really no nead for threading it ...
threading simply means you create a process that runs the function
async means this function wont block the code
and the result you want is only to not block the code, of course can you thread the function but there would be no benefits by doing so
My Goal
Hello, I want my bot to cycle through 3 prefixes, The prefix for the bot, The amount of servers its in, and another message of my choosing. I use the #bot.command/#bot thing, so the example in the FaQ in the readthedocs website doesn't help me. What i tried didn't work, and i also do not know how to get the number of servers the bot is in.
What I tried
async def my_background_task(self):
await bot.change_presence(activity=discord.Game(name="message"))
await asyncio.sleep(7)
await bot.change_presence(activity=discord.Game(name="PREFIX: CF>"))
await asyncio.sleep(7)
#bot.event
async def on_ready(self):
print('Bot is online and ready.')
self.bg_task = self.loop.create_task(self.my_background_task())
This solution has been tested:
prefixes = [lambda: 'prefix1', lambda: 'prefix2', lambda: str(len(bot.guilds))]
bot = commands.Bot(command_prefix=prefixes[0]())
async def my_background_task():
prefix_num = 0
while True:
prefix = prefixes[prefix_num]()
await bot.change_presence(activity=discord.Game(name=prefix))
bot.command_prefix = prefix
# increase the current prefix - if it's reached the length of the prefix list, set it back to 0
prefix_num = (prefix_num + 1) % len(prefixes)
await asyncio.sleep(7)
#bot.event
async def on_ready():
bot.loop.create_task(my_background_task())
First of all, your background task and on_ready function shouldn't have any self in them. Also, your background task has to have a while loop inside of it, otherwise, it will only run once.
This code utilizes anonymous lambda functions because it allows the bot to be added to a different server and adjust the prefix when that happens. When it comes time to change the prefix, one of the functions is called, returning a string.
I would also recommend looking at the API reference because it is very helpful.
I'm currently trying to learn asyncio in Python. I know that the await keyword tells the loop that it can switch coroutines. However, when should I actually use it? Why not put it before everything?
Additionally, why is the await before 'response.text()', why not before the session.get(url)?
async def print_preview(url):
# connect to the server
async with aiohttp.ClientSession() as session:
# create get request
async with session.get(url) as response:
# wait for response
response = await response.text()
# print first 3 not empty lines
count = 0
lines = list(filter(lambda x: len(x) > 0, response.split('\n')))
print('-'*80)
for line in lines[:3]:
print(line)
print()
You use await with functions that are marked as coroutine in the documentation. For example, ClientResponse.text is marked as coroutine, while ClientResponse.close is not, which means you must await the former and must not await the latter. If you forget to await a coroutine, it simply won't execute and its return value will be a "coroutine object", which is useless (except for use with await).
session.get() returns an async context manager. When passed to async with, the coroutines it implements are awaited behind the scenes.
Also note that awaiting is not the only thing you can do with coroutines, the other is converting them into tasks, which allows them to run in parallel (without additional cost on the OS level). For more information, consult a tutorial on asyncio.
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)
I'm trying to consume multiple queues concurrently using python, asyncio and asynqp.
I don't understand why my asyncio.sleep() function call does not have any effect. The code doesn't pause there. To be fair, I actually don't understand in which context the callback is executed, and whether I can yield control bavck to the event loop at all (so that the asyncio.sleep() call would make sense).
What If I had to use a aiohttp.ClientSession.get() function call in my process_msg callback function? I'm not able to do it since it's not a coroutine. There has to be a way which is beyond my current understanding of asyncio.
#!/usr/bin/env python3
import asyncio
import asynqp
USERS = {'betty', 'bob', 'luis', 'tony'}
def process_msg(msg):
asyncio.sleep(10)
print('>> {}'.format(msg.body))
msg.ack()
async def connect():
connection = await asynqp.connect(host='dev_queue', virtual_host='asynqp_test')
channel = await connection.open_channel()
exchange = await channel.declare_exchange('inboxes', 'direct')
# we have 10 users. Set up a queue for each of them
# use different channels to avoid any interference
# during message consumption, just in case.
for username in USERS:
user_channel = await connection.open_channel()
queue = await user_channel.declare_queue('Inbox_{}'.format(username))
await queue.bind(exchange, routing_key=username)
await queue.consume(process_msg)
# deliver 10 messages to each user
for username in USERS:
for msg_idx in range(10):
msg = asynqp.Message('Msg #{} for {}'.format(msg_idx, username))
exchange.publish(msg, routing_key=username)
loop = asyncio.get_event_loop()
loop.run_until_complete(connect())
loop.run_forever()
I don't understand why my asyncio.sleep() function call does not have
any effect.
Because asyncio.sleep() returns a future object that has to be used in combination with an event loop (or async/await semantics).
You can't use await in simple def declaration because the callback is called outside of async/await context which is attached to some event loop under the hood. In other words mixing callback style with async/await style is quite tricky.
The simple solution though is to schedule the work back to the event loop:
async def process_msg(msg):
await asyncio.sleep(10)
print('>> {}'.format(msg.body))
msg.ack()
def _process_msg(msg):
loop = asyncio.get_event_loop()
loop.create_task(process_msg(msg))
# or if loop is always the same one single line is enough
# asyncio.ensure_future(process_msg(msg))
# some code
await queue.consume(_process_msg)
Note that there is no recursion in _process_msg function, i.e. the body of process_msg is not executed while in _process_msg. The inner process_msg function will be called once the control goes back to the event loop.
This can be generalized with the following code:
def async_to_callback(coro):
def callback(*args, **kwargs):
asyncio.ensure_future(coro(*args, **kwargs))
return callback
async def process_msg(msg):
# the body
# some code
await queue.consume(async_to_callback(process_msg))
See Drizzt1991's response on github for a solution.