Is there a way to to "hide" parameters of a command in Discord.py

Question:

For my bot I want to implement random games (such as tossing a coin), and a betting system on top of that.
For exemple, anyone could use //coin_toss and the bot will answer "Heads" or "Tails", but if the command is //bet 15 coin_toss, the bot will also answer "Heads" or "Tails" but also say after "Congratulation you won X " or "You lost your bet".

    @commands.command()
    async def coin_toss(self, ctx: Context):
        outcomes = ("Heads", "Tails")
        result = outcomes[random.randint(0,1)]
        await ctx.reply(result)
        return result 

The problem is that I want the //coin_toss command to have no parameters that can be passed via calling the command, but I still want parameters to be passed when calling it via self.coin_toss(ctx, amount_to_bet=5) for exemple. This also works for calling self.coin_toss

command: Command = utils.get(self.get_commands(), name=game_name)
result = await command(ctx, amount_to_bet)

For now I am using *args to "gather" all the arguments a user is trying to pass but I don’t know if that can be bypassed when calling the command via a message in a channel.

    @commands.command(aliases=["pile_ou_face", "pof"])
    @has_user_role_from_config()
    async def coin_toss(self, ctx: Context, *args, amount_to_bet: float = 0):
        outcomes = ("Heads", "Tails")
        result = outcomes[random.randint(0,1)]
        await ctx.reply(result)
        return result 

Is there a way to do that ? Feel free to comment if you want more precisions

Asked By: Nathan Marotte

||

Answers:

I’m not sure that this answers your question about hiding parameters of a discord command, but I think it may give you some ideas on how you could implement your bet command in terms of your other game commands without needing to do so much argument parsing.

I have two suggestions. Think of coin_toss as a sub-routine of your bet function or implement a shared logical implementation of your coin_toss function.

Coin Toss as a Sub-routine of Bet

import os
import random

from discord.ext import commands
from dotenv import load_dotenv

load_dotenv()

DISCORD_TOKEN = os.getenv("DISCORD_TOKEN")

bot = commands.Bot(command_prefix="//")


@bot.command()
async def coin_toss(ctx):
    outcomes = ("Heads", "Tails")
    result = outcomes[random.randint(0, 1)]
    await ctx.reply(result)
    return result


# Simulates your Utils Get Function
def get_commands():
    return [coin_toss]


def get_command_by_name(command_list, name):
    names = [c.name for c in command_list]
    if name not in names:
        return None
    else:
        return command_list[names.index(name)]


@bot.command()
async def bet(ctx, *args):
    if len(args) != 3:
        # Example //bet 15 Heads coin_toss
        await ctx.reply("Command is //bet amount outcome game_name")

    c = get_command_by_name(get_commands(), name=args[2])

    # Check Command Exists
    if c is None:
        await ctx.reply(f"Command {args[2]} does not exist")
    else:
        # Assumes result comes back as a string!!
        result = await c(ctx)
        # Compare case insensitive
        if result.lower() == args[1].lower():
            msg = f"Congratulation you won {args[0]}"
        else:
            msg = f'You lost your bet of {args[0]}'
        await ctx.reply(msg)


bot.run(DISCORD_TOKEN)

I have a simulated your get_commands, and your utils.get(self.get_commands(), name=value) functions here so please update your bet implementation to reflect how your functions actually perform. I’ve also done this outside of a class for ease of testing, but please make the necessary changes to incorporate this code into your current implementation.

The downside of this approach is the delay. You’re affectively sending two separate replies, once from the coin_toss function, then again from bet this can make for a delayed user experience.

Coin Toss with Shared Logical Implementation

import os
import random

from discord.ext import commands
from dotenv import load_dotenv

load_dotenv()

DISCORD_TOKEN = os.getenv("DISCORD_TOKEN")

bot = commands.Bot(command_prefix="//")


# Separated Game Logic from the Bot Command
def coin_toss_logic():
    outcomes = ("Heads", "Tails")
    result = outcomes[random.randint(0, 1)]
    return result


@bot.command()
async def coin_toss(ctx):
    await ctx.reply(coin_toss_logic())


# Simulates your Utils Get Function
def get_commands():
    return [coin_toss_logic]


def get_command_by_name(command_list, name):
    # Strip off the "_logic" portion of your command names
    # uses __name__ since not a command anymore
    # Requires a consistent naming convention!
    names = [c.__name__[:-6] for c in command_list]
    if name not in names:
        return None
    else:
        return command_list[names.index(name)]


@bot.command()
async def bet(ctx, *args):
    if len(args) != 3:
        # Example //bet 15 Heads coin_toss
        await ctx.reply("Command is //bet amount outcome game_name")

    c = get_command_by_name(get_commands(), name=args[2])

    # Check Command Exists
    if c is None:
        await ctx.reply(f"Command {args[2]} does not exist")
    else:
        # Assumes result comes back as a string!!
        # No await needed since it's not an asyc command
        result = c()
        # Need to add Result to msg since not handled by command anymore
        msg = f'{result}n'
        # Compare case insensitive
        if result.lower() == args[1].lower():
            msg += f"Congratulation you won {args[0]}"
        else:
            msg += f'You lost your bet of {args[0]}'
        await ctx.reply(msg)


bot.run(DISCORD_TOKEN)

The benefit of this implementation being that there is less delay between messages, however, there is more work because you have essentially two separate functions that both map to the same name.

Answered By: Henry Ecker

I’ve stumbled across the same problem recently (if I understood you correctly):
To solve this you can use an attribute "extras" in discord.py

and then get it using something like this code:

@bot.tree.command(name="test", description="testing extras params", extras=<your-dictionary>)
...
[ctx | interaction] .command.extras.get(<your-parameter>)

Hope, it will help.

Answered By: Vladimir
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.