Make sure that message has been sent - python-3.x

Let's say you are sending a message through your bot to all servers.
If you do too many actions through the api at the same time, some actions will fail silently.
Is there any way to prevent this? Or can you make sure, that a message really has been sent?
for tmpChannel in tmpAllChannels:
channel = bot.get_channel(tmpChannel)
if(not channel == None):
try:
if(pText == None):
await channel.send(embed= pEmbed)
else:
await channel.send(pText, embed= pEmbed)
except Exception as e:
ExceptionHandler.handle(e)

for tmpChannel in tmpAllChannels:
channel = bot.get_channel(tmpChannel)
if(not channel == None):
try:
if(pText == None):
await channel.send(embed= pEmbed)
else:
await channel.send(pText, embed= pEmbed)
except Exception as e:
ExceptionHandler.handle(e)
finally:
await asyncio.sleep(1) # this will sleep the bot for 1 second
Never hammer the API without any delay between them. asyncio.sleep will put a delay between messages, and you will send all of them without failing.

Related

Stop event handling by another event

I am developing bot with telegram client api and telethon. I created a command handler.
#client.on(events.NewMessage(outgoing=True, forwards=False, pattern=r'some_pattern'))
async def my_event_handler(event):
send_messages = []
while True:
send_messages.append(await client.send_message((await event.get_chat()).id, 'valueable_info'))
await asyncio.sleep(2)
I would like to delete send_messages and stop my_event_handler execution when events.NewMessage(outgoing=True, forwards=False, pattern=r'another_pattern') is triggered. How do i achive that?
Found a way to do that:
ids = []
def start_handler(event):
ids.append(event.id)
while event.id in ids:
do_stuff()
def stop_handler(event):
try:
ids.remove(event.id)
except ValueError:
pass

Python Discord Bot - Keep real time message parser from blocking async

I am writing a Discord Bot to take messages from a live chat and relay them to Discord channel, but want it to have other responsive features. Currently the script relays messages by entering a while loop which runs until the right message is recieved.
def chat_parser():
resp = sock.recv(4096).decode('utf-8')
#print(' - ', end='')
filtered_response = filter_fighter_announce(resp)
if resp.startswith('PING'):
# sock.send("PONG :tmi.twitch.tv\n".encode('utf-8'))
print("Ponging iirc server")
sock.send("PONG\n".encode('utf-8'))
return ''
elif (len(filtered_response) > 0):
if (filtered_response.count('ets are OPEN for') > 0):
filtered_response = get_fighters(filtered_response)
return filtered_response
return ''
fight = fight_tracker('') #initialize first fight object
def message_loop():
global first_loop_global
while True:
chat_reception = chat_parser()
if (chat_reception == ''):
continue
fight.set_variables(chat_reception)
return fight.announcement
return ''
The issue with this is that responsive functions for Discord are stuck waiting for this loop to finish. Here is the other code for reference.
#client.event
async def on_ready():
print('Finding channel...')
for guild in client.guilds:
if guild.name == GUILD:
break
channel = guild.get_channel(salty_bet_chat_id)
print('Connected to Channel.')
try:
print('Beginning main loop.')
while True:
message_out = await message_loop()
if (message_out != None and message_out != None):
print('Sending to Discord: ', message_out)
msg = await channel.send(message_out)
await msg.add_reaction(fight.fighter_1[1])
await msg.add_reaction(fight.fighter_2[1])
print('message sent...')
except KeyboardInterrupt:
print('KeyboardInterrupt')
sock.close()
exit()
#client.event
async def on_raw_reaction_add(reaction):
print(reaction)
#client.event
async def on_message(message):
print(message.author)
print(client.user)
client.run(TOKEN)
I have tried making async functions out of chat_parser() and message_loo() and awaiting their return where they are called, but the code is still blocking for the loop. I am new to both async and coding with Discord's library, so I am not sure how to make an async loop function when the only way to start the Discord client is by client.run(TOKEN), which I could not figure out how to incorporate into another event loop.

Discord.py v1.0 How to retrieve every message from a channel and delete them one by one

I have a clear command setup and the idea was to delete every message in a channel which I have the name hard-coded, but since I read that the method only deletes messages newer than 14 days I figured I'd have to somehow manually retrieve all the messages and then delete each one of them with the delete method.
I looked around but every example is either from before v1.0 or doesn't use commands with context the way I do.
Code:
#self.discord_bot.command()
async def clear(ctx):
try:
if ctx.channel != self.channel_name:
return
# clear history
# retrieve messages using context ctx
# for each loop that deletes them with self.discord_bot.delete()
except Exception as e:
await ctx.trigger_typing()
await ctx.send("Oops something happened! %s" % str(e))
return
Thanks in advance!
This should be possible using channel.purge(). This will delete every message in the specified channel if the bot account has sufficient permissions.
#self.discord_bot.command()
async def clear(ctx):
try:
if ctx.channel != self.channel_name:
return
# clear history
await ctx.channel.purge(limit=None)
except Exception as e:
await ctx.trigger_typing()
await ctx.send("Oops something happened! %s" % str(e))
return

Reaction Handling in Discord.py Rewrite Commands

Is there a way to capture a reaction from a command. I have made a command that deletes a channel but I would like to ask the user if they are sure using reactions. I would like to prevent others from reacting to this message (Only the Context Author should React).
So far what I have found is just to use the on_reaction_add() but this can not detect the user who sent the command. I would like to only update the command if the message author is the one who reacted to the message, anyone else, ignore it.
Update: I found that wait_for() does exactly what I want but the issue now is how do I check for if the wrong reaction is set? (i.e if I press the second reaction, delete the message)
if is_admin:
msg = await ctx.send('Clear: Are you sure you would like to purge this entire channel?')
emoji1 = u"\u2705"
emoji2 = u"\u274E"
await msg.add_reaction(emoji=emoji1)
await msg.add_reaction(emoji=emoji2)
def check(reaction, user):
return user == ctx.message.author and reaction.emoji == u"\u2705"
try:
reaction, user = await self.client.wait_for('reaction_add', timeout=10.0, check=check)
except asyncio.TimeoutError:
return await msg.delete()
else:
channel = ctx.message.channel
new_channel = await channel.clone(name=channel.name, reason=f'Clear Channel ran by {ctx.message.author.name}')
await new_channel.edit(position=channel.position)
await channel.delete(reason=f'Clear Channel ran by {ctx.message.author.name}')
await new_channel.send('Clear: Channel has now been cleared.', delete_after=7)
else:
await ctx.send(f"Sorry, you do not have access to this command.", delete_after=5)
Here's a function I use for generating check functions for wait_for:
from collections.abc import Sequence
def make_sequence(seq):
if seq is None:
return ()
if isinstance(seq, Sequence) and not isinstance(seq, str):
return seq
else:
return (seq,)
def reaction_check(message=None, emoji=None, author=None, ignore_bot=True):
message = make_sequence(message)
message = tuple(m.id for m in message)
emoji = make_sequence(emoji)
author = make_sequence(author)
def check(reaction, user):
if ignore_bot and user.bot:
return False
if message and reaction.message.id not in message:
return False
if emoji and reaction.emoji not in emoji:
return False
if author and user not in author:
return False
return True
return check
We can pass the message(s), user(s), and emoji(s) that we want to wait for, and it will automatically ignore everything else.
check = reaction_check(message=msg, author=ctx.author, emoji=(emoji1, emoji2))
try:
reaction, user = await self.client.wait_for('reaction_add', timeout=10.0, check=check)
if reaction.emoji == emoji1:
# emoji1 logic
elif reaction.emoji == emoji2:
# emoji2 logic
except TimeoutError:
# timeout logic

Adding an exception to tell the user in the channel they dont have permission

I don't get any errors with this code but bot still wont tell the non-admin user they dont have permission, it just stays in the terminal, what am i doing wrong in this that bot wont say that or what would i need to change to fix that, also using discord.py rewrite
bans a user with a reason
#client.command()
#commands.has_any_role("Keyblade Master","Foretellers")
async def ban (ctx, member:discord.User=None, reason =None):
adminroles = ("Keyblade Master","Foretellers")
try:
if member == None or member == ctx.message.author:
await ctx.channel.send("You cannot ban yourself")
return
elif reason == None:
reason = "being a jerk!"
message = f"You have been banned from {ctx.guild.name} for {reason}"
await member.send(message)
# await ctx.guild.ban(member)
await ctx.channel.send(f"{member} is banned!")
except commands.errors.MissingAnyRole(adminroles):
await ctx.channel.send("You do not have permission to do that!")
return
Errors raised during command invocation, before the callback is called (like CheckFailure, UserInputError, and their derivatives) need to be handled by a separate error handler, because the code that is failing is not actually in your try block.
#client.command(name="ban")
#commands.has_any_role("Keyblade Master","Foretellers")
async def ban_command(ctx, member:discord.User=None, reason =None):
adminroles = ("Keyblade Master","Foretellers")
if member == None or member == ctx.message.author:
await ctx.channel.send("You cannot ban yourself")
return
elif reason == None:
reason = "being a jerk!"
message = f"You have been banned from {ctx.guild.name} for {reason}"
await member.send(message)
# await ctx.guild.ban(member)
await ctx.send(f"{member} is banned!")
#ban_command.error
async def ban_error(ctx, error):
if isinstance(error, commands.MissingAnyRole):
await ctx.send("You do not have permission to do that!")
else:
raise error

Resources