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 #[arg(short, long, default_value = "127.0.0.1:8080")]
19 bind: String,
20
21 #[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 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}