Merge pull request 'Fedi support' (#1) from akkomadev into main

Slay queen
This commit is contained in:
lafresita 2024-01-14 01:00:45 +00:00
commit 974ef812a4
6 changed files with 118 additions and 44 deletions

26
Cargo.lock generated
View File

@ -437,6 +437,16 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mime_guess"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
dependencies = [
"mime",
"unicase",
]
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.7.1" version = "0.7.1"
@ -667,6 +677,7 @@ dependencies = [
"js-sys", "js-sys",
"log", "log",
"mime", "mime",
"mime_guess",
"native-tls", "native-tls",
"once_cell", "once_cell",
"percent-encoding", "percent-encoding",
@ -1009,6 +1020,15 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
[[package]]
name = "unicase"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
dependencies = [
"version_check",
]
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.13" version = "0.3.13"
@ -1047,6 +1067,12 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]] [[package]]
name = "want" name = "want"
version = "0.3.1" version = "0.3.1"

View File

@ -9,7 +9,7 @@ edition = "2021"
dirs = "5.0.1" dirs = "5.0.1"
serde = { version = "1.0.192", features = ["derive"] } serde = { version = "1.0.192", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
reqwest = { version = "0.11.22", features = ["json"] } reqwest = { version = "0.11.22", features = ["json", "multipart"] }
rofi = "0.3.0" rofi = "0.3.0"
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }

View File

@ -13,16 +13,21 @@ On the first run, if it does not exist, PluralSync will create a configuration d
{ {
"pk_key": "// Pluralkit token", "pk_key": "// Pluralkit token",
"sp_key": "// Simplplural token", "sp_key": "// Simplplural token",
"pfp_module": { "avatar_module": {
"enabled": false, "enabled": false,
"pfp_folder": "// Folder to grab profile pictures, they follow they member1member2...memberX.png format", "avatar_folder": "// Folder to grab profile pictures, they follow they member1member2...memberX.png format",
"pfp_output_path": "// Path for the copied selected pfp" "avatar_output_path": "// Path for the copied selected avatar"
}, },
"disc_module": { "disc_module": {
"enabled": false, "enabled": false,
"token": "// Discord user token", "token": "// Discord user token",
"python_path": "// Path to the python executable", "python_path": "// Path to the python executable",
"script_path": "// Path to updatepfp.py" "script_path": "// Path to updatediscordavatar.py"
},
"fedi_module": {
"enabled": false,
"instance" : "// Fedi instance url",
"token": "// Fedi bearer token"
} }
} }
#+end_src #+end_src
@ -43,17 +48,22 @@ Just fill it with PluralKit and SimplyPlural tokens associated to your account.
- *Memberlist* - Writes the known list of members based on the =system.json= file. - *Memberlist* - Writes the known list of members based on the =system.json= file.
** Modules ** Modules
*** PFP module *** Avatar module
Will get a profile picture from a folder based on the currently fronting members and will copy it to the desired location. I recommend something it's easy to have at hand like =~/face=. Will get a profile picture from a folder based on the currently fronting members and will copy it to the desired location. I recommend something it's easy to have at hand like =~/face=.
The pictures in the path should be named according to the front. For example, if Bob and Alice are fronting, the module will copy ={PATH}/bobalice.png= to the location, or if it's only Bob it'll be =bob.png=. The pictures in the path should be named according to the front. For example, if Bob and Alice are fronting, the module will copy ={PATH}/bobalice.png= to the location, or if it's only Bob it'll be =bob.png=.
Notice this will cover all combinations, if the main front is Alice the combination =alicebob.png= instead. I recommend the use of a python script to generate the possible combinations. Notice this will cover all combinations, if the main front is Alice the combination =alicebob.png= instead. I personally use a python script to generate my set.
*** Discord module *** Discord module
**Use of this module goes against discord terms of service. USE AT YOUR OWN RISK.** **Use of this module goes against discord terms of service. USE AT YOUR OWN RISK.**
Requires the =PFP module= to be enabled and the latest version of [[https://github.com/dolfies/discord.py-self/][discord.py-self]]. Requires the =avatar module= to be enabled and the latest version of [[https://github.com/dolfies/discord.py-self/][discord.py-self]].
Will change your discord avatar with the one selected by the PFP module. Will change your discord avatar with the one selected by the avatar module.
*** Fedi module
Should be compatible with Mastodon, Pleroma and Akkoma.
Requires the =avatar module= to be enabled. Will set the avatar of your fediverse account to the one selected by the avatar module.
* Installation * Installation
Just build with cargo and move or symlink the executable wherever is more comofortable for you. Just build with cargo and move or symlink the executable wherever is more comofortable for you.
@ -65,5 +75,6 @@ cargo build -r
* Future * Future
Given the nature of the project as a learning exercise, there are currently a set of features and improvements I'd like to add eventually. Given the nature of the project as a learning exercise, there are currently a set of features and improvements I'd like to add eventually.
- [X] Proper error handling (What I have currently is simply a mess) - [X] Proper error handling (What I have currently is simply a mess)
- [ ] Rofi and zenity support - [ ] Allow the user to set which front is used in cased of mismatch between PK and SP
- [ ] Proper way to handle front conflicts between PluralKit and SimplyPlural - [ ] Implement clap for command inputs
- [ ] Actually separate the modules as actual modules

View File

@ -1,15 +1,20 @@
{ {
"pk_key": "// Pluralkit token", "pk_key": "// Pluralkit token",
"sp_key": "// Simplplural token", "sp_key": "// Simplplural token",
"pfp_module": { "avatar_module": {
"enabled": false, "enabled": false,
"pfp_folder": "// Folder to grab profile pictures, they follow they member1member2.png format", "avatar_folder": "// Folder to grab profile pictures, they follow they member1member2...memberX.png format",
"pfp_output_path": "// Path for the copied selected pfp" "avatar_output_path": "// Path for the copied selected avatar"
}, },
"disc_module": { "disc_module": {
"enabled": false, "enabled": false,
"token": "// Discord user token", "token": "// Discord user token",
"python_path": "// Path to the python executable", "python_path": "// Path to the python executable",
"script_path": "// Path to updatepfp.py" "script_path": "// Path to updatediscordavatar.py"
},
"fedi_module": {
"enabled": false,
"instance" : "// Fedi instance url",
"token": "// Fedi bearer token"
} }
} }

View File

@ -5,6 +5,7 @@ use std::collections::HashMap;
use std::process::Command; use std::process::Command;
use reqwest::header::{USER_AGENT, AUTHORIZATION}; use reqwest::header::{USER_AGENT, AUTHORIZATION};
use reqwest::multipart::{Part, Form};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use serde_json::Value; use serde_json::Value;
@ -15,15 +16,16 @@ use dirs;
struct Config { struct Config {
pk_key: String, pk_key: String,
sp_key: String, sp_key: String,
pfp_module: PfpModule, avatar_module: AvatarModule,
disc_module: DiscModule disc_module: DiscModule,
fedi_module: FediModule,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
struct PfpModule { struct AvatarModule {
enabled: bool, enabled: bool,
pfp_folder: String, avatar_folder: String,
pfp_output_path: String, avatar_output_path: String,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
@ -34,6 +36,13 @@ struct DiscModule {
script_path: String, script_path: String,
} }
#[derive(Debug, Serialize, Deserialize)]
struct FediModule {
enabled: bool,
instance: String,
token: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
struct System { struct System {
pk_userid: String, pk_userid: String,
@ -65,19 +74,24 @@ const PK_URL: &str = "https://api.pluralkit.me/v2";
const SP_URL: &str = "https://api.apparyllis.com/v1"; const SP_URL: &str = "https://api.apparyllis.com/v1";
const EXAMPLE_JSON: &str = r#"{ const EXAMPLE_JSON: &str = r#"{
"pk_key": "// Pluralkit token", "pk_key": "// Pluralkit token",
"sp_key": "// Simplplural token", "sp_key": "// Simplplural token",
"pfp_module": { "avatar_module": {
"enabled": false, "enabled": false,
"pfp_folder": "// Folder to grab profile pictures, they follow they member1member2.png format", "avatar_folder": "// Folder to grab profile pictures, they follow they member1member2...memberX.png format",
"pfp_output_path": "// Path for the copied selected pfp" "avatar_output_path": "// Path for the copied selected avatar"
}, },
"disc_module": { "disc_module": {
"enabled": false, "enabled": false,
"token": "// Discord user token", "token": "// Discord user token",
"python_path": "// Path to the python executable", "python_path": "// Path to the python executable",
"script_path": "// Path to updatepfp.py" "script_path": "// Path to updatediscordavatar.py"
} },
"fedi_module": {
"enabled": false,
"instance" : "// Fedi instance url",
"token": "// Fedi bearer token"
}
}"#; }"#;
const HELP_STRING: &str = r#"PluralSync const HELP_STRING: &str = r#"PluralSync
@ -293,16 +307,16 @@ fn get(config_path: String) -> Result<(), &'static str> {
println!("Currently fronting: {}", fronters); println!("Currently fronting: {}", fronters);
let _ = fs::write(format!("{}/.front", config_path), fronters); let _ = fs::write(format!("{}/.front", config_path), fronters);
if config.pfp_module.enabled { if config.avatar_module.enabled {
let pfpnames = names.join("").to_lowercase() + ".png"; let avatarnames = names.join("").to_lowercase() + ".png";
if Path::new(&config.pfp_module.pfp_output_path).exists() { if Path::new(&config.avatar_module.avatar_output_path).exists() {
let _ = remove_file(&config.pfp_module.pfp_output_path); let _ = remove_file(&config.avatar_module.avatar_output_path);
} }
if cfg!(target_os = "windows") { if cfg!(target_os = "windows") {
let cmdaug = format!("copy {}\\{} {}", &config.pfp_module.pfp_folder, pfpnames, &config.pfp_module.pfp_output_path); let cmdaug = format!("copy {}\\{} {}", &config.avatar_module.avatar_folder, avatarnames, &config.avatar_module.avatar_output_path);
Command::new("cmd").args(["/C", &cmdaug]).output().expect("PFP module error"); Command::new("cmd").args(["/C", &cmdaug]).output().expect("Avatar module error");
if config.disc_module.enabled { if config.disc_module.enabled {
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 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");
@ -310,12 +324,16 @@ fn get(config_path: String) -> Result<(), &'static str> {
} }
} else { } else {
Command::new("sh").arg("-c").arg(format!("cp {}/{} {}", &config.pfp_module.pfp_folder, pfpnames, &config.pfp_module.pfp_output_path)).output().expect("PFP module error"); 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 config.disc_module.enabled { if config.disc_module.enabled {
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 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"); let _ = c.wait().expect("Error");
} }
if config.fedi_module.enabled {
let _ = http_patch_request_fedi(config.fedi_module.instance + "/api/v1/accounts/update_credentials", format!("Bearer {}", &config.fedi_module.token).as_str(), config.avatar_module.avatar_output_path);
}
} }
} }
@ -620,3 +638,17 @@ async fn http_patch_request(url: String, key: &str, body: String ) -> Result<(),
.await?; .await?;
Ok(()) Ok(())
} }
#[tokio::main]
async fn http_patch_request_fedi(url: String, key: &str, file_path: String ) -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::Client::new();
let form = Form::new().part("avatar", Part::bytes(fs::read(file_path).unwrap()).file_name("face.png").mime_str("image/png").unwrap());
let c = client
.patch(url)
.multipart(form)
.header(USER_AGENT, "Pluralsync")
.header(AUTHORIZATION, key);
let _res = c.send()
.await?;
Ok(())
}

View File

@ -13,10 +13,10 @@ json = json.load(f)
class MyClient(discord.Client): class MyClient(discord.Client):
async def on_ready(self): async def on_ready(self):
print('Logged on as', self.user) print('Logged on as', self.user)
pfp_path = json["pfp_module"]["pfp_output_path"] avatar_path = json["avatar_module"]["avatar_output_path"]
fp = open(pfp_path, 'rb') fp = open(avatar_path, 'rb')
pfp = fp.read() avatar = fp.read()
await self.user.edit(avatar=pfp) await self.user.edit(avatar=avatar)
print('Discord module finished') print('Discord module finished')
await self.close() await self.close()