use std::path::PathBuf;
use anyhow::{Context, Result, bail};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ApiKeys {
pub id: i32,
pub hash: String,
}
pub struct Config {
pub api_keys: Option<ApiKeys>,
pub data_dir: PathBuf,
}
impl Config {
pub fn load() -> Result<Self> {
let api_keys = match (var("TG_API_ID"), var("TG_API_HASH")) {
(Some(id), Some(hash)) => Some(ApiKeys {
id: parse_api_id(&id).context("TG_API_ID must be a number")?,
hash,
}),
(None, None) => None,
_ => bail!("set both TG_API_ID and TG_API_HASH, or neither"),
};
let data_dir = data_dir()?;
std::fs::create_dir_all(&data_dir)
.with_context(|| format!("cannot create {}", data_dir.display()))?;
Ok(Self { api_keys, data_dir })
}
}
pub fn data_dir() -> Result<PathBuf> {
if let Some(dir) = var("TG_DATA_DIR") {
return Ok(PathBuf::from(dir));
}
let base = dirs::data_local_dir().context("no home directory found; set TG_DATA_DIR")?;
Ok(base.join("tuigram"))
}
pub fn parse_api_id(text: &str) -> Option<i32> {
text.trim().parse().ok().filter(|&id| id > 0)
}
pub fn is_api_hash(text: &str) -> bool {
text.len() == 32 && text.bytes().all(|b| b.is_ascii_hexdigit())
}
fn var(name: &str) -> Option<String> {
let value = std::env::var(name).ok()?;
let value = value.trim();
(!value.is_empty()).then(|| value.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn api_credentials_are_checked_before_telegram_sees_them() {
assert_eq!(parse_api_id(" 1234567 "), Some(1234567));
assert_eq!(parse_api_id("0"), None);
assert_eq!(parse_api_id("abc"), None);
assert!(is_api_hash("0123456789abcdef0123456789ABCDEF"));
assert!(!is_api_hash("0123456789abcdef"), "too short");
assert!(!is_api_hash("0123456789abcdef0123456789abcdeg"), "not hex");
}
}