import axios from "axios"; import * as cheerio from "cheerio"; import formatTitle from "../helper/formatTitle.helper.js"; import { v1_base_url } from "../utils/base_v1.js"; import extractRecommendedData from "./recommend.extractor.js"; import extractRelatedData from "./related.extractor.js"; import extractPopularData from "./popular.extractor.js"; async function extractAnimeInfo(id) { try { const [resp, characterData] = await Promise.all([ axios.get(`https://${v1_base_url}/${id}`), axios.get( `https://${v1_base_url}/ajax/character/list/${id.split("-").pop()}` ), ]); const characterHtml = characterData.data?.html || ""; const $1 = cheerio.load(characterHtml); const $ = cheerio.load(resp.data); const data_id = id.split("-").pop(); const titleElement = $("#ani_detail .film-name"); const showType = $("#ani_detail .prebreadcrumb ol li") .eq(1) .find("a") .text() .trim(); const posterElement = $("#ani_detail .film-poster"); const tvInfoElement = $("#ani_detail .film-stats"); const tvInfo = {}; tvInfoElement.find(".tick-item, span.item").each((_, element) => { const el = $(element); const text = el.text().trim(); if (el.hasClass("tick-quality")) tvInfo.quality = text; else if (el.hasClass("tick-sub")) tvInfo.sub = text; else if (el.hasClass("tick-dub")) tvInfo.dub = text; else if (el.hasClass("tick-eps")) tvInfo.eps = text; else if (el.hasClass("tick-pg")) tvInfo.rating = text; else if (el.is("span.item")) { if (!tvInfo.showType) tvInfo.showType = text; else if (!tvInfo.duration) tvInfo.duration = text; } }); const element = $( "#ani_detail > .ani_detail-stage > .container > .anis-content > .anisc-info-wrap > .anisc-info > .item" ); const overviewElement = $("#ani_detail .film-description .text"); const title = titleElement.text().trim(); const japanese_title = titleElement.attr("data-jname"); const synonyms = $('.item.item-title:has(.item-head:contains("Synonyms")) .name').text().trim(); const poster = posterElement.find("img").attr("src"); const syncDataScript = $("#syncData").html(); let anilistId = null; let malId = null; if (syncDataScript) { try { const syncData = JSON.parse(syncDataScript); anilistId = syncData.anilist_id || null; malId = syncData.mal_id || null; } catch (error) { console.error("Error parsing syncData:", error); } } const animeInfo = {}; element.each((_, el) => { const key = $(el).find(".item-head").text().trim().replace(":", ""); const value = key === "Genres" || key === "Producers" ? $(el) .find("a") .map((_, a) => $(a).text().split(" ").join("-").trim()) .get() : $(el).find(".name").text().split(" ").join("-").trim(); animeInfo[key] = value; }); const trailers = []; $('.block_area-promotions-list .screen-items .item').each((_, element) => { const el = $(element); const title = el.attr('data-title'); const url = el.attr('data-src'); if (url) { const fullUrl = url.startsWith('//') ? `https:${url}` : url; let videoId = null; const match = fullUrl.match(/\/embed\/([^?&]+)/); if (match && match[1]) { videoId = match[1]; } trailers.push({ title: title || null, url: fullUrl, thumbnail: videoId ? `https://img.youtube.com/vi/${videoId}/hqdefault.jpg` : null }); } }); animeInfo.trailers = trailers; const season_id = formatTitle(title, data_id); animeInfo["Overview"] = overviewElement.text().trim(); animeInfo["tvInfo"] = tvInfo; let adultContent = false; const tickRateText = $(".tick-rate", posterElement).text().trim(); if (tickRateText.includes("18+")) { adultContent = true; } const [recommended_data, related_data, popular_data] = await Promise.all([ extractRecommendedData($), extractRelatedData($), extractPopularData($), ]); let charactersVoiceActors = []; if (characterHtml) { charactersVoiceActors = $1(".bac-list-wrap .bac-item") .map((index, el) => { const character = { id: $1(el) .find(".per-info.ltr .pi-avatar") .attr("href") ?.split("/")[2] || "", poster: $1(el).find(".per-info.ltr .pi-avatar img").attr("data-src") || "", name: $1(el).find(".per-info.ltr .pi-detail a").text(), cast: $1(el).find(".per-info.ltr .pi-detail .pi-cast").text(), }; let voiceActors = []; const rtlVoiceActors = $1(el).find(".per-info.rtl"); const xxVoiceActors = $1(el).find( ".per-info.per-info-xx .pix-list .pi-avatar" ); if (rtlVoiceActors.length > 0) { voiceActors = rtlVoiceActors .map((_, actorEl) => ({ id: $1(actorEl).find("a").attr("href")?.split("/").pop() || "", poster: $1(actorEl).find("img").attr("data-src") || "", name: $1(actorEl).find(".pi-detail .pi-name a").text().trim() || "", })) .get(); } else if (xxVoiceActors.length > 0) { voiceActors = xxVoiceActors .map((_, actorEl) => ({ id: $1(actorEl).attr("href")?.split("/").pop() || "", poster: $1(actorEl).find("img").attr("data-src") || "", name: $1(actorEl).attr("title") || "", })) .get(); } if (voiceActors.length === 0) { voiceActors = $1(el) .find(".per-info.per-info-xx .pix-list .pi-avatar") .map((_, actorEl) => ({ id: $1(actorEl).attr("href")?.split("/")[2] || "", poster: $1(actorEl).find("img").attr("data-src") || "", name: $1(actorEl).attr("title") || "", })) .get(); } return { character, voiceActors }; }) .get(); } return { adultContent, data_id, id: season_id, anilistId, malId, title, japanese_title, synonyms, poster, showType, animeInfo, charactersVoiceActors, recommended_data, related_data, popular_data, }; } catch (e) { console.error("Error extracting anime info:", e); } } export default extractAnimeInfo;