I've been trying to develop a discord bot in python recently. I made it so that if a message contains a certain number, it will react and send a message. Here is the code in the cog file:
import discord
from discord.ext import commands
nice_numbers = ['69', '420']
class Auto(commands.Cog):
def __init__(self, client):
self.client = client
#commands.Cog.listener()
async def on_message(self, message):
msg = message.content
if message.author == self.client.user:
return
if any (word in msg for word in nice_numbers):
await message.add_reaction('đź‘Ś')
await message.channel.send(f'lmao nice')
def setup(client):
client.add_cog(Auto(client))
The problem is, the bot also responds with the same message and reaction when a user mentions a certain channel (in this case #general, #super-private-testing, and #resources). I can't seem to fix it or figure out why it's happening. I'm still pretty new to python so can someone please tell me what I'm doing wrong?
Basically what is happening is that mentions have a special syntax within the Discord API where they are basically a bunch of numbers put together.
For example when you are mentioning another user like the following:
Hello #User1234!
The real syntax within the discord message is the following:
Hello <#125342019199458000>!
And in the case of mentioning channels, it works similar, as a channel mentioned like:
#general
Internally would be written as:
<#550012071928922144>
Of course, the problem is that within this big number there could be false positive of finding your nice_numbers. There could be different ways to avoid this, for example you could check if a channel or a user is being mentioned in the message and return in that case.
if message.channel_mentions or message.mentions:
return
I think a better solution would be to changing the way you are checking if the nice_numbers are within message.content.
Using if word in msg would return true if message.content also includes something like 'My favourite number is 45669'. To overcome this issue it is better to make use of regular expressions.
You can declare a new function like this answers explains, which would return a <match object> if what you pass as parameter is found.
It would be something like this:
import re
def findCoincidences(w):
return re.compile(r'\b({0})\b'.format(w)).search
findCoincidences('69')('I like 69') # -> return <match object>
findCoincidences('69')('My favourite number is 45669') # -> return None
Expanding on Shunya's answer, you can use message.clean_content instead of message.content:
A property that returns the content in a “cleaned up” manner. This basically means that mentions are transformed into the way the client shows it. e.g. <#id> will transform into #name.
This will also transform #everyone and #here mentions into non-mentions.
This will prevent you from inadvertently matching against the IDs of channels, users, roles, etc. Of course, if the actual name contains a nice_number, it'll still match.
Related
Hey i am trying to get all the users in a specific guide, and i am getting part of the users and not all of them, why?, And i dont care about discord terms, i am not gonna spam servers or something like this so please help instead telling me the discord rules because i am know it well, This is the code i did,
import discord
import asyncio
intents = discord.Intents(messages=True, guilds=True, members=True)
client = discord.Client(intents=intents)
token = ""
#client.event
async def on_ready():
print("Bot Is Ready!")
guild = client.get_guild(328154277111398403)
for member in guild.members:
print(member)
await asyncio.sleep(0.1)
client.run(token, bot=False)
in the context of selfbots/userbots, discord.py doesn't actually have the ability to get the member list (or, at least, a significant portion of it), and therefore is not suited for this task. Instead, you'll either have to use a different library or code your own solution. Keep on reading for some code :)
Code:
A python lib with this support is discum.
Here's the least amount of code required to get the member list with discum:
import discum
bot = discum.Client(token='blah blah blah')
#bot.gateway.command
def helloworld(resp):
if resp.event.ready_supplemental:
bot.gateway.fetchMembers("GUILD_ID_HERE", "CHANNEL_ID_HERE")
bot.gateway.run()
And here's another example that creates a function called get_members that returns the member list: https://github.com/Merubokkusu/Discord-S.C.U.M/blob/master/examples/gettingGuildMembers.py
And here are more examples:
https://github.com/Merubokkusu/Discord-S.C.U.M/blob/master/docs/fetchingGuildMembers.md
How does it work?
Since we can't request for the member list the same way bot accounts can, we instead need to exploit the member list (yea, that members sidebar on the right that you see when going to a guild). We need to read that entire thing, piece by piece.
Essentially how it's done is the client first subscribes to member events in a channel in a guild (Luna somewhat went over this in her unofficial discord docs, but left out a lot). Then, as the user scrolls through the member list, more lazy requests are sent to get each chunk of the member list. And this is what happens in discum: lazy requests are sent until the entire member list is fetched. Here's some more info.
I want to get the user that was mentioned in a message and send him a private message. That's working without problems, but now I want to add a message for the case, that the mentioned member is not on the same server.
I searched a lot and try now for over 3 hours to find a solution.
Showcase of the problem: https://youtu.be/PYZMVXYtxpE
Heres my code:
#bot.event
async def on_message(message):
if len(message.mentions) == 1:
membe1 = message.mentions[0].id
membe2 = bot.get_user(membe1)
guild = bot.get_guild(message.author.guild.id)
if guild.get_member(membe1) is not None:
await membe2.send(content=f"you was mentioned in the server chat")
else:
embed2 = discord.Embed(title=f"» :warning: | PING not possible", description=f"not possible")
await message.channel.send(content=f"{message.author.mention}", embed=embed)
await message.delete()
return
The first part is working without problems, but at the "else" part, does the bot nothing. He still posts the message with the "invalid" ping in the chat and just ignores it. What can I do to fix that?
There is an error of design in what you are trying to do.
In Discord, you are not able to mention a user if he is not on that same server, so what you are checking will never work at all, currently your code is pretty much checking if the mentioned user exists, which will always happen unless he leaves the guild at the same time the command is executed.
Say for example, to make your command work you want to mention the user user123, who is in your server, you do the following:
#user123 blablabla
And that works because Discord internal cache can find user123 in your server and it is mentioned, which means that clicking on "#user123" will show us a target with his avatar, his roles or whatsoever.
But if you try to do the same for an invalid user, let's say notuser321:
#notuser321 blablabla
This is not a mention, take for example that you know who notuser321 is but he is in the same server as the bot is. Discord cannot retrieve this user from the cache and it is not considered a mention, just a single string, so not a single part of your code will be triggered, because len(message.mentions) will be 0.
Something that you could try would be using regular expressions to find an attempt to tag within message.content. An example would be something like this:
import re
message = "I love #user1 and #user2"
mention_attempt = re.findall(r'[#]\S*', message) # ['#user1', '#user2']
I'm trying to capture a users input, what they say in a message, and have it returned to them in a message from the bot. More specifically, when they run a command, it'll return what ever text they have entered after that.
So far, I'm here:
async def on_message(message):
if message.content.startswith["=ok"]:
await client.send_message(message.channel, message.content[6:])
...unfortunately, I believe this was valid for the previous version of Discord.py before the rewrite. Essentially I want someone to be able to run the command =pressf and have the bot return the message "Everyone, lets pay respects to (string)!" An event probably isn't the best way to go about this but I'm stumped.
I've been struggling to find a specific answer online for my issue so I greatly appreciate anyone who could point me in the proper direction. Thanks!
I would recommend using the newer Commands Extension, it is much simpler to implement what you are wanting. See this bit specifically for passing everything a user types after the command into a variable.
There is an official example I would recommend looking at here: https://github.com/Rapptz/discord.py/blob/master/examples/basic_bot.py
You should use commands instead of on_message event. Here is a simple command:
#client.command()
async def test(ctx):
await ctx.send('A Simple Command')
ctx parameter is the parameter that all commands must have. So, when you type =test, it will send to that channel A Simple Command.
If we come to what you try to do, you can use more parameters than ctx. Here is how you can do it:
#client.command()
async def pressf(ctx, *, mess):
await ctx.send(mess)
In this code, you have 1 more parameter called mess and also there's a *. That means mess parameter includes every message after =pressf. So when a user type =pressf Hello, it will send the channel Hello.
I want my Discord bot to send a certain message, For example: "Hello" when he joins a new server. The bot should search for the top channel to write to and then send the message there.
i saw this, but this isn't helpful to me
async def on_guild_join(guild):
general = find(lambda x: x.name == 'general', guild.text_channels)
if general and general.permissions_for(guild.me).send_messages:
await general.send('Hello {}!'.format(guild.name))```
The code you used is actually very helpful, as it contains all of the building blocks you need:
The on_guild_join event
The list of all channels in order from top to bottom (according to the official reference). If you want to get the "top channel" you can therefore just use guild.text_channels[0]
checking the permissions of said channel
async def on_guild_join(guild):
general = guild.text_channels[0]
if general and general.permissions_for(guild.me).send_messages:
await general.send('Hello {}!'.format(guild.name))
else:
# raise an error
Now one problem you might encounter is the fact that if the top channel is something like an announcement channel, you might not have permissions to message in it. So logically you would want to try the next channel and if that doesn't work, the next etc. You can do this in a normal for loop:
async def on_guild_join(guild):
for general in guild.text_channels:
if general and general.permissions_for(guild.me).send_messages:
await general.send('Hello {}!'.format(guild.name))
return
print('I could not send a message in any channel!')
So in actuality, the piece of code you said was "not useful" was actually the key to doing the thing you want. Next time please be concise and say what of it is not useful instead of just saying "This whole thing is not useful because it does not do the thing I want".
I've been working on a discord bot recently and have run into an issue where I would like to have only me and one other user be able to use a command, but I just can't figure out how, I read up on exceptions and lists but I just can't seem to wrap my head around how to use them, I'm basically learning as I go along, some stuff comes easier than others.
I know I am going to have to add and remove stuff I just don't know what, where and how.
Well, if I'm being honest, I tried being a nutcase and ended up copying the first return and pasting it below itself. I knew that probably wouldn't work. I also tried to add a comma after the first id with a space then put the second id.
def is_me():
def predicate(ctx):
return ctx.message.author.id == 413956481197670401
return commands.check(predicate)
#client.command()
#is_me()
async def Secret(ctx):
await ctx.send(f'{ctx.author.mention} Only you!')
#Secret.error
async def Secret_error(ctx, error):
if isinstance(error, commands.CheckFailure):
await ctx.send('Nothing here buckaroo.')
Well, my end goal is to have it so whenever one of us trigger the command, it spits out what I have it set to, and when anyone else uses it, it spits out the error.
However, whenever I tried it out with the comma attempt, it just allowed anyone to use the command. There were no error messages.
I am sorry for this text wall.
Ok, so you gotta define the command first off. Once you make that command, inside THAT, have an IF statement to check if the ID is equal to whoever or whoever else (use their IDs), if it's not, return null, or more user friendly return a message with no permissions.
Or you could make a variable storing both IDs, maybe as an array? Then instead of using the IDs, just use the array and its indexes.
def is_Mod(user_id):
## This function will check if the given id is already in the file. True if in file, False if not ##
with open('Mod.txt', 'r') as f:
if str(user_id) in f.read():
return True
else:
return False
Defined now we'll use it as
#client.command()
async def Secret(ctx):
if is_Mod(ctx.author.id) == True:
await ctx.send("Your response")
else:
#error
Discord.py provides #is_owner decorator so only owners can use a specific commands. You can provide owner_ids explicitly in a list on initialization or you can store the ids in db with command name as dict or something and write your own decorator function comparing the author id with the ids present in db, return true if the id is in list else false
allowing 2 or more people to use a command can be done in alot of ways, down below is the method i use for allowing certain people/IDs to use commands:
IDCARD = [ID1, ID2] <---Make a list of IDs
#client.command()
async def hello(ctx):
if ctx.author.id in IDCARD:
#your code here (Hello World)
else:
await ctx.send('Access Denied!')
If you want to be your own decorator, you have to do it like this:
def is_me(func):
#functools.wraps(func) # Don't forget to import functools
async def wrapper(ctx, *param, **kwargs):
if ctx.message.author.id == 413956481197670401:
await func(ctx, *param, **kwargs)
else:
await ctx.send("Nothing here buckaroo.")
return wrapper
#client.command()
#is_me
async def Secret(ctx):
await ctx.send(f'{ctx.author.mention} Only you!')
But you should also know that you can use decorators provided by discord.py to do the checking you want to do. For example :
https://discordpy.readthedocs.io/en/stable/ext/commands/api.html#checks
https://discordpy.readthedocs.io/en/stable/ext/commands/api.html#discord.ext.commands.Bot.before_invoke