use crate::fatal; use elefren::{ data::Data, http_send::HttpSender, registration::Registered, scopes::Scopes, Mastodon, }; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::{ collections::BTreeMap, fs::{File, OpenOptions}, io::{self, Read, Write}, path::{Path, PathBuf}, }; pub type Location = String; pub type Name = String; type InnerMap = BTreeMap>>; #[derive(Debug, Deserialize, Serialize)] pub struct UnionMap { #[serde(flatten)] inner: InnerMap, #[serde(skip)] path: PathBuf, } #[derive(Debug, Deserialize, Serialize)] pub struct Config { /// The instance URL (example: `botsin.space`) pub instance: String, /// The username (example: `bot@beep.boop`) pub username: String, /// The required login secret #[serde(flatten)] pub secret: Option, /// Union suggestion map pub unions: Unions, } #[derive(Debug, Deserialize, Serialize)] #[serde(untagged)] pub enum Unions { /// The configuration contains a path Path(PathBuf), /// Which is then turned into the union map Map(UnionMap), } impl Unions { fn close(&mut self) { let path = match self { Unions::Path(ref path) => path, Unions::Map(ref map) => &map.path, }; *self = Unions::Path(path.clone()); } } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Secret { pub id: String, pub secret: String, pub token: String, } impl<'d> From<&'d Data> for Secret { fn from(d: &'d Data) -> Self { Self { id: d.client_id.to_string(), secret: d.client_secret.to_string(), token: d.token.to_string(), } } } fn load_path(p: &Path) -> String { let mut f = File::open(p) .unwrap_or_else(|e| fatal!("Failed to open path `{}`: {}", p.to_str().unwrap(), e)); let mut buf = String::new(); f.read_to_string(&mut buf) .unwrap_or_else(|e| fatal!("Failed to read file: {}", e)); info!("Loaded path {}", p.to_str().unwrap()); buf } fn parse_toml(s: String) -> T { toml::from_str(s.as_str()).unwrap_or_else(|e| fatal!("Failed to parse toml: {}", e)) } impl Config { pub fn load(path: &Path) -> Self { let cfg_buf = load_path(path); let mut cfg: Config = parse_toml(cfg_buf); dbg!(&cfg); // Load the union map from the provided path cfg.load_unions(); // Return cfg } /// Load the unions from the path pub fn load_unions(&mut self) { let union_buf = load_path(self.unions_path()); let inner: InnerMap = parse_toml(union_buf); // Swap the path for the map self.unions = Unions::Map(UnionMap { inner, path: self.unions_path().to_path_buf(), }); } pub fn registration(&self) -> Mastodon { let sec = self.assume_secret().clone(); Data { base: (&self.instance).clone().into(), client_id: sec.id.into(), client_secret: sec.secret.into(), token: sec.token.into(), redirect: "".into(), } .into() } pub fn save(&mut self, path: &Path) -> io::Result<()> { let mut f = OpenOptions::new() .write(true) .create(false) .truncate(true) .open(path)?; let string = self.as_toml(); f.write_all(string.as_bytes())?; Ok(()) } pub fn as_toml(&mut self) -> String { self.unions.close(); let string = toml::to_string(self).unwrap(); self.load_unions(); string } fn unions_path(&self) -> &Path { match self.unions { Unions::Path(ref pathbuf) => pathbuf.as_path(), _ => unreachable!(), } } fn assume_secret(&self) -> &Secret { self.secret.as_ref().unwrap() } pub fn unions(&self, loc: Location) -> &BTreeMap> { match self.unions { Unions::Map(ref map) => map.inner.get(&loc).unwrap(), _ => unreachable!(), } } /// Update the secret in the configuration pub fn update_secret>(&mut self, secret: S) { self.secret = Some(secret.into()); } pub fn secret(&self) -> Option { self.secret.clone() } }