aniscraper/
hianime.rs

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
1392// Function to extract the last page number from the response
1393fn 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    // Release the lock.
1408    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}