Tool written in rust to syncronize API calls between pluralkit and simplyplural
Go to file
Junko 46e0eb210d get members 2023-11-21 15:38:25 +01:00
src get members 2023-11-21 15:38:25 +01:00
.gitignore get members 2023-11-21 15:38:25 +01:00
Cargo.lock initial commit 2023-11-20 16:39:40 +01:00
Cargo.toml initial commit 2023-11-20 16:39:40 +01:00
README.org get members 2023-11-21 15:38:25 +01:00
TODO.org get members 2023-11-21 15:38:25 +01:00
config.example.json docs start 2023-11-21 12:47:07 +01:00

README.org

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::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;

Constants

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

Structs

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

#[derive(Debug)]
#[derive(Serialize)]
#[derive(Deserialize)]
#[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" => {
                        let _ = sync(config_path);
                    },
                    "set" => {
                        if std::env::args().len() > 2 {
                            match set_member(config_path, std::env::args().nth(2).expect("No member given")) {
                                Ok(_) => (),
                                Err(e) => println!("{e}"),
                            }
                        } 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) -> 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")
    }

}

/*
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) -> 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<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());

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

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

    return datas;
}

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();
    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;
}
Simplyplural
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();
}

fn sp_get_memberids(key: &str, system_id: &str) -> HashMap<String, String> {
    let url = format!("{}/members/{}", SP_URL, system_id);

    let res = http_get_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;
}

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

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

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;

}

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;
}
Http Request handler
#[tokio::main]
async fn http_get_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)
}