pluralsync/README.org

11 KiB

Pluralsync (Codename pluralshit)

About

Configuration

{
  "pk_key": "// Pluralkit token",
  "sp_key": "// Simplplural token"
}

Code

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"] }

Main.rs

Imports

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;

Constants

const PK_URL: &str = "https://api.pluralkit.me/v2";
const SP_URL: &str = "https://api.apparyllis.com/v1";

Structs

#[derive(Debug)]
#[derive(Serialize)]
struct System {
    pk_userid: String,
    sp_userid: String,
    members: Vec<Member>,
}

#[derive(Debug)]
#[derive(Serialize)]
#[derive(Clone)]
struct Member {
    pk_id: String,
    sp_id: String,
    name: String,
    alias: String
}

Main

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");
    }
}

Functions

Main commands
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<Member>
    // 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<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());

}
Pluralkit
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<Value> {
    let url = format!("{}/systems/{}/members", PK_URL, sysid);

    let res = http_request(url,key);
    let datas: Vec<Value> = serde_json::from_str(&res.unwrap()).unwrap();

    return datas;
}
Simplyplural
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<String, String> {
    let url = format!("{}/members/{}", SP_URL, system_id);

    let res = http_request(url,key);
    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;
}

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;
}
Utilities
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();
    }
}
Http Request handler
#[tokio::main]
async fn http_request(url: String, key: &str) -> Result<String, Box<dyn std::error::Error>> {
    let client = reqwest::Client::new();
    let res = client
        .get(url)
        .header(USER_AGENT, "Pluralsync")
        .header(AUTHORIZATION, key)
        .send()
        .await?
        .text()
        .await?;
    Ok(res)
}

Organization

Tasks

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