Buttplug sex toy control library
at dev 9.4 kB view raw
1use std::{ 2 collections::BTreeMap, convert::Infallible, io, net::SocketAddr, sync::Arc, 3}; 4 5use axum::{ 6 Json, Router, 7 extract::{Path, State, rejection::JsonRejection}, 8 http::StatusCode, 9 response::{ 10 IntoResponse, Response, Sse, 11 sse::{Event, KeepAlive}, 12 }, 13 routing::{get, put}, 14}; 15use buttplug_client::{ 16 ButtplugClient, ButtplugClientDevice, ButtplugClientError, 17 device::{ClientDeviceFeature, ClientDeviceOutputCommand}, 18}; 19use buttplug_client_in_process::ButtplugInProcessClientConnectorBuilder; 20use buttplug_core::message::{DeviceFeature, OutputType}; 21use buttplug_server::ButtplugServer; 22use futures::{Stream, StreamExt}; 23use serde::Serialize; 24use thiserror::Error; 25use tokio::net::TcpListener; 26 27#[derive(Error, Debug)] 28enum IntifaceRestError { 29 #[error("JsonRejection: {0}")] 30 JsonRejection(JsonRejection), 31 #[error("Library Error: {0}")] 32 ButtplugClientError(ButtplugClientError), 33 #[error("Device index {0} does not refer to a currently connected device.")] 34 InvalidDevice(u32), 35 #[error("Device index {0} feature index {1} does not refer to a valid device feature.")] 36 InvalidFeature(u32, u32), 37 /* 38 #[error("{0} is not a valid output type. Valid output types are: {1:?}")] 39 InvalidOutputType(String, Vec<OutputType>), 40 #[error("{0} is not a valid input type. Valid input types are: {1:?}")] 41 InvalidInputType(String, Vec<String>), 42 #[error("{0} is not a valid input commands. Valid input commands are: {1:?})")] 43 InvalidInputCommand(u32, Vec<String>), 44 #[error("Value {0} is not valid for the current command.)")] 45 InvalidValue(u32), 46 */ 47} 48 49// Tell axum how `AppError` should be converted into a response. 50// 51// This is also a convenient place to log errors. 52impl IntoResponse for IntifaceRestError { 53 fn into_response(self) -> Response { 54 let (status, message) = match self { 55 IntifaceRestError::JsonRejection(rejection) => { 56 // This error is caused by bad user input so don't log it 57 (rejection.status(), rejection.body_text()) 58 } 59 _ => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()), 60 }; 61 (status, message).into_response() 62 } 63} 64 65impl From<JsonRejection> for IntifaceRestError { 66 fn from(rejection: JsonRejection) -> Self { 67 Self::JsonRejection(rejection) 68 } 69} 70 71impl From<ButtplugClientError> for IntifaceRestError { 72 fn from(error: ButtplugClientError) -> Self { 73 Self::ButtplugClientError(error) 74 } 75} 76 77#[derive(Serialize)] 78struct IntifaceRestDevice { 79 index: u32, 80 name: String, 81 display_name: Option<String>, 82 features: BTreeMap<u32, DeviceFeature>, 83} 84 85impl From<&ButtplugClientDevice> for IntifaceRestDevice { 86 fn from(device: &ButtplugClientDevice) -> Self { 87 Self { 88 index: device.index(), 89 name: device.name().clone(), 90 display_name: device.display_name().clone(), 91 features: device 92 .device_features() 93 .iter() 94 .map(|(i, d)| (*i, d.feature().clone())) 95 .collect(), 96 } 97 } 98} 99 100pub struct IntifaceRestServer {} 101 102fn get_device( 103 client: &ButtplugClient, 104 index: u32, 105) -> Result<ButtplugClientDevice, IntifaceRestError> { 106 client 107 .devices() 108 .get(&index) 109 .ok_or(IntifaceRestError::InvalidDevice(index)) 110 .cloned() 111} 112 113fn get_feature( 114 client: &ButtplugClient, 115 index: u32, 116 feature_index: u32, 117) -> Result<ClientDeviceFeature, IntifaceRestError> { 118 get_device(client, index)? 119 .device_features() 120 .get(&feature_index) 121 .ok_or(IntifaceRestError::InvalidFeature(index, feature_index)) 122 .cloned() 123} 124 125async fn start_scanning( 126 State(client): State<Arc<ButtplugClient>>, 127) -> Result<(), IntifaceRestError> { 128 client 129 .start_scanning() 130 .await 131 .map_err(IntifaceRestError::ButtplugClientError) 132} 133 134async fn stop_scanning(State(client): State<Arc<ButtplugClient>>) -> Result<(), IntifaceRestError> { 135 client 136 .stop_scanning() 137 .await 138 .map_err(IntifaceRestError::ButtplugClientError) 139} 140 141async fn stop_all_devices( 142 State(client): State<Arc<ButtplugClient>>, 143) -> Result<(), IntifaceRestError> { 144 client 145 .stop_all_devices() 146 .await 147 .map_err(IntifaceRestError::ButtplugClientError) 148} 149 150async fn stop_device( 151 State(client): State<Arc<ButtplugClient>>, 152 Path(index): Path<u32>, 153) -> Result<(), IntifaceRestError> { 154 get_device(&client, index)? 155 .stop() 156 .await 157 .map_err(IntifaceRestError::ButtplugClientError) 158} 159 160async fn set_device_output( 161 State(client): State<Arc<ButtplugClient>>, 162 Path((index, output_type, level)): Path<(u32, OutputType, f64)>, 163) -> Result<(), IntifaceRestError> { 164 let cmd = ClientDeviceOutputCommand::from_command_value_float(output_type, level) 165 .map_err(IntifaceRestError::ButtplugClientError)?; 166 167 get_device(&client, index)? 168 .send_command(&cmd) 169 .await 170 .map_err(IntifaceRestError::ButtplugClientError) 171} 172 173async fn set_feature_output( 174 State(client): State<Arc<ButtplugClient>>, 175 Path((index, feature_index, output_type, level)): Path<(u32, u32, OutputType, f64)>, 176) -> Result<(), IntifaceRestError> { 177 let cmd = ClientDeviceOutputCommand::from_command_value_float(output_type, level) 178 .map_err(IntifaceRestError::ButtplugClientError)?; 179 180 get_feature(&client, index, feature_index)? 181 .send_command(&cmd) 182 .await 183 .map_err(IntifaceRestError::ButtplugClientError) 184} 185 186async fn get_devices( 187 State(client): State<Arc<ButtplugClient>>, 188) -> Json<BTreeMap<u32, IntifaceRestDevice>> { 189 client 190 .devices() 191 .iter() 192 .map(|(i, x)| (*i, x.into())) 193 .collect::<BTreeMap<u32, IntifaceRestDevice>>() 194 .into() 195} 196 197async fn get_device_info( 198 State(client): State<Arc<ButtplugClient>>, 199 Path(index): Path<u32>, 200) -> Result<Json<IntifaceRestDevice>, IntifaceRestError> { 201 Ok( 202 IntifaceRestDevice::from( 203 client 204 .devices() 205 .get(&index) 206 .ok_or(IntifaceRestError::InvalidDevice(index))?, 207 ) 208 .into(), 209 ) 210} 211 212async fn get_features( 213 State(client): State<Arc<ButtplugClient>>, 214 Path(index): Path<u32>, 215) -> Result<Json<BTreeMap<u32, DeviceFeature>>, IntifaceRestError> { 216 Ok( 217 get_device(&client, index)? 218 .device_features() 219 .iter() 220 .map(|(i, f)| (*i, f.feature().clone())) 221 .collect::<BTreeMap<u32, DeviceFeature>>() 222 .into(), 223 ) 224} 225 226async fn get_feature_info( 227 State(client): State<Arc<ButtplugClient>>, 228 Path((index, feature_index)): Path<(u32, u32)>, 229) -> Result<Json<DeviceFeature>, IntifaceRestError> { 230 Ok( 231 get_device(&client, index)? 232 .device_features() 233 .get(&feature_index) 234 .ok_or(IntifaceRestError::InvalidFeature(index, feature_index))? 235 .feature() 236 .clone() 237 .into(), 238 ) 239} 240 241/* 242async fn feature_input_command( 243 State(client): State<Arc<ButtplugClient>>, 244 Path((index, feature_index, input_type, command)): Path<(u32, u32, String, String)>, 245) -> Result<(), IntifaceRestError> { 246 let cmd = convert_output_command(&command, level)?; 247 248 Ok( 249 get_feature(&client, index, feature_index)? 250 .send_command(&cmd) 251 .await 252 .map_err(|e| IntifaceRestError::ButtplugClientError(e))?, 253 ) 254 255 Ok(()) 256} 257*/ 258 259async fn server_sse( 260 State(client): State<Arc<ButtplugClient>>, 261) -> Sse<impl Stream<Item = Result<Event, Infallible>>> { 262 let stream = client 263 .event_stream() 264 .map(|e| Ok(Event::default().data(format!("{:?}", e)))); 265 266 Sse::new(stream).keep_alive(KeepAlive::default()) 267} 268 269impl IntifaceRestServer { 270 pub async fn run(port: u16, server: ButtplugServer) -> Result<(), io::Error> { 271 let connector = ButtplugInProcessClientConnectorBuilder::default() 272 .server(server) 273 .finish(); 274 let client = ButtplugClient::new("Intiface REST API"); 275 client.connect(connector).await.unwrap(); 276 info!("Setting up app!"); 277 // pass incoming GET requests on "/hello-world" to "hello_world" handler. 278 let app = Router::new().nest( 279 "/api/v1", 280 Router::new() 281 .route("/start-scanning", get(start_scanning)) 282 .route("/stop-scanning", get(stop_scanning)) 283 .route("/devices", get(get_devices)) 284 .route("/devices/stop", put(stop_all_devices)) 285 .route("/devices/{index}", get(get_device_info)) 286 .route("/devices/{index}/stop", put(stop_device)) 287 .route("/devices/{index}/features", get(get_features)) 288 .route("/devices/{index}/features/{index}/", put(get_feature_info)) 289 .route( 290 "/devices/{index}/outputs/{output_type}/{level}", 291 put(set_device_output), 292 ) 293 .route( 294 "/devices/{index}/features/{index}/outputs/{output_type}/{level}", 295 put(set_feature_output), 296 ) 297 /* 298 .route( 299 "/devices/{index}/inputs/{input_type}/{input_command}", 300 put(device_input_command), 301 ) 302 .route( 303 "/devices/{index}/features/{index}/inputs/{input_type}/{input_command}", 304 put(feature_input_command), 305 ) 306 .route("/devices/{index}/events", get(device_sse)) 307 */ 308 .route("/events", get(server_sse)) 309 //.route("/devices/{*index}/vibrate", post(set_feature_vibrate_speed)) 310 .with_state(Arc::new(client)), 311 ); 312 313 // write address like this to not make typos 314 let addr = SocketAddr::from(([127, 0, 0, 1], port)); 315 info!("Buttplug REST API Server now listening on {:?}", addr); 316 let listener = TcpListener::bind(addr).await?; 317 axum::serve(listener, app.into_make_service()).await?; 318 319 Ok(()) 320 } 321}