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

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())

Related

Whats wrong in my code my code for scheduled messages on discord.py on replit

so I've been trying to make my discord bot send a message every day at 12:30 UCT but i cant seem to get my code to work I'm not sure if its not working because of incorrect code or because its on replit or whatever else the issue could be as i get no errors from this it just send the message once it loads online and that's all.
import datetime, asyncio
bot = commands.Bot(command_prefix="+")
Async def on_Ready():
await schedule_daily_message()
async def schedule_daily_message():
now = datetime.datetime.now()
then = now+datetime.timedelta(days=1)
then.replace(hour=12, minute=30)
wait_time = (then-now).total_seconds()
await asyncio.sleep(wait_time)
channel = bot.get_channel(Channel_id)
await channel.send("Enemies Spawned!")
client.run(os.getenv('TOKEN'))
await asyncio.sleep is non blocking. Your script will execute beyond that statement. You will need to use time.sleep, which that will block all execution of code until the timer has run out.
See here for a more in depth explanation and how to use this in functions:
asyncio.sleep() vs time.sleep()
A way to implement a function that posts a message to a channel after a delay could look like this:
async def send_after_delay(d, c, m):
time.sleep(d)
await c.send(m)
Calling this function asynchronously allows you to continue with code execution beyond it, while still waiting past the calling of the function to send the message.

How to intergrate telethon script into my event loop?

I have two bots, one is using pydle for IRC, like:
async def start_ircbot ():
try:
client = MyOwnBot(NICK,
realname=REALNAME,
sasl_username=SASL_USERNAME,
sasl_password=SASL_PASSWORD,
sasl_identity=SASL_IDENTITY,)
loop = asyncio.get_event_loop()
asyncio.ensure_future(client.connect(HOST, PORT, tls=True, tls_verify=False), loop=loop)
loop.run_forever()
loop.close()
except Exception as e:
print (e)
and another is using telethon for Telegram:
#client.on(events.NewMessage)
async def my_event_handler(event):
...
async def start_client ():
print ("Telegram monitor started...")
await client.start()
await client.run_until_disconnected()
Both of them work without problem separately.
Now, I want to integrate both of them, I tried to launch both of them in my main function like this,
import Notifier
...
async def main():
await asyncio.gather (Notifier.start_client (), start_ircbot ())
asyncio.run(main())
It starts without issue but my_event_handler seems never to get new messages. If I swap the order of functions:
await asyncio.gather (start_ircbot (), Notifier.start_client ())
The script will be stuck at launching, I suspect it has to be something within events loops and tried some different methods but without luck, could anyone shed light on this for me?
Newer Python versions are removing the loop parameter from most methods, so you should try to avoid using it. As long as you don't use asyncio.run (which creates a new loop) or you don't create a new loop yourself, both libraries should be using the default loop from the main thread (I can't speak for pydle, but Telethon does this).
As long as the asyncio event loop is running, Telethon should have no trouble receiving updates. You can use client.loop to make sure it's using the same loop:
tlclient = TelegramClient(...)
irclient = MyOwnBot(...)
#tlclient.on(events.NewMessage)
async def my_event_handler(event):
...
async def main():
await tlclient.start()
await irclient.connect(HOST, PORT, tls=True, tls_verify=False), loop=tlclient.tlclient)
await tlclient.run_until_disconnected() # similar to loop.run_forever() but stops when the client disconnects
client.loop.run_until_complete(main())

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!'

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

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')

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