| 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}`); |
| }); |