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