//! A cache containing data received from [`Shard`]s.
//!
//! Using the cache allows to avoid REST API requests via the [`http`] module
//! where possible. Issuing too many requests will lead to ratelimits.
//!
//! Following a policy to never hand out locks, the cache will clone all values
//! when calling its methods.
//!
//! # Use by Models
//!
//! Most models of Discord objects, such as the [`Message`], [`GuildChannel`],
//! or [`Emoji`], have methods for interacting with that single instance. This
//! feature is only compiled if the `methods` feature is enabled. An example of
//! this is [`Guild::edit`], which performs a check to ensure that the current
//! user is the owner of the guild, prior to actually performing the HTTP
//! request. The cache is involved due to the function's use of unlocking the
//! cache and retrieving the Id of the current user, and comparing it to the Id
//! of the user that owns the guild. This is an inexpensive method of being able
//! to access data required by these sugary methods.
//!
//! # Do I need the Cache?
//!
//! If you're asking this, the answer is likely "definitely yes" or
//! "definitely no"; any in-between tends to be "yes". If you are low on RAM,
//! and need to run on only a couple MB, then the answer is "definitely no". If
//! you do not care about RAM and want your bot to be able to access data
//! while needing to hit the REST API as little as possible, then the answer
//! is "yes".
//!
//! [`Shard`]: crate::gateway::Shard
//! [`http`]: crate::http
use std::collections::hash_map::RandomState;
use std::collections::{HashMap, VecDeque};
use std::hash::BuildHasher;
use std::str::FromStr;
#[cfg(feature = "temp_cache")]
use std::time::Duration;
use dashmap::iter::Iter;
use dashmap::mapref::entry::Entry;
use dashmap::mapref::multiple::RefMulti;
use dashmap::{DashMap, DashSet};
#[cfg(feature = "temp_cache")]
use moka::dash::Cache as DashCache;
use parking_lot::RwLock;
use tracing::instrument;
use crate::model::prelude::*;
mod cache_update;
mod event;
mod settings;
pub use self::cache_update::CacheUpdate;
pub use self::settings::Settings;
type MessageCache = DashMap<ChannelId, DashMap<MessageId, Message>>;
pub trait FromStrAndCache: Sized {
type Err;
#[allow(clippy::missing_errors_doc)]
fn from_str<CRL>(cache: CRL, s: &str) -> Result<Self, Self::Err>
where
CRL: AsRef<Cache> + Send + Sync;
}
pub trait StrExt: Sized {
#[allow(clippy::missing_errors_doc)]
fn parse_cached<CRL, F: FromStrAndCache>(&self, cache: CRL) -> Result<F, F::Err>
where
CRL: AsRef<Cache> + Send + Sync;
}
impl StrExt for &str {
#[allow(clippy::missing_errors_doc)]
fn parse_cached<CRL, F: FromStrAndCache>(&self, cache: CRL) -> Result<F, F::Err>
where
CRL: AsRef<Cache> + Send + Sync,
{
F::from_str(&cache, self)
}
}
impl<F: FromStr> FromStrAndCache for F {
type Err = F::Err;
#[allow(clippy::missing_errors_doc)]
fn from_str<CRL>(_cache: CRL, s: &str) -> Result<Self, Self::Err>
where
CRL: AsRef<Cache> + Send + Sync,
{
s.parse::<F>()
}
}
/// Iterator given to the selector closure in [`Cache::channel_messages_field`].
// Wrapper around a specific iterator type to allow swapping out iterators on cache design changes
#[derive(Clone)]
pub struct MessageIterator<'a, S: BuildHasher + Clone>(
Iter<'a, MessageId, Message, S, DashMap<MessageId, Message, S>>,
);
impl<'a, S: 'a + BuildHasher + Clone> Iterator for MessageIterator<'a, S> {
// type Item = &'a Message;
type Item = RefMulti<'a, MessageId, Message, S>;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
/// A cache containing data received from [`Shard`]s.
///
/// Using the cache allows to avoid REST API requests via the [`http`] module
/// where possible. Issuing too many requests will lead to ratelimits.
///
/// The cache will clone all values when calling its methods.
///
/// [`Shard`]: crate::gateway::Shard
/// [`http`]: crate::http
#[derive(Debug)]
#[non_exhaustive]
pub struct Cache {
/// A map of channels in [`Guild`]s that the current user has received data
/// for.
///
/// When a [`Event::GuildDelete`] or [`Event::GuildUnavailable`] is
/// received and processed by the cache, the relevant channels are also
/// removed from this map.
pub(crate) channels: DashMap<ChannelId, GuildChannel>,
/// Cache of channels that have been fetched via to_channel.
///
/// Each value has a maximum TTL of 1 hour.
#[cfg(feature = "temp_cache")]
pub(crate) temp_channels: DashCache<ChannelId, GuildChannel>,
/// A map of channel categories.
pub(crate) categories: DashMap<ChannelId, ChannelCategory>,
/// A map of guilds with full data available. This includes data like
/// [`Role`]s and [`Emoji`]s that are not available through the REST API.
pub(crate) guilds: DashMap<GuildId, Guild>,
pub(crate) messages: MessageCache,
/// A map of users' presences. This is updated in real-time. Note that
/// status updates are often "eaten" by the gateway, and this should not
/// be treated as being entirely 100% accurate.
pub(crate) presences: DashMap<UserId, Presence>,
/// A map of direct message channels that the current user has open with
/// other users.
pub(crate) private_channels: DashMap<ChannelId, PrivateChannel>,
/// The total number of shards being used by the bot.
pub(crate) shard_count: RwLock<u64>,
/// A list of guilds which are "unavailable". Refer to the documentation for
/// [`Event::GuildUnavailable`] for more information on when this can occur.
///
/// Additionally, guilds are always unavailable for bot users when a Ready
/// is received. Guilds are "sent in" over time through the receiving of
/// [`Event::GuildCreate`]s.
pub(crate) unavailable_guilds: DashSet<GuildId>,
/// The current user "logged in" and for which events are being received
/// for.
///
/// The current user contains information that a regular [`User`] does not,
/// such as whether it is a bot, whether the user is verified, etc.
///
/// Refer to the documentation for [`CurrentUser`] for more information.
pub(crate) user: RwLock<CurrentUser>,
/// A map of users that the current user sees.
///
/// Users are added to - and updated from - this map via the following
/// received events:
///
/// - [`GuildMemberAdd`][`GuildMemberAddEvent`]
/// - [`GuildMemberRemove`][`GuildMemberRemoveEvent`]
/// - [`GuildMembersChunk`][`GuildMembersChunkEvent`]
/// - [`PresenceUpdate`][`PresenceUpdateEvent`]
/// - [`Ready`][`ReadyEvent`]
///
/// Note, however, that users are _not_ removed from the map on removal
/// events such as [`GuildMemberRemove`][`GuildMemberRemoveEvent`], as other
/// structs such as members or recipients may still exist.
pub(crate) users: DashMap<UserId, User>,
/// Queue of message IDs for each channel.
///
/// This is simply a vecdeque so we can keep track of the order of messages
/// inserted into the cache. When a maximum number of messages are in a
/// channel's cache, we can pop the front and remove that ID from the cache.
pub(crate) message_queue: DashMap<ChannelId, VecDeque<MessageId>>,
/// Cache of users who have been fetched from `to_user`.
///
/// Each value has a max TTL of 1 hour.
#[cfg(feature = "temp_cache")]
pub(crate) temp_users: DashCache<UserId, User>,
/// The settings for the cache.
settings: RwLock<Settings>,
}
impl Cache {
/// Creates a new cache.
#[inline]
#[must_use]
pub fn new() -> Self {
Self::default()
}
/// Creates a new cache instance with settings applied.
///
/// # Examples
///
/// ```rust
/// use serenity::cache::{Cache, Settings};
///
/// let mut settings = Settings::new();
/// settings.max_messages(10);
///
/// let cache = Cache::new_with_settings(settings);
/// ```
#[instrument]
pub fn new_with_settings(settings: Settings) -> Self {
Self {
settings: RwLock::new(settings),
..Default::default()
}
}
/// Fetches the number of [`Member`]s that have not had data received.
///
/// The important detail to note here is that this is the number of
/// _member_s that have not had data received. A single [`User`] may have
/// multiple associated member objects that have not been received.
///
/// This can be used in combination with [`Shard::chunk_guild`], and can be
/// used to determine how many members have not yet been received.
///
/// ```rust,no_run
/// # use serenity::model::prelude::*;
/// # use serenity::prelude::*;
/// #
/// # #[cfg(feature = "client")]
/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
/// use std::thread;
/// use std::time::Duration;
///
/// struct Handler;
///
/// #[serenity::async_trait]
/// impl EventHandler for Handler {
/// async fn ready(&self, ctx: Context, _: Ready) {
/// // Wait some time for guilds to be received.
/// //
/// // You should keep track of this in a better fashion by tracking how
/// // many guilds each `ready` has, and incrementing a counter on
/// // GUILD_CREATEs. Once the number is equal, print the number of
/// // unknown members.
/// //
/// // For demonstrative purposes we're just sleeping the thread for 5
/// // seconds.
/// tokio::time::sleep(Duration::from_secs(5)).await;
///
/// println!("{} unknown members", ctx.cache.unknown_members());
/// }
/// }
///
/// let mut client =
/// Client::builder("token", GatewayIntents::default()).event_handler(Handler).await?;
///
/// client.start().await?;
/// # Ok(())
/// # }
/// ```
///
/// [`Shard::chunk_guild`]: crate::gateway::Shard::chunk_guild
pub fn unknown_members(&self) -> u64 {
let mut total = 0;
for guild_entry in self.guilds.iter() {
let guild = guild_entry.value();
let members = guild.members.len() as u64;
if guild.member_count > members {
total += guild.member_count - members;
}
}
total
}
/// Fetches a vector of all [`PrivateChannel`] Ids that are
/// stored in the cache.
///
/// # Examples
///
/// If there are 6 private channels and 2 groups in the cache, then `8` Ids
/// will be returned.
///
/// Printing the count of all private channels and groups:
///
/// ```rust,no_run
/// # use serenity::cache::Cache;
/// #
/// # let cache = Cache::default();
/// let amount = cache.private_channels().len();
///
/// println!("There are {} private channels", amount);
/// ```
pub fn private_channels(&self) -> DashMap<ChannelId, PrivateChannel> {
self.private_channels.clone()
}
/// Fetches a vector of all [`Guild`]s' Ids that are stored in the cache.
///
/// Note that if you are utilizing multiple [`Shard`]s, then the guilds
/// retrieved over all shards are included in this count -- not just the
/// current [`Context`]'s shard, if accessing from one.
///
/// # Examples
///
/// Print all of the Ids of guilds in the Cache:
///
/// ```rust,no_run
/// # use serenity::model::prelude::*;
/// # use serenity::prelude::*;
/// #
/// struct Handler;
///
/// #[serenity::async_trait]
/// impl EventHandler for Handler {
/// async fn ready(&self, context: Context, _: Ready) {
/// let guilds = context.cache.guilds().len();
///
/// println!("Guilds in the Cache: {}", guilds);
/// }
/// }
/// ```
///
/// [`Context`]: crate::client::Context
/// [`Shard`]: crate::gateway::Shard
pub fn guilds(&self) -> Vec<GuildId> {
let chain = self.unavailable_guilds.clone().into_iter();
self.guilds.iter().map(|i| *i.key()).chain(chain).collect()
}
/// Retrieves a [`Channel`] from the cache based on the given Id.
///
/// This will search the `channels` map, then the [`Self::private_channels`] map.
///
/// If you know what type of channel you're looking for, you should instead
/// manually retrieve from one of the respective methods:
///
/// - [`GuildChannel`]: [`Self::guild_channel`] or [`Self::guild_channels`]
/// - [`PrivateChannel`]: [`Self::private_channel`] or [`Self::private_channels`]
#[inline]
pub fn channel<C: Into<ChannelId>>(&self, id: C) -> Option<Channel> {
self._channel(id.into())
}
fn _channel(&self, id: ChannelId) -> Option<Channel> {
if let Some(channel) = self.channels.get(&id) {
let channel = channel.clone();
return Some(Channel::Guild(channel));
}
#[cfg(feature = "temp_cache")]
{
if let Some(channel) = self.temp_channels.get(&id) {
return Some(Channel::Guild(channel));
}
}
if let Some(private_channel) = self.private_channels.get(&id) {
return Some(Channel::Private(private_channel.clone()));
}
None
}
/// This method allows to extract specific data from the cached messages of a channel by
/// providing a `selector` closure picking what you want to extract from the messages
/// iterator of a given channel.
///
/// ```rust,no_run
/// # let cache: serenity::cache::Cache = todo!();
/// // Find all messages by user ID 8 in channel ID 7
/// let messages_by_user = cache.channel_messages_field(7, |msgs| {
/// msgs.filter_map(|m| if m.author.id == 8 { Some(m.clone()) } else { None })
/// .collect::<Vec<_>>()
/// });
/// ```
pub fn channel_messages_field<T>(
&self,
channel_id: impl Into<ChannelId>,
selector: impl FnOnce(MessageIterator<'_, RandomState>) -> T,
) -> Option<T> {
let msg = self.messages.get(&channel_id.into())?;
let message_iter = MessageIterator(msg.iter());
Some(selector(message_iter))
}
/// Clones an entire guild from the cache based on the given `id`.
///
/// In order to clone only a field of the guild, use [`Self::guild_field`].
///
///
/// # Examples
///
/// Retrieve a guild from the cache and print its name:
///
/// ```rust,no_run
/// # use serenity::cache::Cache;
/// #
/// # let cache = Cache::default();
/// // assuming the cache is in scope, e.g. via `Context`
/// if let Some(guild) = cache.guild(7) {
/// println!("Guild name: {}", guild.name);
/// }
/// ```
#[inline]
pub fn guild<G: Into<GuildId>>(&self, id: G) -> Option<Guild> {
self._guild(id.into())
}
fn _guild(&self, id: GuildId) -> Option<Guild> {
self.guilds.get(&id).map(|i| i.clone())
}
/// This method allows to select a field of the guild instead of
/// the entire guild by providing a `field_selector`-closure picking what
/// you want to clone.
///
/// ```rust,no_run
/// # use serenity::cache::Cache;
/// #
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// # let cache = Cache::default();
/// // We clone only the `len()` returned `usize` instead of the entire guild or the channels.
/// if let Some(channel_len) = cache.guild_field(7, |guild| guild.channels.len()) {
/// println!("Guild channels count: {}", channel_len);
/// }
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn guild_field<Ret, Fun>(&self, id: impl Into<GuildId>, field_selector: Fun) -> Option<Ret>
where
Fun: FnOnce(&Guild) -> Ret,
{
self._guild_field(id.into(), field_selector)
}
fn _guild_field<Ret, Fun>(&self, id: GuildId, field_accessor: Fun) -> Option<Ret>
where
Fun: FnOnce(&Guild) -> Ret,
{
let guild = self.guilds.get(&id)?;
Some(field_accessor(&*guild))
}
/// Returns the number of cached guilds.
pub fn guild_count(&self) -> usize {
self.guilds.len()
}
/// Retrieves a reference to a [`Guild`]'s channel. Unlike [`Self::channel`],
/// this will only search guilds for the given channel.
///
/// The only advantage of this method is that you can pass in anything that
/// is indirectly a [`ChannelId`].
///
/// # Examples
///
/// Getting a guild's channel via the Id of the message received through a
/// [`EventHandler::message`] event dispatch:
///
/// ```rust,no_run
/// # use serenity::model::prelude::*;
/// # use serenity::prelude::*;
/// #
/// struct Handler;
///
/// #[serenity::async_trait]
/// impl EventHandler for Handler {
/// async fn message(&self, context: Context, message: Message) {
/// let channel = match context.cache.guild_channel(message.channel_id) {
/// Some(channel) => channel,
/// None => {
/// let result = message
/// .channel_id
/// .say(&context, "Could not find guild's channel data")
/// .await;
/// if let Err(why) = result {
/// println!("Error sending message: {:?}", why);
/// }
///
/// return;
/// },
/// };
/// }
/// }
///
/// # #[cfg(feature = "client")]
/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let mut client =
/// Client::builder("token", GatewayIntents::default()).event_handler(Handler).await?;
///
/// client.start().await?;
/// # Ok(())
/// # }
/// ```
///
/// [`EventHandler::message`]: crate::client::EventHandler::message
#[inline]
pub fn guild_channel<C: Into<ChannelId>>(&self, id: C) -> Option<GuildChannel> {
self._guild_channel(id.into())
}
fn _guild_channel(&self, id: ChannelId) -> Option<GuildChannel> {
self.channels.get(&id).map(|i| i.clone())
}
/// This method allows to only clone a field of the guild channel instead of
/// the entire guild by providing a `field_selector`-closure picking what
/// you want to clone.
///
/// ```rust,no_run
/// # use serenity::cache::Cache;
/// #
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// # let cache = Cache::default();
/// // We clone only the `name` instead of the entire channel.
/// if let Some(channel_name) = cache.guild_channel_field(7, |channel| channel.name.clone()) {
/// println!("Guild channel name: {}", channel_name);
/// }
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn guild_channel_field<Ret, Fun>(
&self,
id: impl Into<ChannelId>,
field_selector: Fun,
) -> Option<Ret>
where
Fun: FnOnce(&GuildChannel) -> Ret,
{
self._guild_channel_field(id.into(), field_selector)
}
fn _guild_channel_field<Ret, Fun>(&self, id: ChannelId, field_selector: Fun) -> Option<Ret>
where
Fun: FnOnce(&GuildChannel) -> Ret,
{
let channel = self.channels.get(&id)?;
Some(field_selector(&*channel))
}
/// Retrieves a [`Guild`]'s member from the cache based on the guild's and
/// user's given Ids.
///
/// **Note**: This will clone the entire member. Instead, retrieve the guild
/// and retrieve from the guild's [`members`] map to avoid this.
///
/// # Examples
///
/// Retrieving the member object of the user that posted a message, in a
/// [`EventHandler::message`] context:
///
/// ```rust,no_run
/// # use serenity::cache::Cache;
/// # use serenity::http::Http;
/// # use serenity::model::channel::Message;
/// #
/// # async fn run(http: Http, cache: Cache, message: Message) {
/// #
/// let member = {
/// let channel = match cache.guild_channel(message.channel_id) {
/// Some(channel) => channel,
/// None => {
/// if let Err(why) = message.channel_id.say(http, "Error finding channel data").await {
/// println!("Error sending message: {:?}", why);
/// }
/// return;
/// },
/// };
///
/// match cache.member(channel.guild_id, message.author.id) {
/// Some(member) => member,
/// None => {
/// if let Err(why) = message.channel_id.say(&http, "Error finding member data").await {
/// println!("Error sending message: {:?}", why);
/// }
/// return;
/// },
/// }
/// };
///
/// let msg = format!("You have {} roles", member.roles.len());
///
/// if let Err(why) = message.channel_id.say(&http, &msg).await {
/// println!("Error sending message: {:?}", why);
/// }
/// # }
/// ```
///
/// [`EventHandler::message`]: crate::client::EventHandler::message
/// [`members`]: crate::model::guild::Guild::members
#[inline]
pub fn member<G, U>(&self, guild_id: G, user_id: U) -> Option<Member>
where
G: Into<GuildId>,
U: Into<UserId>,
{
self._member(guild_id.into(), user_id.into())
}
fn _member(&self, guild_id: GuildId, user_id: UserId) -> Option<Member> {
match self.guilds.get(&guild_id) {
Some(guild) => guild.members.get(&user_id).cloned(),
None => None,
}
}
/// This method allows to only clone a field of a member instead of
/// the entire member by providing a `field_selector`-closure picking what
/// you want to clone.
///
/// ```rust,no_run
/// # use serenity::cache::Cache;
/// #
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// # let cache = Cache::default();
/// // We clone only the `name` instead of the entire channel.
/// if let Some(Some(nick)) = cache.member_field(7, 8, |member| member.nick.clone()) {
/// println!("Member's nick: {}", nick);
/// }
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn member_field<Ret, Fun>(
&self,
guild_id: impl Into<GuildId>,
user_id: impl Into<UserId>,
field_selector: Fun,
) -> Option<Ret>
where
Fun: FnOnce(&Member) -> Ret,
{
self._member_field(guild_id.into(), user_id.into(), field_selector)
}
fn _member_field<Ret, Fun>(
&self,
guild_id: GuildId,
user_id: UserId,
field_selector: Fun,
) -> Option<Ret>
where
Fun: FnOnce(&Member) -> Ret,
{
let guild = self.guilds.get(&guild_id)?;
let member = guild.members.get(&user_id)?;
Some(field_selector(member))
}
#[inline]
pub fn guild_roles(&self, guild_id: impl Into<GuildId>) -> Option<HashMap<RoleId, Role>> {
self._guild_roles(guild_id.into())
}
fn _guild_roles(&self, guild_id: GuildId) -> Option<HashMap<RoleId, Role>> {
self.guilds.get(&guild_id).map(|g| g.roles.clone())
}
/// This method clones and returns all unavailable guilds.
#[inline]
pub fn unavailable_guilds(&self) -> DashSet<GuildId> {
self.unavailable_guilds.clone()
}
/// This method returns all channels from a guild of with the given `guild_id`.
#[inline]
pub fn guild_channels(
&self,
guild_id: impl Into<GuildId>,
) -> Option<DashMap<ChannelId, GuildChannel>> {
self._guild_channels(guild_id.into())
}
fn _guild_channels(&self, guild_id: GuildId) -> Option<DashMap<ChannelId, GuildChannel>> {
self.guilds.get(&guild_id).map(|g| {
g.channels
.iter()
.filter_map(|c| match c.1 {
Channel::Guild(channel) => Some((channel.id, channel.clone())),
_ => None,
})
.collect()
})
}
/// Returns the number of guild channels in the cache.
pub fn guild_channel_count(&self) -> usize {
self.channels.len()
}
/// This method returns all categories from a guild of with the given `guild_id`.
#[inline]
pub fn guild_categories(
&self,
guild_id: impl Into<GuildId>,
) -> Option<DashMap<ChannelId, ChannelCategory>> {
self._guild_categories(guild_id.into())
}
fn _guild_categories(&self, guild_id: GuildId) -> Option<DashMap<ChannelId, ChannelCategory>> {
self.guilds.get(&guild_id).map(|g| {
g.channels
.iter()
.filter_map(|c| match c.1 {
Channel::Category(category) => Some((category.id, category.clone())),
_ => None,
})
.collect()
})
}
/// Returns the number of shards.
#[inline]
pub fn shard_count(&self) -> u64 {
*self.shard_count.read()
}
/// Retrieves a [`Channel`]'s message from the cache based on the channel's and
/// message's given Ids.
///
/// **Note**: This will clone the entire message.
///
/// # Examples
///
/// Retrieving the message object from a channel, in a
/// [`EventHandler::message`] context:
///
/// ```rust,no_run
/// # use serenity::cache::Cache;
/// # use serenity::model::channel::Message;
/// #
/// # fn run(cache: Cache, message: Message) {
/// #
/// match cache.message(message.channel_id, message.id) {
/// Some(m) => assert_eq!(message.content, m.content),
/// None => println!("No message found in cache."),
/// };
/// # }
/// ```
///
/// [`EventHandler::message`]: crate::client::EventHandler::message
#[inline]
pub fn message<C, M>(&self, channel_id: C, message_id: M) -> Option<Message>
where
C: Into<ChannelId>,
M: Into<MessageId>,
{
self._message(channel_id.into(), message_id.into())
}
fn _message(&self, channel_id: ChannelId, message_id: MessageId) -> Option<Message> {
self.messages
.get(&channel_id)
.and_then(|messages| messages.get(&message_id).map(|i| i.clone()))
}
/// Retrieves a [`PrivateChannel`] from the cache's [`Self::private_channels`]
/// map, if it exists.
///
/// The only advantage of this method is that you can pass in anything that
/// is indirectly a [`ChannelId`].
///
/// # Examples
///
/// Retrieve a private channel from the cache and print its recipient's
/// name:
///
/// ```rust,no_run
/// # use serenity::cache::Cache;
/// #
/// # let cache = Cache::default();
/// // assuming the cache has been unlocked
///
/// if let Some(channel) = cache.private_channel(7) {
/// println!("The recipient is {}", channel.recipient);
/// }
/// ```
#[inline]
pub fn private_channel(&self, channel_id: impl Into<ChannelId>) -> Option<PrivateChannel> {
self._private_channel(channel_id.into())
}
fn _private_channel(&self, channel_id: ChannelId) -> Option<PrivateChannel> {
self.private_channels.get(&channel_id).map(|i| i.clone())
}
/// Retrieves a [`Guild`]'s role by their Ids.
///
/// **Note**: This will clone the entire role. Instead, retrieve the guild
/// and retrieve from the guild's [`roles`] map to avoid this.
///
/// [`Guild`]: crate::model::guild::Guild
/// [`roles`]: crate::model::guild::Guild::roles
///
/// # Examples
///
/// Retrieve a role from the cache and print its name:
///
/// ```rust,no_run
/// # use serenity::cache::Cache;
/// #
/// # let cache = Cache::default();
/// // assuming the cache is in scope, e.g. via `Context`
/// if let Some(role) = cache.role(7, 77) {
/// println!("Role with Id 77 is called {}", role.name);
/// }
/// ```
#[inline]
pub fn role<G, R>(&self, guild_id: G, role_id: R) -> Option<Role>
where
G: Into<GuildId>,
R: Into<RoleId>,
{
self._role(guild_id.into(), role_id.into())
}
fn _role(&self, guild_id: GuildId, role_id: RoleId) -> Option<Role> {
self.guilds.get(&guild_id).and_then(|g| g.roles.get(&role_id).cloned())
}
/// Returns the settings.
///
/// # Examples
///
/// Printing the maximum number of messages in a channel to be cached:
///
/// ```rust
/// use serenity::cache::Cache;
///
/// # fn test() {
/// let mut cache = Cache::new();
/// println!("Max settings: {}", cache.settings().max_messages);
/// # }
/// ```
pub fn settings(&self) -> Settings {
self.settings.read().clone()
}
/// Sets the maximum amount of messages per channel to cache.
///
/// By default, no messages will be cached.
pub fn set_max_messages(&self, max: usize) {
self.settings.write().max_messages = max;
}
/// Retrieves a [`User`] from the cache's [`Self::users`] map, if it exists.
///
/// The only advantage of this method is that you can pass in anything that
/// is indirectly a [`UserId`].
///
/// # Examples
///
/// Retrieve a user from the cache and print their name:
///
/// ```rust,no_run
/// # use serenity::client::Context;
/// # use serenity::framework::standard::{CommandResult, macros::command};
/// #
/// # #[command]
/// # async fn test(context: &Context) -> CommandResult {
/// if let Some(user) = context.cache.user(7) {
/// println!("User with Id 7 is currently named {}", user.name);
/// }
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn user<U: Into<UserId>>(&self, user_id: U) -> Option<User> {
self._user(user_id.into())
}
#[cfg(feature = "temp_cache")]
fn _user(&self, user_id: UserId) -> Option<User> {
if let Some(user) = self.users.get(&user_id) {
Some(user.clone())
} else {
self.temp_users.get(&user_id)
}
}
#[cfg(not(feature = "temp_cache"))]
fn _user(&self, user_id: UserId) -> Option<User> {
self.users.get(&user_id).map(|u| u.clone())
}
/// Clones all users and returns them.
#[inline]
pub fn users(&self) -> DashMap<UserId, User> {
self.users.clone()
}
/// Returns the amount of cached users.
#[inline]
pub fn user_count(&self) -> usize {
self.users.len()
}
/// Clones a category matching the `channel_id` and returns it.
#[inline]
pub fn category<C: Into<ChannelId>>(&self, channel_id: C) -> Option<ChannelCategory> {
self._category(channel_id.into())
}
fn _category(&self, channel_id: ChannelId) -> Option<ChannelCategory> {
self.categories.get(&channel_id).map(|i| i.clone())
}
/// Clones all categories and returns them.
#[inline]
pub fn categories(&self) -> DashMap<ChannelId, ChannelCategory> {
self.categories.clone()
}
/// Returns the amount of cached categories.
#[inline]
pub fn category_count(&self) -> usize {
self.categories.len()
}
/// Returns the optional category ID of a channel.
#[inline]
pub fn channel_category_id(&self, channel_id: ChannelId) -> Option<ChannelId> {
self.categories.get(&channel_id).map(|category| category.id)
}
/// This method clones and returns the user used by the bot.
#[inline]
pub fn current_user(&self) -> CurrentUser {
self.user.read().clone()
}
/// This method returns the bot's ID.
#[inline]
pub fn current_user_id(&self) -> UserId {
self.user.read().id
}
/// This method allows to only clone a field of the current user instead of
/// the entire user by providing a `field_selector`-closure picking what
/// you want to clone.
///
/// ```rust,no_run
/// # use serenity::cache::Cache;
/// #
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// # let cache = Cache::default();
/// // We clone only the `name` instead of the entire channel.
/// let id = cache.current_user_field(|user| user.id);
/// println!("Current user's ID: {}", id);
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn current_user_field<Ret: Clone, Fun>(&self, field_selector: Fun) -> Ret
where
Fun: FnOnce(&CurrentUser) -> Ret,
{
let user = self.user.read();
field_selector(&user)
}
/// Updates the cache with the update implementation for an event or other
/// custom update implementation.
///
/// Refer to the documentation for [`CacheUpdate`] for more information.
///
/// # Examples
///
/// Refer to the [`CacheUpdate` examples].
///
/// [`CacheUpdate` examples]: CacheUpdate#examples
#[instrument(skip(self, e))]
pub fn update<E: CacheUpdate>(&self, e: &mut E) -> Option<E::Output> {
e.update(self)
}
pub(crate) fn update_user_entry(&self, user: &User) {
match self.users.entry(user.id) {
Entry::Vacant(e) => {
e.insert(user.clone());
},
Entry::Occupied(mut e) => {
e.get_mut().clone_from(user);
},
}
}
}
impl Default for Cache {
fn default() -> Cache {
Cache {
channels: DashMap::default(),
#[cfg(feature = "temp_cache")]
temp_channels: DashCache::builder().time_to_live(Duration::from_secs(60 * 60)).build(),
categories: DashMap::default(),
guilds: DashMap::default(),
messages: DashMap::default(),
presences: DashMap::default(),
private_channels: DashMap::with_capacity(128),
settings: RwLock::new(Settings::default()),
shard_count: RwLock::new(1),
unavailable_guilds: DashSet::default(),
user: RwLock::new(CurrentUser::default()),
users: DashMap::default(),
#[cfg(feature = "temp_cache")]
temp_users: DashCache::builder().time_to_live(Duration::from_secs(60 * 60)).build(),
message_queue: DashMap::default(),
}
}
}
#[cfg(test)]
mod test {
use std::collections::HashMap;
use crate::cache::{Cache, CacheUpdate, Settings};
use crate::json::from_number;
use crate::model::prelude::*;
#[test]
fn test_cache_messages() {
let mut settings = Settings::new();
settings.max_messages(2);
let cache = Cache::new_with_settings(settings);
// Test inserting one message into a channel's message cache.
let datetime = Timestamp::now();
let mut event = MessageCreateEvent {
message: Message {
id: MessageId(3),
attachments: vec![],
author: User {
id: UserId(2),
avatar: None,
bot: false,
discriminator: 1,
name: "user 1".to_owned(),
public_flags: None,
banner: None,
accent_colour: None,
},
channel_id: ChannelId(2),
guild_id: Some(GuildId(1)),
content: String::new(),
edited_timestamp: None,
embeds: vec![],
kind: MessageType::Regular,
member: None,
mention_everyone: false,
mention_roles: vec![],
mention_channels: vec![],
mentions: vec![],
nonce: from_number(1),
pinned: false,
reactions: vec![],
timestamp: datetime,
tts: false,
webhook_id: None,
activity: None,
application: None,
message_reference: None,
flags: None,
sticker_items: vec![],
referenced_message: None,
interaction: None,
components: vec![],
},
};
// Check that the channel cache doesn't exist.
assert!(!cache.messages.contains_key(&event.message.channel_id));
// Add first message, none because message ID 2 doesn't already exist.
assert!(event.update(&cache).is_none());
// None, it only returns the oldest message if the cache was already full.
assert!(event.update(&cache).is_none());
// Assert there's only 1 message in the channel's message cache.
assert_eq!(cache.messages.get(&event.message.channel_id).unwrap().len(), 1);
// Add a second message, assert that channel message cache length is 2.
event.message.id = MessageId(4);
assert!(event.update(&cache).is_none());
assert_eq!(cache.messages.get(&event.message.channel_id).unwrap().len(), 2);
// Add a third message, the first should now be removed.
event.message.id = MessageId(5);
assert!(event.update(&cache).is_some());
{
let channel = cache.messages.get(&event.message.channel_id).unwrap();
assert_eq!(channel.len(), 2);
// Check that the first message is now removed.
assert!(!channel.contains_key(&MessageId(3)));
}
let channel = Channel::Guild(GuildChannel {
id: event.message.channel_id,
bitrate: None,
parent_id: None,
guild_id: event.message.guild_id.unwrap(),
kind: ChannelType::Text,
last_message_id: None,
last_pin_timestamp: None,
name: String::new(),
permission_overwrites: vec![],
position: 0,
topic: None,
user_limit: None,
nsfw: false,
rate_limit_per_user: Some(0),
rtc_region: None,
video_quality_mode: None,
message_count: None,
member_count: None,
thread_metadata: None,
member: None,
default_auto_archive_duration: None,
});
// Add a channel delete event to the cache, the cached messages for that
// channel should now be gone.
let mut delete = ChannelDeleteEvent {
channel: channel.clone(),
};
assert!(cache.update(&mut delete).is_none());
assert!(!cache.messages.contains_key(&delete.channel.id()));
// Test deletion of a guild channel's message cache when a GuildDeleteEvent
// is received.
let mut guild_create = {
let mut channels = HashMap::new();
channels.insert(ChannelId(2), channel);
GuildCreateEvent {
guild: Guild {
id: GuildId(1),
afk_channel_id: None,
afk_timeout: 0,
application_id: None,
default_message_notifications: DefaultMessageNotificationLevel::All,
emojis: HashMap::new(),
explicit_content_filter: ExplicitContentFilter::None,
features: vec![],
icon: None,
joined_at: datetime,
large: false,
member_count: 0,
members: HashMap::new(),
mfa_level: MfaLevel::None,
name: String::new(),
owner_id: UserId(3),
presences: HashMap::new(),
roles: HashMap::new(),
splash: None,
discovery_splash: None,
system_channel_id: None,
system_channel_flags: SystemChannelFlags::default(),
rules_channel_id: None,
public_updates_channel_id: None,
verification_level: VerificationLevel::Low,
voice_states: HashMap::new(),
description: None,
premium_tier: PremiumTier::Tier0,
channels,
premium_subscription_count: 0,
banner: None,
vanity_url_code: Some("bruhmoment".to_string()),
preferred_locale: "en-US".to_string(),
welcome_screen: None,
approximate_member_count: None,
approximate_presence_count: None,
nsfw_level: NsfwLevel::Default,
max_video_channel_users: None,
max_presences: None,
max_members: None,
widget_enabled: Some(false),
widget_channel_id: None,
stage_instances: vec![],
threads: vec![],
stickers: HashMap::new(),
},
}
};
assert!(cache.update(&mut guild_create).is_none());
assert!(cache.update(&mut event).is_none());
let mut guild_delete = GuildDeleteEvent {
guild: UnavailableGuild {
id: GuildId(1),
unavailable: false,
},
};
// The guild existed in the cache, so the cache's guild is returned by the
// update.
assert!(cache.update(&mut guild_delete).is_some());
// Assert that the channel's message cache no longer exists.
assert!(!cache.messages.contains_key(&ChannelId(2)));
}
}