server/
server.rs

1use std::env;
2use std::io::Read;
3use std::sync::Arc;
4
5use clap::Parser;
6use native_tls::{Identity, TlsAcceptor as NativeTlsAcceptor};
7use serde_json::json;
8use tokio::io::{AsyncReadExt, AsyncWriteExt};
9use tokio_native_tls::TlsAcceptor;
10use tracing::{debug, error, info};
11
12use mrc::{get_property, playlist_clear, playlist_next, playlist_prev, quit, seek, set_property};
13
14#[derive(Parser)]
15#[command(author, version, about)]
16struct Config {
17    /// The IP address and port to bind the server to
18    #[arg(short, long, default_value = "127.0.0.1:8080")]
19    bind: String,
20
21    /// Path to MPV IPC socket
22    #[arg(short, long, default_value = "/tmp/mpvsocket")]
23    socket: String,
24}
25
26async fn handle_connection(
27    stream: tokio::net::TcpStream,
28    acceptor: Arc<TlsAcceptor>,
29) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
30    let mut stream = acceptor.accept(stream).await?;
31    let mut buffer = vec![0; 2048];
32
33    let n = stream.read(&mut buffer).await?;
34    let request = String::from_utf8_lossy(&buffer[..n]);
35
36    debug!("Received request:\n{}", request);
37
38    let headers = request.split("\r\n").collect::<Vec<&str>>();
39    let token_line = headers
40        .iter()
41        .find(|&&line| line.starts_with("Authorization:"));
42    let token = match token_line {
43        Some(line) => line.split(" ").nth(1).unwrap_or_default(),
44        None => "",
45    };
46
47    let auth_token = match env::var("AUTH_TOKEN") {
48        Ok(token) => token,
49        Err(_) => {
50            error!("Authentication token is not set. Connection cannot be accepted.");
51            stream.write_all(b"Authentication token not set\n").await?;
52
53            // You know what? I do not care to panic when the authentication token is
54            // missing in the environment. Start the goddamned server and hell, even
55            // accept incoming connections. Authenticated requests will be refused
56            // when the token is incorrect or not set, so we can simply continue here.
57            return Ok(());
58        }
59    };
60
61    if token != auth_token {
62        stream.write_all(b"Authentication failed\n").await?;
63        return Ok(());
64    }
65
66    info!("Client authenticated");
67    stream.write_all(b"Authenticated\n").await?;
68
69    let command = request.split("\r\n\r\n").last().unwrap_or("");
70    info!("Received command: {}", command);
71
72    let response = match process_command(command.trim()).await {
73        Ok(response) => response,
74        Err(e) => {
75            error!("Error processing command: {}", e);
76            format!("Error: {:?}", e)
77        }
78    };
79
80    let http_response = format!(
81        "HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n{}",
82        response.len(),
83        response
84    );
85    stream.write_all(http_response.as_bytes()).await?;
86
87    Ok(())
88}
89
90async fn process_command(command: &str) -> Result<String, String> {
91    match command {
92        "pause" => {
93            info!("Pausing playback");
94            set_property("pause", &json!(true), None)
95                .await
96                .map_err(|e| format!("Failed to pause: {:?}", e))?;
97            Ok("Paused playback\n".to_string())
98        }
99
100        "play" => {
101            info!("Unpausing playback");
102            set_property("pause", &json!(false), None)
103                .await
104                .map_err(|e| format!("Failed to play: {:?}", e))?;
105            Ok("Resumed playback\n".to_string())
106        }
107
108        "stop" => {
109            info!("Stopping playback and quitting MPV");
110            quit(None)
111                .await
112                .map_err(|e| format!("Failed to stop: {:?}", e))?;
113            Ok("Stopped playback\n".to_string())
114        }
115
116        "next" => {
117            info!("Skipping to next item in the playlist");
118            playlist_next(None)
119                .await
120                .map_err(|e| format!("Failed to skip to next: {:?}", e))?;
121            Ok("Skipped to next item\n".to_string())
122        }
123
124        "prev" => {
125            info!("Skipping to previous item in the playlist");
126            playlist_prev(None)
127                .await
128                .map_err(|e| format!("Failed to skip to previous: {:?}", e))?;
129            Ok("Skipped to previous item\n".to_string())
130        }
131
132        "seek" => {
133            let parts: Vec<&str> = command.split_whitespace().collect();
134            if let Some(seconds) = parts.get(1) {
135                if let Ok(sec) = seconds.parse::<i32>() {
136                    info!("Seeking to {} seconds", sec);
137                    seek(sec.into(), None)
138                        .await
139                        .map_err(|e| format!("Failed to seek: {:?}", e))?;
140                    return Ok(format!("Seeking to {} seconds\n", sec));
141                }
142            }
143            Err("Invalid seek command".to_string())
144        }
145
146        "clear" => {
147            info!("Clearing the playlist");
148            playlist_clear(None)
149                .await
150                .map_err(|e| format!("Failed to clear playlist: {:?}", e))?;
151            Ok("Cleared playlist\n".to_string())
152        }
153
154        "list" => {
155            info!("Listing playlist items");
156            match get_property("playlist", None).await {
157                Ok(Some(data)) => Ok(format!(
158                    "Playlist: {}",
159                    serde_json::to_string_pretty(&data).unwrap()
160                )),
161                Ok(None) => Err("No playlist data available".to_string()),
162                Err(e) => Err(format!("Failed to fetch playlist: {:?}", e)),
163            }
164        }
165        _ => Err("Unknown command".to_string()),
166    }
167}
168
169fn create_tls_acceptor() -> Result<TlsAcceptor, Box<dyn std::error::Error + Send + Sync>> {
170    let pfx_path = env::var("TLS_PFX_PATH")
171        .map_err(|_| std::io::Error::new(std::io::ErrorKind::NotFound, "TLS_PFX_PATH not set"))?;
172    let password = env::var("TLS_PASSWORD")
173        .map_err(|_| std::io::Error::new(std::io::ErrorKind::NotFound, "TLS_PASSWORD not set"))?;
174
175    let mut file = std::fs::File::open(&pfx_path)?;
176    let mut identity = vec![];
177    file.read_to_end(&mut identity)?;
178
179    let identity = Identity::from_pkcs12(&identity, &password)?;
180    let native_acceptor = NativeTlsAcceptor::new(identity)?;
181    Ok(TlsAcceptor::from(native_acceptor))
182}
183
184#[tokio::main]
185async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
186    tracing_subscriber::fmt::init();
187    let config = Config::parse();
188
189    if !std::path::Path::new(&config.socket).exists() {
190        error!(
191            "Error: MPV socket not found at '{}'. Is MPV running?",
192            config.socket
193        );
194    }
195
196    info!("Server is starting...");
197    match create_tls_acceptor() {
198        Ok(acceptor) => {
199            let acceptor = Arc::new(acceptor);
200            let listener = tokio::net::TcpListener::bind(&config.bind).await?;
201            info!("Server is listening on {}", config.bind);
202
203            loop {
204                let (stream, _) = listener.accept().await?;
205                info!("New connection accepted.");
206
207                let acceptor = Arc::clone(&acceptor);
208                tokio::spawn(handle_connection(stream, acceptor));
209            }
210        }
211
212        Err(e) => {
213            error!("Failed to initialize TLS: {}", e);
214            return Err(e);
215        }
216    }
217}