Merge pull request 'Codebase refactor' (!3) from refactor into main
Reviewed-on: #3
This commit is contained in:
commit
c1788330f5
File diff suppressed because it is too large
Load Diff
|
@ -13,8 +13,12 @@ serde_json = "1.0"
|
|||
reqwest = { version = "0.11.22", features = ["json", "multipart"] }
|
||||
clap = { version = "4.4.10", features = ["derive"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
ratatui = "0.26.0"
|
||||
crossterm = "0.27.0"
|
||||
color-eyre = "0.6.2"
|
||||
|
||||
[features]
|
||||
default = ["avatar"]
|
||||
avatar = []
|
||||
discord = ["avatar"]
|
||||
fedi = ["avatar"]
|
||||
|
|
|
@ -0,0 +1,292 @@
|
|||
pub mod pk {
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use reqwest::header::{USER_AGENT, AUTHORIZATION};
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::api::request::*;
|
||||
use crate::systemdata::*;
|
||||
|
||||
use color_eyre::eyre::{eyre, Result};
|
||||
|
||||
pub const PK_URL: &str = "https://api.pluralkit.me/v2";
|
||||
|
||||
pub fn get_system(key: &str) -> Result<Value> {
|
||||
let url = format!("{}/systems/@me", PK_URL);
|
||||
|
||||
let response = http_get(url,key)?;
|
||||
|
||||
Ok(serde_json::from_str(&response)?)
|
||||
}
|
||||
|
||||
pub fn get_members(key: &str, sysid: &str) -> Result<Vec<Value>> {
|
||||
let url = format!("{}/systems/{}/members", PK_URL, sysid);
|
||||
|
||||
let response = http_get(url,key)?;
|
||||
|
||||
let values: Vec<Value> = serde_json::from_str(&response)?;
|
||||
|
||||
Ok(values)
|
||||
}
|
||||
|
||||
pub fn get_fronters(key: &str, sys: &System) -> Result<Vec<Member>> {
|
||||
let url = format!("{}/systems/{}/fronters", PK_URL, sys.pk_userid);
|
||||
|
||||
let result = http_get(url,key)?;
|
||||
let json: Value = serde_json::from_str(&result).unwrap();
|
||||
let json_members = &json["members"].as_array();
|
||||
|
||||
let mut members: Vec<Member> = Vec::new();
|
||||
for member in json_members {
|
||||
for m in member.into_iter() {
|
||||
for dbmem in &sys.members {
|
||||
if m["name"].as_str().unwrap().to_string() == dbmem.name {
|
||||
members.push(dbmem.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(members)
|
||||
}
|
||||
|
||||
|
||||
pub fn set_fronters(key: &str, sys: &System, to_front: &Vec<Member>, fronters: &Fronters) -> Result<()> {
|
||||
let url = format!("{}/systems/{}/switches", PK_URL, sys.pk_userid);
|
||||
|
||||
if to_front != &fronters.pk {
|
||||
let mut frontcodes = Vec::new();
|
||||
for tf in to_front {
|
||||
frontcodes.push(String::from(&tf.pk_id));
|
||||
}
|
||||
|
||||
let mut body: HashMap<&str, Vec<String>> = HashMap::new();
|
||||
body.insert("members", frontcodes);
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let rb = client
|
||||
.post(url)
|
||||
.json(&body)
|
||||
.header("content-type", "application/json; charset=utf-8")
|
||||
.header(USER_AGENT, "Pluralsync")
|
||||
.header(AUTHORIZATION, key);
|
||||
|
||||
match http_request(rb) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(eyre!("{}", e.to_string())),
|
||||
}
|
||||
|
||||
} else {
|
||||
println!("Members already fonting");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod sp {
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use reqwest::header::{USER_AGENT, AUTHORIZATION};
|
||||
|
||||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
use crate::api::request::*;
|
||||
use crate::systemdata::*;
|
||||
|
||||
use color_eyre::eyre::{eyre, Result};
|
||||
|
||||
pub const SP_URL: &str = "https://api.apparyllis.com/v1";
|
||||
|
||||
pub fn get_user_id(key: &str) -> Result<String> {
|
||||
let url = format!("{}/me", SP_URL);
|
||||
|
||||
let response = http_get(url, key)?;
|
||||
|
||||
let json: Value = serde_json::from_str(&response)?;
|
||||
|
||||
Ok(json["id"].as_str().unwrap().to_string())
|
||||
}
|
||||
|
||||
pub fn get_member_ids(key: &str, system_id: &str) -> Result<HashMap<String, String>> {
|
||||
let url = format!("{}/members/{}", SP_URL, system_id);
|
||||
|
||||
let response = http_get(url,key)?;
|
||||
let json: Vec<Value> = serde_json::from_str(&response)?;
|
||||
|
||||
let mut sp_member_data: HashMap<String, String> = HashMap::new();
|
||||
for member in json {
|
||||
sp_member_data.insert(
|
||||
member["content"]["name"].as_str().unwrap().to_string(),
|
||||
member["id"].as_str().unwrap().to_string());
|
||||
}
|
||||
|
||||
Ok(sp_member_data)
|
||||
}
|
||||
|
||||
pub fn get_member_id(m: &Member, ids: &HashMap<String, String>) -> Result<String> {
|
||||
|
||||
for (sp_name, sp_id) in ids {
|
||||
if &m.name == sp_name || m.aliases.iter().any(|e| sp_name.contains(e)) {
|
||||
return Ok(sp_id.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
Err(eyre!(format!("SP ID not found for {}", &m.name)))
|
||||
}
|
||||
|
||||
pub fn get_fronters(key: &str, sys: &System) -> Result<Vec<Member>> {
|
||||
let url = format!("{}/fronters", SP_URL);
|
||||
|
||||
let result = http_get(url,key)?;
|
||||
let json: Vec<Value> = serde_json::from_str(&result)?;
|
||||
|
||||
let mut members = Vec::new();
|
||||
for data in json {
|
||||
let sp_id = &data["content"]["member"].as_str().unwrap().to_string();
|
||||
for member in &sys.members {
|
||||
if &member.sp_id == sp_id {
|
||||
members.push(member.clone());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Ok(members)
|
||||
}
|
||||
|
||||
pub fn get_front_id(key: &str, to_id: &Member) -> Result<String> {
|
||||
let url = format!("{}/fronters", SP_URL);
|
||||
|
||||
let result = http_get(url,key)?;
|
||||
let datas: Vec<Value> = serde_json::from_str(&result)?;
|
||||
|
||||
for data in datas {
|
||||
let sp_f_id = &data["id"].to_string();
|
||||
let sp_id = &data["content"]["member"].to_string();
|
||||
if sp_id.to_string() == to_id.sp_id {
|
||||
return Ok(sp_f_id.to_string());
|
||||
}
|
||||
}
|
||||
Err(eyre!("SP ID not found"))
|
||||
}
|
||||
|
||||
pub fn set_fronters(key: &str, to_front: &Vec<Member>, fronters: &Fronters) -> Result<()> {
|
||||
if to_front == &fronters.sp {
|
||||
println!("Members already fonting");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for fronting_member in &fronters.sp {
|
||||
if !to_front.contains(&fronting_member) {
|
||||
let f_id = get_front_id(&key, &fronting_member)?;
|
||||
|
||||
let url = format!("{}/frontHistory/{}", SP_URL, f_id);
|
||||
|
||||
#[derive (Serialize)]
|
||||
#[allow (non_snake_case)]
|
||||
struct SpRem {
|
||||
live: bool,
|
||||
endTime: u64
|
||||
}
|
||||
|
||||
let end_time = std::time::SystemTime::now().duration_since(std::time::SystemTime::UNIX_EPOCH)?.as_secs();
|
||||
let rem = SpRem {
|
||||
live: false,
|
||||
endTime: end_time * 1000
|
||||
};
|
||||
|
||||
let body = serde_json::to_string(&rem)?;
|
||||
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let rb = client
|
||||
.patch(url)
|
||||
.body(body)
|
||||
.header("content-type", "application/json; charset=utf-8")
|
||||
.header(USER_AGENT, "Pluralsync")
|
||||
.header(AUTHORIZATION, key);
|
||||
|
||||
match http_request(rb) {
|
||||
Ok(_) => (),
|
||||
Err(e) => println!("{}", e.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for tf_member in to_front {
|
||||
if !fronters.sp.contains(&tf_member) {
|
||||
let url = format!("{}/frontHistory", SP_URL);
|
||||
|
||||
#[derive (Serialize)]
|
||||
#[allow (non_snake_case)]
|
||||
struct SpAdd {
|
||||
member: String,
|
||||
custom: bool,
|
||||
live: bool,
|
||||
startTime: u64
|
||||
}
|
||||
|
||||
let start_time = std::time::SystemTime::now().duration_since(std::time::SystemTime::UNIX_EPOCH)?.as_secs();
|
||||
let rem = SpAdd {
|
||||
member: String::from(&tf_member.sp_id),
|
||||
custom: false,
|
||||
live: true,
|
||||
startTime: start_time * 1000
|
||||
};
|
||||
|
||||
let body = serde_json::to_string(&rem)?;
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let rb = client
|
||||
.post(url)
|
||||
.body(body)
|
||||
.header("content-type", "application/json; charset=utf-8")
|
||||
.header(USER_AGENT, "Pluralsync")
|
||||
.header(AUTHORIZATION, key);
|
||||
|
||||
match http_request(rb) {
|
||||
Ok(_) => (),
|
||||
Err(e) => println!("{}", e.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub mod request {
|
||||
|
||||
use reqwest::RequestBuilder;
|
||||
use reqwest::header::{USER_AGENT, AUTHORIZATION};
|
||||
#[cfg(feature = "fedi")]
|
||||
use reqwest::multipart::{Part, Form};
|
||||
|
||||
use color_eyre::eyre::Result;
|
||||
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn http_get(url: String, key: &str) -> Result<String> {
|
||||
let client = reqwest::Client::new();
|
||||
let rb = client
|
||||
.get(url)
|
||||
.header(USER_AGENT, "Pluralsync")
|
||||
.header(AUTHORIZATION, key);
|
||||
let res = rb.send()
|
||||
.await?
|
||||
.text()
|
||||
.await?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn http_request(rb: RequestBuilder) -> Result<String> {
|
||||
let res = rb.send()
|
||||
.await?
|
||||
.text()
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
use crate::configdata::*;
|
||||
use crate::systemdata::*;
|
||||
use crate::clap_ps::ForceFrom;
|
||||
|
||||
use color_eyre::eyre::{eyre, Result};
|
||||
|
||||
pub enum CurrentScreen {
|
||||
Dash,
|
||||
Configuration,
|
||||
Exit,
|
||||
}
|
||||
|
||||
pub enum ConfigEdit {
|
||||
PkKey,
|
||||
SpKey,
|
||||
AvatarEnabled,
|
||||
AvatarFolder,
|
||||
AvatarOutput,
|
||||
AvatarBlacklist,
|
||||
DiscEnabled,
|
||||
DiscToken,
|
||||
FediEnabled,
|
||||
FediInstance,
|
||||
FediToken,
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
pub input: String,
|
||||
pub cfg_dir: String,
|
||||
pub cfg: Option<Config>,
|
||||
pub sys: Option<System>,
|
||||
pub fronters: Option<Fronters>,
|
||||
pub current_screen: Option<CurrentScreen>,
|
||||
pub editing: Option<ConfigEdit>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new(confpath: String) -> App {
|
||||
App {
|
||||
input: String::new(),
|
||||
cfg_dir: confpath,
|
||||
cfg: None,
|
||||
sys: None,
|
||||
fronters: None,
|
||||
current_screen: None,
|
||||
editing: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_system(&mut self, update: bool) -> Result<()> {
|
||||
|
||||
match System::get_system(&self, update) {
|
||||
Ok(s) => {
|
||||
self.sys = Some(s);
|
||||
Ok(())
|
||||
},
|
||||
Err(e) => {
|
||||
self.sys = None;
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_config(&mut self) -> Result<()> {
|
||||
|
||||
match Config::get_config(&self.cfg_dir) {
|
||||
Ok(c) => {
|
||||
self.cfg = Some(c);
|
||||
Ok(())
|
||||
},
|
||||
Err(e) => {
|
||||
self.cfg = None;
|
||||
Err(e)
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn get(&mut self, ff: ForceFrom) -> Result<()> {
|
||||
|
||||
self.fronters = Some(Fronters::get(self, ff)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set(&mut self, to_front: Vec<Member>) -> Result<()> {
|
||||
|
||||
self.get(ForceFrom::None)?;
|
||||
|
||||
self.fronters = Some(Fronters::set(self, to_front)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add(&mut self, to_front: Vec<Member>) -> Result<()> {
|
||||
|
||||
let aux_fronters: Fronters = Fronters::get(self, ForceFrom::None)?;
|
||||
|
||||
let mut addfronters: Vec<Member> = Vec::new();
|
||||
addfronters.append(&mut aux_fronters.pk.clone());
|
||||
addfronters.append(&mut to_front.clone());
|
||||
|
||||
self.fronters = Some(Fronters::set(self, addfronters)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn memberlist(&self) -> Result<()> {
|
||||
|
||||
if let Some(sys) = &self.sys {
|
||||
for member in &sys.members {
|
||||
println!("{} / {}", member.name, member.aliases.join(" | "));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_member(&mut self, tf_members: Vec<String>, append: bool) -> Result<()> {
|
||||
|
||||
if let Some(sys) = &self.sys {
|
||||
let mut to_front: Vec<Member> = Vec::new();
|
||||
for member in &tf_members {
|
||||
for mem in &sys.members {
|
||||
let lowcase_aliases: Vec<String> = mem.aliases.iter().map(|x| x.to_lowercase()).collect();
|
||||
if mem.name.to_lowercase() == member.to_lowercase() || lowcase_aliases.iter().any(|e| member.contains(e)) {
|
||||
println!("Member {member} found.");
|
||||
|
||||
to_front.push(mem.clone());
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if to_front.len() == tf_members.len() {
|
||||
if !append {
|
||||
self.set(to_front)?;
|
||||
} else {
|
||||
self.add(to_front)?;
|
||||
}
|
||||
} else {
|
||||
println!("One or more members were not found. Known members:\n--------------------------");
|
||||
self.memberlist()?;
|
||||
println!("--------------------------\nIf a member is missing from the system try running \"pluralsync update\" to refresh the local database");
|
||||
return Err(eyre!("Missing member"));
|
||||
}
|
||||
|
||||
#[cfg(feature = "jlog")] {
|
||||
let mut names = Vec::new();
|
||||
for m in &to_front {
|
||||
names.push(String::from(&m.name));
|
||||
}
|
||||
let log_fronters = names.join(" || ");
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
std::process::Command::new("jlog").args(["info", format!("Switch registered: {}", log_fronters)]).output().expect("Logging error");
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
std::process::Command::new("jlog").arg("info").arg(format!("Switch registered: {}", log_fronters)).output().expect("Logging error");
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
Err(eyre!("No system found"))
|
||||
}
|
||||
|
||||
pub fn get_front(&mut self, ff: ForceFrom) -> Result<Vec<String>> {
|
||||
|
||||
self.get(ff)?;
|
||||
|
||||
if let Some(fronters) = &self.fronters {
|
||||
let mut names: Vec<String> = Vec::new();
|
||||
for member in &fronters.pk {
|
||||
names.push(String::from(&member.name));
|
||||
}
|
||||
|
||||
let fronters = names.join(" || ");
|
||||
println!("Currently fronting: {}", fronters);
|
||||
std::fs::write(format!("{}/.front", &self.cfg_dir), &fronters)?;
|
||||
|
||||
return Ok(names);
|
||||
}
|
||||
Err(eyre!("No fronters found"))
|
||||
}
|
||||
}
|
|
@ -19,10 +19,13 @@ pub struct Args {
|
|||
|
||||
#[derive(Subcommand)]
|
||||
pub enum Commands {
|
||||
///Fetches the system member information from PluralKit and SimplyPlural
|
||||
Sync,
|
||||
/// Fetches the system member information from PluralKit and SimplyPlural
|
||||
Update,
|
||||
/// TUI configuration tool
|
||||
TUI,
|
||||
/// Sets one or more members to the front
|
||||
#[clap(arg_required_else_help = true)]
|
||||
#[non_exhaustive]
|
||||
Set {
|
||||
/// Members to set
|
||||
members: Vec<String>,
|
||||
|
|
|
@ -1,6 +1,16 @@
|
|||
use serde::{Serialize, Deserialize};
|
||||
use std::{
|
||||
fs::create_dir,
|
||||
path::Path,
|
||||
};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
use crate::utils::*;
|
||||
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde_json::Value;
|
||||
|
||||
use color_eyre::eyre::{eyre, Result};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Config {
|
||||
pub pk_key: String,
|
||||
pub sp_key: String,
|
||||
|
@ -13,7 +23,7 @@ pub struct Config {
|
|||
}
|
||||
|
||||
#[cfg(feature = "avatar")]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct AvatarModule {
|
||||
pub enabled: bool,
|
||||
pub avatar_folder: String,
|
||||
|
@ -22,7 +32,7 @@ pub struct AvatarModule {
|
|||
}
|
||||
|
||||
#[cfg(feature = "discord")]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct DiscModule {
|
||||
pub enabled: bool,
|
||||
pub token: String,
|
||||
|
@ -31,9 +41,29 @@ pub struct DiscModule {
|
|||
}
|
||||
|
||||
#[cfg(feature = "fedi")]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct FediModule {
|
||||
pub enabled: bool,
|
||||
pub instance: String,
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn get_config(cfg_dir: &str) -> Result<Config> {
|
||||
|
||||
let cfg_file_path = format!("{}/config.json", cfg_dir);
|
||||
|
||||
if !Path::new(&cfg_dir).exists() {
|
||||
let _ = create_dir(cfg_dir);
|
||||
}
|
||||
|
||||
if Path::new(&cfg_file_path).exists() {
|
||||
let json_config: Value = load_json(&cfg_file_path)?;
|
||||
let conf: Config = serde_json::from_value(json_config)?;
|
||||
Ok(conf)
|
||||
} else {
|
||||
// TODO Trigger file creation screen if there is no config file.
|
||||
Err(eyre!("Config file not found"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
831
src/main.rs
831
src/main.rs
|
@ -1,839 +1,74 @@
|
|||
use std::fs;
|
||||
#[cfg(feature = "avatar")]
|
||||
use std::fs::remove_file;
|
||||
use std::fs::create_dir;
|
||||
use std::path::Path;
|
||||
use std::collections::HashMap;
|
||||
#[cfg(feature = "avatar")]
|
||||
use std::process::Command;
|
||||
|
||||
use reqwest::RequestBuilder;
|
||||
use reqwest::header::{USER_AGENT, AUTHORIZATION};
|
||||
#[cfg(feature = "fedi")]
|
||||
use reqwest::multipart::{Part, Form};
|
||||
|
||||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
mod app;
|
||||
mod configdata;
|
||||
mod systemdata;
|
||||
mod api;
|
||||
mod utils;
|
||||
mod clap_ps;
|
||||
|
||||
use dirs;
|
||||
|
||||
mod configdata;
|
||||
use configdata::*;
|
||||
use app::*;
|
||||
use utils::update_avatars;
|
||||
|
||||
mod systemdata;
|
||||
use systemdata::*;
|
||||
|
||||
mod clap_ps;
|
||||
use clap_ps::*;
|
||||
use clap::Parser;
|
||||
|
||||
const PK_URL: &str = "https://api.pluralkit.me/v2";
|
||||
const SP_URL: &str = "https://api.apparyllis.com/v1";
|
||||
use color_eyre::eyre::{eyre, Result};
|
||||
|
||||
const EXAMPLE_JSON: &str = r#"{
|
||||
"pk_key": "// Pluralkit token",
|
||||
"sp_key": "// Simplplural token",
|
||||
"avatar_module": {
|
||||
"enabled": false,
|
||||
"avatar_folder": "// Folder to grab profile pictures, they follow they member1member2...memberX.png format",
|
||||
"avatar_output_path": "// Path for the copied selected avatar",
|
||||
"blacklist": "// Array of members to not include in the avatar module"
|
||||
},
|
||||
"disc_module": {
|
||||
"enabled": false,
|
||||
"token": "// Discord user token",
|
||||
"python_path": "// Path to the python executable",
|
||||
"script_path": "// Path to updatediscordavatar.py"
|
||||
},
|
||||
"fedi_module": {
|
||||
"enabled": false,
|
||||
"instance" : "// Fedi instance url",
|
||||
"token": "// Fedi bearer token"
|
||||
}
|
||||
}"#;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<()> {
|
||||
|
||||
let cli = Args::parse();
|
||||
|
||||
let config_path = match dirs::config_dir() {
|
||||
Some(d) => format!("{}/pluralsync", d.display()),
|
||||
None => {
|
||||
println!("Could not fetch the config directory");
|
||||
return ();
|
||||
return Err(eyre!("Could not figure out the config directory!"));
|
||||
}
|
||||
};
|
||||
|
||||
let mut res: Result<(), &str> = Ok(());
|
||||
let mut app: App = App::new(config_path.clone());
|
||||
app.load_config()?;
|
||||
app.load_system(false)?;
|
||||
|
||||
match cli.cmd {
|
||||
Commands::Sync => {
|
||||
res = sync(config_path);
|
||||
Commands::Update => {
|
||||
app.load_system(true)?;
|
||||
},
|
||||
|
||||
// SET MEMBER
|
||||
#[cfg(all(feature = "discord", feature = "fedi"))]
|
||||
Commands::Set { members, discord, fedi } => {
|
||||
res = set_member(config_path.clone(), members);
|
||||
|
||||
#[cfg(feature = "avatar")] {
|
||||
match res {
|
||||
Ok(_) => {
|
||||
let _ = update_avatars(config_path.clone(), discord, fedi);
|
||||
},
|
||||
Err(_) => (),
|
||||
}
|
||||
}
|
||||
},
|
||||
#[cfg(all(feature = "discord", not(feature = "fedi")))]
|
||||
Commands::Set { members, discord } => {
|
||||
res = set_member(config_path.clone(), members);
|
||||
|
||||
#[cfg(feature = "avatar")] {
|
||||
match res {
|
||||
Ok(_) => {
|
||||
let _ = update_avatars(config_path.clone(), discord, false);
|
||||
},
|
||||
Err(_) => (),
|
||||
}
|
||||
}
|
||||
},
|
||||
#[cfg(all(not(feature = "discord"), feature = "fedi"))]
|
||||
Commands::Set { members, fedi} => {
|
||||
res = set_member(config_path.clone(), members);
|
||||
|
||||
#[cfg(feature = "avatar")] {
|
||||
match res {
|
||||
Ok(_) => {
|
||||
let _ = update_avatars(config_path.clone(), false, fedi);
|
||||
},
|
||||
Err(_) => (),
|
||||
}
|
||||
}
|
||||
},
|
||||
#[cfg(all(not(feature = "discord"), not(feature = "fedi")))]
|
||||
Commands::Set { members } => {
|
||||
res = set_member(config_path.clone(), members);
|
||||
|
||||
#[cfg(feature = "avatar")] {
|
||||
match res {
|
||||
Ok(_) => {
|
||||
let _ = update_avatars(config_path.clone(), false, false);
|
||||
},
|
||||
Err(_) => (),
|
||||
}
|
||||
}
|
||||
Commands::TUI => {
|
||||
()
|
||||
},
|
||||
|
||||
// ADD MEMBER
|
||||
#[cfg(all(feature = "discord", feature = "fedi"))]
|
||||
Commands::Add { members, discord, fedi } => {
|
||||
res = add_member(config_path.clone(), members);
|
||||
Commands::Set { members, #[cfg(feature = "discord")] discord, #[cfg(feature = "fedi")] fedi } => {
|
||||
app.set_member(members, false)?;
|
||||
|
||||
#[cfg(feature = "avatar")] {
|
||||
match res {
|
||||
Ok(_) => {
|
||||
let _ = update_avatars(config_path.clone(), discord, fedi);
|
||||
},
|
||||
Err(_) => (),
|
||||
}
|
||||
}
|
||||
},
|
||||
#[cfg(all(feature = "discord", not(feature = "fedi")))]
|
||||
Commands::Add { members, discord } => {
|
||||
res = add_member(config_path.clone(), members);
|
||||
|
||||
#[cfg(feature = "avatar")] {
|
||||
match res {
|
||||
Ok(_) => {
|
||||
let _ = update_avatars(config_path.clone(), discord, false);
|
||||
},
|
||||
Err(_) => (),
|
||||
}
|
||||
}
|
||||
},
|
||||
#[cfg(all(not(feature = "discord"), feature = "fedi"))]
|
||||
Commands::Add { members, fedi} => {
|
||||
res = add_member(config_path.clone(), members);
|
||||
|
||||
#[cfg(feature = "avatar")] {
|
||||
match res {
|
||||
Ok(_) => {
|
||||
let _ = update_avatars(config_path.clone(), false, fedi);
|
||||
},
|
||||
Err(_) => (),
|
||||
}
|
||||
}
|
||||
},
|
||||
#[cfg(all(not(feature = "discord"), not(feature = "fedi")))]
|
||||
Commands::Add { members } => {
|
||||
res = add_member(config_path.clone(), members);
|
||||
|
||||
#[cfg(feature = "avatar")] {
|
||||
match res {
|
||||
Ok(_) => {
|
||||
let _ = update_avatars(config_path.clone(), false, false);
|
||||
},
|
||||
Err(_) => (),
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "avatar")]
|
||||
update_avatars(&mut app, #[cfg(feature = "discord")] discord, #[cfg(feature = "fedi")] fedi)?;
|
||||
},
|
||||
|
||||
// Get MEMBER
|
||||
#[cfg(all(feature = "discord", feature = "fedi"))]
|
||||
Commands::Get { force_from, discord, fedi } => {
|
||||
Commands::Add { members, #[cfg(feature = "discord")] discord, #[cfg(feature = "fedi")] fedi } => {
|
||||
app.set_member(members, true)?;
|
||||
|
||||
#[cfg(feature = "avatar")]
|
||||
update_avatars(&mut app, #[cfg(feature = "discord")] discord, #[cfg(feature = "fedi")] fedi)?;
|
||||
},
|
||||
|
||||
Commands::Get { force_from, #[cfg(feature = "discord")] discord, #[cfg(feature = "fedi")] fedi } => {
|
||||
let ff = match force_from {
|
||||
Some(x) => x,
|
||||
None => ForceFrom::None,
|
||||
};
|
||||
|
||||
let _ = get(config_path.clone(), ff);
|
||||
app.get_front(ff)?;
|
||||
|
||||
#[cfg(feature = "avatar")]
|
||||
let _ = update_avatars(config_path.clone(), discord, fedi);
|
||||
},
|
||||
#[cfg(all(feature = "discord", not(feature = "fedi")))]
|
||||
Commands::Get { force_from, discord } => {
|
||||
let ff = match force_from {
|
||||
Some(x) => x,
|
||||
None => ForceFrom::None,
|
||||
};
|
||||
|
||||
let _ = get(config_path.clone(), ff);
|
||||
|
||||
#[cfg(feature = "avatar")]
|
||||
let _ = update_avatars(config_path.clone(), discord, false);
|
||||
},
|
||||
#[cfg(all(not(feature = "discord"), feature = "fedi"))]
|
||||
Commands::Get { force_from, fedi} => {
|
||||
let ff = match force_from {
|
||||
Some(x) => x,
|
||||
None => ForceFrom::None,
|
||||
};
|
||||
|
||||
let _ = get(config_path.clone(), ff);
|
||||
|
||||
#[cfg(feature = "avatar")]
|
||||
let _ = update_avatars(config_path.clone(), false, fedi);
|
||||
},
|
||||
#[cfg(all(not(feature = "discord"), not(feature = "fedi")))]
|
||||
Commands::Get { force_from } => {
|
||||
let ff = match force_from {
|
||||
Some(x) => x,
|
||||
None => ForceFrom::None,
|
||||
};
|
||||
|
||||
let _ = get(config_path.clone(), ff);
|
||||
|
||||
#[cfg(feature = "avatar")]
|
||||
let _ = update_avatars(config_path.clone(), false, false);
|
||||
update_avatars(&mut app, #[cfg(feature = "discord")] discord, #[cfg(feature = "fedi")] fedi)?;
|
||||
},
|
||||
|
||||
Commands::Members => {
|
||||
res = memberlist(config_path);
|
||||
app.memberlist()?;
|
||||
},
|
||||
}
|
||||
|
||||
match res {
|
||||
Ok(_) => (),
|
||||
Err(e) => println!("{}", e),
|
||||
}
|
||||
}
|
||||
|
||||
fn sync(config_path: String) -> Result<(), &'static str> {
|
||||
// Get config
|
||||
let config: Config = match get_config(&config_path) {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Err(e)
|
||||
};
|
||||
|
||||
// Get Pluralkit system id
|
||||
let pk_sys = pk_get_system(&config.pk_key);
|
||||
let pk_sysid = pk_sys["id"].as_str().unwrap();
|
||||
|
||||
// Get Simplyplural user id
|
||||
let sp_user_id = sp_get_userid(&config.sp_key);
|
||||
|
||||
// Get Simplyplural member ids
|
||||
let sp_member_ids = sp_get_memberids(&config.sp_key, &sp_user_id);
|
||||
|
||||
// get members
|
||||
let pk_members = pk_get_members(&config.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(())
|
||||
}
|
||||
|
||||
fn set_member(config_path: String, tf_members: Vec<String>) -> Result<(), &'static str> {
|
||||
let config: Config = match get_config(&config_path) {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Err(e)
|
||||
};
|
||||
|
||||
let system: System = get_system(&config_path);
|
||||
|
||||
let mut to_front: Vec<Member> = 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() == tf_members.len() {
|
||||
let fronters = get_fronters(&config.pk_key, &config.sp_key, &system, ForceFrom::None);
|
||||
|
||||
pk_set_fronters(&config.pk_key, &system, &to_front, &fronters);
|
||||
sp_set_fronters(&config.sp_key, &to_front, &fronters);
|
||||
} else {
|
||||
println!("One or more members were not found. Known members:\n--------------------------");
|
||||
let _ = memberlist(config_path);
|
||||
println!("--------------------------\nIf a member is missing from the system try running \"pluralsync sync\" to refresh the local database");
|
||||
return Err("Missing member");
|
||||
}
|
||||
|
||||
#[cfg(feature = "jlog")] {
|
||||
let mut names = Vec::new();
|
||||
for m in &to_front {
|
||||
names.push(String::from(&m.name));
|
||||
}
|
||||
let log_fronters = names.join(" || ");
|
||||
#[cfg(target_os = "windows")]
|
||||
std::process::Command::new("jlog").args(["info", format!("Switch registered: {}", log_fronters)]).output().expect("Logging error");
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
std::process::Command::new("jlog").arg("info").arg(format!("Switch registered: {}", log_fronters)).output().expect("Logging error");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_member(config_path: String, tf_members: Vec<String>) -> Result<(), &'static str> {
|
||||
let config: Config = match get_config(&config_path) {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Err(e)
|
||||
};
|
||||
|
||||
let system: System = get_system(&config_path);
|
||||
|
||||
let mut to_front: Vec<Member> = 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() {
|
||||
to_front.push(mem.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if to_front.len() == tf_members.len() {
|
||||
let fronters = get_fronters(&config.pk_key, &config.sp_key, &system, ForceFrom::None);
|
||||
|
||||
let mut aux: Vec<Member> = Vec::new();
|
||||
aux.append(&mut fronters.pk.clone());
|
||||
aux.append(&mut to_front);
|
||||
to_front = aux;
|
||||
|
||||
pk_set_fronters(&config.pk_key, &system, &to_front, &fronters);
|
||||
sp_set_fronters(&config.sp_key, &to_front, &fronters);
|
||||
|
||||
let _ = get(config_path, ForceFrom::None);
|
||||
|
||||
} else {
|
||||
println!("One or more members were not found. Known members:\n--------------------------");
|
||||
let _ = memberlist(config_path);
|
||||
println!("--------------------------\nIf a member is missing from the system try running \"pluralsync sync\" to refresh the local database");
|
||||
return Err("Missing member");
|
||||
}
|
||||
|
||||
#[cfg(feature = "jlog")] {
|
||||
let mut names = Vec::new();
|
||||
for m in &to_front {
|
||||
names.push(String::from(&m.name));
|
||||
}
|
||||
let log_fronters = names.join(" || ");
|
||||
#[cfg(target_os = "windows")]
|
||||
std::process::Command::new("jlog").args(["info", format!("Switch registered: {}", log_fronters)]).output().expect("Logging error");
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
std::process::Command::new("jlog").arg("info").arg(format!("Switch registered: {}", log_fronters)).output().expect("Logging error");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn memberlist(config_path: String) -> Result<(), &'static str> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
}
|
||||
|
||||
fn get(config_path: String, ff: ForceFrom) -> Result<Vec<String>, &'static str> {
|
||||
let config: Config = match get_config(&config_path) {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Err(e)
|
||||
};
|
||||
let sys = get_system(&config_path);
|
||||
|
||||
let f = get_fronters(&config.pk_key, &config.sp_key, &sys, ff);
|
||||
let mut names = Vec::new();
|
||||
for m in &f.pk {
|
||||
names.push(String::from(&m.name));
|
||||
}
|
||||
let fronters = names.join(" || ");
|
||||
println!("Currently fronting: {}", fronters);
|
||||
let _ = fs::write(format!("{}/.front", config_path), fronters);
|
||||
|
||||
Ok(names)
|
||||
}
|
||||
|
||||
#[cfg(feature = "avatar")]
|
||||
#[allow(unused_variables)]
|
||||
fn update_avatars(config_path: String, discord: bool, fedi: bool) -> Result<(), &'static str>{
|
||||
#[allow(unused_mut)]
|
||||
let mut config: Config = match get_config(&config_path) {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Err(e)
|
||||
};
|
||||
|
||||
let names = get(config_path, ForceFrom::None).unwrap();
|
||||
|
||||
|
||||
#[cfg(feature = "avatar")]
|
||||
match avatar_module(&config, &names) {
|
||||
Ok(_) => {
|
||||
#[cfg(feature = "discord")] {
|
||||
config.disc_module.enabled |= discord;
|
||||
discord_module(&config);
|
||||
}
|
||||
#[cfg(feature = "fedi")] {
|
||||
config.fedi_module.enabled |= fedi;
|
||||
fedi_module(&config);
|
||||
}
|
||||
},
|
||||
Err(e) => println!("{}", e),
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "avatar")]
|
||||
fn avatar_module(config: &Config, names: &Vec<String>) -> Result<(), &'static str>{
|
||||
if config.avatar_module.enabled {
|
||||
let mut whitelisted_names: Vec<String> = names.iter().map(|i| i.to_lowercase()).collect();
|
||||
let blacklist: Vec<String> = config.avatar_module.blacklist.iter().map(|i| i.to_lowercase()).collect();
|
||||
for name in names {
|
||||
if blacklist.contains(&name.to_lowercase()) {
|
||||
let index = whitelisted_names.iter().position(|x| *x == name.to_lowercase()).unwrap();
|
||||
whitelisted_names.remove(index);
|
||||
println!("{} blacklisted from avatar module", name);
|
||||
}
|
||||
}
|
||||
|
||||
let avatarnames = whitelisted_names.join("").to_lowercase() + ".png";
|
||||
|
||||
if Path::new(&config.avatar_module.avatar_output_path).exists() {
|
||||
let _ = remove_file(&config.avatar_module.avatar_output_path);
|
||||
}
|
||||
|
||||
if cfg!(target_os = "windows") {
|
||||
let cmdaug = format!("copy {}\\{} {}", &config.avatar_module.avatar_folder, avatarnames, &config.avatar_module.avatar_output_path);
|
||||
Command::new("cmd").args(["/C", &cmdaug]).output().expect("Avatar module error");
|
||||
} else {
|
||||
Command::new("sh").arg("-c").arg(format!("cp {}/{} {}", &config.avatar_module.avatar_folder, avatarnames, &config.avatar_module.avatar_output_path)).output().expect("Avatar module error");
|
||||
}
|
||||
if Path::new(&config.avatar_module.avatar_output_path).exists() {
|
||||
println!("Avatar module finished");
|
||||
return Ok(())
|
||||
} else {
|
||||
return Err("Avatar module failed")
|
||||
}
|
||||
} else {
|
||||
Err("Avatar mode disabled")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[cfg(feature = "discord")]
|
||||
fn discord_module(config: &Config) {
|
||||
if config.disc_module.enabled {
|
||||
if cfg!(target_os = "windows") {
|
||||
let mut c = Command::new("cmd").args(["/C", format!("{} {}", &config.disc_module.python_path, &config.disc_module.script_path).as_str()]).spawn().expect("Discord module error");
|
||||
let _ = c.wait().expect("Error");
|
||||
} else {
|
||||
let mut c = Command::new("sh").arg("-c").arg(format!("{} {}", &config.disc_module.python_path, &config.disc_module.script_path)).spawn().expect("Discord module error");
|
||||
let _ = c.wait().expect("Error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "fedi")]
|
||||
fn fedi_module(config: &Config) {
|
||||
if config.fedi_module.enabled {
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let form = Form::new().part("avatar", Part::bytes(fs::read(config.avatar_module.avatar_output_path.clone()).unwrap()).file_name("face.png").mime_str("image/png").unwrap());
|
||||
let rb = client
|
||||
.patch(config.fedi_module.instance.clone() + "/api/v1/accounts/update_credentials")
|
||||
.multipart(form)
|
||||
.header(USER_AGENT, "Pluralsync")
|
||||
.header(AUTHORIZATION, format!("Bearer {}", &config.fedi_module.token).as_str());
|
||||
|
||||
println!("Fedi module finished");
|
||||
match http_request(rb) {
|
||||
Ok(_) => (),
|
||||
Err(e) => println!("{}", e.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn pk_get_system(key: &str) -> Value {
|
||||
let url = format!("{}/systems/@me", PK_URL);
|
||||
|
||||
let res = http_get(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(url,key);
|
||||
let datas: Vec<Value> = serde_json::from_str(&res.unwrap()).unwrap();
|
||||
|
||||
return datas;
|
||||
}
|
||||
|
||||
fn pk_get_fronters(key: &str, sys: &System) -> Vec<Member> {
|
||||
let url = format!("{}/systems/{}/fronters", PK_URL, sys.pk_userid);
|
||||
|
||||
let res = http_get(url,key);
|
||||
let data: Value = serde_json::from_str(&res.unwrap()).unwrap();
|
||||
let memberdata = &data["members"].as_array();
|
||||
|
||||
let mut members: Vec<Member> = 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<Member>, fronters: &Fronters) {
|
||||
let url = format!("{}/systems/{}/switches", PK_URL, sys.pk_userid);
|
||||
|
||||
if to_front != &fronters.pk {
|
||||
let mut frontcodes = Vec::new();
|
||||
for tf in to_front {
|
||||
frontcodes.push(String::from(&tf.pk_id));
|
||||
}
|
||||
|
||||
let mut body: HashMap<&str, Vec<String>> = HashMap::new();
|
||||
body.insert("members", frontcodes);
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let rb = client
|
||||
.post(url)
|
||||
.json(&body)
|
||||
.header("content-type", "application/json; charset=utf-8")
|
||||
.header(USER_AGENT, "Pluralsync")
|
||||
.header(AUTHORIZATION, key);
|
||||
|
||||
match http_request(rb) {
|
||||
Ok(_) => (),
|
||||
Err(e) => println!("{}", e.to_string()),
|
||||
}
|
||||
|
||||
} else {
|
||||
println!("Members already fonting");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn sp_get_userid(key: &str) -> String {
|
||||
let url = format!("{}/me", SP_URL);
|
||||
|
||||
let res = http_get(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(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<Member> {
|
||||
let url = format!("{}/fronters", SP_URL);
|
||||
|
||||
let res = http_get(url,key);
|
||||
let datas: Vec<Value> = serde_json::from_str(&res.unwrap()).unwrap();
|
||||
|
||||
let mut members = 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;
|
||||
}
|
||||
|
||||
fn sp_get_frontids(key: &str, to_id: &Member) -> String {
|
||||
let url = format!("{}/fronters", SP_URL);
|
||||
|
||||
let res = http_get(url,key);
|
||||
let datas: Vec<Value> = serde_json::from_str(&res.unwrap()).unwrap();
|
||||
|
||||
for data in datas {
|
||||
let sp_f_id = &data["id"].as_str().unwrap();
|
||||
let sp_id = &data["content"]["member"].as_str().unwrap();
|
||||
if sp_id.to_string() == to_id.sp_id {
|
||||
return sp_f_id.to_string();
|
||||
}
|
||||
}
|
||||
return String::new();
|
||||
}
|
||||
|
||||
fn sp_set_fronters(key: &str, to_front: &Vec<Member>, fronters: &Fronters) {
|
||||
if to_front == &fronters.sp {
|
||||
println!("Members already fonting");
|
||||
return;
|
||||
}
|
||||
|
||||
for fronting_member in &fronters.sp {
|
||||
if !to_front.contains(&fronting_member) {
|
||||
let f_id = sp_get_frontids(&key, &fronting_member);
|
||||
|
||||
let url = format!("{}/frontHistory/{}", SP_URL, f_id);
|
||||
|
||||
#[derive (Serialize)]
|
||||
#[allow (non_snake_case)]
|
||||
struct SpRem {
|
||||
live: bool,
|
||||
endTime: u64
|
||||
}
|
||||
|
||||
let end_time = std::time::SystemTime::now().duration_since(std::time::SystemTime::UNIX_EPOCH).expect("wa").as_secs();
|
||||
let rem = SpRem {
|
||||
live: false,
|
||||
endTime: end_time * 1000
|
||||
};
|
||||
|
||||
let body = serde_json::to_string(&rem).expect("Error");
|
||||
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let rb = client
|
||||
.patch(url)
|
||||
.body(body)
|
||||
.header("content-type", "application/json; charset=utf-8")
|
||||
.header(USER_AGENT, "Pluralsync")
|
||||
.header(AUTHORIZATION, key);
|
||||
|
||||
match http_request(rb) {
|
||||
Ok(_) => (),
|
||||
Err(e) => println!("{}", e.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for tf_member in to_front {
|
||||
if !fronters.sp.contains(&tf_member) {
|
||||
let url = format!("{}/frontHistory", SP_URL);
|
||||
|
||||
#[derive (Serialize)]
|
||||
#[allow (non_snake_case)]
|
||||
struct SpAdd {
|
||||
member: String,
|
||||
custom: bool,
|
||||
live: bool,
|
||||
startTime: u64
|
||||
}
|
||||
|
||||
let start_time = std::time::SystemTime::now().duration_since(std::time::SystemTime::UNIX_EPOCH).expect("wa").as_secs();
|
||||
let rem = SpAdd {
|
||||
member: String::from(&tf_member.sp_id),
|
||||
custom: false,
|
||||
live: true,
|
||||
startTime: start_time * 1000
|
||||
};
|
||||
|
||||
let body = serde_json::to_string(&rem).expect("Error");
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let rb = client
|
||||
.post(url)
|
||||
.body(body)
|
||||
.header("content-type", "application/json; charset=utf-8")
|
||||
.header(USER_AGENT, "Pluralsync")
|
||||
.header(AUTHORIZATION, key);
|
||||
|
||||
match http_request(rb) {
|
||||
Ok(_) => (),
|
||||
Err(e) => println!("{}", e.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) -> Result<Config, &'static str> {
|
||||
let path = format!("{}/config.json", config_path);
|
||||
if Path::new(config_path).exists() {
|
||||
|
||||
let result = load_json(String::from(&path));
|
||||
|
||||
if result == Value::Null {
|
||||
let _ = fs::write(path, EXAMPLE_JSON);
|
||||
return Err("Config file missing, creating template in {path}");
|
||||
} else {
|
||||
let config: Config = serde_json::from_value(result).expect("Error unwrapping");
|
||||
return Ok(config);
|
||||
}
|
||||
} else {
|
||||
let _ = create_dir(config_path);
|
||||
let _ = fs::write(path, EXAMPLE_JSON);
|
||||
return Err("Directory {config_path} does not exist. Creating with template config");
|
||||
}
|
||||
}
|
||||
|
||||
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, sp_key: &str, sys: &System, ff: ForceFrom) -> Fronters {
|
||||
let mut fronters = Fronters::new(
|
||||
pk_get_fronters(pk_key, sys),
|
||||
sp_get_fronters(sp_key, sys)
|
||||
);
|
||||
|
||||
if fronters.pk != fronters.sp {
|
||||
match ff {
|
||||
ForceFrom::PK => {
|
||||
fronters.sp = fronters.pk.clone()
|
||||
}
|
||||
ForceFrom::SP => {
|
||||
fronters.pk = fronters.sp.clone()
|
||||
}
|
||||
ForceFrom::None => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fronters;
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn http_get(url: String, key: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||
let client = reqwest::Client::new();
|
||||
let rb = client
|
||||
.get(url)
|
||||
.header(USER_AGENT, "Pluralsync")
|
||||
.header(AUTHORIZATION, key);
|
||||
let res = rb.send()
|
||||
.await?
|
||||
.text()
|
||||
.await?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn http_request(rb: RequestBuilder) -> Result<String, Box<dyn std::error::Error>> {
|
||||
let res = rb.send()
|
||||
.await?
|
||||
.text()
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,15 @@
|
|||
use std::path::Path;
|
||||
use std::fs;
|
||||
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde_json::Value;
|
||||
|
||||
use color_eyre::eyre::{eyre, Result};
|
||||
|
||||
use crate::api::{pk, sp};
|
||||
use crate::utils::load_json;
|
||||
use crate::app::App;
|
||||
use crate::clap_ps::ForceFrom;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct System {
|
||||
|
@ -12,17 +23,129 @@ pub struct Member {
|
|||
pub pk_id: String,
|
||||
pub sp_id: String,
|
||||
pub name: String,
|
||||
pub alias: String
|
||||
pub aliases: Vec<String>
|
||||
}
|
||||
|
||||
impl System {
|
||||
pub fn get_system(app: &App, update: bool) -> Result<System>{
|
||||
|
||||
let sys_file_path = format!("{}/system.json", app.cfg_dir);
|
||||
|
||||
if Path::new(&sys_file_path).exists() && !update {
|
||||
let json_system: Value = load_json(&sys_file_path)?;
|
||||
let sys: System = serde_json::from_value(json_system)?;
|
||||
Ok(sys)
|
||||
} else {
|
||||
match Self::update_system(app) {
|
||||
Ok(s) => {
|
||||
Ok(s)
|
||||
},
|
||||
Err(e) => {
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_system(app: &App) -> Result<System> {
|
||||
|
||||
if let Some(conf) = &app.cfg {
|
||||
|
||||
// PluralKit
|
||||
let pk_sys = pk::get_system(&conf.pk_key)?;
|
||||
let pk_id = pk_sys["id"].as_str().unwrap();
|
||||
let pk_members = pk::get_members(&conf.pk_key, &pk_id)?;
|
||||
|
||||
// Simplyplural
|
||||
let sp_user_id = sp::get_user_id(&conf.sp_key)?;
|
||||
let sp_member_ids = sp::get_member_ids(&conf.sp_key, &sp_user_id)?;
|
||||
|
||||
// Get members
|
||||
let mut members: Vec<Member> = Vec::new();
|
||||
for pk_mem in pk_members {
|
||||
let mut m = Member {
|
||||
pk_id: pk_mem["id"].as_str().unwrap().to_string(),
|
||||
sp_id: String::new(),
|
||||
name: pk_mem["name"].as_str().unwrap().to_string(),
|
||||
aliases: Vec::new(),
|
||||
};
|
||||
|
||||
if pk_mem["display_name"].as_str() != None {
|
||||
m.aliases.push(pk_mem["display_name"].as_str().unwrap().to_string());
|
||||
}
|
||||
|
||||
m.sp_id = sp::get_member_id(&m, &sp_member_ids)?;
|
||||
|
||||
members.push(m);
|
||||
}
|
||||
|
||||
// Create system
|
||||
let sys = System {
|
||||
pk_userid: pk_id.to_string(),
|
||||
sp_userid: sp_user_id,
|
||||
members: members.clone(),
|
||||
};
|
||||
|
||||
let json = serde_json::to_string(&sys)?;
|
||||
fs::write(format!("{}/system.json", &app.cfg_dir), &json)?;
|
||||
|
||||
return Ok(sys);
|
||||
}
|
||||
|
||||
Err(eyre!("No configuration loaded!"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Fronters {
|
||||
pub sp: Vec<Member>,
|
||||
pub pk: Vec<Member>,
|
||||
pub sp: Vec<Member>,
|
||||
pub pk: Vec<Member>,
|
||||
}
|
||||
|
||||
impl Fronters {
|
||||
pub fn new(pk: Vec<Member>, sp: Vec<Member>) -> Self {
|
||||
Self {sp, pk}
|
||||
}
|
||||
pub fn new(pk: Vec<Member>, sp: Vec<Member>) -> Self {
|
||||
Self {sp, pk}
|
||||
}
|
||||
|
||||
pub fn get(app: &App, ff: ForceFrom) -> Result<Self> {
|
||||
if let Some(cfg) = &app.cfg {
|
||||
if let Some(sys) = &app.sys {
|
||||
let mut fronters = Fronters::new(
|
||||
pk::get_fronters(&cfg.pk_key, &sys)?,
|
||||
sp::get_fronters(&cfg.sp_key, &sys)?,
|
||||
);
|
||||
|
||||
if fronters.pk != fronters.sp {
|
||||
match ff {
|
||||
ForceFrom::SP => {
|
||||
fronters.pk = fronters.sp.clone();
|
||||
}
|
||||
_ => {
|
||||
fronters.sp = fronters.pk.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(fronters);
|
||||
}
|
||||
}
|
||||
Err(eyre!("System or configuration missing"))
|
||||
}
|
||||
|
||||
pub fn set(app: &App, to_front: Vec<Member>) -> Result<Fronters> {
|
||||
|
||||
if let Some(cfg) = &app.cfg {
|
||||
if let Some(sys) = &app.sys {
|
||||
if let Some(fronters) = &app.fronters {
|
||||
pk::set_fronters(&cfg.pk_key, &sys, &to_front, &fronters)?;
|
||||
sp::set_fronters(&cfg.sp_key, &to_front, &fronters)?;
|
||||
|
||||
return Ok(Fronters::new(to_front.clone(), to_front.clone()));
|
||||
}
|
||||
return Err(eyre!("Missing Fronters"));
|
||||
}
|
||||
return Err(eyre!("Missing System"));
|
||||
}
|
||||
Err(eyre!("Missing Config"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
use std::fs;
|
||||
use serde_json::Value;
|
||||
use color_eyre::eyre::Result;
|
||||
|
||||
#[cfg(feature = "avatar")]
|
||||
use {
|
||||
std::{
|
||||
process::Command,
|
||||
fs::remove_file,
|
||||
path::Path,
|
||||
},
|
||||
crate::{
|
||||
configdata::Config,
|
||||
app::App,
|
||||
clap_ps::ForceFrom,
|
||||
},
|
||||
color_eyre::eyre::eyre,
|
||||
};
|
||||
|
||||
pub fn load_json(file_path: &str) -> Result<Value> {
|
||||
|
||||
let file_data = fs::read_to_string(&file_path)?;
|
||||
let json_value: Value = serde_json::from_str(&file_data)?;
|
||||
|
||||
Ok(json_value)
|
||||
}
|
||||
|
||||
#[cfg(feature = "avatar")]
|
||||
pub fn update_avatars(app: &mut App, #[cfg(feature = "discord")] discord: bool, #[cfg(feature = "fedi")] fedi: bool) -> Result<()>{
|
||||
|
||||
if let Some(cfg) = app.cfg.clone() {
|
||||
let names = &app.get_front(ForceFrom::None)?; // TODO CHANGE INTO USING LOCAL FRONT
|
||||
|
||||
match avatar_module(&cfg, &names) {
|
||||
Ok(_) => {
|
||||
#[cfg(feature = "discord")] {
|
||||
config.disc_module.enabled |= discord;
|
||||
discord_module(&config);
|
||||
}
|
||||
#[cfg(feature = "fedi")] {
|
||||
config.fedi_module.enabled |= fedi;
|
||||
fedi_module(&config);
|
||||
}
|
||||
},
|
||||
Err(e) => println!("{}", e),
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
Err(eyre!("No config found"))
|
||||
}
|
||||
|
||||
#[cfg(feature = "avatar")]
|
||||
fn avatar_module(config: &Config, names: &Vec<String>) -> Result<(), &'static str>{
|
||||
if config.avatar_module.enabled {
|
||||
let mut whitelisted_names: Vec<String> = names.iter().map(|i| i.to_lowercase()).collect();
|
||||
let blacklist: Vec<String> = config.avatar_module.blacklist.iter().map(|i| i.to_lowercase()).collect();
|
||||
for name in names {
|
||||
if blacklist.contains(&name.to_lowercase()) {
|
||||
let index = whitelisted_names.iter().position(|x| *x == name.to_lowercase()).unwrap();
|
||||
whitelisted_names.remove(index);
|
||||
println!("{} blacklisted from avatar module", name);
|
||||
}
|
||||
}
|
||||
|
||||
let avatarnames = whitelisted_names.join("").to_lowercase() + ".png";
|
||||
|
||||
if Path::new(&config.avatar_module.avatar_output_path).exists() {
|
||||
let _ = remove_file(&config.avatar_module.avatar_output_path);
|
||||
}
|
||||
|
||||
if cfg!(target_os = "windows") {
|
||||
let cmdaug = format!("copy {}\\{} {}", &config.avatar_module.avatar_folder, avatarnames, &config.avatar_module.avatar_output_path);
|
||||
Command::new("cmd").args(["/C", &cmdaug]).output().expect("Avatar module error");
|
||||
} else {
|
||||
Command::new("sh").arg("-c").arg(format!("cp {}/{} {}", &config.avatar_module.avatar_folder, avatarnames, &config.avatar_module.avatar_output_path)).output().expect("Avatar module error");
|
||||
}
|
||||
if Path::new(&config.avatar_module.avatar_output_path).exists() {
|
||||
println!("Avatar module finished");
|
||||
Ok(())
|
||||
} else {
|
||||
Err("Avatar module failed")
|
||||
}
|
||||
} else {
|
||||
Err("Avatar mode disabled")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[cfg(feature = "discord")]
|
||||
fn discord_module(config: &Config) {
|
||||
if config.disc_module.enabled {
|
||||
if cfg!(target_os = "windows") {
|
||||
let mut c = Command::new("cmd").args(["/C", format!("{} {}", &config.disc_module.python_path, &config.disc_module.script_path).as_str()]).spawn().expect("Discord module error");
|
||||
let _ = c.wait().expect("Error");
|
||||
} else {
|
||||
let mut c = Command::new("sh").arg("-c").arg(format!("{} {}", &config.disc_module.python_path, &config.disc_module.script_path)).spawn().expect("Discord module error");
|
||||
let _ = c.wait().expect("Error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "fedi")]
|
||||
fn fedi_module(config: &Config) {
|
||||
if config.fedi_module.enabled {
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let form = Form::new().part("avatar", Part::bytes(fs::read(config.avatar_module.avatar_output_path.clone()).unwrap()).file_name("face.png").mime_str("image/png").unwrap());
|
||||
let rb = client
|
||||
.patch(config.fedi_module.instance.clone() + "/api/v1/accounts/update_credentials")
|
||||
.multipart(form)
|
||||
.header(USER_AGENT, "Pluralsync")
|
||||
.header(AUTHORIZATION, format!("Bearer {}", &config.fedi_module.token).as_str());
|
||||
|
||||
println!("Fedi module finished");
|
||||
match http_request(rb) {
|
||||
Ok(_) => (),
|
||||
Err(e) => println!("{}", e.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue