├── .cargo
└── config.toml
├── .editorconfig
├── .git-blame-ignore-revs
├── .github
├── labeler.yml
├── matchers
│ └── rust.json
└── workflows
│ ├── ci.yml
│ ├── docs.yml
│ ├── labeler.yml
│ └── lint.yml
├── .gitignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Cargo.toml
├── LICENSE.md
├── Makefile.toml
├── README.md
├── benches
└── bench_args.rs
├── build.rs
├── clippy.toml
├── command_attr
├── Cargo.toml
└── src
│ ├── attributes.rs
│ ├── consts.rs
│ ├── lib.rs
│ ├── structures.rs
│ └── util.rs
├── examples
├── Makefile.toml
├── README.md
├── e01_basic_ping_bot
│ ├── Cargo.toml
│ ├── Makefile.toml
│ └── src
│ │ └── main.rs
├── e02_transparent_guild_sharding
│ ├── Cargo.toml
│ ├── Makefile.toml
│ └── src
│ │ └── main.rs
├── e03_struct_utilities
│ ├── Cargo.toml
│ ├── Makefile.toml
│ └── src
│ │ └── main.rs
├── e04_message_builder
│ ├── Cargo.toml
│ ├── Makefile.toml
│ └── src
│ │ └── main.rs
├── e05_command_framework
│ ├── Cargo.toml
│ ├── Makefile.toml
│ └── src
│ │ └── main.rs
├── e06_sample_bot_structure
│ ├── .env.example
│ ├── Cargo.toml
│ ├── Makefile.toml
│ └── src
│ │ ├── commands
│ │ ├── math.rs
│ │ ├── meta.rs
│ │ ├── mod.rs
│ │ └── owner.rs
│ │ └── main.rs
├── e07_env_logging
│ ├── Cargo.toml
│ ├── Makefile.toml
│ └── src
│ │ └── main.rs
├── e08_shard_manager
│ ├── Cargo.toml
│ ├── Makefile.toml
│ └── src
│ │ └── main.rs
├── e09_create_message_builder
│ ├── Cargo.toml
│ ├── Makefile.toml
│ ├── ferris_eyes.png
│ └── src
│ │ └── main.rs
├── e10_collectors
│ ├── Cargo.toml
│ ├── Makefile.toml
│ └── src
│ │ └── main.rs
├── e11_gateway_intents
│ ├── Cargo.toml
│ ├── Makefile.toml
│ └── src
│ │ └── main.rs
├── e12_global_data
│ ├── Cargo.toml
│ ├── Makefile.toml
│ └── src
│ │ └── main.rs
├── e13_parallel_loops
│ ├── Cargo.toml
│ ├── Makefile.toml
│ └── src
│ │ └── main.rs
├── e14_slash_commands
│ ├── Cargo.toml
│ ├── Makefile.toml
│ ├── README.md
│ └── src
│ │ ├── commands
│ │ ├── attachmentinput.rs
│ │ ├── id.rs
│ │ ├── mod.rs
│ │ ├── modal.rs
│ │ ├── numberinput.rs
│ │ ├── ping.rs
│ │ ├── welcome.rs
│ │ └── wonderful_command.rs
│ │ └── main.rs
├── e15_simple_dashboard
│ ├── Cargo.toml
│ ├── Makefile.toml
│ └── src
│ │ └── main.rs
├── e16_sqlite_database
│ ├── .gitignore
│ ├── .sqlx
│ │ ├── query-597707a72d1ed8eab0cb48a3bef8cdb981362e089a462fa6d156b27b57468678.json
│ │ ├── query-7636fc64c882305305814ffb66676ef09a92d3f1d46021b94ded4e9c073775d1.json
│ │ ├── query-8a7bb6fe3b960d1d10bc8442bb1494f2c758dd890293c313811a8c4acb8edaeb.json
│ │ └── query-90153b8cd85a905a1d5557ad4eb190e9be4cf55d7308973d74cb180cd2323f8a.json
│ ├── Cargo.toml
│ ├── Makefile.toml
│ ├── README.md
│ ├── migrations
│ │ └── 20210906145552_initial_migration.sql
│ ├── pre-commit
│ └── src
│ │ └── main.rs
├── e17_message_components
│ ├── Cargo.toml
│ ├── Makefile.toml
│ └── src
│ │ └── main.rs
├── e18_webhook
│ ├── Cargo.toml
│ ├── Makefile.toml
│ └── src
│ │ └── main.rs
├── e19_interactions_endpoint
│ ├── Cargo.toml
│ ├── Makefile.toml
│ └── src
│ │ └── main.rs
└── testing
│ ├── Cargo.toml
│ ├── Makefile.toml
│ └── src
│ ├── main.rs
│ └── model_type_sizes.rs
├── logo.png
├── rustfmt.toml
├── src
├── builder
│ ├── add_member.rs
│ ├── bot_auth_parameters.rs
│ ├── create_allowed_mentions.rs
│ ├── create_attachment.rs
│ ├── create_channel.rs
│ ├── create_command.rs
│ ├── create_command_permission.rs
│ ├── create_components.rs
│ ├── create_embed.rs
│ ├── create_forum_post.rs
│ ├── create_forum_tag.rs
│ ├── create_interaction_response.rs
│ ├── create_interaction_response_followup.rs
│ ├── create_invite.rs
│ ├── create_message.rs
│ ├── create_poll.rs
│ ├── create_scheduled_event.rs
│ ├── create_stage_instance.rs
│ ├── create_sticker.rs
│ ├── create_thread.rs
│ ├── create_webhook.rs
│ ├── edit_automod_rule.rs
│ ├── edit_channel.rs
│ ├── edit_guild.rs
│ ├── edit_guild_welcome_screen.rs
│ ├── edit_guild_widget.rs
│ ├── edit_interaction_response.rs
│ ├── edit_member.rs
│ ├── edit_message.rs
│ ├── edit_profile.rs
│ ├── edit_role.rs
│ ├── edit_scheduled_event.rs
│ ├── edit_stage_instance.rs
│ ├── edit_sticker.rs
│ ├── edit_thread.rs
│ ├── edit_voice_state.rs
│ ├── edit_webhook.rs
│ ├── edit_webhook_message.rs
│ ├── execute_webhook.rs
│ ├── get_entitlements.rs
│ ├── get_messages.rs
│ └── mod.rs
├── cache
│ ├── cache_update.rs
│ ├── event.rs
│ ├── mod.rs
│ ├── settings.rs
│ └── wrappers.rs
├── client
│ ├── context.rs
│ ├── dispatch.rs
│ ├── error.rs
│ ├── event_handler.rs
│ └── mod.rs
├── collector.rs
├── constants.rs
├── error.rs
├── framework
│ ├── mod.rs
│ └── standard
│ │ ├── args.rs
│ │ ├── configuration.rs
│ │ ├── help_commands.rs
│ │ ├── mod.rs
│ │ ├── parse
│ │ ├── map.rs
│ │ └── mod.rs
│ │ └── structures
│ │ ├── buckets.rs
│ │ ├── check.rs
│ │ └── mod.rs
├── gateway
│ ├── bridge
│ │ ├── event.rs
│ │ ├── mod.rs
│ │ ├── shard_manager.rs
│ │ ├── shard_messenger.rs
│ │ ├── shard_queuer.rs
│ │ ├── shard_runner.rs
│ │ ├── shard_runner_message.rs
│ │ └── voice.rs
│ ├── error.rs
│ ├── mod.rs
│ ├── shard.rs
│ └── ws.rs
├── http
│ ├── client.rs
│ ├── error.rs
│ ├── mod.rs
│ ├── multipart.rs
│ ├── ratelimiting.rs
│ ├── request.rs
│ ├── routing.rs
│ └── typing.rs
├── interactions_endpoint.rs
├── internal
│ ├── macros.rs
│ ├── mod.rs
│ ├── prelude.rs
│ └── tokio.rs
├── json.rs
├── lib.rs
├── model
│ ├── application
│ │ ├── command.rs
│ │ ├── command_interaction.rs
│ │ ├── component.rs
│ │ ├── component_interaction.rs
│ │ ├── interaction.rs
│ │ ├── mod.rs
│ │ ├── modal_interaction.rs
│ │ ├── oauth.rs
│ │ └── ping_interaction.rs
│ ├── channel
│ │ ├── attachment.rs
│ │ ├── channel_id.rs
│ │ ├── embed.rs
│ │ ├── guild_channel.rs
│ │ ├── message.rs
│ │ ├── mod.rs
│ │ ├── partial_channel.rs
│ │ ├── private_channel.rs
│ │ └── reaction.rs
│ ├── colour.rs
│ ├── connection.rs
│ ├── error.rs
│ ├── event.rs
│ ├── gateway.rs
│ ├── guild
│ │ ├── audit_log
│ │ │ ├── change.rs
│ │ │ ├── mod.rs
│ │ │ └── utils.rs
│ │ ├── automod.rs
│ │ ├── emoji.rs
│ │ ├── guild_id.rs
│ │ ├── guild_preview.rs
│ │ ├── integration.rs
│ │ ├── member.rs
│ │ ├── mod.rs
│ │ ├── partial_guild.rs
│ │ ├── premium_tier.rs
│ │ ├── role.rs
│ │ ├── scheduled_event.rs
│ │ ├── system_channel.rs
│ │ └── welcome_screen.rs
│ ├── id.rs
│ ├── invite.rs
│ ├── mention.rs
│ ├── misc.rs
│ ├── mod.rs
│ ├── monetization.rs
│ ├── permissions.rs
│ ├── sticker.rs
│ ├── timestamp.rs
│ ├── user.rs
│ ├── utils.rs
│ ├── voice.rs
│ └── webhook.rs
├── prelude.rs
└── utils
│ ├── argument_convert
│ ├── _template.rs
│ ├── channel.rs
│ ├── emoji.rs
│ ├── guild.rs
│ ├── member.rs
│ ├── message.rs
│ ├── mod.rs
│ ├── role.rs
│ └── user.rs
│ ├── content_safe.rs
│ ├── custom_message.rs
│ ├── formatted_timestamp.rs
│ ├── message_builder.rs
│ ├── mod.rs
│ ├── quick_modal.rs
│ └── token.rs
├── tests
└── test_reaction.rs
└── voice-model
├── Cargo.toml
├── benches
└── de.rs
├── rustfmt.toml
└── src
├── close_code.rs
├── constants.rs
├── event
├── from.rs
├── mod.rs
└── tests.rs
├── id.rs
├── lib.rs
├── opcode.rs
├── payload.rs
├── protocol_data.rs
├── speaking_state.rs
└── util.rs
/.cargo/config.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | rustflags = ["-C", "target-cpu=haswell"]
3 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 4
6 |
--------------------------------------------------------------------------------
/.git-blame-ignore-revs:
--------------------------------------------------------------------------------
1 | # List of noisy revisions that can be ignored with git-blame(1).
2 | #
3 | # See `blame.ignoreRevsFile` in git-config(1) to enable it by default, or
4 | # use it with `--ignore-revs-file` manually with git-blame.
5 | #
6 | # To "install" it:
7 | #
8 | # git config --local blame.ignoreRevsFile .git-blame-ignore-revs
9 |
10 | # rustfmt
11 | 550030264952f0e0043b63f4582bb817ef8bbf37
12 |
13 | # Apply rustfmt
14 | dae2cb77b407044f44a7a2790d93efba3891854e
15 |
16 | # Format the repository and add a workflow for formatting and linting (#1174)
17 | 9bbb25aac4d651804286f333eb503a72d41e473b
18 |
19 | # Format imports with module granularity (#1846)
20 | 4c97810b6d25617fa0a0a4b4769deb751a17d373
21 |
22 | # Unify the rustfmt configurations
23 | aafc1d04ac04fc9691d7bf56c1b4e8eb7cfd6597
24 |
--------------------------------------------------------------------------------
/.github/labeler.yml:
--------------------------------------------------------------------------------
1 | ci:
2 | - .github/**/*
3 | command_attr:
4 | - command_attr/**/*
5 | examples:
6 | - examples/**/*
7 | builder:
8 | - src/builder/**/*
9 | cache:
10 | - src/cache/**/*
11 | client:
12 | - src/client/**/*
13 | collector:
14 | - src/collector/**/*
15 | framework:
16 | - src/framework/**/*
17 | gateway:
18 | - src/gateway/**/*
19 | http:
20 | - src/http/**/*
21 | model:
22 | - src/model/**/*
23 | utils:
24 | - src/utils/**/*
25 | voice:
26 | - voice-model/**/*
27 |
--------------------------------------------------------------------------------
/.github/matchers/rust.json:
--------------------------------------------------------------------------------
1 | {
2 | "problemMatcher": [
3 | {
4 | "owner": "cargo-common",
5 | "pattern": [
6 | {
7 | "regexp": "^(warning|warn|error)(\\[(\\S*)\\])?: (.*)$",
8 | "severity": 1,
9 | "message": 4,
10 | "code": 3
11 | },
12 | {
13 | "regexp": "^\\s+-->\\s(\\S+):(\\d+):(\\d+)$",
14 | "file": 1,
15 | "line": 2,
16 | "column": 3
17 | }
18 | ]
19 | },
20 | {
21 | "owner": "cargo-test",
22 | "pattern": [
23 | {
24 | "regexp": "^.*panicked\\s+at\\s+'(.*)',\\s+(.*):(\\d+):(\\d+)$",
25 | "message": 1,
26 | "file": 2,
27 | "line": 3,
28 | "column": 4
29 | }
30 | ]
31 | },
32 | {
33 | "owner": "cargo-fmt",
34 | "pattern": [
35 | {
36 | "regexp": "^(Diff in (\\S+)) at line (\\d+):",
37 | "message": 1,
38 | "file": 2,
39 | "line": 3
40 | }
41 | ]
42 | }
43 | ]
44 | }
45 |
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | name: Publish docs
2 |
3 | on:
4 | push:
5 | branches:
6 | - current
7 | - next
8 |
9 | env:
10 | rust_toolchain: nightly
11 |
12 | jobs:
13 | docs:
14 | name: Publish docs
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - name: Checkout sources
19 | uses: actions/checkout@v4
20 |
21 | - name: Install toolchain (${{ env.rust_toolchain }})
22 | uses: dtolnay/rust-toolchain@master
23 | with:
24 | toolchain: ${{ env.rust_toolchain }}
25 |
26 | - name: Cache
27 | uses: Swatinem/rust-cache@v2
28 |
29 | - name: Build docs
30 | env:
31 | RUSTDOCFLAGS: --cfg docsrs -D warnings
32 | run: |
33 | cargo doc --no-deps --features full
34 | cargo doc --no-deps -p command_attr
35 |
36 | - name: Prepare docs
37 | shell: bash -e -O extglob {0}
38 | run: |
39 | DIR=${GITHUB_REF/refs\/+(heads|tags)\//}
40 | mkdir -p ./docs/$DIR
41 | touch ./docs/.nojekyll
42 | echo '' > ./docs/$DIR/index.html
43 | mv ./target/doc/* ./docs/$DIR/
44 |
45 | - name: Deploy docs
46 | uses: peaceiris/actions-gh-pages@v3
47 | with:
48 | github_token: ${{ secrets.GITHUB_TOKEN }}
49 | publish_branch: gh-pages
50 | publish_dir: ./docs
51 | allow_empty_commit: false
52 | keep_files: true
53 |
--------------------------------------------------------------------------------
/.github/workflows/labeler.yml:
--------------------------------------------------------------------------------
1 | name: Labeler
2 |
3 | permissions:
4 | contents: read
5 | pull-requests: write
6 |
7 | on: [pull_request_target]
8 |
9 | jobs:
10 | label:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/labeler@v4
14 | with:
15 | repo-token: ${{ secrets.GITHUB_TOKEN }}
16 | sync-labels: true
17 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | # Copied from Twilight's Lint workflow.
2 | #
3 | # https://github.com/twilight-rs/twilight/blob/trunk/.github/workflows/lint.yml
4 | name: Lint
5 |
6 | on: [push, pull_request]
7 |
8 | jobs:
9 | clippy:
10 | name: Clippy
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - name: Checkout sources
15 | uses: actions/checkout@v4
16 |
17 | - name: Install toolchain
18 | uses: dtolnay/rust-toolchain@nightly
19 | with:
20 | components: clippy
21 |
22 | - name: Add problem matchers
23 | run: echo "::add-matcher::.github/matchers/rust.json"
24 |
25 | - name: Cache
26 | uses: Swatinem/rust-cache@v2
27 |
28 | - name: Run clippy
29 | run: cargo clippy --workspace --tests --features full -- -D warnings --cfg ignore_serenity_deprecated
30 |
31 | rustfmt:
32 | name: Format
33 | runs-on: ubuntu-latest
34 |
35 | steps:
36 | - name: Checkout sources
37 | uses: actions/checkout@v4
38 |
39 | - name: Install toolchain
40 | uses: dtolnay/rust-toolchain@nightly
41 | with:
42 | components: rustfmt
43 |
44 | - name: Add problem matchers
45 | run: echo "::add-matcher::.github/matchers/rust.json"
46 |
47 | - name: Cache
48 | uses: Swatinem/rust-cache@v2
49 |
50 | - name: Run cargo fmt
51 | run: cargo fmt --all -- --check
52 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # IDE directories and folders
2 | .vscode/
3 | .idea/
4 |
5 | # Target directory
6 | target/
7 |
8 | # Lockfile
9 | Cargo.lock
10 |
11 | # Misc
12 | rls/
13 | *.iml
14 | .env
15 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | ISC License (ISC)
2 |
3 | Copyright (c) 2016, Serenity Contributors
4 |
5 | Permission to use, copy, modify, and/or distribute this software for any purpose
6 | with or without fee is hereby granted, provided that the above copyright notice
7 | and this permission notice appear in all copies.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
11 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
13 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
14 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
15 | THIS SOFTWARE.
16 |
--------------------------------------------------------------------------------
/benches/bench_args.rs:
--------------------------------------------------------------------------------
1 | #![feature(test)]
2 |
3 | #[cfg(test)]
4 | mod benches {
5 | extern crate test;
6 |
7 | use serenity::framework::standard::{Args, Delimiter};
8 |
9 | use self::test::Bencher;
10 |
11 | #[bench]
12 | fn single_with_one_delimiter(b: &mut Bencher) {
13 | b.iter(|| {
14 | let mut args = Args::new("1,2", &[Delimiter::Single(',')]);
15 | args.single::().unwrap();
16 | })
17 | }
18 |
19 | #[bench]
20 | fn single_with_one_delimiter_and_long_string(b: &mut Bencher) {
21 | b.iter(|| {
22 | let mut args =
23 | Args::new("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", &[
24 | Delimiter::Single(','),
25 | ]);
26 | args.single::().unwrap();
27 | })
28 | }
29 |
30 | #[bench]
31 | fn single_with_three_delimiters(b: &mut Bencher) {
32 | b.iter(|| {
33 | let mut args = Args::new("1,2 @3@4 5,", &[
34 | Delimiter::Single(','),
35 | Delimiter::Single(' '),
36 | Delimiter::Single('@'),
37 | ]);
38 | args.single::().unwrap();
39 | })
40 | }
41 |
42 | #[bench]
43 | fn single_with_three_delimiters_and_long_string(b: &mut Bencher) {
44 | b.iter(|| {
45 | let mut args =
46 | Args::new("1,2 @3@4 5,1,2 @3@4 5,1,2 @3@4 5,1,2 @3@4 5,1,2 @3@4 5,1,2 @3@4 5,", &[
47 | Delimiter::Single(','),
48 | Delimiter::Single(' '),
49 | Delimiter::Single('@'),
50 | ]);
51 | args.single::().unwrap();
52 | })
53 | }
54 |
55 | #[bench]
56 | fn single_quoted_with_one_delimiter(b: &mut Bencher) {
57 | b.iter(|| {
58 | let mut args = Args::new(r#""1","2""#, &[Delimiter::Single(',')]);
59 | args.single_quoted::().unwrap();
60 | })
61 | }
62 |
63 | #[bench]
64 | fn iter_with_one_delimiter(b: &mut Bencher) {
65 | b.iter(|| {
66 | let mut args = Args::new("1,2,3,4,5,6,7,8,9,10", &[Delimiter::Single(',')]);
67 | args.iter::().collect::, _>>().unwrap();
68 | })
69 | }
70 |
71 | #[bench]
72 | fn iter_with_three_delimiters(b: &mut Bencher) {
73 | b.iter(|| {
74 | let mut args = Args::new("1-2<3,4,5,6,7<8,9,10", &[
75 | Delimiter::Single(','),
76 | Delimiter::Single('-'),
77 | Delimiter::Single('<'),
78 | ]);
79 | args.iter::().collect::, _>>().unwrap();
80 | })
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/build.rs:
--------------------------------------------------------------------------------
1 | #[cfg(all(
2 | any(feature = "http", feature = "gateway"),
3 | not(any(feature = "rustls_backend", feature = "native_tls_backend"))
4 | ))]
5 | compile_error!(
6 | "You have the `http` or `gateway` feature enabled, either the `rustls_backend` or \
7 | `native_tls_backend` feature must be selected to let Serenity use `http` or `gateway`.\n\
8 | - `rustls_backend` uses Rustls, a pure Rust TLS-implemenation.\n\
9 | - `native_tls_backend` uses SChannel on Windows, Secure Transport on macOS, and OpenSSL on \
10 | other platforms.\n\
11 | If you are unsure, go with `rustls_backend`."
12 | );
13 |
14 | fn main() {
15 | println!("cargo:rustc-check-cfg=cfg(tokio_unstable, ignore_serenity_deprecated)");
16 | }
17 |
--------------------------------------------------------------------------------
/clippy.toml:
--------------------------------------------------------------------------------
1 | cognitive-complexity-threshold = 20
2 |
--------------------------------------------------------------------------------
/command_attr/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "command_attr"
3 | version = "0.5.3"
4 | authors = ["Alex M. M. "]
5 | description = "Procedural macros for command creation for the Serenity library."
6 |
7 | documentation.workspace = true
8 | homepage.workspace = true
9 | repository.workspace = true
10 | keywords.workspace = true
11 | license.workspace = true
12 | edition.workspace = true
13 | rust-version.workspace = true
14 |
15 | [lib]
16 | proc-macro = true
17 |
18 | [dependencies]
19 | quote = "^1.0"
20 | syn = { version = "^1.0", features = ["full", "derive", "extra-traits"] }
21 | proc-macro2 = "^1.0.60"
22 |
--------------------------------------------------------------------------------
/command_attr/src/consts.rs:
--------------------------------------------------------------------------------
1 | pub mod suffixes {
2 | pub const COMMAND: &str = "COMMAND";
3 | pub const COMMAND_OPTIONS: &str = "COMMAND_OPTIONS";
4 | pub const HELP_OPTIONS: &str = "OPTIONS";
5 | pub const GROUP: &str = "GROUP";
6 | pub const GROUP_OPTIONS: &str = "GROUP_OPTIONS";
7 | pub const CHECK: &str = "CHECK";
8 | }
9 |
10 | pub use self::suffixes::*;
11 |
--------------------------------------------------------------------------------
/examples/Makefile.toml:
--------------------------------------------------------------------------------
1 | extend = "../Makefile.toml"
2 |
3 | [env]
4 | EXAMPLES_PATH = "../../examples"
5 |
6 | [tasks.build_example]
7 | command = "cargo"
8 | args = ["make", "--cwd", "./${@}", "examples_build"]
9 | dependencies = ["build"]
10 |
11 | [tasks.build_example_release]
12 | command = "cargo"
13 | args = ["make", "--cwd", "./${@}", "examples_build_release"]
14 | dependencies = ["build_release"]
15 |
16 | [tasks.run_example]
17 | command = "cargo"
18 | args = ["make", "--cwd", "./${@}", "examples_run"]
19 | dependencies = ["build"]
20 |
21 | [tasks.run_example_release]
22 | command = "cargo"
23 | args = ["make", "--cwd", "./${@}", "examples_run_release"]
24 | dependencies = ["build_release"]
25 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | # Serenity Examples
2 |
3 | The examples listed in each directory demonstrate different use cases of the
4 | library, and increasingly show more advanced or in-depth code.
5 |
6 | All examples have documentation for new concepts, and try to explain any new
7 | concepts. Examples should be completed in order, so as not to miss any
8 | documentation.
9 |
10 | If you are looking for voice examples, they can be found in [songbird's repository](https://github.com/serenity-rs/songbird/tree/current/examples/serenity) and on [lavalink-rs](https://gitlab.com/vicky5124/lavalink-rs/-/tree/master/examples)
11 |
12 | To provide a token for them to use, you need to set the `DISCORD_TOKEN`
13 | environmental variable to the Bot token.\
14 | If you don't like environment tokens, you can hardcode your token in instead.\
15 | TIP: A valid token starts with M, N or O and has 2 dots.
16 |
17 | ## Running Examples
18 |
19 | To run an example, you have various options:
20 |
21 | 1. [cargo-make](https://lib.rs/crates/cargo-make)
22 |
23 | - Install cargo-make `cargo install --force cargo-make`
24 | - Clone the repository: `git clone https://github.com/serenity-rs/serenity.git`
25 | - CD into the serenity folder: `cd serenity`
26 | - Run `cargo make 1`, where 1 is the number of the example you wish to run; these are:
27 |
28 | ```
29 | 1 => Basic Ping Bot: A bare minimum serenity application.
30 | 2 => Transparent Guild Sharding: How to use sharding and shared cache.
31 | 3 => Structure Utilities: Simple usage of the utils feature.
32 | 4 => Message Builder: A demonstration of the message builder utility, to generate messages safely.
33 | 5 => Command Framework: The main example, where it's demonstrated how to use serenity's command framework,
34 | along with most of its utilities.
35 | This example also shows how to share data between events and commands, using `Context.data`
36 | 6 => Simple Bot Structure: An example showing the recommended file structure to use.
37 | 7 => Env Logging: How to use the tracing crate along with serenity.
38 | 8 => Shard Manager: How to get started with using the shard manager.
39 | 9 => Create Message Builder: How to send embeds and files.
40 | 10 => Collectors: How to use the collectors feature to wait for messages and reactions.
41 | 11 => Gateway Intents: How to use intents to limit the events the bot will receive.
42 | 12 => Global Data: How to use the client data to share data between commands and events safely.
43 | 13 => Parallel Loops: How to run tasks in a loop with context access.
44 | Additionally, show how to send a message to a specific channel.
45 | 14 => Slash Commands: How to use the low level slash command API.
46 | 15 => Simple Dashboard: A simple dashboard to control and monitor the bot with `rillrate`.
47 | 16 => SQLite Database: How to run an embedded SQLite database alongside the bot using SQLx
48 | 17 => Message Components: How to structure and use buttons and select menus
49 | 18 => Webhook: How to construct and call a webhook
50 | ```
51 |
52 | 2. Manually running:
53 |
54 | - Clone the repository: `git clone https://github.com/serenity-rs/serenity.git`
55 | - Run the example of choice, selected using the `-p` flag: `cargo run --release -p e01_basic_ping_bot `
56 |
57 | 3. Copy Paste:
58 |
59 | - Copy the contents of the example into your local binary project\
60 | (created via `cargo new test-project --bin`)\
61 | and ensuring that the contents of the `Cargo.toml` file
62 | contains that of the example's `[dependencies]` section,\
63 | and _then_ executing `cargo run`.
64 |
65 | ### Questions
66 |
67 | If you have any questions, feel free to submit an issue with what can be
68 | clarified.
69 |
70 | ### Contributing
71 |
72 | If you add a new example also add it to the following files:
73 |
74 | - `.github/workflows/ci.yml`
75 | - `Makefile.toml`
76 | - `examples/README.md`
77 |
--------------------------------------------------------------------------------
/examples/e01_basic_ping_bot/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "e01_basic_ping_bot"
3 | version = "0.1.0"
4 | authors = ["my name "]
5 | edition = "2018"
6 |
7 | [dependencies]
8 | serenity = { path = "../../", default-features = false, features = ["client", "gateway", "rustls_backend", "model"] }
9 | tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
10 |
--------------------------------------------------------------------------------
/examples/e01_basic_ping_bot/Makefile.toml:
--------------------------------------------------------------------------------
1 | extend = "../../Makefile.toml"
2 |
3 | [tasks.examples_build]
4 | alias = "build"
5 |
6 | [tasks.examples_build_release]
7 | alias = "build_release"
8 |
9 | [tasks.examples_run]
10 | alias = "run"
11 |
12 | [tasks.examples_run_release]
13 | alias = "run_release"
14 |
--------------------------------------------------------------------------------
/examples/e01_basic_ping_bot/src/main.rs:
--------------------------------------------------------------------------------
1 | use std::env;
2 |
3 | use serenity::async_trait;
4 | use serenity::model::channel::Message;
5 | use serenity::model::gateway::Ready;
6 | use serenity::prelude::*;
7 |
8 | struct Handler;
9 |
10 | #[async_trait]
11 | impl EventHandler for Handler {
12 | // Set a handler for the `message` event. This is called whenever a new message is received.
13 | //
14 | // Event handlers are dispatched through a threadpool, and so multiple events can be
15 | // dispatched simultaneously.
16 | async fn message(&self, ctx: Context, msg: Message) {
17 | if msg.content == "!ping" {
18 | // Sending a message can fail, due to a network error, an authentication error, or lack
19 | // of permissions to post in the channel, so log to stdout when some error happens,
20 | // with a description of it.
21 | if let Err(why) = msg.channel_id.say(&ctx.http, "Pong!").await {
22 | println!("Error sending message: {why:?}");
23 | }
24 | }
25 | }
26 |
27 | // Set a handler to be called on the `ready` event. This is called when a shard is booted, and
28 | // a READY payload is sent by Discord. This payload contains data like the current user's guild
29 | // Ids, current user data, private channels, and more.
30 | //
31 | // In this case, just print what the current user's username is.
32 | async fn ready(&self, _: Context, ready: Ready) {
33 | println!("{} is connected!", ready.user.name);
34 | }
35 | }
36 |
37 | #[tokio::main]
38 | async fn main() {
39 | // Configure the client with your Discord bot token in the environment.
40 | let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment");
41 | // Set gateway intents, which decides what events the bot will be notified about
42 | let intents = GatewayIntents::GUILD_MESSAGES
43 | | GatewayIntents::DIRECT_MESSAGES
44 | | GatewayIntents::MESSAGE_CONTENT;
45 |
46 | // Create a new instance of the Client, logging in as a bot. This will automatically prepend
47 | // your bot token with "Bot ", which is a requirement by Discord for bot users.
48 | let mut client =
49 | Client::builder(&token, intents).event_handler(Handler).await.expect("Err creating client");
50 |
51 | // Finally, start a single shard, and start listening to events.
52 | //
53 | // Shards will automatically attempt to reconnect, and will perform exponential backoff until
54 | // it reconnects.
55 | if let Err(why) = client.start().await {
56 | println!("Client error: {why:?}");
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/examples/e02_transparent_guild_sharding/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "e02_transparent_guild_sharding"
3 | version = "0.1.0"
4 | authors = ["my name "]
5 | edition = "2018"
6 |
7 | [dependencies]
8 | serenity = { path = "../../", default-features = false, features = ["client", "gateway", "rustls_backend", "model"] }
9 | tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
10 |
--------------------------------------------------------------------------------
/examples/e02_transparent_guild_sharding/Makefile.toml:
--------------------------------------------------------------------------------
1 | extend = "../../Makefile.toml"
2 |
3 | [tasks.examples_build]
4 | alias = "build"
5 |
6 | [tasks.examples_build_release]
7 | alias = "build_release"
8 |
9 | [tasks.examples_run]
10 | alias = "run"
11 |
12 | [tasks.examples_run_release]
13 | alias = "run_release"
14 |
--------------------------------------------------------------------------------
/examples/e02_transparent_guild_sharding/src/main.rs:
--------------------------------------------------------------------------------
1 | use std::env;
2 |
3 | use serenity::async_trait;
4 | use serenity::model::channel::Message;
5 | use serenity::model::gateway::Ready;
6 | use serenity::prelude::*;
7 |
8 | // Serenity implements transparent sharding in a way that you do not need to handle separate
9 | // processes or connections manually.
10 | //
11 | // Transparent sharding is useful for a shared cache. Instead of having caches with duplicated
12 | // data, a shared cache means all your data can be easily accessible across all shards.
13 | //
14 | // If your bot is on many guilds - or over the maximum of 2500 - then you should/must use guild
15 | // sharding.
16 | //
17 | // This is an example file showing how guild sharding works. For this to properly be able to be
18 | // seen in effect, your bot should be in at least 2 guilds.
19 | //
20 | // Taking a scenario of 2 guilds, try saying "!ping" in one guild. It should print either "0" or
21 | // "1" in the console. Saying "!ping" in the other guild, it should cache the other number in the
22 | // console. This confirms that guild sharding works.
23 | struct Handler;
24 |
25 | #[async_trait]
26 | impl EventHandler for Handler {
27 | async fn message(&self, ctx: Context, msg: Message) {
28 | if msg.content == "!ping" {
29 | println!("Shard {}", ctx.shard_id);
30 |
31 | if let Err(why) = msg.channel_id.say(&ctx.http, "Pong!").await {
32 | println!("Error sending message: {why:?}");
33 | }
34 | }
35 | }
36 |
37 | async fn ready(&self, _: Context, ready: Ready) {
38 | println!("{} is connected!", ready.user.name);
39 | }
40 | }
41 |
42 | #[tokio::main]
43 | async fn main() {
44 | // Configure the client with your Discord bot token in the environment.
45 | let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment");
46 | let intents = GatewayIntents::GUILD_MESSAGES
47 | | GatewayIntents::DIRECT_MESSAGES
48 | | GatewayIntents::MESSAGE_CONTENT;
49 | let mut client =
50 | Client::builder(&token, intents).event_handler(Handler).await.expect("Err creating client");
51 |
52 | // The total number of shards to use. The "current shard number" of a shard - that is, the
53 | // shard it is assigned to - is indexed at 0, while the total shard count is indexed at 1.
54 | //
55 | // This means if you have 5 shards, your total shard count will be 5, while each shard will be
56 | // assigned numbers 0 through 4.
57 | if let Err(why) = client.start_shards(2).await {
58 | println!("Client error: {why:?}");
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/examples/e03_struct_utilities/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "e03_struct_utilities"
3 | version = "0.1.0"
4 | authors = ["my name "]
5 | edition = "2018"
6 |
7 | [dependencies]
8 | serenity = { path = "../../", default-features = false, features = ["client", "gateway", "rustls_backend", "model"] }
9 | tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
10 |
--------------------------------------------------------------------------------
/examples/e03_struct_utilities/Makefile.toml:
--------------------------------------------------------------------------------
1 | extend = "../../Makefile.toml"
2 |
3 | [tasks.examples_build]
4 | alias = "build"
5 |
6 | [tasks.examples_build_release]
7 | alias = "build_release"
8 |
9 | [tasks.examples_run]
10 | alias = "run"
11 |
12 | [tasks.examples_run_release]
13 | alias = "run_release"
14 |
--------------------------------------------------------------------------------
/examples/e03_struct_utilities/src/main.rs:
--------------------------------------------------------------------------------
1 | use std::env;
2 |
3 | use serenity::async_trait;
4 | use serenity::builder::CreateMessage;
5 | use serenity::model::channel::Message;
6 | use serenity::model::gateway::Ready;
7 | use serenity::prelude::*;
8 |
9 | struct Handler;
10 |
11 | #[async_trait]
12 | impl EventHandler for Handler {
13 | async fn message(&self, context: Context, msg: Message) {
14 | if msg.content == "!messageme" {
15 | // If the `utils`-feature is enabled, then model structs will have a lot of useful
16 | // methods implemented, to avoid using an often otherwise bulky Context, or even much
17 | // lower-level `rest` method.
18 | //
19 | // In this case, you can direct message a User directly by simply calling a method on
20 | // its instance, with the content of the message.
21 | let builder = CreateMessage::new().content("Hello!");
22 | let dm = msg.author.dm(&context, builder).await;
23 |
24 | if let Err(why) = dm {
25 | println!("Error when direct messaging user: {why:?}");
26 | }
27 | }
28 | }
29 |
30 | async fn ready(&self, _: Context, ready: Ready) {
31 | println!("{} is connected!", ready.user.name);
32 | }
33 | }
34 |
35 | #[tokio::main]
36 | async fn main() {
37 | // Configure the client with your Discord bot token in the environment.
38 | let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment");
39 | let intents = GatewayIntents::GUILD_MESSAGES
40 | | GatewayIntents::DIRECT_MESSAGES
41 | | GatewayIntents::MESSAGE_CONTENT;
42 | let mut client =
43 | Client::builder(&token, intents).event_handler(Handler).await.expect("Err creating client");
44 |
45 | if let Err(why) = client.start().await {
46 | println!("Client error: {why:?}");
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/examples/e04_message_builder/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "e04_message_builder"
3 | version = "0.1.0"
4 | authors = ["my name "]
5 | edition = "2018"
6 |
7 | [dependencies]
8 | serenity = { path = "../../", default-features = false, features = ["client", "gateway", "rustls_backend", "model"] }
9 | tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
10 |
--------------------------------------------------------------------------------
/examples/e04_message_builder/Makefile.toml:
--------------------------------------------------------------------------------
1 | extend = "../../Makefile.toml"
2 |
3 | [tasks.examples_build]
4 | alias = "build"
5 |
6 | [tasks.examples_build_release]
7 | alias = "build_release"
8 |
9 | [tasks.examples_run]
10 | alias = "run"
11 |
12 | [tasks.examples_run_release]
13 | alias = "run_release"
14 |
--------------------------------------------------------------------------------
/examples/e04_message_builder/src/main.rs:
--------------------------------------------------------------------------------
1 | use std::env;
2 |
3 | use serenity::async_trait;
4 | use serenity::model::channel::Message;
5 | use serenity::model::gateway::Ready;
6 | use serenity::prelude::*;
7 | use serenity::utils::MessageBuilder;
8 |
9 | struct Handler;
10 |
11 | #[async_trait]
12 | impl EventHandler for Handler {
13 | async fn message(&self, context: Context, msg: Message) {
14 | if msg.content == "!ping" {
15 | let channel = match msg.channel_id.to_channel(&context).await {
16 | Ok(channel) => channel,
17 | Err(why) => {
18 | println!("Error getting channel: {why:?}");
19 |
20 | return;
21 | },
22 | };
23 |
24 | // The message builder allows for creating a message by mentioning users dynamically,
25 | // pushing "safe" versions of content (such as bolding normalized content), displaying
26 | // emojis, and more.
27 | let response = MessageBuilder::new()
28 | .push("User ")
29 | .push_bold_safe(&msg.author.name)
30 | .push(" used the 'ping' command in the ")
31 | .mention(&channel)
32 | .push(" channel")
33 | .build();
34 |
35 | if let Err(why) = msg.channel_id.say(&context.http, &response).await {
36 | println!("Error sending message: {why:?}");
37 | }
38 | }
39 | }
40 |
41 | async fn ready(&self, _: Context, ready: Ready) {
42 | println!("{} is connected!", ready.user.name);
43 | }
44 | }
45 |
46 | #[tokio::main]
47 | async fn main() {
48 | // Configure the client with your Discord bot token in the environment.
49 | let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment");
50 | let intents = GatewayIntents::GUILD_MESSAGES
51 | | GatewayIntents::DIRECT_MESSAGES
52 | | GatewayIntents::MESSAGE_CONTENT;
53 | let mut client =
54 | Client::builder(&token, intents).event_handler(Handler).await.expect("Err creating client");
55 |
56 | if let Err(why) = client.start().await {
57 | println!("Client error: {why:?}");
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/examples/e05_command_framework/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "e05_command_framework"
3 | version = "0.1.0"
4 | authors = ["my name "]
5 | edition = "2018"
6 |
7 | [dependencies.serenity]
8 | features = ["framework", "standard_framework", "rustls_backend"]
9 | path = "../../"
10 |
11 | [dependencies.tokio]
12 | version = "1.0"
13 | features = ["macros", "rt-multi-thread"]
14 |
--------------------------------------------------------------------------------
/examples/e05_command_framework/Makefile.toml:
--------------------------------------------------------------------------------
1 | extend = "../../Makefile.toml"
2 |
3 | [tasks.examples_build]
4 | alias = "build"
5 |
6 | [tasks.examples_build_release]
7 | alias = "build_release"
8 |
9 | [tasks.examples_run]
10 | alias = "run"
11 |
12 | [tasks.examples_run_release]
13 | alias = "run_release"
14 |
--------------------------------------------------------------------------------
/examples/e06_sample_bot_structure/.env.example:
--------------------------------------------------------------------------------
1 | # This declares an environment variable named "DISCORD_TOKEN" with the given
2 | # value. When calling `dotenv::dotenv()`, it will read the `.env` file and parse
3 | # these key-value pairs and insert them into the environment.
4 | #
5 | # Environment variables are separated by newlines and must not have space
6 | # around the equals sign (`=`).
7 | DISCORD_TOKEN=put your token here
8 | # Declares the level of logging to use. Read the documentation for the `log`
9 | # and `env_logger` crates for more information.
10 | RUST_LOG=debug
11 |
--------------------------------------------------------------------------------
/examples/e06_sample_bot_structure/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "e06_sample_bot_structure"
3 | version = "0.1.0"
4 | authors = ["my name "]
5 | edition = "2018"
6 |
7 | [dependencies]
8 | dotenv = "0.15"
9 | tracing = "0.1.23"
10 | tracing-subscriber = "0.3"
11 |
12 | [dependencies.tokio]
13 | version = "1.0"
14 | features = ["macros", "signal", "rt-multi-thread"]
15 |
16 | [dependencies.serenity]
17 | features = ["cache", "framework", "standard_framework", "rustls_backend"]
18 | path = "../../"
19 |
--------------------------------------------------------------------------------
/examples/e06_sample_bot_structure/Makefile.toml:
--------------------------------------------------------------------------------
1 | extend = "../../Makefile.toml"
2 |
3 | [tasks.examples_build]
4 | alias = "build"
5 |
6 | [tasks.examples_build_release]
7 | alias = "build_release"
8 |
9 | [tasks.examples_run]
10 | alias = "run"
11 |
12 | [tasks.examples_run_release]
13 | alias = "run_release"
14 |
--------------------------------------------------------------------------------
/examples/e06_sample_bot_structure/src/commands/math.rs:
--------------------------------------------------------------------------------
1 | use serenity::framework::standard::macros::command;
2 | use serenity::framework::standard::{Args, CommandResult};
3 | use serenity::model::prelude::*;
4 | use serenity::prelude::*;
5 |
6 | #[command]
7 | pub async fn multiply(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
8 | let one = args.single::()?;
9 | let two = args.single::()?;
10 |
11 | let product = one * two;
12 |
13 | msg.channel_id.say(&ctx.http, product.to_string()).await?;
14 |
15 | Ok(())
16 | }
17 |
--------------------------------------------------------------------------------
/examples/e06_sample_bot_structure/src/commands/meta.rs:
--------------------------------------------------------------------------------
1 | use serenity::framework::standard::macros::command;
2 | use serenity::framework::standard::CommandResult;
3 | use serenity::model::prelude::*;
4 | use serenity::prelude::*;
5 |
6 | #[command]
7 | async fn ping(ctx: &Context, msg: &Message) -> CommandResult {
8 | msg.channel_id.say(&ctx.http, "Pong!").await?;
9 |
10 | Ok(())
11 | }
12 |
--------------------------------------------------------------------------------
/examples/e06_sample_bot_structure/src/commands/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod math;
2 | pub mod meta;
3 | pub mod owner;
4 |
--------------------------------------------------------------------------------
/examples/e06_sample_bot_structure/src/commands/owner.rs:
--------------------------------------------------------------------------------
1 | use serenity::framework::standard::macros::command;
2 | use serenity::framework::standard::CommandResult;
3 | use serenity::model::prelude::*;
4 | use serenity::prelude::*;
5 |
6 | use crate::ShardManagerContainer;
7 |
8 | #[command]
9 | #[owners_only]
10 | async fn quit(ctx: &Context, msg: &Message) -> CommandResult {
11 | let data = ctx.data.read().await;
12 |
13 | if let Some(manager) = data.get::() {
14 | msg.reply(ctx, "Shutting down!").await?;
15 | manager.shutdown_all().await;
16 | } else {
17 | msg.reply(ctx, "There was a problem getting the shard manager").await?;
18 |
19 | return Ok(());
20 | }
21 |
22 | Ok(())
23 | }
24 |
--------------------------------------------------------------------------------
/examples/e06_sample_bot_structure/src/main.rs:
--------------------------------------------------------------------------------
1 | //! Requires the 'framework' feature flag be enabled in your project's `Cargo.toml`.
2 | //!
3 | //! This can be enabled by specifying the feature in the dependency section:
4 | //!
5 | //! ```toml
6 | //! [dependencies.serenity]
7 | //! git = "https://github.com/serenity-rs/serenity.git"
8 | //! features = ["framework", "standard_framework"]
9 | //! ```
10 | #![allow(deprecated)] // We recommend migrating to poise, instead of using the standard command framework.
11 | mod commands;
12 |
13 | use std::collections::HashSet;
14 | use std::env;
15 | use std::sync::Arc;
16 |
17 | use serenity::async_trait;
18 | use serenity::framework::standard::macros::group;
19 | use serenity::framework::standard::Configuration;
20 | use serenity::framework::StandardFramework;
21 | use serenity::gateway::ShardManager;
22 | use serenity::http::Http;
23 | use serenity::model::event::ResumedEvent;
24 | use serenity::model::gateway::Ready;
25 | use serenity::prelude::*;
26 | use tracing::{error, info};
27 |
28 | use crate::commands::math::*;
29 | use crate::commands::meta::*;
30 | use crate::commands::owner::*;
31 |
32 | pub struct ShardManagerContainer;
33 |
34 | impl TypeMapKey for ShardManagerContainer {
35 | type Value = Arc;
36 | }
37 |
38 | struct Handler;
39 |
40 | #[async_trait]
41 | impl EventHandler for Handler {
42 | async fn ready(&self, _: Context, ready: Ready) {
43 | info!("Connected as {}", ready.user.name);
44 | }
45 |
46 | async fn resume(&self, _: Context, _: ResumedEvent) {
47 | info!("Resumed");
48 | }
49 | }
50 |
51 | #[group]
52 | #[commands(multiply, ping, quit)]
53 | struct General;
54 |
55 | #[tokio::main]
56 | async fn main() {
57 | // This will load the environment variables located at `./.env`, relative to the CWD.
58 | // See `./.env.example` for an example on how to structure this.
59 | dotenv::dotenv().expect("Failed to load .env file");
60 |
61 | // Initialize the logger to use environment variables.
62 | //
63 | // In this case, a good default is setting the environment variable `RUST_LOG` to `debug`.
64 | tracing_subscriber::fmt::init();
65 |
66 | let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment");
67 |
68 | let http = Http::new(&token);
69 |
70 | // We will fetch your bot's owners and id
71 | let (owners, _bot_id) = match http.get_current_application_info().await {
72 | Ok(info) => {
73 | let mut owners = HashSet::new();
74 | if let Some(owner) = &info.owner {
75 | owners.insert(owner.id);
76 | }
77 |
78 | (owners, info.id)
79 | },
80 | Err(why) => panic!("Could not access application info: {:?}", why),
81 | };
82 |
83 | // Create the framework
84 | let framework = StandardFramework::new().group(&GENERAL_GROUP);
85 | framework.configure(Configuration::new().owners(owners).prefix("~"));
86 |
87 | let intents = GatewayIntents::GUILD_MESSAGES
88 | | GatewayIntents::DIRECT_MESSAGES
89 | | GatewayIntents::MESSAGE_CONTENT;
90 | let mut client = Client::builder(&token, intents)
91 | .framework(framework)
92 | .event_handler(Handler)
93 | .await
94 | .expect("Err creating client");
95 |
96 | {
97 | let mut data = client.data.write().await;
98 | data.insert::(client.shard_manager.clone());
99 | }
100 |
101 | let shard_manager = client.shard_manager.clone();
102 |
103 | tokio::spawn(async move {
104 | tokio::signal::ctrl_c().await.expect("Could not register ctrl+c handler");
105 | shard_manager.shutdown_all().await;
106 | });
107 |
108 | if let Err(why) = client.start().await {
109 | error!("Client error: {:?}", why);
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/examples/e07_env_logging/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "e07_env_logging"
3 | version = "0.1.0"
4 | authors = ["my name "]
5 | edition = "2018"
6 |
7 | [dependencies]
8 | tracing = "0.1.23"
9 | tracing-subscriber = "0.3"
10 | tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
11 |
12 | [dependencies.serenity]
13 | features = ["client", "rustls_backend"]
14 | path = "../../"
15 |
--------------------------------------------------------------------------------
/examples/e07_env_logging/Makefile.toml:
--------------------------------------------------------------------------------
1 | extend = "../../Makefile.toml"
2 |
3 | [env]
4 | RUST_LOG = "info"
5 |
6 | [tasks.examples_build]
7 | alias = "build"
8 |
9 | [tasks.examples_build_release]
10 | alias = "build_release"
11 |
12 | [tasks.examples_run]
13 | alias = "run"
14 |
15 | [tasks.examples_run_release]
16 | alias = "run_release"
17 |
--------------------------------------------------------------------------------
/examples/e07_env_logging/src/main.rs:
--------------------------------------------------------------------------------
1 | #![allow(deprecated)] // We recommend migrating to poise, instead of using the standard command framework.
2 |
3 | use std::env;
4 |
5 | use serenity::async_trait;
6 | use serenity::framework::standard::macros::{command, group, hook};
7 | use serenity::framework::standard::{CommandResult, Configuration, StandardFramework};
8 | use serenity::model::channel::Message;
9 | use serenity::model::event::ResumedEvent;
10 | use serenity::model::gateway::Ready;
11 | use serenity::prelude::*;
12 | use tracing::{debug, error, info, instrument};
13 |
14 | struct Handler;
15 |
16 | #[async_trait]
17 | impl EventHandler for Handler {
18 | async fn ready(&self, _: Context, ready: Ready) {
19 | // Log at the INFO level. This is a macro from the `tracing` crate.
20 | info!("{} is connected!", ready.user.name);
21 | }
22 |
23 | // For instrument to work, all parameters must implement Debug.
24 | //
25 | // Handler doesn't implement Debug here, so we specify to skip that argument.
26 | // Context doesn't implement Debug either, so it is also skipped.
27 | #[instrument(skip(self, _ctx))]
28 | async fn resume(&self, _ctx: Context, _resume: ResumedEvent) {
29 | // Log at the DEBUG level.
30 | //
31 | // In this example, this will not show up in the logs because DEBUG is
32 | // below INFO, which is the set debug level.
33 | debug!("Resumed");
34 | }
35 | }
36 |
37 | #[hook]
38 | // instrument will show additional information on all the logs that happen inside the function.
39 | //
40 | // This additional information includes the function name, along with all it's arguments formatted
41 | // with the Debug impl. This additional information will also only be shown if the LOG level is set
42 | // to `debug`
43 | #[instrument]
44 | async fn before(_: &Context, msg: &Message, command_name: &str) -> bool {
45 | info!("Got command '{}' by user '{}'", command_name, msg.author.name);
46 |
47 | true
48 | }
49 |
50 | #[group]
51 | #[commands(ping)]
52 | struct General;
53 |
54 | #[tokio::main]
55 | #[instrument]
56 | async fn main() {
57 | // Call tracing_subscriber's initialize function, which configures `tracing` via environment
58 | // variables.
59 | //
60 | // For example, you can say to log all levels INFO and up via setting the environment variable
61 | // `RUST_LOG` to `INFO`.
62 | //
63 | // This environment variable is already preset if you use cargo-make to run the example.
64 | tracing_subscriber::fmt::init();
65 |
66 | // Configure the client with your Discord bot token in the environment.
67 | let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment");
68 |
69 | let framework = StandardFramework::new().before(before).group(&GENERAL_GROUP);
70 | framework.configure(Configuration::new().prefix("~"));
71 |
72 | let intents = GatewayIntents::GUILD_MESSAGES
73 | | GatewayIntents::DIRECT_MESSAGES
74 | | GatewayIntents::MESSAGE_CONTENT;
75 | let mut client = Client::builder(&token, intents)
76 | .event_handler(Handler)
77 | .framework(framework)
78 | .await
79 | .expect("Err creating client");
80 |
81 | if let Err(why) = client.start().await {
82 | error!("Client error: {:?}", why);
83 | }
84 | }
85 |
86 | // Currently, the instrument macro doesn't work with commands.
87 | // If you wish to instrument commands, use it on the before function.
88 | #[command]
89 | async fn ping(ctx: &Context, msg: &Message) -> CommandResult {
90 | if let Err(why) = msg.channel_id.say(&ctx.http, "Pong! : )").await {
91 | error!("Error sending message: {:?}", why);
92 | }
93 |
94 | Ok(())
95 | }
96 |
--------------------------------------------------------------------------------
/examples/e08_shard_manager/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "e08_shard_manager"
3 | version = "0.1.0"
4 | authors = ["my name "]
5 | edition = "2018"
6 |
7 | [dependencies]
8 | tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "time"] }
9 |
10 | [dependencies.serenity]
11 | default-features = false
12 | features = ["client", "gateway", "rustls_backend", "model"]
13 | path = "../../"
14 |
--------------------------------------------------------------------------------
/examples/e08_shard_manager/Makefile.toml:
--------------------------------------------------------------------------------
1 | extend = "../../Makefile.toml"
2 |
3 | [tasks.examples_build]
4 | alias = "build"
5 |
6 | [tasks.examples_build_release]
7 | alias = "build_release"
8 |
9 | [tasks.examples_run]
10 | alias = "run"
11 |
12 | [tasks.examples_run_release]
13 | alias = "run_release"
14 |
--------------------------------------------------------------------------------
/examples/e08_shard_manager/src/main.rs:
--------------------------------------------------------------------------------
1 | //! This is an example showing how to interact with the client's `ShardManager`, which is a struct
2 | //! that can be used to interact with shards. This allows an easy method of retrieving shards'
3 | //! current status, restarting them, or shutting them down.
4 | //!
5 | //! In this example, we run two shards; this means that there will be two WebSocket connections to
6 | //! Discord, and each will receive events for _approximately_ 1/2 of all guilds that the bot is on.
7 | //!
8 | //! This isn't particularly useful for small bots, but is useful for large bots that may need to
9 | //! split load on separate VPSs or dedicated servers. Additionally, Discord requires that there be
10 | //! at least one shard for every
11 | //! 2500 guilds that a bot is on.
12 | //!
13 | //! For the purposes of this example, we'll print the current statuses of the two shards to the
14 | //! terminal every 30 seconds. This includes the ID of the shard, the current connection stage,
15 | //! (e.g. "Connecting" or "Connected"), and the approximate WebSocket latency (time between when a
16 | //! heartbeat is sent to Discord and when a heartbeat acknowledgement is received).
17 | //!
18 | //! # Notes
19 | //!
20 | //! Note that it may take a minute or more for a latency to be recorded or to update, depending on
21 | //! how often Discord tells the client to send a heartbeat.
22 | use std::env;
23 | use std::time::Duration;
24 |
25 | use serenity::async_trait;
26 | use serenity::model::gateway::Ready;
27 | use serenity::prelude::*;
28 | use tokio::time::sleep;
29 |
30 | struct Handler;
31 |
32 | #[async_trait]
33 | impl EventHandler for Handler {
34 | async fn ready(&self, _: Context, ready: Ready) {
35 | if let Some(shard) = ready.shard {
36 | // Note that array index 0 is 0-indexed, while index 1 is 1-indexed.
37 | //
38 | // This may seem unintuitive, but it models Discord's behaviour.
39 | println!("{} is connected on shard {}/{}!", ready.user.name, shard.id, shard.total);
40 | }
41 | }
42 | }
43 |
44 | #[tokio::main]
45 | async fn main() {
46 | // Configure the client with your Discord bot token in the environment.
47 | let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment");
48 |
49 | let intents = GatewayIntents::GUILD_MESSAGES
50 | | GatewayIntents::DIRECT_MESSAGES
51 | | GatewayIntents::MESSAGE_CONTENT;
52 | let mut client =
53 | Client::builder(&token, intents).event_handler(Handler).await.expect("Err creating client");
54 |
55 | // Here we clone a lock to the Shard Manager, and then move it into a new thread. The thread
56 | // will unlock the manager and print shards' status on a loop.
57 | let manager = client.shard_manager.clone();
58 |
59 | tokio::spawn(async move {
60 | loop {
61 | sleep(Duration::from_secs(30)).await;
62 |
63 | let shard_runners = manager.runners.lock().await;
64 |
65 | for (id, runner) in shard_runners.iter() {
66 | println!(
67 | "Shard ID {} is {} with a latency of {:?}",
68 | id, runner.stage, runner.latency,
69 | );
70 | }
71 | }
72 | });
73 |
74 | // Start two shards. Note that there is an ~5 second ratelimit period between when one shard
75 | // can start after another.
76 | if let Err(why) = client.start_shards(2).await {
77 | println!("Client error: {why:?}");
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/examples/e09_create_message_builder/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "e09_create_message_builder"
3 | version = "0.1.0"
4 | authors = ["my name "]
5 | edition = "2018"
6 |
7 | [dependencies]
8 | serenity = { path = "../../", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "chrono"] }
9 | tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
10 |
--------------------------------------------------------------------------------
/examples/e09_create_message_builder/Makefile.toml:
--------------------------------------------------------------------------------
1 | extend = "../../Makefile.toml"
2 |
3 | [tasks.examples_build]
4 | alias = "build"
5 |
6 | [tasks.examples_build_release]
7 | alias = "build_release"
8 |
9 | [tasks.examples_run]
10 | alias = "run"
11 |
12 | [tasks.examples_run_release]
13 | alias = "run_release"
14 |
--------------------------------------------------------------------------------
/examples/e09_create_message_builder/ferris_eyes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/serenity-rs/serenity/bb9610216eed49ba0c1fb9bc54fd0219d759df00/examples/e09_create_message_builder/ferris_eyes.png
--------------------------------------------------------------------------------
/examples/e09_create_message_builder/src/main.rs:
--------------------------------------------------------------------------------
1 | use std::env;
2 |
3 | use serenity::async_trait;
4 | use serenity::builder::{CreateAttachment, CreateEmbed, CreateEmbedFooter, CreateMessage};
5 | use serenity::model::channel::Message;
6 | use serenity::model::gateway::Ready;
7 | use serenity::model::Timestamp;
8 | use serenity::prelude::*;
9 |
10 | struct Handler;
11 |
12 | #[async_trait]
13 | impl EventHandler for Handler {
14 | async fn message(&self, ctx: Context, msg: Message) {
15 | if msg.content == "!hello" {
16 | // The create message builder allows you to easily create embeds and messages using a
17 | // builder syntax.
18 | // This example will create a message that says "Hello, World!", with an embed that has
19 | // a title, description, an image, three fields, and a footer.
20 | let footer = CreateEmbedFooter::new("This is a footer");
21 | let embed = CreateEmbed::new()
22 | .title("This is a title")
23 | .description("This is a description")
24 | .image("attachment://ferris_eyes.png")
25 | .fields(vec![
26 | ("This is the first field", "This is a field body", true),
27 | ("This is the second field", "Both fields are inline", true),
28 | ])
29 | .field("This is the third field", "This is not an inline field", false)
30 | .footer(footer)
31 | // Add a timestamp for the current time
32 | // This also accepts a rfc3339 Timestamp
33 | .timestamp(Timestamp::now());
34 | let builder = CreateMessage::new()
35 | .content("Hello, World!")
36 | .embed(embed)
37 | .add_file(CreateAttachment::path("./ferris_eyes.png").await.unwrap());
38 | let msg = msg.channel_id.send_message(&ctx.http, builder).await;
39 |
40 | if let Err(why) = msg {
41 | println!("Error sending message: {why:?}");
42 | }
43 | }
44 | }
45 |
46 | async fn ready(&self, _: Context, ready: Ready) {
47 | println!("{} is connected!", ready.user.name);
48 | }
49 | }
50 |
51 | #[tokio::main]
52 | async fn main() {
53 | // Configure the client with your Discord bot token in the environment.
54 | let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment");
55 | let intents = GatewayIntents::GUILD_MESSAGES
56 | | GatewayIntents::DIRECT_MESSAGES
57 | | GatewayIntents::MESSAGE_CONTENT;
58 | let mut client =
59 | Client::builder(&token, intents).event_handler(Handler).await.expect("Err creating client");
60 |
61 | if let Err(why) = client.start().await {
62 | println!("Client error: {why:?}");
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/examples/e10_collectors/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "e10_collectors"
3 | version = "0.1.0"
4 | authors = ["my name "]
5 | edition = "2018"
6 |
7 | [dependencies.serenity]
8 | features = ["framework", "standard_framework", "rustls_backend", "collector"]
9 | path = "../../"
10 |
11 | [dependencies]
12 | tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
13 |
--------------------------------------------------------------------------------
/examples/e10_collectors/Makefile.toml:
--------------------------------------------------------------------------------
1 | extend = "../../Makefile.toml"
2 |
3 | [tasks.examples_build]
4 | alias = "build"
5 |
6 | [tasks.examples_build_release]
7 | alias = "build_release"
8 |
9 | [tasks.examples_run]
10 | alias = "run"
11 |
12 | [tasks.examples_run_release]
13 | alias = "run_release"
14 |
--------------------------------------------------------------------------------
/examples/e11_gateway_intents/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "e11_gateway_intents"
3 | version = "0.1.0"
4 | authors = ["my name "]
5 | edition = "2018"
6 |
7 | [dependencies]
8 | serenity = { path = "../../", default-features = false, features = ["client", "gateway", "rustls_backend", "model"] }
9 | tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
10 |
--------------------------------------------------------------------------------
/examples/e11_gateway_intents/Makefile.toml:
--------------------------------------------------------------------------------
1 | extend = "../../Makefile.toml"
2 |
3 | [tasks.examples_build]
4 | alias = "build"
5 |
6 | [tasks.examples_build_release]
7 | alias = "build_release"
8 |
9 | [tasks.examples_run]
10 | alias = "run"
11 |
12 | [tasks.examples_run_release]
13 | alias = "run_release"
14 |
--------------------------------------------------------------------------------
/examples/e11_gateway_intents/src/main.rs:
--------------------------------------------------------------------------------
1 | use std::env;
2 |
3 | use serenity::async_trait;
4 | use serenity::model::channel::Message;
5 | use serenity::model::gateway::{Presence, Ready};
6 | use serenity::prelude::*;
7 |
8 | struct Handler;
9 |
10 | #[async_trait]
11 | impl EventHandler for Handler {
12 | // This event will be dispatched for guilds, but not for direct messages.
13 | async fn message(&self, _ctx: Context, msg: Message) {
14 | println!("Received message: {}", msg.content);
15 | }
16 |
17 | // As the intents set in this example, this event shall never be dispatched.
18 | // Try it by changing your status.
19 | async fn presence_update(&self, _ctx: Context, _new_data: Presence) {
20 | println!("Presence Update");
21 | }
22 |
23 | async fn ready(&self, _: Context, ready: Ready) {
24 | println!("{} is connected!", ready.user.name);
25 | }
26 | }
27 |
28 | #[tokio::main]
29 | async fn main() {
30 | // Configure the client with your Discord bot token in the environment.
31 | let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment");
32 |
33 | // Intents are a bitflag, bitwise operations can be used to dictate which intents to use
34 | let intents =
35 | GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT;
36 | // Build our client.
37 | let mut client = Client::builder(token, intents)
38 | .event_handler(Handler)
39 | .await
40 | .expect("Error creating client");
41 |
42 | // Finally, start a single shard, and start listening to events.
43 | //
44 | // Shards will automatically attempt to reconnect, and will perform exponential backoff until
45 | // it reconnects.
46 | if let Err(why) = client.start().await {
47 | println!("Client error: {why:?}");
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/examples/e12_global_data/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "e12_global_data"
3 | version = "0.1.0"
4 | authors = ["my name "]
5 | edition = "2018"
6 |
7 | [dependencies]
8 | serenity = { path = "../../" }
9 | tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
10 |
--------------------------------------------------------------------------------
/examples/e12_global_data/Makefile.toml:
--------------------------------------------------------------------------------
1 | extend = "../../Makefile.toml"
2 |
3 | [tasks.examples_build]
4 | alias = "build"
5 |
6 | [tasks.examples_build_release]
7 | alias = "build_release"
8 |
9 | [tasks.examples_run]
10 | alias = "run"
11 |
12 | [tasks.examples_run_release]
13 | alias = "run_release"
14 |
--------------------------------------------------------------------------------
/examples/e13_parallel_loops/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "e13_parallel_loops"
3 | version = "0.1.0"
4 | authors = ["my name "]
5 | edition = "2018"
6 |
7 | [dependencies]
8 | serenity = { path = "../../", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "cache"] }
9 | tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
10 | sys-info = "0.9"
11 | chrono = { version = "0.4", default-features = false, features = ["clock"] }
12 |
--------------------------------------------------------------------------------
/examples/e13_parallel_loops/Makefile.toml:
--------------------------------------------------------------------------------
1 | extend = "../../Makefile.toml"
2 |
3 | [tasks.examples_build]
4 | alias = "build"
5 |
6 | [tasks.examples_build_release]
7 | alias = "build_release"
8 |
9 | [tasks.examples_run]
10 | alias = "run"
11 |
12 | [tasks.examples_run_release]
13 | alias = "run_release"
14 |
--------------------------------------------------------------------------------
/examples/e14_slash_commands/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "e14_slash_commands"
3 | version = "0.1.0"
4 | authors = ["my name "]
5 | edition = "2018"
6 |
7 | [dependencies]
8 | serenity = { path = "../../", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "collector"] }
9 | tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
10 |
--------------------------------------------------------------------------------
/examples/e14_slash_commands/Makefile.toml:
--------------------------------------------------------------------------------
1 | extend = "../../Makefile.toml"
2 |
3 | [tasks.examples_build]
4 | alias = "build"
5 |
6 | [tasks.examples_build_release]
7 | alias = "build_release"
8 |
9 | [tasks.examples_run]
10 | alias = "run"
11 |
12 | [tasks.examples_run_release]
13 | alias = "run_release"
14 |
--------------------------------------------------------------------------------
/examples/e14_slash_commands/README.md:
--------------------------------------------------------------------------------
1 | # Slash commands
2 |
3 | This example demonstrates serenity's low-level slash command functions. It is
4 | possible to write a bot just with these, but it's usually easier to use the
5 | [poise] framework, a high-level framework for slash commands (and text
6 | commands, too).
7 |
8 | [poise]: https://github.com/serenity-rs/poise/
9 |
--------------------------------------------------------------------------------
/examples/e14_slash_commands/src/commands/attachmentinput.rs:
--------------------------------------------------------------------------------
1 | use serenity::builder::{CreateCommand, CreateCommandOption};
2 | use serenity::model::application::{CommandOptionType, ResolvedOption, ResolvedValue};
3 |
4 | pub fn run(options: &[ResolvedOption]) -> String {
5 | if let Some(ResolvedOption {
6 | value: ResolvedValue::Attachment(attachment), ..
7 | }) = options.first()
8 | {
9 | format!("Attachment name: {}, attachment size: {}", attachment.filename, attachment.size)
10 | } else {
11 | "Please provide a valid attachment".to_string()
12 | }
13 | }
14 |
15 | pub fn register() -> CreateCommand {
16 | CreateCommand::new("attachmentinput")
17 | .description("Test command for attachment input")
18 | .add_option(
19 | CreateCommandOption::new(CommandOptionType::Attachment, "attachment", "A file")
20 | .required(true),
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/examples/e14_slash_commands/src/commands/id.rs:
--------------------------------------------------------------------------------
1 | use serenity::builder::{CreateCommand, CreateCommandOption};
2 | use serenity::model::application::{CommandOptionType, ResolvedOption, ResolvedValue};
3 |
4 | pub fn run(options: &[ResolvedOption]) -> String {
5 | if let Some(ResolvedOption {
6 | value: ResolvedValue::User(user, _), ..
7 | }) = options.first()
8 | {
9 | format!("{}'s id is {}", user.tag(), user.id)
10 | } else {
11 | "Please provide a valid user".to_string()
12 | }
13 | }
14 |
15 | pub fn register() -> CreateCommand {
16 | CreateCommand::new("id").description("Get a user id").add_option(
17 | CreateCommandOption::new(CommandOptionType::User, "id", "The user to lookup")
18 | .required(true),
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/examples/e14_slash_commands/src/commands/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod attachmentinput;
2 | pub mod id;
3 | pub mod modal;
4 | pub mod numberinput;
5 | pub mod ping;
6 | pub mod welcome;
7 | pub mod wonderful_command;
8 |
--------------------------------------------------------------------------------
/examples/e14_slash_commands/src/commands/modal.rs:
--------------------------------------------------------------------------------
1 | use serenity::builder::*;
2 | use serenity::model::prelude::*;
3 | use serenity::prelude::*;
4 | use serenity::utils::CreateQuickModal;
5 |
6 | pub async fn run(ctx: &Context, interaction: &CommandInteraction) -> Result<(), serenity::Error> {
7 | let modal = CreateQuickModal::new("About you")
8 | .timeout(std::time::Duration::from_secs(600))
9 | .short_field("First name")
10 | .short_field("Last name")
11 | .paragraph_field("Hobbies and interests");
12 | let response = interaction.quick_modal(ctx, modal).await?.unwrap();
13 |
14 | let inputs = response.inputs;
15 | let (first_name, last_name, hobbies) = (&inputs[0], &inputs[1], &inputs[2]);
16 |
17 | response
18 | .interaction
19 | .create_response(
20 | ctx,
21 | CreateInteractionResponse::Message(CreateInteractionResponseMessage::new().content(
22 | format!("**Name**: {first_name} {last_name}\n\nHobbies and interests: {hobbies}"),
23 | )),
24 | )
25 | .await?;
26 | Ok(())
27 | }
28 |
29 | pub fn register() -> CreateCommand {
30 | CreateCommand::new("modal").description("Asks some details about you")
31 | }
32 |
--------------------------------------------------------------------------------
/examples/e14_slash_commands/src/commands/numberinput.rs:
--------------------------------------------------------------------------------
1 | use serenity::builder::{CreateCommand, CreateCommandOption};
2 | use serenity::model::application::CommandOptionType;
3 |
4 | pub fn register() -> CreateCommand {
5 | CreateCommand::new("numberinput")
6 | .description("Test command for number input")
7 | .add_option(
8 | CreateCommandOption::new(CommandOptionType::Integer, "int", "An integer from 5 to 10")
9 | .min_int_value(5)
10 | .max_int_value(10)
11 | .required(true),
12 | )
13 | .add_option(
14 | CreateCommandOption::new(
15 | CommandOptionType::Number,
16 | "number",
17 | "A float from -3.3 to 234.5",
18 | )
19 | .min_number_value(-3.3)
20 | .max_number_value(234.5)
21 | .required(true),
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/examples/e14_slash_commands/src/commands/ping.rs:
--------------------------------------------------------------------------------
1 | use serenity::builder::CreateCommand;
2 | use serenity::model::application::ResolvedOption;
3 |
4 | pub fn run(_options: &[ResolvedOption]) -> String {
5 | "Hey, I'm alive!".to_string()
6 | }
7 |
8 | pub fn register() -> CreateCommand {
9 | CreateCommand::new("ping").description("A ping command")
10 | }
11 |
--------------------------------------------------------------------------------
/examples/e14_slash_commands/src/commands/welcome.rs:
--------------------------------------------------------------------------------
1 | use serenity::builder::{CreateCommand, CreateCommandOption};
2 | use serenity::model::application::CommandOptionType;
3 |
4 | pub fn register() -> CreateCommand {
5 | CreateCommand::new("welcome")
6 | .description("Welcome a user")
7 | .name_localized("de", "begrüßen")
8 | .description_localized("de", "Einen Nutzer begrüßen")
9 | .add_option(
10 | CreateCommandOption::new(CommandOptionType::User, "user", "The user to welcome")
11 | .name_localized("de", "nutzer")
12 | .description_localized("de", "Der zu begrüßende Nutzer")
13 | .required(true),
14 | )
15 | .add_option(
16 | CreateCommandOption::new(CommandOptionType::String, "message", "The message to send")
17 | .name_localized("de", "nachricht")
18 | .description_localized("de", "Die versendete Nachricht")
19 | .required(true)
20 | .add_string_choice_localized(
21 | "Welcome to our cool server! Ask me if you need help",
22 | "pizza",
23 | [(
24 | "de",
25 | "Willkommen auf unserem coolen Server! Frag mich, falls du Hilfe brauchst",
26 | )],
27 | )
28 | .add_string_choice_localized("Hey, do you want a coffee?", "coffee", [(
29 | "de",
30 | "Hey, willst du einen Kaffee?",
31 | )])
32 | .add_string_choice_localized(
33 | "Welcome to the club, you're now a good person. Well, I hope.",
34 | "club",
35 | [(
36 | "de",
37 | "Willkommen im Club, du bist jetzt ein guter Mensch. Naja, hoffentlich.",
38 | )],
39 | )
40 | .add_string_choice_localized(
41 | "I hope that you brought a controller to play together!",
42 | "game",
43 | [("de", "Ich hoffe du hast einen Controller zum Spielen mitgebracht!")],
44 | ),
45 | )
46 | }
47 |
--------------------------------------------------------------------------------
/examples/e14_slash_commands/src/commands/wonderful_command.rs:
--------------------------------------------------------------------------------
1 | use serenity::builder::CreateCommand;
2 |
3 | pub fn register() -> CreateCommand {
4 | CreateCommand::new("wonderful_command").description("An amazing command")
5 | }
6 |
--------------------------------------------------------------------------------
/examples/e14_slash_commands/src/main.rs:
--------------------------------------------------------------------------------
1 | mod commands;
2 |
3 | use std::env;
4 |
5 | use serenity::async_trait;
6 | use serenity::builder::{CreateInteractionResponse, CreateInteractionResponseMessage};
7 | use serenity::model::application::{Command, Interaction};
8 | use serenity::model::gateway::Ready;
9 | use serenity::model::id::GuildId;
10 | use serenity::prelude::*;
11 |
12 | struct Handler;
13 |
14 | #[async_trait]
15 | impl EventHandler for Handler {
16 | async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
17 | if let Interaction::Command(command) = interaction {
18 | println!("Received command interaction: {command:#?}");
19 |
20 | let content = match command.data.name.as_str() {
21 | "ping" => Some(commands::ping::run(&command.data.options())),
22 | "id" => Some(commands::id::run(&command.data.options())),
23 | "attachmentinput" => Some(commands::attachmentinput::run(&command.data.options())),
24 | "modal" => {
25 | commands::modal::run(&ctx, &command).await.unwrap();
26 | None
27 | },
28 | _ => Some("not implemented :(".to_string()),
29 | };
30 |
31 | if let Some(content) = content {
32 | let data = CreateInteractionResponseMessage::new().content(content);
33 | let builder = CreateInteractionResponse::Message(data);
34 | if let Err(why) = command.create_response(&ctx.http, builder).await {
35 | println!("Cannot respond to slash command: {why}");
36 | }
37 | }
38 | }
39 | }
40 |
41 | async fn ready(&self, ctx: Context, ready: Ready) {
42 | println!("{} is connected!", ready.user.name);
43 |
44 | let guild_id = GuildId::new(
45 | env::var("GUILD_ID")
46 | .expect("Expected GUILD_ID in environment")
47 | .parse()
48 | .expect("GUILD_ID must be an integer"),
49 | );
50 |
51 | let commands = guild_id
52 | .set_commands(&ctx.http, vec![
53 | commands::ping::register(),
54 | commands::id::register(),
55 | commands::welcome::register(),
56 | commands::numberinput::register(),
57 | commands::attachmentinput::register(),
58 | commands::modal::register(),
59 | ])
60 | .await;
61 |
62 | println!("I now have the following guild slash commands: {commands:#?}");
63 |
64 | let guild_command =
65 | Command::create_global_command(&ctx.http, commands::wonderful_command::register())
66 | .await;
67 |
68 | println!("I created the following global slash command: {guild_command:#?}");
69 | }
70 | }
71 |
72 | #[tokio::main]
73 | async fn main() {
74 | // Configure the client with your Discord bot token in the environment.
75 | let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment");
76 |
77 | // Build our client.
78 | let mut client = Client::builder(token, GatewayIntents::empty())
79 | .event_handler(Handler)
80 | .await
81 | .expect("Error creating client");
82 |
83 | // Finally, start a single shard, and start listening to events.
84 | //
85 | // Shards will automatically attempt to reconnect, and will perform exponential backoff until
86 | // it reconnects.
87 | if let Err(why) = client.start().await {
88 | println!("Client error: {why:?}");
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/examples/e15_simple_dashboard/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "e15_simple_dashboard"
3 | version = "0.1.0"
4 | edition = "2018"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [dependencies]
9 | rillrate = "0.41"
10 | notify = "=5.0.0-pre.14"
11 |
12 | tracing = "0.1"
13 | tracing-subscriber = "0.3"
14 |
15 | webbrowser = "0.8"
16 |
17 | [dependencies.serenity]
18 | path = "../../"
19 |
20 | [dependencies.tokio]
21 | version = "1"
22 | features = ["full"]
23 |
24 | [dependencies.reqwest]
25 | version = "0.11"
26 | default-features = false
27 | features = ["json", "rustls-tls"]
28 |
29 | [features]
30 | post-ping = []
31 |
--------------------------------------------------------------------------------
/examples/e15_simple_dashboard/Makefile.toml:
--------------------------------------------------------------------------------
1 | extend = "../../Makefile.toml"
2 |
3 | [tasks.examples_build]
4 | alias = "build"
5 |
6 | [tasks.examples_build_release]
7 | alias = "build_release"
8 |
9 | [tasks.examples_run]
10 | alias = "run"
11 |
12 | [tasks.examples_run_release]
13 | alias = "run_release"
14 |
--------------------------------------------------------------------------------
/examples/e16_sqlite_database/.gitignore:
--------------------------------------------------------------------------------
1 | database.sqlite*
2 |
--------------------------------------------------------------------------------
/examples/e16_sqlite_database/.sqlx/query-597707a72d1ed8eab0cb48a3bef8cdb981362e089a462fa6d156b27b57468678.json:
--------------------------------------------------------------------------------
1 | {
2 | "db_name": "SQLite",
3 | "query": "SELECT rowid, task FROM todo WHERE user_id = ? ORDER BY rowid LIMIT 1 OFFSET ?",
4 | "describe": {
5 | "columns": [
6 | {
7 | "name": "rowid",
8 | "ordinal": 0,
9 | "type_info": "Int64"
10 | },
11 | {
12 | "name": "task",
13 | "ordinal": 1,
14 | "type_info": "Text"
15 | }
16 | ],
17 | "parameters": {
18 | "Right": 2
19 | },
20 | "nullable": [
21 | false,
22 | false
23 | ]
24 | },
25 | "hash": "597707a72d1ed8eab0cb48a3bef8cdb981362e089a462fa6d156b27b57468678"
26 | }
27 |
--------------------------------------------------------------------------------
/examples/e16_sqlite_database/.sqlx/query-7636fc64c882305305814ffb66676ef09a92d3f1d46021b94ded4e9c073775d1.json:
--------------------------------------------------------------------------------
1 | {
2 | "db_name": "SQLite",
3 | "query": "INSERT INTO todo (task, user_id) VALUES (?, ?)",
4 | "describe": {
5 | "columns": [],
6 | "parameters": {
7 | "Right": 2
8 | },
9 | "nullable": []
10 | },
11 | "hash": "7636fc64c882305305814ffb66676ef09a92d3f1d46021b94ded4e9c073775d1"
12 | }
13 |
--------------------------------------------------------------------------------
/examples/e16_sqlite_database/.sqlx/query-8a7bb6fe3b960d1d10bc8442bb1494f2c758dd890293c313811a8c4acb8edaeb.json:
--------------------------------------------------------------------------------
1 | {
2 | "db_name": "SQLite",
3 | "query": "SELECT task FROM todo WHERE user_id = ? ORDER BY rowid",
4 | "describe": {
5 | "columns": [
6 | {
7 | "name": "task",
8 | "ordinal": 0,
9 | "type_info": "Text"
10 | }
11 | ],
12 | "parameters": {
13 | "Right": 1
14 | },
15 | "nullable": [
16 | false
17 | ]
18 | },
19 | "hash": "8a7bb6fe3b960d1d10bc8442bb1494f2c758dd890293c313811a8c4acb8edaeb"
20 | }
21 |
--------------------------------------------------------------------------------
/examples/e16_sqlite_database/.sqlx/query-90153b8cd85a905a1d5557ad4eb190e9be4cf55d7308973d74cb180cd2323f8a.json:
--------------------------------------------------------------------------------
1 | {
2 | "db_name": "SQLite",
3 | "query": "DELETE FROM todo WHERE rowid = ?",
4 | "describe": {
5 | "columns": [],
6 | "parameters": {
7 | "Right": 1
8 | },
9 | "nullable": []
10 | },
11 | "hash": "90153b8cd85a905a1d5557ad4eb190e9be4cf55d7308973d74cb180cd2323f8a"
12 | }
13 |
--------------------------------------------------------------------------------
/examples/e16_sqlite_database/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "e16_sqlite_database"
3 | version = "0.1.0"
4 | authors = ["my name "]
5 | edition = "2018"
6 |
7 | [dependencies]
8 | serenity = { path = "../../", default-features = false, features = ["client", "gateway", "rustls_backend", "model"] }
9 | tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
10 | sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "sqlite"] }
11 |
--------------------------------------------------------------------------------
/examples/e16_sqlite_database/Makefile.toml:
--------------------------------------------------------------------------------
1 | extend = "../../Makefile.toml"
2 |
3 | [tasks.examples_build]
4 | alias = "build"
5 |
6 | [tasks.examples_build_release]
7 | alias = "build_release"
8 |
9 | [tasks.examples_run]
10 | alias = "run"
11 |
12 | [tasks.examples_run_release]
13 | alias = "run_release"
14 |
--------------------------------------------------------------------------------
/examples/e16_sqlite_database/README.md:
--------------------------------------------------------------------------------
1 | # Setting up the database
2 |
3 | In order to compile the project, a database needs to be set-up. That's because SQLx accesses the
4 | database at compile time to make sure your SQL queries are correct.
5 |
6 | To set up the database, download the [SQLx CLI](https://github.com/launchbadge/sqlx/tree/master/sqlx-cli)
7 | and run `sqlx database setup`. This command will create the database and its tables by applying
8 | the migration files in `migrations/`.
9 |
10 | Most SQLx CLI commands require the `DATABASE_URL` environment variable to be set to the database
11 | URL, for example `sqlite:database.sqlite` (where `sqlite:` is the protocol and `database.sqlite` the
12 | actual filename). A convenient way to supply this information to SQLx is to create a `.env` file
13 | which SQLx automatically detects and reads:
14 |
15 | ```rust
16 | DATABASE_URL=sqlite:database.sqlite
17 | ```
18 |
19 | # Running the example
20 |
21 | ```sh
22 | # Note: due to a bug in SQLx (https://github.com/launchbadge/sqlx/issues/3099),
23 | # you have to provide the full path to `DATABASE_URL` when compiling.
24 | # Once the bug is fixed, you can omit `DATABASE_URL=...` and let SQLx read the `.env` file.
25 | DATABASE_URL=sqlite:examples/e16_sqlite_database/database.sqlite DISCORD_TOKEN=... cargo run
26 | ```
27 |
28 | Interact with the bot via `~todo list`, `~todo add` and `~todo remove`.
29 |
30 | # What are migrations
31 |
32 | In SQLx, migrations are SQL query files that update the database schema. Most SQLx project have at
33 | least one migration file, often called `initial_migration`, which sets up tables initially.
34 |
35 | If you need to modify the database schema in the future, call `sqlx migrate add "MIGRATION NAME"`
36 | and write the migration queries into the newly created .sql file in `migrations/`.
37 |
38 | # Make it easy to host your bot
39 |
40 | Normally, users have to download and install SQLx CLI in order to compile your bot. Remember:
41 | SQLx accesses the database at compile time. However, you can enable building in "offline mode":
42 | https://github.com/launchbadge/sqlx/tree/master/sqlx-cli#enable-building-in-offline-mode-with-query.
43 | That way, your bot will work out-of-the-box with `cargo run`.
44 |
45 | Note that users still have to set `SQLX_OFFLINE` to `true` even if `sqlx-data.json` is present.
46 |
47 | Tip: create a git pre-commit hook which executes `cargo sqlx prepare` for you before every commit.
48 | See the `pre-commit` file for an example. Copy the file into `.git/hooks` to install the pre-commit
49 | hook.
50 |
51 | # Using SQLx
52 |
53 | SQLx's GitHub repository explains a lot about SQLx, like the difference between `query!` and
54 | `query_as!`. Please follow the links to learn more:
55 |
56 | - SQLx: https://github.com/launchbadge/sqlx
57 | - SQLx CLI: https://github.com/launchbadge/sqlx/tree/master/sqlx-cli
58 |
--------------------------------------------------------------------------------
/examples/e16_sqlite_database/migrations/20210906145552_initial_migration.sql:
--------------------------------------------------------------------------------
1 | -- Add migration script here
2 | CREATE TABLE todo (
3 | task TEXT NOT NULL,
4 | user_id INTEGER NOT NULL
5 | )
6 |
--------------------------------------------------------------------------------
/examples/e16_sqlite_database/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | echo "Caching SQL query analysis..."
4 | cargo sqlx prepare
5 | git add sqlx-data.json
6 |
--------------------------------------------------------------------------------
/examples/e17_message_components/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "e17_message_components"
3 | version = "0.1.0"
4 | authors = ["my name "]
5 | edition = "2018"
6 |
7 | [dependencies]
8 | serenity = { path = "../../", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "collector"] }
9 | tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
10 | dotenv = { version = "0.15.0" }
11 |
--------------------------------------------------------------------------------
/examples/e17_message_components/Makefile.toml:
--------------------------------------------------------------------------------
1 | extend = "../../Makefile.toml"
2 |
3 | [tasks.examples_build]
4 | alias = "build"
5 |
6 | [tasks.examples_build_release]
7 | alias = "build_release"
8 |
9 | [tasks.examples_run]
10 | alias = "run"
11 |
12 | [tasks.examples_run_release]
13 | alias = "run_release"
14 |
--------------------------------------------------------------------------------
/examples/e18_webhook/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "e18_webhook"
3 | version = "0.1.0"
4 | authors = ["my name "]
5 | edition = "2018"
6 |
7 | [dependencies]
8 | serenity = { path = "../../", default-features = false, features = ["rustls_backend", "model"] }
9 | tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
10 |
--------------------------------------------------------------------------------
/examples/e18_webhook/Makefile.toml:
--------------------------------------------------------------------------------
1 | extend = "../../Makefile.toml"
2 |
3 | [tasks.examples_build]
4 | alias = "build"
5 |
6 | [tasks.examples_build_release]
7 | alias = "build_release"
8 |
9 | [tasks.examples_run]
10 | alias = "run"
11 |
12 | [tasks.examples_run_release]
13 | alias = "run_release"
14 |
--------------------------------------------------------------------------------
/examples/e18_webhook/src/main.rs:
--------------------------------------------------------------------------------
1 | use serenity::builder::ExecuteWebhook;
2 | use serenity::http::Http;
3 | use serenity::model::webhook::Webhook;
4 |
5 | #[tokio::main]
6 | async fn main() {
7 | // You don't need a token when you are only dealing with webhooks.
8 | let http = Http::new("");
9 | let webhook = Webhook::from_url(&http, "https://discord.com/api/webhooks/133742013374206969/hello-there-oPNtRN5UY5DVmBe7m1N0HE-replace-me-Dw9LRkgq3zI7LoW3Rb-k-q")
10 | .await
11 | .expect("Replace the webhook with your own");
12 |
13 | let builder = ExecuteWebhook::new().content("hello there").username("Webhook test");
14 | webhook.execute(&http, false, builder).await.expect("Could not execute webhook.");
15 | }
16 |
--------------------------------------------------------------------------------
/examples/e19_interactions_endpoint/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "e19_interactions_endpoint"
3 | version = "0.1.0"
4 | authors = ["my name "]
5 | edition = "2018"
6 |
7 | [dependencies]
8 | serenity = { path = "../../", default-features = false, features = ["builder", "interactions_endpoint"] }
9 | tiny_http = "0.12.0"
10 |
--------------------------------------------------------------------------------
/examples/e19_interactions_endpoint/Makefile.toml:
--------------------------------------------------------------------------------
1 | extend = "../../Makefile.toml"
2 |
3 | [tasks.examples_build]
4 | alias = "build"
5 |
6 | [tasks.examples_build_release]
7 | alias = "build_release"
8 |
9 | [tasks.examples_run]
10 | alias = "run"
11 |
12 | [tasks.examples_run_release]
13 | alias = "run_release"
14 |
--------------------------------------------------------------------------------
/examples/e19_interactions_endpoint/src/main.rs:
--------------------------------------------------------------------------------
1 | use serenity::builder::*;
2 | use serenity::interactions_endpoint::Verifier;
3 | use serenity::json;
4 | use serenity::model::application::*;
5 |
6 | type Error = Box;
7 |
8 | fn handle_command(interaction: CommandInteraction) -> CreateInteractionResponse {
9 | CreateInteractionResponse::Message(CreateInteractionResponseMessage::new().content(format!(
10 | "Hello from interactions webhook HTTP server! <@{}>",
11 | interaction.user.id
12 | )))
13 | }
14 |
15 | fn handle_request(
16 | mut request: tiny_http::Request,
17 | body: &mut Vec,
18 | verifier: &Verifier,
19 | ) -> Result<(), Error> {
20 | println!("Received request from {:?}", request.remote_addr());
21 |
22 | // Read the request body (containing the interaction JSON)
23 | body.clear();
24 | request.as_reader().read_to_end(body)?;
25 |
26 | // Reject request if it fails cryptographic verification
27 | // Discord rejects the interaction endpoints URL if this check is not done
28 | // (This part is very specific to your HTTP server crate of choice, so serenity cannot abstract
29 | // away the boilerplate)
30 | let find_header =
31 | |name| Some(request.headers().iter().find(|h| h.field.equiv(name))?.value.as_str());
32 | let signature = find_header("X-Signature-Ed25519").ok_or("missing signature header")?;
33 | let timestamp = find_header("X-Signature-Timestamp").ok_or("missing timestamp header")?;
34 | if verifier.verify(signature, timestamp, body).is_err() {
35 | request.respond(tiny_http::Response::empty(401))?;
36 | return Ok(());
37 | }
38 |
39 | // Build Discord response
40 | let response = match json::from_slice::(body)? {
41 | // Discord rejects the interaction endpoints URL if pings are not acknowledged
42 | Interaction::Ping(_) => CreateInteractionResponse::Pong,
43 | Interaction::Command(interaction) => handle_command(interaction),
44 | _ => return Ok(()),
45 | };
46 |
47 | // Send the Discord response back via HTTP
48 | request.respond(
49 | tiny_http::Response::from_data(json::to_vec(&response)?)
50 | .with_header("Content-Type: application/json".parse::().unwrap()),
51 | )?;
52 |
53 | Ok(())
54 | }
55 |
56 | fn main() -> Result<(), Error> {
57 | // Change this string to the Public Key value in your bot dashboard
58 | let verifier =
59 | Verifier::new("67c6bd767ca099e79efac9fcce4d2022a63bf7dea780e7f3d813f694c1597089");
60 |
61 | // Setup an HTTP server and listen for incoming interaction requests
62 | // Choose any port here (but be consistent with the interactions endpoint URL in your bot
63 | // dashboard)
64 | let server = tiny_http::Server::http("0.0.0.0:8787")?;
65 | let mut body = Vec::new();
66 | loop {
67 | let request = server.recv()?;
68 | if let Err(e) = handle_request(request, &mut body, &verifier) {
69 | eprintln!("Error while handling request: {e}");
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/examples/testing/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "testing"
3 | version = "0.1.0"
4 | authors = ["my name "]
5 | edition = "2018"
6 |
7 | [dependencies]
8 | serenity = { path = "../../", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "cache", "collector"] }
9 | tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
10 | env_logger = "0.10.0"
11 |
--------------------------------------------------------------------------------
/examples/testing/Makefile.toml:
--------------------------------------------------------------------------------
1 | extend = "../../Makefile.toml"
2 |
3 | [tasks.examples_build]
4 | alias = "build"
5 |
6 | [tasks.examples_build_release]
7 | alias = "build_release"
8 |
9 | [tasks.examples_run]
10 | alias = "run"
11 |
12 | [tasks.examples_run_release]
13 | alias = "run_release"
14 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/serenity-rs/serenity/bb9610216eed49ba0c1fb9bc54fd0219d759df00/logo.png
--------------------------------------------------------------------------------
/rustfmt.toml:
--------------------------------------------------------------------------------
1 | edition = "2021"
2 | match_block_trailing_comma = true
3 | newline_style = "Unix"
4 | use_field_init_shorthand = true
5 | use_small_heuristics = "Max"
6 | use_try_shorthand = true
7 |
8 | # Turn on once the rustfmt supporting these becomes available on rustup
9 | # width_heuristics = "Max"
10 |
11 | # nightly/unstable features
12 | wrap_comments = true
13 | comment_width = 100
14 | format_code_in_doc_comments = true
15 | group_imports = "StdExternalCrate"
16 | imports_granularity = "Module"
17 | imports_layout = "HorizontalVertical"
18 | match_arm_blocks = true
19 | normalize_comments = true
20 | overflow_delimited_expr = true
21 | struct_lit_single_line = false
22 |
--------------------------------------------------------------------------------
/src/builder/add_member.rs:
--------------------------------------------------------------------------------
1 | #[cfg(feature = "http")]
2 | use super::Builder;
3 | #[cfg(feature = "http")]
4 | use crate::http::CacheHttp;
5 | #[cfg(feature = "http")]
6 | use crate::internal::prelude::*;
7 | use crate::model::prelude::*;
8 |
9 | /// A builder to add parameters when using [`GuildId::add_member`].
10 | ///
11 | /// [Discord docs](https://discord.com/developers/docs/resources/guild#add-guild-member).
12 | #[derive(Clone, Debug, Serialize)]
13 | #[must_use]
14 | pub struct AddMember {
15 | access_token: String,
16 | #[serde(skip_serializing_if = "Option::is_none")]
17 | nick: Option,
18 | #[serde(skip_serializing_if = "Vec::is_empty")]
19 | roles: Vec,
20 | #[serde(skip_serializing_if = "Option::is_none")]
21 | mute: Option,
22 | #[serde(skip_serializing_if = "Option::is_none")]
23 | deaf: Option,
24 | }
25 |
26 | impl AddMember {
27 | /// Constructs a new builder with the given access token, leaving all other fields empty.
28 | pub fn new(access_token: String) -> Self {
29 | Self {
30 | access_token,
31 | nick: None,
32 | roles: Vec::new(),
33 | mute: None,
34 | deaf: None,
35 | }
36 | }
37 |
38 | /// Sets the OAuth2 access token for this request, replacing the current one.
39 | ///
40 | /// Requires the access token to have the `guilds.join` scope granted.
41 | pub fn access_token(mut self, access_token: impl Into) -> Self {
42 | self.access_token = access_token.into();
43 | self
44 | }
45 |
46 | /// Sets the member's nickname.
47 | ///
48 | /// Requires the [Manage Nicknames] permission.
49 | ///
50 | /// [Manage Nicknames]: crate::model::permissions::Permissions::MANAGE_NICKNAMES
51 | pub fn nickname(mut self, nickname: impl Into) -> Self {
52 | self.nick = Some(nickname.into());
53 | self
54 | }
55 |
56 | /// Sets the list of roles that the member should have.
57 | ///
58 | /// Requires the [Manage Roles] permission.
59 | ///
60 | /// [Manage Roles]: crate::model::permissions::Permissions::MANAGE_ROLES
61 | pub fn roles(mut self, roles: impl IntoIterator- >) -> Self {
62 | self.roles = roles.into_iter().map(Into::into).collect();
63 | self
64 | }
65 |
66 | /// Whether to mute the member.
67 | ///
68 | /// Requires the [Mute Members] permission.
69 | ///
70 | /// [Mute Members]: crate::model::permissions::Permissions::MUTE_MEMBERS
71 | pub fn mute(mut self, mute: bool) -> Self {
72 | self.mute = Some(mute);
73 | self
74 | }
75 |
76 | /// Whether to deafen the member.
77 | ///
78 | /// Requires the [Deafen Members] permission.
79 | ///
80 | /// [Deafen Members]: crate::model::permissions::Permissions::DEAFEN_MEMBERS
81 | pub fn deafen(mut self, deafen: bool) -> Self {
82 | self.deaf = Some(deafen);
83 | self
84 | }
85 | }
86 |
87 | #[cfg(feature = "http")]
88 | #[async_trait::async_trait]
89 | impl Builder for AddMember {
90 | type Context<'ctx> = (GuildId, UserId);
91 | type Built = Option;
92 |
93 | /// Adds a [`User`] to this guild with a valid OAuth2 access token.
94 | ///
95 | /// Returns the created [`Member`] object, or nothing if the user is already a member of the
96 | /// guild.
97 | ///
98 | /// # Errors
99 | ///
100 | /// Returns [`Error::Http`] if the current user lacks permission, or if invalid data is given.
101 | async fn execute(
102 | self,
103 | cache_http: impl CacheHttp,
104 | ctx: Self::Context<'_>,
105 | ) -> Result {
106 | cache_http.http().add_guild_member(ctx.0, ctx.1, &self).await
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/builder/bot_auth_parameters.rs:
--------------------------------------------------------------------------------
1 | use arrayvec::ArrayVec;
2 | use url::Url;
3 |
4 | #[cfg(feature = "http")]
5 | use crate::http::Http;
6 | #[cfg(feature = "http")]
7 | use crate::internal::prelude::*;
8 | use crate::model::prelude::*;
9 |
10 | /// A builder for constructing an invite link with custom OAuth2 scopes.
11 | #[derive(Debug, Clone, Default)]
12 | #[must_use]
13 | pub struct CreateBotAuthParameters {
14 | client_id: Option,
15 | scopes: Vec,
16 | permissions: Permissions,
17 | guild_id: Option,
18 | disable_guild_select: bool,
19 | }
20 |
21 | impl CreateBotAuthParameters {
22 | /// Equivalent to [`Self::default`].
23 | pub fn new() -> Self {
24 | Self::default()
25 | }
26 |
27 | /// Builds the url with the provided data.
28 | #[must_use]
29 | pub fn build(self) -> String {
30 | let mut valid_data = ArrayVec::<_, 5>::new();
31 | let bits = self.permissions.bits();
32 |
33 | if let Some(client_id) = self.client_id {
34 | valid_data.push(("client_id", client_id.to_string()));
35 | }
36 |
37 | if !self.scopes.is_empty() {
38 | valid_data.push((
39 | "scope",
40 | self.scopes.iter().map(ToString::to_string).collect::>().join(" "),
41 | ));
42 | }
43 |
44 | if bits != 0 {
45 | valid_data.push(("permissions", bits.to_string()));
46 | }
47 |
48 | if let Some(guild_id) = self.guild_id {
49 | valid_data.push(("guild", guild_id.to_string()));
50 | }
51 |
52 | if self.disable_guild_select {
53 | valid_data.push(("disable_guild_select", self.disable_guild_select.to_string()));
54 | }
55 |
56 | let url = Url::parse_with_params("https://discord.com/api/oauth2/authorize", &valid_data)
57 | .expect("failed to construct URL");
58 |
59 | url.to_string()
60 | }
61 |
62 | /// Specify the client Id of your application.
63 | pub fn client_id(mut self, client_id: impl Into) -> Self {
64 | self.client_id = Some(client_id.into());
65 | self
66 | }
67 |
68 | /// Automatically fetch and set the client Id of your application by inquiring Discord's API.
69 | ///
70 | /// # Errors
71 | ///
72 | /// Returns an [`HttpError::UnsuccessfulRequest`] if the user is not authorized for this
73 | /// endpoint.
74 | ///
75 | /// [`HttpError::UnsuccessfulRequest`]: crate::http::HttpError::UnsuccessfulRequest
76 | #[cfg(feature = "http")]
77 | pub async fn auto_client_id(mut self, http: impl AsRef) -> Result {
78 | self.client_id = http.as_ref().get_current_application_info().await.map(|v| Some(v.id))?;
79 | Ok(self)
80 | }
81 |
82 | /// Specify the scopes for your application.
83 | ///
84 | /// **Note**: This needs to include the [`Bot`] scope.
85 | ///
86 | /// [`Bot`]: Scope::Bot
87 | pub fn scopes(mut self, scopes: &[Scope]) -> Self {
88 | self.scopes = scopes.to_vec();
89 | self
90 | }
91 |
92 | /// Specify the permissions your application requires.
93 | pub fn permissions(mut self, permissions: Permissions) -> Self {
94 | self.permissions = permissions;
95 | self
96 | }
97 |
98 | /// Specify the Id of the guild to prefill the dropdown picker for the user.
99 | pub fn guild_id(mut self, guild_id: impl Into) -> Self {
100 | self.guild_id = Some(guild_id.into());
101 | self
102 | }
103 |
104 | /// Specify whether the user cannot change the guild in the dropdown picker.
105 | pub fn disable_guild_select(mut self, disable: bool) -> Self {
106 | self.disable_guild_select = disable;
107 | self
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/builder/create_forum_tag.rs:
--------------------------------------------------------------------------------
1 | use crate::model::prelude::*;
2 |
3 | /// [Discord docs](https://discord.com/developers/docs/resources/channel#forum-tag-object-forum-tag-structure)
4 | ///
5 | /// Contrary to the [`ForumTag`] struct, only the name field is required.
6 | #[must_use]
7 | #[derive(Clone, Debug, Serialize)]
8 | pub struct CreateForumTag {
9 | name: String,
10 | moderated: bool,
11 | emoji_id: Option,
12 | emoji_name: Option,
13 | }
14 |
15 | impl CreateForumTag {
16 | pub fn new(name: impl Into) -> Self {
17 | Self {
18 | name: name.into(),
19 | moderated: false,
20 | emoji_id: None,
21 | emoji_name: None,
22 | }
23 | }
24 |
25 | pub fn moderated(mut self, moderated: bool) -> Self {
26 | self.moderated = moderated;
27 | self
28 | }
29 |
30 | pub fn emoji(mut self, emoji: impl Into) -> Self {
31 | match emoji.into() {
32 | ReactionType::Custom {
33 | id, ..
34 | } => {
35 | self.emoji_id = Some(id);
36 | self.emoji_name = None;
37 | },
38 | ReactionType::Unicode(unicode_emoji) => {
39 | self.emoji_id = None;
40 | self.emoji_name = Some(unicode_emoji);
41 | },
42 | }
43 | self
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/builder/create_stage_instance.rs:
--------------------------------------------------------------------------------
1 | #[cfg(feature = "http")]
2 | use super::Builder;
3 | #[cfg(feature = "http")]
4 | use crate::http::CacheHttp;
5 | #[cfg(feature = "http")]
6 | use crate::internal::prelude::*;
7 | use crate::model::prelude::*;
8 |
9 | /// Builder for creating a stage instance
10 | ///
11 | /// [Discord docs](https://discord.com/developers/docs/resources/stage-instance#create-stage-instance)
12 | #[derive(Clone, Debug, Serialize)]
13 | #[must_use]
14 | pub struct CreateStageInstance<'a> {
15 | channel_id: Option, // required field, filled in Builder impl
16 | topic: String,
17 | privacy_level: StageInstancePrivacyLevel,
18 | #[serde(skip_serializing_if = "Option::is_none")]
19 | send_start_notification: Option,
20 |
21 | #[serde(skip)]
22 | audit_log_reason: Option<&'a str>,
23 | }
24 |
25 | impl<'a> CreateStageInstance<'a> {
26 | /// Creates a builder with the provided topic.
27 | pub fn new(topic: impl Into) -> Self {
28 | Self {
29 | channel_id: None,
30 | topic: topic.into(),
31 | privacy_level: StageInstancePrivacyLevel::default(),
32 | send_start_notification: None,
33 | audit_log_reason: None,
34 | }
35 | }
36 |
37 | /// Sets the topic of the stage channel instance, replacing the current value as set in
38 | /// [`Self::new`].
39 | pub fn topic(mut self, topic: impl Into) -> Self {
40 | self.topic = topic.into();
41 | self
42 | }
43 |
44 | /// Whether or not to notify @everyone that a stage instance has started.
45 | pub fn send_start_notification(mut self, send_start_notification: bool) -> Self {
46 | self.send_start_notification = Some(send_start_notification);
47 | self
48 | }
49 |
50 | /// Sets the request's audit log reason.
51 | pub fn audit_log_reason(mut self, reason: &'a str) -> Self {
52 | self.audit_log_reason = Some(reason);
53 | self
54 | }
55 | }
56 |
57 | #[cfg(feature = "http")]
58 | #[async_trait::async_trait]
59 | impl Builder for CreateStageInstance<'_> {
60 | type Context<'ctx> = ChannelId;
61 | type Built = StageInstance;
62 |
63 | /// Creates the stage instance.
64 | ///
65 | /// # Errors
66 | ///
67 | /// Returns [`Error::Http`] if there is already a stage instance currently.
68 | async fn execute(
69 | mut self,
70 | cache_http: impl CacheHttp,
71 | ctx: Self::Context<'_>,
72 | ) -> Result {
73 | self.channel_id = Some(ctx);
74 | cache_http.http().create_stage_instance(&self, self.audit_log_reason).await
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/builder/create_sticker.rs:
--------------------------------------------------------------------------------
1 | #[cfg(feature = "http")]
2 | use super::Builder;
3 | use super::CreateAttachment;
4 | #[cfg(feature = "http")]
5 | use crate::http::CacheHttp;
6 | #[cfg(feature = "http")]
7 | use crate::internal::prelude::*;
8 | #[cfg(feature = "http")]
9 | use crate::model::prelude::*;
10 |
11 | /// A builder to create a guild sticker
12 | ///
13 | /// [Discord docs](https://discord.com/developers/docs/resources/sticker#create-guild-sticker)
14 | #[derive(Clone, Debug)]
15 | #[must_use]
16 | pub struct CreateSticker<'a> {
17 | name: String,
18 | description: String,
19 | tags: String,
20 | file: CreateAttachment,
21 | audit_log_reason: Option<&'a str>,
22 | }
23 |
24 | impl<'a> CreateSticker<'a> {
25 | /// Creates a new builder with the given data. All of this builder's fields are required.
26 | pub fn new(name: impl Into, file: CreateAttachment) -> Self {
27 | Self {
28 | name: name.into(),
29 | tags: String::new(),
30 | description: String::new(),
31 | file,
32 | audit_log_reason: None,
33 | }
34 | }
35 |
36 | /// Set the name of the sticker, replacing the current value as set in [`Self::new`].
37 | ///
38 | /// **Note**: Must be between 2 and 30 characters long.
39 | pub fn name(mut self, name: impl Into) -> Self {
40 | self.name = name.into();
41 | self
42 | }
43 |
44 | /// Set the description of the sticker.
45 | ///
46 | /// **Note**: Must be empty or 2-100 characters.
47 | pub fn description(mut self, description: impl Into) -> Self {
48 | self.description = description.into();
49 | self
50 | }
51 |
52 | /// The Discord name of a unicode emoji representing the sticker's expression.
53 | ///
54 | /// **Note**: Max 200 characters long.
55 | pub fn tags(mut self, tags: impl Into) -> Self {
56 | self.tags = tags.into();
57 | self
58 | }
59 |
60 | /// Set the sticker file. Replaces the current value as set in [`Self::new`].
61 | ///
62 | /// **Note**: Must be a PNG, APNG, or Lottie JSON file, max 500 KB.
63 | pub fn file(mut self, file: CreateAttachment) -> Self {
64 | self.file = file;
65 | self
66 | }
67 |
68 | /// Sets the request's audit log reason.
69 | pub fn audit_log_reason(mut self, reason: &'a str) -> Self {
70 | self.audit_log_reason = Some(reason);
71 | self
72 | }
73 | }
74 |
75 | #[cfg(feature = "http")]
76 | #[async_trait::async_trait]
77 | impl Builder for CreateSticker<'_> {
78 | type Context<'ctx> = GuildId;
79 | type Built = Sticker;
80 |
81 | /// Creates a new sticker in the guild with the data set, if any.
82 | ///
83 | /// **Note**: Requires the [Create Guild Expressions] permission.
84 | ///
85 | /// # Errors
86 | ///
87 | /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] if the current user
88 | /// lacks permission. Otherwise returns [`Error::Http`], as well as if invalid data is given.
89 | ///
90 | /// [Create Guild Expressions]: Permissions::CREATE_GUILD_EXPRESSIONS
91 | async fn execute(
92 | self,
93 | cache_http: impl CacheHttp,
94 | ctx: Self::Context<'_>,
95 | ) -> Result {
96 | #[cfg(feature = "cache")]
97 | crate::utils::user_has_guild_perms(
98 | &cache_http,
99 | ctx,
100 | Permissions::CREATE_GUILD_EXPRESSIONS,
101 | )?;
102 |
103 | let map = [("name", self.name), ("tags", self.tags), ("description", self.description)];
104 | cache_http.http().create_sticker(ctx, map, self.file, self.audit_log_reason).await
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/builder/create_webhook.rs:
--------------------------------------------------------------------------------
1 | #[cfg(feature = "http")]
2 | use super::Builder;
3 | use super::CreateAttachment;
4 | #[cfg(feature = "http")]
5 | use crate::http::CacheHttp;
6 | #[cfg(feature = "http")]
7 | use crate::internal::prelude::*;
8 | #[cfg(feature = "http")]
9 | use crate::model::prelude::*;
10 |
11 | /// [Discord docs](https://discord.com/developers/docs/resources/webhook#create-webhook)
12 | #[derive(Clone, Debug, Serialize)]
13 | #[must_use]
14 | pub struct CreateWebhook<'a> {
15 | name: String,
16 | #[serde(skip_serializing_if = "Option::is_none")]
17 | avatar: Option,
18 |
19 | #[serde(skip)]
20 | audit_log_reason: Option<&'a str>,
21 | }
22 |
23 | impl<'a> CreateWebhook<'a> {
24 | /// Creates a new builder with the given webhook name, leaving all other fields empty.
25 | pub fn new(name: impl Into) -> Self {
26 | Self {
27 | name: name.into(),
28 | avatar: None,
29 | audit_log_reason: None,
30 | }
31 | }
32 |
33 | /// Set the webhook's name, replacing the current value as set in [`Self::new`].
34 | ///
35 | /// This must be between 1-80 characters.
36 | pub fn name(mut self, name: impl Into) -> Self {
37 | self.name = name.into();
38 | self
39 | }
40 |
41 | /// Set the webhook's default avatar.
42 | pub fn avatar(mut self, avatar: &CreateAttachment) -> Self {
43 | self.avatar = Some(avatar.to_base64());
44 | self
45 | }
46 |
47 | /// Sets the request's audit log reason.
48 | pub fn audit_log_reason(mut self, reason: &'a str) -> Self {
49 | self.audit_log_reason = Some(reason);
50 | self
51 | }
52 | }
53 |
54 | #[cfg(feature = "http")]
55 | #[async_trait::async_trait]
56 | impl Builder for CreateWebhook<'_> {
57 | type Context<'ctx> = ChannelId;
58 | type Built = Webhook;
59 |
60 | /// Creates the webhook.
61 | ///
62 | /// # Errors
63 | ///
64 | /// If the provided name is less than 2 characters, returns [`ModelError::NameTooShort`]. If it
65 | /// is more than 100 characters, returns [`ModelError::NameTooLong`].
66 | ///
67 | /// Returns a [`Error::Http`] if the current user lacks permission, or if invalid data is
68 | /// given.
69 | ///
70 | /// [`Text`]: ChannelType::Text
71 | /// [`News`]: ChannelType::News
72 | async fn execute(
73 | self,
74 | cache_http: impl CacheHttp,
75 | ctx: Self::Context<'_>,
76 | ) -> Result {
77 | if self.name.len() < 2 {
78 | return Err(Error::Model(ModelError::NameTooShort));
79 | } else if self.name.len() > 100 {
80 | return Err(Error::Model(ModelError::NameTooLong));
81 | }
82 |
83 | cache_http.http().create_webhook(ctx, &self, self.audit_log_reason).await
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/builder/edit_guild_widget.rs:
--------------------------------------------------------------------------------
1 | #[cfg(feature = "http")]
2 | use super::Builder;
3 | #[cfg(feature = "http")]
4 | use crate::http::CacheHttp;
5 | #[cfg(feature = "http")]
6 | use crate::internal::prelude::*;
7 | use crate::model::prelude::*;
8 |
9 | /// A builder to specify the fields to edit in a [`GuildWidget`].
10 | ///
11 | /// [Discord docs](https://discord.com/developers/docs/resources/guild#modify-guild-widget)
12 | #[derive(Clone, Debug, Default, Serialize)]
13 | #[must_use]
14 | pub struct EditGuildWidget<'a> {
15 | #[serde(skip_serializing_if = "Option::is_none")]
16 | enabled: Option,
17 | #[serde(skip_serializing_if = "Option::is_none")]
18 | channel_id: Option,
19 |
20 | #[serde(skip)]
21 | audit_log_reason: Option<&'a str>,
22 | }
23 |
24 | impl<'a> EditGuildWidget<'a> {
25 | /// Equivalent to [`Self::default`].
26 | pub fn new() -> Self {
27 | Self::default()
28 | }
29 |
30 | /// Whether the widget is enabled or not.
31 | pub fn enabled(mut self, enabled: bool) -> Self {
32 | self.enabled = Some(enabled);
33 | self
34 | }
35 |
36 | /// The server description shown in the welcome screen.
37 | pub fn channel_id(mut self, id: impl Into) -> Self {
38 | self.channel_id = Some(id.into());
39 | self
40 | }
41 |
42 | /// Sets the request's audit log reason.
43 | pub fn audit_log_reason(mut self, reason: &'a str) -> Self {
44 | self.audit_log_reason = Some(reason);
45 | self
46 | }
47 | }
48 |
49 | #[cfg(feature = "http")]
50 | #[async_trait::async_trait]
51 | impl Builder for EditGuildWidget<'_> {
52 | type Context<'ctx> = GuildId;
53 | type Built = GuildWidget;
54 |
55 | /// Edits the guild's widget.
56 | ///
57 | /// **Note**: Requires the [Manage Guild] permission.
58 | ///
59 | /// # Errors
60 | ///
61 | /// Returns [`Error::Http`] if the current user lacks permission.
62 | ///
63 | /// [Manage Guild]: Permissions::MANAGE_GUILD
64 | async fn execute(
65 | self,
66 | cache_http: impl CacheHttp,
67 | ctx: Self::Context<'_>,
68 | ) -> Result {
69 | cache_http.http().edit_guild_widget(ctx, &self, self.audit_log_reason).await
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/builder/edit_profile.rs:
--------------------------------------------------------------------------------
1 | #[cfg(feature = "http")]
2 | use super::Builder;
3 | use super::CreateAttachment;
4 | #[cfg(feature = "http")]
5 | use crate::http::CacheHttp;
6 | #[cfg(feature = "http")]
7 | use crate::internal::prelude::*;
8 | #[cfg(feature = "http")]
9 | use crate::model::user::CurrentUser;
10 |
11 | /// A builder to edit the current user's settings, to be used in conjunction with
12 | /// [`CurrentUser::edit`].
13 | ///
14 | /// [Discord docs](https://discord.com/developers/docs/resources/user#modify-current-user)
15 | #[derive(Clone, Debug, Default, Serialize)]
16 | #[must_use]
17 | pub struct EditProfile {
18 | #[serde(skip_serializing_if = "Option::is_none")]
19 | username: Option,
20 | #[serde(skip_serializing_if = "Option::is_none")]
21 | avatar: Option