Pinging someone at an interval discord.py - python

I am making a bot and want it to ping a user every ten minutes. Here is what I have right now
#client.event
while True:
channel = client.get_channel(717346417592762457)
await message.channel.send(<#!561464966427705374>)
time.sleep(600)
But this results in a lot of errors.

One easy way to do it would be to use discord's tasks extension. You can easily set the amount of time for each loop. The only main drawback is that the loop restarts every time your bot restarts, or rather everytime you run the command, in this example. In the example below, I added the method to start the loop in a command, but you can have it start in an on_ready event or similar.
# New Import
from discord.ext import tasks, commands
# ... Other code ...
#tasks.loop(minutes=10) # #tasks.loop(seconds=600) <-- does the same amount of time
async def ping_loop():
channel = client.get_channel(717346417592762457)
await channel.send("<#!561464966427705374>")
# 1. Don't use message.channel, as you only defined channel and this isn't an event
# 2. As mentioned by Tim Roberts in the comments
# add speech marks to your send() since it's a string
# somewhere else in your code
#client.command()
async def loop(ctx):
try:
ping_loop.start() # start the loop if it isn't currently running
await ctx.send("Started pinging <#!561464966427705374>")
except: # happens if the loop is already running
ping_loop.cancel() # if so, cancel the loop
await ctx.send("Stopped pinging <#!561464966427705374>")

For the specific errors you mentioned:
#client.event
async def somefunction(ctx):
# you need to define a function to call (pass to discord.py)
# also the function has to be async, for the usage of `await`
while True:
channel = client.get_channel(717346417592762457)
await message.channel.send(<#!561464966427705374>)
# not sure if you can mention user like this
await asyncio.sleep(600)
# time.sleep(600) # prefer non-blocking asyncio sleep. make sure `asyncio` is imported
To mention a user, you need to first get user with
user_instance = ctx.guild.get_member(YOUR_USER_discord_id)
Note: You may need to enable the privilege to use the .get_member() function. If you have not enabled it yet, just follow the instructions in the exception.
Then you can sent message with
await ctx.send(f"THE_TEXT_YOU_WANT_TO_SPAM {user_instance.mention}!")
Also, if you are using it in a bot command like me, you may want to add some validation, so that not everyone can cast/invoke the command. I did this with has_role():
#commands.has_role(YOUR_ROLE_ID_THAT_CAN_CAST_THIS_COMMAND)
So in the end, it looks like this:
#bot.command('name': 'spam', 'help':'use at # to mention a user')
#commands.has_role(YOUR_ROLE_ID_THAT_CAN_CAST_THIS_COMMAND)
async def spamMention(ctx, spam_duration_second: int = 3):
user_instance = ctx.guild.get_member(YOUR_USER_discord_id)
for cntr in range(spam_duration_second):
await ctx.send(f"THE_TEXT_YOU_WANT_TO_SPAM {user_instance.mention}!")
await asyncio.sleep(THE_INTERVAL_YOU_WANT_IN_SECOND)
return
It's part of my code so I know it works (my python version: 3.7.9).

Related

How to send a message using discord.py into a channel without input but on true condition

here is my code i'm trying to integrate with a script that runs 24/7
import discord
client = discord.Client(intents=discord.Intents.default())
#client.event
async def on_ready():
channel = client.get_channel(3534536365654654)
await channel.send("Bot is ready")
#client.event
async def background_task():
channel = client.get_channel(3534536365654654)
embed = discord.Embed(title="Testing")
embed.add_field(name="Req Amount", value="100")
embed.add_field(name="Return Amount", value="120")
await channel.send(embed=embed)
client.run(token)
basically every time my condition sets to true in the main code i want to run background_task()
in the main file but right now while running just this code only the on_ready() function is sending an output.
My bot is not supposed to take any inputs or commands just send the message each time the condition is true in code.
I've also tested all previous solutions and they have been rendered useless after the async update to discord.py
Any help would be appreciated
Use tasks and just have it run every few minutes (or hours/whatever) and check that the condition you want is True - if it is then send the message you want to send.
The #client.event syntax is usually reserved for actual client events - so background_task is never going to be executed.
Have a look in to discord.ext.tasks
Here's a simple example which executes every 5 seconds. With every loop you can check on a value and perform the actions accordingly.
from discord.ext import tasks, commands
#tasks.loop(seconds=5.0)
async def bg_task(self):
if value:
// Do something on True
else:
// Do something on False
Then start the task like this:
bg_task.start()

rotating status and command usage

so, im trying to make a multiple status for my discord bot, but i cant find anything.
also im trying to make the status show how many times two commands have been used globally
this is what ive tried for multiple status
async def change_playing():
threading.Timer(10, change_playing).start()
await test_bot.change_presence(game=discord.Game(name='Currently on ' + str(len(client.servers)) +
' servers'))
threading.Timer(10, change_playing).start()
await test_bot.change_presence(game=discord.Game(name='Say shelp'))```
i found it, but thanks for the help
guilds = len(client.guilds)
users = len(client.users)
await client.wait_until_ready()
statuses = ["Shelp", f"im in {guilds} guilds| watching {users} users", "with knives"]
while True:
game = discord.Game(random.choice(statuses))
await client.change_presence(status=discord.Status, activity=game)
await asyncio.sleep(10)
client.loop.create_task(roll_presence())```
You could add this to your bot with the discord.ext.tasks decorator that was made for the very purpose of repeating an action every n seconds.
The docs for it are here.
You could do something like
import discord
from discord.ext import tasks
SAYHELP = discord.Game(name="Say shelp")
SERVER_COUNT = discord.Game(name=f'Currently on {len(client.servers)} servers'))
#tasks.loop(seconds=10)
async def change_playing():
if test_bot.activity is SAYHELP:
test_bot.change_presence(game=SERVER_COUNT)
else
test_bot.change_presence(game=SAYHELP)
async def on_ready(self, *args, **kwargs):
change_playing.start()
super().__on_ready__(*args, **kwargs)
Just don't forget to update the SERVER_COUNT activity whenever the bot gets added to another server. There's this event that gets fired then.
As for the command usage counter, there's a couple approaches to this that depend entirely on how you're instantiating your bot.
If you're just using the discord.ext.commands.Bot class as-is, you'll want to define an integer for that in your top-level code and increment that every time the relevant command is called.
If you're subclassing the bot, however, then you can define self.commandcounter in your bot and have the commands update that instead.
If you're also using cogs, then make sure the cog has a reference back to the bot so you can increment self.bot.commandcounter.
You could then have the #task decorated function set that as presence as well. Might need to add another check for the current activity though.
The docs for dpy are a good place to start looking if you can't get something to work.

(Python) How to make discord bot respond able to respond to multiple users at the same time

I created a discord bot in python that runs some other code if a user sends it a message. The problem I'm having is that while the code is being run, the bot will not respond to any other messages. How can I make it so that while the bot is running code behind the scenes it can still respond to other messages?
Code looks like this:
from secrets import TOKEN
import discord
def code_that_takes_time():
items = []
for x in range(10):
item = do_something() #get request in my case. takes 20s per iteration
items.append(item)
return items
#client.event
async def on_message(message):
user_message = str(message.content)
send = message.channel.send
if message.author == client.user:
return
if user_message.startswith('$do'):
result = code_that_takes_time()
send(result)
if user_message.startswith('hello'):
send('hello, nice to meet you')
client.run(TOKEN)
I want it so that the bot can still respond to the 'hello' message while it's running code_that_takes_time and then send the result whenever it finishes.
Thanks in advance for the help and ideas.
anything that has a chance of blocking, such as your code_that_takes_time should ideally be written as a co-routine so that it will sleep and let other code execute while it is waiting. I'm relatively new to asyncio myself, but I believe the syntax is to use async on your code_that_takes_time, i.e.:
async def code_that_takes_time:
items = []
for x in range(10):
item = do_something() #get request in my case. takes 20s per iteration
items.append(item)
return items
then make sure to call it with the await keyword (if you try to call a coroutine normally, the call just retrieves and returns the coroutine object), e.g.:
#put any needed arguments in the parentheses as with a normal call
await code_that_takes_time()
I wish I could be more help than that, but I'm also a newb with this stuff and don't want to mislead.

Discord.py bot, can I do a heavy task "off to the side" so I don't lag inputs?

I have a Discord bot in Python / Discord.py where people can enter commands, and normally the bot responds very quickly.
However the bot is also gathering/scraping webdata every iteration of the main loop. Normally the scraping is pretty short and sweet so nobody really notices, but from time to time the code is set up to do a more thorough scraping which takes a lot more time. But during these heavy scrapings, the bot is sort of unresponsive to user commands.
#bot.command()
async def sample_command(ctx):
# may actually take a while for this command to respond if we happen to be
# in the middle of a heavier site scrape
await ctx.channel.send("Random message, something indicating bot has responded")
async def main_loop():
sem = asyncio.Semaphore(60)
connector = aiohttp.TCPConnector(limit=60)
async with aiohttp.ClientSession(connector=connector, headers=headers) as session:
while True:
# main guts of loop here ...
scrapers = [scraper_1(session, sem), scraper_2(session, sem), ...]
data = list(chain(*await asyncio.gather(*scrapers))) # this may take a while
# do stuff with data
Is there a way to sort of have it go "Hey, you want to do a heavy scrape, fine go process it elsewhere - meanwhile let's continue with the main loop and I'll hook back up with you later when you're done and we'll process the data then", if that makes sense?
I mainly want to separate this scraping step so it's not holding up the ability for people to actually interact with the rest of the bot.
You can use the discord.py tasks extension docs.
For example:
from discord.ext import tasks
#bot.event()
async def on_ready():
main_loop.start()
#bot.command()
async def sample_command(ctx):
await ctx.channel.send("Random message, something indicating bot has responded")
#tasks.loop(seconds=60)
async def main_loop():
do_something()
Note: It's not recommended to start the tasks in on_ready because the bot will reconnect to discord and the task will start several times, Put it somewhere else or on_ready check if this the first connect.
Another simple tip: you can use await ctx.send() instead of await ctx.channel.send()
You can use asyncio.create_task() to spawn the scraping in the "background":
async def scrape_and_process(...):
scrapers = [scraper_1(session, sem), scraper_2(session, sem), ...]
data = list(chain(*await asyncio.gather(*scrapers))) # this may take a while
# do stuff with data
async def main_loop():
sem = asyncio.Semaphore(60)
connector = aiohttp.TCPConnector(limit=60)
async with aiohttp.ClientSession(connector=connector, headers=headers) as session:
while True:
# main guts of loop here ...
# initiate scraping and processing in the background and
# proceed with the loop
asyncio.create_task(scrape_and_process(...))
You can try to use python threading.
Learn more here
It basically allows you to run it on different threads
example:
import threading
def 1():
print("Helo! This is the first thread")
def 2():
print("Bonjour! This is the second thread")
thread1 = threading.Thread(target=1)
thread2 = Threading.Thread(target=2)
thread1.start()
thread2.start()

How can I save a command coroutine in a variable that can be accessed through another command?

What I'm trying to implement in my discord bot is a system that stops a running command coroutine as soon as another command is invoked by the same user, in an attempt to sort of keep a unique active session for each user.
So essentially I need to be able to access that coroutine object and stop it from within another coroutine.
What I've tried so far looks logic to me but I'm sure there is some misconception about how discord API handles coroutines. Here's a basic example:
active_sessions = {}
#bot.command()
async def first_command(ctx):
# Saving the coroutine in the dictionary with the user ID as key:
active_sessions[ctx.author.id] = ctx.command.callback
# do stuff
#bot.command()
async def second_command(ctx):
# Accessing the coroutine object and trying to stop it.
try:
coro = active_sessions[ctx.author.id]
coro.close()
except KeyError:
pass
which throws an AttributeError stating that function object has no attribute close.
But, as per discord.py docs, command.callback refers to a coroutine object that should have the close method. Also tried coro.cancel() with the same result.
I'm conceptually doing something wrong here but I'm not sure what exactly, and also I have no clue how to implement my idea correctly, so any help is very appreciated.
EDIT
So, since my goal might still be unclear, here is what I'm trying to accomplish.
I'm doing a discord bot that is sort of a game. Users can run several commands, many of which would wait for a certain amount of time for a reaction or message from the command author.
But this user might run another command without 'completing' the first one, and if they come back to the first, their reactions would still be accepted.
I'm trying to avoid this behaviour and keep the user focused on a 'single session'; whatever command they run, the execution of the previous one needs to be automatically stopped.
So how I'm implementing this, which comes from a wrong misconception of coroutines, is having a dictionary that associates each user with the loop of the command thy're currently running. If another loop starts with the other one still running, the 'old loop' would be stopped and the respective entry in the dictionary would be updated with the new loop:
active_sessions = {}
#bot.command()
async def first_command(ctx):
# In this first command I want to assign the current loop to a variable and
# add it to the active_sessions dictionary along with the ID of the command author.
# Then there's a loop that waits for user's reaction.
# Ignore the details such as the check function.
while True:
try:
reaction, user = await bot.wait_for("reaction_add",timeout=60,check=check)
# some if conditions here make the bot behave differently according
# to the specific reaction added
except asyncio.TimeoutError:
return
#bot.command()
async def second_command(ctx):
# As soon as this second command is invoked, I need to access the 'loop object'
# previously stored in the dictionary (if any) and stop it from here.
There are two ways to actually stop the wait_for from working.
Switching to a bot event.
import json
#bot.event()
async def on_reaction_add(reaction, user):
# use a config variable
with open('config.json', 'r') as f:
data = json.load(f)
if data['wait_for_reaction']:
# do stuff here
else:
return
#commands.command()
async def activate(ctx):
#activates the listenere
with open('config.json', 'r') as f:
data = json.load(f)
data['wait_for_reaction'] = True
with open('config.json', 'w') as f:
json.write(data, f, indent=4)
await ctx.send('activated listener')
#commands.command()
async def deactivate(ctx):
with open('config.json', 'r') as f:
data = json.load(f)
data['wait_for_reaction'] = False
with open('config.json', 'w') as f:
json.write(data, f, indent=4)
await ctx.send('activated listener')
You can also use a global variable or a .env file to load the config variable
Using a task
from discord.ext import tasks
#tasks.loop(seconds=0)
async def task(ctx):
try:
reaction, user = await bot.wait_for("reaction_add",timeout=60, check=check)
except asyncio.TimeoutError:
return
#commands.command():
async def activate(ctx):
try:
task.start(ctx)
except RuntimeError:
await ctx.send("task is already running")
finally:
await ctx.send('started task')
#commands.command()
async def deactivate(ctx):
task.stop()
await ctx.send('stopped task')
The problem is tasks is that you can only run one task at once, so you cannot use the command in multiple contexts. There is a workaround but I wouldn't recommend using it. Use tasks only if your bot is a personal one that only you run.
References:
tasks
Note:
If you are going to use a .env file to store your variable. Look into dotenv

Categories

Resources