Skip to main content

lavende_core/
protocol.rs

1pub mod codec {
2    pub mod io {
3        use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
4        use std::io::{self, Cursor, Read, Write};
5        pub const V1: u8 = 1;
6        pub const V2: u8 = 2;
7        pub const V3: u8 = 3;
8        pub struct BinaryBuffer<T>(Cursor<T>);
9        impl BinaryBuffer<Vec<u8>> {
10            pub fn new() -> Self {
11                Self(Cursor::new(Vec::new()))
12            }
13            pub fn with_capacity(capacity: usize) -> Self {
14                Self(Cursor::new(Vec::with_capacity(capacity)))
15            }
16            pub fn into_inner(self) -> Vec<u8> {
17                self.0.into_inner()
18            }
19        }
20        impl Default for BinaryBuffer<Vec<u8>> {
21            fn default() -> Self {
22                Self::new()
23            }
24        }
25        impl<T: AsRef<[u8]>> BinaryBuffer<T> {
26            pub fn from_data(data: T) -> Self {
27                Self(Cursor::new(data))
28            }
29            pub fn read_string(&mut self) -> io::Result<String> {
30                let size = self.0.read_u16::<BigEndian>()? as usize;
31                let mut content = vec![0u8; size];
32                self.0.read_exact(&mut content)?;
33                String::from_utf8(content)
34                    .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
35            }
36            pub fn read_nullable_string(&mut self) -> io::Result<Option<String>> {
37                if self.0.read_u8()? != 0 {
38                    self.read_string().map(Some)
39                } else {
40                    Ok(None)
41                }
42            }
43            pub fn read_json<V: serde::de::DeserializeOwned>(&mut self) -> io::Result<V> {
44                let s = self.read_string()?;
45                serde_json::from_str(&s).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
46            }
47        }
48        impl BinaryBuffer<Vec<u8>> {
49            pub fn write_string(&mut self, text: &str) -> io::Result<()> {
50                let raw = text.as_bytes();
51                self.0.write_u16::<BigEndian>(raw.len() as u16)?;
52                self.0.write_all(raw)
53            }
54            pub fn write_nullable_string(&mut self, text: Option<&str>) -> io::Result<()> {
55                match text {
56                    Some(s) => {
57                        self.0.write_u8(1)?;
58                        self.write_string(s)
59                    }
60                    None => self.0.write_u8(0),
61                }
62            }
63            pub fn write_json<V: serde::Serialize>(&mut self, value: &V) -> io::Result<()> {
64                let s = serde_json::to_string(value).map_err(io::Error::other)?;
65                self.write_string(&s)
66            }
67        }
68        impl<T> std::ops::Deref for BinaryBuffer<T> {
69            type Target = Cursor<T>;
70            fn deref(&self) -> &Self::Target {
71                &self.0
72            }
73        }
74        impl<T> std::ops::DerefMut for BinaryBuffer<T> {
75            fn deref_mut(&mut self) -> &mut Self::Target {
76                &mut self.0
77            }
78        }
79    }
80    pub mod decode {
81        use crate::protocol::{
82            CodecError, PlaylistInfo, Track, TrackInfo,
83            codec::io::{BinaryBuffer, V1, V2, V3},
84        };
85        use base64::{Engine, prelude::BASE64_STANDARD};
86        use byteorder::{BigEndian, ReadBytesExt};
87        pub fn decode_track(encoded: &str) -> Result<Track, CodecError> {
88            if encoded.is_empty() {
89                return Err(CodecError::EmptyInput);
90            }
91            let raw_payload = BASE64_STANDARD.decode(encoded)?;
92            if raw_payload.len() < 4 {
93                return Err(CodecError::TruncatedBuffer("header".into()));
94            }
95            let mut stream = BinaryBuffer::from_data(raw_payload);
96            let envelope = stream.read_u32::<BigEndian>()?;
97            let flags = (envelope >> 30) & 0x03;
98            let ver = if flags & 1 != 0 {
99                stream.read_u8()?
100            } else {
101                V1
102            };
103            if !(V1..=V3).contains(&ver) {
104                return Err(CodecError::UnknownVersion(ver));
105            }
106            let title = stream.read_string()?;
107            let author = stream.read_string()?;
108            let length = stream.read_u64::<BigEndian>()?;
109            let identifier = stream.read_string()?;
110            let is_stream = stream.read_u8()? != 0;
111            let (uri, artwork_url, isrc) = match ver {
112                V2 => (stream.read_nullable_string()?, None, None),
113                V3 => (
114                    stream.read_nullable_string()?,
115                    stream.read_nullable_string()?,
116                    stream.read_nullable_string()?,
117                ),
118                _ => (None, None, None),
119            };
120            let source_name = stream.read_string()?;
121            let position = stream.read_u64::<BigEndian>()?;
122            let user_data = stream.read_json().unwrap_or_else(|_| serde_json::json!({}));
123            Ok(Track {
124                encoded: encoded.to_owned(),
125                info: TrackInfo {
126                    identifier,
127                    is_seekable: !is_stream,
128                    author,
129                    length,
130                    is_stream,
131                    position,
132                    title,
133                    uri,
134                    artwork_url,
135                    isrc,
136                    source_name,
137                },
138                plugin_info: serde_json::json!({}),
139                user_data,
140            })
141        }
142        pub fn decode_playlist_info(encoded: &str) -> Result<PlaylistInfo, CodecError> {
143            let raw = BASE64_STANDARD.decode(encoded)?;
144            let mut stream = BinaryBuffer::from_data(raw);
145            Ok(PlaylistInfo {
146                name: stream.read_string()?,
147                selected_track: stream.read_i32::<BigEndian>()?,
148            })
149        }
150    }
151    pub mod encode {
152        use crate::protocol::{
153            CodecError, PlaylistInfo, TrackInfo,
154            codec::io::{BinaryBuffer, V3},
155        };
156        use base64::{Engine, prelude::BASE64_STANDARD};
157        use byteorder::{BigEndian, WriteBytesExt};
158        use std::io::Write;
159        pub fn encode_track(
160            metadata: &TrackInfo,
161            user_data: &serde_json::Value,
162        ) -> Result<String, CodecError> {
163            let mut blob = BinaryBuffer::with_capacity(128);
164            blob.write_u8(V3)?;
165            blob.write_string(&metadata.title)?;
166            blob.write_string(&metadata.author)?;
167            blob.write_u64::<BigEndian>(metadata.length)?;
168            blob.write_string(&metadata.identifier)?;
169            blob.write_u8(u8::from(metadata.is_stream))?;
170            blob.write_nullable_string(metadata.uri.as_deref())?;
171            blob.write_nullable_string(metadata.artwork_url.as_deref())?;
172            blob.write_nullable_string(metadata.isrc.as_deref())?;
173            blob.write_string(&metadata.source_name)?;
174            blob.write_u64::<BigEndian>(metadata.position)?;
175            if let Some(obj) = user_data.as_object() {
176                if !obj.is_empty() {
177                    blob.write_json(user_data)?;
178                }
179            } else if !user_data.is_null() {
180                blob.write_json(user_data)?;
181            }
182            let inner = blob.into_inner();
183            let header = (inner.len() as u32) | (1u32 << 30);
184            let mut out = Vec::with_capacity(4 + inner.len());
185            out.write_u32::<BigEndian>(header)?;
186            out.write_all(&inner)?;
187            Ok(BASE64_STANDARD.encode(&out))
188        }
189        pub fn encode_playlist_info(info: &PlaylistInfo) -> Result<String, CodecError> {
190            let mut blob = BinaryBuffer::with_capacity(64);
191            blob.write_string(&info.name)?;
192            blob.write_i32::<BigEndian>(info.selected_track)?;
193            Ok(BASE64_STANDARD.encode(blob.into_inner()))
194        }
195    }
196    pub use decode::{decode_playlist_info, decode_track};
197    pub use encode::{encode_playlist_info, encode_track};
198}
199pub mod info {
200    use serde::Serialize;
201    #[derive(Debug, Serialize)]
202    #[serde(rename_all = "camelCase")]
203    pub struct Info {
204        pub version: Version,
205        pub build_time: u64,
206        pub git: GitInfo,
207        pub jvm: String,
208        pub lavaplayer: String,
209        pub source_managers: Vec<String>,
210        pub filters: Vec<String>,
211        pub plugins: Vec<Plugin>,
212    }
213    #[derive(Debug, Serialize)]
214    #[serde(rename_all = "camelCase")]
215    pub struct Version {
216        pub semver: String,
217        pub major: u32,
218        pub minor: u32,
219        pub patch: u32,
220        pub pre_release: Option<String>,
221    }
222    #[derive(Debug, Serialize)]
223    #[serde(rename_all = "camelCase")]
224    pub struct GitInfo {
225        pub branch: String,
226        pub commit: String,
227        pub commit_time: u64,
228    }
229    #[derive(Debug, Serialize)]
230    pub struct Plugin {
231        pub name: String,
232        pub version: String,
233    }
234}
235pub mod stats {
236    use serde::Serialize;
237    #[derive(Debug, Clone, Serialize)]
238    #[serde(rename_all = "camelCase")]
239    pub struct Stats {
240        pub players: u64,
241        pub playing_players: u64,
242        pub uptime: u64,
243        pub memory: Memory,
244        pub cpu: Cpu,
245        #[serde(skip_serializing_if = "Option::is_none")]
246        pub frame_stats: Option<FrameStats>,
247    }
248    #[derive(Debug, Clone, Serialize)]
249    #[serde(rename_all = "camelCase")]
250    pub struct Memory {
251        pub free: u64,
252        pub used: u64,
253        pub allocated: u64,
254        pub reservable: u64,
255    }
256    #[derive(Debug, Clone, Serialize)]
257    #[serde(rename_all = "camelCase")]
258    pub struct Cpu {
259        pub cores: i32,
260        pub system_load: f64,
261        pub lavalink_load: f64,
262    }
263    #[derive(Debug, Clone, Serialize)]
264    #[serde(rename_all = "camelCase")]
265    pub struct FrameStats {
266        pub sent: i32,
267        pub nulled: i32,
268        pub deficit: i32,
269    }
270}
271pub mod routeplanner {
272    use serde::Serialize;
273    #[derive(Debug, Serialize, Clone)]
274    #[serde(tag = "class", content = "details")]
275    pub enum RoutePlannerStatus {
276        RotatingIpRoutePlanner(RotatingIpDetails),
277        NanoIpRoutePlanner(NanoIpDetails),
278        RotatingNanoIpRoutePlanner(RotatingNanoIpDetails),
279        BalancingIpRoutePlanner(BalancingIpDetails),
280    }
281    #[derive(Debug, Serialize, Clone)]
282    #[serde(rename_all = "camelCase")]
283    pub struct RotatingIpDetails {
284        pub ip_block: IpBlock,
285        pub failing_addresses: Vec<FailingAddress>,
286        pub rotate_index: String,
287        pub ip_index: String,
288        pub current_address: String,
289    }
290    #[derive(Debug, Serialize, Clone)]
291    #[serde(rename_all = "camelCase")]
292    pub struct NanoIpDetails {
293        pub ip_block: IpBlock,
294        pub failing_addresses: Vec<FailingAddress>,
295        pub current_address: String,
296    }
297    #[derive(Debug, Serialize, Clone)]
298    #[serde(rename_all = "camelCase")]
299    pub struct RotatingNanoIpDetails {
300        pub ip_block: IpBlock,
301        pub failing_addresses: Vec<FailingAddress>,
302        pub block_index: String,
303        pub current_address_index: String,
304    }
305    #[derive(Debug, Serialize, Clone)]
306    #[serde(rename_all = "camelCase")]
307    pub struct BalancingIpDetails {
308        pub ip_block: IpBlock,
309        pub failing_addresses: Vec<FailingAddress>,
310    }
311    #[derive(Debug, Serialize, Clone)]
312    #[serde(rename_all = "camelCase")]
313    pub struct IpBlock {
314        #[serde(rename = "type")]
315        pub block_type: String,
316        pub size: String,
317    }
318    #[derive(Debug, Serialize, Clone)]
319    #[serde(rename_all = "camelCase")]
320    pub struct FailingAddress {
321        pub failing_address: String,
322        pub failing_timestamp: u64,
323        pub failing_time: String,
324    }
325    #[derive(Debug, serde::Deserialize)]
326    pub struct FreeAddressRequest {
327        pub address: String,
328    }
329}
330pub mod tracks {
331    use crate::protocol::codec::{decode_track, encode_track};
332    use serde::{Deserialize, Serialize};
333    #[derive(Debug, Clone, Serialize, Deserialize)]
334    #[serde(rename_all = "camelCase")]
335    pub struct Track {
336        pub encoded: String,
337        pub info: TrackInfo,
338        #[serde(default = "serde_json::Value::default")]
339        pub plugin_info: serde_json::Value,
340        #[serde(default = "serde_json::Value::default")]
341        pub user_data: serde_json::Value,
342    }
343    impl Track {
344        pub fn new(info: TrackInfo) -> Self {
345            let mut track = Self {
346                encoded: String::new(),
347                info,
348                plugin_info: serde_json::json!({}),
349                user_data: serde_json::json!({}),
350            };
351            track.encoded = track.encode();
352            track
353        }
354        pub fn encode(&self) -> String {
355            encode_track(&self.info, &self.user_data).unwrap_or_else(|_| self.encoded.clone())
356        }
357        pub fn decode(encoded: &str) -> Option<Self> {
358            decode_track(encoded).ok()
359        }
360    }
361    #[derive(Debug, Clone, Serialize, Deserialize, Default)]
362    #[serde(rename_all = "camelCase")]
363    pub struct TrackInfo {
364        pub identifier: String,
365        pub is_seekable: bool,
366        pub author: String,
367        pub length: u64,
368        pub is_stream: bool,
369        pub position: u64,
370        pub title: String,
371        pub uri: Option<String>,
372        pub artwork_url: Option<String>,
373        pub isrc: Option<String>,
374        pub source_name: String,
375    }
376    #[derive(Debug, Serialize, Deserialize)]
377    #[serde(tag = "loadType", content = "data", rename_all = "camelCase")]
378    pub enum LoadResult {
379        Track(Track),
380        Playlist(PlaylistData),
381        Search(Vec<Track>),
382        Empty {},
383        Error(LoadError),
384    }
385    #[derive(Debug, Clone, Serialize, Deserialize)]
386    #[serde(rename_all = "camelCase")]
387    pub struct PlaylistData {
388        pub info: PlaylistInfo,
389        pub plugin_info: serde_json::Value,
390        pub tracks: Vec<Track>,
391    }
392    #[derive(Debug, Clone, Serialize, Deserialize)]
393    #[serde(rename_all = "camelCase")]
394    pub struct TextData {
395        pub text: String,
396        pub plugin: serde_json::Value,
397    }
398    #[derive(Debug, Clone, Serialize, Deserialize)]
399    #[serde(rename_all = "camelCase")]
400    pub struct SearchResult {
401        pub tracks: Vec<Track>,
402        pub albums: Vec<PlaylistData>,
403        pub artists: Vec<PlaylistData>,
404        pub playlists: Vec<PlaylistData>,
405        pub texts: Vec<TextData>,
406        pub plugin: serde_json::Value,
407    }
408    #[derive(Debug, Clone, Serialize, Deserialize)]
409    #[serde(rename_all = "camelCase")]
410    pub struct PlaylistInfo {
411        pub name: String,
412        pub selected_track: i32,
413    }
414    #[derive(Debug, Serialize, Deserialize)]
415    #[serde(rename_all = "camelCase")]
416    pub struct LoadError {
417        pub message: Option<String>,
418        pub severity: crate::common::Severity,
419        pub cause: String,
420        #[serde(skip_serializing_if = "Option::is_none")]
421        pub cause_stack_trace: Option<String>,
422    }
423}
424pub mod events {
425    use crate::protocol::tracks::Track;
426    use serde::{Deserialize, Serialize};
427    #[derive(Debug, Clone, Serialize, Deserialize)]
428    #[serde(rename_all = "camelCase")]
429    pub struct PlayerState {
430        pub time: u64,
431        pub position: u64,
432        pub connected: bool,
433        pub ping: i64,
434    }
435    #[derive(Debug, Serialize)]
436    #[serde(tag = "op", rename_all = "camelCase")]
437    pub enum OutgoingMessage {
438        Ready {
439            resumed: bool,
440            #[serde(rename = "sessionId")]
441            session_id: crate::common::types::SessionId,
442        },
443        #[serde(rename = "playerUpdate")]
444        PlayerUpdate {
445            #[serde(rename = "guildId")]
446            guild_id: crate::common::types::GuildId,
447            state: PlayerState,
448        },
449        #[serde(rename = "stats")]
450        Stats {
451            #[serde(flatten)]
452            stats: super::stats::Stats,
453        },
454        #[serde(rename = "event")]
455        Event {
456            #[serde(flatten)]
457            event: Box<RustalinkEvent>,
458        },
459    }
460    #[derive(Debug, Serialize)]
461    #[serde(tag = "type", rename_all = "camelCase")]
462    pub enum RustalinkEvent {
463        #[serde(rename = "TrackStartEvent")]
464        TrackStart {
465            #[serde(rename = "guildId")]
466            guild_id: crate::common::types::GuildId,
467            track: Track,
468        },
469        #[serde(rename = "TrackEndEvent")]
470        TrackEnd {
471            #[serde(rename = "guildId")]
472            guild_id: crate::common::types::GuildId,
473            track: Track,
474            reason: TrackEndReason,
475        },
476        #[serde(rename = "TrackExceptionEvent")]
477        TrackException {
478            #[serde(rename = "guildId")]
479            guild_id: crate::common::types::GuildId,
480            track: Track,
481            exception: TrackException,
482        },
483        #[serde(rename = "TrackStuckEvent")]
484        TrackStuck {
485            #[serde(rename = "guildId")]
486            guild_id: crate::common::types::GuildId,
487            track: Track,
488            #[serde(rename = "thresholdMs")]
489            threshold_ms: u64,
490        },
491        #[serde(rename = "LyricsFoundEvent")]
492        LyricsFound {
493            #[serde(rename = "guildId")]
494            guild_id: crate::common::types::GuildId,
495            lyrics: super::models::RustalinkLyrics,
496        },
497        #[serde(rename = "LyricsNotFoundEvent")]
498        LyricsNotFound {
499            #[serde(rename = "guildId")]
500            guild_id: crate::common::types::GuildId,
501        },
502        #[serde(rename = "LyricsLineEvent")]
503        LyricsLine {
504            #[serde(rename = "guildId")]
505            guild_id: crate::common::types::GuildId,
506            line_index: i32,
507            line: super::models::RustalinkLyricsLine,
508            skipped: bool,
509        },
510        #[serde(rename = "WebSocketClosedEvent")]
511        WebSocketClosed {
512            #[serde(rename = "guildId")]
513            guild_id: crate::common::types::GuildId,
514            code: u16,
515            reason: String,
516            #[serde(rename = "byRemote")]
517            by_remote: bool,
518        },
519    }
520    #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
521    #[serde(rename_all = "camelCase")]
522    pub enum TrackEndReason {
523        #[serde(rename = "finished")]
524        Finished,
525        #[serde(rename = "loadFailed")]
526        LoadFailed,
527        #[serde(rename = "stopped")]
528        Stopped,
529        #[serde(rename = "replaced")]
530        Replaced,
531        #[serde(rename = "cleanup")]
532        Cleanup,
533    }
534    #[derive(Debug, Clone, Serialize)]
535    #[serde(rename_all = "camelCase")]
536    pub struct TrackException {
537        pub message: Option<String>,
538        pub severity: crate::common::Severity,
539        pub cause: String,
540        pub cause_stack_trace: Option<String>,
541    }
542}
543pub mod models {
544    use serde::{Deserialize, Serialize};
545    #[derive(Deserialize)]
546    pub struct LoadTracksQuery {
547        pub identifier: String,
548    }
549    #[derive(Deserialize)]
550    pub struct LoadSearchQuery {
551        pub query: String,
552        pub types: Option<String>,
553    }
554    #[derive(Deserialize)]
555    #[serde(rename_all = "camelCase")]
556    pub struct DecodeTrackQuery {
557        pub encoded_track: Option<String>,
558        pub track: Option<String>,
559    }
560    #[derive(Deserialize)]
561    pub struct EncodedTracks(pub Vec<String>);
562    #[derive(Serialize)]
563    pub struct Tracks {
564        pub tracks: Vec<crate::protocol::tracks::Track>,
565    }
566    #[derive(Serialize)]
567    pub struct Exception {
568        pub message: String,
569        pub severity: String,
570        pub cause: String,
571    }
572    #[derive(Serialize, Deserialize, Debug, Clone)]
573    #[serde(rename_all = "camelCase")]
574    pub struct LyricsLine {
575        pub text: String,
576        pub timestamp: u64,
577        pub duration: u64,
578    }
579    #[derive(Serialize, Deserialize, Debug, Clone)]
580    #[serde(rename_all = "camelCase")]
581    pub struct LyricsData {
582        pub name: String,
583        pub author: String,
584        pub provider: String,
585        pub text: String,
586        pub lines: Option<Vec<LyricsLine>>,
587    }
588    #[derive(Debug, Serialize, Deserialize)]
589    #[serde(tag = "loadType", content = "data", rename_all = "camelCase")]
590    pub enum LyricsLoadResult {
591        Lyrics(LyricsResultData),
592        Text(LyricsTextData),
593        Empty {},
594        Error(LyricsLoadError),
595    }
596    #[derive(Debug, Clone, Serialize, Deserialize)]
597    #[serde(rename_all = "camelCase")]
598    pub struct LyricsResultData {
599        pub name: String,
600        pub synced: bool,
601        pub lines: Vec<LyricsLine>,
602    }
603    #[derive(Debug, Clone, Serialize, Deserialize)]
604    #[serde(rename_all = "camelCase")]
605    pub struct LyricsTextData {
606        pub text: String,
607    }
608    #[derive(Debug, Serialize, Deserialize)]
609    #[serde(rename_all = "camelCase")]
610    pub struct LyricsLoadError {
611        pub message: String,
612        pub severity: crate::common::Severity,
613    }
614    #[derive(Serialize, Deserialize, Debug, Clone)]
615    #[serde(rename_all = "camelCase")]
616    pub struct RustalinkLyrics {
617        pub source_name: String,
618        pub provider: Option<String>,
619        pub text: Option<String>,
620        pub lines: Option<Vec<RustalinkLyricsLine>>,
621        pub plugin: serde_json::Value,
622    }
623    #[derive(Serialize, Deserialize, Debug, Clone)]
624    #[serde(rename_all = "camelCase")]
625    pub struct RustalinkLyricsLine {
626        pub timestamp: u64,
627        pub duration: Option<u64>,
628        pub line: String,
629        pub plugin: serde_json::Value,
630    }
631    #[derive(Deserialize)]
632    #[serde(rename_all = "camelCase")]
633    pub struct GetLyricsQuery {
634        pub track: String,
635        #[serde(default)]
636        pub skip_track_source: bool,
637    }
638    #[derive(Deserialize)]
639    #[serde(rename_all = "camelCase")]
640    pub struct GetPlayerLyricsQuery {
641        #[serde(default)]
642        pub skip_track_source: bool,
643    }
644}
645use thiserror::Error;
646#[derive(Debug, Error)]
647pub enum CodecError {
648    #[error("Empty input")]
649    EmptyInput,
650    #[error("Corrupt buffer: {0}")]
651    CorruptBuffer(String),
652    #[error("Truncated buffer: {0}")]
653    TruncatedBuffer(String),
654    #[error("Unknown track version: {0}")]
655    UnknownVersion(u8),
656    #[error("Base64 error: {0}")]
657    Base64(#[from] base64::DecodeError),
658    #[error("IO error: {0}")]
659    Io(#[from] std::io::Error),
660    #[error("UTF-8 error: {0}")]
661    Utf8(#[from] std::string::FromUtf8Error),
662}
663pub use codec::*;
664pub use events::*;
665pub use info::*;
666pub use models::*;
667pub use routeplanner::*;
668pub use stats::*;
669pub use tracks::*;