tulpje_cache/models/
voice_state.rs

1use serde::{Deserialize, Serialize};
2use twilight_model::{
3    id::{
4        Id,
5        marker::{ChannelMarker, GuildMarker, UserMarker},
6    },
7    util::Timestamp,
8    voice::VoiceState,
9};
10
11use crate::{Cache, Error};
12
13#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
14pub struct CachedVoiceState {
15    channel_id: Id<ChannelMarker>,
16    deaf: bool,
17    guild_id: Id<GuildMarker>,
18    mute: bool,
19    request_to_speak_timestamp: Option<Timestamp>,
20    self_deaf: bool,
21    self_mute: bool,
22    self_stream: bool,
23    self_video: bool,
24    session_id: String,
25    suppress: bool,
26    user_id: Id<UserMarker>,
27}
28
29impl From<(Id<ChannelMarker>, Id<GuildMarker>, VoiceState)> for CachedVoiceState {
30    fn from(
31        (channel_id, guild_id, voice_state): (Id<ChannelMarker>, Id<GuildMarker>, VoiceState),
32    ) -> Self {
33        // Reasons for dropping fields:
34        //
35        // - `channel_id`: provided as a function parameter
36        // - `guild_id`: provided as a function parameter
37        // - `member`: we have the user's ID from the `user_id` field
38        #[expect(
39            clippy::unneeded_field_pattern,
40            reason = "clearer that we're explicitly skipping those fields"
41        )]
42        let VoiceState {
43            channel_id: _,
44            deaf,
45            guild_id: _,
46            member: _,
47            mute,
48            self_deaf,
49            self_mute,
50            self_stream,
51            self_video,
52            session_id,
53            suppress,
54            user_id,
55            request_to_speak_timestamp,
56        } = voice_state;
57
58        Self {
59            channel_id,
60            deaf,
61            guild_id,
62            mute,
63            request_to_speak_timestamp,
64            self_deaf,
65            self_mute,
66            self_stream,
67            self_video,
68            session_id,
69            suppress,
70            user_id,
71        }
72    }
73}
74
75impl PartialEq<VoiceState> for CachedVoiceState {
76    fn eq(&self, other: &VoiceState) -> bool {
77        Some(self.channel_id) == other.channel_id
78            && self.deaf == other.deaf
79            && Some(self.guild_id) == other.guild_id
80            && self.mute == other.mute
81            && self.request_to_speak_timestamp == other.request_to_speak_timestamp
82            && self.self_deaf == other.self_deaf
83            && self.self_mute == other.self_mute
84            && self.self_stream == other.self_stream
85            && self.self_video == other.self_video
86            && self.session_id == other.session_id
87            && self.suppress == other.suppress
88            && self.user_id == other.user_id
89    }
90}
91
92impl Cache {
93    pub(crate) async fn cache_voice_states(
94        &self,
95        voice_states: impl IntoIterator<Item = VoiceState>,
96    ) -> Result<(), Error> {
97        for voice_state in voice_states {
98            self.cache_voice_state(voice_state).await?;
99        }
100
101        Ok(())
102    }
103
104    pub(crate) async fn cache_voice_state(&self, voice_state: VoiceState) -> Result<(), Error> {
105        // This should always exist, but let's check just in case.
106        let Some(guild_id) = voice_state.guild_id else {
107            return Ok(());
108        };
109
110        let user_id = voice_state.user_id;
111
112        // Check if the user is switching channels in the same guild (ie. they already have a voice state entry)
113        if let Some(voice_state) = self.voice_states.get(&(guild_id, user_id)).await? {
114            self.voice_state_channels
115                .remove(&voice_state.channel_id, &(guild_id, user_id))
116                .await?;
117        }
118
119        if let Some(channel_id) = voice_state.channel_id {
120            let cached_voice_state = CachedVoiceState::from((channel_id, guild_id, voice_state));
121
122            self.voice_states
123                .insert(&(guild_id, user_id), &cached_voice_state)
124                .await?;
125            self.voice_state_guilds.insert(&guild_id, &user_id).await?;
126            self.voice_state_channels
127                .insert(&channel_id, &(guild_id, user_id))
128                .await?;
129        } else {
130            // voice channel_id does not exist, signifying that the user has left
131
132            self.voice_state_guilds.remove(&guild_id, &user_id).await?;
133            self.voice_states.remove(&(guild_id, user_id)).await?;
134        }
135
136        Ok(())
137    }
138}