Threading/multiprocessing in replacement to dictionary in discord.py music bot - python

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

Related

Discord.py Bot Takes too Long to Respond

Goal:
I'm developing a discord bot which scans a url every 5 seconds or so, checks for a specified change on that webpage, and will send a message in the discord channel if that change occurs. I've done this by sending the url to the bot using an if statement in on_message. The url is then passed to a tasks.loop() function, where it is scanned and processed in another function for the change.
Problem:
I'd like to be able to send a message in the discord channel which quickly ends the process taking place in the tasks.loop(), so that I can pass it a different url to scan using the on_message function. In its current form, it works-- just very slowly. From the time the cancel trigger is sent, it takes around 3 minutes to send the verification message that the process has been cancelled. I need to make this 5 seconds or less. For what its worth, the bot is kept running using replit and uptime robot, but I am sure that the long response time is not related to the frequency the repl is awoken by uptime robot.
Code:
My code is much more complex and riddled with obscurely named variables, so here is a much simpler snippet of code with the same general structure.
client = discord.Client()
channel = client.get_channel(CHANNEL_ID)
#tasks.loop()
async def myloop(website, dataframe):
channel = client.get_channel(CHANNEL_ID)
try:
# iteratively scrape data from a website for
# a predefined change in the dataframe
if change = True:
await channel.send(notification)
except:
pass
#client.event
async def on_message(message):
channel = client.get_channel(CHANNEL_ID)
msg = message.content
if msg.startswith('track'):
website = msg[6:]
await channel.send('Now tracking '+str(website))
myloop(website,df)
if msg.starswith('stop'):
myloop.cancel()
await channel.send('Done tracking, awaiting orders.')
Attempted Solutions:
I have tried using some forms of threading, which I am very new to, and I haven't found a way to make it work any faster. Any suggestions or solutions would be greatly appreciated! I've been combing the web for help for quite some time now.
Looks like you could use client.loop.create_task to create asyncio task objects, and their cancel method to immediately cancel those asyncio tasks at the right time, e.g.
import asyncio
from replit import db
_task = None
async def myloop():
website = db['website']
dataframe = db['dataframe']
channel = client.get_channel(CHANNEL_ID)
while not client.is_closed():
await asyncio.sleep(5)
try:
# iteratively scrape data from a website for
# a predefined change in the dataframe
if change:
await channel.send(notification)
except:
pass
#client.event
async def on_message(message):
global _task # This gives the function access to the variable that was already created above.
msg = message.content
if msg.startswith('track'):
website = msg[6:]
await message.channel.send('Now tracking '+str(website))
db['website'] = website
db['dataframe'] = df
if _task is not None:
_task.cancel()
_task = client.loop.create_task(myloop())
if msg.startswith('stop'):
if _task is not None:
_task.cancel()
_task = None
await message.channel.send('Done tracking, awaiting orders.')
The argument create_task takes is a coroutine that takes no arguments, so the website URL and dataframe need to be accessible to the function a different way (I'm not sure which way you would prefer or would be best; using replit's db is just an example).
With this approach, you should be able to use track again to change which website is being monitored without using stop in between.
More details in the docs:
discord.Client.loop
loop.create_task
Task.cancel
asyncio.sleep
discord.Client.is_closed
Replit db

Discord.py || How to loop a part of Program infinite times without crashing it eventually?

I am Trying to Make a 24/7 Streaming Discord Bot using while(True): Infinite Loop,
But I think that the while(True): will get crashed by the OS (Using Heroku for Hosting) or Some kind of threads problem
#commands.command(name='play',aliases=["p"])
async def _play(self, ctx: commands.Context):
i = 0
while(True): #Infinite While Loop
search = URL[i] #URL is the List of Musics I What to Stream 24/7
if not ctx.voice_state.voice:
await ctx.invoke(self._join) #joins the voice channel if not in voice channel previously
async with ctx.typing():
try:
source = await YTDLSource.create_source(ctx, search, loop=self.bot.loop)
#Sets the Status of the Bot to What Music it is Playing Currently
await self.bot.change_presence(activity=discord.Activity(type=discord.ActivityType.listening, name=f"{source.title}"))
except YTDLError as e:
await ctx.send('An error occurred while processing this request: {}'.format(str(e)))
else:
song = Song(source)
await ctx.voice_state.songs.put(song) #Enqueue the Song
i = (i + 1) % len(URL) # Updates the Index of URL
await asyncio.sleep(source.DURATION) #Gets the Lenght of the Music in int and Sleeps
I thought of using Asyncio Event Loops but Can't Figure it Out How to Use it Because my Function Has Parameters/Arguments like context (ctx)
I have Also thought of Cleaning the Memory stack after each songs play... But is it possible ??? If yes can we use it to fix my problem
So Is there anyway to Loop The above Mentioned Block or Code Infinitely without crashing it eventually
Thanks in advance !!!
I have a Out of Topic Query... Can we Use the ability of Context (ctx) without passing it as a Argument.
If we Can then I might be able to use asyncio_event_loop, I suppose

Discord.py-Rewrite Cycle through statuses using background tasks

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.

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