• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

fertkir / prowlarr-telegram-client / 16918733619

12 Aug 2025 07:23PM UTC coverage: 33.252% (+2.4%) from 30.846%
16918733619

push

github

web-flow
Merge pull request #133 from fertkir/dependabot/cargo/minor-and-patch-63f0ba6a64

Bump warp from 0.3.7 to 0.4.1 in the minor-and-patch group

0 of 4 new or added lines in 2 files covered. (0.0%)

5 existing lines in 3 files now uncovered.

137 of 412 relevant lines covered (33.25%)

0.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/core/input_handler.rs
1
use std::fmt::Display;
2
use std::sync::Arc;
3

4
use crate::core::download_meta::{DownloadMeta, DownloadMetaProvider};
5
use crate::core::downloads_tracker::DownloadsTracker;
6
use crate::core::HandlingResult;
7
use crate::core::prowlarr::{ProwlarrClient, SearchResult};
8
use crate::core::torrent_meta::TorrentMeta;
9
use crate::core::traits::input::{Command, Destination, Input, ItemUuid, Locale, ReplyToMessage, SearchQuery, Source};
10
use crate::core::traits::search_result_serializer::SearchResultSerializer;
11
use crate::core::traits::sender::Sender;
12
use crate::core::traits::uuid_mapper::{MapperError, UuidMapper};
13

14
pub struct InputHandler {
15
    prowlarr: ProwlarrClient,
16
    uuid_mapper: Box<dyn UuidMapper<TorrentMeta>>,
17
    downloads_tracker: Arc<DownloadsTracker>,
18
    allowed_users: Vec<u64>,
19
    sender: Box<dyn Sender>,
20
    search_result_serializer: Box<dyn SearchResultSerializer>
21
}
22

23
const RESULTS_COUNT: usize = 10;
24

25
impl InputHandler {
26

27
    pub fn new(prowlarr: ProwlarrClient,
×
28
               uuid_mapper: Box<dyn UuidMapper<TorrentMeta>>,
29
               downloads_tracker: Arc<DownloadsTracker>,
30
               allowed_users: Vec<u64>,
31
               sender: Box<dyn Sender>,
32
               search_result_serializer: Box<dyn SearchResultSerializer>) -> InputHandler {
33
        InputHandler {
34
            prowlarr,
35
            uuid_mapper,
36
            downloads_tracker,
37
            allowed_users,
38
            sender,
39
            search_result_serializer,
40
        }
41
    }
42

43
    pub async fn handle(&self, input: Box<dyn Input>) -> HandlingResult {
×
44
        let source = input.get_source();
×
45
        let destination = input.get_destination();
×
46
        let locale = input.get_locale();
×
47
        let reply_to_message = input.get_reply_to_message();
×
48
        if self.allowed_users.is_empty() || self.allowed_users.contains(&source) {
×
49
            self.sender.send_progress_indication(destination).await?;
×
50
            match input.get_command() {
×
51
                Command::Search(query) => self.search(source, destination, reply_to_message, &locale, &query).await?,
×
52
                Command::Download(uuid) => self.download(source, destination, &locale, &uuid).await?,
×
53
                Command::GetLink(uuid) => self.link(source, destination, &locale, &uuid).await?,
×
54
                Command::Help => self.sender.send_plain_message(destination, &t!("help", locale = &locale)).await?,
×
55
            }
56
        }
57
        Ok(())
×
58
    }
59

60
    async fn search(&self,
×
61
                    source: Source,
62
                    destination: Destination,
63
                    reply_to_message: ReplyToMessage,
64
                    locale: &Locale,
65
                    query: &SearchQuery
66
    ) -> HandlingResult {
67
        log::info!("from {} | Received search request \"{}\"", source, query);
×
68
        match self.prowlarr.search(query).await {
×
69
            Ok(results) => {
×
70
                let first_n_sorted_results: Vec<SearchResult> = sorted_by_seeders(results)
×
71
                    .into_iter()
UNCOV
72
                    .take(RESULTS_COUNT)
×
73
                    .collect();
74
                let bot_uuids = self.uuid_mapper.put_all(first_n_sorted_results
×
UNCOV
75
                    .iter()
×
76
                    .map(|a| a.into())
×
77
                    .collect()).await;
×
78
                match bot_uuids {
×
79
                    Ok(bot_uuids) => {
×
80
                        let response = first_n_sorted_results
×
81
                            .iter()
82
                            .enumerate()
83
                            .map(|(index, search_result)|
×
84
                                self.search_result_serializer.serialize(search_result, &bot_uuids[index], locale))
×
85
                            .reduce(|acc, e| acc + &e);
×
86
                        match response {
×
87
                            None => {
88
                                self.sender.send_plain_reply(destination, reply_to_message, &t!("no_results", locale = &locale)).await?;
×
89
                                log::info!("  to {} | Sent \"No results\" response", destination);
×
90
                            }
91
                            Some(response) => {
×
92
                                self.sender.send_reply(destination, reply_to_message, &response).await?;
×
93
                                log::info!("  to {} | Sent search response \"{}\"", destination, to_digest(&response));
×
94
                            }
95
                        }
96
                    }
97
                    Err(err) => self.handle_mapper_error(destination, locale, err).await?,
×
98
                }
99
            }
100
            Err(err) => self.handle_prowlarr_error(destination, locale, err).await?,
×
101
        }
102
        Ok(())
×
103
    }
104

105
    async fn handle_prowlarr_error(&self,
×
106
                                   destination: Destination,
107
                                   locale: &Locale,
108
                                   err: impl Display) -> HandlingResult {
109
        log::error!("  to {} | Error when interacting with Prowlarr: {}", destination, err);
×
110
        self.sender.send_plain_message(destination, &t!("prowlarr_error", locale = &locale)).await
×
111
    }
112

113
    async fn handle_mapper_error(&self,
×
114
                                 destination: Destination,
115
                                 locale: &Locale,
116
                                 err: MapperError) -> HandlingResult {
117
        log::error!("  to {} | {}", destination, err);
×
118
        self.sender.send_plain_message(destination, &t!("mapper_error", locale = locale)).await
×
119
    }
120

121
    async fn download(&self, source: Source, destination: Destination, locale: &Locale, uuid: &ItemUuid) -> HandlingResult {
×
122
        log::info!("from {} | Received download request for {}", source, uuid);
×
123
        match self.uuid_mapper.get(uuid).await {
×
124
            Ok(torrent_data) => match torrent_data {
×
125
                None => self.link_not_found(destination, locale, uuid).await?,
×
126
                Some(meta) => {
×
127
                    match self.prowlarr.download(&meta.indexer_id, &meta.guid).await {
×
128
                        Ok(response) => {
×
129
                            if response.status().is_success() {
×
130
                                self.sender.send_plain_message(destination, &t!("sent_to_download", locale = &locale)).await?;
×
131
                                log::info!("  to {} | Sent {} for downloading", destination, meta);
×
132
                                match meta.get_torrent_hash(&self.prowlarr).await {
×
133
                                    Ok(hash) => self.downloads_tracker.add(hash, destination, locale.clone()),
×
134
                                    Err(err) => {
×
135
                                        log::error!("  to {} | {}", destination, err);
×
136
                                    }
137
                                };
138
                            } else {
139
                                log::error!("  to {} | Download response from Prowlarr wasn't successful: {} {}",
×
140
                                    destination, response.status(), response.text().await.unwrap_or_default());
141
                                self.sender.send_plain_message(destination, &t!("could_not_send_to_download", locale = &locale)).await?;
×
142
                            }
143
                        }
144
                        Err(err) => self.handle_prowlarr_error(destination, locale, err).await?
×
145
                    }
146
                }
147
            }
148
            Err(err) => self.handle_mapper_error(destination, locale, err).await?,
×
149
        }
150
        Ok(())
×
151
    }
152

153
    async fn link(&self, source: Source, destination: Destination, locale: &Locale, uuid: &ItemUuid) -> HandlingResult {
×
154
        log::info!("from {} | Received get link request for {}", source, uuid);
×
155
        match self.uuid_mapper.get(uuid).await {
×
156
            Ok(torrent_data) => match torrent_data {
×
157
                None => self.link_not_found(destination, locale, uuid).await?,
×
158
                Some(torrent_data) => {
×
159
                    if torrent_data.magnet_url.is_some() { // todo this code resembles TorrentMeta.get_torrent_hash()
×
160
                        self.sender.send_magnet(destination, torrent_data.magnet_url.as_ref().unwrap()).await?;
×
161
                        log::info!("  to {} | Sent magnet link for {} ", destination, &torrent_data);
×
162
                    } else if torrent_data.download_url.is_some() {
×
163
                        match self.prowlarr.get_download_meta(torrent_data.download_url.as_ref().unwrap()).await {
×
164
                            Ok(content) => {
×
165
                                match content {
×
166
                                    DownloadMeta::MagnetLink(link) => {
×
167
                                        self.sender.send_magnet(destination, &link).await?;
×
168
                                        log::info!("  to {} | Sent magnet link for {} ", destination, &torrent_data);
×
169
                                    }
170
                                    DownloadMeta::TorrentFile(file) => {
×
171
                                        self.sender.send_torrent_file(destination, &format!("{}.torrent", uuid), file).await?;
×
172
                                        log::info!("  to {} | Sent .torrent file for {} ", destination, &torrent_data);
×
173
                                    }
174
                                }
175
                            }
176
                            Err(err) => self.handle_prowlarr_error(destination, locale, err).await?,
×
177
                        }
178
                    } else {
179
                        log::warn!("  to {} | Neither magnet nor download link exist for torrent {}", destination, torrent_data);
×
180
                        self.sender.send_plain_message(destination, &t!("link_not_found", locale = &locale)).await?;
×
181
                    }
182
                }
183
            }
184
            Err(err) => self.handle_mapper_error(destination, locale, err).await?,
×
185
        }
186
        Ok(())
×
187
    }
188

189
    async fn link_not_found(&self, destination: Destination, locale: &Locale, uuid: &ItemUuid) -> HandlingResult {
×
190
        log::warn!("  to {} | Link for uuid {} not found", destination, &uuid);
×
191
        self.sender.send_plain_message(destination, &t!("link_not_found", locale = locale)).await?;
×
192
        Ok(())
×
193
    }
194
}
195

196
fn sorted_by_seeders(mut results: Vec<SearchResult>) -> Vec<SearchResult> {
×
197
    results.sort_unstable_by(|a, b| b.seeders.cmp(&a.seeders));
×
198
    results
×
199
}
200

201
fn to_digest(str: &str) -> String {
×
202
    str.char_indices()
×
203
        .map(|(i, _)| i)
×
204
        .nth(100)
205
        .map(|end| str[0..end].to_string())
×
206
        .unwrap_or(str.to_string())
×
207
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc