Skip to content

Commit

Permalink
Add Plex username filtering option
Browse files Browse the repository at this point in the history
  • Loading branch information
Hamuko committed Mar 22, 2024
1 parent a4371f7 commit 6c825db
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 2 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ You can customise the anime title and episode number matching logic for anifunne

**Important:** These overrides are currently stored in-memory only and will disappear once anifunnel is terminated. You will need to redo any applicable overrides after starting anifunnel up again.

### Username filtering

anifunnel processes events for all Plex users by default. If you are using a multi-user Plex instance, you can limit processing of webhook events to a single user with the `--plex-user` argument / `ANILIST_PLEX_USER` environment variable.

## Disclaimer

This project is not associated or affiliated with Plex or Anilist in any way or form.
1 change: 1 addition & 0 deletions src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ pub mod state {
pub struct Global {
pub multi_season: bool,
pub token: String,
pub plex_user: Option<String>,
pub user: anilist::User,
pub title_overrides: RwLock<TitleOverrides>,
pub episode_offsets: RwLock<EpisodeOverrides>,
Expand Down
49 changes: 47 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ struct AnifunnelArgs {
/// Match against all Plex library seasons. May not accurately find matches.
#[arg(long, env = "ANIFUNNEL_MULTI_SEASON")]
multi_season: bool,

/// Only process updates from a specific Plex username.
#[clap(long, env = "ANILIST_PLEX_USER")]
plex_user: Option<String>
}

#[get("/admin")]
Expand Down Expand Up @@ -104,6 +108,16 @@ async fn scrobble(
return "NO OP";
}

// Check possible Plex username restriction.
if let Some(plex_user) = &state.plex_user {
if plex_user == &webhook.account.name {
debug!("Update matches Plex username restriction '{}'", plex_user);
} else {
info!("Ignoring update for Plex user '{}'", webhook.account.name);
return "NO OP";
}
}

if let Ok(media_list_entries) = anilist::get_watching_list(&state.token, &state.user).await {
let title_overrides = state.title_overrides.read().await;
let matched_media_list = match title_overrides.get(&webhook.metadata.title) {
Expand Down Expand Up @@ -161,6 +175,7 @@ async fn main() {

let state = data::state::Global {
multi_season: args.multi_season,
plex_user: args.plex_user,
token: args.anilist_token,
user: user,
title_overrides: RwLock::new(data::state::TitleOverrides::new()),
Expand Down Expand Up @@ -211,6 +226,7 @@ mod test {
fn build_client() -> Client {
let state = data::state::Global {
multi_season: false,
plex_user: None,
token: String::from("A"),
user: anilist::User {
id: 1,
Expand Down Expand Up @@ -308,13 +324,42 @@ mod test {
.body(
"payload={\"event\": \"media.scrobble\", \"Metadata\": {\
\"type\": \"episode\", \"grandparentTitle\": \"Onii-chan wa Oshimai!\", \
\"parentIndex\": 1, \"index\": 2}}",
\"parentIndex\": 1, \"index\": 2}, \"Account\": {\"title\": \"yukikaze\"}}",
)
.dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.into_string().unwrap(), "OK")
}

#[test_case("yukikaze", "OK" ; "correct username")]
#[test_case("shiranui", "NO OP" ; "incorrect username")]
fn scrobble_username_filter(plex_user: &str, expected_response: &str) {
let state = data::state::Global {
multi_season: false,
plex_user: Some(String::from(plex_user)),
token: String::from("A"),
user: anilist::User {
id: 1,
name: String::from("A"),
},
title_overrides: RwLock::new(data::state::TitleOverrides::new()),
episode_offsets: RwLock::new(data::state::EpisodeOverrides::new()),
};
let rocket = rocket::build().manage(state).mount("/", routes![scrobble]);
let client = Client::tracked(rocket).expect("valid rocket instance");
let response = client
.post(uri!(scrobble))
.header(ContentType::Form)
.body(
"payload={\"event\": \"media.scrobble\", \"Metadata\": {\
\"type\": \"episode\", \"grandparentTitle\": \"Onii-chan wa Oshimai!\", \
\"parentIndex\": 1, \"index\": 2}, \"Account\": {\"title\": \"yukikaze\"}}",
)
.dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.into_string().unwrap(), expected_response)
}

#[test]
fn scrobble_non_actionable() {
let client = build_client();
Expand All @@ -324,7 +369,7 @@ mod test {
.body(
"payload={\"event\": \"library.new\", \"Metadata\": {\
\"type\": \"episode\", \"grandparentTitle\": \"Onii-chan wa Oshimai!\", \
\"parentIndex\": 1, \"index\": 2}}",
\"parentIndex\": 1, \"index\": 2}, \"Account\": {\"title\": \"yukikaze\"}}",
)
.dispatch();
assert_eq!(response.status(), Status::Ok);
Expand Down
17 changes: 17 additions & 0 deletions src/plex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ use serde::Deserialize;
pub struct Webhook {
event: String,

#[serde(rename = "Account")]
pub account: WebhookAccount,

#[serde(rename = "Metadata")]
pub metadata: WebhookMetadata,
}
Expand All @@ -17,6 +20,12 @@ impl Webhook {
}
}

#[derive(Debug, Deserialize)]
pub struct WebhookAccount {
#[serde(rename = "title")]
pub name: String,
}

#[derive(Debug, Deserialize)]
pub struct WebhookMetadata {
#[serde(rename = "type")]
Expand All @@ -40,6 +49,7 @@ mod tests {
fn webhook_actionable() {
let webhook = Webhook {
event: String::from("media.scrobble"),
account: WebhookAccount { name: String::from("yukikaze") },
metadata: WebhookMetadata {
media_type: String::from("episode"),
title: String::from("Onii-chan wa Oshimai!"),
Expand All @@ -55,6 +65,7 @@ mod tests {
fn webhook_actionable_first_episode() {
let webhook = Webhook {
event: String::from("media.scrobble"),
account: WebhookAccount { name: String::from("yukikaze") },
metadata: WebhookMetadata {
media_type: String::from("episode"),
title: String::from("Onii-chan wa Oshimai!"),
Expand All @@ -70,6 +81,7 @@ mod tests {
fn webhook_actionable_music() {
let webhook = Webhook {
event: String::from("media.scrobble"),
account: WebhookAccount { name: String::from("yukikaze") },
metadata: WebhookMetadata {
media_type: String::from("track"),
title: String::from("Onii-chan wa Oshimai!"),
Expand All @@ -85,6 +97,7 @@ mod tests {
fn webhook_actionable_playback() {
let webhook = Webhook {
event: String::from("media.play"),
account: WebhookAccount { name: String::from("yukikaze") },
metadata: WebhookMetadata {
media_type: String::from("episode"),
title: String::from("Onii-chan wa Oshimai!"),
Expand All @@ -100,6 +113,7 @@ mod tests {
fn webhook_actionable_second_season() {
let webhook = Webhook {
event: String::from("media.scrobble"),
account: WebhookAccount { name: String::from("yukikaze") },
metadata: WebhookMetadata {
media_type: String::from("episode"),
title: String::from("Kidou Senshi Gundam: Suisei no Majo"),
Expand All @@ -115,6 +129,7 @@ mod tests {
fn webhook_actionable_second_season_multi_season() {
let webhook = Webhook {
event: String::from("media.scrobble"),
account: WebhookAccount { name: String::from("yukikaze") },
metadata: WebhookMetadata {
media_type: String::from("episode"),
title: String::from("Kidou Senshi Gundam: Suisei no Majo"),
Expand All @@ -130,6 +145,7 @@ mod tests {
fn webhook_actionable_special() {
let webhook = Webhook {
event: String::from("media.scrobble"),
account: WebhookAccount { name: String::from("yukikaze") },
metadata: WebhookMetadata {
media_type: String::from("episode"),
title: String::from("Bakemonogatari"),
Expand All @@ -145,6 +161,7 @@ mod tests {
fn webhook_actionable_special_multi_season() {
let webhook = Webhook {
event: String::from("media.scrobble"),
account: WebhookAccount { name: String::from("yukikaze") },
metadata: WebhookMetadata {
media_type: String::from("episode"),
title: String::from("Bakemonogatari"),
Expand Down

0 comments on commit 6c825db

Please sign in to comment.