Skip to content

Read command arguments

A command you declared has been run. This is how to find out which one, and read what it was given.

Declaring the surface is Declare a command surface.

1. Take interactions off the feed

for in := range interactive.Interactions() {
    if in.Type != chatplatform.CommandInvoked {
        continue
    }
    // ...
}

2. Work out which command ran

Command is the top-level name. Path is the verbs beneath it, and is empty for a flat command:

switch {
case in.Command == "ask":
    // /ask

case in.Command == "scout" && slices.Equal(in.Path, []string{"join"}):
    // /scout join

case in.Command == "scout" && slices.Equal(in.Path, []string{"table", "new"}):
    // /scout table new
}

Path is reported rather than reconstructed, so you never parse a verb back out of a name you flattened.

3. Read each argument through its own accessor

channel, ok := in.Args.Channel("channel")
if !ok {
    _ = actor.Respond(ctx, in.Token, "I need a channel to set that up.", true)

    return
}

Every accessor answers (value, bool): String, Int, Number, Bool, Channel, User, Role.

The second return is the whole point. It is false when the argument was not sent, and false when it arrived as a different kind. The obvious code (take the value, give up if there is none) is also the correct code.

A zero Args is safe to read from. A command invoked with nothing is ordinary, not exceptional.

4. Know why String refuses a channel

id, ok := in.Args.Channel("channel") // ("123", true)  — the platform resolved it
txt, ok := in.Args.String("channel") // ("",    false) — deliberately

A channel the platform resolved is an identifier, not text. Handing its id back through String would make it indistinguishable from a channel name somebody typed by hand, which is the ambiguity typed options exist to remove.

That distinction is also how you detect a fallback. If you asked SupportsOption before registering, found OptionChannel unsupported, and declared OptionString instead, then the value arrives as text and String answers true:

if id, ok := in.Args.Channel("channel"); ok {
    use(id)                       // the platform resolved it
} else if name, ok := in.Args.String("channel"); ok {
    resolveYourself(name)         // you chose text, knowingly
}

A provider never makes that substitution on your behalf. Where a platform cannot carry a type, RegisterCommands refuses rather than quietly downgrading it, so the only way that second branch runs is that you asked for it.

5. Answer

An interaction expects a response, and the token is what addresses it:

_ = actor.Respond(ctx, in.Token, "Table created.", true)

The final argument keeps the reply private to the person who ran the command.

Deciding who may run what

Read in.By.Roles. CommandSpec.RequiredRoles asks the platform to gate the command where it can, but platform-side gating is a convenience and never the authority, because a provider that cannot enforce it still delivers the interaction, so the check has to be yours regardless.

Never decide authorisation from By.Name, and never from anything in message content.