2023-11-21 11:47:07 +00:00
|
|
|
#+title: Pluralsync (Codename pluralshit)
|
|
|
|
#+author: Jvnko
|
|
|
|
|
|
|
|
* 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
|
2023-11-21 13:20:21 +00:00
|
|
|
use std::fs;
|
|
|
|
use std::fs::create_dir;
|
2023-11-21 11:47:07 +00:00
|
|
|
use std::path::Path;
|
2023-11-21 13:20:21 +00:00
|
|
|
use std::collections::HashMap;
|
|
|
|
|
2023-11-21 11:47:07 +00:00
|
|
|
use reqwest::header::{USER_AGENT, AUTHORIZATION};
|
2023-11-21 13:20:21 +00:00
|
|
|
|
2023-11-21 11:47:07 +00:00
|
|
|
use serde::{Serialize, Deserialize};
|
|
|
|
use serde_json::Value;
|
2023-11-21 13:20:21 +00:00
|
|
|
|
2023-11-21 11:47:07 +00:00
|
|
|
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)]
|
2023-11-21 13:20:21 +00:00
|
|
|
#[derive(Deserialize)]
|
2023-11-21 14:38:25 +00:00
|
|
|
#[derive(Clone)]
|
2023-11-21 11:47:07 +00:00
|
|
|
struct System {
|
|
|
|
pk_userid: String,
|
|
|
|
sp_userid: String,
|
|
|
|
members: Vec<Member>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
#[derive(Serialize)]
|
2023-11-21 13:20:21 +00:00
|
|
|
#[derive(Deserialize)]
|
2023-11-21 11:47:07 +00:00
|
|
|
#[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() {
|
2023-11-21 13:20:21 +00:00
|
|
|
"sync" => {
|
|
|
|
let _ = sync(config_path);
|
|
|
|
},
|
2023-11-21 11:47:07 +00:00
|
|
|
"set" => {
|
|
|
|
if std::env::args().len() > 2 {
|
2023-11-21 13:20:21 +00:00
|
|
|
match set_member(config_path, std::env::args().nth(2).expect("No member given")) {
|
|
|
|
Ok(_) => (),
|
|
|
|
Err(e) => println!("{e}"),
|
|
|
|
}
|
2023-11-21 11:47:07 +00:00
|
|
|
} else {
|
2023-11-21 13:20:21 +00:00
|
|
|
//set_empty(config_path);
|
2023-11-21 11:47:07 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
&_ => println!("Invalid command"),
|
|
|
|
}
|
|
|
|
},
|
|
|
|
None => println!("Something went wrong")
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
println!("No arguments given");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#+end_src
|
|
|
|
|
|
|
|
*** Functions
|
|
|
|
**** Main commands
|
2023-11-21 15:03:51 +00:00
|
|
|
***** Sync
|
2023-11-21 11:47:07 +00:00
|
|
|
#+begin_src rust :tangle src/main.rs :comments link
|
2023-11-21 13:20:21 +00:00
|
|
|
fn sync(config_path: String) -> Result<(), String>{
|
2023-11-21 11:47:07 +00:00
|
|
|
// Get config
|
2023-11-21 13:20:21 +00:00
|
|
|
let config = get_config(&config_path);
|
|
|
|
if config == Value::Null {
|
|
|
|
println!("Stopping.");
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
2023-11-21 11:47:07 +00:00
|
|
|
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<Member> = 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());
|
|
|
|
|
2023-11-21 13:20:21 +00:00
|
|
|
Ok(())
|
2023-11-21 11:47:07 +00:00
|
|
|
}
|
|
|
|
#+end_src
|
|
|
|
|
2023-11-21 15:03:51 +00:00
|
|
|
***** Set member
|
|
|
|
#+begin_src rust :tangle src/main.rs :comments link
|
|
|
|
|
|
|
|
/*
|
|
|
|
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, member: 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 flag = false;
|
|
|
|
for mem in &system.members {
|
|
|
|
if mem.name.to_lowercase() == member.to_lowercase() {
|
|
|
|
flag = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if flag {
|
|
|
|
println!("Member {member} found");
|
|
|
|
get_fronters(&config["pk_key"].as_str().unwrap(), &system.pk_userid, &config["sp_key"].as_str().unwrap(), &system);
|
|
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err("Member {member} not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
#+end_src
|
|
|
|
|
2023-11-21 11:47:07 +00:00
|
|
|
**** Pluralkit
|
2023-11-21 15:03:51 +00:00
|
|
|
***** Get system
|
2023-11-21 11:47:07 +00:00
|
|
|
#+begin_src rust :tangle src/main.rs :comments link
|
|
|
|
fn pk_get_system(key: &str) -> Value {
|
|
|
|
let url = format!("{}/systems/@me", PK_URL);
|
|
|
|
|
2023-11-21 13:20:21 +00:00
|
|
|
let res = http_get_request(url,key);
|
2023-11-21 11:47:07 +00:00
|
|
|
return serde_json::from_str(&res.unwrap()).unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn pk_get_members(key: &str, sysid: &str) -> Vec<Value> {
|
|
|
|
let url = format!("{}/systems/{}/members", PK_URL, sysid);
|
|
|
|
|
2023-11-21 13:20:21 +00:00
|
|
|
let res = http_get_request(url,key);
|
2023-11-21 11:47:07 +00:00
|
|
|
let datas: Vec<Value> = serde_json::from_str(&res.unwrap()).unwrap();
|
|
|
|
|
|
|
|
return datas;
|
|
|
|
}
|
2023-11-21 15:03:51 +00:00
|
|
|
#+end_src
|
2023-11-21 14:38:25 +00:00
|
|
|
|
2023-11-21 15:03:51 +00:00
|
|
|
***** Get fronters
|
|
|
|
#+begin_src rust :tangle src/main.rs :comments link
|
2023-11-21 14:38:25 +00:00
|
|
|
fn pk_get_fronters(key: &str, sysid: &str) -> Vec<String> {
|
|
|
|
let url = format!("{}/systems/{}/fronters", PK_URL, sysid);
|
|
|
|
|
|
|
|
let res = http_get_request(url,key);
|
|
|
|
let data: Value = serde_json::from_str(&res.unwrap()).unwrap();
|
2023-11-21 15:03:51 +00:00
|
|
|
println!("{:?}", data);
|
2023-11-21 14:38:25 +00:00
|
|
|
let memberdata = &data["members"].as_array();
|
|
|
|
|
|
|
|
let mut members: Vec<String> = Vec::new();
|
|
|
|
for member in memberdata {
|
|
|
|
for m in member.into_iter() {
|
|
|
|
let mname = m["name"].as_str().unwrap();
|
|
|
|
members.push(String::from(mname));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return members;
|
|
|
|
}
|
2023-11-21 11:47:07 +00:00
|
|
|
#+end_src
|
|
|
|
|
|
|
|
**** Simplyplural
|
2023-11-21 15:03:51 +00:00
|
|
|
***** Get user ID
|
2023-11-21 11:47:07 +00:00
|
|
|
#+begin_src rust :tangle src/main.rs :comments link
|
|
|
|
fn sp_get_userid(key: &str) -> String {
|
|
|
|
let url = format!("{}/me", SP_URL);
|
|
|
|
|
2023-11-21 13:20:21 +00:00
|
|
|
let res = http_get_request(url,key);
|
2023-11-21 11:47:07 +00:00
|
|
|
let json_res : Value = serde_json::from_str(&res.unwrap()).unwrap();
|
|
|
|
return json_res["id"].as_str().unwrap().to_string();
|
|
|
|
}
|
2023-11-21 15:03:51 +00:00
|
|
|
#+end_src
|
2023-11-21 11:47:07 +00:00
|
|
|
|
2023-11-21 15:03:51 +00:00
|
|
|
***** Get members ID
|
|
|
|
#+begin_src rust :tangle src/main.rs :comments link
|
2023-11-21 11:47:07 +00:00
|
|
|
fn sp_get_memberids(key: &str, system_id: &str) -> HashMap<String, String> {
|
|
|
|
let url = format!("{}/members/{}", SP_URL, system_id);
|
|
|
|
|
2023-11-21 13:20:21 +00:00
|
|
|
let res = http_get_request(url,key);
|
2023-11-21 11:47:07 +00:00
|
|
|
let datas: Vec<Value> = serde_json::from_str(&res.unwrap()).unwrap();
|
|
|
|
|
|
|
|
let mut sp_memberdata: HashMap<String, String> = 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;
|
|
|
|
}
|
2023-11-21 15:03:51 +00:00
|
|
|
#+end_src
|
2023-11-21 11:47:07 +00:00
|
|
|
|
2023-11-21 15:03:51 +00:00
|
|
|
***** Get ID from member
|
|
|
|
#+begin_src rust :tangle src/main.rs :comments link
|
2023-11-21 11:47:07 +00:00
|
|
|
fn get_sp_id(mem: &Member, ids: &HashMap<String, String>) -> 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;
|
|
|
|
}
|
2023-11-21 15:03:51 +00:00
|
|
|
#+end_src
|
2023-11-21 14:38:25 +00:00
|
|
|
|
2023-11-21 15:03:51 +00:00
|
|
|
***** Get fronters
|
|
|
|
#+begin_src rust :tangle src/main.rs :comments link
|
2023-11-21 14:38:25 +00:00
|
|
|
fn sp_get_fronters(key: &str, sys: &System) -> Vec<String> {
|
|
|
|
let url = format!("{}/fronters", SP_URL);
|
|
|
|
|
|
|
|
let res = http_get_request(url,key);
|
|
|
|
let datas: Vec<Value> = serde_json::from_str(&res.unwrap()).unwrap();
|
|
|
|
|
|
|
|
let mut members: Vec<String> = Vec::new();
|
|
|
|
for data in datas {
|
|
|
|
let sp_id = &data["content"]["member"].as_str().unwrap();
|
|
|
|
let mut push_name = String::new();
|
|
|
|
for member in &sys.members {
|
|
|
|
if &member.sp_id == sp_id {
|
|
|
|
push_name = String::from(&member.name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
members.push(push_name);
|
|
|
|
|
|
|
|
}
|
|
|
|
return members;
|
|
|
|
}
|
2023-11-21 11:47:07 +00:00
|
|
|
#+end_src
|
|
|
|
|
|
|
|
**** Utilities
|
2023-11-21 15:03:51 +00:00
|
|
|
***** Load JSON
|
2023-11-21 11:47:07 +00:00
|
|
|
#+begin_src rust :tangle src/main.rs :comments link
|
|
|
|
fn load_json(path: String) -> Value {
|
|
|
|
if Path::new(&path).exists() {
|
2023-11-21 13:20:21 +00:00
|
|
|
let config_data = fs::read_to_string(&path).expect("File not found");
|
2023-11-21 11:47:07 +00:00
|
|
|
return serde_json::from_str(&config_data).unwrap();
|
|
|
|
} else {
|
|
|
|
println!("Config file in {path} not found");
|
|
|
|
return Value::Null;
|
|
|
|
}
|
|
|
|
}
|
2023-11-21 15:03:51 +00:00
|
|
|
#+end_src
|
2023-11-21 11:47:07 +00:00
|
|
|
|
2023-11-21 15:03:51 +00:00
|
|
|
***** Get config json
|
|
|
|
#+begin_src rust :tangle src/main.rs :comments link
|
2023-11-21 13:20:21 +00:00
|
|
|
fn get_config(config_path: &str) -> Value {
|
|
|
|
let path = format!("{}/config.json", config_path);
|
|
|
|
if Path::new(config_path).exists() {
|
2023-11-21 11:47:07 +00:00
|
|
|
|
2023-11-21 13:20:21 +00:00
|
|
|
let result = load_json(String::from(&path));
|
2023-11-21 11:47:07 +00:00
|
|
|
|
2023-11-21 13:20:21 +00:00
|
|
|
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"
|
|
|
|
}
|
|
|
|
"#);
|
|
|
|
}
|
2023-11-21 11:47:07 +00:00
|
|
|
|
2023-11-21 13:20:21 +00:00
|
|
|
return result;
|
2023-11-21 11:47:07 +00:00
|
|
|
} else {
|
2023-11-21 13:20:21 +00:00
|
|
|
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;
|
2023-11-21 11:47:07 +00:00
|
|
|
}
|
|
|
|
}
|
2023-11-21 15:03:51 +00:00
|
|
|
#+end_src
|
2023-11-21 13:20:21 +00:00
|
|
|
|
2023-11-21 15:03:51 +00:00
|
|
|
***** Get system json
|
|
|
|
#+begin_src rust :tangle src/main.rs :comments link
|
2023-11-21 13:20:21 +00:00
|
|
|
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::<System>(&vec).unwrap();
|
|
|
|
|
|
|
|
return sys;
|
|
|
|
|
|
|
|
}
|
2023-11-21 14:38:25 +00:00
|
|
|
|
|
|
|
fn get_fronters(pk_key: &str, pk_sysid: &str, sp_key: &str, sys: &System) -> HashMap<String, Vec<String>> {
|
|
|
|
let mut fronters: HashMap<String, Vec<String>> = HashMap::new();
|
|
|
|
fronters.insert(String::from("pk"), pk_get_fronters(pk_key, pk_sysid));
|
|
|
|
fronters.insert(String::from("sp"), sp_get_fronters(sp_key, sys));
|
|
|
|
println!("{:?}", fronters);
|
|
|
|
|
|
|
|
return fronters;
|
|
|
|
}
|
2023-11-21 11:47:07 +00:00
|
|
|
#+end_src
|
|
|
|
|
|
|
|
**** Http Request handler
|
|
|
|
#+begin_src rust :tangle src/main.rs :comments link
|
|
|
|
#[tokio::main]
|
2023-11-21 13:20:21 +00:00
|
|
|
async fn http_get_request(url: String, key: &str) -> Result<String, Box<dyn std::error::Error>> {
|
2023-11-21 11:47:07 +00:00
|
|
|
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
|