autopulse_service/settings/targets/
sonarr.rs1use crate::settings::path_filter::PathFilter;
2use crate::settings::rewrite::Rewrite;
3use crate::settings::targets::TargetProcess;
4use autopulse_database::models::ScanEvent;
5use autopulse_utils::get_url;
6use reqwest::header;
7use serde::{Deserialize, Serialize};
8use std::{collections::HashMap, path::Path};
9use tracing::error;
10
11use super::{Request, RequestBuilderPerform};
12
13#[derive(Serialize, Deserialize, Clone)]
14pub struct Sonarr {
15 pub url: String,
17 pub token: String,
19 pub rewrite: Option<Rewrite>,
21 #[serde(default)]
23 pub filter: PathFilter,
24 #[serde(default)]
26 pub request: Request,
27}
28
29#[derive(Deserialize, Debug)]
30struct SonarrSeries {
31 id: i64,
32 path: String,
33}
34
35#[derive(Serialize)]
36#[serde(rename_all = "camelCase")]
37struct RefreshSeries {
38 series_id: i64,
39}
40
41#[derive(Serialize)]
42#[serde(tag = "name")]
43#[serde(rename_all = "PascalCase")]
44enum Command {
45 RefreshSeries(RefreshSeries),
46}
47
48impl Sonarr {
49 fn get_client(&self) -> anyhow::Result<reqwest::Client> {
50 let mut headers = header::HeaderMap::new();
51
52 headers.insert("X-Api-Key", self.token.parse()?);
53 headers.insert("Accept", "application/json".parse()?);
54
55 self.request
56 .client_builder(headers)
57 .build()
58 .map_err(Into::into)
59 }
60
61 async fn get_series(&self, evs: &[&ScanEvent]) -> anyhow::Result<Vec<(i64, Vec<String>)>> {
62 let client = self.get_client()?;
63 let url = get_url(&self.url)?.join("api/v3/series")?;
64 let mut to_be_refreshed: HashMap<i64, Vec<String>> = HashMap::new();
65
66 let res = client.get(url).perform().await?;
67
68 let series = res.json::<Vec<SonarrSeries>>().await?;
69
70 for ev in evs {
71 let ev_path = ev.get_path(&self.rewrite);
72 let ev_path = Path::new(&ev_path);
73
74 for s in &series {
75 let series_path = Path::new(&s.path);
76 if ev_path.starts_with(series_path) {
77 to_be_refreshed.entry(s.id).or_default().push(ev.id.clone());
78 break;
79 }
80 }
81 }
82
83 Ok(to_be_refreshed.into_iter().collect())
84 }
85
86 async fn refresh_series(&self, series_id: i64) -> anyhow::Result<()> {
87 let client = self.get_client()?;
88 let url = get_url(&self.url)?.join("api/v3/command")?;
89 let payload = Command::RefreshSeries(RefreshSeries { series_id });
90
91 client.post(url).json(&payload).perform().await.map(|_| ())
92 }
93}
94
95impl TargetProcess for Sonarr {
96 async fn process(&self, evs: &[&ScanEvent]) -> anyhow::Result<Vec<String>> {
97 let mut succeeded = Vec::new();
98
99 let series = self.get_series(evs).await?;
100
101 for (series_id, ev_ids) in series {
102 match self.refresh_series(series_id).await {
103 Ok(()) => {
104 succeeded.extend(ev_ids);
105 }
106 Err(e) => {
107 error!("failed to refresh series: {}", e);
108 }
109 }
110 }
111
112 Ok(succeeded)
113 }
114}