plex_mal_scrobbler/
lib.rs1use lazy_static::lazy_static;
2use rocket::{form::validate::Contains, serde::json::Json};
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::{fs::File, sync::Mutex};
6
7pub mod mal_api;
8
9lazy_static! {
10 pub static ref CONFIG_PATH: String = {
11 match home::home_dir() {
12 Some(path) => format!("{}/.config/plex-mal-scrobbler/config.yml", path.display()),
13 None => "/etc/plex-mal-scrobbler/config.yml".to_string(),
14 }
15 };
16 pub static ref CONFIG: Mutex<Config> = {
17 serde_yml::from_reader(
18 File::open(CONFIG_PATH.as_str())
19 .unwrap_or_else(|_| panic!("Couldn't find {}", CONFIG_PATH.as_str())),
20 )
21 .expect("Failed to parse the config.yml")
22 };
23}
24
25#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
26pub struct Config {
27 pub mal_client_id: String,
28 pub mal_access_token: Option<String>,
29 pub mal_refresh_token: Option<String>,
30 pub mal_token_expires_in: Option<i64>,
31 pub plex_users: Option<Vec<String>>,
32 pub plex_libraries: Option<Vec<String>>,
33 pub scrobble_last_ep: bool,
34 pub require_match: bool,
35 pub test: bool,
36 pub port: Option<u64>,
37}
38
39pub async fn scrobble(config: &Config, payload: &Json<Value>) {
40 if !check_filters(config, payload) {
41 return;
42 }
43
44 let anime_title = &payload["Metadata"]["grandparentTitle"]
46 .as_str()
47 .unwrap()
48 .to_string()
49 .to_lowercase();
50 let anime_episode = &payload["Metadata"]["index"].as_u64().unwrap();
52
53 let anime_list = mal_api::get_user_anime_list(config).await;
55
56 if let Some(anime) = anime_list.iter().find(|a| a.titles.contains(anime_title)) {
57 if (anime_episode != &anime.total_episodes || config.scrobble_last_ep)
59 && anime_episode > &anime.watched_episodes
60 {
61 mal_api::update_anime_details(config, &anime.id, &anime.total_episodes, anime_episode)
62 .await;
63 }
64 } else if !config.require_match && !anime_list.is_empty() {
65 let anime = &anime_list[0];
67
68 if (anime_episode != &anime.total_episodes || config.scrobble_last_ep)
69 && anime_episode > &anime.watched_episodes
70 {
71 mal_api::update_anime_details(config, &anime.id, &anime.total_episodes, anime_episode)
72 .await;
73 }
74 } else {
75 eprintln!("Currently watching anime list is empty");
76 }
77}
78
79fn check_filters(config: &Config, payload: &Json<Value>) -> bool {
80 if payload["event"].as_str().unwrap_or("") != "media.scrobble" {
82 return false;
83 }
84
85 if config.plex_users.is_some() {
87 let user = payload["Account"]["title"]
88 .as_str()
89 .unwrap_or("")
90 .to_string();
91
92 if !config.plex_users.contains(user) {
93 return false;
94 }
95 }
96
97 if config.plex_libraries.is_some() {
99 let library = payload["Metadata"]["librarySectionTitle"]
100 .as_str()
101 .unwrap_or("")
102 .to_string();
103
104 if !config.plex_libraries.contains(library) {
105 return false;
106 }
107 }
108 true
109}