commit
7e328e5ff2
@ -0,0 +1 @@
|
||||
/target
|
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…
Reference in new issue