UNPKG

consumet.custom

Version:

Nodejs library that provides high-level APIs for obtaining information on various entertainment media such as books, movies, comic books, anime, manga, and so on.

634 lines 28 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const cheerio_1 = require("cheerio"); const models_1 = require("../../models"); const utils_1 = require("../../utils"); const utils_2 = require("../../utils"); class Zoro extends models_1.AnimeParser { constructor(customBaseURL) { super(...arguments); this.name = 'Zoro'; this.baseUrl = 'https://hianime.to'; this.logo = 'https://is3-ssl.mzstatic.com/image/thumb/Purple112/v4/7e/91/00/7e9100ee-2b62-0942-4cdc-e9b93252ce1c/source/512x512bb.jpg'; this.classPath = 'ANIME.Zoro'; /** * @param id Anime id */ this.fetchAnimeInfo = async (id) => { const info = { id: id, title: '', }; try { const { data } = await this.client.get(`${this.baseUrl}/watch/${id}`); const $ = (0, cheerio_1.load)(data); const { mal_id, anilist_id } = JSON.parse($('#syncData').text()); info.malID = Number(mal_id); info.alID = Number(anilist_id); info.title = $('h2.film-name > a.text-white').text(); info.japaneseTitle = $('div.anisc-info div:nth-child(2) span.name').text(); info.image = $('img.film-poster-img').attr('src'); info.description = $('div.film-description').text().trim(); // Movie, TV, OVA, ONA, Special, Music info.type = $('span.item').last().prev().prev().text().toUpperCase(); info.url = `${this.baseUrl}/${id}`; info.recommendations = await this.scrapeCard($); info.relatedAnime = []; $('#main-sidebar section:nth-child(1) div.anif-block-ul li').each((i, ele) => { var _a, _b, _c, _d, _e, _f, _g; const card = $(ele); const aTag = card.find('.film-name a'); const id = (_a = aTag.attr('href')) === null || _a === void 0 ? void 0 : _a.split('/')[1].split('?')[0]; info.relatedAnime.push({ id: id, title: aTag.text(), url: `${this.baseUrl}${aTag.attr('href')}`, image: (_b = card.find('img')) === null || _b === void 0 ? void 0 : _b.attr('data-src'), japaneseTitle: aTag.attr('data-jname'), type: (_d = (_c = card.find('.tick').contents().last()) === null || _c === void 0 ? void 0 : _c.text()) === null || _d === void 0 ? void 0 : _d.trim(), sub: parseInt((_e = card.find('.tick-item.tick-sub')) === null || _e === void 0 ? void 0 : _e.text()) || 0, dub: parseInt((_f = card.find('.tick-item.tick-dub')) === null || _f === void 0 ? void 0 : _f.text()) || 0, episodes: parseInt((_g = card.find('.tick-item.tick-eps')) === null || _g === void 0 ? void 0 : _g.text()) || 0, }); }); const hasSub = $('div.film-stats div.tick div.tick-item.tick-sub').length > 0; const hasDub = $('div.film-stats div.tick div.tick-item.tick-dub').length > 0; if (hasSub) { info.subOrDub = models_1.SubOrSub.SUB; info.hasSub = hasSub; } if (hasDub) { info.subOrDub = models_1.SubOrSub.DUB; info.hasDub = hasDub; } if (hasSub && hasDub) { info.subOrDub = models_1.SubOrSub.BOTH; } const episodesAjax = await this.client.get(`${this.baseUrl}/ajax/v2/episode/list/${id.split('-').pop()}`, { headers: { 'X-Requested-With': 'XMLHttpRequest', Referer: `${this.baseUrl}/watch/${id}`, }, }); const $$ = (0, cheerio_1.load)(episodesAjax.data.html); info.totalEpisodes = $$('div.detail-infor-content > div > a').length; info.episodes = []; $$('div.detail-infor-content > div > a').each((i, el) => { var _a, _b, _c, _d; const episodeId = (_c = (_b = (_a = $$(el) .attr('href')) === null || _a === void 0 ? void 0 : _a.split('/')[2]) === null || _b === void 0 ? void 0 : _b.replace('?ep=', '$episode$')) === null || _c === void 0 ? void 0 : _c.concat(`$${info.subOrDub}`); const number = parseInt($$(el).attr('data-number')); const title = $$(el).attr('title'); const url = this.baseUrl + $$(el).attr('href'); const isFiller = $$(el).hasClass('ssl-item-filler'); (_d = info.episodes) === null || _d === void 0 ? void 0 : _d.push({ id: episodeId, number: number, title: title, isFiller: isFiller, url: url, }); }); return info; } catch (err) { throw new Error(err.message); } }; /** * * @param episodeId Episode id */ this.fetchEpisodeSources = async (episodeId, server = models_1.StreamingServers.VidCloud) => { var _a; if (episodeId.startsWith('http')) { const serverUrl = new URL(episodeId); switch (server) { case models_1.StreamingServers.VidStreaming: case models_1.StreamingServers.VidCloud: return { ...(await new utils_1.MegaCloud().extract(serverUrl)), }; case models_1.StreamingServers.StreamSB: return { headers: { Referer: serverUrl.href, watchsb: 'streamsb', 'User-Agent': utils_2.USER_AGENT, }, sources: await new utils_1.StreamSB(this.proxyConfig, this.adapter).extract(serverUrl, true), }; case models_1.StreamingServers.StreamTape: return { headers: { Referer: serverUrl.href, 'User-Agent': utils_2.USER_AGENT }, sources: await new utils_1.StreamTape(this.proxyConfig, this.adapter).extract(serverUrl), }; default: case models_1.StreamingServers.VidCloud: return { headers: { Referer: serverUrl.href }, ...(await new utils_1.MegaCloud().extract(serverUrl)), }; } } if (!episodeId.includes('$episode$')) throw new Error('Invalid episode id'); // Fallback to using sub if no info found in case of compatibility // TODO: add both options later const subOrDub = ((_a = episodeId.split('$')) === null || _a === void 0 ? void 0 : _a.pop()) === 'dub' ? 'dub' : 'sub'; episodeId = `${this.baseUrl}/watch/${episodeId .replace('$episode$', '?ep=') .replace(/\$auto|\$sub|\$dub/gi, '')}`; try { const { data } = await this.client.get(`${this.baseUrl}/ajax/v2/episode/servers?episodeId=${episodeId.split('?ep=')[1]}`); const $ = (0, cheerio_1.load)(data.html); /** * vidtreaming -> 4 * rapidcloud -> 1 * streamsb -> 5 * streamtape -> 3 */ let serverId = ''; try { switch (server) { case models_1.StreamingServers.VidCloud: serverId = this.retrieveServerId($, 1, subOrDub); // zoro's vidcloud server is rapidcloud if (!serverId) throw new Error('RapidCloud not found'); break; case models_1.StreamingServers.VidStreaming: serverId = this.retrieveServerId($, 4, subOrDub); // zoro's vidcloud server is rapidcloud if (!serverId) throw new Error('vidtreaming not found'); break; case models_1.StreamingServers.StreamSB: serverId = this.retrieveServerId($, 5, subOrDub); if (!serverId) throw new Error('StreamSB not found'); break; case models_1.StreamingServers.StreamTape: serverId = this.retrieveServerId($, 3, subOrDub); if (!serverId) throw new Error('StreamTape not found'); break; } } catch (err) { throw new Error("Couldn't find server. Try another server"); } const { data: { link }, } = await this.client.get(`${this.baseUrl}/ajax/v2/episode/sources?id=${serverId}`); return await this.fetchEpisodeSources(link, server); } catch (err) { throw err; } }; this.verifyLoginState = async (connectSid) => { try { const { data } = await this.client.get(`${this.baseUrl}/ajax/login-state`, { headers: { Cookie: `connect.sid=${connectSid}`, }, }); return data.is_login; } catch (err) { return false; } }; this.retrieveServerId = ($, index, subOrDub) => { return $(`.ps_-block.ps_-block-sub.servers-${subOrDub} > .ps__-list .server-item`) .map((i, el) => ($(el).attr('data-server-id') == `${index}` ? $(el) : null)) .get()[0] .attr('data-id'); }; /** * @param url string */ this.scrapeCardPage = async (url) => { var _a, _b, _c; try { const res = { currentPage: 0, hasNextPage: false, totalPages: 0, results: [], }; const { data } = await this.client.get(url); const $ = (0, cheerio_1.load)(data); const pagination = $('ul.pagination'); res.currentPage = parseInt((_a = pagination.find('.page-item.active')) === null || _a === void 0 ? void 0 : _a.text()); const nextPage = (_b = pagination.find('a[title=Next]')) === null || _b === void 0 ? void 0 : _b.attr('href'); if (nextPage != undefined && nextPage != '') { res.hasNextPage = true; } const totalPages = (_c = pagination.find('a[title=Last]').attr('href')) === null || _c === void 0 ? void 0 : _c.split('=').pop(); if (totalPages === undefined || totalPages === '') { res.totalPages = res.currentPage; } else { res.totalPages = parseInt(totalPages); } res.results = await this.scrapeCard($); if (res.results.length === 0) { res.currentPage = 0; res.hasNextPage = false; res.totalPages = 0; } return res; } catch (err) { throw new Error('Something went wrong. Please try again later.'); } }; /** * @param $ cheerio instance */ this.scrapeCard = async ($) => { try { const results = []; $('.flw-item').each((i, ele) => { var _a, _b, _c, _d, _e, _f, _g, _h, _j; const card = $(ele); const atag = card.find('.film-name a'); const id = (_a = atag.attr('href')) === null || _a === void 0 ? void 0 : _a.split('/')[1].split('?')[0]; const type = (_c = (_b = card .find('.fdi-item')) === null || _b === void 0 ? void 0 : _b.first()) === null || _c === void 0 ? void 0 : _c.text().replace(' (? eps)', '').replace(/\s\(\d+ eps\)/g, ''); results.push({ id: id, title: atag.text(), url: `${this.baseUrl}${atag.attr('href')}`, image: (_d = card.find('img')) === null || _d === void 0 ? void 0 : _d.attr('data-src'), duration: (_e = card.find('.fdi-duration')) === null || _e === void 0 ? void 0 : _e.text(), japaneseTitle: atag.attr('data-jname'), type: type, nsfw: ((_f = card.find('.tick-rate')) === null || _f === void 0 ? void 0 : _f.text()) === '18+' ? true : false, sub: parseInt((_g = card.find('.tick-item.tick-sub')) === null || _g === void 0 ? void 0 : _g.text()) || 0, dub: parseInt((_h = card.find('.tick-item.tick-dub')) === null || _h === void 0 ? void 0 : _h.text()) || 0, episodes: parseInt((_j = card.find('.tick-item.tick-eps')) === null || _j === void 0 ? void 0 : _j.text()) || 0, }); }); return results; } catch (err) { throw new Error('Something went wrong. Please try again later.'); } }; /** * @deprecated * @param episodeId Episode id */ this.fetchEpisodeServers = (episodeId) => { throw new Error('Method not implemented.'); }; if (customBaseURL) { if (customBaseURL.startsWith('http://') || customBaseURL.startsWith('https://')) { this.baseUrl = customBaseURL; } else { this.baseUrl = `http://${customBaseURL}`; } } else { this.baseUrl = this.baseUrl; } } /** * @param query Search query * @param page Page number (optional) */ search(query, page = 1) { if (0 >= page) { page = 1; } return this.scrapeCardPage(`${this.baseUrl}/search?keyword=${decodeURIComponent(query)}&page=${page}`); } /** * @param page number */ fetchTopAiring(page = 1) { if (0 >= page) { page = 1; } return this.scrapeCardPage(`${this.baseUrl}/top-airing?page=${page}`); } /** * @param page number */ fetchMostPopular(page = 1) { if (0 >= page) { page = 1; } return this.scrapeCardPage(`${this.baseUrl}/most-popular?page=${page}`); } /** * @param page number */ fetchMostFavorite(page = 1) { if (0 >= page) { page = 1; } return this.scrapeCardPage(`${this.baseUrl}/most-favorite?page=${page}`); } /** * @param page number */ fetchLatestCompleted(page = 1) { if (0 >= page) { page = 1; } return this.scrapeCardPage(`${this.baseUrl}/completed?page=${page}`); } /** * @param page number */ fetchRecentlyUpdated(page = 1) { if (0 >= page) { page = 1; } return this.scrapeCardPage(`${this.baseUrl}/recently-updated?page=${page}`); } /** * @param page number */ fetchRecentlyAdded(page = 1) { if (0 >= page) { page = 1; } return this.scrapeCardPage(`${this.baseUrl}/recently-added?page=${page}`); } /** * @param page number */ fetchTopUpcoming(page = 1) { if (0 >= page) { page = 1; } return this.scrapeCardPage(`${this.baseUrl}/top-upcoming?page=${page}`); } /** * @param studio Studio id, e.g. "toei-animation" * @param page page number (optional) `default 1` */ fetchStudio(studio, page = 1) { if (0 >= page) { page = 1; } return this.scrapeCardPage(`${this.baseUrl}/producer/${studio}?page=${page}`); } /** * @param page number */ fetchSubbedAnime(page = 1) { if (0 >= page) { page = 1; } return this.scrapeCardPage(`${this.baseUrl}/subbed-anime?page=${page}`); } /** * @param page number */ fetchDubbedAnime(page = 1) { if (0 >= page) { page = 1; } return this.scrapeCardPage(`${this.baseUrl}/dubbed-anime?page=${page}`); } /** * @param page number */ fetchMovie(page = 1) { if (0 >= page) { page = 1; } return this.scrapeCardPage(`${this.baseUrl}/movie?page=${page}`); } /** * @param page number */ fetchTV(page = 1) { if (0 >= page) { page = 1; } return this.scrapeCardPage(`${this.baseUrl}/tv?page=${page}`); } /** * @param page number */ fetchOVA(page = 1) { if (0 >= page) { page = 1; } return this.scrapeCardPage(`${this.baseUrl}/ova?page=${page}`); } /** * @param page number */ fetchONA(page = 1) { if (0 >= page) { page = 1; } return this.scrapeCardPage(`${this.baseUrl}/ona?page=${page}`); } /** * @param page number */ fetchSpecial(page = 1) { if (0 >= page) { page = 1; } return this.scrapeCardPage(`${this.baseUrl}/special?page=${page}`); } async fetchGenres() { try { const res = []; const { data } = await this.client.get(`${this.baseUrl}/home`); const $ = (0, cheerio_1.load)(data); const sideBar = $('#main-sidebar'); sideBar.find('ul.sb-genre-list li a').each((i, ele) => { const genres = $(ele); res.push(genres.text().toLowerCase()); }); return res; } catch (err) { throw new Error('Something went wrong. Please try again later.'); } } /** * @param page number */ genreSearch(genre, page = 1) { if (genre == '') { throw new Error('genre is empty'); } if (0 >= page) { page = 1; } return this.scrapeCardPage(`${this.baseUrl}/genre/${genre}?page=${page}`); } /** * Fetches the schedule for a given date. * @param date The date in format 'YYYY-MM-DD'. Defaults to the current date. * @returns A promise that resolves to an object containing the search results. */ async fetchSchedule(date = new Date().toISOString().slice(0, 10)) { try { const res = { results: [], }; const { data: { html }, } = await this.client.get(`${this.baseUrl}/ajax/schedule/list?tzOffset=360&date=${date}`); const $ = (0, cheerio_1.load)(html); $('li').each((i, ele) => { var _a; const card = $(ele); const title = card.find('.film-name'); const id = (_a = card.find('a.tsl-link').attr('href')) === null || _a === void 0 ? void 0 : _a.split('/')[1].split('?')[0]; const airingTime = card.find('div.time').text().replace('\n', '').trim(); const airingEpisode = card.find('div.film-detail div.fd-play button').text().replace('\n', '').trim(); res.results.push({ id: id, title: title.text(), japaneseTitle: title.attr('data-jname'), url: `${this.baseUrl}/${id}`, airingEpisode: airingEpisode, airingTime: airingTime, }); }); return res; } catch (err) { throw new Error('Something went wrong. Please try again later.'); } } async fetchSpotlight() { try { const res = { results: [] }; const { data } = await this.client.get(`${this.baseUrl}/home`); const $ = (0, cheerio_1.load)(data); $('#slider div.swiper-wrapper div.swiper-slide').each((i, el) => { var _a, _b, _c; const card = $(el); const titleElement = card.find('div.desi-head-title'); const id = ((_b = (_a = card .find('div.desi-buttons .btn-secondary') .attr('href')) === null || _a === void 0 ? void 0 : _a.match(/\/([^/]+)$/)) === null || _b === void 0 ? void 0 : _b[1]) || null; const img = card.find('img.film-poster-img'); res.results.push({ id: id, title: titleElement.text(), japaneseTitle: titleElement.attr('data-jname'), banner: img.attr('data-src') || img.attr('src') || null, rank: parseInt((_c = card.find('.desi-sub-text').text().match(/(\d+)/g)) === null || _c === void 0 ? void 0 : _c[0]), url: `${this.baseUrl}/${id}`, type: card.find('div.sc-detail .scd-item:nth-child(1)').text().trim(), duration: card.find('div.sc-detail > div:nth-child(2)').text().trim(), releaseDate: card.find('div.sc-detail > div:nth-child(3)').text().trim(), quality: card.find('div.sc-detail > div:nth-child(4)').text().trim(), sub: parseInt(card.find('div.sc-detail div.tick-sub').text().trim()) || 0, dub: parseInt(card.find('div.sc-detail div.tick-dub').text().trim()) || 0, episodes: parseInt(card.find('div.sc-detail div.tick-eps').text()) || 0, description: card.find('div.desi-description').text().trim(), }); }); return res; } catch (error) { throw new Error('Something went wrong. Please try again later.'); } } async fetchSearchSuggestions(query) { try { const encodedQuery = encodeURIComponent(query); const { data } = await this.client.get(`${this.baseUrl}/ajax/search/suggest?keyword=${encodedQuery}`); const $ = (0, cheerio_1.load)(data.html); const res = { results: [], }; $('.nav-item').each((i, el) => { var _a; const card = $(el); if (!card.hasClass('nav-bottom')) { const image = card.find('.film-poster img').attr('data-src'); const title = card.find('.film-name'); const id = (_a = card.attr('href')) === null || _a === void 0 ? void 0 : _a.split('/')[1].split('?')[0]; const duration = card.find('.film-infor span').last().text().trim(); const releaseDate = card.find('.film-infor span:nth-child(1)').text().trim(); const type = card.find('.film-infor').find('span, i').remove().end().text().trim(); res.results.push({ image: image, id: id, title: title.text(), japaneseTitle: title.attr('data-jname'), aliasTitle: card.find('.alias-name').text(), releaseDate: releaseDate, type: type, duration: duration, url: `${this.baseUrl}/${id}`, }); } }); return res; } catch (error) { throw new Error('Something went wrong. Please try again later.'); } } /** * Fetches the list of episodes that the user is currently watching. * @param connectSid The session ID of the user. Note: This can be obtained from the browser cookies (needs to be signed in) * @returns A promise that resolves to an array of anime episodes. */ async fetchContinueWatching(connectSid) { try { if (!(await this.verifyLoginState(connectSid))) { throw new Error('Invalid session ID'); } const res = []; const { data } = await this.client.get(`${this.baseUrl}/user/continue-watching`, { headers: { Cookie: `connect.sid=${connectSid}`, }, }); const $ = (0, cheerio_1.load)(data); $('.flw-item').each((i, ele) => { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m; const card = $(ele); const atag = card.find('.film-name a'); const id = (_b = (_a = atag.attr('href')) === null || _a === void 0 ? void 0 : _a.replace('/watch/', '')) === null || _b === void 0 ? void 0 : _b.replace('?ep=', '$episode$'); const timeText = (_e = (_d = (_c = card.find('.fdb-time')) === null || _c === void 0 ? void 0 : _c.text()) === null || _d === void 0 ? void 0 : _d.split('/')) !== null && _e !== void 0 ? _e : []; const duration = (_g = (_f = timeText.pop()) === null || _f === void 0 ? void 0 : _f.trim()) !== null && _g !== void 0 ? _g : ''; const watchedTime = timeText.length > 0 ? timeText[0].trim() : ''; res.push({ id: id, title: atag.text(), number: parseInt(card.find('.fdb-type').text().replace('EP', '').trim()), duration: duration, watchedTime: watchedTime, url: `${this.baseUrl}${atag.attr('href')}`, image: (_h = card.find('img')) === null || _h === void 0 ? void 0 : _h.attr('data-src'), japaneseTitle: atag.attr('data-jname'), nsfw: ((_j = card.find('.tick-rate')) === null || _j === void 0 ? void 0 : _j.text()) === '18+' ? true : false, sub: parseInt((_k = card.find('.tick-item.tick-sub')) === null || _k === void 0 ? void 0 : _k.text()) || 0, dub: parseInt((_l = card.find('.tick-item.tick-dub')) === null || _l === void 0 ? void 0 : _l.text()) || 0, episodes: parseInt((_m = card.find('.tick-item.tick-eps')) === null || _m === void 0 ? void 0 : _m.text()) || 0, }); }); return res; } catch (err) { throw new Error(err.message); } } } // (async () => { // const zoro = new Zoro(); // const anime = await zoro.search('classroom of the elite'); // const info = await zoro.fetchAnimeInfo(anime.results[0].id); // const sources = await zoro.fetchEpisodeSources(info.episodes![0].id); // console.log(sources); // })(); exports.default = Zoro; //# sourceMappingURL=zoro.js.map