Single action script for discord bot with python discord.py - python

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

Related

discord.py running inside subprocess

Hi I want to discord bot that would be controlled by multiple cpu cores at once. So I would be able to use more computational power from my cpu (servers will have assigned different servers to respond to).
Code:
import asyncio
from aiomultiprocess import Pool
import discord
from discord.ext import commands
async def c(core):
print('core assigned' + str(core))
client = commands.Bot(command_prefix='!', intents=discord.Intents().all())
client.remove_command('prefix')
client.remove_command('help')
#client.event
async def on_ready():
print("ready")
#client.event
async def on_message(ctx):
if ctx.author == client.user:
return
await ctx.channel.send("something")
client.run('token')
async def main():
async with Pool() as pool:
await pool.map(c, core)
if __name__ == "__main__":
core = [1, 2...]
asyncio.run(main())
Error is pretty long and I dont want to blank out every personal information.
Is there any way to do that or I am trying to do impossible?
It would be impractical to run two discord bots from one program (in the way above) as they'd share the same IP address with the code above - because ratelimits are done by ip address, and since both the bots would be connecting from the same IP, you could surpass the ratelimits and get temporarily banned from the discord API.
Since you want the discord bots to be controlled by 1 program, you could theoretically proxy the traffic or use a vpn on one bot (although this might be impractical).
Alternatively, if you're happy with two programs on separate machines, you could build a mechanism to communicate between them, and get similar functionality to running both the discord bots in one script.
You could put the other bots in the event loop of the main one by doing something like this.
import asyncio
import discord
from discord.ext import commands
loop = asyncio.get_event_loop()
mainbot = commands.Bot(loop=loop, command_prefix="!")
bot_one = commands.Bot(loop=loop, command_prefix="1>")
bot_two = commands.Bot(loop=loop, command_prefix="2>")
#mainbot.command()
async def ping1(ctx):
await ctx.send("Pong. I'm main bot.")
#bot_one.command()
async def ping2(ctx):
await ctx.send("Pong. I'm bot one.")
#bot_two.command()
async def ping3(ctx):
await ctx.send("Pong. I'm bot two.")
loop.create_task(bot_one.start(bot1token))
loop.create_task(bot_two.start(bot2token))
mainbot.run(mainbottoken)

How to run discord.py interactively in jupyter notebook?

In discord.py, you can initialize a Client, and run it with client.run(). But the function never returns.
What if I just want to just retrieve some message history then use it in jupyter notebook?
#bot.command()
async def history(ctx):
counter = 0
messageList=[]
async for message in ctx.channel.history(limit = 100):
print(message.author, message.content)
counter += 1
messageList.append(message)
await ctx.send(f'total **{counter}** messages in this channel.')
# How to return the messageList to my jupyter notebook cell and I start playing with the messageList?
How to return the messageList?
There isn't a good way to do this. discord.py is designed to be started once and run until your program terminates since all of the internal objects are destroyed upon closure of the bot, which makes starting the bot back up again nearly impossible. And it is not possible to "pause" discord.py and run your code then resumes discord.py afterwards, because Discord API communicates via web sockets, which relies on a constant stream of heartbeats and acks.
A possible workaround would be to:
Use a single event loop through out the code.
Use loop.run_until_complete() to start the bot's internal loop.
Create a new Client every time you need to run the bot.
Create an on_ready event handle that fetches the information.
Downside is that you won't be able to interact with the Message objects (ie. delete, edit, etc), they will be view only.
Example:
import asyncio
import discord
TOKEN = "YOURBOTTOKEN"
loop = asyncio.get_event_loop()
def get_message_list(token, channel_id):
client = discord.Client(loop=loop)
message_list = []
#client.event
async def on_ready():
nonlocal message_list
channel = client.get_channel(channel_id)
if not channel: # incase the channel cache isn't fully populated yet
channel = await client.fetch_channel(channel_id)
async for message in channel.history(limit=100):
message_list.append(message)
await client.close()
async def runner():
try:
await client.start(token)
finally:
if not client.is_closed():
# Incase the bot was terminated abruptly (ie. KeyboardInterrupt)
await client.close()
loop.run_until_complete(runner())
return message_list
message_list = get_message_list(TOKEN, channel_id=747699344911712345)
print("Message list for channel #1:", message_list)
# ... do stuff with message_list
message_list = get_message_list(TOKEN, channel_id=747699344911754321)
print("Message list for channel #2:", message_list)
# ... do more stuff with message_list
# Finally closes the event loop when everything's done
loop.close()
Instead of doing this, I'd recommend you to find another solution to the task you're trying to accomplish.

Sending messages in discord.py #tasks.loop()

Goal:
I am simply trying to send a message to a discord channel from a #tasks.loop() without the need for a discord message variable from #client.event async def on_message. The discord bot is kept running in repl.it using uptime robot.
Method / Background:
A simple while True loop in will not work for the larger project I will be applying this principle to, as detailed by Karen's answer here. I am now using #tasks.loop() which Lovesh has quickly detailed here: (see Lovesh's work).
Problem:
I still get an error for using the most common method to send a message in discord using discord.py. The error has to have something to do with the await channel.send( ) method. Neither of the messages get sent in discord. Here is the error message.
Code:
from discord.ext import tasks, commands
import os
from keep_alive import keep_alive
import time
token = os.environ['goofyToken']
# Set Up Discord Client & Ready Function
client = discord.Client()
channel = client.get_channel(CHANNEL-ID)
#client.event
async def on_ready():
print('We have logged in as {0.user}'.format(client))
#tasks.loop(count=1)
async def myloop(word):
await channel.send(word)
#client.event
async def on_message(message):
msg = message.content
if msg.startswith('!'):
message_to_send = 'Hello World!'
await channel.send(message_to_send)
myloop.start(message_to_send)
keep_alive()
client.run(token)
Attempted Solutions:
A message can be sent from the on_message event using the syntax await message.channel.send('Hello World!). However, I just can't use this. The code is kept running online by uptimerobot, a free website which pings the repository on repl.it. When the robot pings the repository, the message variable is lost, so the loop would stop scanning my data in the larger project I am working on which is giving me this issue.
When using any client.get_* method the bot will try to grab the object from the cache, the channel global variable is defined before the bot actually runs (so the cache is empty). You should get the channel inside the loop function:
#tasks.loop(count=1)
async def myloop(word):
channel = client.get_channel(CHANNEL_ID)
await channel.send(word)

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