hianime
Version:
a scraper for hianime
203 lines (200 loc) • 5.92 kB
JavaScript
// src/hianime.ts
import axios from "axios";
import * as cheerio from "cheerio";
var Hianime = class {
BASE_URL = "https://hianime.to";
MALSYNC_URL = "https://api.malsync.moe";
async getAnimeList(category, page = 1, query) {
const { data } = await axios.get(`${this.BASE_URL}/${category}`, {
params: {
page,
...query && { keyword: decodeURIComponent(query) }
}
});
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")?.match(/page=(\d+)/)?.[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 search(query, page = 1) {
return this.getAnimeList("search", page, query);
}
async getEpisodesByMALID(malId) {
const { data: malsyncData } = await axios.get(
`${this.MALSYNC_URL}/mal/anime/${malId}`
);
const ids = malsyncData.Sites.Zoro;
if (!ids) {
throw new Error(`No Zoro IDs found for MAL ID ${malId}`);
}
let id = Object.keys(ids)[0];
const episodes = await this.getEpisodes(id ?? "");
return episodes;
}
async getEpisodes(dataId) {
const { data } = await axios.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.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.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.get(embedLink, {
headers: {
Referer: this.BASE_URL
}
});
const $ = cheerio.load(embedData);
const dataId = $("div[data-id]").attr("data-id");
let nonce = null;
const regex48 = /\b[a-zA-Z0-9]{48}\b/;
const match48 = embedData.match(regex48);
if (match48 && match48[0]) {
nonce = match48[0];
} else {
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.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;
}
};
var hianime_default = Hianime;
// src/types/hianime-result.ts
var Type = /* @__PURE__ */ ((Type2) => {
Type2["Ona"] = "ONA";
Type2["Special"] = "SPECIAL";
Type2["Tv"] = "TV";
return Type2;
})(Type || {});
// src/index.ts
var index_default = hianime_default;
export {
hianime_default as Hianime,
Type,
index_default as default
};