1mod config;
2mod event;
3mod repository;
4
5pub mod models;
6
7use std::{
8 collections::VecDeque,
9 hash::{DefaultHasher, Hash, Hasher as _},
10 ops::Deref as _,
11};
12
13use redis::aio::ConnectionManager;
14use serde::{Deserialize, Serialize};
15use twilight_model::{
16 gateway::event::Event,
17 id::{
18 Id,
19 marker::{
20 ChannelMarker, EmojiMarker, GuildMarker, IntegrationMarker, MessageMarker, RoleMarker,
21 ScheduledEventMarker, StageMarker, StickerMarker, UserMarker,
22 },
23 },
24};
25
26use models::{
27 channel::CachedChannel,
28 emoji::CachedEmoji,
29 guild::CachedGuild,
30 guild_scheduled_event::CachedGuildScheduledEvent,
31 integration::CachedGuildIntegration,
32 member::CachedMember,
33 message::CachedMessage,
34 presence::CachedPresence,
35 role::CachedRole,
36 stage_instance::CachedStageInstance,
37 sticker::CachedSticker,
38 user::{CachedCurrentUser, CachedUser},
39 voice_state::CachedVoiceState,
40};
41use repository::{MappedSetRepository, Repository, SetRepository, SingleRepository};
42
43pub use config::Config;
44pub use twilight_cache_inmemory::Config as TwilightConfig;
45pub use twilight_cache_inmemory::ResourceType;
46
47pub(crate) type Error = Box<dyn std::error::Error + Send + Sync>;
48
49pub struct Cache {
50 pub config: Config,
51
52 pub guilds: Repository<Id<GuildMarker>, CachedGuild>,
53 pub guild_channels: MappedSetRepository<Id<GuildMarker>, Id<ChannelMarker>>,
54 pub guild_scheduled_events: MappedSetRepository<Id<GuildMarker>, Id<ScheduledEventMarker>>,
55 pub guild_integrations: MappedSetRepository<Id<GuildMarker>, Id<IntegrationMarker>>,
56 pub guild_members: MappedSetRepository<Id<GuildMarker>, Id<UserMarker>>,
57 pub guild_presences: MappedSetRepository<Id<GuildMarker>, Id<UserMarker>>,
58 pub guild_emojis: MappedSetRepository<Id<GuildMarker>, Id<EmojiMarker>>,
59 pub guild_roles: MappedSetRepository<Id<GuildMarker>, Id<RoleMarker>>,
60 pub guild_stage_instances: MappedSetRepository<Id<GuildMarker>, Id<StageMarker>>,
61 pub guild_stickers: MappedSetRepository<Id<GuildMarker>, Id<StickerMarker>>,
62 pub unavailable_guilds: SetRepository<Id<GuildMarker>>,
63
64 pub channels: Repository<Id<ChannelMarker>, CachedChannel>,
65 pub channel_messages: Repository<Id<ChannelMarker>, VecDeque<Id<MessageMarker>>>,
66
67 pub scheduled_events: Repository<Id<ScheduledEventMarker>, CachedGuildScheduledEvent>,
68 pub integrations:
69 Repository<(Id<GuildMarker>, Id<IntegrationMarker>), GuildResource<CachedGuildIntegration>>,
70 pub members: Repository<(Id<GuildMarker>, Id<UserMarker>), CachedMember>,
71 pub messages: Repository<Id<MessageMarker>, CachedMessage>,
72 pub presences: Repository<(Id<GuildMarker>, Id<UserMarker>), CachedPresence>,
73 pub emojis: Repository<Id<EmojiMarker>, GuildResource<CachedEmoji>>,
74 pub roles: Repository<Id<RoleMarker>, GuildResource<CachedRole>>,
75 pub stage_instances: Repository<Id<StageMarker>, GuildResource<CachedStageInstance>>,
76 pub stickers: Repository<Id<StickerMarker>, GuildResource<CachedSticker>>,
77
78 pub current_user: SingleRepository<CachedCurrentUser>,
79 pub users: Repository<Id<UserMarker>, CachedUser>,
80 pub user_guilds: MappedSetRepository<Id<UserMarker>, Id<GuildMarker>>,
81
82 pub voice_state_channels:
83 MappedSetRepository<Id<ChannelMarker>, (Id<GuildMarker>, Id<UserMarker>)>,
84 pub voice_state_guilds: MappedSetRepository<Id<GuildMarker>, Id<UserMarker>>,
85 pub voice_states: Repository<(Id<GuildMarker>, Id<UserMarker>), CachedVoiceState>,
86}
87
88pub(crate) fn hash<T: Hash>(val: T) -> u64 {
89 let mut hasher = DefaultHasher::new();
90 val.hash(&mut hasher);
91 hasher.finish()
92}
93
94impl Cache {
95 pub fn new(redis: ConnectionManager, config: Config) -> Self {
96 Self {
97 guilds: Repository::new("guilds", config.wants(ResourceType::GUILD), redis.clone()),
98 guild_channels: MappedSetRepository::new(
99 "guild_channels",
100 config.wants(ResourceType::CHANNEL),
101 redis.clone(),
102 ),
103 guild_scheduled_events: MappedSetRepository::new(
104 "guild_scheduled_events",
105 config
106 .resource_types
107 .contains(ResourceType::GUILD_SCHEDULED_EVENT),
108 redis.clone(),
109 ),
110 guild_integrations: MappedSetRepository::new(
111 "guild_integrations",
112 config.wants(ResourceType::INTEGRATION),
113 redis.clone(),
114 ),
115 guild_members: MappedSetRepository::new(
116 "guild_members",
117 config.wants(ResourceType::MEMBER),
118 redis.clone(),
119 ),
120 guild_presences: MappedSetRepository::new(
121 "guild_presences",
122 config.wants(ResourceType::PRESENCE),
123 redis.clone(),
124 ),
125 guild_emojis: MappedSetRepository::new(
126 "guild_emojis",
127 config.wants(ResourceType::EMOJI),
128 redis.clone(),
129 ),
130 guild_roles: MappedSetRepository::new(
131 "guild_roles",
132 config.wants(ResourceType::ROLE),
133 redis.clone(),
134 ),
135 guild_stage_instances: MappedSetRepository::new(
136 "guild_stage_instances",
137 config.wants(ResourceType::STAGE_INSTANCE),
138 redis.clone(),
139 ),
140 guild_stickers: MappedSetRepository::new(
141 "guild_stickers",
142 config.wants(ResourceType::STICKER),
143 redis.clone(),
144 ),
145 unavailable_guilds: SetRepository::new(
146 "unavailable_guilds",
147 config.wants(ResourceType::GUILD),
148 redis.clone(),
149 ),
150
151 channels: Repository::new(
152 "channels",
153 config.wants(ResourceType::CHANNEL),
154 redis.clone(),
155 ),
156 channel_messages: Repository::new(
157 "channel_messages",
158 config.wants(ResourceType::MESSAGE),
159 redis.clone(),
160 ),
161
162 scheduled_events: Repository::new(
163 "scheduled_events",
164 config
165 .resource_types
166 .contains(ResourceType::GUILD_SCHEDULED_EVENT),
167 redis.clone(),
168 ),
169 integrations: Repository::new(
170 "integrations",
171 config.wants(ResourceType::INTEGRATION),
172 redis.clone(),
173 ),
174 members: Repository::new("members", config.wants(ResourceType::MEMBER), redis.clone()),
175 messages: Repository::new(
176 "messages",
177 config.wants(ResourceType::MESSAGE),
178 redis.clone(),
179 ),
180 presences: Repository::new(
181 "presences",
182 config.wants(ResourceType::PRESENCE),
183 redis.clone(),
184 ),
185 emojis: Repository::new("emojis", config.wants(ResourceType::EMOJI), redis.clone()),
186
187 roles: Repository::new("roles", config.wants(ResourceType::ROLE), redis.clone()),
188 stage_instances: Repository::new(
189 "stage_instances",
190 config.wants(ResourceType::STAGE_INSTANCE),
191 redis.clone(),
192 ),
193 stickers: Repository::new(
194 "stickers",
195 config.wants(ResourceType::STICKER),
196 redis.clone(),
197 ),
198
199 current_user: SingleRepository::new(
200 "current_user",
201 config.wants(ResourceType::USER_CURRENT),
202 redis.clone(),
203 ),
204 users: Repository::new("emojis", config.wants(ResourceType::USER), redis.clone()),
205 user_guilds: MappedSetRepository::new(
206 "user_guilds",
207 config.wants(ResourceType::USER),
208 redis.clone(),
209 ),
210
211 voice_state_channels: MappedSetRepository::new(
212 "voice_state_channels",
213 config.wants(ResourceType::VOICE_STATE),
214 redis.clone(),
215 ),
216 voice_state_guilds: MappedSetRepository::new(
217 "voice_state_guilds",
218 config.wants(ResourceType::VOICE_STATE),
219 redis.clone(),
220 ),
221 voice_states: Repository::new(
222 "voice_states",
223 config.wants(ResourceType::VOICE_STATE),
224 redis,
225 ),
226
227 config,
228 }
229 }
230
231 pub async fn update(&self, event: &impl UpdateCache) -> Result<(), Error> {
232 event.update(self).await
233 }
234}
235
236impl UpdateCache for Event {
237 async fn update(&self, cache: &Cache) -> Result<(), Error> {
238 #[expect(clippy::use_self, reason = "it's clearer to refer to Event")]
239 match self {
240 Event::ChannelCreate(v) => cache.update(v.deref()).await,
241 Event::ChannelDelete(v) => cache.update(v.deref()).await,
242 Event::ChannelPinsUpdate(v) => cache.update(v).await,
243 Event::ChannelUpdate(v) => cache.update(v.deref()).await,
244 Event::GuildCreate(v) => cache.update(v.deref()).await,
245 Event::GuildDelete(v) => cache.update(v).await,
246 Event::GuildEmojisUpdate(v) => cache.update(v).await,
247 Event::GuildStickersUpdate(v) => cache.update(v).await,
248 Event::GuildUpdate(v) => cache.update(v.deref()).await,
249 Event::GuildScheduledEventCreate(v) => cache.update(v.deref()).await,
250 Event::GuildScheduledEventDelete(v) => cache.update(v.deref()).await,
251 Event::GuildScheduledEventUpdate(v) => cache.update(v.deref()).await,
252 Event::GuildScheduledEventUserAdd(v) => cache.update(v).await,
253 Event::GuildScheduledEventUserRemove(v) => cache.update(v).await,
254 Event::IntegrationCreate(v) => cache.update(v.deref()).await,
255 Event::IntegrationDelete(v) => cache.update(v).await,
256 Event::IntegrationUpdate(v) => cache.update(v.deref()).await,
257 Event::InteractionCreate(v) => cache.update(v.deref()).await,
258 Event::MemberAdd(v) => cache.update(v.deref()).await,
259 Event::MemberRemove(v) => cache.update(v).await,
260 Event::MemberUpdate(v) => cache.update(v.deref()).await,
261 Event::MemberChunk(v) => cache.update(v).await,
262 Event::MessageCreate(v) => cache.update(v.deref()).await,
263 Event::MessageDelete(v) => cache.update(v).await,
264 Event::MessageDeleteBulk(v) => cache.update(v).await,
265 Event::MessageUpdate(v) => cache.update(v.deref()).await,
266 Event::PresenceUpdate(v) => cache.update(v.deref()).await,
267 Event::ReactionAdd(v) => cache.update(v.deref()).await,
268 Event::ReactionRemove(v) => cache.update(v.deref()).await,
269 Event::ReactionRemoveAll(v) => cache.update(v).await,
270 Event::ReactionRemoveEmoji(v) => cache.update(v).await,
271 Event::Ready(v) => cache.update(v).await,
272 Event::RoleCreate(v) => cache.update(v).await,
273 Event::RoleDelete(v) => cache.update(v).await,
274 Event::RoleUpdate(v) => cache.update(v).await,
275 Event::StageInstanceCreate(v) => cache.update(v).await,
276 Event::StageInstanceDelete(v) => cache.update(v).await,
277 Event::StageInstanceUpdate(v) => cache.update(v).await,
278 Event::ThreadCreate(v) => cache.update(v.deref()).await,
279 Event::ThreadUpdate(v) => cache.update(v.deref()).await,
280 Event::ThreadDelete(v) => cache.update(v).await,
281 Event::ThreadListSync(v) => cache.update(v).await,
282 Event::UnavailableGuild(v) => cache.update(v).await,
283 Event::UserUpdate(v) => cache.update(v).await,
284 Event::VoiceStateUpdate(v) => cache.update(v.deref()).await,
285 Event::AutoModerationActionExecution(_)
287 | Event::AutoModerationRuleCreate(_)
288 | Event::AutoModerationRuleDelete(_)
289 | Event::AutoModerationRuleUpdate(_)
290 | Event::BanAdd(_)
291 | Event::BanRemove(_)
292 | Event::CommandPermissionsUpdate(_)
293 | Event::GatewayClose(_)
294 | Event::GatewayHeartbeat
295 | Event::GatewayHeartbeatAck
296 | Event::GatewayHello(_)
297 | Event::GatewayInvalidateSession(_)
298 | Event::GatewayReconnect
299 | Event::GuildAuditLogEntryCreate(_)
300 | Event::GuildIntegrationsUpdate(_)
301 | Event::InviteCreate(_)
302 | Event::InviteDelete(_)
303 | Event::Resumed
304 | Event::ThreadMembersUpdate(_)
305 | Event::ThreadMemberUpdate(_)
306 | Event::TypingStart(_)
307 | Event::VoiceServerUpdate(_)
308 | Event::WebhooksUpdate(_) => Ok(()),
309 _ => Ok(()), }
311 }
312}
313
314pub trait UpdateCache {
315 #[expect(async_fn_in_trait, reason = "honestly, what else do we do")]
316 async fn update(&self, cache: &Cache) -> Result<(), Error>;
317}
318
319#[derive(Serialize, Deserialize)]
320pub struct GuildResource<T> {
321 guild_id: Id<GuildMarker>,
322 value: T,
323}