use anyhow::{bail, ensure, format_err, Error}; use itertools::Itertools; use std::{collections::HashMap, time::Duration}; use telnet::{Telnet, TelnetEvent}; use uuid::Uuid; #[derive(Debug)] pub struct Response { pub raw_response: String, action_id: String, } impl Response { fn new(raw_response: String, fields: HashMap) -> Result { let action_id = fields .get("ActionID") .ok_or_else(|| format_err!("Response missing ActionID field"))?; Ok(Self { raw_response, action_id: action_id.to_string(), }) } } pub struct Ami { connection: Telnet, } const BUFFER_SIZE: usize = 32 * 1024; impl Ami { fn read_data(connection: &mut Telnet) -> Result { let mut result = String::new(); loop { let event = connection.read_timeout(Duration::from_millis(100))?; match event { TelnetEvent::Data(data) => { result.push_str(&String::from_utf8(data.to_vec())?); } TelnetEvent::Error(err) => { bail!("telnet error: {}", err); } TelnetEvent::TimedOut => { return Ok(result); } _ => println!("{:?}", event), } } } pub fn new(host: &str, port: u16) -> Result { let mut connection = Telnet::connect((host, port), BUFFER_SIZE)?; let data = Self::read_data(&mut connection)?.trim().to_string(); ensure!( data == "Asterisk Call Manager/1.3", "Unexpected response from AMI" ); Ok(Self { connection }) } fn execute(&mut self, command: &str, options: &[(&str, &str)]) -> Result { let my_uuid = Uuid::new_v4(); let commands: Vec = [ format!("Action: {}", command), format!("ActionID: {}", my_uuid), ] .iter() .map(|s| s.clone()) .chain(options.iter().map(|(k, v)| format!("{}: {}", k, v))) .chain(std::iter::once(String::from(""))) .collect(); let mut command_string = commands.join("\r\n"); command_string.push_str("\r\n"); self.connection.write(command_string.as_bytes())?; let result_text = Self::read_data(&mut self.connection)?; let result: HashMap = result_text .split("\r\n") .map(|s| s.split(": ").next_tuple().unwrap_or(("", ""))) .map(|(k, v)| { if k.len() > 0 { Some((k.to_string(), v.to_string())) } else { None } }) .filter_map(|t| t) .collect(); Response::new(result_text, result) } pub fn login(&mut self, username: &str, password: &str) -> Result { let options = [ ("Username", username), ("Secret", password), ("Event", "Off"), ]; self.execute("Login", &options) } pub fn command(&mut self, command: &str) -> Result { self.execute("Command", &[("Command", command)]) } } // execute 'Login', {'Username' => username, 'Secret' => password, 'Event' => 'On'}