Skip to content

Instantly share code, notes, and snippets.

@rtsuk
Created October 31, 2020 17:43
Show Gist options
  • Save rtsuk/39e7ded637326a5605c6c08ec0ff4d30 to your computer and use it in GitHub Desktop.
Save rtsuk/39e7ded637326a5605c6c08ec0ff4d30 to your computer and use it in GitHub Desktop.
asterisk manager interface
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