1use std::fmt::{Display, Formatter};
8use std::io::ErrorKind;
9use std::net::SocketAddr;
10use std::time::Duration;
11
12use futures_util::{SinkExt, StreamExt};
13use tokio::net::{TcpListener, TcpStream};
14use tokio_tungstenite::{accept_async, WebSocketStream};
15use tokio_tungstenite::tungstenite::{Error, Message};
16
17#[repr(u32)]
21#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
22pub enum TrackState {
23 Playing = 2,
24 Paused = 1,
25 Stopped = 0,
26}
27
28impl Display for TrackState {
29 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
30 match self {
31 TrackState::Playing => write!(f, "Playing"),
32 TrackState::Paused => write!(f, "Paused"),
33 TrackState::Stopped => write!(f, "Stopped"),
34 }
35 }
36}
37
38impl TrackState {
39 pub fn from_u32(n: u32) -> Self {
45 match n {
46 2 => Self::Playing,
47 1 => Self::Paused,
48 _ => Self::Stopped
49 }
50 }
51}
52
53impl Default for TrackState {
54 fn default() -> Self {
55 Self::Stopped
56 }
57}
58
59#[derive(Debug, Clone, Default, Eq, PartialEq, Ord, PartialOrd)]
61pub struct TrackInfo {
62 pub uid: String,
64 pub uri: String,
66 pub state: TrackState,
68 pub duration: Duration,
70 pub title: String,
72 pub album: String,
74 pub artist: Vec<String>,
76 pub cover_url: Option<String>,
78 pub background_url: Option<String>,
81}
82
83impl TrackInfo {
84 pub fn eq_ignore_state(&self, other: &Self) -> bool {
85 self.uid == other.uid
86 }
87}
88
89#[derive(Debug)]
90pub enum SpotifyEvent {
91 TrackChanged(TrackInfo),
93 StateChanged(TrackState),
97 ProgressChanged(f64),
102}
103
104pub struct SpotifyListener {
105 pub listener: TcpListener,
106}
107
108#[derive(Debug)]
109pub struct SpotifyConnection {
110 pub ws: WebSocketStream<TcpStream>,
111}
112
113impl SpotifyConnection {
114 fn parse_track_info(data: &[&str]) -> TrackInfo {
115 TrackInfo {
116 uid: data[0].to_string(),
117 uri: data[1].to_string(),
118 state: TrackState::from_u32(data[2].parse().unwrap_or(0)),
119 duration: Duration::from_millis(data[3].parse().unwrap_or(0)),
120 title: data[4].to_string().replace("${#{#{SEMI_COLON}#}#}$", ";"),
121 album: data[5].to_string().replace("${#{#{SEMI_COLON}#}#}$", ";"),
122 artist: vec![data[6].to_string().replace("${#{#{SEMI_COLON}#}#}$", ";")],
123 cover_url: Some(data[7].to_string()).filter(|it| !it.contains("NONE")),
124 background_url: Some(data[8].to_string()).filter(|it| !it.contains("NONE")),
125 }
126 }
127
128 fn handle_message(message: String) -> Option<Result<SpotifyEvent, Error>> {
129 let mut data = message.split(';').collect::<Vec<_>>();
130 let invalid_data_err = Some(Err(Error::Io(std::io::Error::new(ErrorKind::InvalidData, "Invalid data"))));
131
132 if data.is_empty() {
133 return invalid_data_err;
134 }
135
136 match data.remove(0) {
137 "TRACK_CHANGED" if data.len() >= 9 => {
138 let info = Self::parse_track_info(&data);
139
140 Some(Ok(SpotifyEvent::TrackChanged(info)))
141 }
142 "STATE_CHANGED" if !data.is_empty() => {
143 let state = TrackState::from_u32(data[0].parse().unwrap_or(0));
144
145 Some(Ok(SpotifyEvent::StateChanged(state)))
146 }
147 "PROGRESS_CHANGED" if !data.is_empty() => {
148 let progress = data[0].parse().unwrap_or(0f64);
149
150 Some(Ok(SpotifyEvent::ProgressChanged(progress)))
151 }
152 _ => invalid_data_err
153 }
154 }
155
156 pub async fn set_progress_interval(&mut self, interval: Duration) -> Result<(), Error> {
160 let ms = interval.as_millis();
161 let text = format!("SET_PROGRESS_INTERVAL;{}", ms);
162
163 self.ws.send(Message::Text(text)).await
164 }
165
166 pub async fn next(&mut self) -> Option<Result<SpotifyEvent, Error>> {
168 let message = self.ws.next().await?;
169
170 match message {
171 Ok(Message::Text(message)) => Self::handle_message(message),
172 Ok(_) => Some(Err(Error::Io(std::io::Error::new(ErrorKind::Unsupported, "Unsupported message type, only supports Text")))),
173 Err(err) => Some(Err(err))
174 }
175 }
176}
177
178impl SpotifyListener {
179 pub async fn bind_default() -> std::io::Result<Self> {
181 Self::bind_local(19532).await
182 }
183
184 pub async fn bind_local(port: u16) -> std::io::Result<Self> {
186 Self::bind(format!("127.0.0.1:{}", port).parse().unwrap()).await
187 }
188
189 pub async fn bind(addr: SocketAddr) -> std::io::Result<Self> {
191 let listener = TcpListener::bind(addr).await?;
192
193 Ok(Self { listener })
194 }
195
196 pub async fn get_connection(&self) -> Result<SpotifyConnection, Error> {
198 let (stream, _) = self.listener.accept().await.map_err(|_| Error::ConnectionClosed)?;
199 let ws = accept_async(stream).await?;
200
201 Ok(SpotifyConnection { ws })
202 }
203}