1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
use lazy_static::lazy_static;
use rocket::{form::validate::Contains, serde::json::Json};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::{fs::File, sync::Mutex};

pub mod mal_api;

lazy_static! {
    pub static ref CONFIG_PATH: String = {
        match home::home_dir() {
            Some(path) => format!("{}/.config/plex-mal-scrobbler/config.yml", path.display()),
            None => "/etc/plex-mal-scrobbler/config.yml".to_string(),
        }
    };
    pub static ref CONFIG: Mutex<Config> = {
        serde_yaml::from_reader(
            File::open(CONFIG_PATH.as_str())
                .unwrap_or_else(|_| panic!("Couldn't find {}", CONFIG_PATH.as_str())),
        )
        .expect("Failed to parse the config.yml")
    };
}

#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct Config {
    pub mal_client_id: String,
    pub mal_access_token: Option<String>,
    pub mal_refresh_token: Option<String>,
    pub mal_token_expires_in: Option<i64>,
    pub plex_users: Option<Vec<String>>,
    pub plex_libraries: Option<Vec<String>>,
    pub scrobble_last_ep: bool,
    pub test: bool,
    pub port: Option<u64>,
}

pub fn scrobble(config: &Config, payload: &Json<Value>) {
    if !check_filters(config, payload) {
        return;
    }

    // Get anime title
    let anime_title = &payload["Metadata"]["grandparentTitle"]
        .as_str()
        .unwrap()
        .to_string()
        .to_lowercase();
    // Get current anime episode number
    let anime_episode = &payload["Metadata"]["index"].as_u64().unwrap();

    // Get user currently watching anime list
    let anime_list = mal_api::get_user_anime_list(config);

    if let Some(anime) = anime_list.iter().find(|a| a.titles.contains(anime_title)) {
        // Found exact match from the synonyms
        if (anime_episode != &anime.total_episodes || config.scrobble_last_ep)
            && anime_episode > &anime.watched_episodes
        {
            mal_api::update_anime_details(config, &anime.id, &anime.total_episodes, anime_episode);
        }
    } else {
        // No match found, falling back to the latest updated one
        let anime = &anime_list[0];

        if (anime_episode != &anime.total_episodes || config.scrobble_last_ep)
            && anime_episode > &anime.watched_episodes
        {
            mal_api::update_anime_details(config, &anime.id, &anime.total_episodes, anime_episode);
        }
    }
}

fn check_filters(config: &Config, payload: &Json<Value>) -> bool {
    // Check if the webhook was about scrobbling
    if payload["event"].as_str().unwrap() != "media.scrobble" {
        return false;
    }

    // Check if user filters match
    if config.plex_users.is_some() {
        let user = payload["Account"]["title"].as_str().unwrap().to_string();

        if !config.plex_users.contains(user) {
            return false;
        }
    }

    // Check if library filters match
    if config.plex_libraries.is_some() {
        let library = payload["Metadata"]["librarySectionTitle"]
            .as_str()
            .unwrap()
            .to_string();

        if !config.plex_libraries.contains(library) {
            return false;
        }
    }
    true
}