Skip to main content

twilight_lavalink/model/
outgoing.rs

1//! Events that clients send to Lavalink.
2use serde::{Deserialize, Serialize};
3use twilight_model::{
4    gateway::payload::incoming::VoiceServerUpdate,
5    id::{
6        Id,
7        marker::{ChannelMarker, GuildMarker},
8    },
9};
10
11/// The track on the player. The encoded and identifier are mutually exclusive.
12/// We don't support userData field currently.
13#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
14#[non_exhaustive]
15#[serde(rename_all = "camelCase")]
16pub struct UpdatePlayerTrack {
17    /// The string of the track to play.
18    #[serde(flatten)]
19    pub track_string: TrackOption,
20}
21
22/// Used to play a specific track. These are mutually exclusive.
23/// When identifier is used, Lavalink will try to resolve the identifier as a
24/// single track. An HTTP 400 error is returned when resolving a playlist,
25/// search result, or no tracks.
26#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
27#[serde(rename_all = "lowercase")]
28pub enum TrackOption {
29    /// The base64 encoded track to play. null stops the current track.
30    Encoded(Option<String>),
31    /// The identifier of the track to play.
32    Identifier(String),
33}
34
35/// An outgoing event to send to Lavalink.
36#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
37#[non_exhaustive]
38#[serde(untagged)]
39pub enum OutgoingEvent {
40    /// Destroy a player for a guild.
41    Destroy(Destroy),
42    /// Equalize a player.
43    Equalizer(Equalizer),
44    /// Pause or unpause a player.
45    Pause(Pause),
46    /// Play a track.
47    Play(Play),
48    /// Seek a player's active track to a new position.
49    Seek(Seek),
50    /// Stop a player.
51    Stop(Stop),
52    /// A combined voice server and voice state update.
53    VoiceUpdate(VoiceUpdate),
54    /// Set the volume of a player.
55    Volume(Volume),
56}
57
58impl From<Destroy> for OutgoingEvent {
59    fn from(event: Destroy) -> OutgoingEvent {
60        Self::Destroy(event)
61    }
62}
63
64impl From<Equalizer> for OutgoingEvent {
65    fn from(event: Equalizer) -> OutgoingEvent {
66        Self::Equalizer(event)
67    }
68}
69
70impl From<Pause> for OutgoingEvent {
71    fn from(event: Pause) -> OutgoingEvent {
72        Self::Pause(event)
73    }
74}
75
76impl From<Play> for OutgoingEvent {
77    fn from(event: Play) -> OutgoingEvent {
78        Self::Play(event)
79    }
80}
81
82impl From<Seek> for OutgoingEvent {
83    fn from(event: Seek) -> OutgoingEvent {
84        Self::Seek(event)
85    }
86}
87
88impl From<Stop> for OutgoingEvent {
89    fn from(event: Stop) -> OutgoingEvent {
90        Self::Stop(event)
91    }
92}
93
94impl From<VoiceUpdate> for OutgoingEvent {
95    fn from(event: VoiceUpdate) -> OutgoingEvent {
96        Self::VoiceUpdate(event)
97    }
98}
99
100impl From<Volume> for OutgoingEvent {
101    fn from(event: Volume) -> OutgoingEvent {
102        Self::Volume(event)
103    }
104}
105
106impl OutgoingEvent {
107    /// ID of the destination guild of this event.
108    pub const fn guild_id(&self) -> Id<GuildMarker> {
109        match self {
110            Self::VoiceUpdate(voice_update) => voice_update.guild_id,
111            Self::Play(play) => play.guild_id,
112            Self::Destroy(destroy) => destroy.guild_id,
113            Self::Equalizer(equalize) => equalize.guild_id,
114            Self::Pause(pause) => pause.guild_id,
115            Self::Seek(seek) => seek.guild_id,
116            Self::Stop(stop) => stop.guild_id,
117            Self::Volume(volume) => volume.guild_id,
118        }
119    }
120
121    /// Whether this event replaces the currently playing track.
122    pub(crate) const fn no_replace(&self) -> bool {
123        match self {
124            Self::Play(play) => play.no_replace,
125            Self::Stop(_) => false,
126            _ => true,
127        }
128    }
129}
130
131/// Destroy a player from a node.
132#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
133#[non_exhaustive]
134#[serde(rename_all = "camelCase")]
135pub struct Destroy {
136    /// The guild ID of the player.
137    pub guild_id: Id<GuildMarker>,
138}
139
140impl Destroy {
141    /// Create a new destroy event.
142    pub const fn new(guild_id: Id<GuildMarker>) -> Self {
143        Self { guild_id }
144    }
145}
146
147impl From<Id<GuildMarker>> for Destroy {
148    fn from(guild_id: Id<GuildMarker>) -> Self {
149        Self { guild_id }
150    }
151}
152
153/// Filters to pass to the update player endpoint.
154#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
155#[non_exhaustive]
156#[serde(rename_all = "camelCase")]
157pub enum Filters {
158    /// Adjusts 15 different bands
159    Equalizer(Equalizer),
160}
161
162/// Equalize a player.
163#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
164#[non_exhaustive]
165#[serde(rename_all = "camelCase")]
166pub struct Equalizer {
167    /// The bands to use as part of the equalizer.
168    pub equalizer: Vec<EqualizerBand>,
169    /// The guild ID of the player.
170    #[serde(skip_serializing)]
171    pub guild_id: Id<GuildMarker>,
172}
173
174impl Equalizer {
175    /// Create a new equalizer event.
176    pub fn new(guild_id: Id<GuildMarker>, bands: Vec<EqualizerBand>) -> Self {
177        Self::from((guild_id, bands))
178    }
179}
180
181impl From<(Id<GuildMarker>, Vec<EqualizerBand>)> for Equalizer {
182    fn from((guild_id, bands): (Id<GuildMarker>, Vec<EqualizerBand>)) -> Self {
183        Self {
184            equalizer: bands,
185            guild_id,
186        }
187    }
188}
189
190/// A band of the equalizer event.
191#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
192#[non_exhaustive]
193#[serde(rename_all = "camelCase")]
194pub struct EqualizerBand {
195    /// The band.
196    pub band: i64,
197    /// The gain.
198    pub gain: f64,
199}
200
201impl EqualizerBand {
202    /// Create a new equalizer band.
203    pub fn new(band: i64, gain: f64) -> Self {
204        Self::from((band, gain))
205    }
206}
207
208impl From<(i64, f64)> for EqualizerBand {
209    fn from((band, gain): (i64, f64)) -> Self {
210        Self { band, gain }
211    }
212}
213
214/// Pause or unpause a player.
215#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
216#[non_exhaustive]
217#[serde(rename_all = "camelCase")]
218pub struct Pause {
219    /// The guild ID of the player.
220    pub guild_id: Id<GuildMarker>,
221    /// Whether to pause the player.
222    ///
223    /// Set to `true` to pause or `false` to resume.
224    pub paused: bool,
225}
226
227impl Pause {
228    /// Create a new pause event.
229    ///
230    /// Set to `true` to pause the player or `false` to resume it.
231    pub fn new(guild_id: Id<GuildMarker>, pause: bool) -> Self {
232        Self::from((guild_id, pause))
233    }
234}
235
236impl From<(Id<GuildMarker>, bool)> for Pause {
237    fn from((guild_id, pause): (Id<GuildMarker>, bool)) -> Self {
238        Self {
239            guild_id,
240            paused: pause,
241        }
242    }
243}
244
245/// Play a track, optionally specifying to not skip the current track.
246#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
247#[non_exhaustive]
248#[serde(rename_all = "camelCase")]
249pub struct Play {
250    /// The position in milliseconds to end the track.
251    ///
252    /// `Some(None)` resets this if it was set previously.
253    #[serde(skip_serializing_if = "Option::is_none")]
254    pub end_time: Option<Option<u64>>,
255    /// The guild ID of the player.
256    #[serde(skip_serializing)]
257    pub guild_id: Id<GuildMarker>,
258    /// Whether or not to replace the currently playing track with this new
259    /// track.
260    ///
261    /// Set to `true` to keep playing the current playing track, or `false`
262    /// to replace the current playing track with a new one.
263    #[serde(skip_serializing)]
264    pub no_replace: bool,
265    /// The position in milliseconds to start the track from.
266    #[serde(skip_serializing_if = "Option::is_none")]
267    pub position: Option<u64>,
268    /// Whether the player is paused
269    #[serde(skip_serializing_if = "Option::is_none")]
270    pub paused: Option<bool>,
271    /// Information about the track to play.
272    #[serde(skip_serializing_if = "Option::is_none")]
273    pub track: Option<UpdatePlayerTrack>,
274    /// The player volume, in percentage, from 0 to 1000
275    #[serde(skip_serializing_if = "Option::is_none")]
276    pub volume: Option<u64>,
277}
278
279impl Play {
280    /// Create a new play event.
281    pub fn new(
282        guild_id: Id<GuildMarker>,
283        track: impl Into<String>,
284        start_time: impl Into<Option<u64>>,
285        end_time: impl Into<Option<u64>>,
286        no_replace: bool,
287    ) -> Self {
288        Self::from((guild_id, track, start_time, end_time, no_replace))
289    }
290}
291
292impl<T: Into<String>> From<(Id<GuildMarker>, T)> for Play {
293    fn from((guild_id, track): (Id<GuildMarker>, T)) -> Self {
294        Self::from((guild_id, track, None, None, true))
295    }
296}
297
298impl<T: Into<String>, S: Into<Option<u64>>> From<(Id<GuildMarker>, T, S)> for Play {
299    fn from((guild_id, track, start_time): (Id<GuildMarker>, T, S)) -> Self {
300        Self::from((guild_id, track, start_time, None, true))
301    }
302}
303
304impl<T: Into<String>, S: Into<Option<u64>>, E: Into<Option<u64>>> From<(Id<GuildMarker>, T, S, E)>
305    for Play
306{
307    fn from((guild_id, track, start_time, end_time): (Id<GuildMarker>, T, S, E)) -> Self {
308        Self::from((guild_id, track, start_time, end_time, true))
309    }
310}
311
312impl<T: Into<String>, S: Into<Option<u64>>, E: Into<Option<u64>>>
313    From<(Id<GuildMarker>, T, S, E, bool)> for Play
314{
315    fn from(
316        (guild_id, track, start_time, end_time, no_replace): (Id<GuildMarker>, T, S, E, bool),
317    ) -> Self {
318        Self {
319            guild_id,
320            no_replace,
321            position: start_time.into(),
322            end_time: Some(end_time.into()),
323            volume: None,
324            paused: None,
325            track: Some(UpdatePlayerTrack {
326                track_string: TrackOption::Encoded(Some(track.into())),
327            }),
328        }
329    }
330}
331
332/// Seek a player's active track to a new position.
333#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
334#[non_exhaustive]
335#[serde(rename_all = "camelCase")]
336pub struct Seek {
337    /// The guild ID of the player.
338    #[serde(skip_serializing)]
339    pub guild_id: Id<GuildMarker>,
340    /// The position in milliseconds to seek to.
341    pub position: i64,
342}
343
344impl Seek {
345    /// Create a new seek event.
346    pub fn new(guild_id: Id<GuildMarker>, position: i64) -> Self {
347        Self::from((guild_id, position))
348    }
349}
350
351impl From<(Id<GuildMarker>, i64)> for Seek {
352    fn from((guild_id, position): (Id<GuildMarker>, i64)) -> Self {
353        Self { guild_id, position }
354    }
355}
356
357/// Stop a player.
358#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
359#[non_exhaustive]
360#[serde(rename_all = "camelCase")]
361pub struct Stop {
362    /// The guild ID of the player.
363    #[serde(skip_serializing)]
364    pub guild_id: Id<GuildMarker>,
365    /// The track object to pass set to null
366    pub track: UpdatePlayerTrack,
367}
368
369impl Stop {
370    /// Create a new stop event.
371    pub fn new(guild_id: Id<GuildMarker>) -> Self {
372        Self::from(guild_id)
373    }
374}
375
376impl From<Id<GuildMarker>> for Stop {
377    fn from(guild_id: Id<GuildMarker>) -> Self {
378        Self {
379            guild_id,
380            track: UpdatePlayerTrack {
381                track_string: TrackOption::Encoded(None),
382            },
383        }
384    }
385}
386/// The voice payload for the combined server and state to send to lavalink.
387#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
388#[non_exhaustive]
389#[serde(rename_all = "camelCase")]
390pub struct Voice {
391    /// The Discord voice channel id the bot is connecting to.
392    #[serde(skip_serializing_if = "Option::is_none")]
393    pub channel_id: Option<Id<ChannelMarker>>,
394    /// The Discord voice endpoint to connect to.
395    pub endpoint: String,
396    /// The Discord voice session id to authenticate with. This is separate from the session id of lavalink.
397    pub session_id: String,
398    /// The Discord voice token to authenticate with.
399    pub token: String,
400}
401
402/// A combined voice server and voice state update.
403#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
404#[non_exhaustive]
405#[serde(rename_all = "camelCase")]
406pub struct VoiceUpdate {
407    /// The guild ID of the player.
408    #[serde(skip_serializing)]
409    pub guild_id: Id<GuildMarker>,
410    /// The voice payload for the combined server and state to send to lavalink.
411    pub voice: Voice,
412}
413
414impl VoiceUpdate {
415    /// Create a new voice update event.
416    pub fn new(
417        guild_id: Id<GuildMarker>,
418        session_id: impl Into<String>,
419        channel_id: Option<Id<ChannelMarker>>,
420        event: VoiceServerUpdate,
421    ) -> Self {
422        Self::from((guild_id, session_id, channel_id, event))
423    }
424}
425
426impl<T: Into<String>>
427    From<(
428        Id<GuildMarker>,
429        T,
430        Option<Id<ChannelMarker>>,
431        VoiceServerUpdate,
432    )> for VoiceUpdate
433{
434    fn from(
435        (guild_id, session_id, channel_id, event): (
436            Id<GuildMarker>,
437            T,
438            Option<Id<ChannelMarker>>,
439            VoiceServerUpdate,
440        ),
441    ) -> Self {
442        Self {
443            guild_id,
444            voice: Voice {
445                channel_id,
446                token: event.token,
447                endpoint: event.endpoint.unwrap_or("NO_ENDPOINT_RETURNED".to_string()),
448                session_id: session_id.into(),
449            },
450        }
451    }
452}
453
454/// Set the volume of a player.
455#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
456#[non_exhaustive]
457#[serde(rename_all = "camelCase")]
458pub struct Volume {
459    /// The guild ID of the player.
460    #[serde(skip_serializing)]
461    pub guild_id: Id<GuildMarker>,
462    /// The volume of the player from 0 to 1000. 100 is the default.
463    pub volume: i64,
464}
465
466impl Volume {
467    /// Create a new volume event.
468    pub fn new(guild_id: Id<GuildMarker>, volume: i64) -> Self {
469        Self::from((guild_id, volume))
470    }
471}
472
473impl From<(Id<GuildMarker>, i64)> for Volume {
474    fn from((guild_id, volume): (Id<GuildMarker>, i64)) -> Self {
475        Self { guild_id, volume }
476    }
477}