Skip to main content

Cache

Struct Cache 

Source
pub struct Cache {
    pub guilds: RwLock<HashMap<Snowflake, Guild>>,
    pub channels: RwLock<HashMap<Snowflake, Channel>>,
    pub users: RwLock<HashMap<Snowflake, User>>,
    pub current_user: RwLock<Option<User>>,
}
Expand description

In-memory gateway cache.

Fields§

§guilds: RwLock<HashMap<Snowflake, Guild>>§channels: RwLock<HashMap<Snowflake, Channel>>§users: RwLock<HashMap<Snowflake, User>>§current_user: RwLock<Option<User>>

Implementations§

Source§

impl Cache

Source

pub fn new() -> Arc<Self>

Source

pub async fn guild(&self, id: &str) -> Option<Guild>

Looks up a guild by ID. Populated from GUILD_CREATE / GUILD_UPDATE events.

Source

pub async fn channel(&self, id: &str) -> Option<Channel>

Looks up a channel by ID. Populated from GUILD_CREATE and CHANNEL_CREATE / CHANNEL_UPDATE events.

Examples found in repository?
examples/bot.rs (line 29)
27    async fn on_message(&self, ctx: Context, msg: Message) {
28        let user_cached = ctx.cache.user(&msg.author.id).await.is_some();
29        let ch_cached   = ctx.cache.channel(msg.channel_id.as_deref().unwrap_or("")).await.is_some();
30        let content_preview = msg.content.as_deref().unwrap_or("").chars().take(60).collect::<String>();
31        let attachments = msg.attachments.as_ref().map(|a| a.len()).unwrap_or(0);
32        let embeds = msg.embeds.as_ref().map(|e| e.len()).unwrap_or(0);
33        println!(
34            "[msg] author={}#{} channel={} guild={} | \"{}\" | attach={} embeds={} | cache: user={} ch={}",
35            msg.author.username,
36            msg.author.discriminator.as_deref().unwrap_or("0"),
37            msg.channel_id.as_deref().unwrap_or("?"),
38            msg.guild_id.as_deref().unwrap_or("DM"),
39            content_preview,
40            attachments,
41            embeds,
42            user_cached,
43            ch_cached,
44        );
45
46        if msg.author.bot.unwrap_or(false) {
47            return;
48        }
49
50        let content = match msg.content.as_deref() {
51            Some(c) => c,
52            None => return,
53        };
54
55        let channel_id = msg.channel_id.as_deref().unwrap_or_default();
56
57        let (cmd, args) = match parse_command(content) {
58            Some(v) => v,
59            None => return,
60        };
61
62        match cmd {
63            "ping" => {
64                let start = Instant::now();
65                let sent = ctx.http.send_message(channel_id, "Pong!").await;
66                let elapsed = start.elapsed().as_millis();
67
68                if let Ok(sent) = sent {
69                    let _ = ctx.http.edit_message(
70                        channel_id,
71                        &sent.id,
72                        &format!("Pong! {}ms", elapsed),
73                    ).await;
74                }
75            }
76
77            "say" => {
78                if args.is_empty() {
79                    let _ = ctx.http.send_message(channel_id, "Say what?").await;
80                    return;
81                }
82                let _ = ctx.http.delete_message(channel_id, &msg.id).await;
83                let _ = ctx.http.send_message(channel_id, args).await;
84            }
85
86            "embed" => {
87                let (title, desc) = match args.split_once('|') {
88                    Some((t, d)) => (t.trim(), d.trim()),
89                    None => {
90                        let _ = ctx.http.send_message(channel_id, "`!embed title | description`").await;
91                        return;
92                    }
93                };
94
95                let embed = EmbedBuilder::new()
96                    .title(title)
97                    .description(desc)
98                    .color(0x5865F2)
99                    .build();
100
101                let _ = ctx.http.send_embed(channel_id, None, vec![embed]).await;
102            }
103
104            "react" => {
105                let _ = ctx.http.add_reaction(channel_id, &msg.id, "❤️").await;
106            }
107
108            "purge" => {
109                let count: u8 = args.parse().unwrap_or(0);
110                if count == 0 || count > 100 {
111                    let _ = ctx.http.send_message(channel_id, "1-100.").await;
112                    return;
113                }
114
115                let query = GetMessagesQuery {
116                    limit: Some(count),
117                    ..Default::default()
118                };
119
120                if let Ok(messages) = ctx.http.get_messages(channel_id, query).await {
121                    let ids: Vec<&str> = messages.iter().map(|m| m.id.as_str()).collect();
122                    let _ = ctx.http.bulk_delete_messages(channel_id, ids).await;
123                }
124            }
125
126            "serverinfo" => {
127                let guild_id = match &msg.guild_id {
128                    Some(id) => id.as_str(),
129                    None => return,
130                };
131
132                if let Ok(guild) = ctx.http.get_guild(guild_id).await {
133                    let name = guild.name.as_deref().unwrap_or("Unknown");
134
135                    let members = ctx.http.get_guild_members(guild_id, Some(1000), None).await
136                        .map(|m| m.len().to_string())
137                        .unwrap_or("?".into());
138
139                    let embed = EmbedBuilder::new()
140                        .title(name)
141                        .field("Members", &members, true)
142                        .color(0x5865F2)
143                        .build();
144
145                    let _ = ctx.http.send_embed(channel_id, None, vec![embed]).await;
146                }
147            }
148
149            "attach" => {
150                if args.is_empty() {
151                    let _ = ctx.http.send_message(channel_id, "Usage: `!attach <file path>`").await;
152                    return;
153                }
154                let path = std::path::Path::new(args);
155                let filename = path
156                    .file_name()
157                    .and_then(|n| n.to_str())
158                    .unwrap_or("file")
159                    .to_string();
160                let content_type = match path.extension().and_then(|e| e.to_str()) {
161                    Some("mp3") => "audio/mpeg",
162                    Some("mp4") => "video/mp4",
163                    Some("mov") => "video/quicktime",
164                    Some("webm") => "video/webm",
165                    Some("png") => "image/png",
166                    Some("jpg") | Some("jpeg") => "image/jpeg",
167                    Some("gif") => "image/gif",
168                    Some("webp") => "image/webp",
169                    Some("txt") => "text/plain",
170                    Some("pdf") => "application/pdf",
171                    _ => "application/octet-stream",
172                };
173                match tokio::fs::read(path).await {
174                    Ok(data) => {
175                        let file = AttachmentFile {
176                            filename,
177                            data,
178                            content_type: Some(content_type.to_string()),
179                        };
180                        match ctx.http.send_files(channel_id, vec![file], None).await {
181                            Ok(msg) => println!("[attach] sent message {}", msg.id),
182                            Err(e) => eprintln!("[attach] error: {}", e),
183                        }
184                    }
185                    Err(e) => {
186                        eprintln!("[attach] failed to read file: {}", e);
187                        let _ = ctx.http.send_message(channel_id, &format!("Failed to read file: {}", e)).await;
188                    }
189                }
190            }
191
192            "addrole" => {
193                let guild_id = match &msg.guild_id {
194                    Some(id) => id.as_str(),
195                    None => return,
196                };
197                let parts: Vec<&str> = args.split_whitespace().collect();
198                if parts.len() != 2 {
199                    let _ = ctx.http.send_message(channel_id, "Usage: `!addrole <user_id> <role_id>`").await;
200                    return;
201                }
202                match ctx.http.add_member_role(guild_id, parts[0], parts[1]).await {
203                    Ok(_) => { let _ = ctx.http.send_message(channel_id, "Role added").await; }
204                    Err(e) => { let _ = ctx.http.send_message(channel_id, &format!("Error: {}", e)).await; }
205                }
206            }
207
208            "removerole" => {
209                let guild_id = match &msg.guild_id {
210                    Some(id) => id.as_str(),
211                    None => return,
212                };
213                let parts: Vec<&str> = args.split_whitespace().collect();
214                if parts.len() != 2 {
215                    let _ = ctx.http.send_message(channel_id, "Usage: `!removerole <user_id> <role_id>`").await;
216                    return;
217                }
218                match ctx.http.remove_member_role(guild_id, parts[0], parts[1]).await {
219                    Ok(_) => { let _ = ctx.http.send_message(channel_id, "Role removed").await; }
220                    Err(e) => { let _ = ctx.http.send_message(channel_id, &format!("Error: {}", e)).await; }
221                }
222            }
223
224            _ => {}
225        }
226    }
Source

pub async fn user(&self, id: &str) -> Option<User>

Looks up a user by ID. Populated from GUILD_CREATE member lists and MESSAGE_CREATE authors.

Examples found in repository?
examples/bot.rs (line 28)
27    async fn on_message(&self, ctx: Context, msg: Message) {
28        let user_cached = ctx.cache.user(&msg.author.id).await.is_some();
29        let ch_cached   = ctx.cache.channel(msg.channel_id.as_deref().unwrap_or("")).await.is_some();
30        let content_preview = msg.content.as_deref().unwrap_or("").chars().take(60).collect::<String>();
31        let attachments = msg.attachments.as_ref().map(|a| a.len()).unwrap_or(0);
32        let embeds = msg.embeds.as_ref().map(|e| e.len()).unwrap_or(0);
33        println!(
34            "[msg] author={}#{} channel={} guild={} | \"{}\" | attach={} embeds={} | cache: user={} ch={}",
35            msg.author.username,
36            msg.author.discriminator.as_deref().unwrap_or("0"),
37            msg.channel_id.as_deref().unwrap_or("?"),
38            msg.guild_id.as_deref().unwrap_or("DM"),
39            content_preview,
40            attachments,
41            embeds,
42            user_cached,
43            ch_cached,
44        );
45
46        if msg.author.bot.unwrap_or(false) {
47            return;
48        }
49
50        let content = match msg.content.as_deref() {
51            Some(c) => c,
52            None => return,
53        };
54
55        let channel_id = msg.channel_id.as_deref().unwrap_or_default();
56
57        let (cmd, args) = match parse_command(content) {
58            Some(v) => v,
59            None => return,
60        };
61
62        match cmd {
63            "ping" => {
64                let start = Instant::now();
65                let sent = ctx.http.send_message(channel_id, "Pong!").await;
66                let elapsed = start.elapsed().as_millis();
67
68                if let Ok(sent) = sent {
69                    let _ = ctx.http.edit_message(
70                        channel_id,
71                        &sent.id,
72                        &format!("Pong! {}ms", elapsed),
73                    ).await;
74                }
75            }
76
77            "say" => {
78                if args.is_empty() {
79                    let _ = ctx.http.send_message(channel_id, "Say what?").await;
80                    return;
81                }
82                let _ = ctx.http.delete_message(channel_id, &msg.id).await;
83                let _ = ctx.http.send_message(channel_id, args).await;
84            }
85
86            "embed" => {
87                let (title, desc) = match args.split_once('|') {
88                    Some((t, d)) => (t.trim(), d.trim()),
89                    None => {
90                        let _ = ctx.http.send_message(channel_id, "`!embed title | description`").await;
91                        return;
92                    }
93                };
94
95                let embed = EmbedBuilder::new()
96                    .title(title)
97                    .description(desc)
98                    .color(0x5865F2)
99                    .build();
100
101                let _ = ctx.http.send_embed(channel_id, None, vec![embed]).await;
102            }
103
104            "react" => {
105                let _ = ctx.http.add_reaction(channel_id, &msg.id, "❤️").await;
106            }
107
108            "purge" => {
109                let count: u8 = args.parse().unwrap_or(0);
110                if count == 0 || count > 100 {
111                    let _ = ctx.http.send_message(channel_id, "1-100.").await;
112                    return;
113                }
114
115                let query = GetMessagesQuery {
116                    limit: Some(count),
117                    ..Default::default()
118                };
119
120                if let Ok(messages) = ctx.http.get_messages(channel_id, query).await {
121                    let ids: Vec<&str> = messages.iter().map(|m| m.id.as_str()).collect();
122                    let _ = ctx.http.bulk_delete_messages(channel_id, ids).await;
123                }
124            }
125
126            "serverinfo" => {
127                let guild_id = match &msg.guild_id {
128                    Some(id) => id.as_str(),
129                    None => return,
130                };
131
132                if let Ok(guild) = ctx.http.get_guild(guild_id).await {
133                    let name = guild.name.as_deref().unwrap_or("Unknown");
134
135                    let members = ctx.http.get_guild_members(guild_id, Some(1000), None).await
136                        .map(|m| m.len().to_string())
137                        .unwrap_or("?".into());
138
139                    let embed = EmbedBuilder::new()
140                        .title(name)
141                        .field("Members", &members, true)
142                        .color(0x5865F2)
143                        .build();
144
145                    let _ = ctx.http.send_embed(channel_id, None, vec![embed]).await;
146                }
147            }
148
149            "attach" => {
150                if args.is_empty() {
151                    let _ = ctx.http.send_message(channel_id, "Usage: `!attach <file path>`").await;
152                    return;
153                }
154                let path = std::path::Path::new(args);
155                let filename = path
156                    .file_name()
157                    .and_then(|n| n.to_str())
158                    .unwrap_or("file")
159                    .to_string();
160                let content_type = match path.extension().and_then(|e| e.to_str()) {
161                    Some("mp3") => "audio/mpeg",
162                    Some("mp4") => "video/mp4",
163                    Some("mov") => "video/quicktime",
164                    Some("webm") => "video/webm",
165                    Some("png") => "image/png",
166                    Some("jpg") | Some("jpeg") => "image/jpeg",
167                    Some("gif") => "image/gif",
168                    Some("webp") => "image/webp",
169                    Some("txt") => "text/plain",
170                    Some("pdf") => "application/pdf",
171                    _ => "application/octet-stream",
172                };
173                match tokio::fs::read(path).await {
174                    Ok(data) => {
175                        let file = AttachmentFile {
176                            filename,
177                            data,
178                            content_type: Some(content_type.to_string()),
179                        };
180                        match ctx.http.send_files(channel_id, vec![file], None).await {
181                            Ok(msg) => println!("[attach] sent message {}", msg.id),
182                            Err(e) => eprintln!("[attach] error: {}", e),
183                        }
184                    }
185                    Err(e) => {
186                        eprintln!("[attach] failed to read file: {}", e);
187                        let _ = ctx.http.send_message(channel_id, &format!("Failed to read file: {}", e)).await;
188                    }
189                }
190            }
191
192            "addrole" => {
193                let guild_id = match &msg.guild_id {
194                    Some(id) => id.as_str(),
195                    None => return,
196                };
197                let parts: Vec<&str> = args.split_whitespace().collect();
198                if parts.len() != 2 {
199                    let _ = ctx.http.send_message(channel_id, "Usage: `!addrole <user_id> <role_id>`").await;
200                    return;
201                }
202                match ctx.http.add_member_role(guild_id, parts[0], parts[1]).await {
203                    Ok(_) => { let _ = ctx.http.send_message(channel_id, "Role added").await; }
204                    Err(e) => { let _ = ctx.http.send_message(channel_id, &format!("Error: {}", e)).await; }
205                }
206            }
207
208            "removerole" => {
209                let guild_id = match &msg.guild_id {
210                    Some(id) => id.as_str(),
211                    None => return,
212                };
213                let parts: Vec<&str> = args.split_whitespace().collect();
214                if parts.len() != 2 {
215                    let _ = ctx.http.send_message(channel_id, "Usage: `!removerole <user_id> <role_id>`").await;
216                    return;
217                }
218                match ctx.http.remove_member_role(guild_id, parts[0], parts[1]).await {
219                    Ok(_) => { let _ = ctx.http.send_message(channel_id, "Role removed").await; }
220                    Err(e) => { let _ = ctx.http.send_message(channel_id, &format!("Error: {}", e)).await; }
221                }
222            }
223
224            _ => {}
225        }
226    }
Source

pub async fn current_user(&self) -> Option<User>

Returns the bot’s own User object, populated from the READY event.

Examples found in repository?
examples/selfbot.rs (line 17)
16async fn is_me(ctx: &Context, msg: &Message) -> bool {
17    ctx.cache.current_user().await
18        .map(|u| u.id == msg.author.id)
19        .unwrap_or(false)
20}
21
22#[async_trait]
23impl EventHandler for Handler {
24    async fn on_ready(&self, _ctx: Context, ready: Ready) {
25        println!("[ready] logged in as {}", ready.user.username);
26    }
27
28    async fn on_message(&self, ctx: Context, msg: Message) {
29        if !is_me(&ctx, &msg).await {
30            return;
31        }
32
33        let content = match msg.content.as_deref() {
34            Some(c) => c,
35            None => return,
36        };
37
38        let channel_id = msg.channel_id.as_deref().unwrap_or_default();
39
40        let (cmd, args) = match parse_command(content) {
41            Some(v) => v,
42            None => return,
43        };
44
45        match cmd {
46            "ping" => {
47                let start = Instant::now();
48                let sent = ctx.http.send_message(channel_id, "Pong!").await;
49                let elapsed = start.elapsed().as_millis();
50                if let Ok(sent) = sent {
51                    let _ = ctx.http.edit_message(
52                        channel_id,
53                        &sent.id,
54                        &format!("Pong! `{}ms`", elapsed),
55                    ).await;
56                }
57            }
58
59            "8ball" => {
60                if args.is_empty() {
61                    let _ = ctx.http.edit_message(channel_id, &msg.id, "Ask a question!").await;
62                    return;
63                }
64                let responses = [
65                    "Yes", "No", "Maybe", "Definitely", "Absolutely not",
66                    "Ask again later", "Better not tell you now", "Cannot predict now",
67                    "Don't count on it", "It is certain", "Most likely", "Outlook good",
68                    "Reply hazy, try again", "Signs point to yes", "Very doubtful", "Without a doubt",
69                ];
70                let idx = (std::time::SystemTime::now()
71                    .duration_since(std::time::UNIX_EPOCH)
72                    .unwrap()
73                    .as_nanos() % responses.len() as u128) as usize;
74                let answer = responses[idx];
75                let _ = ctx.http.edit_message(channel_id, &msg.id, &format!("{}", answer)).await;
76            }
77
78            "flip" => {
79                let result = if std::time::SystemTime::now()
80                    .duration_since(std::time::UNIX_EPOCH)
81                    .unwrap()
82                    .as_nanos() % 2 == 0 { "Heads" } else { "Tails" };
83                let _ = ctx.http.edit_message(channel_id, &msg.id, &format!("{}", result)).await;
84            }
85
86            "roll" => {
87                let sides: u32 = args.parse().unwrap_or(6);
88                if sides < 2 || sides > 100 {
89                    let _ = ctx.http.edit_message(channel_id, &msg.id, "Use 2-100 sides").await;
90                    return;
91                }
92                let result = (std::time::SystemTime::now()
93                    .duration_since(std::time::UNIX_EPOCH)
94                    .unwrap()
95                    .as_nanos() % sides as u128) as u32 + 1;
96                let _ = ctx.http.edit_message(channel_id, &msg.id, &format!("Rolled {}/{}", result, sides)).await;
97            }
98
99            "purge" => {
100                let count: u8 = args.parse().unwrap_or(10).min(100);
101                let query = GetMessagesQuery {
102                    limit: Some(count),
103                    before: None,
104                    after: None,
105                    around: None,
106                };
107                match ctx.http.get_messages(channel_id, query).await {
108                    Ok(messages) => {
109                        let my_id = ctx.cache.current_user().await.map(|u| u.id).unwrap_or_default();
110                        let my_messages: Vec<_> = messages.iter()
111                            .filter(|m| m.author.id == my_id)
112                            .map(|m| m.id.as_str())
113                            .collect();
114
115                        if !my_messages.is_empty() {
116                            let _ = ctx.http.bulk_delete_messages(channel_id, my_messages).await;
117                        }
118                    }
119                    Err(e) => eprintln!("[purge] failed: {}", e),
120                }
121            }
122
123            "embed" => {
124                if args.is_empty() {
125                    let _ = ctx.http.edit_message(channel_id, &msg.id, "Usage: sb!embed <text>").await;
126                    return;
127                }
128                let embed = EmbedBuilder::new()
129                    .description(args)
130                    .color(0x5865F2)
131                    .build();
132
133                let _ = ctx.http.delete_message(channel_id, &msg.id).await;
134                let _ = ctx.http.send_embed(channel_id, None, vec![embed]).await;
135            }
136
137            "edit" => {
138                if args.is_empty() { return; }
139                let _ = ctx.http.edit_message(channel_id, &msg.id, args).await;
140            }
141
142            "help" => {
143                let help_text = "**Selfbot Commands**\n\
144                    `sb!ping` - Check latency\n\
145                    `sb!8ball <question>` - Ask the magic 8ball\n\
146                    `sb!flip` - Flip a coin\n\
147                    `sb!roll [sides]` - Roll a die (default 6)\n\
148                    `sb!purge [count]` - Delete your recent messages\n\
149                    `sb!embed <text>` - Send an embed\n\
150                    `sb!edit <text>` - Edit your message\n\
151                    `sb!help` - Show this message";
152                let _ = ctx.http.edit_message(channel_id, &msg.id, help_text).await;
153            }
154
155            _ => {}
156        }
157    }
More examples
Hide additional examples
examples/bot.rs (line 20)
18    async fn on_ready(&self, ctx: Context, ready: Ready) {
19        println!("Logged in as {}", ready.user.username);
20        println!("current_user from cache: {:?}", ctx.cache.current_user().await);
21    }
Source

pub async fn guild_count(&self) -> usize

Returns the number of guilds currently in cache.

Examples found in repository?
examples/bot.rs (line 24)
23    async fn on_guild_create(&self, ctx: Context, guild: Guild) {
24        println!("GUILD_CREATE: {} (cache size: {})", guild.id, ctx.cache.guild_count().await);
25    }

Auto Trait Implementations§

§

impl !Freeze for Cache

§

impl !RefUnwindSafe for Cache

§

impl Send for Cache

§

impl Sync for Cache

§

impl Unpin for Cache

§

impl UnsafeUnpin for Cache

§

impl !UnwindSafe for Cache

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more