const express = require("express"); const axios = require("axios"); const cheerio = require("cheerio"); const stringSimilarity = require("string-similarity"); const app = express(); const PORT = 7860; const baseURL = "https://www.tokyoinsider.com/anime/"; app.get("/anime/:name/:episode", async (req, res) => { const animeName = decodeURIComponent(req.params.name).replace(/\s+/g, " ").trim(); const episodeNumber = req.params.episode; const firstLetter = animeName[0].toUpperCase(); const categoryURL = `${baseURL}${firstLetter}`; console.log(`Fetching: ${categoryURL}`); try { let response = await axios.get(categoryURL); let html = response.data; let $ = cheerio.load(html); let animeLinks = {}; $("a[href*='/anime/']").each((i, link) => { let title = $(link).text().replace(/\s+/g, " ").trim(); let href = $(link).attr("href"); if (title && href) { animeLinks[title] = `https://www.tokyoinsider.com${href}`; } }); if (Object.keys(animeLinks).length === 0) { return res.status(404).json({ error: "No anime found." }); } let bestMatch = findClosestMatch(animeName, Object.keys(animeLinks)); if (bestMatch) { let baseAnimeURL = animeLinks[bestMatch].replace(/\/episode\/\d+$/, ""); let episodeURL = `${baseAnimeURL}/episode/${episodeNumber}`; console.log(`Anime Found: ${bestMatch}`); console.log(`Episode URL: ${episodeURL}`); let downloads = await getEpisodeDownloads(episodeURL); if (downloads.length === 0) { console.log("No downloads found, retrying alternative URL..."); let alternativeURL = `${baseAnimeURL}/ep/${episodeNumber}`; downloads = await getEpisodeDownloads(alternativeURL); if (downloads.length === 0) { return res.status(404).json({ error: "No download links found." }); } episodeURL = alternativeURL; } let categorizedDownloads = categorizeDownloads(downloads); let responseObj = { anime: bestMatch, episode: episodeNumber, episodeURL, ...categorizedDownloads }; return res.json(responseObj); } else { return res.status(404).json({ error: "No close match found." }); } } catch (error) { console.error("Error:", error.message); return res.status(500).json({ error: "Server error." }); } }); async function getEpisodeDownloads(episodeURL) { console.log(`Fetching Episode Page: ${episodeURL}`); try { let response = await axios.get(episodeURL); let html = response.data; let $ = cheerio.load(html); let downloads = []; $(".c_h2, .c_h2b").each((i, div) => { let linkElement = $(div).find("a[href*='media.tokyoinsider.com']"); let infoElement = $(div).find(".finfo"); if (linkElement.length > 0 && infoElement.length > 0) { let title = linkElement.text().trim(); let url = linkElement.attr("href"); let sizeText = infoElement.find("b").eq(0).text(); let downloadsCount = infoElement.find("b").eq(1).text(); let uploader = infoElement.find("b").eq(2).text(); let addedOn = infoElement.find("b").eq(3).text(); let size = parseSize(sizeText); if (size > 0) { downloads.push({ title, url, size, downloadsCount, uploader, addedOn }); } } }); return downloads; } catch (error) { console.error("Error fetching episode page:", error.message); return []; } } function categorizeDownloads(downloads) { let resolutions = { "360p": null, "720p": null, "1080p": null }; downloads.forEach(d => { if (d.size >= 25 && d.size < 100 && !resolutions["360p"]) { resolutions["360p"] = d; } else if (d.size >= 100 && d.size < 400 && !resolutions["720p"]) { resolutions["720p"] = d; } else if (d.size >= 400 && !resolutions["1080p"]) { resolutions["1080p"] = d; } }); return resolutions; } function parseSize(sizeText) { let size = parseFloat(sizeText); if (sizeText.includes("GB")) { size *= 1024; } return isNaN(size) ? 0 : size; } function findClosestMatch(query, animeList) { let bestMatch = stringSimilarity.findBestMatch(query, animeList); return bestMatch.bestMatch.rating > 0.5 ? bestMatch.bestMatch.target : null; } app.listen(PORT, () => { console.log(`Server running on http://localhost:${PORT}`); });