Spaces:
Running
Running
File size: 6,407 Bytes
95f4e64 |
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 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 |
import bytes from "bytes";
import fetch from "cross-fetch";
import { load, CheerioAPI, Element } from 'cheerio';
export type EpisodeType = {
showLink: string | undefined;
title: string;
magnet: string | undefined;
torrent: string | undefined;
size: number;
released: string;
seeds: number;
}
export type ShowType = {
title: string;
summary: string;
description: string;
imdbId: string | null;
episodes: EpisodeType[];
}
export type ImdbEpisodeType = {
id: number;
hash: string;
filename: string;
episode_url: string;
torrent_url: string;
magnet_url: string;
title: string;
imdb_id: string;
season: string;
episode: string;
small_screenshot: string;
large_screenshot: string;
seeds: number;
peers: number;
date_released_unix: number;
size_bytes: string;
}
export type ApiResponseType = {
imdb_id?: string;
torrents_count: number;
limit: number;
page: number;
torrents: ImdbEpisodeType[];
}
/**
* Crawl any given url
*
* @link https://www.npmjs.com/package/crawler
* @param url
* @returns `Crawler.CrawlerRequestResponse`
*/
async function crawl(url: string) {
const body = await fetch(url).then(async resp => resp.text());
const $ = load(body);
return { body, $ };
}
/**
* Get all shows listen on EZTV
*
* @returns `object`
*/
export async function getShows() {
const { $ } = await crawl(`https://eztv.wf/showlist/`);
const shows = $('a[class="thread_link"]').toArray();
return shows.map(show => {
const showIdRegex = $(show).attr('href')?.match(/shows\/(\d+)\//);
return {
id: showIdRegex ? parseInt(showIdRegex[1]) : null,
title: $(show).text()
}
}).filter(show => show.id) as { id: number, title: string }[];
}
/**
* Get a show and its episodes
*
* Recommended to use an ID, if using a showname
* it must be an exact match except for uppercase
*
* @param showId - A show ID or a show name
* @returns `object`
*/
export async function getShow(show: number|string): Promise<ShowType> {
/**
* If a string is passed find show based on title
*/
if(typeof show === 'string') {
const shows = await getShows();
const findShow = shows.find(s => s.title.toLowerCase() === show.toLowerCase());
if (!findShow) {
throw new EztvCrawlerException(`Did not find a show with name ${show}`)
}
return getShow(findShow.id);
}
const { $ } = await crawl(`https://eztv.wf/shows/${show}/`);
const episodes = $('[name="hover"]').toArray();
const imdbIdRegex = $('[itemprop="aggregateRating"] a').attr('href')?.match(/tt\d+/);
const result = {
title: $('.section_post_header [itemprop="name"]').text(),
summary: $('[itemprop="description"] p').text(),
description: $('span[itemprop="description"] + br + br + hr + br + span').text(),
imdbId: imdbIdRegex ? imdbIdRegex[0] : null,
episodes: episodes.map(episode => {
return transformToEpisode($, episode);
})
}
if (!result || !result.title || result.title === '') {
throw new EztvCrawlerException(`Did not find a show with name ${show}`)
}
return result;
}
/**
* Search for a TV show episode
*
* @param query - string
* @returns `episodeObject`
*/
export async function search(query: string) {
const { $ } = await crawl(`https://eztv.wf/search/${query}`);
const episodes = $('[name="hover"]').toArray();
return episodes.map(episode => {
return transformToEpisode($, episode);
})
}
/**
* Get a list of torrents
*
* @param limit
* @param page
* @param apiBaseUrl - If eztv domain changed or eztv is blocked in your country provide a proxy url here
* @returns `ApiResponseType`
*/
export async function getTorrents(limit = 10, page = 1, apiBaseUrl = 'https://eztv.wf/api/') {
return await makeApiRequest('/get-torrents', { limit: limit.toString(), page: page.toString() }, apiBaseUrl);
}
/**
* Get a list of torrents based on IMDb ID
*
* NOTE:
* For TV Shows provide the IMDb id of the show itself, it does not work
* when you provide an IMDb for individual episodes
*
* @param imdbId - IMDb ID
* @param apiBaseUrl - If eztv domain changed or eztv is blocked in your country provide a proxy url here
* @returns `ApiResponseType`
*/
export async function getTorrentsByImdbId(imdbId: string, limit = 30, page = 1, apiBaseUrl = 'https://eztv.wf/api/') {
return await makeApiRequest('/get-torrents', { imdb_id: imdbId, limit: limit.toString(), page: page.toString() }, apiBaseUrl);
}
/**
* Send a request to EZTV's API
*
* @param path
* @param params
* @param apiBaseUrl
* @returns `ApiResponseType`
*/
async function makeApiRequest(path: string, params: Record<string, string>, apiBaseUrl = 'https://eztv.wf/api/') {
if (params.imdb_id) {
params.imdb_id = params.imdb_id.replace(/\D+/, '');
}
try {
const request = await fetch(`${apiBaseUrl}/${path}?${new URLSearchParams(params)}`);
const json: ApiResponseType = await request.json();
return json;
} catch(e) {
if(e instanceof Error) {
throw new EztvCrawlerException(e.message);
}
throw new EztvCrawlerException('Could not fullfill request.');
}
}
/**
* Transforms a <table> Element into a episode object
*
* Note: in torrents, don't parse the `.download_2` class, it contains spam and malware in some cases
*
* @param $ - cheerio.CheerioAPI
* @param episode - cheerio.Element
* @returns `episodeObject`
*/
function transformToEpisode($: CheerioAPI, episode: Element) {
return {
showLink: $(episode).find('td:nth-child(1) a').attr('href'),
title: $(episode).find('td:nth-child(2)').text()?.replace(/\n/g, ''),
magnet: $(episode).find('td:nth-child(3) .magnet').attr('href')?.replace(/\n/g, ''),
torrent: $(episode).find('td:nth-child(3) .download_1').attr('href')?.replace(/\n/g, ''),
size: bytes($(episode).find('td:nth-child(4)').text()),
released: $(episode).find('td:nth-child(5)').text(),
seeds: parseInt($(episode).find('td:nth-child(6)').text()) || 0
}
}
export class EztvCrawlerException extends Error {} |