1use lazy_static::lazy_static;
2use regex::Regex;
3use scraper::{selectable::Selectable, Html, Selector};
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7use crate::{
8 env::{self, EnvVar, SecretConfig},
9 error::AniRustError,
10 proxy::{load_proxies, Proxy},
11 servers::{AnimeServer, EpisodeType, MegaCloudServer, ServerExtractedInfo, StreamTapeServer},
12 utils::{anirust_error_vec_to_string, get_ajax_curl, get_curl},
13};
14
15lazy_static! {
16 static ref TRENDING_SELECTOR: Selector =
17 Selector::parse("#anime-trending #trending-home .swiper-wrapper .swiper-slide").unwrap();
18 static ref LATEST_EPISODES_SELECTOR: Selector = Selector::parse(
19 "#main-content .block_area_home:nth-of-type(1) .tab-content .film_list-wrap .flw-item"
20 )
21 .unwrap();
22 static ref TOP_UPCOMING_SELECTOR: Selector = Selector::parse(
23 "#main-content .block_area_home:nth-of-type(3) .tab-content .film_list-wrap .flw-item"
24 )
25 .unwrap();
26 static ref GENRES_SELECTOR: Selector = Selector::parse(
27 "#main-sidebar .block_area.block_area_sidebar.block_area-genres .sb-genre-list li"
28 )
29 .unwrap();
30 static ref SPOTLIGHT_SELECTOR: Selector =
31 Selector::parse("#slider .swiper-wrapper .swiper-slide").unwrap();
32 static ref FEATURED_SELECTOR: Selector =
33 Selector::parse("#anime-featured .row div:nth-of-type(1) .anif-block-ul ul li").unwrap();
34 static ref TOP_10_SELECTOR: Selector =
35 Selector::parse("#main-sidebar .block_area-realtime [id^=\"top-viewed-\"]").unwrap();
36 static ref A_TO_Z_SELECTOR: Selector = Selector::parse("#main-wrapper div div.page-az-wrap section div.tab-content div div.film_list-wrap .flw-item").unwrap();
37 static ref NAVIGATION_SELECTOR: Selector = Selector::parse("div.pre-pagination.mt-5.mb-5 > nav > ul > li:last-child a").unwrap();
38 static ref ABOUT_ANIME_SELECTOR: Selector = Selector::parse("#ani_detail .ani_detail-stage .container .anis-content").unwrap();
39 static ref MOST_POPULAR_ANIME_SELECTOR: Selector = Selector::parse("#main-sidebar .block_area.block_area_sidebar.block_area-realtime:nth-of-type(2) .anif-block-ul ul li").unwrap();
40 static ref RELATED_ANIME_SELECTOR: Selector = Selector::parse("#main-sidebar .block_area.block_area_sidebar.block_area-realtime:nth-of-type(1) .anif-block-ul ul li").unwrap();
41 static ref RECOMMENDED_ANIME_SELECTOR: Selector = Selector::parse("#main-content .block_area.block_area_category .tab-content .flw-item").unwrap();
42 static ref SEASONS_SELECTOR: Selector = Selector::parse(".os-list a.os-item").unwrap();
43 static ref EPISODE_SELECTOR: Selector = Selector::parse(".detail-infor-content .ss-list a").unwrap();
44 static ref CATEGORY_SELECTOR: Selector = Selector::parse("#main-content .tab-content .film_list-wrap .flw-item").unwrap();
45 static ref SEARCH_SELECTOR: Selector = Selector::parse("#main-content .tab-content .film_list-wrap .flw-item").unwrap();
46 static ref EPISODE_NO_SELECTOR: Selector = Selector::parse(".server-notice strong").unwrap();
47 static ref EPISODE_SUB_SELECTOR: Selector = Selector::parse(".ps_-block.ps_-block-sub.servers-sub .ps__-list .server-item").unwrap();
48 static ref EPISODE_DUB_SELECTOR: Selector = Selector::parse(".ps_-block.ps_-block-sub.servers-dub .ps__-list .server-item").unwrap();
49}
50
51#[derive(Debug)]
52pub struct HiAnimeRust {
53 domains: Vec<String>,
54 proxies: Vec<Proxy>,
55 secret: Option<SecretConfig>,
56}
57
58#[derive(Serialize, Deserialize, Debug, Clone)]
59pub struct HomeInfo {
60 pub trending: Vec<MinimalAnime>,
61 pub latest_episodes: Vec<Anime>,
62 pub top_upcoming_animes: Vec<Anime>,
63 pub spotlight_animes: Vec<SpotlightAnime>,
64 pub featured: FeaturedAnime,
65 pub top_10_animes: Top10PeriodRankedAnime,
66 pub genres: Vec<String>,
67}
68
69#[derive(Serialize, Deserialize, Debug, Clone)]
70pub struct CategoryInfo {
71 pub total_pages: u32,
72 pub current_page: u32,
73 pub has_next_page: bool,
74 pub animes: Vec<Anime>,
75 pub top_10_animes: Top10PeriodRankedAnime,
76 pub genres: Vec<String>,
77}
78
79#[derive(Serialize, Deserialize, Debug, Clone)]
80pub struct SearchInfo {
81 pub total_pages: u32,
82 pub current_page: u32,
83 pub has_next_page: bool,
84 pub animes: Vec<Anime>,
85 pub most_popular_animes: Vec<SideBarAnimes>,
86 pub genres: Vec<String>,
87}
88
89#[derive(Serialize, Deserialize, Debug, Clone)]
90pub struct EpisodesInfo {
91 pub total_episodes: u32,
92 pub episodes: Vec<AnimeEpisode>,
93}
94
95#[derive(Serialize, Deserialize, Debug, Clone)]
96pub struct AtoZ {
97 pub has_next_page: bool,
98 pub current_page: u32,
99 pub total_pages: u32,
100 pub animes: Vec<Anime>,
101}
102
103#[derive(Serialize, Deserialize, Debug, Clone)]
104pub struct MinimalAnime {
105 pub id: String,
106 pub title: String,
107 pub image: String,
108}
109
110#[derive(Serialize, Deserialize, Debug, Clone)]
111pub struct Anime {
112 pub id: String,
113 pub title: String,
114 pub subs: u32,
115 pub dubs: u32,
116 pub eps: u32,
117 pub duration: String,
118 pub rating: String,
119 pub image: String,
120}
121
122#[derive(Serialize, Deserialize, Debug, Clone)]
123pub struct SpotlightAnime {
124 pub id: String,
125 pub title: String,
126 pub subs: u32,
127 pub dubs: u32,
128 pub eps: u32,
129 pub duration: String,
130 pub rank: u32,
131 pub image: String,
132 pub description: String,
133 pub category: String,
134 pub released_day: String,
135 pub quality: String,
136}
137
138#[derive(Serialize, Deserialize, Debug, Clone)]
139pub struct Top10Anime {
140 pub id: String,
141 pub title: String,
142 pub subs: u32,
143 pub dubs: u32,
144 pub eps: u32,
145 pub rank: u32,
146 pub image: String,
147}
148
149#[derive(Serialize, Deserialize, Debug, Clone)]
150pub struct SideBarAnimes {
151 pub id: String,
152 pub title: String,
153 pub subs: u32,
154 pub dubs: u32,
155 pub eps: u32,
156 pub category: String,
157 pub image: String,
158}
159
160#[derive(Serialize, Deserialize, Debug, Clone)]
161pub struct AnimeSeason {
162 pub id: String,
163 pub title: String,
164 pub anime_title: String,
165 pub image: String,
166 pub is_current: bool,
167}
168
169#[derive(Serialize, Deserialize, Debug, Clone)]
170pub struct AnimeEpisode {
171 pub id: String,
172 pub episode_no: u32,
173 pub title: String,
174 pub is_filler: bool,
175}
176
177#[derive(Serialize, Deserialize, Debug, Clone)]
178pub struct FeaturedAnime {
179 pub top_airing_animes: Vec<MinimalAnime>,
180 pub most_popular_animes: Vec<MinimalAnime>,
181 pub most_favorite_animes: Vec<MinimalAnime>,
182 pub latest_completed_animes: Vec<MinimalAnime>,
183}
184
185#[derive(Serialize, Deserialize, Debug, Clone)]
186pub struct Top10PeriodRankedAnime {
187 pub day: Vec<Top10Anime>,
188 pub week: Vec<Top10Anime>,
189 pub month: Vec<Top10Anime>,
190}
191
192#[derive(Serialize, Deserialize, Debug, Clone)]
193pub struct AboutAnime {
194 pub id: String,
195 pub mal_id: u32,
196 pub al_id: u32,
197 pub anime_id: u32,
198 pub title: String,
199 pub description: String,
200 pub image: String,
201 pub rating: String,
202 pub category: String,
203 pub duration: String,
204 pub quality: String,
205 pub subs: u32,
206 pub dubs: u32,
207 pub eps: u32,
208 pub japanese: String,
209 pub synonyms: String,
210 pub aired: String,
211 pub premiered: String,
212 pub status: String,
213 pub mal_score: String,
214 pub studios: Vec<String>,
215 pub producers: Vec<String>,
216 pub genres: Vec<String>,
217 pub most_popular_animes: Vec<SideBarAnimes>,
218 pub related_animes: Vec<SideBarAnimes>,
219 pub recommended_animes: Vec<Anime>,
220 pub seasons: Vec<AnimeSeason>,
221}
222
223#[derive(Serialize, Deserialize, Debug, Clone)]
224pub struct Server {
225 pub server_name: String,
226 pub server_id: u32,
227 pub data_id: u32,
228}
229
230#[derive(Serialize, Deserialize, Debug, Clone)]
231pub struct ServerInfo {
232 pub episode_no: u32,
233 pub sub: Vec<Server>,
234 pub dub: Vec<Server>,
235}
236
237trait HasClass {
238 fn has_class(&self, class_name: &str) -> bool;
239}
240
241impl HasClass for scraper::ElementRef<'_> {
242 fn has_class(&self, class_name: &str) -> bool {
243 self.value()
244 .attr("class")
245 .map(|class| class.split_whitespace().any(|c| c == class_name))
246 .unwrap_or(false)
247 }
248}
249
250impl HiAnimeRust {
251 pub async fn new(secret: Option<SecretConfig>) -> Self {
252 let secret_clone = initialize_secret(secret);
253 let domain_list = EnvVar::HIANIME_DOMAINS.get_config();
254 let domains: Vec<String> = if domain_list.is_empty() {
255 vec!["https://aniwatchtv.to".to_string()]
256 } else {
257 domain_list
258 .split(',')
259 .map(|s| s.trim().to_string())
260 .collect()
261 };
262
263 let proxies = match load_proxies().await {
264 Ok(p) => p,
265 Err(e) => {
266 eprintln!("Failed to load proxies: {:?}", e);
267 Vec::new()
268 }
269 };
270
271 HiAnimeRust {
272 domains,
273 proxies,
274 secret: secret_clone,
275 }
276 }
277
278 pub async fn scrape_home(&self) -> Result<HomeInfo, AniRustError> {
279 let mut error_vec = vec![];
280 let mut curl = String::new();
281
282 for domain in &self.domains {
283 let url = format!("{}/home", domain);
284
285 match get_curl(&url, &self.proxies).await {
286 Ok(curl_string) => {
287 curl = curl_string;
288 break;
289 }
290 Err(e) => {
291 error_vec.push(Some(e));
292 }
293 }
294 }
295
296 if curl.is_empty() {
297 let error_string: String = anirust_error_vec_to_string(error_vec);
298 return Err(AniRustError::UnknownError(error_string));
299 }
300
301 let document = Html::parse_document(&curl);
302
303 let trending = extract_minimal_anime(&document, &TRENDING_SELECTOR);
304 let latest_episodes = extract_anime_data(&document, &LATEST_EPISODES_SELECTOR);
305 let top_upcoming_animes = extract_anime_data(&document, &TOP_UPCOMING_SELECTOR);
306 let spotlight_animes = extract_spotlight_anime_data(&document, &SPOTLIGHT_SELECTOR);
307 let genres = extract_genres(&document, &GENRES_SELECTOR);
308 let top_10_animes = extract_top_10(&document, &TOP_10_SELECTOR);
309
310 let (top_airing_animes, most_popular_animes, most_favorite_animes, latest_completed_animes) =
311 extract_featured_anime(&document, &FEATURED_SELECTOR);
312 let featured = FeaturedAnime {
313 top_airing_animes,
314 most_popular_animes,
315 most_favorite_animes,
316 latest_completed_animes,
317 };
318
319 Ok(HomeInfo {
320 trending,
321 latest_episodes,
322 top_upcoming_animes,
323 spotlight_animes,
324 featured,
325 top_10_animes,
326 genres,
327 })
328 }
329
330 pub async fn scrape_atoz(&self, page_no: u32) -> Result<AtoZ, AniRustError> {
331 let mut error_vec = vec![];
332 let mut curl = String::new();
333
334 for domain in &self.domains {
335 let url = format!("{}/az-list?page={}", domain, page_no);
336
337 match get_curl(&url, &self.proxies).await {
338 Ok(curl_string) => {
339 curl = curl_string;
340 break;
341 }
342 Err(e) => {
343 error_vec.push(Some(e));
344 }
345 }
346 }
347
348 if curl.is_empty() {
349 let error_string: String = anirust_error_vec_to_string(error_vec);
350 return Err(AniRustError::UnknownError(error_string));
351 }
352
353 let document = Html::parse_document(&curl);
354
355 let animes = extract_anime_data(&document, &A_TO_Z_SELECTOR);
356
357 let total_pages = get_last_page_no(&document);
358 let current_page = page_no;
359 let has_next_page = page_no != total_pages;
360
361 Ok(AtoZ {
362 has_next_page,
363 current_page,
364 total_pages,
365 animes,
366 })
367 }
368
369 pub async fn scrape_about_anime(&self, id: &str) -> Result<AboutAnime, AniRustError> {
370 let mut error_vec = vec![];
371 let mut curl = String::new();
372
373 for domain in &self.domains {
374 let url = format!("{}/{}", domain, id);
375
376 match get_curl(&url, &self.proxies).await {
377 Ok(curl_string) => {
378 curl = curl_string;
379 break;
380 }
381 Err(e) => {
382 error_vec.push(Some(e));
383 }
384 }
385 }
386
387 if curl.is_empty() {
388 let error_string: String = anirust_error_vec_to_string(error_vec);
389 return Err(AniRustError::UnknownError(error_string));
390 }
391
392 let document = Html::parse_document(&curl);
393 let about = extract_anime_about_info(&document, &ABOUT_ANIME_SELECTOR);
394 Ok(about)
395 }
396
397 pub async fn scrape_category(
398 &self,
399 category: &str,
400 page_no: u32,
401 ) -> Result<CategoryInfo, AniRustError> {
402 let mut error_vec = vec![];
403 let mut curl = String::new();
404
405 for domain in &self.domains {
406 let url = format!("{}/{}?page={}", domain, category, page_no);
407
408 match get_curl(&url, &self.proxies).await {
409 Ok(curl_string) => {
410 curl = curl_string;
411 break;
412 }
413 Err(e) => {
414 error_vec.push(Some(e));
415 }
416 }
417 }
418
419 if curl.is_empty() {
420 let error_string: String = anirust_error_vec_to_string(error_vec);
421 return Err(AniRustError::UnknownError(error_string));
422 }
423
424 let document = Html::parse_document(&curl);
425 let animes = extract_anime_data(&document, &CATEGORY_SELECTOR);
426 let top_10_animes = extract_top_10(&document, &TOP_10_SELECTOR);
427 let genres = extract_genres(&document, &GENRES_SELECTOR);
428 let total_pages = get_last_page_no(&document);
429 let current_page = page_no;
430 let has_next_page = page_no != total_pages;
431
432 Ok(CategoryInfo {
433 total_pages,
434 current_page,
435 has_next_page,
436 animes,
437 top_10_animes,
438 genres,
439 })
440 }
441
442 pub async fn scrape_search(
443 &self,
444 query: &str,
445 page_no: u32,
446 ) -> Result<SearchInfo, AniRustError> {
447 let mut error_vec = vec![];
448 let mut curl = String::new();
449
450 for domain in &self.domains {
451 let url = format!("{}/search?keyword={}&page={}", domain, query, page_no);
452
453 match get_curl(&url, &self.proxies).await {
454 Ok(curl_string) => {
455 curl = curl_string;
456 break;
457 }
458 Err(e) => {
459 error_vec.push(Some(e));
460 }
461 }
462 }
463
464 if curl.is_empty() {
465 let error_string: String = anirust_error_vec_to_string(error_vec);
466 return Err(AniRustError::UnknownError(error_string));
467 }
468
469 let document = Html::parse_document(&curl);
470 let most_popular_selector = Selector::parse(
471 "#main-sidebar .block_area.block_area_sidebar.block_area-realtime .anif-block-ul ul li",
472 )
473 .unwrap();
474
475 let animes = extract_anime_data(&document, &SEARCH_SELECTOR);
476 let most_popular_animes = extract_side_bar_animes(&document, &most_popular_selector);
477 let total_pages = get_last_page_no(&document);
478 let current_page = page_no;
479 let has_next_page = page_no != total_pages;
480 let genres = extract_genres(&document, &GENRES_SELECTOR);
481
482 Ok(SearchInfo {
483 total_pages,
484 current_page,
485 has_next_page,
486 animes,
487 most_popular_animes,
488 genres,
489 })
490 }
491
492 pub async fn scrape_episodes(&self, id: &str) -> Result<EpisodesInfo, AniRustError> {
493 let mut error_vec = vec![];
494 let mut curl = String::new();
495 let anime_id = id.split('-').last().unwrap();
496
497 for domain in &self.domains {
498 let url = format!("{}/ajax/v2/episode/list/{}", domain, anime_id);
499
500 match get_ajax_curl(&url, "html").await {
501 Ok(curl_string) => {
502 curl = curl_string;
503 break;
504 }
505 Err(e) => {
506 error_vec.push(Some(e));
507 }
508 }
509 }
510
511 if curl.is_empty() {
512 let error_string: String = anirust_error_vec_to_string(error_vec);
513 return Err(AniRustError::UnknownError(error_string));
514 }
515
516 let document = Html::parse_document(&curl);
517
518 let episodes = extract_anime_episode(&document, &EPISODE_SELECTOR);
519 let total_episodes = episodes.len() as u32;
520
521 Ok(EpisodesInfo {
522 total_episodes,
523 episodes,
524 })
525 }
526
527 pub async fn scrape_servers(&self, id: &str) -> Result<ServerInfo, AniRustError> {
528 let mut error_vec = vec![];
529 let mut curl = String::new();
530 let episode_id = id.split("ep=").last().unwrap_or_default();
531
532 for domain in &self.domains {
533 let url = format!(
534 "{}/ajax/v2/episode/servers?episodeId={}",
535 domain, episode_id
536 );
537
538 match get_ajax_curl(&url, "html").await {
539 Ok(curl_string) => {
540 curl = curl_string;
541 break;
542 }
543 Err(e) => {
544 error_vec.push(Some(e));
545 }
546 }
547 }
548
549 if curl.is_empty() {
550 let error_string: String = anirust_error_vec_to_string(error_vec);
551 return Err(AniRustError::UnknownError(error_string));
552 }
553
554 let document = Html::parse_document(&curl);
555
556 let episode_str = document
557 .select(&EPISODE_NO_SELECTOR)
558 .next()
559 .map(|e| e.text().collect::<String>().trim().to_string())
560 .unwrap_or_default();
561 let last_part = episode_str.split_whitespace().last().unwrap_or_default();
562
563 let episode_no = last_part.parse::<u32>().unwrap_or_default();
564 let sub = extract_episode_servers(&document, &EPISODE_SUB_SELECTOR);
565 let dub = extract_episode_servers(&document, &EPISODE_DUB_SELECTOR);
566
567 Ok(ServerInfo {
568 episode_no,
569 sub,
570 dub,
571 })
572 }
573
574 pub async fn scrape_episode_server_source(
575 &self,
576 id: &str,
577 episode_type: EpisodeType,
578 anime_server: Option<AnimeServer>,
579 ) -> Result<ServerExtractedInfo, AniRustError> {
580 let server_list = self.scrape_servers(id).await?;
581 let mut error_vec = vec![];
582 let mut link = String::new();
583
584 let mut server_id: u32 = 0;
585 let mut data_id: u32 = 0;
586
587 match episode_type {
588 EpisodeType::Dub => {
589 update_server_id(&mut server_id, &mut data_id, server_list.dub, anime_server)
590 }
591 _ => update_server_id(&mut server_id, &mut data_id, server_list.sub, anime_server),
592 }
593
594 for domain in &self.domains {
595 let url = format!("{}/ajax/v2/episode/sources?id={}", domain, data_id);
596 println!("{:?}", url);
597
598 match get_ajax_curl(&url, "link").await {
599 Ok(curl_string) => {
600 link = curl_string;
601 break;
602 }
603 Err(e) => {
604 error_vec.push(Some(e));
605 }
606 }
607 }
608
609 if link.is_empty() {
610 let error_string: String = anirust_error_vec_to_string(error_vec);
611 return Err(AniRustError::UnknownError(error_string));
612 }
613
614 let server_info = match server_id {
615 3 => StreamTapeServer::extract(&link, &self.proxies).await?,
616 4 => MegaCloudServer::extract(&link, &self.proxies).await?,
617 5 => MegaCloudServer::extract(&link, &self.proxies).await?,
618 1 => MegaCloudServer::extract(&link, &self.proxies).await?,
619 _ => MegaCloudServer::extract(&link, &self.proxies).await?,
620 };
621
622 Ok(server_info)
623 }
624}
625
626fn extract_anime_data(document: &Html, selector: &Selector) -> Vec<Anime> {
627 document
628 .select(selector)
629 .map(|element| {
630 let id = element
631 .select(&Selector::parse(".film-name .dynamic-name").unwrap())
632 .next()
633 .and_then(|e| e.value().attr("href"))
634 .map(|s| s.trim_start_matches('/').to_string())
635 .unwrap_or_default();
636
637 let title = element
638 .select(&Selector::parse(".film-name .dynamic-name").unwrap())
639 .next()
640 .map(|e| e.text().collect::<String>().trim().to_string())
641 .unwrap_or_default();
642
643 let subs = element
644 .select(&Selector::parse(".film-poster .tick-sub").unwrap())
645 .next()
646 .and_then(|e| e.text().collect::<String>().parse::<u32>().ok())
647 .unwrap_or_default();
648
649 let dubs = element
650 .select(&Selector::parse(".film-poster .tick-dub").unwrap())
651 .next()
652 .and_then(|e| e.text().collect::<String>().parse::<u32>().ok())
653 .unwrap_or_default();
654
655 let eps = element
656 .select(&Selector::parse(".film-poster .tick-eps").unwrap())
657 .next()
658 .and_then(|e| e.text().collect::<String>().parse::<u32>().ok())
659 .unwrap_or_default();
660
661 let duration = element
662 .select(&Selector::parse(".fd-infor .fdi-duration").unwrap())
663 .next()
664 .map(|e| e.text().collect::<String>().trim().to_string())
665 .unwrap_or_default();
666
667 let rating = element
668 .select(&Selector::parse(".film-poster .tick-rate").unwrap())
669 .next()
670 .map(|e| e.text().collect::<String>().trim().to_string())
671 .unwrap_or_default();
672
673 let image = element
674 .select(&Selector::parse(".film-poster .film-poster-img").unwrap())
675 .next()
676 .and_then(|e| e.value().attr("data-src").map(|s| s.to_string()))
677 .unwrap_or_default();
678
679 Anime {
680 id,
681 title,
682 subs,
683 dubs,
684 eps,
685 duration,
686 rating,
687 image,
688 }
689 })
690 .collect()
691}
692
693fn extract_spotlight_anime_data(document: &Html, selector: &Selector) -> Vec<SpotlightAnime> {
694 document
695 .select(selector)
696 .map(|element| {
697 let id = element
698 .select(&Selector::parse(".deslide-item-content .desi-buttons a").unwrap())
699 .next()
700 .and_then(|e| e.value().attr("href"))
701 .map(|s| s.trim_start_matches('/').to_string())
702 .unwrap_or_default()
703 .split("/")
704 .last()
705 .unwrap_or_default()
706 .to_string();
707
708 let title = element
709 .select(
710 &Selector::parse(".deslide-item-content .desi-head-title.dynamic-name")
711 .unwrap(),
712 )
713 .next()
714 .map(|e| e.text().collect::<String>().trim().to_string())
715 .unwrap_or_default();
716
717 let rank = element
718 .select(&Selector::parse(".deslide-item-content .desi-sub-text").unwrap())
719 .next()
720 .map(|e| e.text().collect::<String>().trim().to_string())
721 .unwrap_or_default()
722 .split_whitespace()
723 .next()
724 .and_then(|s| s.trim_start_matches('#').parse::<u32>().ok())
725 .unwrap_or_default();
726
727 let image = element
728 .select(
729 &Selector::parse(".deslide-cover .deslide-cover-img .film-poster-img").unwrap(),
730 )
731 .next()
732 .and_then(|e| e.value().attr("data-src").map(|s| s.to_string()))
733 .unwrap_or_default();
734
735 let description = element
736 .select(&Selector::parse(".deslide-item-content .desi-description").unwrap())
737 .next()
738 .map(|e| e.text().collect::<String>().trim().to_string())
739 .unwrap_or_default();
740
741 let extra_info: Vec<String> = element
742 .select(&Selector::parse(".deslide-item-content .sc-detail .scd-item").unwrap())
743 .map(|e| e.text().collect::<String>().trim().to_string())
744 .collect();
745
746 let eps = extra_info
747 .get(4)
748 .and_then(|s| {
749 s.split_whitespace()
750 .map(|s| s.parse().ok())
751 .collect::<Vec<_>>()
752 .get(2)
753 .copied()
754 })
755 .flatten()
756 .unwrap_or_default();
757
758 let subs = extra_info
759 .get(4)
760 .and_then(|s| {
761 s.split_whitespace()
762 .map(|s| s.parse().ok())
763 .collect::<Vec<_>>()
764 .first()
765 .copied()
766 })
767 .flatten()
768 .unwrap_or_default();
769
770 let dubs = extra_info
771 .get(4)
772 .and_then(|s| {
773 s.split_whitespace()
774 .map(|s| s.parse().ok())
775 .collect::<Vec<_>>()
776 .get(1)
777 .copied()
778 })
779 .flatten()
780 .unwrap_or_default();
781
782 SpotlightAnime {
783 id,
784 title,
785 rank,
786 image,
787 description,
788 subs,
789 dubs,
790 eps,
791 duration: extra_info.get(1).cloned().unwrap_or_default(),
792 quality: extra_info.get(3).cloned().unwrap_or_default(),
793 category: extra_info.first().cloned().unwrap_or_default(),
794 released_day: extra_info.get(2).cloned().unwrap_or_default(),
795 }
796 })
797 .collect()
798}
799
800fn extract_minimal_anime(document: &Html, selector: &Selector) -> Vec<MinimalAnime> {
801 document
802 .select(selector)
803 .map(|element| {
804 let id = element
805 .select(&Selector::parse(".item .film-poster").unwrap())
806 .next()
807 .and_then(|e| e.value().attr("href"))
808 .map(|href| href.trim_start_matches('/'))
809 .map(|s| s.to_string())
810 .unwrap_or_default();
811
812 let title = element
813 .select(&Selector::parse(".item .number .film-title.dynamic-name").unwrap())
814 .next()
815 .map(|e| e.text().collect::<String>().trim().to_string())
816 .unwrap_or_default();
817
818 let image = element
819 .select(&Selector::parse(".item .film-poster .film-poster-img").unwrap())
820 .next()
821 .and_then(|e| e.value().attr("data-src"))
822 .map(|s| s.trim().to_string())
823 .unwrap_or_default();
824
825 MinimalAnime { id, title, image }
826 })
827 .collect()
828}
829
830fn extract_featured_anime(
831 document: &Html,
832 selector: &Selector,
833) -> (
834 Vec<MinimalAnime>,
835 Vec<MinimalAnime>,
836 Vec<MinimalAnime>,
837 Vec<MinimalAnime>,
838) {
839 let id_selector = Selector::parse(".film-detail .film-name .dynamic-name").unwrap();
840 let image_selector = Selector::parse(".film-poster a .film-poster-img").unwrap();
841
842 let res: Vec<MinimalAnime> = document
843 .select(selector)
844 .map(|element| {
845 let id = element
846 .select(&id_selector)
847 .next()
848 .and_then(|e| e.value().attr("href"))
849 .map(|href| href.trim_start_matches('/').to_string())
850 .unwrap_or_default();
851
852 let title = element
853 .select(&id_selector)
854 .next()
855 .map(|e| e.text().collect::<String>().trim().to_string())
856 .unwrap_or_default();
857
858 let image = element
859 .select(&image_selector)
860 .next()
861 .and_then(|e| e.value().attr("data-src"))
862 .map(|s| s.trim().to_string())
863 .unwrap_or_default();
864
865 MinimalAnime { id, title, image }
866 })
867 .collect();
868
869 let top_airing_animes = res[0..5].to_vec();
870 let most_popular_animes = res[5..10].to_vec();
871 let most_favorite_animes = res[10..15].to_vec();
872 let latest_completed_animes = res[15..20].to_vec();
873
874 (
875 top_airing_animes,
876 most_popular_animes,
877 most_favorite_animes,
878 latest_completed_animes,
879 )
880}
881
882fn extract_top_10(document: &Html, selector: &Selector) -> Top10PeriodRankedAnime {
883 let (day, week, month) = document
884 .select(selector)
885 .filter_map(|element| element.value().attr("id"))
886 .map(|id| id.split('-').last().unwrap_or("").trim().to_string())
887 .fold(
888 (vec![], vec![], vec![]),
889 |(mut day, mut week, mut month), period_type| {
890 match period_type.as_str() {
891 "week" => week.extend(extract_top_10_by_period_type(document, "week")),
892 "month" => month.extend(extract_top_10_by_period_type(document, "month")),
893 _ => day.extend(extract_top_10_by_period_type(document, "day")),
894 }
895 (day, week, month)
896 },
897 );
898
899 Top10PeriodRankedAnime { day, week, month }
900}
901
902fn extract_top_10_by_period_type(document: &Html, period_type: &str) -> Vec<Top10Anime> {
903 let selector_format = format!("#top-viewed-{} ul li", period_type);
904 let selector = Selector::parse(&selector_format).unwrap();
905
906 document
907 .select(&selector)
908 .map(|element| {
909 let id = element
910 .select(&Selector::parse(".film-detail .film-name .dynamic-name").unwrap())
911 .next()
912 .and_then(|e| e.value().attr("href"))
913 .map(|s| s.trim_start_matches('/').to_string())
914 .unwrap_or_default();
915
916 let title = element
917 .select(&Selector::parse(".film-detail .film-name .dynamic-name").unwrap())
918 .next()
919 .map(|e| e.text().collect::<String>().trim().to_string())
920 .unwrap_or_default();
921
922 let rank = element
923 .select(&Selector::parse(".film-number span").unwrap())
924 .next()
925 .map(|e| e.text().collect::<String>().trim().to_string())
926 .and_then(|e| e.parse::<u32>().ok())
927 .unwrap_or_default();
928
929 let subs = element
930 .select(&Selector::parse(".film-detail .fd-infor .tick-item.tick-sub").unwrap())
931 .next()
932 .and_then(|e| e.text().collect::<String>().parse::<u32>().ok())
933 .unwrap_or_default();
934
935 let dubs = element
936 .select(&Selector::parse(".film-detail .fd-infor .tick-item.tick-dub").unwrap())
937 .next()
938 .and_then(|e| e.text().collect::<String>().parse::<u32>().ok())
939 .unwrap_or_default();
940
941 let eps = element
942 .select(&Selector::parse(".film-detail .fd-infor .tick-item.tick-eps").unwrap())
943 .next()
944 .and_then(|e| e.text().collect::<String>().parse::<u32>().ok())
945 .unwrap_or_default();
946
947 let image = element
948 .select(&Selector::parse(".film-poster .film-poster-img").unwrap())
949 .next()
950 .and_then(|e| e.value().attr("data-src").map(|s| s.to_string()))
951 .unwrap_or_default();
952
953 Top10Anime {
954 id,
955 title,
956 image,
957 subs,
958 dubs,
959 eps,
960 rank,
961 }
962 })
963 .collect()
964}
965
966fn extract_anime_about_info(document: &Html, selector: &Selector) -> AboutAnime {
967 let play_button_selector = Selector::parse(".anisc-detail .film-buttons a.btn-play").unwrap();
968 let name_selector = Selector::parse(".anisc-detail .film-name.dynamic-name").unwrap();
969 let rating_selector = Selector::parse(".film-stats .tick .tick-pg").unwrap();
970 let quality_selector = Selector::parse(".film-stats .tick .tick-quality").unwrap();
971 let subs_selector = Selector::parse(".film-stats .tick .tick-sub").unwrap();
972 let dubs_selector = Selector::parse(".film-stats .tick .tick-dub").unwrap();
973 let eps_selector = Selector::parse(".film-stats .tick .tick-eps").unwrap();
974 let image_selector = Selector::parse(".anisc-poster .film-poster .film-poster-img").unwrap();
975 let description_selector = Selector::parse(".anisc-detail .film-description .text").unwrap();
976 let tick_selector = Selector::parse(".film-stats .tick").unwrap();
977 let json_script_selector = Selector::parse("#syncData").unwrap();
978 let more_info_selector = Selector::parse(
979 "#ani_detail .ani_detail-stage .container .anis-content .anisc-info .item-title",
980 )
981 .unwrap();
982 let genres_selector = Selector::parse(
983 "#ani_detail .ani_detail-stage .container .anis-content .anisc-info .item-list",
984 )
985 .unwrap();
986
987 let mut about_anime = AboutAnime {
988 id: String::new(),
989 mal_id: 0,
990 anime_id: 0,
991 al_id: 0,
992 title: String::new(),
993 description: String::new(),
994 image: String::new(),
995 category: String::new(),
996 rating: String::new(),
997 quality: String::new(),
998 duration: String::new(),
999 subs: 0,
1000 dubs: 0,
1001 eps: 0,
1002 japanese: String::new(),
1003 synonyms: String::new(),
1004 aired: String::new(),
1005 premiered: String::new(),
1006 status: String::new(),
1007 mal_score: String::new(),
1008 studios: vec![],
1009 producers: vec![],
1010 genres: vec![],
1011 most_popular_animes: vec![],
1012 related_animes: vec![],
1013 recommended_animes: vec![],
1014 seasons: vec![],
1015 };
1016
1017 document.select(selector).for_each(|element| {
1018 about_anime.id = element
1019 .select(&play_button_selector)
1020 .next()
1021 .and_then(|e| e.value().attr("href"))
1022 .map(|s| s.split('/').last().unwrap_or("").to_string())
1023 .unwrap_or_default();
1024
1025 about_anime.title = element
1026 .select(&name_selector)
1027 .next()
1028 .map(|e| e.text().collect::<String>().trim().to_string())
1029 .unwrap_or_default();
1030
1031 about_anime.rating = element
1032 .select(&rating_selector)
1033 .next()
1034 .map(|e| e.text().collect::<String>().trim().to_string())
1035 .unwrap_or_default();
1036
1037 about_anime.quality = element
1038 .select(&quality_selector)
1039 .next()
1040 .map(|e| e.text().collect::<String>().trim().to_string())
1041 .unwrap_or_default();
1042
1043 about_anime.subs = element
1044 .select(&subs_selector)
1045 .next()
1046 .and_then(|e| e.text().collect::<String>().parse::<u32>().ok())
1047 .unwrap_or_default();
1048
1049 about_anime.dubs = element
1050 .select(&dubs_selector)
1051 .next()
1052 .and_then(|e| e.text().collect::<String>().parse::<u32>().ok())
1053 .unwrap_or_default();
1054
1055 about_anime.eps = element
1056 .select(&eps_selector)
1057 .next()
1058 .and_then(|e| e.text().collect::<String>().parse::<u32>().ok())
1059 .unwrap_or_default();
1060
1061 about_anime.image = element
1062 .select(&image_selector)
1063 .next()
1064 .and_then(|e| e.value().attr("src").map(|s| s.to_string()))
1065 .unwrap_or_default();
1066
1067 about_anime.description = element
1068 .select(&description_selector)
1069 .next()
1070 .map(|e| e.text().collect::<String>().trim().to_string())
1071 .unwrap_or_default();
1072
1073 if let Some(tick) = element.select(&tick_selector).next() {
1074 let text = tick
1075 .text()
1076 .collect::<String>()
1077 .replace('\n', " ")
1078 .trim()
1079 .to_string();
1080
1081 let mut parts = text.split_whitespace().rev();
1082 about_anime.category = parts.nth(1).unwrap_or("").to_string();
1083 about_anime.duration = parts.next().unwrap_or("").to_string();
1084 }
1085
1086 let json_text = document
1087 .select(&json_script_selector)
1088 .next()
1089 .map(|script| script.text().collect::<String>())
1090 .unwrap_or_default();
1091
1092 if let Ok(json) = serde_json::from_str::<Value>(&json_text) {
1093 about_anime.anime_id = json
1094 .get("anime_id")
1095 .and_then(Value::as_str)
1096 .unwrap_or("")
1097 .parse::<u32>()
1098 .unwrap_or_default();
1099
1100 about_anime.mal_id = json
1101 .get("mal_id")
1102 .and_then(Value::as_str)
1103 .unwrap_or("")
1104 .parse::<u32>()
1105 .unwrap_or_default();
1106
1107 about_anime.al_id = json
1108 .get("anilist_id")
1109 .and_then(Value::as_str)
1110 .unwrap_or("")
1111 .parse::<u32>()
1112 .unwrap_or_default();
1113 }
1114 });
1115
1116 document.select(&more_info_selector).for_each(|element| {
1117 let head = element
1118 .select(&Selector::parse(".item-head").unwrap())
1119 .next()
1120 .map(|e| e.text().collect::<String>().trim().to_string())
1121 .unwrap_or_default();
1122
1123 let key = element
1124 .select(&Selector::parse(".name").unwrap())
1125 .next()
1126 .map(|e| e.text().collect::<String>().trim().to_string())
1127 .unwrap_or_default();
1128
1129 match head.as_str() {
1130 "Japanese:" => about_anime.japanese = key,
1131 "Synonyms:" => about_anime.synonyms = key,
1132 "Aired:" => about_anime.aired = key,
1133 "Premiered:" => about_anime.premiered = key,
1134 "Status:" => about_anime.status = key,
1135 "MAL Score:" => about_anime.mal_score = key,
1136 "Producers:" => {
1137 about_anime.producers.extend(
1138 element
1139 .select(&Selector::parse("a.name").unwrap())
1140 .map(|e| e.text().collect::<String>().trim().to_string()),
1141 );
1142 }
1143 "Studios:" => {
1144 about_anime.studios.extend(
1145 element
1146 .select(&Selector::parse("a.name").unwrap())
1147 .map(|e| e.text().collect::<String>().trim().to_string()),
1148 );
1149 }
1150 _ => {}
1151 }
1152 });
1153
1154 document.select(&genres_selector).for_each(|element| {
1155 about_anime.genres.extend(
1156 element
1157 .select(&Selector::parse("a").unwrap())
1158 .map(|e| e.text().collect::<String>().trim().to_string()),
1159 );
1160 });
1161
1162 about_anime
1163 .most_popular_animes
1164 .extend(extract_side_bar_animes(
1165 document,
1166 &MOST_POPULAR_ANIME_SELECTOR,
1167 ));
1168 about_anime
1169 .related_animes
1170 .extend(extract_side_bar_animes(document, &RELATED_ANIME_SELECTOR));
1171 about_anime
1172 .recommended_animes
1173 .extend(extract_anime_data(document, &RECOMMENDED_ANIME_SELECTOR));
1174 about_anime
1175 .seasons
1176 .extend(extract_anime_seasons(document, &SEASONS_SELECTOR));
1177
1178 about_anime
1179}
1180
1181fn extract_side_bar_animes(document: &Html, selector: &Selector) -> Vec<SideBarAnimes> {
1182 let dynamic_name_selector = Selector::parse(".film-detail .dynamic-name").unwrap();
1183 let tick_selector = Selector::parse(".fd-infor .tick").unwrap();
1184 let tick_item_sub_selector = Selector::parse(".fd-infor .tick .tick-item.tick-sub").unwrap();
1185 let tick_item_dub_selector = Selector::parse(".fd-infor .tick .tick-item.tick-dub").unwrap();
1186 let tick_item_eps_selector = Selector::parse(".fd-infor .tick .tick-item.tick-eps").unwrap();
1187 let film_poster_selector = Selector::parse(".film-poster .film-poster-img").unwrap();
1188
1189 document
1190 .select(selector)
1191 .map(|element| {
1192 let id = element
1193 .select(&dynamic_name_selector)
1194 .next()
1195 .and_then(|e| e.value().attr("href"))
1196 .map(|s| s.trim_start_matches('/').to_string())
1197 .unwrap_or_default();
1198
1199 let title = element
1200 .select(&dynamic_name_selector)
1201 .next()
1202 .map(|e| e.text().collect::<String>().trim().to_string())
1203 .unwrap_or_default();
1204
1205 let image = element
1206 .select(&film_poster_selector)
1207 .next()
1208 .and_then(|e| e.value().attr("data-src").map(|s| s.to_string()))
1209 .unwrap_or_default();
1210
1211 let subs = element
1212 .select(&tick_item_sub_selector)
1213 .next()
1214 .and_then(|e| e.text().collect::<String>().trim().parse::<u32>().ok())
1215 .unwrap_or_default();
1216
1217 let dubs = element
1218 .select(&tick_item_dub_selector)
1219 .next()
1220 .and_then(|e| e.text().collect::<String>().trim().parse::<u32>().ok())
1221 .unwrap_or_default();
1222
1223 let eps = element
1224 .select(&tick_item_eps_selector)
1225 .next()
1226 .and_then(|e| e.text().collect::<String>().trim().parse::<u32>().ok())
1227 .unwrap_or_default();
1228
1229 let category = element
1230 .select(&tick_selector)
1231 .next()
1232 .map(|e| e.text().collect::<String>().trim().to_string())
1233 .map(|s| s.replace('\n', " ").replace(" ", " ").trim().to_string())
1234 .map(|s| s.split_whitespace().last().unwrap_or_default().to_string())
1235 .unwrap_or_default();
1236
1237 SideBarAnimes {
1238 id,
1239 title,
1240 image,
1241 subs,
1242 dubs,
1243 eps,
1244 category,
1245 }
1246 })
1247 .collect()
1248}
1249
1250fn extract_anime_seasons(document: &Html, selector: &Selector) -> Vec<AnimeSeason> {
1251 document
1252 .select(selector)
1253 .map(|element| {
1254 let id = element
1255 .value()
1256 .attr("href")
1257 .map(|s| s.trim_start_matches('/').to_string())
1258 .unwrap_or_default();
1259
1260 let title = element
1261 .value()
1262 .attr("title")
1263 .map(|e| e.trim().to_string())
1264 .unwrap_or_default();
1265
1266 let anime_title = element
1267 .select(&Selector::parse(".title").unwrap())
1268 .next()
1269 .map(|e| e.text().collect::<String>().trim().to_string())
1270 .unwrap_or_default();
1271
1272 let mut image = element
1273 .select(&Selector::parse(".season-poster").unwrap())
1274 .next()
1275 .and_then(|e| e.value().attr("style").map(|s| s.to_string()))
1276 .unwrap_or_default();
1277
1278 let re = Regex::new(r"url\((?P<url>.*?)\)").unwrap();
1279
1280 image = re
1281 .captures(&image)
1282 .and_then(|caps| caps.name("url"))
1283 .map(|m| m.as_str().trim_matches('"').to_string())
1284 .unwrap_or_default();
1285
1286 let is_current = element.has_class("active");
1287
1288 AnimeSeason {
1289 id,
1290 title,
1291 anime_title,
1292 image,
1293 is_current,
1294 }
1295 })
1296 .collect()
1297}
1298
1299fn extract_anime_episode(document: &Html, selector: &Selector) -> Vec<AnimeEpisode> {
1300 document
1301 .select(selector)
1302 .filter_map(|element| {
1303 let id = element
1304 .value()
1305 .attr("href")
1306 .map(|s| s.trim_start_matches('/').to_string())
1307 .unwrap_or_default()
1308 .split('/')
1309 .last()
1310 .unwrap_or_default()
1311 .to_string();
1312
1313 if id.is_empty() {
1314 return None;
1315 }
1316
1317 let title = element
1318 .value()
1319 .attr("title")
1320 .map(|e| e.trim().to_string())
1321 .unwrap_or_default();
1322
1323 let episode_no = id
1324 .split('=')
1325 .last()
1326 .unwrap_or_default()
1327 .parse::<u32>()
1328 .ok()
1329 .unwrap_or_default();
1330
1331 let is_filler = element.has_class("ssl-item-filler");
1332
1333 Some(AnimeEpisode {
1334 id,
1335 title,
1336 episode_no,
1337 is_filler,
1338 })
1339 })
1340 .collect()
1341}
1342
1343fn extract_episode_servers(document: &Html, selector: &Selector) -> Vec<Server> {
1344 document
1345 .select(selector)
1346 .map(|element| {
1347 let server_name = element
1348 .select(&Selector::parse("a").unwrap())
1349 .next()
1350 .map(|e| {
1351 e.text()
1352 .collect::<String>()
1353 .to_lowercase()
1354 .trim()
1355 .to_string()
1356 })
1357 .unwrap_or_default();
1358
1359 let data_id = element
1360 .attr("data-id")
1361 .and_then(|id| id.trim().parse::<u32>().ok())
1362 .unwrap_or_default();
1363
1364 let server_id = element
1365 .attr("data-server-id")
1366 .and_then(|id| id.trim().parse::<u32>().ok())
1367 .unwrap_or_default();
1368
1369 Server {
1370 server_name,
1371 server_id,
1372 data_id,
1373 }
1374 })
1375 .collect()
1376}
1377
1378fn extract_genres(document: &Html, selector: &Selector) -> Vec<String> {
1379 document
1380 .select(selector)
1381 .map(|element| {
1382 let text = element.text().collect::<String>().trim().to_string();
1383 if text.is_empty() {
1384 String::new()
1385 } else {
1386 text
1387 }
1388 })
1389 .collect()
1390}
1391
1392fn get_last_page_no(document: &Html) -> u32 {
1394 document
1395 .select(&NAVIGATION_SELECTOR)
1396 .last()
1397 .and_then(|element| element.value().attr("href"))
1398 .and_then(|href| href.split('=').last())
1399 .and_then(|page_str| page_str.parse::<u32>().ok())
1400 .unwrap_or(1)
1401}
1402
1403fn initialize_secret(secret: Option<SecretConfig>) -> Option<SecretConfig> {
1404 let mut secret_lock = env::SECRET.lock().unwrap();
1405 secret_lock.clone_from(&secret);
1406 let secret_clone = secret_lock.clone();
1407 drop(secret_lock);
1409 secret_clone
1410}
1411
1412fn update_server_id(
1413 server_id: &mut u32,
1414 data_id: &mut u32,
1415 servers: Vec<Server>,
1416 anime_server: Option<AnimeServer>,
1417) {
1418 let anime_server = anime_server.unwrap_or(AnimeServer::Vidstreaming);
1419
1420 for server in servers {
1421 println!("{} - {}", server.server_name, anime_server.as_str());
1422 if server.server_name == anime_server.as_str() {
1423 *server_id = server.server_id;
1424 *data_id = server.data_id;
1425 return;
1426 }
1427 }
1428}