From b77057b0e1796983b37312c2bc1d247a82eeb0d9 Mon Sep 17 00:00:00 2001 From: Jasanpreet Nagra Date: Thu, 5 Sep 2024 11:11:18 -0500 Subject: [PATCH] feat: zoro - added continue watching --- docs/providers/zoro.md | 37 +++++++++++++++++++ src/providers/anime/zoro.ts | 74 ++++++++++++++++++++++++++++++++++--- test/anime/zoro.test.ts | 7 ++++ 3 files changed, 113 insertions(+), 5 deletions(-) diff --git a/docs/providers/zoro.md b/docs/providers/zoro.md index cd5e57b0c..fa6f38560 100644 --- a/docs/providers/zoro.md +++ b/docs/providers/zoro.md @@ -796,6 +796,43 @@ output: } ``` +### fetchContinueWatching + +

Parameters

+ + +| Parameter | Type | Description | +| ----------- | -------- | ------------------------------------------------ | +| connectSid | `string` | The session ID obtained from the website cookies | + +```ts +zoro.fetchContinueWatching("{user_connect_sid}").then(data => { + console.log(data); +}) +``` + +returns a promise which resolves into an array of episodes. (*[`Promise`](https://github.com/consumet/extensions/blob/master/src/models/types.ts#L13-L26)*)\ +output: +```js +[ + { + id: 'dragon-ball-super-broly-387$episode$58063', + title: 'Dragon Ball Super: Broly', + number: 1, + duration: '1:39:43', + watchedTime: '00:01', + url: 'http://hianime.to/watch/dragon-ball-super-broly-387?ep=58063', + image: 'https://cdn.noitatnemucod.net/thumbnail/300x400/100/6b138786ca53a86413d89806cb6836dd.jpg', + japaneseTitle: 'Dragon Ball Super Movie: Broly', + nsfw: false, + sub: 1, + dub: 1, + episodes: 0 + }, + {...}, + ... +] +``` Make sure to check the `headers` property of the returned object. It contains the referer header, which might be needed to bypass the 403 error and allow you to stream the video without any issues. diff --git a/src/providers/anime/zoro.ts b/src/providers/anime/zoro.ts index e476a2b7b..6f5d46330 100644 --- a/src/providers/anime/zoro.ts +++ b/src/providers/anime/zoro.ts @@ -11,6 +11,7 @@ import { StreamingServers, MediaFormat, SubOrSub, + IAnimeEpisode, } from '../../models'; import { StreamSB, RapidCloud, MegaCloud, StreamTape } from '../../utils'; @@ -25,11 +26,15 @@ class Zoro extends AnimeParser { constructor(customBaseURL?: string) { super(...arguments); - this.baseUrl = customBaseURL - ? customBaseURL.startsWith('http://') || customBaseURL.startsWith('https://') - ? customBaseURL - : `http://${customBaseURL}` - : this.baseUrl; + if (customBaseURL) { + if (customBaseURL.startsWith('http://') || customBaseURL.startsWith('https://')) { + this.baseUrl = customBaseURL; + } else { + this.baseUrl = `http://${customBaseURL}`; + } + } else { + this.baseUrl = this.baseUrl; + } } /** @@ -233,6 +238,52 @@ class Zoro extends AnimeParser { } } + /** + * Fetches the list of episodes that the user is currently watching. + * @param connectSid The session ID of the user. + * @returns A promise that resolves to an array of anime episodes. + */ + async fetchContinueWatching(connectSid: string): Promise { + try { + if (!(await this.verifyLoginState(connectSid))) { + throw new Error('Invalid session ID'); + } + const res: IAnimeEpisode[] = []; + const { data } = await this.client.get(`${this.baseUrl}/user/continue-watching`, { + headers: { + Cookie: `connect.sid=${connectSid}`, + }, + }); + const $ = load(data); + $('.flw-item').each((i, ele) => { + const card = $(ele); + const atag = card.find('.film-name a'); + const id = atag.attr('href')?.replace("/watch/", "")?.replace('?ep=', '$episode$'); + const timeText = card.find('.fdb-time')?.text()?.split("/") ?? []; + const duration = timeText.pop()?.trim() ?? ''; + 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: card.find('img')?.attr('data-src'), + japaneseTitle: atag.attr('data-jname'), + nsfw: card.find('.tick-rate')?.text() === '18+' ? true : false, + sub: parseInt(card.find('.tick-item.tick-sub')?.text()) || 0, + dub: parseInt(card.find('.tick-item.tick-dub')?.text()) || 0, + episodes: parseInt(card.find('.tick-item.tick-eps')?.text()) || 0, + }); + }); + + return res; + } catch (err) { + throw new Error((err as Error).message); + } + } + /** * @param id Anime id */ @@ -430,6 +481,19 @@ class Zoro extends AnimeParser { } }; + private verifyLoginState = async (connectSid: string): Promise => { + 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; + } + }; + private retrieveServerId = ($: any, index: number, subOrDub: 'sub' | 'dub') => { return $(`.ps_-block.ps_-block-sub.servers-${subOrDub} > .ps__-list .server-item`) .map((i: any, el: any) => ($(el).attr('data-server-id') == `${index}` ? $(el) : null)) diff --git a/test/anime/zoro.test.ts b/test/anime/zoro.test.ts index fb3025353..28d3a4787 100644 --- a/test/anime/zoro.test.ts +++ b/test/anime/zoro.test.ts @@ -64,6 +64,13 @@ test('returns a filled array of anime list', async () => { expect(data.results).not.toEqual([]); }); +test('returns a filled array of episode list for continue watching', async () => { + const connectSid = 'users_connect_sid'; + const data = await zoro.fetchContinueWatching(`${connectSid}`); + console.log(data) + expect(data).not.toEqual([]); +}); + test('returns a filled object of anime data', async () => { const res = await zoro.search('Overlord IV'); const data = await zoro.fetchAnimeInfo('one-piece-100'); // Overlord IV id