First port from Go using GPT after fixing some errors

master
Aaron Johnon 5 months ago
commit 7e328e5ff2

1
.gitignore vendored

@ -0,0 +1 @@
/target

1796
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,18 @@
[package]
name = "tbotsend-rs"
version = "0.1.0"
edition = "2024"
authors = ["Aaron Johnson <amjohnson@skyfall.tech>"]
description = "Telegram bot message sender"
license = "MIT"
[dependencies]
clap = { version = "4", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
serde_yaml = "0.9"
serde_json = "1.0"
reqwest = { version = "0.11", features = ["blocking", "json"] }
regex = "1"
chrono = "0.4"
dirs = "5"

@ -0,0 +1,144 @@
use clap::{Arg, ArgAction, Command};
use regex::Regex;
use reqwest::blocking::Client;
use serde::Deserialize;
use std::fs;
use std::io::Read;
use std::path::PathBuf;
use std::process;
use std::time::Duration;
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
#[derive(Debug, Deserialize)]
struct Config {
chat_id: String,
token: String,
timeout: Option<u8>,
}
#[derive(Deserialize)]
struct TelegramResponse {
ok: bool,
description: Option<String>,
}
fn main() -> Result<()> {
let matches = Command::new("tbotsend")
.version("development")
.arg(Arg::new("config").short('f').long("config").value_name("FILE"))
.arg(Arg::new("chatid").short('c').long("chatid").value_name("chat_id"))
.arg(Arg::new("token").short('t').long("token").value_name("token"))
.arg(Arg::new("parse").short('p').long("parse").default_value("MarkdownV2").value_name("MODE"))
.arg(Arg::new("timeout").long("timeout").value_parser(clap::value_parser!(u8)).default_value("10").value_name("SECONDS"))
.arg(Arg::new("silent").short('s').long("silent").action(ArgAction::SetTrue))
.arg(Arg::new("version").short('v').long("version").action(ArgAction::SetTrue))
.arg(Arg::new("help").short('h').long("help").action(ArgAction::Help))
.arg(Arg::new("message").num_args(1..).required(false))
.get_matches();
let message: String = matches
.get_many::<String>("message")
.map(|vals| vals.map(String::as_str).collect::<Vec<_>>().join(" "))
.unwrap_or_default();
println!(" -- {}", chrono::Local::now().format("%Y-%m-%d %H:%M:%S"));
let config_used;
let config = if let (Some(chatid), Some(token)) = (matches.get_one::<String>("chatid"), matches.get_one::<String>("token")) {
if matches.get_one::<String>("config").is_some() {
print_error("Cannot use both configuration file and configuration flags");
process::exit(2);
}
config_used = "MANUAL".to_string();
Config {
chat_id: chatid.clone(),
token: token.clone(),
timeout: matches.get_one::<u8>("timeout").copied(),
}
} else {
let config_path = if let Some(cfg) = matches.get_one::<String>("config") {
PathBuf::from(cfg)
} else {
dirs::home_dir()
.map(|p| p.join(".config/tbotsend.yaml"))
.unwrap()
};
let mut config_data = fs::read_to_string(&config_path).or_else(|_| {
let fallback = dirs::home_dir().unwrap().join(".tbotsend.yaml");
fs::read_to_string(&fallback)
}).map_err(|e| {
print_error(&format!("Failed to read configuration file: {}", e));
e
})?;
config_used = config_path.to_string_lossy().to_string();
serde_yaml::from_str::<Config>(&mut config_data)?
};
if config.chat_id.is_empty() || config.token.is_empty() {
print_error("Configuration is missing ChatID or Token");
process::exit(2);
}
if message.trim().is_empty() {
print_error("No message provided");
process::exit(2);
}
let parse_mode = matches.get_one::<String>("parse").unwrap();
let silent = matches.get_flag("silent");
let timeout_secs = config.timeout.unwrap_or(10);
let url = format!("https://api.telegram.org/bot{}/sendMessage", config.token);
let client = Client::builder()
.timeout(Duration::from_secs(timeout_secs as u64))
.build()?;
let mut resp = client
.post(&url)
.form(&[
("chat_id", config.chat_id.as_str()),
("disable_web_page_preview", "1"),
("disable_notification", if silent { "true" } else { "false" }),
("text", &message),
("parse_mode", parse_mode),
])
.send()?;
let mut body = String::new();
resp.read_to_string(&mut body)?;
let telegram_response: TelegramResponse = serde_json::from_str(&body)?;
if !telegram_response.ok {
print_failure(telegram_response.description.as_deref().unwrap_or("Unknown error"));
process::exit(13);
}
println!("\x1b[1mConfig Used:\x1b[0m {}", config_used);
println!("\x1b[1mChat ID:\x1b[0m {}", config.chat_id);
println!("\x1b[1mSilent:\x1b[0m {}", silent);
println!("\x1b[1mMessage:\x1b[0m {}", message);
print_success();
Ok(())
}
fn print_error(msg: &str) {
eprintln!("[\x1b[1;31mERROR\x1b[0m] {}", msg);
}
fn print_failure(msg: &str) {
eprintln!("\x1b[1mResult:\x1b[0m [\x1b[1;31mFAILURE\x1b[0m]: {}\n", msg);
}
fn print_success() {
println!("\x1b[1mResult:\x1b[0m [\x1b[1;32mSUCCESS\x1b[0m]\n");
}
#[allow(dead_code)]
fn strip_ansi(input: &str) -> String {
let ansi_regex = Regex::new(r"\x1B\[[0-9;]*[mK]").unwrap();
ansi_regex.replace_all(input, "").to_string()
}
Loading…
Cancel
Save