use super::fabric::execute_fabric_command;
use super::whisper;
use crate::db::Database;
use grammers_client::types::{Chat, Downloadable, Media, Message};
use grammers_client::{Client, InputMessage};
use log::{debug, error, info};
use regex::Regex;
use std::collections::HashMap;
use std::error::Error;
use std::path::Path;
pub async fn handle_message(
tg: &mut Client,
message: &Message,
chat_map: HashMap<i64, String>,
db: &mut Database,
) -> Result<(), Box<dyn std::error::Error>> {
let (cmd, params, text) = parse_command(message.text().trim_start_matches(&['/', '!']));
info!(
"Chat:{} parse_command: {} {:?} {}",
message.chat().id(),
cmd,
params,
text
);
let output: String = match cmd.as_str() {
"start" | "help" => help_text(),
"patterns" | "l" => super::fabric::patterns().await?,
"models" | "L" => super::fabric::models().await?,
"changeDefaultModel" | "d" => super::fabric::change_default_model(¶ms[0]).await?,
"chatIds" | "ids" => pretty_print_chat_map(&chat_map, 0),
"chatAdd" | "a" => enable_chat(db, ¶ms[0]),
"chatRemove" | "r" => disable_chat(db, ¶ms[0]),
"fabric" | "f" => run_fabric(db, ¶ms, &text).await,
"whisperModel" | "wm" => {
if params.len() > 0 {
db.set_whisper_model(¶ms[0])
} else {
db.get_whisper_model().to_owned()
}
}
"whisper" | "w" => {
run_whisper(
tg,
&message,
match ¶ms.is_empty() {
true => db.get_whisper_model(),
false => ¶ms[0],
},
)
.await
}
_ => {
if let (true, Some(id)) = is_chat_id(message.text(), &db.get_chat_ids()) {
if db.chat_id_exists(&id) {
db.remove_chat_id(id);
"Chat id removed".to_owned()
} else {
db.add_chat_id(id);
"Chat id added".to_owned()
}
} else {
"".to_owned()
}
}
};
if !output.is_empty() {
send_response(tg, &message.chat(), &output, message.id()).await
} else {
Ok(())
}
}
async fn run_whisper(tg: &Client, message: &&Message, whisper_model: &str) -> String {
let parent_media = parse_parent_video(message).await;
let media_path = match parent_media {
Some((media, message_id)) => {
Some(crate::handlers::download_media(tg, media, message_id).await)
}
None => None,
};
let output = match media_path {
Some(path) => whisper::whisper_rs(&path, whisper_model).await,
None => "Message has no audio".to_owned(),
};
output
}
async fn parse_parent_photo(message: &&Message) -> Option<(grammers_client::types::Photo, i32)> {
match message.get_reply().await {
Ok(parent) => match parent {
Some(parent) => match parent.media() {
Some(media) => match media {
Media::Photo(photo) => Some((photo, parent.id())),
_ => None, },
None => None,
},
None => None,
},
Err(_) => None,
}
}
async fn parse_parent_video(message: &&Message) -> Option<(grammers_client::types::Media, i32)> {
match message.get_reply().await {
Ok(parent) => match parent {
Some(parent) => match parent.media() {
Some(media) => match media {
Media::Document(_) => Some((media, parent.id())),
_ => None, },
None => None,
},
None => None,
},
Err(_) => None,
}
}
fn enable_chat(db: &mut Database, params: &str) -> String {
match params.parse::<i64>() {
Ok(id) => {
db.add_chat_id(id);
format!("Enabled AI on {}", id)
}
Err(e) => format!("Chat Id Error: {} ", e),
}
}
fn disable_chat(db: &mut Database, params: &str) -> String {
match params.parse::<i64>() {
Ok(id) => {
db.remove_chat_id(id);
format!("Disabled AI on {}", id)
}
Err(e) => format!("Chat Id Error: {} ", e),
}
}
async fn run_fabric(_db: &mut Database, params: &Vec<String>, text: &str) -> String {
let mut args = params.iter().map(|s| s.as_str()).collect::<Vec<&str>>();
args.push(&text);
match execute_fabric_command(&args).await {
Ok(output) => output,
Err(e) => format!("Fabric failed {}", e),
}
}
fn is_chat_id(text: &str, chat_ids: &Vec<i64>) -> (bool, Option<i64>) {
match text.parse::<i64>() {
Ok(id) => (chat_ids.contains(&id), Some(id)),
Err(_) => (false, None),
}
}
fn help_text() -> String {
return "Getting started:
`/ids` - Show your chat names and their ID
`/chatAdd <id>` - Enable AI features on that chat
`/models` - List available models
`/patterns - List available fabric patterns"
.to_owned();
}
fn pretty_print_chat_map(chat_map: &HashMap<i64, String>, indent: usize) -> String {
let mut output = String::new();
for (k, v) in chat_map.iter() {
output.push_str(&" ".repeat(indent));
output.push_str(&format!("{}: {}\n", k, v));
}
output
}
async fn send_response(
tg: &Client,
chat: &Chat,
message: &str,
message_id: i32,
) -> Result<(), Box<dyn Error>> {
let chunk_size = 4096;
let mut start = 0;
let mut end;
while start < message.len() {
end = start + chunk_size;
if end > message.len() {
end = message.len();
} else {
while !message.is_char_boundary(end) {
end -= 1;
}
if let Some(last_space) = message[start..end].rfind(|c: char| c == ' ' || c == '\n') {
end = start + last_space;
}
}
if start == 0 {
tg.edit_message(chat, message_id, InputMessage::text(&message[start..end]))
.await?;
} else {
tg.send_message(
chat,
InputMessage::text(&message[start..end]).reply_to(Some(message_id)),
)
.await?;
}
start = end;
while start < message.len() && message[start..].starts_with(|c: char| c == ' ' || c == '\n')
{
start += 1;
}
}
Ok(())
}
pub fn parse_command(text: &str) -> (String, Vec<String>, String) {
let re = Regex::new(r#"(?P<cmd>\w+)(?: (?P<params>(?:-\w+ "(?:[^"]+)"|-\w+ \w+|\w+)(?:\s+-\w+ "(?:[^"]+)"|\s+-\w+ \w+)*))?(?: (?P<text>.+))?"#)
.unwrap();
if let Some(captures) = re.captures(text.trim_start_matches('/')) {
let cmd = captures.name("cmd").unwrap().as_str().to_string();
let params_str = captures.name("params").map_or("", |p| p.as_str());
let mut params: Vec<String> = Vec::new();
let mut in_quotes = false;
let mut current_param = String::new();
for token in params_str.split_whitespace() {
if token.starts_with('"') {
in_quotes = true;
current_param.push_str(&token[1..]); } else if token.ends_with('"') {
in_quotes = false;
current_param.push(' ');
current_param.push_str(&token[..token.len() - 1]); params.push(current_param.clone());
current_param.clear();
} else if in_quotes {
current_param.push(' ');
current_param.push_str(token);
} else {
params.push(token.to_string());
}
}
let mut text = captures.name("text").map_or("", |t| t.as_str()).to_string();
if text.starts_with('"') && text.ends_with('"') {
text = text[1..text.len() - 1].to_string();
}
(cmd, params, text)
} else {
("".to_owned(), vec![], "".to_owned())
}
}
#[test]
fn test_parse_command() {
assert_eq!(parse_command(""), ("".to_owned(), vec![], "".to_owned()));
assert_eq!(
parse_command("ping"),
("ping".to_owned(), vec![], "".to_owned())
);
assert_eq!(
parse_command("fabric -p summarize"),
(
"fabric".to_owned(),
vec!["-p".to_string(), "summarize".to_string()],
"".to_owned()
)
);
assert_eq!(
parse_command("chatAdd -d 1234567890 -u user_name"),
(
"chatAdd".to_owned(),
vec![
"-d".to_string(),
"1234567890".to_string(),
"-u".to_string(),
"user_name".to_string()
],
"".to_owned()
)
);
assert_eq!(
parse_command("fabric -p summarize This is some text for the fabric command"),
(
"fabric".to_owned(),
vec!["-p".to_string(), "summarize".to_string()],
"This is some text for the fabric command".to_owned()
)
);
assert_eq!(
parse_command("chatRemove -d \"value with spaces\" And this is some extra text"),
(
"chatRemove".to_owned(),
vec!["-d".to_string(), "value with spaces".to_string()],
"And this is some extra text".to_owned()
)
);
assert_eq!(
parse_command("ping \"Some quoted text\""),
("ping".to_owned(), vec![], "Some quoted text".to_owned())
);
assert_eq!(
parse_command("fabric -p summarize -d 1234567890 -u user_name This is some additional text for the fabric command"),
("fabric".to_owned(),
vec![
"-p".to_string(), "summarize".to_string(),
"-d".to_string(), "1234567890".to_string(),
"-u".to_string(), "user_name".to_string()
],
"This is some additional text for the fabric command".to_owned())
);
println!("All test cases passed");
}