[][src]Crate automate

A low level and asynchronous rust library made for interacting with Discord's API.

This library provides all the tools that will handle setting up and maintaining a connection to Discord's API in order to make a bot. Before messing with the code of this library, you first need to get a bot token on Discord's developers portal. Create a new application and add a bot to the newly created application. You can then copy the bot's token by clicking the copy button.

In order to build your bot, you must first provide it with the settings you'd like to use which is done using the Configuration struct:

The resulting configuration object can then be sent to Automate::launch which will start the bot.

Listeners

Discord sends various events through their API about messages, guild and user updates, etc. Automate will then relay these events to your bot through the listeners you will define. There are two ways to create listeners:

Stateless listeners

The easiest way to create a listener is to use a stateless listener. A stateless listener is a simple asynchronous function with the #[listener] attribute. As its name says, it doesn't have a state and thus can't save data across calls.

#[listener]
async fn print_hello(ctx: &Context, data: &MessageCreateDispatch) -> Result<(), Error> {
    println!("Hello!");
    Ok(())
}

The function you declare must take two arguments as in the example above. The first argument is the session, it provides information about the bot and all the methods allowing you to send instructions to Discord through their HTTP API. The second argument is the dispatch struct which contains all the data about the event you received. Events and thus allowed types for the second argument are:

A listener function can be registered in the library by sending the name of the function to the Configuration::register method using the stateless! macro:

Configuration::from_env("DISCORD_API_TOKEN")
        .register(stateless!(print_hello));

More advanced examples can be found in the examples folder. If you want to keep data between calls, you probably want to use a stateful listener.

It is possible to use ̀lazy_static! to store data across calls but this is probably not what you want since data in the lazy_static will be shared between shards and kept across sessions.

Stateful listeners

Stateless listeners provide a clean and quick way to setup a listener, but as stated earlier, they do not allow keeping variables between two events which is necessary for a more advanced bot.

States will not be the same across shards and they will be destroyed and recreated in the case of a disconnection that could not resume and results in the creation of a new Discord session.

Stateful listeners work in the exact same way as stateless listeners except they're declared in an impl block of a struct that derives the State trait. Structs containing stateful listeners must do 3 things:

  • Derive the State trait which can be done automatically using the #[derive(State)] derive macro.
  • Implement Clone since they need to be cloned to be used between different shards and sessions.
  • Implement the Initializable trait which defines a single function that should return all the listeners of the struct. This can be done using the methods! macro which takes the name of the struct followed by a colon and a comma-separated list of the listener methods.
#[macro_use] extern crate automate;

use automate::{Context, Error, Snowflake};
use automate::events::{Initializable, StatefulListener};
use automate::gateway::MessageCreateDispatch;
use std::collections::HashMap;

#[derive(State, Default, Clone)]
struct MessageCounter {
    messages: i32,
}
 
impl Initializable for MessageCounter {
    fn initialize() -> Vec<StatefulListener<Self>> {
        methods!(MessageCounter: count)
    }
}
 
impl MessageCounter {
    #[listener]
    async fn count(&mut self, _: &Context, data: &MessageCreateDispatch) -> Result<(), Error> {
        self.messages += 1;
        println!("A total of {} messages have been sent!", self.messages);

        Ok(())
    }
}

A state struct can be registered in the library by sending an instance of the struct to the Configuration::register method using the stateful! macro.

Configuration::from_env("DISCORD_API_TOKEN")
        .register(stateful!(MessageCounter::default()));

More advanced examples can be found in the ̀examples/counter.rs` example file.

Storage API

When receiving events, you will usually need more data than the event sends you. For example, you may need to know what the role of the user who sent a message is. This data can be fetched through the storages using the Context::storage and Context::storage_mut to fetch mutable data.

That can be achieved by fetching the data from Discord API each time you need it, but you will quickly get rate limited. That is why the storage API caches some of the data discord sends.

Caching storages

Automate creates 3 storages which you can not mutate, they only get mutated through gateway events:

use automate::{Context, Error};
use automate::gateway::{MessageCreateDispatch, User, Guild};

#[listener]
async fn greet_multiple_roles(ctx: &Context, data: &MessageCreateDispatch) -> Result<(), Error> {
    if let Some(guild) = data.0.guild_id {
        let storage = ctx.storage::<Guild>().await;
        let guild = storage.get(guild);
        let member = guild.members.get(&data.0.author.id).unwrap();

        //print hello if the user has at least 2 roles
        if member.roles.len() >= 2 {
            println!("Hello!");
        }
    }

    Ok(())
}

Custom storages

You can also create your own storages. Having your own custom storages will usually allow you to store data without using stateful listeners and in a simpler way.

In order to do that, you will need to create two structs one being the stored struct which should implement Stored and a storage struct which should keep the stored values in memory and provide ways to retrieve and inserts objects. The storage struct should implement Storage.

See examples/levels.rs for a detailed example.

Deactivating storages

The storages API is by default enabled but you might not want it because you simply do not need to cache the data sent by discord or because you do not have a lot of RAM available to run the bot. In that case, you can disable the feature by setting the default-features key to false for automate in your Cargo.toml file.

Sharding

Automate implements support for sharding through the ShardManager struct. However, you will not need to use the ShardManager directly in most cases since Automate::launch will automatically create as many shards as Discord recommends.

The reasons you would need to use the ShardManager are if you want to spread your bot across multiple servers or if you want to launch more or less shards than what Discord recommends.

Models

All the data sent by discord is deserialized into model structs and enums.

The data returned by discord can be of 4 kinds:

  • Always present
  • Nullable: Field will be included but the data can either be null or present.
  • Optional: Field will either not be included at all or present
  • Optional and nullable: The field can be present, null or not included.

Both nullable and optional are handled with Option enum, but optional nullable are wrapped in a double Options because in some cases you may need to know whether the data was not present, null or both.

For example, when editing a guild member, if you need to modify some fields but NOT the nickname (which is optional and nullable), you will set the nick field to None. But if you want to remove the nick, it needs to be set to null and you can achieve that by sending Some(None).

Examples

use automate::{listener, stateless, Error, Context, Configuration, Automate};
use automate::gateway::MessageCreateDispatch;
use automate::http::CreateMessage;

#[listener]
async fn say_hello(ctx: &Context, data: &MessageCreateDispatch) -> Result<(), Error> {
    let message = &data.0;
 
    if message.author.id != ctx.bot.id {
        let content = Some(format!("Hello {}!", message.author.username));
 
        ctx.create_message(message.channel_id, CreateMessage {
            content,
            ..Default::default()
        }).await?;
    }
 
    Ok(())
}
 
let config = Configuration::from_env("DISCORD_API_TOKEN")
    .register(stateless!(say_hello));

Automate::launch(config);

Re-exports

pub extern crate log;
pub use async_trait::async_trait;
pub use chrono;
pub use lazy_static;
pub use tokio;
pub use sharding::ShardManager;

Modules

encode

Types used by the library to transform objects into data that can be sent to and understood by Discord's API.

events

Defines all the types and macros required to make and register listeners.

gateway

Tools to interact with Discord's gateway API

http

Tools to interact with Discord's HTTP API

sharding
storage

Macros

json

Creates a JSON string associating the given keys with the given values.

map

Creates a hashmap associating the given keys to the given values.

methods
stateful

Parses a list of state structs before sending them to the Configuration::register method.

stateless

Structs

Automate

Defines utility functions.

Configuration

Allows specifying API token, registering stateful and stateless listeners, stating the shard id, intents and configuring logger.

Context

Context about the current gateway session. Provides a way to interact with Discord HTTP API by dereferencing to HttpAPI.

HttpAPI

Struct used to interact with the discord HTTP API.

Snowflake

Twitter's snowflake used by discord API to provide unique IDs for guilds, channels, users, etc. Since a snowflake is 64bits in size, it is stored as a u64 in the automate library.

Enums

Error

Represents an error that occurred while using the library.

Intent

System to help define which events discord will send to your bot. An intent is a single or a group of event types.

Traits

Identifiable

Any object that has an id

Attribute Macros

listener

Derive Macros

State
Storage
Stored