#+title: Pluralsync (Codename pluralshit) #+author: Jvnko #+seq_todo: Backlog LowPriority HighPriority InProgress | Finished * Table of contents :toc_4: - [[#about][About]] - [[#configuration][Configuration]] - [[#code][Code]] - [[#cargotoml][Cargo.toml]] - [[#mainrs][Main.rs]] - [[#imports][Imports]] - [[#constants][Constants]] - [[#structs][Structs]] - [[#main][Main]] - [[#functions][Functions]] - [[#main-commands][Main commands]] - [[#pluralkit][Pluralkit]] - [[#simplyplural][Simplyplural]] - [[#utilities][Utilities]] - [[#http-request-handler][Http Request handler]] - [[#organization][Organization]] - [[#tasks][Tasks]] - [[#milestone-10-1119][Milestone 1.0]] - [[#main-functions-1911][Main functions]] - [[#commands-00100][Commands]] - [[#utils-020][Utils]] - [[#kanban][Kanban]] * About * Configuration #+begin_src json :tangle config.example.json { "pk_key": "// Pluralkit token", "sp_key": "// Simplplural token" } #+end_src * Code ** Cargo.toml #+begin_src toml :tangle Cargo.toml [package] name = "pluralshit" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] dirs = "5.0.1" serde = { version = "1.0.192", features = ["derive"] } serde_json = "1.0" reqwest = "0.11.22" rofi = "0.3.0" tokio = { version = "1", features = ["full"] } #+end_src ** Main.rs *** Imports #+begin_src rust :tangle src/main.rs :comments link use std::path::Path; use std::{fs, collections::HashMap}; use reqwest::header::{USER_AGENT, AUTHORIZATION}; use serde::{Serialize, Deserialize}; use serde_json::Value; use dirs; #+end_src *** Constants #+begin_src rust :tangle src/main.rs :comments link const PK_URL: &str = "https://api.pluralkit.me/v2"; const SP_URL: &str = "https://api.apparyllis.com/v1"; #+end_src *** Structs #+begin_src rust :tangle src/main.rs :comments link #[derive(Debug)] #[derive(Serialize)] struct System { pk_userid: String, sp_userid: String, members: Vec, } #[derive(Debug)] #[derive(Serialize)] #[derive(Clone)] struct Member { pk_id: String, sp_id: String, name: String, alias: String } #+end_src *** Main #+begin_src rust :tangle src/main.rs :comments link fn main() { if std::env::args().len() > 1 { let command = std::env::args().nth(1).expect("No command given"); let config_path: String; match dirs::config_dir() { Some(x) => { config_path = format!("{}/pluralshit", x.display()); match command.as_str() { "sync" => sync(config_path), "set" => { if std::env::args().len() > 2 { set_member(config_path, std::env::args().nth(2).expect("No member given")); } else { set_empty(config_path); } }, &_ => println!("Invalid command"), } }, None => println!("Something went wrong") } } else { println!("No arguments given"); } } #+end_src *** Functions **** Main commands #+begin_src rust :tangle src/main.rs :comments link fn set_member(config_path: String, member: String) { let config = load_json(format!("{}/config.json", config_path)); let system = get_system(config_path); //for mem in system["members"].as_str().unwrap() { // TODO // TODO Hacer que devuelva el json en forma de Vec // TODO //} } fn set_empty(config_path: String) { let config = load_json(format!("{}/config.json", config_path)); let system = get_system(config_path); } fn sync(config_path: String) { // Get config let config = load_json(format!("{}/config.json", config_path)); 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()); } #+end_src **** Pluralkit #+begin_src rust :tangle src/main.rs :comments link fn pk_get_system(key: &str) -> Value { let url = format!("{}/systems/@me", PK_URL); let res = http_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_request(url,key); let datas: Vec = serde_json::from_str(&res.unwrap()).unwrap(); return datas; } #+end_src **** Simplyplural #+begin_src rust :tangle src/main.rs :comments link fn sp_get_userid(key: &str) -> String { let url = format!("{}/me", SP_URL); let res = http_request(url,key); let json_res : Value = serde_json::from_str(&res.unwrap()).unwrap(); return json_res["id"].as_str().unwrap().to_string(); } fn sp_get_memberids(key: &str, system_id: &str) -> HashMap { let url = format!("{}/members/{}", SP_URL, system_id); let res = http_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; } 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; } #+end_src **** Utilities #+begin_src rust :tangle src/main.rs :comments link 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; } } // TODO // TODO fn get_config() -> Value { // TODO load_json // TODO if Value:null - > mkconfig dir - > make empty config.json // TODO else -> return // TODO } // TODO fn get_system(config_path: String) -> Value{ let path = format!("{}/system.json", config_path); // TODO // TODO load_json // TODO if Value:null - > sync - > load_json // TODO 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!("System file in {path} not found. Syncing"); sync(config_path); let config_data = fs::read_to_string(path).expect("File not found"); return serde_json::from_str(&config_data).unwrap(); } } #+end_src **** Http Request handler #+begin_src rust :tangle src/main.rs :comments link #[tokio::main] async fn http_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) } #+end_src * Organization ** Tasks :PROPERTIES: :COOKIE_DATA: recursive :END: *** Milestone 1.0 [1/11][9%] **** Main functions [1/9][11%] ***** Finished Add `sync` command ***** InProgress Add `set` command [0/2] ****** HighPriority [SET] Add empty ****** InProgress [SET] Add set ***** HighPriority Add `get` command ***** LowPriority Add `Add` command [0/2] ****** LowPriority [ADD] Add empty ****** LowPriority [ADD] Add set ***** LowPriority Add `setgroup` command **** Commands [0/0][100%] **** Utils [0/2][0%] ***** Json loading [0/2] ****** HighPriority Add new function to get the config create empty in path if not exists ****** HighPriority `Get system` if Value::Null sync and load json ** Kanban | Backlog | LowPriority | HighPriority | InProgress | Finished | |---------+-------------------------+--------------------------------+-------------------------+--------------------| | | [[/home/alicia/git/pluralshit/README.org::Add `Add` command \[0/2\]][Add `Add` command {0/2}]] | [[/home/alicia/git/pluralshit/README.org::\[SET\] Add empty][{SET} Add empty]] | [[/home/alicia/git/pluralshit/README.org::Add `set` command \[0/2\]][Add `set` command {0/2}]] | [[/home/alicia/git/pluralshit/README.org::Add `sync` command][Add `sync` command]] | | | [[/home/alicia/git/pluralshit/README.org::\[ADD\] Add empty][{ADD} Add empty]] | [[/home/alicia/git/pluralshit/README.org::Add `get` command][Add `get` command]] | [[/home/alicia/git/pluralshit/README.org::\[SET\] Add set][{SET} Add set]] | | | | [[/home/alicia/git/pluralshit/README.org::\[ADD\] Add set][{ADD} Add set]] | [[/home/alicia/git/pluralshit/README.org::Add new function to get the config create empty in path if not exists][Add new function to get the co]] | | | | | [[/home/alicia/git/pluralshit/README.org::Add `setgroup` command][Add `setgroup` command]] | [[/home/alicia/git/pluralshit/README.org::`Get system` if Value::Null sync and load json][`Get system` if Value::Null sy]] | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | #+TBLFM: @1='(kanban-headers $#)::@2$1..@>$>='(kanban-zero @# $# nil (list (buffer-file-name)))