File size: 4,902 Bytes
9a1241a
 
 
 
ff4da35
 
9694c50
ff4da35
 
9a1241a
 
ff4da35
 
 
 
 
 
 
 
 
 
 
 
 
9a1241a
 
 
ff4da35
 
 
 
 
 
 
 
 
 
 
 
 
9a1241a
 
ff4da35
 
 
 
 
 
9a1241a
 
 
 
 
 
 
 
 
ff4da35
 
 
 
9a1241a
 
 
ff4da35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9a1241a
 
 
ff4da35
 
 
9a1241a
 
 
 
 
ff4da35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9a1241a
ff4da35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
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}`);
});