User data

Let's start with the code from the previous chapter:

// src/main.rs
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::FrameworkWrap;
use robespierre_cache::CacheConfig;
use robespierre_events::Connection;
use robespierre_http::Http;
use robespierre_models::channels::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("ping", ping as CommandCodeFn))
        });

    let handler = FrameworkWrap::new(fw, Handler);
    let handler = CacheWrap::new(EventHandlerWrap::new(handler));

    connection.run(context, handler).await?;

    Ok(())
}

#[command]
async fn ping(ctx: &FwContext, msg: &Message) -> CommandResult {
    msg.reply(ctx, "pong").await?;
    Ok(())
}

#[derive(Clone)]
struct Handler;

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

Specifically this line is of interest:

// src/main.rs
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::FrameworkWrap;
use robespierre_cache::CacheConfig;
use robespierre_events::Connection;
use robespierre_http::Http;
use robespierre_models::channels::Message;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // --snip--
    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());

    // --snip--
    let fw = StandardFramework::default()
        .configure(|c| c.prefix("!"))
        .group(|g| {
            g.name("General")
                .command(|| Command::new("ping", ping as CommandCodeFn))
        });

    let handler = FrameworkWrap::new(fw, Handler);
    let handler = CacheWrap::new(EventHandlerWrap::new(handler));

    connection.run(context, handler).await?;

    Ok(())
}

#[command]
async fn ping(ctx: &FwContext, msg: &Message) -> CommandResult {
    msg.reply(ctx, "pong").await?;
    Ok(())
}

#[derive(Clone)]
struct Handler;

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

So let's add a "command counter", just a number to count how many commands we executed.

For performance, instead of using an Arc<Mutex<usize>>, we'll use an Arc<AtomicUsize>.

First, we'll start with a definition of a typemap key:

// src/main.rs
use std::sync::Arc;
use std::sync::atomic::AtomicUsize;
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::FrameworkWrap;
use robespierre_cache::CacheConfig;
use robespierre_events::Connection;
use robespierre_http::Http;
use robespierre_models::channels::Message;

struct CommandCounterKey;

impl robespierre::typemap::Key for CommandCounterKey {
    type Value = Arc<AtomicUsize>;
}

#[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("ping", ping as CommandCodeFn))
        });

    let handler = FrameworkWrap::new(fw, Handler);
    let handler = CacheWrap::new(EventHandlerWrap::new(handler));

    connection.run(context, handler).await?;

    Ok(())
}

#[command]
async fn ping(ctx: &FwContext, msg: &Message) -> CommandResult {
    msg.reply(ctx, "pong").await?;
    Ok(())
}

#[derive(Clone)]
struct Handler;

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

And add it to the map:

// src/main.rs
use std::sync::Arc;
use std::sync::atomic::AtomicUsize;
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::FrameworkWrap;
use robespierre_cache::CacheConfig;
use robespierre_events::Connection;
use robespierre_http::Http;
use robespierre_models::channels::Message;

// --snip--
struct CommandCounterKey;

impl robespierre::typemap::Key for CommandCounterKey {
    type Value = Arc<AtomicUsize>;
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // --snip--
    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 mut data = robespierre::typemap::ShareMap::custom();
    data.insert::<CommandCounterKey>(Arc::new(AtomicUsize::new(0)));
    let context = Context::new(http, data).with_cache(CacheConfig::default());

    // --snip--
    let fw = StandardFramework::default()
        .configure(|c| c.prefix("!"))
        .group(|g| {
            g.name("General")
                .command(|| Command::new("ping", ping as CommandCodeFn))
        });

    let handler = FrameworkWrap::new(fw, Handler);
    let handler = CacheWrap::new(EventHandlerWrap::new(handler));

    connection.run(context, handler).await?;

    Ok(())
}

#[command]
async fn ping(ctx: &FwContext, msg: &Message) -> CommandResult {
    msg.reply(ctx, "pong").await?;
    Ok(())
}

#[derive(Clone)]
struct Handler;

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

And when we get a command we have to increment it:

// src/main.rs
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use robespierre::CacheWrap;
use robespierre::EventHandlerWrap;
use robespierre::Context;
use robespierre::Authentication;
use robespierre::UserData;
use robespierre::model::MessageExt;
use robespierre::framework::standard::{FwContext, CommandResult, macros::command};
use robespierre::framework::standard::{StandardFramework, Command, CommandCodeFn};
use robespierre::FrameworkWrap;
use robespierre_cache::CacheConfig;
use robespierre_events::Connection;
use robespierre_http::Http;
use robespierre_models::channels::Message;

// --snip--
struct CommandCounterKey;

impl robespierre::typemap::Key for CommandCounterKey {
    type Value = Arc<AtomicUsize>;
}

#[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 mut data = robespierre::typemap::ShareMap::custom();
    data.insert::<CommandCounterKey>(Arc::new(AtomicUsize::new(0)));
    let context = Context::new(http, data).with_cache(CacheConfig::default());

    let fw = StandardFramework::default()
        .configure(|c| c.prefix("!"))
        .group(|g| {
            g.name("General")
                .command(|| Command::new("ping", ping as CommandCodeFn))
        });

    let handler = FrameworkWrap::new(fw, Handler);
    let handler = CacheWrap::new(EventHandlerWrap::new(handler));

    connection.run(context, handler).await?;

    Ok(())
}

#[command]
async fn ping(ctx: &FwContext, msg: &Message) -> CommandResult {
    // --snip--
    msg.reply(ctx, "pong").await?;

    let data = ctx.data_lock_read().await;
    let counter = data.get::<CommandCounterKey>().unwrap();
    counter.fetch_add(1, Ordering::SeqCst);
    
    // --snip--
    Ok(())
}

#[derive(Clone)]
struct Handler;

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

And finally, let's add another command to display the counter:

// src/main.rs
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use robespierre::CacheWrap;
use robespierre::EventHandlerWrap;
use robespierre::Context;
use robespierre::Authentication;
use robespierre::UserData;
use robespierre::model::MessageExt;
use robespierre::framework::standard::{FwContext, CommandResult, macros::command};
use robespierre::framework::standard::{StandardFramework, Command, CommandCodeFn};
use robespierre::FrameworkWrap;
use robespierre_cache::CacheConfig;
use robespierre_events::Connection;
use robespierre_http::Http;
use robespierre_models::channels::Message;

// --snip--
struct CommandCounterKey;

impl robespierre::typemap::Key for CommandCounterKey {
    type Value = Arc<AtomicUsize>;
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // --snip--
    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 mut data = robespierre::typemap::ShareMap::custom();
    data.insert::<CommandCounterKey>(Arc::new(AtomicUsize::new(0)));
    let context = Context::new(http, data).with_cache(CacheConfig::default());

    let fw = StandardFramework::default()
        .configure(|c| c.prefix("!"))
        .group(|g| {
            g.name("General")
                .command(|| Command::new("ping", ping as CommandCodeFn))
                .command(|| Command::new("command_counter", command_counter as CommandCodeFn))
        });

    // --snip--
    let handler = FrameworkWrap::new(fw, Handler);
    let handler = CacheWrap::new(EventHandlerWrap::new(handler));

    connection.run(context, handler).await?;

    Ok(())
}

// --snip--
#[command]
async fn ping(ctx: &FwContext, msg: &Message) -> CommandResult {
    msg.reply(ctx, "pong").await?;

    let data = ctx.data_lock_read().await;
    let counter = data.get::<CommandCounterKey>().unwrap();
    counter.fetch_add(1, Ordering::SeqCst);
    
    Ok(())
}

#[command]
async fn command_counter(ctx: &FwContext, msg: &Message) -> CommandResult {
    let data = ctx.data_lock_read().await;
    let counter = data.get::<CommandCounterKey>().unwrap();
    let count = counter.fetch_add(1, Ordering::SeqCst); // this is itself a command,
                                                        // so fetch previous count and add one.
    msg.reply(ctx, format!("I received {} commands since I started running", count)).await?;
    
    Ok(())
}

#[derive(Clone)]
struct Handler;

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

For the source code of this, see the framework-with-data-example.