diff options
| author | Austin Hellyer <[email protected]> | 2016-11-29 23:33:59 -0800 |
|---|---|---|
| committer | Austin Hellyer <[email protected]> | 2016-11-29 23:33:59 -0800 |
| commit | efad058f596c9df717774cb2e9dafc0035a8df9c (patch) | |
| tree | ef54b7d65841fb63609a2b5135727922f2d8f81a | |
| parent | Clean up the codebase (diff) | |
| download | serenity-efad058f596c9df717774cb2e9dafc0035a8df9c.tar.xz serenity-efad058f596c9df717774cb2e9dafc0035a8df9c.zip | |
Add documentation for examples
The examples include a README located in `examples/README.md`, which
contains instructions for running these examples.
They act as a simple form of tutorial to the library, without getting
into too many details.
| -rw-r--r-- | README.md | 74 | ||||
| -rw-r--r-- | examples/01_basic_ping_bot/src/main.rs | 31 | ||||
| -rw-r--r-- | examples/02_transparent_guild_sharding/src/main.rs | 11 | ||||
| -rw-r--r-- | examples/03_struct_utilities/Cargo.toml | 1 | ||||
| -rw-r--r-- | examples/03_struct_utilities/src/main.rs | 16 | ||||
| -rw-r--r-- | examples/04_message_builder/src/main.rs | 13 | ||||
| -rw-r--r-- | examples/05_user_login/src/main.rs | 12 | ||||
| -rw-r--r-- | examples/06_command_framework/Cargo.toml | 1 | ||||
| -rw-r--r-- | examples/06_command_framework/src/main.rs | 85 | ||||
| -rw-r--r-- | examples/07_voice/Cargo.toml | 1 | ||||
| -rw-r--r-- | examples/07_voice/src/main.rs | 2 | ||||
| -rw-r--r-- | examples/README.md | 28 | ||||
| -rw-r--r-- | src/ext/cache/mod.rs | 6 | ||||
| -rw-r--r-- | src/lib.rs | 62 |
14 files changed, 298 insertions, 45 deletions
@@ -18,28 +18,70 @@ when a [`Event::MessageCreate`] is received. Each handler is given a [`Context`], giving information about the event. See the [client's module-level documentation]. -The [`Connection`] is transparently handled by the library, removing +The [`Shard`] is transparently handled by the library, removing unnecessary complexity. Sharded connections are automatically handled for -you. See the [Connection's documentation][`Connection`] for more -information. +you. See the [gateway's documentation][gateway docs] for more information. -A [`State`] is also provided for you. This will be updated automatically for +A [`Cache`] is also provided for you. This will be updated automatically for you as data is received from the Discord API via events. When calling a -method on a [`Context`], the state will first be searched for relevant data +method on a [`Context`], the cache will first be searched for relevant data to avoid unnecessary HTTP requests to the Discord API. For more information, -see the [state's module-level documentation][state docs]. +see the [cache's module-level documentation][cache docs]. Note that - although this documentation will try to be as up-to-date and accurate as possible - Discord hosts [official documentation][discord docs]. If you need to be sure that some information piece is accurate, refer to their docs. +# Features + +Features can be enabled or disabled by configuring the library through +Cargo.toml: + +```toml +[dependencies.serenity] +git = "https://github.com/zeyla/serenity.rs.git" +default-features = false +features = ["pick", "your", "feature", "names", "here"] +``` + +The following is a full list of features: + +- **cache**: The cache will store information about guilds, channels, users, and +other data, to avoid performing REST requests. If you are low on RAM, do not +enable this; +- **framework**: Enables the framework, which is a utility to allow simple +command parsing, before/after command execution, prefix setting, and more; +- **methods**: Enables compilation of extra methods on struct implementations, +such as `Message::delete()`, `Message::reply()`, `Guild::edit()`, and more. +Without this enabled, requests will need to go through the [`Context`] or +[`rest`] module, which are slightly less efficient from a development +standpoint, and do not automatically perform permission checking; +- **voice**: Enables compilation of voice support, so that voice channels can be +connected to and audio can be sent/received. + # Dependencies Serenity requires the following dependencies: - openssl +### Voice + +The following dependencies all require the **voice** feature to be enabled in +your Cargo.toml: + +- libsodium (Arch: `community/libsodium`) +- opus (Arch: `extra/opus`) + +Voice+ffmpeg: + +- ffmpeg (Arch: `extra/ffmpeg`) + +Voice+youtube-dl: + +- youtube-dl (Arch: `community/youtube-dl`) + # Example Bot A basic ping-pong bot looks like: @@ -59,20 +101,28 @@ fn main() { let _ = message.reply("Pong!"); })); - // start listening for events by starting a connection + // start listening for events by starting a single shard let _ = client.start(); } ``` +### Full Examples + +Full examples, detailing and explaining usage of the basic functionality of the +library, can be found in the [`examples`] directory. + +[`Cache`]: https://serenity.zey.moe/serenity/ext/cache/struct.Cache.html [`Client::login_bot`]: https://serenity.zey.moe/serenity/client/struct.Client.html#method.login_bot [`Client::login_user`]: https://serenity.zey.moe/serenity/client/struct.Client.html#method.login_user [`Client::on_message`]: https://serenity.zey.moe/serenity/client/struct.Client.html#method.on_message -[`validate_token`]: https://serenity.zey.moe/serenity/client/fn.validate_token.html -[`Connection`]: https://serenity.zey.moe/serenity/client/struct.Connection.html +[`Shard`]: https://serenity.zey.moe/serenity/client/gateway/struct.Shard.html [`Context`]: https://serenity.zey.moe/serenity/client/struct.Context.html [`Event`]: https://serenity.zey.moe/serenity/model/enum.Event.html -[`Event::MessageCreate`]: https://serenity.zey.moe/serenity/model/enum.Event.html#MessageCreate.v -[`State`]: https://serenity.zey.moe/serenity/ext/state/struct.State.html +[`Event::MessageCreate`]: https://serenity.zey.moe/serenity/model/enum.Event.html#variant.MessageCreate +[`examples`]: https://github.com/zeyla/serenity.rs/blob/master/examples +[`rest`]: https://serenity.zey.moe/serenity/client/rest/index.html +[`validate_token`]: https://serenity.zey.moe/serenity/client/fn.validate_token.html +[cache docs]: https://serenity.zey.moe/serenity/ext/cache/index.html [ci]: https://travis-ci.org/zeyla/serenity.rs [ci-badge]: https://travis-ci.org/zeyla/serenity.rs.svg?branch=master [contribs]: https://img.shields.io/github/contributors/zeyla/serenity.rs.svg @@ -84,6 +134,6 @@ fn main() { [docs]: https://serenity.zey.moe/ [docs-badge]: https://img.shields.io/badge/docs-online-5023dd.svg [examples]: https://github.com/zeyla/serenity.rs/tree/master/examples +[gateway docs]: https://serenity.zey.moe/serenity/client/gateway/index.html [license]: https://opensource.org/licenses/ISC [license-badge]: https://img.shields.io/badge/license-ISC-blue.svg -[state docs]: https://serenity.zey.moe/serenity/ext/state/index.html diff --git a/examples/01_basic_ping_bot/src/main.rs b/examples/01_basic_ping_bot/src/main.rs index 8db4614..e00263b 100644 --- a/examples/01_basic_ping_bot/src/main.rs +++ b/examples/01_basic_ping_bot/src/main.rs @@ -7,17 +7,44 @@ fn main() { // Configure the client with your Discord bot token in the environment. let token = env::var("DISCORD_TOKEN") .expect("Expected a token in the environment"); + + // Create a new instance of the Client, logging in as a bot. This will + // automatically prepend your bot token with "Bot ", which is a requirement + // by Discord for bot users. let mut client = Client::login_bot(&token); + // Set a handler for the `on_message` event - so that whenever a new message + // is received - the closure (or function) passed will be called. + // + // Event handlers are dispatched through multi-threading, and so multiple + // of a single event can be dispatched simultaneously. client.on_message(|context, message| { if message.content == "!ping" { - let _ = context.say("Pong!"); + // Sending a message can fail, due to a network error, an + // authentication error, or lack of permissions to post in the + // channel, so log to stdout when some error happens, with a + // description of it. + if let Err(why) = context.say("Pong!") { + println!("Error sending message: {:?}", why); + } } }); + // Set a handler to be called on the `on_ready` event. This is called when a + // shard is booted, and a READY payload is sent by Discord. This payload + // contains data like the current user's guild Ids, current user data, + // relationships, and more. + // + // In this case, just print what the current user's username is. client.on_ready(|_context, ready| { println!("{} is connected!", ready.user.name); }); - let _ = client.start(); + // Finally, start a single shard, and start listening to events. + // + // Shards will automatically attempt to reconnect, and will perform + // exponential backoff until it reconnects. + if let Err(why) = client.start() { + println!("Client error: {:?}", why); + } } diff --git a/examples/02_transparent_guild_sharding/src/main.rs b/examples/02_transparent_guild_sharding/src/main.rs index 19b61d0..1e3446f 100644 --- a/examples/02_transparent_guild_sharding/src/main.rs +++ b/examples/02_transparent_guild_sharding/src/main.rs @@ -29,6 +29,9 @@ fn main() { client.on_message(|context, message| { if message.content == "!ping" { + // The current shard needs to be unlocked so it can be read from, as + // multiple threads may otherwise attempt to read from or mutate it + // concurrently. { let shard = context.shard.lock().unwrap(); @@ -37,7 +40,9 @@ fn main() { } } - let _ = context.say("Pong!"); + if let Err(why) = context.say("Pong!") { + println!("Error sending message: {:?}", why); + } } }); @@ -51,5 +56,7 @@ fn main() { // // This means if you have 5 shards, your total shard count will be 5, while // each shard will be assigned numbers 0 through 4. - let _ = client.start_shards(2); + if let Err(why) = client.start_shards(2) { + println!("Client error: {:?}", why); + } } diff --git a/examples/03_struct_utilities/Cargo.toml b/examples/03_struct_utilities/Cargo.toml index 8dc013c..2153419 100644 --- a/examples/03_struct_utilities/Cargo.toml +++ b/examples/03_struct_utilities/Cargo.toml @@ -5,3 +5,4 @@ authors = ["my name <[email protected]>"] [dependencies] serenity = { git = "https://github.com/zeyla/serenity.rs.git" } +features = ["methods"] diff --git a/examples/03_struct_utilities/src/main.rs b/examples/03_struct_utilities/src/main.rs index 6bf436d..8334b71 100644 --- a/examples/03_struct_utilities/src/main.rs +++ b/examples/03_struct_utilities/src/main.rs @@ -21,7 +21,17 @@ fn main() { client.on_message(|_context, message| { if message.content == "!messageme" { - let _ = message.author.dm("Hello!"); + // If the `methods` feature is enabled, then model structs will + // have a lot of useful methods implemented, to avoid using an + // often otherwise bulky Context, or even much lower-level `rest` + // method. + // + // In this case, you can direct message a User directly by simply + // calling a method on its instance, with the content of the + // message. + if let Err(why) = message.author.dm("Hello!") { + println!("Error when direct messaging user: {:?}", why); + } } }); @@ -29,5 +39,7 @@ fn main() { println!("{} is connected!", ready.user.name); }); - let _ = client.start(); + if let Err(why) = client.start() { + println!("Client error: {:?}", why); + } } diff --git a/examples/04_message_builder/src/main.rs b/examples/04_message_builder/src/main.rs index 41f390b..2712fe6 100644 --- a/examples/04_message_builder/src/main.rs +++ b/examples/04_message_builder/src/main.rs @@ -21,15 +21,20 @@ fn main() { }, }; + // The message builder allows for creating a message by mentioning + // users dynamically, pushing "safe" versions of content (such as + // bolding normalized content), displaying emojis, and more. let response = MessageBuilder::new() .push("User ") - .mention(message.author) + .push_bold_safe(&message.author.name) .push(" used the 'ping' command in the ") .mention(channel) .push(" channel") .build(); - let _ = context.say(&response); + if let Err(why) = context.say(&response) { + println!("Error sending message: {:?}", why); + } } }); @@ -37,5 +42,7 @@ fn main() { println!("{} is connected!", ready.user.name); }); - let _ = client.start(); + if let Err(why) = client.start() { + println!("Client error: {:?}", why); + } } diff --git a/examples/05_user_login/src/main.rs b/examples/05_user_login/src/main.rs index bbc9303..0328042 100644 --- a/examples/05_user_login/src/main.rs +++ b/examples/05_user_login/src/main.rs @@ -7,11 +7,21 @@ fn main() { // Configure the client with your Discord bot token in the environment. let token = env::var("DISCORD_TOKEN") .expect("Expected a token in the environment"); + + // Logging in is essentially equivilant to logging in as a user. + // + // The primary difference is that by using `login_user`, the "Bot " string + // is not prefixed to the token. + // + // Additionally, the Client will now know that you are a user, and will + // disallow you from performing bot-only commands. let mut client = Client::login_user(&token); client.on_ready(|_context, ready| { println!("{} is connected!", ready.user.name); }); - println!("{:?}", client.start()); + if let Err(why) = client.start() { + println!("Client error: {:?}", why); + } } diff --git a/examples/06_command_framework/Cargo.toml b/examples/06_command_framework/Cargo.toml index fa93475..26cfca9 100644 --- a/examples/06_command_framework/Cargo.toml +++ b/examples/06_command_framework/Cargo.toml @@ -5,3 +5,4 @@ authors = ["my name <[email protected]>"] [dependencies] serenity = { git = "https://github.com/zeyla/serenity.rs.git" } +features = ["framework", "methods"] diff --git a/examples/06_command_framework/src/main.rs b/examples/06_command_framework/src/main.rs index 95d832c..b399dff 100644 --- a/examples/06_command_framework/src/main.rs +++ b/examples/06_command_framework/src/main.rs @@ -30,18 +30,34 @@ fn main() { // "~about" // "~emoji cat" // "~emoji dog" + // "~multiply" // "~ping" - // "~some complex command" + // "~some long command" client.with_framework(|f| f + // Configures the client, allowing for options to mutate how the + // framework functions. + // + // Refer to the documentation for + // `serenity::ext::framework::Configuration` for all available + // configurations. .configure(|c| c .allow_whitespace(true) .on_mention(true) .prefix("~")) + // Set a function to be called prior to each command execution. This + // provides the context of the command, the message that was received, + // and the full name of the command that will be called. + // + // You can not use this to determine whether a command should be + // executed. Instead, `set_check` is provided to give you this + // functionality. .before(|_context, message, command_name| { println!("Got command '{}' by user '{}'", command_name, message.author.name); }) + // Very similar to `before`, except this will be called directly _after_ + // command execution. .after(|_context, _message, command_name| { println!("Processed command '{}'", command_name) }) @@ -50,39 +66,82 @@ fn main() { .on("emoji cat", cat_command) .on("emoji dog", dog_command) .on("multiply", multiply) - .on("some complex command", some_complex_command) - // Commands can be in closure-form as well + .on("some long command", some_long_command) + // Commands can be in closure-form as well. + // + // This is not recommended though, as any closure larger than a couple + // lines will look ugly. .on("about", |context, _message, _args| drop(context.say("A test bot")))); - let _ = client.start(); + if let Err(why) = client.start() { + println!("Client error: {:?}", why); + } } +// Commands can be created via the `command!` macro, to avoid manually typing +// type annotations. +// +// This may bring more features available for commands in the future. See the +// "multiply" command below for some of the power that the `command!` macro can +// bring. command!(cat_command(context, _msg, _arg) { - let _ = context.say(":cat:"); + if let Err(why) = context.say(":cat:") { + println!("Eror sending message: {:?}", why); + } }); fn dog_command(context: &Context, _msg: &Message, _args: Vec<String>) { - let _ = context.say(":dog:"); + if let Err(why) = context.say(":dog:") { + println!("Error sending message: {:?}", why); + } } -// `Message::reply` is only compiled if the `methods` feature flag is enabled. fn ping_command(_context: &Context, message: &Message, _args: Vec<String>) { - let _ = message.reply("Pong!"); + if let Err(why) = message.reply("Pong!") { + println!("Error sending reply: {:?}", why); + } } +// A function which acts as a "check", to determine whether to call a command. +// +// In this case, this command checks to ensure you are the owner of the message +// in order for the command to be executed. If the check fails, the command is +// not called. fn owner_check(_context: &Context, message: &Message) -> bool { // Replace 7 with your ID message.author.id == 7 } -fn some_complex_command(context: &Context, _msg: &Message, args: Vec<String>) { - let _ = context.say(&format!("Arguments: {:?}", args)); +fn some_long_command(context: &Context, _msg: &Message, args: Vec<String>) { + if let Err(why) = context.say(&format!("Arguments: {:?}", args)) { + println!("Error sending message: {:?}", why); + } } +// Using the `command!` macro, commands can be created with a certain type of +// "dynamic" type checking. This is a method of requiring that the arguments +// given match the required type, and maps those arguments to the specified +// bindings. +// +// For example, the following will be correctly parsed by the macro: +// +// `~multiply 3.7 4.3` +// +// However, the following will not, as the second argument can not be an f64: +// +// `~multiply 3.7 four` +// +// Since the argument can't be converted, the command returns early. +// +// Additionally, if not enough arguments are given (e.g. `~multiply 3`), then +// the command will return early. If additional arguments are provided, they +// will be ignored. +// +// Argument type overloading is currently not supported. command!(multiply(context, _message, args, first: f64, second: f64) { let res = first * second; - let _ = context.say(&res.to_string()); - - println!("{:?}", args); + if let Err(why) = context.say(&res.to_string()) { + println!("Err sending product of {} and {}: {:?}", first, second, why); + } }); diff --git a/examples/07_voice/Cargo.toml b/examples/07_voice/Cargo.toml index d17a835..20c32bc 100644 --- a/examples/07_voice/Cargo.toml +++ b/examples/07_voice/Cargo.toml @@ -5,3 +5,4 @@ authors = ["my name <[email protected]>"] [dependencies] serenity = { git = "https://github.com/zeyla/serenity.rs.git" } +features = ["cache", "framework", "methods", "voice"] diff --git a/examples/07_voice/src/main.rs b/examples/07_voice/src/main.rs index fe02245..3870f9c 100644 --- a/examples/07_voice/src/main.rs +++ b/examples/07_voice/src/main.rs @@ -4,7 +4,7 @@ //! ```toml //! [dependencies.serenity] //! version = "*" -//! features = ["cache", "methods", "voice"] +//! features = ["cache", "framework", "methods", "voice"] //! ``` extern crate serenity; diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..04b7b89 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,28 @@ +# Serenity Examples + +The examples listed in each directory demonstrate different use cases of the +library, and increasingly show more advanced or in-depth code. + +All examples have documentation for new concepts, and try to explain any new +concepts. Examples should be completed in order, so as not to miss any +documentation. + +### Running Examples + +To run an example, you have the option of either: + +1. cloning this repository, `cd`ing into the example's directory, and then +running `cargo run` to run the example; or +2. copying the contents of the example into your local binary project +(created via `cargo new test-project --bin`) and ensuring that the contents of +the `Cargo.toml` file contains that of the example's `[dependencies]` section, +and _then_ executing `cargo run`. + +Note that all examples - by default - require an environment token of +`DISCORD_TOKEN` to be set. If you don't like environment tokens, you can +hardcode your token in. + +### Questions + +If you have any questions, feel free to submit an issue with what can be +clarified. diff --git a/src/ext/cache/mod.rs b/src/ext/cache/mod.rs index 2272178..2778d6b 100644 --- a/src/ext/cache/mod.rs +++ b/src/ext/cache/mod.rs @@ -2,7 +2,7 @@ //! data from the event is possible. //! //! This acts as a hot cache, to avoid making requests over the REST API through -//! the [`http`] module where possible. All fields are public, and do not have +//! the [`rest`] module where possible. All fields are public, and do not have //! getters, to allow you more flexibility with the stored data. However, this //! allows data to be "corrupted", and _may or may not_ cause misfunctions //! within the library. Mutate data at your own discretion. @@ -22,7 +22,7 @@ //! This allows you to save a step, by only needing to perform the //! [`Context::get_channel`] call and not need to first search through the cache //! - and if not found - _then_ perform an HTTP request through the Context or -//! [`http`] module. +//! [`rest`] module. //! //! Additionally, note that some information received through events can _not_ //! be retrieved through the REST API. This is information such as [`Role`]s in @@ -74,7 +74,7 @@ //! [`Role`]: ../../model/struct.Role.html //! [`Shard`]: ../../client/gateway/struct.Shard.html //! [`client::CACHE`]: ../../client/struct.CACHE.html -//! [`http`]: ../../client/http/index.html +//! [`rest`]: ../../client/rest/index.html use std::collections::hash_map::Entry; use std::collections::HashMap; @@ -14,10 +14,9 @@ //! [`Context`], giving information about the event. See the //! [client's module-level documentation]. //! -//! The [`Connection`] is transparently handled by the library, removing +//! The [`Shard`] is transparently handled by the library, removing //! unnecessary complexity. Sharded connections are automatically handled for -//! you. See the [Connection's documentation][`Connection`] for more -//! information. +//! you. See the [gateway's documentation][gateway docs] for more information. //! //! A [`Cache`] is also provided for you. This will be updated automatically for //! you as data is received from the Discord API via events. When calling a @@ -30,12 +29,56 @@ //! need to be sure that some information piece is sanctioned by Discord, refer //! to their own documentation. //! +//! # Features +//! +//! Features can be enabled or disabled by configuring the library through +//! Cargo.toml: +//! +//! ```toml +//! [dependencies.serenity] +//! git = "https://github.com/zeyla/serenity.rs.git" +//! default-features = false +//! features = ["pick", "your", "feature", "names", "here"] +//! ``` +//! +//! The following is a full list of features: +//! +//! - **cache**: The cache will store information about guilds, channels, users, +//! and other data, to avoid performing REST requests. If you are low on RAM, do +//! not enable this; +//! - **framework**: Enables the framework, which is a utility to allow simple +//! command parsing, before/after command execution, prefix setting, and more; +//! - **methods**: Enables compilation of extra methods on struct +//! implementations, such as `Message::delete()`, `Message::reply()`, +//! `Guild::edit()`, and more. Without this enabled, requests will need to go +//! through the [`Context`] or [`rest`] module, which are slightly less +//! efficient from a development standpoint, and do not automatically perform +//! permission checking; +//! - **voice**: Enables compilation of voice support, so that voice channels +//! can be connected to and audio can be sent/received. +//! //! # Dependencies //! //! Serenity requires the following dependencies: //! //! - openssl //! +//! ### Voice +//! +//! The following dependencies all require the **voice** feature to be enabled +//! in your Cargo.toml: +//! +//! - libsodium (Arch: `community/libsodium`) +//! - opus (Arch: `extra/opus`) +//! +//! Voice+ffmpeg: +//! +//! - ffmpeg (Arch: `extra/ffmpeg`) +//! +//! Voice+youtube-dl: +//! +//! - youtube-dl (Arch: `community/youtube-dl`) +//! //! # Example Bot //! //! A basic ping-pong bot looks like: @@ -56,24 +99,31 @@ //! let _ = message.reply("Pong!"); //! })); //! -//! // start listening for events by starting a connection +//! // start listening for events by starting a single shard //! let _ = client.start(); //! } //! ``` +//! ### Full Examples +//! +//! Full examples, detailing and explaining usage of the basic functionality of the +//! library, can be found in the [`examples`] directory. //! //! [`Cache`]: ext/cache/struct.Cache.html //! [`Client::login_bot`]: client/struct.Client.html#method.login_bot //! [`Client::login_user`]: client/struct.Client.html#method.login_user //! [`Client::on_message`]: client/struct.Client.html#method.on_message -//! [`Connection`]: client/struct.Connection.html //! [`Context`]: client/struct.Context.html //! [`Event`]: model/event/enum.Event.html //! [`Event::MessageCreate`]: model/event/enum.Event.html#variant.MessageCreate +//! [`Shard`]: client/struct.Shard.html +//! [`examples`]: https://github.com/zeyla/serenity.rs.git/blob/master/examples +//! [`rest`]: client/rest/index.html +//! [`validate_token`]: client/fn.validate_token.html //! [cache docs]: ext/cache/index.html //! [client's module-level documentation]: client/index.html //! [docs]: https://discordapp.com/developers/docs/intro //! [examples]: https://github.com/zeyla/serenity.rs/tree/master/examples -//! [`validate_token`]: client/fn.validate_token.html +//! [gateway docs]: client/gateway/index.html #![allow(doc_markdown, inline_always, unknown_lints)] #![doc(html_logo_url = "https://docs.austinhellyer.me/serenity.rs/docs_header.png")] #![warn(enum_glob_use, if_not_else)] |