I made a function that echoes a text with classic commands. I wanted to make it with the slash command, but it didn't work. Honestly, I was confused, and the discord.py documentation wasn't precise about many things.
In my code, the first two arguments are reserved for channels and time.
The bot will check if the two arguments are provided. If one or both are not, it will ignore them. This is for the channel:
async def echo(ctx, *args):
#If a command is not followed with a text
if not args:
await ctx.send("Please provide some text for me to echo dummy.")
return
#Channel mention
if args[0].startswith("<#") and args[0].endswith(">"):
channel_id = int(args[0][2:-1])
channel = client.get_channel(channel_id)
#If the channel is not valid or doesn't exist
if not channel:
await ctx.send("Invalid channel mention.")
return
args = args[1:]
else:
channel = ctx.channel
The rest of the code is for the time argument:
#Check if there is a time argument
time_match = re.match(r"(\d+)(h|m)", args[0])
if time_match:
time_delta = timedelta(hours=int(time_match.group(1))) if time_match.group(2) == "h" else timedelta(minutes=int(time_match.group(1)))
await ctx.send(f"I will echo the message in {time_delta}.")
await asyncio.sleep(time_delta.total_seconds())
args = args[1:]
else:
time_delta = timedelta()
message = " ".join(args)
await channel.send(message)
I found that converting those will require a lot of understanding, and I am a beginner at python.
I dont know if i understood your problem correct, that creating slash commands is not working for you? I know 2 main ways of how to create slash commands with discord.py:
Use discord.Client and manage the commandTree on your own
Use discord.ext.commands.Bot and use cogs to create commands
Also i want to point out the difference between the commands and the app_commands.
app_commands are the commands you know as slash commands. They are supported by the discord api and will show up in the text channel after typing "/". The normal commands are unknown to disocrd. Discord only sends all text messages to discord.py. discord.py works out if a command was texted or not and will call the coreesponding method.
I highly recommend using commands.Bot and cogs. Also this example is based on it.
So first of all your class for a bot should inherits from commands.Bot. Also we add the mehtod setup_hook to load all cogs you are going to implement.
class MyBot(commands.Bot):
def __init__(self):
super().__init__(intents=discord.Intents.all(), command_prefix='/')
async def setup_hook(self):
for file in os.listdir(f'./cogs'):
if file.endswith('.py'):
await self.load_extension(f'cogs.{file[:-3]}')
Now you can create you first cog in a subfolder named "cogs". I recomment to create one file for every Cog. So for this exmaple we create a file and put the following code inside(note that the name inside #app_commands.command defines how the command will be shown in discord):
class MyCog(commands.Cog):
def __init__(self, client):
self.client = client
#app_commands.command(name='test_command')
async def test_command_method(self, ctx: discord.Interaction, param1: str = None, param2: str = None):
if param1 is None:
param1 = ctx.channel_id # set default value
if param2 is None:
await ctx.response.send_message(f'param2 missing!')
return
await ctx.response.send_message(f'Called with parameters param1: "{param1}" and param2: "{param2}"')
async def setup(client):
await client.add_cog(MyCog(client))
The last thing you need to do is to add another Cog, which will sync your created commands with the discord api, so you can see them in discord after typing "/". Note, that this is using commands and not app_commands! So the sync command will not appear after typing "/", but any other app_command does (after syncing).
class Sync(commands.Cog):
def __init__(self, client):
self.client = client
#commands.command()
async def sync(self, ctx: Context, spec: Optional[Literal["~", "*", "^"]] = None) -> None:
ctx.bot.tree.copy_global_to(guild=ctx.guild)
synced = await ctx.bot.tree.sync(guild=ctx.guild)
await ctx.send(f"Synced {len(synced)} commands {'globally' if spec is None else 'to the current guild.'}")
async def setup(client):
await client.add_cog(Sync(client))
If you have all this things set up correctly and your bot starts without errors you can type "/sync" in any textchannel on any discord server the bot is connected to. After this you should be able to see your commands after typing "/" in a text channel.
Related
I installed discord.py using python3 -m pip install -U git+https://github.com/Rapptz/discord.py.
pic of what I want
This is main.py
guild_id = 964345157480747029
import discord
from discord import app_commands
class aclient(discord.Client):
def __init__(self):
super().__init__(intents = discord.Intents.default())
self.synced = False #we use this so the bot doesn't sync commands more than once
async def on_ready(self):
await self.wait_until_ready()
if not self.synced: #check if slash commands have been synced
await tree.sync(guild = discord.Object(id=guild_id)) #guild specific: leave blank if global (global registration can take 1-24 hours)
self.synced = True
print(f"We have logged in as {self.user}.")
client = aclient()
tree = app_commands.CommandTree(client)
#tree.command(guild = discord.Object(id=guild_id), name = 'test', description='A test slash command') #guild specific slash command
async def slash2(interaction: discord.Interaction, text: str)
await interaction.response.send_message(f'You said "{text}"!')
client.run('token')
The slash command works, but I want the text option to have a description that says
"Text to repeat".
I used this tutorial and got most of my code from here.
Please help. Thank you!
When using slash commands I'd recommend py-cord instead of discord.py.
Installing
Basic slash command
More detailed slash commands
Options
An example would be
import discord
from discord import option
bot = discord.Bot()
#bot.slash_command(name="test", guild_ids=[000000000000000000]) # replace with your guild id
#option(
"text",
str,
description="Enter some text"
)
async def test_command(ctx, text):
await ctx.respond(f"You wrote: {text}")
bot.run("TOKEN")
There are two ways of adding a description to app command arguments.
by using the app_commands.describe(arg=desc) decorator.
by creating a docstring for the function which takes the argument descriptions as well as the command description from there.
Both of them are shown below.
#app_commands.command(name="item")
#app_commands.describe(
choices="Do you want to buy or sell, in fact?!",
item_name="Which item, in fact?!",
)
async def get_item(
self,
interaction: discord.Interaction,
choices: app_commands.Choice[str],
item_name: str,
):
"""Get item information and buy/sell orders.
Args:
interaction (discord.Interaction): the interaction that invokes this coroutine
choices (app_commands.Choice[str]): buying or selling?
item_name (str): item name to search for
"""
pass
I have imported discord.ext and discord.ext.commands
I have defined the owner_id attribute when defining my client variable
#client.group(aliases=['owncmds'])
#commands.is_owner()
async def ownercmds(ctx):
if ctx.invoked_subcommand is None:
em = discord.Embed(title="Hello, owner.",description="Here are the commands that only you can use!",color=discord.Colour.blue())
em.add_field(name="b?clearconsole",value="Clears the console.")
await ctx.send(embed=em)
When I run the command, it doesn't give me a response, nor an error.
Yes,
After a bit of testing, it does work, this is the response I got after copying your code and pasting it to a bot: The working embed from the command that is specified in the question:
If this does not work for you, you could just use an if statement to check whether the id is equal to yours (if you are the owner), to add this if statement to the specified command it would be:
#client.group(aliases=['owncmds'])
async def ownercmds(ctx):
if ctx.author.id = "add your id here without the quotes":
if ctx.invoked_subcommand is None:
em = discord.Embed(title="Hello, owner.",description="Here are the commands that only you can use!",color=discord.Colour.blue())
em.add_field(name="b?clearconsole",value="Clears the console.")
await ctx.send(embed=em)
In discord.py is there some option how i can get access to msg.content (or ctx.content how everyone uses it) under a command ? Below you can see 2 examples of what i mean. The first one is event and i simply copy the message and let the bot to send it back. The second is command but there the msg.content doesnt work. My problem is that i dont want to use events so much and have everything under a command.
#bot.event
async def on_message(msg):
chat = bot.get_channel(797224597443051611)
if msg.channel.id != channel:
return
if msg.content.startswith("!rps"):
message = str(msg.content)
await chat.send(message)
Someone types !rps hello. Outpup in discord is !rps hello
#bot.command()
async def rps(msg):
if msg.channel.id != channel:
return
message = str(msg.content)
await msg.send(message)
Someone types !rps hello (my prefix is !). Output is error in console:
discord.ext.commands.errors.CommandInvokeError: Command raised an exception: AttributeError: 'Context' object has no attribute 'content'
Commands always take commands.Context as the first argument, also you should call it ctx instead of msg, to access the message content you can use ctx.message.content
#bot.command()
async def rps(ctx):
if ctx.channel.id != channel:
return
message = str(ctx.message.content)
await ctx.send(message)
Take a look at the commands introduction
In order to get the rest of the message of a command, you need to pass another argument. This argument will include all the messages that's sent after !rps. Also, using ctx instead of msg is better in commands.
#bot.command()
async def rps(ctx, *, args):
if ctx.channel.id != channel:
return
await ctx.send(args)
In this code, args argument includes all the messages after the !rps.
So I'm in discord.py right now, and whenever I directly edit bot.py (my main file) I can implement a version of my suggest command.
#bot.command(name = 'suggest', help = 'Use this command to give a suggestion to the server')
async def suggest(ctx, content):
channel = discord.utils.get(bot.guilds[0].channels, name = 'suggestions')
print('Suggest command triggered, content is {}'.format(content))
await channel.send(content)
^ bot is just my version of client
This works perfectly fine (except the fact that I only get the 1st word of content, so if someone could solve that, that would also be nice
But when I copy paste into my cog
#commands.command(name = 'suggest', help = 'Use this command to give a suggestion to the server')
async def suggest(self, bot, ctx, content):
print('Suggest command triggered')
channel = discord.utils.get(bot.guilds[0].channels, name = 'suggestions')
print('Content is {}'.format(content))
await channel.send(content)
It doesn't work, can someone help me?
For a cog, that bot parameter is unnecessary, so you just put
#commands.command(name = 'suggest', help = 'Use this command to give a suggestion to the server')
async def suggest(self, ctx, content):
print('Suggest command triggered')
channel = discord.utils.get(bot.guilds[0].channels, name = 'suggestions')
print('Content is {}'.format(content))
await channel.send(content)
As for the only having the first word, discord.py separates arguments by words, so you can just group them all with a * like so:
async def suggest(self, ctx, *content): # gets all the words in a tuple
So, I was wondering if there could be a command I could write that allows me to set the bots presence and activity (ex. ~~set presence idle or ~~set activity watching "people typing ~~help") or something like that.
Unrelated question: How do I set commands to be used by me only?
I haven't found any example code for this, and i'm a beginner.
You can use the is_owner check to ensure that you are the only person who can invoke a command.
To change the presence or status of the bot, use the change_presence method:
from discord.ext.commands import Bot, is_owner
from discord import Status, Activity, ActivityType
bot = Bot("~~")
def getEnum(enum):
def getter(arg):
return enum[arg]
return getter
#bot.group(invoke_without_command=True)
#is_owner()
async def set(ctx):
await ctx.send("You must provide a subcommand.")
#set.command()
async def presence(ctx, status: getEnum(Status)):
await bot.change_presence(status=status)
#set.command(invoke_without_command=True)
async def activity(ctx, type: getEnum(ActivityType), *, description):
await bot.change_presence(activity=Activity(type=type, name=description))
#set.error
async def set_error(ctx, error):
if isinstance(error, BadArgument):
await ctx.send(error.message)
await ctx.send(error.args)
bot.run("token")
The above will fail silently if you try to provide an unrecognized name to Status or ActivityType, you could also try writing an error handler to provide some feedback.