hianime
Version:
a scraper for hianime
208 lines • 7.66 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const axios_1 = __importDefault(require("axios"));
const cheerio = __importStar(require("cheerio"));
class Hianime {
BASE_URL = "https://hianime.to";
async getAnimeList(category, page = 1) {
const { data } = await axios_1.default.get(`${this.BASE_URL}/${category}`, {
params: {
page: page,
},
});
const $ = cheerio.load(data);
const animeList = $(".tab-content div.film_list-wrap > div.flw-item")
.map((_, element) => {
const anime = {
id: $(element).find("a[data-id]").attr("href"),
image: $(element).find("div.film-poster > img").attr("data-src"),
title: $(element)
.find("div.film-detail > .film-name > a")
.attr("title"),
type: $(element)
.find("div.fd-infor > span.fdi-item")
.first()
.text()
.toUpperCase(),
language: {
sub: $(element).find(".tick.ltr > .tick-sub").text() || null,
dub: $(element).find(".tick.ltr > .tick-dub").text() || null,
},
dataId: $(element).find("a[data-id]").attr("data-id"),
};
return anime;
})
.get();
const totalPage = parseInt($('a[title="Last"]').attr("href")?.split("=")[1] ?? page.toString());
return {
page,
totalPage,
hasNextPage: page < totalPage,
results: animeList,
};
}
async getSubbedAnime(page = 1) {
return this.getAnimeList("subbed-anime", page);
}
async getDubbedAnime(page = 1) {
return this.getAnimeList("dubbed-anime", page);
}
async getMostPopular(page = 1) {
return this.getAnimeList("most-popular", page);
}
async getMovies(page = 1) {
return this.getAnimeList("movie", page);
}
async getTVShows(page = 1) {
return this.getAnimeList("tv", page);
}
async getSpecialList(page = 1) {
return this.getAnimeList("special", page);
}
async getONAList(page = 1) {
return this.getAnimeList("ona", page);
}
async getOVAList(page = 1) {
return this.getAnimeList("ova", page);
}
async getTopAiring(page = 1) {
return this.getAnimeList("top-airing", page);
}
async getEpisodes(dataId) {
const { data } = await axios_1.default.get(`${this.BASE_URL}/ajax/v2/episode/list/${dataId}`);
const $ = cheerio.load(data.html);
const episodes = $("div.ss-list > a.ep-item")
.map((_, element) => {
const episode = {
id: $(element).attr("data-id"),
number: $(element).attr("data-number"),
title: $(element).attr("title"),
href: $(element).attr("href"),
};
return episode;
})
.get();
return episodes;
}
async getEpisodeServers(episodeId) {
const { data } = await axios_1.default.get(`${this.BASE_URL}/ajax/v2/episode/servers`, {
params: {
episodeId,
},
});
const $ = cheerio.load(data.html);
const subServers = $("div.servers-sub .server-item")
.map((_, element) => {
const server = {
type: $(element).attr("data-type"),
id: $(element).attr("data-id"),
serverId: $(element).attr("data-server-id"),
name: $(element).find("a.btn").text(),
};
return server;
})
.get();
const dubServers = $("div.servers-dub .server-item")
.map((_, element) => {
const server = {
type: $(element).attr("data-type"),
id: $(element).attr("data-id"),
serverId: $(element).attr("data-server-id"),
name: $(element).find("a.btn").text(),
};
return server;
})
.get();
return {
sub: subServers,
dub: dubServers,
};
}
async getEpisodeSources(serverId) {
const { data } = await axios_1.default.get(`${this.BASE_URL}/ajax/v2/episode/sources?id=${serverId}`);
const embedLink = data?.link;
if (!embedLink) {
throw new Error(`No embed link returned for serverId ${serverId}. Response: ${JSON.stringify(data).slice(0, 200)}...`);
}
const { data: embedData } = await axios_1.default.get(embedLink, {
headers: {
Referer: this.BASE_URL,
},
});
const $ = cheerio.load(embedData);
const dataId = $("div[data-id]").attr("data-id");
let nonce = null;
// Try a single 48-char token first
const regex48 = /\b[a-zA-Z0-9]{48}\b/;
const match48 = embedData.match(regex48);
if (match48 && match48[0]) {
nonce = match48[0];
}
else {
// Fallback: concatenate multiple 16-char tokens
const regex16 = /"([a-zA-Z0-9]{16})"/g;
const parts = [];
let m;
while ((m = regex16.exec(embedData)) !== null) {
if (m[1])
parts.push(m[1]);
}
if (parts.length)
nonce = parts.join("");
}
if (!nonce) {
throw new Error(`Failed to extract nonce from embed page for serverId ${serverId}`);
}
const { data: sources } = await axios_1.default.get(`https://megacloud.blog/embed-2/v3/e-1/getSources`, {
params: {
id: dataId,
_k: nonce,
},
headers: {
Referer: embedLink,
},
});
sources.headers = {
Referer: "https://megacloud.blog/",
};
return sources;
}
}
exports.default = Hianime;
//# sourceMappingURL=hianime.js.map