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