From 93b62f38f126e719f58b7217896c0dc7d93215c4 Mon Sep 17 00:00:00 2001 From: Swakshan Date: Mon, 27 Jan 2025 16:05:42 +0530 Subject: [PATCH 1/7] extension(Animeparadise): Added popular & latest --- javascript/anime/src/en/animeparadise.js | 80 ++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 javascript/anime/src/en/animeparadise.js diff --git a/javascript/anime/src/en/animeparadise.js b/javascript/anime/src/en/animeparadise.js new file mode 100644 index 00000000..acb0b915 --- /dev/null +++ b/javascript/anime/src/en/animeparadise.js @@ -0,0 +1,80 @@ +const mangayomiSources = [{ + "name": "Animeparadise", + "lang": "en", + "baseUrl": "https://animeparadise.moe", + "apiUrl": "https://api.animeparadise.moe", + "iconUrl": "https://www.google.com/s2/favicons?sz=128&domain=https://animeparadise.moe", + "typeSource": "single", + "itemType": 1, + "version": "0.0.1", + "pkgPath": "anime/src/en/animeparadise.js" +}]; + +class DefaultExtension extends MProvider { + getHeaders(url) { + throw new Error("getHeaders not implemented"); + } + + async requestAPI(slug){ + var api = `${this.source.apiUrl}/${slug}` + var response = await new Client().get(api); + var body = JSON.parse(response.body); + return body; + } + + async formList(slug){ + var jsonData = await this.requestAPI(slug); + var list = []; + jsonData.data.forEach(item => { + list.push({ + "name": item.title, + "link": item.link, + "imageUrl": item.posterImage.original + }); + }) + + return { + "list":list, + "hasNextPage":false + } + + } + + async getPopular(page) { + return await this.formList('?sort={"rate": -1 }') + } + get supportsLatest() { + throw new Error("supportsLatest not implemented"); + } + async getLatestUpdates(page) { + return await this.formList('?sort={"postDate": -1 }') + } + async search(query, page, filters) { + throw new Error("search not implemented"); + } + async getDetail(url) { + throw new Error("getDetail not implemented"); + } + // For novel html content + async getHtmlContent(url) { + throw new Error("getHtmlContent not implemented"); + } + // Clean html up for reader + async cleanHtmlContent(html) { + throw new Error("cleanHtmlContent not implemented"); + } + // For anime episode video list + async getVideoList(url) { + throw new Error("getVideoList not implemented"); + } + // For manga chapter pages + async getPageList(url) { + throw new Error("getPageList not implemented"); + } + getFilterList() { + throw new Error("getFilterList not implemented"); + } + getSourcePreferences() { + throw new Error("getSourcePreferences not implemented"); + } +} From 7ee0f13eff9e38ada79cb4624f73b36d6116567d Mon Sep 17 00:00:00 2001 From: Swakshan Date: Mon, 27 Jan 2025 16:41:56 +0530 Subject: [PATCH 2/7] extension(Animeparadise): customise latest tab --- javascript/anime/src/en/animeparadise.js | 59 ++++++++++++++++++------ 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/javascript/anime/src/en/animeparadise.js b/javascript/anime/src/en/animeparadise.js index acb0b915..3610bc0e 100644 --- a/javascript/anime/src/en/animeparadise.js +++ b/javascript/anime/src/en/animeparadise.js @@ -6,7 +6,7 @@ const mangayomiSources = [{ "iconUrl": "https://www.google.com/s2/favicons?sz=128&domain=https://animeparadise.moe", "typeSource": "single", "itemType": 1, - "version": "0.0.1", + "version": "0.0.2", "pkgPath": "anime/src/en/animeparadise.js" }]; @@ -15,27 +15,42 @@ class DefaultExtension extends MProvider { throw new Error("getHeaders not implemented"); } - async requestAPI(slug){ + getPreference(key) { + const preferences = new SharedPreferences(); + return preferences.get(key); + } + + async requestAPI(slug) { var api = `${this.source.apiUrl}/${slug}` var response = await new Client().get(api); var body = JSON.parse(response.body); return body; } - async formList(slug){ + async formList(slug) { var jsonData = await this.requestAPI(slug); var list = []; - jsonData.data.forEach(item => { - list.push({ - "name": item.title, - "link": item.link, - "imageUrl": item.posterImage.original - }); - }) - + if ("episodes" in jsonData) { + jsonData.episodes.forEach(item => { + list.push({ + "name": item.origin.title, + "link": item.origin.link, + "imageUrl": item.image + }); + }) + } else { + jsonData.data.forEach(item => { + list.push({ + "name": item.title, + "link": item.link, + "imageUrl": item.posterImage.original + }); + }) + } + return { - "list":list, - "hasNextPage":false + "list": list, + "hasNextPage": false } } @@ -47,7 +62,12 @@ class DefaultExtension extends MProvider { throw new Error("supportsLatest not implemented"); } async getLatestUpdates(page) { - return await this.formList('?sort={"postDate": -1 }') + var slug = '?sort={"postDate": -1 }'; + + var choice = this.getPreference("animeparadise_pref_latest_tab"); + if (choice === "recent_ep") slug = 'ep/recently-added'; + + return await this.formList(slug) } async search(query, page, filters) { throw new Error("search not implemented"); @@ -75,6 +95,15 @@ class DefaultExtension extends MProvider { throw new Error("getFilterList not implemented"); } getSourcePreferences() { - throw new Error("getSourcePreferences not implemented"); + return [{ + key: 'animeparadise_pref_latest_tab', + listPreference: { + title: 'Latest tab category', + summary: 'Anime list to be shown in latest tab', + valueIndex: 0, + entries: ["Recently added anime", "Recently added episode"], + entryValues: ["recent_ani", "recent_ep"] + } + },] } } From 8157b91d8bbf45c55df9a722877313e666050a4e Mon Sep 17 00:00:00 2001 From: Swakshan Date: Mon, 3 Feb 2025 23:39:49 +0530 Subject: [PATCH 3/7] extension(Animeparadise): Added details --- javascript/anime/src/en/animeparadise.js | 36 ++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/javascript/anime/src/en/animeparadise.js b/javascript/anime/src/en/animeparadise.js index 3610bc0e..bcd1e348 100644 --- a/javascript/anime/src/en/animeparadise.js +++ b/javascript/anime/src/en/animeparadise.js @@ -6,7 +6,7 @@ const mangayomiSources = [{ "iconUrl": "https://www.google.com/s2/favicons?sz=128&domain=https://animeparadise.moe", "typeSource": "single", "itemType": 1, - "version": "0.0.2", + "version": "0.0.3", "pkgPath": "anime/src/en/animeparadise.js" }]; @@ -20,6 +20,13 @@ class DefaultExtension extends MProvider { return preferences.get(key); } + async extractFromUrl(url) { + var res = await new Client().get(url); + var doc = new Document(res.body); + var jsonData = doc.selectFirst("#__NEXT_DATA__").text + return JSON.parse(jsonData).props.pageProps.data; + } + async requestAPI(slug) { var api = `${this.source.apiUrl}/${slug}` var response = await new Client().get(api); @@ -72,8 +79,33 @@ class DefaultExtension extends MProvider { async search(query, page, filters) { throw new Error("search not implemented"); } + statusCode(status) { + return { + "current": 0, + "finished": 1, + }[status] ?? 5; + } + async getDetail(url) { - throw new Error("getDetail not implemented"); + var link = this.source.baseUrl + `/anime/${url}` + var jsonData = await this.extractFromUrl(link) + var details = {} + var chapters = [] + details.name = jsonData.title + details.link = link + details.imageUrl = jsonData.posterImage.original + details.description = jsonData.synopsys + details.genre = jsonData.genres + details.status = this.statusCode(jsonData.status) + var id = jsonData._id + var epAPI = await this.requestAPI(`anime/${id}/episode`) + epAPI.data.forEach(ep => { + var epName = `E${ep.number}: ${ep.title}`; + var epUrl = `${ep.uid}?origin=${ep.origin}` + chapters.push({ name: epName, url: epUrl }) + }) + details.chapters = chapters.reverse(); + return details; } // For novel html content async getHtmlContent(url) { From 5d6f400bf97f6cb56afc0a1d325801e5d55a9474 Mon Sep 17 00:00:00 2001 From: Swakshan Date: Tue, 4 Feb 2025 12:06:07 +0530 Subject: [PATCH 4/7] extension(Animeparadise): extract stream --- javascript/anime/src/en/animeparadise.js | 87 ++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 6 deletions(-) diff --git a/javascript/anime/src/en/animeparadise.js b/javascript/anime/src/en/animeparadise.js index bcd1e348..dde455c9 100644 --- a/javascript/anime/src/en/animeparadise.js +++ b/javascript/anime/src/en/animeparadise.js @@ -6,7 +6,7 @@ const mangayomiSources = [{ "iconUrl": "https://www.google.com/s2/favicons?sz=128&domain=https://animeparadise.moe", "typeSource": "single", "itemType": 1, - "version": "0.0.3", + "version": "0.0.4", "pkgPath": "anime/src/en/animeparadise.js" }]; @@ -21,10 +21,10 @@ class DefaultExtension extends MProvider { } async extractFromUrl(url) { - var res = await new Client().get(url); + var res = await new Client().get(this.source.baseUrl + url); var doc = new Document(res.body); var jsonData = doc.selectFirst("#__NEXT_DATA__").text - return JSON.parse(jsonData).props.pageProps.data; + return JSON.parse(jsonData).props.pageProps } async requestAPI(slug) { @@ -88,7 +88,8 @@ class DefaultExtension extends MProvider { async getDetail(url) { var link = this.source.baseUrl + `/anime/${url}` - var jsonData = await this.extractFromUrl(link) + var jsonData = await this.extractFromUrl(`/anime/${url}`) + jsonData = jsonData.data var details = {} var chapters = [] details.name = jsonData.title @@ -115,9 +116,73 @@ class DefaultExtension extends MProvider { async cleanHtmlContent(html) { throw new Error("cleanHtmlContent not implemented"); } + // Sorts streams based on user preference. + async sortStreams(streams) { + var sortedStreams = []; + var copyStreams = streams.slice() + + var pref = await this.getPreference("animeparadise_pref_video_resolution"); + for (var stream of streams) { + + if (stream.quality.indexOf(pref) > -1) { + sortedStreams.push(stream); + var index = copyStreams.indexOf(stream); + if (index > -1) { + copyStreams.splice(index, 1); + } + break; + } + } + return [...sortedStreams, ...copyStreams] + } + + // Extracts the streams url for different resolutions from a hls stream. + async extractStreams(url) { + const response = await new Client().get(url); + const body = response.body; + const lines = body.split('\n'); + var streams = [{ + url: url, + originalUrl: url, + quality: `Auto`, + }]; + + for (let i = 0; i < lines.length; i++) { + if (lines[i].startsWith('#EXT-X-STREAM-INF:')) { + var resolution = lines[i].match(/RESOLUTION=(\d+x\d+)/)[1]; + var m3u8Url = lines[i + 1].trim(); + m3u8Url = url.replace("master.m3u8", m3u8Url) + + streams.push({ + url: m3u8Url, + originalUrl: m3u8Url, + quality: resolution + }); + } + } + return streams + + } + // For anime episode video list async getVideoList(url) { - throw new Error("getVideoList not implemented"); + var streams = [] + var jsonData = await this.extractFromUrl(`/watch/${url}`); + var epData = jsonData.episode + streams = await this.extractStreams(epData.streamLink) + + var subtitles = [] + epData.subData.forEach(sub => { + subtitles.push({ + "label": sub.label, + "file": `${this.source.apiUrl}/stream/file/${sub.src}`, + }); + }) + + streams[0].subtitles = subtitles + + return streams + } // For manga chapter pages async getPageList(url) { @@ -136,6 +201,16 @@ class DefaultExtension extends MProvider { entries: ["Recently added anime", "Recently added episode"], entryValues: ["recent_ani", "recent_ep"] } - },] + }, { + key: 'animeparadise_pref_video_resolution', + listPreference: { + title: 'Preferred video resolution', + summary: '', + valueIndex: 0, + entries: ["Auto", "1080p", "720p", "360p"], + entryValues: ["auto", "1080", "720", "360"] + } + }, + ] } } From 352cce6575bae2d1ddd3b6ff0cdd9868d03024e2 Mon Sep 17 00:00:00 2001 From: Swakshan Date: Thu, 6 Feb 2025 16:20:17 +0530 Subject: [PATCH 5/7] extension(Animeparadise): Added search & filter --- javascript/anime/src/en/animeparadise.js | 52 ++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/javascript/anime/src/en/animeparadise.js b/javascript/anime/src/en/animeparadise.js index dde455c9..517efa88 100644 --- a/javascript/anime/src/en/animeparadise.js +++ b/javascript/anime/src/en/animeparadise.js @@ -6,7 +6,7 @@ const mangayomiSources = [{ "iconUrl": "https://www.google.com/s2/favicons?sz=128&domain=https://animeparadise.moe", "typeSource": "single", "itemType": 1, - "version": "0.0.4", + "version": "0.0.5", "pkgPath": "anime/src/en/animeparadise.js" }]; @@ -77,7 +77,16 @@ class DefaultExtension extends MProvider { return await this.formList(slug) } async search(query, page, filters) { - throw new Error("search not implemented"); + var season = filters[0].values[filters[0].state].value + var year = filters[1].values[filters[1].state].value + + var genre = "genre[]=" + for (var filter of filters[2].state) { + if (filter.state == true) + genre += `${filter.value}&genre[]=` + } + var slug = `search?q=${query}&year=${year}&season=${season}&${genre}` + return await this.formList(slug); } statusCode(status) { return { @@ -188,9 +197,44 @@ class DefaultExtension extends MProvider { async getPageList(url) { throw new Error("getPageList not implemented"); } - getFilterList() { - throw new Error("getFilterList not implemented"); + + addCatogory(arr, typ) { + arr = arr.map(x => ({ type_name: typ, name: x, value: x })) + arr.unshift({ + type_name: typ, + name: 'All', + value: '' + }) + return arr } + + getFilterList() { + var seasons = ["Winter", "Spring", "Summer", "Fall"] + + const currentYear = new Date().getFullYear(); + var years = Array.from({ length: currentYear - 1939 }, (_, i) => (i + 1940).toString()).reverse() + + var genres = ["Action", "Adventure", "Comedy", "Drama", "Ecchi", "Fantasy", "Horror", "Mahou Shojo", "Mecha", "Music", "Mystery", "Psychological", "Romance", "Sci-Fi", "Slice of Life", "Sports", "Supernatural", "Thriller"].map(x => ({ type_name: "CheckBox", name: x, value: x })) + + return [ + { + type_name: "SelectFilter", + name: "Season", + state: 0, + values: this.addCatogory(seasons, "SelectOption") + }, { + type_name: "SelectFilter", + name: "Year", + state: 0, + values: this.addCatogory(years, "SelectOption") + }, { + type_name: "GroupFilter", + name: "Genres", + state: genres + } + ] + } + getSourcePreferences() { return [{ key: 'animeparadise_pref_latest_tab', From f9d6caaf9538a01606151537ec1894c1159a6b4f Mon Sep 17 00:00:00 2001 From: Swakshan Date: Thu, 6 Feb 2025 16:23:12 +0530 Subject: [PATCH 6/7] extension(Animeparadise): remove unused --- javascript/anime/src/en/animeparadise.js | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/javascript/anime/src/en/animeparadise.js b/javascript/anime/src/en/animeparadise.js index 517efa88..432532af 100644 --- a/javascript/anime/src/en/animeparadise.js +++ b/javascript/anime/src/en/animeparadise.js @@ -6,7 +6,7 @@ const mangayomiSources = [{ "iconUrl": "https://www.google.com/s2/favicons?sz=128&domain=https://animeparadise.moe", "typeSource": "single", "itemType": 1, - "version": "0.0.5", + "version": "0.0.6", "pkgPath": "anime/src/en/animeparadise.js" }]; @@ -117,14 +117,6 @@ class DefaultExtension extends MProvider { details.chapters = chapters.reverse(); return details; } - // For novel html content - async getHtmlContent(url) { - throw new Error("getHtmlContent not implemented"); - } - // Clean html up for reader - async cleanHtmlContent(html) { - throw new Error("cleanHtmlContent not implemented"); - } // Sorts streams based on user preference. async sortStreams(streams) { var sortedStreams = []; @@ -193,10 +185,6 @@ class DefaultExtension extends MProvider { return streams } - // For manga chapter pages - async getPageList(url) { - throw new Error("getPageList not implemented"); - } addCatogory(arr, typ) { arr = arr.map(x => ({ type_name: typ, name: x, value: x })) From faa2a0935a31ab76c73786cea654aed27cb662fb Mon Sep 17 00:00:00 2001 From: Moustapha Kodjo Amadou Date: Fri, 7 Feb 2025 15:08:06 +0100 Subject: [PATCH 7/7] Update animeparadise.js --- javascript/anime/src/en/animeparadise.js | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/javascript/anime/src/en/animeparadise.js b/javascript/anime/src/en/animeparadise.js index 432532af..b5c60969 100644 --- a/javascript/anime/src/en/animeparadise.js +++ b/javascript/anime/src/en/animeparadise.js @@ -1,19 +1,16 @@ const mangayomiSources = [{ - "name": "Animeparadise", + "name": "AnimeParadise", "lang": "en", "baseUrl": "https://animeparadise.moe", "apiUrl": "https://api.animeparadise.moe", "iconUrl": "https://www.google.com/s2/favicons?sz=128&domain=https://animeparadise.moe", "typeSource": "single", "itemType": 1, - "version": "0.0.6", + "version": "0.0.1", "pkgPath": "anime/src/en/animeparadise.js" }]; class DefaultExtension extends MProvider { - getHeaders(url) { - throw new Error("getHeaders not implemented"); - } getPreference(key) { const preferences = new SharedPreferences(); @@ -65,9 +62,7 @@ class DefaultExtension extends MProvider { async getPopular(page) { return await this.formList('?sort={"rate": -1 }') } - get supportsLatest() { - throw new Error("supportsLatest not implemented"); - } + async getLatestUpdates(page) { var slug = '?sort={"postDate": -1 }'; @@ -101,8 +96,6 @@ class DefaultExtension extends MProvider { jsonData = jsonData.data var details = {} var chapters = [] - details.name = jsonData.title - details.link = link details.imageUrl = jsonData.posterImage.original details.description = jsonData.synopsys details.genre = jsonData.genres