Conditional commands in discord.py - python

I want a user to be able to find out about something by, for example, saying something like !ask [___]. I would have a certain list of things that it could ask. Specifically, what notes are in a certain musical scale. So they would ask !scale Cmajor and get a response of "the notes in C major are C,D,E,F,G,A,B." I'm an absolute beginner and have no idea how to do this. I've made one bot before, but that's all my python experience.

You can view how to do such a task from the discord.py documentation
Specifically something like this could work, assuming you're using the latest version of discord.py, and following the appropriate setup:
# This is assuming your prefix is already defined, and you have a general bot setup
#bot.command()
async def scale(ctx, arg): # assuming the prefix is !, the command will be !scale argument
if arg.lower() == "cmajor":
await ctx.send("The notes in C major are C,D,E,F,G,A,B.")
else:
await ctx.send("Invalid scale.") # add more scales as needed

Related

Cooldowns in discord.py with on_message

So, I basically messed up. This is the first time I've ever tried a discord bot and I've done all my code in on_message with it checking the content of the message to see if it matches with the command name (example below). I've added a few commands, which are quite long, and I don't really want to rewrite it. Is there any way around this or do I have to rewrite it?
if message.content.lower().startswith("!test"):
await message.channel.send(db[str(message.author.id)])
Simple example of what I'm doing, just a test command.
I have tried looking inside other questions but I either: don't want to do that or don't understand what people are saying.
Any help would be appreciated and since I'm new to discord.py; I might need it explained in bit easier terms please.
You can do something like this:
import asyncio
users_on_cooldown = [] # Consider renaming this if you are going to have multiple commands with cooldowns.
def on_message(msg):
if msg.content.lower().startswith("!test") and not msg.author.id in users_on_cooldown:
await msg.channel.send(db[str(msg.author.id)])
users_on_cooldown.append(msg.author.id)
await asyncio.sleep(20) # time in seconds
users_on_cooldown.remove(msg.author.id)
Since you said you are a beginner, please note that if you make another command with a separate cooldown, use another variable that users_on_cooldown, maybe something like ban_cmd_cooldown and test_cmd_cooldown.
How It Works When the command is used, the user is added to a list, and after a certain amount of seconds, they are removed. When the command is run, it is checked if the user is on the list.
Note: When the bot is reset, cooldowns will be reset too.
If you have any questions about this, feel free to ask in the comments below.
Here how to use
#client.command()
#commands.cooldown(1, 60, commands.BucketType.user)
async def test(ctx):
await ctx.send(db[str(message.author.id)])
(1, 60, commands.BucketType.user) means 1 msg per 60sec or a 60sec cooldown.
I would recommend you rewrite your bot. It may take some time but it'll be worth it.

Match to the nearest command name in discord.py

I'm trying to create a feature for my discord.py bot that would send command names that are similar to what the user has used as a command when what they have typed in is incorrect. For example, a command called .slap exists. But the user enters .slp or something similar.
I want the bot to respond with the most similar command(s) which in this case is gonna be .slap. I'm a beginner still so I have no idea how to do this. I discovered about a lib called fuzzywuzzy and Levenshtein distance and I have no idea how to use them for my bot.
Any help would be highly appreciated!
Thanks
First of all, fuzzymatching commands and executing what it thinks is right, is not a great thing to add to your bot. It adds a point of failure, which can be pretty frustrating for the user regardless.
However, if you suggest a list of possible commands, it would probably work a lot better.
FuzzyWuzzy is a great tool for this.
The documentations for it are extremely helpful, so I really don't think you would have an issue if you actually read them.
My 2 cents for implementing would be (in pythonian pesudocode)
# user had input an invalid command
invalid_command = #userinput
command_list = [#list of your commands]
fuzzy_ratios = []
for command in command_list:
ratio = fuzzywuzzy.ratio(invalid_command, command)
fuzzy_ratios.append(ratio)
max_ratio_index = fuzzy_ratios.index(max(fuzzy_ratios))
fuzzy_matched = command_list[max_ratio_index]
return f"did you mean {fuzzy_matched}?"
Please try to implement and think why you need to implement it.
You need to actually try to implement yourself, or you will never learn.
You can try this:
disables = []
#client.command()
#commands.has_permissions(administrator=True)
async def disable(ctx, command):
command = client.get_command(command)
if not f"{command}: {ctx.guild.id}" in disables:
disables.append(f"{command}: {ctx.guild.id}")
await ctx.send(f"Disabled **{command}** for this server.")
else:
await ctx.send('This command is already disabled')
#client.command()
#commands.has_permissions(administrator=True)
async def enable(ctx, command):
command = client.get_command(command)
if f"{command}: {ctx.guild.id}\n" in disables:
await ctx.send(f"Enabled **{command}** for this server.")
else:
await ctx.send('This command is already enabled')
Now you have to add:
if "COMMAND: {ctx.guild.id}" in disables:
return
Between async def command(ctx) and your code for this command.
Warning: This is really bad way to do this. You can try to save a disables list to a json file. If you need help message me - Special unit#5323
Something you could use is aliases. Aliases are shortcuts for commands, here's an example:
#client.command(aliases=["slp","spla","spal","slpa","sap","salp"])
async def slap(ctx):
#Do whatever slap does
to create an alias you add aliases=[""] and start adding aliases. Aliases will invoke as commands. If I did .spla or whatever alias you added, it will still do what .slap does. Hope this helped!

Bot responding to channel mentions

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.

Capturing user input as a string in Discord.py rewrite and returning said input in a message

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.

How do I make a bot write a certain message when it first joins a server?

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".

Categories