// [[file:../README.org::*Imports][Imports:1]] use std::fs; use std::fs::create_dir; use std::path::Path; use std::collections::HashMap; use reqwest::header::{USER_AGENT, AUTHORIZATION}; use serde::{Serialize, Deserialize}; use serde_json::Value; use dirs; // Imports:1 ends here // [[file:../README.org::*Constants][Constants:1]] const PK_URL: &str = "https://api.pluralkit.me/v2"; const SP_URL: &str = "https://api.apparyllis.com/v1"; // Constants:1 ends here // [[file:../README.org::*Structs][Structs:1]] #[derive(Debug)] #[derive(Serialize)] #[derive(Deserialize)] #[derive(Clone)] struct System { pk_userid: String, sp_userid: String, members: Vec, } #[derive(Debug)] #[derive(Serialize)] #[derive(Deserialize)] #[derive(Clone)] struct Member { pk_id: String, sp_id: String, name: String, alias: String } // Structs:1 ends here // [[file:../README.org::*Main][Main:1]] fn main() { if std::env::args().len() > 1 { let augs: Vec = std::env::args().collect(); let command = &augs[1]; let config_path: String; match dirs::config_dir() { Some(x) => { config_path = format!("{}/pluralshit", x.display()); match command.as_str() { "sync" => { let _ = sync(config_path); }, "set" => { if std::env::args().len() > 2 { match set_member(config_path, &augs[2..]) { Ok(_) => (), Err(e) => println!("{e}"), } } else { //set_empty(config_path); } }, "memberlist" => memberlist(config_path), &_ => println!("Invalid command"), } }, None => println!("Something went wrong") } } else { println!("No arguments given"); } } // Main:1 ends here // [[file:../README.org::*Sync][Sync:1]] fn sync(config_path: String) -> Result<(), String>{ // Get config let config = get_config(&config_path); if config == Value::Null { println!("Stopping."); return Ok(()); } let pk_key = &config["pk_key"].as_str().unwrap(); let sp_key = &config["sp_key"].as_str().unwrap(); // Get Pluralkit system id let pk_sys = pk_get_system(pk_key); let pk_sysid = pk_sys["id"].as_str().unwrap(); // Get Simplyplural user id let sp_user_id = sp_get_userid(sp_key); // Get Simplyplural member ids let sp_member_ids = sp_get_memberids(sp_key, &sp_user_id); // get members let pk_members = pk_get_members(pk_key, pk_sysid); let mut members: Vec = Vec::new(); for member in pk_members { let mut m = Member { pk_id: member["id"].as_str().unwrap().to_string(), sp_id: String::new(), name: member["name"].as_str().unwrap().to_string(), alias: String::new() }; if member["display_name"].as_str() != None { m.alias = member["display_name"].as_str().unwrap().to_string(); } else { m.alias = String::from(&m.name); } m.sp_id = get_sp_id(&m, &sp_member_ids); members.push(m); } let sys = System { pk_userid: pk_sysid.to_string(), sp_userid: sp_user_id, members: members.clone(), }; let json = serde_json::to_string(&sys); let _ = fs::write(format!("{}/system.json", config_path), &json.unwrap()); Ok(()) } // Sync:1 ends here // [[file:../README.org::*Set member][Set member:1]] /* TODO fn set_empty(config_path: String) { TODO let config = load_json(format!("{}/config.json", config_path)); TODO let system = get_system(&config_path); TODO } */ fn set_member(config_path: String, tf_members: &[String]) -> Result<(), &'static str> { let config = get_config(&config_path); if config == Value::Null { return Err("Config not found. Stopping"); } let system: System = get_system(&config_path); let mut to_front: Vec = Vec::new(); for member in tf_members { for mem in &system.members { if mem.name.to_lowercase() == member.to_lowercase() || mem.alias.to_lowercase() == member.to_lowercase() { println!("Member {member} found"); to_front.push(mem.clone()); break; } } } if to_front.len() != 0 { let fronters = get_fronters(&config["pk_key"].as_str().unwrap(), &config["sp_key"].as_str().unwrap(), &system); pk_set_fronters(&config["pk_key"].as_str().unwrap(), &system, to_front, &fronters); } Ok(()) } fn memberlist(config_path: String) { let sys = get_system(&config_path); for mem in sys.members { if mem.name != mem.alias { println!("{} / {}", mem.name, mem.alias); } else { println!("{}", mem.name); } } } // Set member:1 ends here // [[file:../README.org::*Get system][Get system:1]] fn pk_get_system(key: &str) -> Value { let url = format!("{}/systems/@me", PK_URL); let res = http_get_request(url,key); return serde_json::from_str(&res.unwrap()).unwrap(); } fn pk_get_members(key: &str, sysid: &str) -> Vec { let url = format!("{}/systems/{}/members", PK_URL, sysid); let res = http_get_request(url,key); let datas: Vec = serde_json::from_str(&res.unwrap()).unwrap(); return datas; } // Get system:1 ends here // [[file:../README.org::*Get fronters][Get fronters:1]] fn pk_get_fronters(key: &str, sys: &System) -> Vec { let url = format!("{}/systems/{}/fronters", PK_URL, sys.pk_userid); let res = http_get_request(url,key); let data: Value = serde_json::from_str(&res.unwrap()).unwrap(); let memberdata = &data["members"].as_array(); let mut members: Vec = Vec::new(); for member in memberdata { for m in member.into_iter() { for dbmem in &sys.members { if m["name"].as_str().unwrap() == dbmem.name { members.push(dbmem.clone()); } } } } return members; } fn pk_set_fronters(key: &str, sys: &System, to_front: Vec, _fronters: &HashMap>) { let url = format!("{}/systems/{}/switches", PK_URL, sys.pk_userid); // if not fronting let mut frontcodes = Vec::new(); for tf in to_front { frontcodes.push(tf.pk_id); } let mut body: HashMap<&str, Vec> = HashMap::new(); body.insert("members", frontcodes); let _ = http_post_request(url, key, &body); } // Get fronters:1 ends here // [[file:../README.org::*Get user ID][Get user ID:1]] fn sp_get_userid(key: &str) -> String { let url = format!("{}/me", SP_URL); let res = http_get_request(url,key); let json_res : Value = serde_json::from_str(&res.unwrap()).unwrap(); return json_res["id"].as_str().unwrap().to_string(); } // Get user ID:1 ends here // [[file:../README.org::*Get members ID][Get members ID:1]] fn sp_get_memberids(key: &str, system_id: &str) -> HashMap { let url = format!("{}/members/{}", SP_URL, system_id); let res = http_get_request(url,key); let datas: Vec = serde_json::from_str(&res.unwrap()).unwrap(); let mut sp_memberdata: HashMap = HashMap::new(); for data in datas { sp_memberdata.insert(String::from(data["content"]["name"].as_str().unwrap()), String::from(data["id"].as_str().unwrap())); } return sp_memberdata; } // Get members ID:1 ends here // [[file:../README.org::*Get ID from member][Get ID from member:1]] fn get_sp_id(mem: &Member, ids: &HashMap) -> String { let mut member_id = String::new(); for (mn, mid) in ids { if &mem.name == mn || &mem.alias == mn { member_id = String::from(mid); } } return member_id; } // Get ID from member:1 ends here // [[file:../README.org::*Get fronters][Get fronters:1]] fn sp_get_fronters(key: &str, sys: &System) -> Vec { let url = format!("{}/fronters", SP_URL); let res = http_get_request(url,key); let datas: Vec = serde_json::from_str(&res.unwrap()).unwrap(); let mut members: Vec = Vec::new(); for data in datas { let sp_id = &data["content"]["member"].as_str().unwrap(); for member in &sys.members { if &member.sp_id == sp_id { members.push(member.clone()); } } } return members; } // Get fronters:1 ends here // [[file:../README.org::*Load JSON][Load JSON:1]] fn load_json(path: String) -> Value { if Path::new(&path).exists() { let config_data = fs::read_to_string(&path).expect("File not found"); return serde_json::from_str(&config_data).unwrap(); } else { println!("Config file in {path} not found"); return Value::Null; } } // Load JSON:1 ends here // [[file:../README.org::*Get config json][Get config json:1]] fn get_config(config_path: &str) -> Value { let path = format!("{}/config.json", config_path); if Path::new(config_path).exists() { let result = load_json(String::from(&path)); if result == Value::Null { println!("Config file missing, creating template in {path}"); let _ = fs::write(path, r#" { "pk_key": "// Pluralkit token", "sp_key": "// Simplplural token" } "#); } return result; } else { println!("Directory {config_path} does not exist. Creating with template config"); let _ = create_dir(config_path); let _ = fs::write(path, r#" { "pk_key": "// Pluralkit token", "sp_key": "// Simplplural token" } "#); return Value::Null; } } // Get config json:1 ends here // [[file:../README.org::*Get system json][Get system json:1]] fn get_system(config_path: &str) -> System { let path = format!("{}/system.json", config_path); let mut result = load_json(String::from(&path)); if result == Value::Null { println!("Syncing system config"); let _ = sync(String::from(config_path)); result = load_json(String::from(&path)); } let vec = serde_json::to_vec(&result).unwrap(); let sys = serde_json::from_slice::(&vec).unwrap(); return sys; } fn get_fronters(pk_key: &str, sp_key: &str, sys: &System) -> HashMap> { let mut fronters: HashMap> = HashMap::new(); fronters.insert(String::from("pk"), pk_get_fronters(pk_key, sys)); fronters.insert(String::from("sp"), sp_get_fronters(sp_key, sys)); return fronters; } // Get system json:1 ends here // [[file:../README.org::*Http Request handler][Http Request handler:1]] #[tokio::main] async fn http_get_request(url: String, key: &str) -> Result> { let client = reqwest::Client::new(); let res = client .get(url) .header(USER_AGENT, "Pluralsync") .header(AUTHORIZATION, key) .send() .await? .text() .await?; Ok(res) } #[tokio::main] async fn http_post_request(url: String, key: &str, body: &HashMap<&str, Vec>) -> Result<(), Box> { let client = reqwest::Client::new(); let _ = client .post(url) .json(body) .header(USER_AGENT, "Pluralsync") .header(AUTHORIZATION, key) .send() .await?; Ok(()) } /* #[tokio::main] async fn http_patch_request(url: String, key: &str) -> Result> { let client = reqwest::Client::new(); let res = client .patch(url) .body("wiwiwiw") .header(USER_AGENT, "Pluralsync") .header(AUTHORIZATION, key) .send() .await? .text() .await?; Ok(res) } */ // Http Request handler:1 ends here