1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742
#![feature(test)] #![feature(try_blocks)] #![allow(clippy::identity_op)] //because clippy forbides 1 << 0 in c-like enums #![allow(clippy::option_option)] //opt<opt<>> is required to properly handle nullables //! 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](https://discordapp.com/developers/applications/). //! 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](automate::Configuration) struct: //! - [Configuration::new](automate::Configuration::new): Takes the bot token as parameter. You //! can provide a hardcoded string, take it from the environment or retrieve it from a configuration //! file. //! - [Configuration::from_env](automate::Configuration::from_env): Does the same as `new` except //! it takes the bot token from the given environment variable. //! - [Configuration::register](automate::Configuration::register): Registers stateful and //! stateless listeners. //! - [Configuration::enable_logging](automate::Configuration::enable_logging) and //! [Configuration::disable_logging](automate::Configuration::disable_logging): Enable or //! disable Automate's built in logger. You can disable it and use your own logger if necessary. //! - [Configuration::level_for](automate::Configuration::level_for): Sets the minimum log level //! for a line to be printed in the console output for the given module. //! - [Configuration::intents](automate::Configuration::intents): Sets the events which will //! be sent to the bot using [intents](automate::Intent). Defaults to all events. //! - [Configuration::presence](automate::Configuration::presence): Sets the presence of the bot. //! //! The resulting configuration object can then be sent to //! [Automate::launch](automate::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. //! ``` //! # use automate::{Context, Error, listener}; //! # use automate::gateway::MessageCreateDispatch; //! # //! #[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: //! - [ReadyDispatch](automate::gateway::ReadyDispatch): called right after the connection with //! discord is established. Provides data about current guilds, DMs and the bot user account. //! - [ChannelCreateDispatch](automate::gateway::ChannelCreateDispatch): a channel (guild channel //! or DM) was created. //! - [ChannelUpdateDispatch](automate::gateway::ChannelUpdateDispatch): a channel (guild channel //! or DM) was updated. //! - [ChannelDeleteDispatch](automate::gateway::ChannelDeleteDispatch): a channel (guild channel //! or DM) was deleted. //! - [ChannelPinsUpdateDispatch](automate::gateway::ChannelPinsUpdateDispatch): a message was //! pinned or unpinned. //! - [GuildCreateDispatch](automate::gateway::GuildCreateDispatch): a guild was created, became //! available or the bot was added to a guild. //! - [GuildUpdateDispatch](automate::gateway::GuildUpdateDispatch): a guild was updated. //! - [GuildDeleteDispatch](automate::gateway::GuildDeleteDispatch): a guild was deleted, became //! unavailable or the bot was removed from the guild. //! - [GuildBanAddDispatch](automate::gateway::GuildBanAddDispatch): a user was banned from a guild. //! - [GuildBanRemoveDispatch](automate::gateway::GuildBanRemoveDispatch): a user was unbanned //! from a guild. //! - [GuildEmojisUpdateDispatch](automate::gateway::GuildEmojisUpdateDispatch): the emojis of a //! guild were updated. //! - [GuildIntegrationsUpdateDispatch](automate::gateway::GuildIntegrationsUpdateDispatch): //! the integration of a guild was updated. //! - [GuildMemberAddDispatch](automate::gateway::GuildMemberAddDispatch): a user joined a guild. //! - [GuildMemberUpdateDispatch](automate::gateway::GuildMemberUpdateDispatch): a guild member was updated. //! - [GuildMemberRemoveDispatch](automate::gateway::GuildMemberRemoveDispatch): a user was removed from a guild. //! - [GuildMembersChunkDispatch](automate::gateway::GuildMembersChunkDispatch): response to a //! request guild members (not yet implemented). //! - [GuildRoleCreateDispatch](automate::gateway::GuildRoleCreateDispatch): a role was created. //! - [GuildRoleUpdateDispatch](automate::gateway::GuildRoleUpdateDispatch): a role was updated. //! - [GuildRoleDeleteDispatch](automate::gateway::GuildRoleDeleteDispatch): a role was deleted. //! - [InviteCreateDispatch](automate::gateway::InviteCreateDispatch): an invite to a channel was created. //! - [InviteDeleteDispatch](automate::gateway::InviteDeleteDispatch): an invited to a channel was deleted. //! - [MessageCreateDispatch](automate::gateway::MessageCreateDispatch): a message was created //! - [MessageUpdateDispatch](automate::gateway::MessageUpdateDispatch): a message updated. //! - [MessageDeleteDispatch](automate::gateway::MessageDeleteDispatch): a message was deleted. //! - [MessageDeleteBulkDispatch](automate::gateway::MessageDeleteBulkDispatch): multiple messages //! were deleted at once. //! - [MessageReactionAddDispatch](automate::gateway::MessageReactionAddDispatch): a user reacted to a message. //! - [MessageReactionRemoveDispatch](automate::gateway::MessageReactionRemoveDispatch): a user's //! reaction was removed from a message. //! - [MessageReactionRemoveAllDispatch](automate::gateway::MessageReactionRemoveAllDispatch): all //! reactions were explicitly removed from a message. //! - [MessageReactionRemoveEmojiDispatch](automate::gateway::MessageReactionRemoveEmojiDispatch): //! all reactions for a given emoji were explicitly removed from a message. //! - [PresenceUpdateDispatch](automate::gateway::PresenceUpdateDispatch): user was updated. //! - [TypingStartDispatch](automate::gateway::TypingStartDispatch): user started typing in a channel. //! - [UserUpdateDispatch](automate::gateway::UserUpdateDispatch): properties about the user changed. //! - [VoiceStateUpdateDispatch](automate::gateway::VoiceStateUpdateDispatch): a user joined, left, //! or moved a voice channel. //! - [VoiceServerUpdateDispatch](automate::gateway::VoiceServerUpdateDispatch): guild's voice //! server was updated. //! - [WebhooksUpdateDispatch](automate::gateway::WebhooksUpdateDispatch): guild channel webhook //! was created, update, or deleted. //! //! A listener function can be registered in the library by sending the name of the function to the //! [Configuration::register](automate::Configuration::register) method using the `stateless!` macro: //! ``` //! # use automate::{stateless, listener, Configuration, Context, Error}; //! # use automate::gateway::MessageCreateDispatch; //! # //! # #[listener] //! # async fn print_hello(ctx: &Context, data: &MessageCreateDispatch) -> Result<(), Error> { //! # println!("Hello!"); //! # Ok(()) //! # } //! # //! Configuration::from_env("DISCORD_API_TOKEN") //! .register(stateless!(print_hello)); //! ``` //! //! More advanced examples can be found in the //! [examples](https://github.com/mbenoukaiss/automate/tree/master/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](automate::events::State) trait. //! Structs containing stateful listeners must do 3 things: //! - Derive the [State](automate::events::State) trait which can be done automatically using //! the `#[derive(State)]` derive macro. //! - Implement [Clone](std::clone::Clone) since they need to be cloned to be used between //! different shards and sessions. //! - Implement the [Initializable](automate::events::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](automate::Configuration::register) method using the `stateful!` macro. //! ``` //! # #[macro_use] extern crate automate; //! # //! # use automate::{methods, stateful, Context, Error, Snowflake, Configuration}; //! # use automate::events::{Initializable, StatefulListener}; //! # use automate::gateway::MessageCreateDispatch; //! # use std::collections::HashMap; //! # //! # #[derive(State, Default, Clone)] //! # struct MessageCounter; //! # //! # impl Initializable for MessageCounter { //! # fn initialize() -> Vec<StatefulListener<Self>> { //! # methods!(MessageCounter) //! # } //! # } //! # //! 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](automate::Context::storage) //! and [Context::storage_mut](automate::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: //! - [Guilds](automate::gateway::Guild) //! - [Channels](automate::gateway::Channel) //! - [Users](automate::gateway::User) //! //! ``` //! # use automate::listener; //! 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](automate::storage::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](automate::storage::Storage). //! //! See [examples/levels.rs](https://github.com/mbenoukaiss/automate/blob/master/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](automate::ShardManager) //! struct. However, you will not need to use the [ShardManager](automate::ShardManager) directly //! in most cases since [Automate::launch](automate::Automate::launch) will automatically //! create as many shards as Discord recommends. //! //! The reasons you would need to use the [ShardManager](automate::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](automate::gateway::models). //! //! 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](std::option::Option) enum, but optional //! nullable are wrapped in a double [Option](std::option::Option)s 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 //! ```no_run //! 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); //! ``` //! extern crate self as automate; extern crate test; #[macro_use] extern crate automate_derive; #[macro_use] pub extern crate log; #[macro_use] extern crate serde; extern crate tokio_tungstenite as tktungstenite; pub mod events; pub mod http; pub mod encode; pub mod gateway; pub mod sharding; #[cfg(feature = "storage")] pub mod storage; mod snowflake; mod macros; mod errors; mod logger; pub use automate_derive::listener; /// Derive macro for a state struct. /// /// Stateful listeners (methods annotated with ̀#[listener]`) /// need a bit of extra code to work properly. The necessary /// code is generated by this derive macro. /// It implements the [State](automate::events::State) /// in order to relay the events to the methods annotated /// with `#[listener]` pub use automate_derive::State; /// Derive macro for a stored struct. /// /// Simply implements the Stored trait on the given type. /// If not specified, the storage will be assumed to be /// the name of the struct concatenated with `Storage`. /// So a stored `Count` struct would define its storage /// to be `CountStorage`. /// /// It is possible to change the storage struct by /// using the storage helper attribute like this : /// ``` /// use automate::{Stored, Storage}; /// /// #[derive(Stored)] /// #[storage(Counter)] //the storage is now the `Counter` struct /// struct Count(i32); /// /// #[derive(Storage)] /// struct Counter; /// ``` #[cfg(feature = "storage")] pub use automate_derive::Stored; /// Derive macro for a storage struct. #[cfg(feature = "storage")] pub use automate_derive::Storage; /// Parses a list of stateless function listeners before sending them /// to the [Configuration::register](automate::Configuration::register) method. pub use automate_derive::stateless; /// Parses a list of method listeners in the initialize method /// of a state struct. /// /// Input should be the name of the struct followed by a colon /// and a comma-separated list of the listener functions that /// should be registered. /// /// ``` /// use automate::{methods, listener, Context, Error}; /// use automate::events::{Initializable, StatefulListener}; /// use automate::gateway::MessageCreateDispatch; /// /// struct MessageCounter; /// /// impl Initializable for MessageCounter { /// fn initialize() -> Vec<StatefulListener<Self>> { /// methods!(MessageCounter: say_hello, say_bye) /// } /// } /// /// impl MessageCounter { /// #[listener] /// async fn say_hello(&mut self, ctx: &Context, data: &MessageCreateDispatch) -> Result<(), Error> { /// println!("Hello!"); /// Ok(()) /// } /// /// #[listener] /// async fn say_bye(&mut self, ctx: &Context, data: &MessageCreateDispatch) -> Result<(), Error> { /// println!("Bye"); /// Ok(()) /// } /// } /// ``` pub use automate_derive::methods; /// Used internally for procedural macros, don't /// rely on it's presence and import it manually in /// your `Cargo.toml` instead #[doc(no_inline)] pub use async_trait::async_trait; /// Used internally for procedural macros, don't /// rely on it's presence and import it manually in /// your `Cargo.toml` instead #[doc(no_inline)] pub use chrono; /// Used internally for procedural macros, don't /// rely on it's presence and import it manually in /// your `Cargo.toml` instead #[doc(no_inline)] pub use lazy_static; #[doc(no_inline)] pub use tokio; #[doc(inline)] pub use http::HttpAPI; #[doc(inline)] pub use gateway::Context; #[doc(inline)] pub use gateway::Intent; pub use sharding::ShardManager; pub use snowflake::{Identifiable, Snowflake}; pub use errors::Error; use events::*; use tokio::runtime::Builder; use std::env; use log::LevelFilter; use std::future::Future; use crate::gateway::UpdateStatus; #[cfg(feature = "storage")] use crate::storage::StorageContainer; /// Allows specifying API token, registering /// stateful and stateless listeners, stating /// the shard id, intents and configuring logger. /// /// The resulting object can then be used in /// [Automate::launch](automate::Automate::launch) /// or the [ShardManager](automate::ShardManager). /// /// # Example /// ``` /// # use automate::{stateless, listener, Context, Configuration, Error}; /// # use automate::gateway::MessageCreateDispatch; /// /// # #[listener] /// # async fn kick_spammer(ctx: &Context, data: &MessageCreateDispatch) -> Result<(), Error> { /// # Ok(()) /// # } /// # /// let config = Configuration::from_env("DISCORD_API_TOKEN") /// .disable_logging() /// .register(stateless!(kick_spammer)); /// ``` #[derive(Clone)] pub struct Configuration { shard_id: Option<u32>, total_shards: Option<u32>, token: String, logging: bool, log_levels: Vec<(String, LevelFilter)>, listeners: ListenerContainer, #[cfg(feature = "storage")] storages: StorageContainer, intents: Option<u32>, member_threshold: Option<u32>, presence: Option<UpdateStatus>, guild_subscriptions: Option<bool>, collector_period: u64 } impl Configuration { /// Creates a configuration with the specified /// API token. /// /// This configuration will by default have /// logging enabled and outputting all logs with /// a level higher or equal to `LevelFiler::Info` pub fn new<S: Into<String>>(token: S) -> Configuration { let mut default_levels = Vec::new(); default_levels.push((String::from("automate"), LevelFilter::Info)); Configuration { shard_id: None, total_shards: None, token: token.into(), logging: true, log_levels: default_levels, listeners: ListenerContainer::default(), #[cfg(feature = "storage")] storages: StorageContainer::for_initialization(), intents: None, member_threshold: None, presence: None, guild_subscriptions: None, collector_period: 3600 } } /// Creates a configuration by taking the API /// token from the specified environment variable. /// The environment variable is retrieved using /// [env::var](std::env::var) and thus needs to /// be specified at runtime and not compile time. /// /// This configuration will by default have /// logging enabled and outputting all logs with /// a level higher or equal to `LevelFiler::Info` pub fn from_env<S: Into<String>>(env: S) -> Configuration { Configuration::new(env::var(&env.into()).expect("API token not found")) } /// Sets the shard id of this configuration and /// the total amount of shards. /// /// If using the [ShardManager](automate::ShardManager) /// or [Automate::launch](automate::Automate::launch) to /// launch the bot, you should not use this function /// since it is done automatically. pub fn shard(&mut self, shard_id: u32, total_shards: u32) -> &mut Self { self.shard_id = Some(shard_id); self.total_shards = Some(total_shards); self } /// Sets the API token. pub fn token<S: Into<String>>(mut self, token: S) -> Self { self.token = token.into(); self } /// Enables logging. Logger is enabled by default. pub fn enable_logging(mut self) -> Self { self.logging = true; self } /// Disables logging. pub fn disable_logging(mut self) -> Self { self.logging = false; self } /// Sets the minimum log level for the given module. /// /// By default, automate will be set to /// [LevelFilter::Info](log::LevelFilter::Info) and its /// dependencies won't log anything. pub fn level_for<S: Into<String>>(mut self, module: S, min: LevelFilter) -> Self { let mut module = module.into().replace('-', "_"); module.push_str("::"); let existing = self.log_levels.iter() .enumerate() .filter(|(_, (m, _))| *m == module) .map(|(i, _)| i) .next(); if let Some(existing) = existing { self.log_levels.remove(existing); } self.log_levels.push((module, min)); self } /// Registers a listener state or a stateless /// listener function with the `̀#[listener]` attribute. pub fn register(mut self, listeners: Vec<ListenerType>) -> Self { self.listeners.register(listeners); self } /// Registers a function that initializes a storage by either calling /// [StorageContainer::initialize](automate::storage::StorageContainer) /// which accepts an existing storage or /// [StorageContainer::write](automate::storage::StorageContainer) /// which creates an empty storage and calls the provided callback function. #[cfg(feature = "storage")] pub fn add_initializer<F: Fn(&mut StorageContainer) + Send + Sync + 'static>(mut self, initializer: F) -> Self { self.storages.add_initializer::<F>(initializer); self } /// [Intents](automate::Intent) are a system to help you /// lower the amount of data you need to process by /// specifying the events Discord should relay to the library. /// /// An [Intents](automate::Intent) concerns one or more /// event. By default, intents are not specified thus the bot /// is subscribed to all events. /// /// When specifying a single [], you must explicitly cast /// the intent to u32. When specifying multiple intents, /// you can aggregate them using the bitwise or operator. /// /// # Example /// The bot in the following example will only receive events /// about message creation, update and deletion and when a /// user starts typing in a guild channel: /// ``` /// use automate::{Configuration, Intent::*}; /// /// Configuration::from_env("DISCORD_API_TOKEN") /// .intents(GuildMessages | GuildMessageTyping); /// ``` /// /// If you want to only listen to one intent type, you must /// explicitly cast the intent to u32 like the following example /// which only listens to events about guild members: /// ``` /// use automate::{Configuration, Intent::*}; /// /// Configuration::from_env("DISCORD_API_TOKEN") /// .intents(GuildMembers as u32); /// ``` pub fn intents(mut self, intents: u32) -> Self { self.intents = Some(intents); self } /// Number of members where the gateway will stop sending offline members /// in the guild member list. The value must be between 50 and 250. /// /// If not set, the value will default to 50. pub fn member_threshold(mut self, threshold: u32) -> Self { self.member_threshold = Some(threshold); self } /// Sets the presence of the bot. /// /// This can later be modified using the /// [update_status](automate::Context::update_status) /// gateway command. pub fn presence(mut self, presence: UpdateStatus) -> Self { self.presence = Some(presence); self } /// Enables dispatching of guild subscription events /// (presence and typing events). /// /// Defaults to true. pub fn guild_subscriptions(mut self, enabled: bool) -> Self { self.guild_subscriptions = Some(enabled); self } /// Sets the bucket collector period in seconds. Defaults /// to one hour. /// /// A bucket is a structure specifying the state of the /// rate-limit of a specified endpoint. It contains the /// amount of remaining API calls for the endpoint and /// the time at which it will reset. /// /// After each HTTP API call and in order to not reach the /// rate-limit set by Discord, the library keeps a hashmap /// associating the routes to their bucket. However, when /// these buckets reset, it is not necessary to keep them /// in memory anymore. /// Keeping buckets as long as the program runs would /// gradually use more and more memory as the bot joins /// and leaves guilds and sends DMs to users. /// /// The bucket collector was thus created to clean /// up memory every `period` seconds. /// /// For bots that are only in a few guilds and are meant to /// stay that way, it is not necessary to run the collector /// often. Once every few days will suffice. pub fn collector_period(mut self, period: u64) -> Self { self.collector_period = period; self } } /// Defines utility functions. pub struct Automate; impl Automate { /// Launches a basic bot with the given configuration /// and the amount of shards recommended by Discord. pub fn launch(config: Configuration) { Automate::block_on(async move { ShardManager::with_config(config).await .unwrap() .auto_setup() .launch().await }) } /// Creates a tokio runtime and runs the /// given future inside. pub fn block_on<F: Future>(future: F) -> F::Output { let mut runtime = Builder::new() .threaded_scheduler() .enable_all() .build() .unwrap(); runtime.block_on(future) } }