Extractors

Extractors here work like they do in actix_web. They implement the FromMessage trait, and get the data they need from the message + context.

Note: The first 2 arguments are always the context and the message; all the others are expected to be extractors.

Note 2: They are only available when using the standard framework.

Here's an example of a stat user command, that just formats the user with the debug formatter and replies with the result:


#![allow(unused)]
fn main() {
use robespierre::framework::standard::{FwContext, CommandResult, macros::command};
use robespierre::framework::standard::extractors::{Args, Author, RawArgs, Rest};
use robespierre::model::MessageExt;
use robespierre_models::channels::Message;
use robespierre_models::users::User;

#[command]
async fn stat_user(ctx: &FwContext, message: &Message,
    Args((user,)): Args<(User,)> // parses a single argument as an UserId, and fetches the user with that id
) -> CommandResult {
    message.reply(ctx, format!("{:?}", user)).await?;

    Ok(())
}
}

Or a stat channel command:


#![allow(unused)]
fn main() {
use robespierre::framework::standard::{FwContext, CommandResult, macros::command};
use robespierre::framework::standard::extractors::{Args, Author, RawArgs, Rest};
use robespierre::model::MessageExt;
use robespierre_models::channels::{Message, Channel};

#[command]
async fn stat_channel(ctx: &FwContext, message: &Message,
    Args((channel,)): Args<(Channel,)> // parses a single argument as a ChannelId, and fetches the channel with that id
) -> CommandResult {
    message.reply(ctx, format!("{:?}", channel)).await?;

    Ok(())
}
}

Or a "repeat" command, which just echoes back the arguments (be careful who you let to run this command):


#![allow(unused)]
fn main() {
use robespierre::framework::standard::{FwContext, CommandResult, macros::command};
use robespierre::framework::standard::extractors::{Args, Author, RawArgs, Rest};
use robespierre::model::MessageExt;
use robespierre_models::channels::Message;

#[command]
async fn repeat(ctx: &FwContext, message: &Message, Author(author): Author, RawArgs(args): RawArgs) -> CommandResult {
    if author.id != "<your user id>" {
        return Ok(());
    }

    message.reply(ctx, &*args).await?;

    Ok(())
}
}

They get added to the framework in the exact same way.

By default, the delimiter Args uses is ``, but you can change it like:


#![allow(unused)]
fn main() {
use robespierre::framework::standard::{FwContext, CommandResult, macros::command};
use robespierre::framework::standard::extractors::{Args, Author, RawArgs, Rest};
use robespierre::model::MessageExt;
use robespierre_models::channels::Message;

#[command]
async fn repeat_with_spaces(
    ctx: &FwContext,
    message: &Message,
    Author(author): Author,
    #[delimiter(" ")] // it's the default, can be removed
    Args((arg1, arg2)): Args<(String, String)>
) -> CommandResult {
    if author.id != "<your user id>" {
        return Ok(());
    }

    message.reply(ctx, format!("first: {}, second: {}", arg1, arg2)).await?;

    Ok(())
}

#[command]
async fn repeat_with_spaces_and_tabs(
    ctx: &FwContext,
    message: &Message,
    Author(author): Author,
    #[delimiters(" ", "\t")]
    Args((arg1, arg2)): Args<(String, String)>
) -> CommandResult {
    if author.id != "<your user id>" {
        return Ok(());
    }

    message.reply(ctx, format!("first: {}, second: {}", arg1, arg2)).await?;

    Ok(())
}

#[command]
async fn repeat_with_spaces_and_tabs_2(
    ctx: &FwContext,
    message: &Message,
    Author(author): Author,
    #[delimiter(" ")]
    #[delimiter("\t")] // they cumulate
    Args((arg1, arg2)): Args<(String, String)>
) -> CommandResult {
    if author.id != "<your user id>" {
        return Ok(());
    }

    message.reply(ctx, format!("first: {}, second: {}", arg1, arg2)).await?;

    Ok(())
}

#[command]
async fn repeat_with_commas(
    ctx: &FwContext,
    message: &Message,
    Author(author): Author,
    #[delimiter(",")]
    Args((arg1, arg2)): Args<(String, String)>
) -> CommandResult {
    if author.id != "<your user id>" {
        return Ok(());
    }

    message.reply(ctx, format!("first: {}, second: {}", arg1, arg2)).await?;

    Ok(())
}
}

Note: valid values include everything that implements Into<Cow<'static, str>>, but for #[delimiters()], they have to all be of the same type.

If you need values of multiple types, then you can use multiple #[delimiters] attributes.

Special types

There are 2 kinds of special argument types (omitting .to_string() calls for simplicity):

Option<T> where T: Arg

Means "try to parse the argument as T, and if failed, pass None as arg, and continue trying to parse as if there was no arg here".

E.g. a type like:

// --snip--
fn f(
#[delimiter(",")]
args: Args<(String, Option<UserId>, String)>,
)->{}

If given aaa, bbb, it will result Args(("aaa", None, "bbb")).

While if given aaa, <@AAAAAAAAAAAAAAAAAAAAAAAAAA>, bbb, it will result Args(("aaa", Some(...), "bbb")).

Rest<T> where T: Arg

It means "use all the remaining text" to parse T.

E.g. a type like:

// --snip--
fn f(
#[delimiter(",")]
args: Args<(String, Rest<String>)>
)->{}

Given:

"aaa, bbb" => Args(("aaa", Rest("bbb")))
"aaa, bbb, ccc" => Args(("aaa", Rest("bbb, ccc")))

Note: No argument should ever come after a Rest<T>.

A full working example:

use robespierre::CacheWrap;
use robespierre::EventHandlerWrap;
use robespierre::Context;
use robespierre::Authentication;
use robespierre::model::MessageExt;
use robespierre::framework::standard::{FwContext, CommandResult, macros::command};
use robespierre::framework::standard::{StandardFramework, Command, CommandCodeFn};
use robespierre::framework::standard::extractors::{Args, Author, RawArgs, Rest};
use robespierre::FrameworkWrap;
use robespierre_cache::CacheConfig;
use robespierre_events::Connection;
use robespierre_http::Http;
use robespierre_models::users::User;
use robespierre_models::channels::{Channel, Message};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    tracing_subscriber::fmt::init();

    let token = std::env::var("TOKEN")
        .expect("Cannot get token; set environment variable TOKEN=... and run again");

    let auth = Authentication::bot(token);

    let http = Http::new(&auth).await?;
    let connection = Connection::connect(&auth).await?;

    let context = Context::new(http, robespierre::typemap::ShareMap::custom()).with_cache(CacheConfig::default());

    let fw = StandardFramework::default()
        .configure(|c| c.prefix("!"))
        .group(|g| {
            g.name("General")
                .command(|| Command::new("stat_user", stat_user as CommandCodeFn))
                .command(|| Command::new("stat_channel", stat_channel as CommandCodeFn))
                .command(|| Command::new("repeat", repeat as CommandCodeFn))
                .command(|| Command::new("repeat_with_commas", repeat_with_commas as CommandCodeFn))
                .command(|| Command::new("repeat_with_commas_using_rest", repeat_with_commas_using_rest as CommandCodeFn))
        });
    let handler = FrameworkWrap::new(fw, Handler);
    let handler = CacheWrap::new(EventHandlerWrap::new(handler));
    connection.run(context, handler).await?;

    Ok(())
}

#[command]
async fn stat_user(ctx: &FwContext, message: &Message,
    Args((user,)): Args<(User,)> // parses a single argument as an UserId, and fetches the user with that id
) -> CommandResult {
    message.reply(ctx, format!("{:?}", user)).await?;

    Ok(())
}

#[command]
async fn stat_channel(ctx: &FwContext, message: &Message,
    Args((channel,)): Args<(Channel,)> // parses a single argument as a ChannelId, and fetches the channel with that id
) -> CommandResult {
    message.reply(ctx, format!("{:?}", channel)).await?;

    Ok(())
}

#[command]
async fn repeat(ctx: &FwContext, message: &Message, Author(author): Author, RawArgs(args): RawArgs) -> CommandResult {
    if author.id != "<your user id>" {
        return Ok(());
    }

    // args is an Arc<String>
    message.reply(ctx, &*args).await?;

    Ok(())
}

#[command]
async fn repeat_with_commas(
    ctx: &FwContext,
    message: &Message,
    Author(author): Author,
    #[delimiter(",")]
    Args((arg1, arg2)): Args<(String, String)>
) -> CommandResult {
    if author.id != "<your user id>" {
        return Ok(());
    }

    message.reply(ctx, format!("first: {}, second: {}", arg1, arg2)).await?;

    Ok(())
}

#[command]
async fn repeat_with_commas_using_rest(
    ctx: &FwContext,
    message: &Message,
    Author(author): Author,
    #[delimiter(",")]
    Args((arg1, Rest(arg2))): Args<(String, Rest<String>)>
) -> CommandResult {
    if author.id != "<your user id>" {
        return Ok(());
    }

    message.reply(ctx, format!("first: {}, second: {}", arg1, arg2)).await?;

    Ok(())
}

#[derive(Clone)]
struct Handler;

#[robespierre::async_trait]
impl robespierre::EventHandler for Handler {}

As always, you can find the example in the repo