I'm programming a bot for discord and trying to delete messages but only the one of the bots since I'm doing some commands that make him spam quite a lot.
So what I've found works well to bulk delete would be
#bot.command(pass_context = True)
async def purge(ctx,msglimit : int):
deleted = await bot.purge_from(ctx.message.channel, limit=msglimit)
await bot.say("Cleared **{}** Messages".format(len(deleted)))
but the documentation shows this command
def is_me(m):
return m.author == client.user
deleted = await client.purge_from(channel, limit=100, check=is_me)
await client.send_message(channel, 'Deleted {} message(s)'.format(len(deleted)))
But i can't really guet it to work if someone has an idea
def is_me(m):
return m.author == bot.user
#bot.command(pass_context = True)
async def purge(ctx):
deleted = await bot.purge_from(ctx.message.channel, check=is_me)
await bot.say("Cleared **{}** Messages".format(len(deleted)))
This deletes a certain number of messages (100 by default).
The purge_from method takes an argument called check which is a function that takes a message and returns whether the message should be deleted.
The is_me function will return true if the message author is the bot.
Which means that calling purge will delete the bot that picked up this command. If you need the bot to delete a different user's messages, you will need to change the condition.
Related
I need help making a afk command for my discord server. When the afk command is triggered, my bot doesn't respond with a reasoning when you ping the person whos afk. Also, when you return from being afk and type, the bot doesn't send a message saying "(user) is no longer afk". Please help me and tell me what i'm doing wrong and how can I fix this?
afkdict = {User: "their reason"} # somewhere in the code
#bot.command("afk")
async def afk(ctx, reason=None):
afkdict[ctx.user] = reason
await ctx.send("You are now afk. Beware of the real world!")
#bot.event
async def on_message(message):
afkdict = {user: "their reason"}
# some other checks here
for user, reason in afkdict.items():
if user in message.mentions:
if reason is None:
reason = ""
embed = discord.Embed(title=f"{user} is AFK", color=0xFF0000, description=reason[:2500])
await message.reply()
I was expecting this to work, the way dyno works. When i ran the command i got a message back saying user has no context. I dont know what to do anymore.
I think there's a couple of issues. Firstly, you are redefining afkdict in your on_message function it doesn't matter that you're adding users to it in the afk command. Secondly, when you're doing await message.reply(), you're not actually sending the created embed along with it.
I've resolved those problems and changed the logic slightly. Instead of iterating over the users in the afk_dict and checking if they're mentioned, we're iterating over the mentions and seeing if they're in the afk_dict. I'm also using user.id rather user objects as keys.
# defined somewhere
afk_dict = {}
#bot.command()
async def afk(ctx, reason=None):
afk_dict[ctx.user.id] = reason
await ctx.send("You are now afk. Beware of the real world!")
#bot.event
async def on_message(message):
# do whatever else you're doing here
for user in message.mentions:
if user.id not in afk_dict:
continue
# mentioned used is "afk"
reason = afk_dict[user.id] or ""
embed = discord.Embed(title=f"{user.mention} is AFK", color=0xFF0000, description=reason[:2500])
await message.reply(embed=embed)
It looks like you are missing some pieces in your code. Here is an updated version of the code:
afkdict = {}
#bot.command("afk")
async def afk(ctx, reason=None):
user = ctx.message.author
afkdict[user] = reason
await ctx.send(f"You are now AFK. {'Reason: ' + reason if reason else ''}")
#bot.event
async def on_message(message):
for user, reason in afkdict.items():
if user in message.mentions:
if reason is None:
reason = ""
embed = discord.Embed(title=f"{user} is AFK", color=0xFF0000, description=reason[:2500])
await message.channel.send(embed=embed)
if message.author in afkdict:
afkdict.pop(message.author)
await message.channel.send(f"{message.author} is no longer AFK")
In this code, the afk command will add the user who runs the command to the afkdict dictionary along with the reason for being AFK. The on_message event handler will then check if any of the mentioned users are in the afkdict and if so, it will send an embed with the AFK status and reason. Finally, if the author of the message is in the afkdict, it will remove them from the dictionary and send a message indicating that they are no longer AFK.
I need my Discord bot to handle DM's and therefore I wrote this script:
#client.command()
async def dm(ctx, user_id=None, *, args=None):
if user_id != None and args != None:
try:
target = await client.fetch_user(user_id)
await target.send(args)
await ctx.channel.send("'" + args + "' sent to: " + target.name)
except:
await ctx.channel.send("Couldn't dm the given user.")
else:
await ctx.channel.send("You didn't provide a user's id and/or a message.")
My problem now is, until this point in my project I was satisfied by coding if-else-branches into the "on_message()"-function in order to make my bot react to certain commands but now my bot isn't reacting to function calls: When I try to call the "dm"-function (my command prefix is set to ".") it doesn't throw an error in the console it just doesn't work and I'm clueless to what I'm doing wrong here.
Is the code snippet not located in the right place inside my script? What am I missing?
I'd be glad for any form of help. Thanks
As per the docs, you need to add a await client.process_commands(message) at the end of your on_message listener.
https://discordpy.readthedocs.io/en/master/faq.html#why-does-on-message-make-my-commands-stop-working
Edit: Separate question but as I wrote in the comment, here's an example on DM'ing people on a certain time:
from discord.ext import tasks
#tasks.loop(hours=24*7)
async def dm_loop():
user_ids = (123, 456, 789)
for i in user_ids:
user = await client.fetch_user(i)
await user.send("hello")
#client.event
async def on_ready():
dm_loop.start() #remember to add this to your on_ready event
This would make it run on startup and then once a week.
Outcome
To snipe messages sent in X channel instead of all the channels within the Discord guild. That is, it should only track message deletions in that one channel (identified by its ID), and only respond to the !snipe command in that same channel. The current code I have here snipes every message sent within the Discord guild.
Question
How can I snipe messages sent in X channel instead of the entire guild?
I mostly intend to run this bot in one guild. However, it would be nice if it could scale to multiple guilds if needed.
The code I have so far is below.
import discord
from discord.ext import commands
from tokens import token
client = commands.Bot(command_prefix="!", self_bot=False)
client.sniped_messages = {}
#client.event
async def on_ready():
print("Your bot is ready.")
#client.event
async def on_message_delete(message):
print(f'sniped message {message}')
client.sniped_messages[message.guild.id] = (
message.content, message.author, message.channel.name, message.created_at)
#client.command()
async def snipe(ctx):
try:
contents, author, channel_name, time = client.sniped_messages[ctx.guild.id]
except:
await ctx.channel.send("Couldn't find a message to snipe!")
return
embed = discord.Embed(description=contents,
color=discord.Color.purple(), timestamp=time)
embed.set_author(
name=f"{author.name}#{author.discriminator}", icon_url=author.avatar_url)
embed.set_footer(text=f"Deleted in : #{channel_name}")
await ctx.channel.send(embed=embed)
client.run(token, bot=True)
I'm going to suggest two slightly different solutions, because the code can be simpler if you're only running this bot on one guild. What's common to both is that you need to check in what channel messages are deleted, and in what channel the !snipe command is sent.
Single-Guild Version
If you're only monitoring/sniping one channel on one guild, then you can only ever have one deleted message to keep track of. Thus, you don't need a dictionary like in your posted code; you can just keep a single message or None.
You're already importing your token from a separate file, so you might as well put the channel ID (which is an int, unlike the bot token) there too for convenience. Note that, by convention, constants (variables you don't intend to change) are usually named in all caps in Python. tokens.py would look something like this:
TOKEN = 'string of characters here'
CHANNEL_ID = 123456789 # actually a 17- or 18-digit integer
And the bot itself:
import discord
from discord.ext import commands
from tokens import TOKEN, CHANNEL_ID
client = commands.Bot(command_prefix='!')
client.sniped_message = None
#client.event
async def on_ready():
print("Your bot is ready.")
#client.event
async def on_message_delete(message):
# Make sure it's in the watched channel, and not one of the bot's own
# messages.
if message.channel.id == CHANNEL_ID and message.author != client.user:
print(f'sniped message: {message}')
client.sniped_message = message
#client.command()
async def snipe(ctx):
# Only respond to the command in the watched channel.
if ctx.channel.id != CHANNEL_ID:
return
if client.sniped_message is None:
await ctx.channel.send("Couldn't find a message to snipe!")
return
message = client.sniped_message
embed = discord.Embed(
description=message.content,
color=discord.Color.purple(),
timestamp=message.created_at
)
embed.set_author(
name=f"{message.author.name}#{message.author.discriminator}",
icon_url=message.author.avatar_url
)
embed.set_footer(text=f"Deleted in: #{message.channel.name}")
await ctx.channel.send(embed=embed)
client.run(TOKEN)
Multi-Guild Version
If you're monitoring one channel each in multiple guilds, then you need to keep track of them separately. Handily, channel IDs are globally unique, not just within a single guild. So you can keep track of them by ID alone, without having to include the guild ID as well.
You could keep them in a list, but I recommend a set, because checking whether something is in a set or not is faster. Comments to help yourself remember which one is which are probably also a good idea.
TOKEN = 'string of characters here'
# Not a dictionary, even though it uses {}
CHANNEL_IDS = {
# That one guild
123456789,
# The other guild
987654322,
}
Then instead of checking against the single channel ID, you check if it's in the set of multiple IDs.
import discord
from discord.ext import commands
from tokens import TOKEN, CHANNEL_IDS
client = commands.Bot(command_prefix='!')
client.sniped_messages = {}
#client.event
async def on_ready():
print("Your bot is ready.")
#client.event
async def on_message_delete(message):
# Make sure it's in a watched channel, and not one of the bot's own
# messages.
if message.channel.id in CHANNEL_IDS and message.author != client.user:
print(f'sniped message: {message}')
client.sniped_messages[message.channel.id] = message
#client.command()
async def snipe(ctx):
# Only respond to the command in a watched channel.
if ctx.channel.id not in CHANNEL_IDS:
return
try:
message = client.sniped_messages[ctx.channel.id]
# See note below
except KeyError:
await ctx.channel.send("Couldn't find a message to snipe!")
return
embed = discord.Embed(
description=message.content,
color=discord.Color.purple(),
timestamp=message.created_at
)
embed.set_author(
name=f"{message.author.name}#{message.author.discriminator}",
icon_url=message.author.avatar_url
)
embed.set_footer(text=f"Deleted in: #{message.channel.name}")
await ctx.channel.send(embed=embed)
client.run(TOKEN)
Note: bare except clauses, like in your original code, are not generally a good idea. Here we only want to catch KeyError, which is what is raised if the requested key isn't in the dictionary.
You could, optionally, implement the same logic in a different way:
message = client.sniped_messages.get(ctx.channel.id)
if message is None:
await ctx.channel.send("Couldn't find a message to snipe!")
return
A dictionary's .get() method returns the corresponding item just like normal indexing. However, if there is no such key, instead of raising an exception, it returns a default value (which is None if you don't specify one in the call to get).
If you're using Python 3.8+, the first two lines could also be combined using an assignment expression (using the "walrus operator"), which assigns and checks all at once:
if (message := client.sniped_messages.get(ctx.channel.id)) is None:
await ctx.channel.send("Couldn't find a message to snipe!")
return
These alternative options are mentioned for completeness; all of these ways of doing it are perfectly fine.
I'm trying to create a bot on discord to tag things with tone indicators (i.e., /s for sarcasm) This project started off as just a library but then I realized I could have it append the tone indicator to the end of the message by using it as an argument. However, I can only get the bot to send it as itself, which is disappointing because it interrupts the flow of conversations. I'm writing it using python. Here's my code:
import discord
from discord_slash import SlashCommand, SlashContext # Importing the newly installed library.
client = discord.Client(intents=discord.Intents.all())
slash = SlashCommand(client, sync_commands=True) # Declares slash commands through the client.
guild_ids = [guild id] # Put your server ID in this array.
#client.event
async def on_ready():
print("Ready!")
#slash.slash(name="j", description="joking", guild_ids=guild_ids)
async def j(ctx:SlashContext, message:str): # Defines a new "context" (ctx) command called "ping."
await ctx.respond(eat=True)
send_message = await ctx.send(f"{message} /j")
def check(message):
return message.author.id == ctx.author_id
try:
answer = await bot.wait_for('message', check=check, timeout = 60.0)
except asyncio.TimeoutError:
await ctx.send("You took to long to answer...", hidden=True)
else:
# do something with answer (convert to tone indicator)
pass
client.run("bot token")
I know I don't actually need any of the last part, but it was suggested to me as a work around to make it ID the user. It returns nothing. Anyone know how to make it send as the user who inputted the command, or mask itself as such?
You can use webhooks for this
#inside your command
guild = client.get_channel(ctx.channel_id)
if channel is None:
#error here
return
webhooks = filter(lambda x: x.user == client.user, await channel.webhooks()) #gets only webhooks created by us
if len(webhooks) == 0:
webhook = await channel.create_webhook(name='something')
else: webhook = webhooks[0]
await webhook.send(content, username=ctx.author.username, avatar = ctx.author.avatar_url
References:
Webhooks
getting webhooks
sending messages with webhooks
discord-slash get channel id
I'm trying to make a command that deletes the message content and send it back to the user if it's not the word "role".
Code is working fine. The message gets deleted if it's not "role" and I do received a dm with the deleted message, but I don't know if I'm doing it right because I get this error "AttributeError: 'ClientUser' object has no attribute 'create_dm'".
Here's my code:
#commands.Cog.listener()
async def on_message(self, message):
dm = await message.author.create_dm()
if not message.guild:
return
if not message.channel.name == 'roles':
return
elif message.channel.name == 'roles' and message.content != 'role':
if message.author.bot:
pass
else:
await dm.send(f'{message.content}')
await message.delete()
Discord bots can't send DM's to eachother, only to Users. Your bot is trying to send a DM to itself, which it can't. You need to check with an if-statement if the message's author is a bot or not.
You're already doing this later on in your code, to see if it should send the message or not, but the create_dm() function at the top will have already failed at that point (as it can't create a dm), so it will never get there.
#commands.Cog.listener()
async def on_message(self, message):
if message.author.bot:
return # Ignore bot messages
if not message.guild:
return
if not message.channel.name == 'roles':
return
elif message.content != 'role':
await message.author.send(message.content)
await message.delete()
PS. A few more things to note here:
You can just use User.send() instead of create_dm() & dm.send().
An f-string with nothing but the content can just be the content itself (f"{word}" == word, or str(word) in case it's not a string).
You have an if checking if the channel name is not "roles", and afterwards in the elif you check if it is - if it wouldn't be "roles" then it would've gotten into the first if & returned so the second one is unnecessary.