1use scraper::{Html, Selector};
2
3use crate::{Genre, Quality};
4
5use super::model;
6
7#[derive(Debug)]
9pub struct Page {
10 pub current: u32,
12 pub of: u32,
14 pub total: u32,
16}
17
18impl Page {
19 fn create(current: u32, total: u32) -> Self {
31 let of = if total > 20 {
32 (total / 20) + (if !total.is_multiple_of(20) { 1 } else { 0 })
33 } else {
34 1
35 };
36
37 Self { current, of, total }
38 }
39}
40
41#[derive(Debug)]
45pub struct Response {
46 pub page: Page,
48 pub movies: Vec<model::Movie>,
50}
51
52impl Response {
53 pub(crate) fn create(html: &str, page: u32) -> crate::Result<Self> {
65 let document = Html::parse_document(html);
66
67 let total: u32 = document
68 .select(&Selector::parse("div.container h2 b")?)
69 .next()
70 .and_then(|value| value.text().next())
71 .and_then(|value| value.parse().ok())
72 .unwrap_or_default();
73
74 let mut movies = Vec::new();
75 if let Some(div) = document.select(&Selector::parse("section div.row")?).next() {
76 for line in div.select(&Selector::parse("div.browse-movie-wrap")?) {
77 let link = line
78 .select(&Selector::parse("a.browse-movie-link")?)
79 .next()
80 .and_then(|e| e.attr("href"))
81 .unwrap_or_default()
82 .to_string();
83
84 let image = line
85 .select(&Selector::parse("img")?)
86 .next()
87 .and_then(|e| e.attr("src"))
88 .unwrap_or_default()
89 .to_string();
90
91 let info = line
92 .text()
93 .filter(|&t| !t.trim().is_empty() && t != "View Details")
94 .collect::<Vec<_>>();
95
96 let rating = info.first().ok_or(crate::Error::MovieRatingError)?;
97 let rating = &rating[..1];
98 let rating: f32 = rating.parse()?;
99
100 let year: u32 = info.last().ok_or(crate::Error::MovieYearError)?.parse()?;
101
102 let name = info
103 .get(info.len() - 2)
104 .ok_or(crate::Error::MovieNameError)?
105 .trim()
106 .to_string();
107
108 let mut genres = Vec::new();
109 for &value in &info[1..info.len() - 2] {
110 let value: Genre = value.into();
111 genres.push(value);
112 }
113
114 movies.push(model::Movie::new(name, year, rating, genres, image, link));
115 }
116 }
117
118 Ok(Self {
119 page: Page::create(page, total),
120 movies,
121 })
122 }
123}
124
125#[derive(Debug)]
127pub struct Torrent {
128 pub quality: Quality,
130 pub size: String,
132 pub language: String,
134 pub runtime: String,
136 pub peers_seeds: String,
138 pub link: String,
140}
141
142impl Torrent {
143 pub(crate) fn new(
156 quality: &str,
157 size: &str,
158 language: &str,
159 runtime: &str,
160 peers_seeds: &str,
161 link: String,
162 ) -> Self {
163 Self {
164 quality: quality.into(),
165 size: size.to_string(),
166 language: language.to_string(),
167 runtime: runtime.to_string(),
168 peers_seeds: peers_seeds.to_string(),
169 link,
170 }
171 }
172
173 pub(crate) fn create(html: &str) -> crate::Result<Vec<Self>> {
181 let document = Html::parse_document(html);
182
183 let mut torrents = Vec::new();
184 if let Some(movie_tech_specs) = document
185 .select(&Selector::parse("div#movie-tech-specs")?)
186 .next()
187 {
188 let qualities = movie_tech_specs
189 .select(&Selector::parse("span.tech-quality")?)
190 .map(|line| line.text().collect::<Vec<_>>())
191 .collect::<Vec<_>>();
192
193 let data = movie_tech_specs
194 .select(&Selector::parse("div.tech-spec-info")?)
195 .map(|line| {
196 line.text()
197 .map(|t| t.trim())
198 .filter(|&t| Self::skip_useless_str(t))
199 .collect::<Vec<_>>()
200 })
201 .collect::<Vec<_>>();
202
203 if let Some(movie_info) = document
204 .select(&Selector::parse("div#movie-info p")?)
205 .next()
206 {
207 let data_len = data.len();
208 for (i, line) in movie_info.select(&Selector::parse("a")?).enumerate() {
209 let link = line.attr("href").unwrap_or_default().to_string();
210
211 if i < data_len {
212 let data = &data[i];
213 let qualities = &qualities[i];
214
215 let index = if !data[3].contains("R") { 3 } else { 4 };
216
217 torrents.push(Torrent::new(
218 qualities[0],
219 data[0],
220 data[2],
221 data[index],
222 data[index + 1],
223 link,
224 ));
225 }
226 }
227 }
228 }
229
230 Ok(torrents)
231 }
232
233 fn skip_useless_str(value: &str) -> bool {
237 !value.is_empty()
238 && value != "P/S"
239 && value != "Subtitles"
240 && value != "NR"
241 && !value.contains("fps")
242 }
243}