Created
October 31, 2020 17:43
-
-
Save rtsuk/39e7ded637326a5605c6c08ec0ff4d30 to your computer and use it in GitHub Desktop.
asterisk manager interface
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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<String, String>) -> Result<Response, Error> { | |
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<String, Error> { | |
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<Ami, Error> { | |
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<Response, Error> { | |
let my_uuid = Uuid::new_v4(); | |
let commands: Vec<String> = [ | |
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<String, String> = 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<Response, Error> { | |
let options = [ | |
("Username", username), | |
("Secret", password), | |
("Event", "Off"), | |
]; | |
self.execute("Login", &options) | |
} | |
pub fn command(&mut self, command: &str) -> Result<Response, Error> { | |
self.execute("Command", &[("Command", command)]) | |
} | |
} | |
// execute 'Login', {'Username' => username, 'Secret' => password, 'Event' => 'On'} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment