From 2e45cbfb471e9a20c4c953c5ee7ebf5ff381dd4a Mon Sep 17 00:00:00 2001 From: Swakshan Date: Fri, 10 Jan 2025 23:50:40 +0530 Subject: [PATCH 1/6] extension(sudatchi): Added popular & latest --- javascript/anime/src/en/sudatchi.js | 129 ++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 javascript/anime/src/en/sudatchi.js diff --git a/javascript/anime/src/en/sudatchi.js b/javascript/anime/src/en/sudatchi.js new file mode 100644 index 00000000..126e9425 --- /dev/null +++ b/javascript/anime/src/en/sudatchi.js @@ -0,0 +1,129 @@ +const mangayomiSources = [{ + "name": "Sudatchi", + "lang": "en", + "baseUrl": "https://sudatchi.com", + "apiUrl": "", + "iconUrl": "https://www.google.com/s2/favicons?sz=128&domain=https://sudatchi.com", + "typeSource": "single", + "isManga": null, + "version": "0.0.1", + "dateFormat": "", + "dateFormatLocale": "", + "pkgPath": "anime/src/en/sudatchi.js" +}]; + +class DefaultExtension extends MProvider { + getHeaders(url) { + return { + "Referer": this.source.baseUrl, + } + } + + getPreference(key) { + const preferences = new SharedPreferences(); + return preferences.get(key); + } + + getImgUrl(slug) { + return `https://ipfs.sudatchi.com/ipfs/${slug}` + } + + async extractFromUrl(url) { + var res = await new Client().get(url, this.getHeaders()); + var doc = new Document(res.body); + var jsonData = doc.selectFirst("#__NEXT_DATA__").text + return JSON.parse(jsonData); + } + + async formList(latestEpisodes) { + var list = [] + for (var item of latestEpisodes) { + var details = "Anime" in item ? item.Anime : item + var lang = this.getPreference("sudatchi_pref_lang") + var name = details.titleRomanji + switch (lang) { + case "e": { + name = "titleEnglish" in details ? details.titleEnglish : name; + break; + } + case "j": { + name = "titleJapanese" in details ? details.titleJapanese : name; + break; + } + + } + var link = details.slug + var imageUrl = this.getImgUrl(details.imgUrl) + list.push({ + name, + imageUrl, + link + }); + } + return list; + } + + async getPopular(page) { + var extract = await this.extractFromUrl(this.source.baseUrl) + var pageProps = extract.props.pageProps + var latestEpisodes = await this.formList(pageProps.latestEpisodes) + var latestAnimes = await this.formList(pageProps.latestAnimes) + var newAnimes = await this.formList(pageProps.newAnimes) + var animeSpotlight = await this.formList(pageProps.AnimeSpotlight) + var list = [...animeSpotlight, ...newAnimes, ...latestAnimes, ...latestEpisodes] + return { + list, + hasNextPage: false + }; + } + get supportsLatest() { + throw new Error("supportsLatest not implemented"); + } + async getLatestUpdates(page) { + var extract = await this.extractFromUrl(this.source.baseUrl) + var latest = extract.props.pageProps.latestEpisodes + var list = await this.formList(latest) + + return { + list, + hasNextPage: false + }; + } + 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() { + throw new Error("getPageList not implemented"); + } + getFilterList() { + throw new Error("getFilterList not implemented"); + } + getSourcePreferences() { + return [{ + key: 'sudatchi_pref_lang', + listPreference: { + title: 'Preferred title language', + summary: '', + valueIndex: 0, + entries: ["Romanji", "English", "Japanese"], + entryValues: ["r", "e", "j"] + } + },] + } +} From 333585d82f3d56a78a9a9056a721809e47d392f6 Mon Sep 17 00:00:00 2001 From: Swakshan Date: Sat, 11 Jan 2025 13:34:04 +0530 Subject: [PATCH 2/6] extension(sudatchi): Added search and filters --- javascript/anime/src/en/sudatchi.js | 121 +++++++++++++++++++++++++--- 1 file changed, 109 insertions(+), 12 deletions(-) diff --git a/javascript/anime/src/en/sudatchi.js b/javascript/anime/src/en/sudatchi.js index 126e9425..07e89636 100644 --- a/javascript/anime/src/en/sudatchi.js +++ b/javascript/anime/src/en/sudatchi.js @@ -6,7 +6,7 @@ const mangayomiSources = [{ "iconUrl": "https://www.google.com/s2/favicons?sz=128&domain=https://sudatchi.com", "typeSource": "single", "isManga": null, - "version": "0.0.1", + "version": "0.0.2", "dateFormat": "", "dateFormatLocale": "", "pkgPath": "anime/src/en/sudatchi.js" @@ -35,11 +35,12 @@ class DefaultExtension extends MProvider { return JSON.parse(jsonData); } - async formList(latestEpisodes) { + + async formListFromHome(animes) { var list = [] - for (var item of latestEpisodes) { + var lang = this.getPreference("sudatchi_pref_lang") + for (var item of animes) { var details = "Anime" in item ? item.Anime : item - var lang = this.getPreference("sudatchi_pref_lang") var name = details.titleRomanji switch (lang) { case "e": { @@ -66,11 +67,11 @@ class DefaultExtension extends MProvider { async getPopular(page) { var extract = await this.extractFromUrl(this.source.baseUrl) var pageProps = extract.props.pageProps - var latestEpisodes = await this.formList(pageProps.latestEpisodes) - var latestAnimes = await this.formList(pageProps.latestAnimes) - var newAnimes = await this.formList(pageProps.newAnimes) - var animeSpotlight = await this.formList(pageProps.AnimeSpotlight) - var list = [...animeSpotlight, ...newAnimes, ...latestAnimes, ...latestEpisodes] + var latestEpisodes = await this.formListFromHome(pageProps.latestEpisodes) + var latestAnimes = await this.formListFromHome(pageProps.latestAnimes) + var newAnimes = await this.formListFromHome(pageProps.newAnimes) + var animeSpotlight = await this.formListFromHome(pageProps.AnimeSpotlight) + var list = [...animeSpotlight, ...latestAnimes, ...latestEpisodes, ...newAnimes] return { list, hasNextPage: false @@ -82,7 +83,7 @@ class DefaultExtension extends MProvider { async getLatestUpdates(page) { var extract = await this.extractFromUrl(this.source.baseUrl) var latest = extract.props.pageProps.latestEpisodes - var list = await this.formList(latest) + var list = await this.formListFromHome(latest) return { list, @@ -90,7 +91,26 @@ class DefaultExtension extends MProvider { }; } async search(query, page, filters) { - throw new Error("search not implemented"); + var type = ''; + for (var filter of filters[0].state) if (filter.state) type += `,${filter.value}`; + var status = ''; + for (var filter of filters[1].state) if (filter.state) status += `,${filter.value}`; + var genre = ''; + for (var filter of filters[2].state) if (filter.state) genre += `,${filter.value}`; + var year = ''; + for (var filter of filters[3].state) if (filter.state) year += `,${filter.value}`; + + var api = `https://sudatchi.com/api/directory?page=${page}&genres=${genre}&years=${year}&types=${type}&status=${status}&title=${query}&category=` + var response = await new Client().get(api); + var body = JSON.parse(response.body); + + var list = await this.formListFromHome(body.animes) + var hasNextPage = body.pages > page ? true : false; + + return { + list, + hasNextPage + }; } async getDetail(url) { throw new Error("getDetail not implemented"); @@ -112,7 +132,84 @@ class DefaultExtension extends MProvider { throw new Error("getPageList not implemented"); } getFilterList() { - throw new Error("getFilterList not implemented"); + var currentYear = new Date().getFullYear(); + var formattedYears = Array.from({ length: currentYear - 2003 }, (_, i) => (i + 2004).toString()).map(year => ({ type_name: 'CheckBox', name: year, value: year })); + + return [ + { + type_name: "GroupFilter", + name: "Type", + state: [ + ["All", ""], + ["BD", "BD"], + ["Movie", "Movie"], + ["ONA", "ONA"], + ["OVA", "OVA"], + ["Special", "Special"], + ["TV", "TV"] + ].map(x => ({ type_name: 'CheckBox', name: x[0], value: x[1] })) + }, + { + type_name: "GroupFilter", + name: "Status", + state: [ + ["All", ""], + ["Currently Airing", "Currently Airing"], + ["Finished Airing", "Finished Airing"], + ["Hiatus", "Hiatus"], + ["Not Yet Released", "Not Yet Released"] + ].map(x => ({ type_name: 'CheckBox', name: x[0], value: x[1] })) + }, { + type_name: "GroupFilter", + name: "Genre", + state: [ + ["Action", "Action"], + ["Adventure", "Adventure"], + ["Comedy", "Comedy"], + ["Cyberpunk", "Cyberpunk"], + ["Demons", "Demons"], + ["Drama", "Drama"], + ["Ecchi", "Ecchi"], + ["Fantasy", "Fantasy"], + ["Harem", "Harem"], + ["Hentai", "Hentai"], + ["Historical", "Historical"], + ["Horror", "Horror"], + ["Isekai", "Isekai"], + ["Josei", "Josei"], + ["Magic", "Magic"], + ["Martial Arts", "Martial Arts"], + ["Mecha", "Mecha"], + ["Military", "Military"], + ["Music", "Music"], + ["Mystery", "Mystery"], + ["Police", "Police"], + ["Post-Apocalyptic", "Post-Apocalyptic"], + ["Psychological", "Psychological"], + ["Romance", "Romance"], + ["School", "School"], + ["Sci-Fi ", "Sci-Fi "], + ["Seinen", "Seinen"], + ["Shoujo", "Shoujo"], + ["Shounen", "Shounen"], + ["Slice of Life", "Slice of Life"], + ["Space", "Space"], + ["Sports", "Sports"], + ["Super Power", "Super Power"], + ["Supernatural", "Supernatural"], + ["Thriller", "Thriller"], + ["Tragedy", "Tragedy"], + ["Vampire", "Vampire"], + ["Yaoi", "Yaoi"], + ["Yuri", "Yuri"] + ].map(x => ({ type_name: 'CheckBox', name: x[0], value: x[1] })) + }, { + type_name: "GroupFilter", + name: "Year", + state: formattedYears + }, + + ]; } getSourcePreferences() { return [{ From e45a68f1a9e1e3f7085d39a69db6bbf4c0f5bbdb Mon Sep 17 00:00:00 2001 From: Swakshan Date: Sun, 12 Jan 2025 00:24:06 +0530 Subject: [PATCH 3/6] extension(sudatchi): Added details --- javascript/anime/src/en/sudatchi.js | 55 ++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/javascript/anime/src/en/sudatchi.js b/javascript/anime/src/en/sudatchi.js index 07e89636..4d5d78ed 100644 --- a/javascript/anime/src/en/sudatchi.js +++ b/javascript/anime/src/en/sudatchi.js @@ -32,7 +32,7 @@ class DefaultExtension extends MProvider { var res = await new Client().get(url, this.getHeaders()); var doc = new Document(res.body); var jsonData = doc.selectFirst("#__NEXT_DATA__").text - return JSON.parse(jsonData); + return JSON.parse(jsonData).props.pageProps; } @@ -65,8 +65,8 @@ class DefaultExtension extends MProvider { } async getPopular(page) { - var extract = await this.extractFromUrl(this.source.baseUrl) - var pageProps = extract.props.pageProps + var pageProps = await this.extractFromUrl(this.source.baseUrl) + // var = extract var latestEpisodes = await this.formListFromHome(pageProps.latestEpisodes) var latestAnimes = await this.formListFromHome(pageProps.latestAnimes) var newAnimes = await this.formListFromHome(pageProps.newAnimes) @@ -112,8 +112,55 @@ class DefaultExtension extends MProvider { hasNextPage }; } + + statusCode(status) { + return { + "Currently Airing": 0, + "Finished Airing": 1, + "Hiatus": 2, + "Discontinued": 3, + "Not Yet Released": 4, + }[status] ?? 5; + } + async getDetail(url) { - throw new Error("getDetail not implemented"); + var link = `https://sudatchi.com/anime/${url}` + var jsonData = await this.extractFromUrl(link); + var details = jsonData.animeData + var name = details.titleRomanji + var lang = this.getPreference("sudatchi_pref_lang") + switch (lang) { + case "e": { + name = "titleEnglish" in details ? details.titleEnglish : name; + break; + } + case "j": { + name = "titleJapanese" in details ? details.titleJapanese : name; + break; + } + } + var description = details.synopsis + var status = this.statusCode(details.Status.name) + var imageUrl = this.getImgUrl(details.imgUrl) + var genre = [] + var animeGenres = details.AnimeGenres + for (var gObj of animeGenres) { + genre.push(gObj.Genre.name) + } + + var chapters = [] + var episodes = details.Episodes + + for (var eObj of episodes) { + var name = eObj.title + var number = eObj.number + var epUrl = `${url}/${number}` + chapters.push({ name, url: epUrl }) + } + + + return { name, description, status, imageUrl, genre, chapters } + } // For novel html content async getHtmlContent(url) { From 963f4650544c67d270d14b981068267e6361a591 Mon Sep 17 00:00:00 2001 From: Swakshan Date: Sun, 12 Jan 2025 15:20:11 +0530 Subject: [PATCH 4/6] extension(sudatchi): Added streams --- javascript/anime/src/en/sudatchi.js | 90 +++++++++++++++++++++++++---- 1 file changed, 80 insertions(+), 10 deletions(-) diff --git a/javascript/anime/src/en/sudatchi.js b/javascript/anime/src/en/sudatchi.js index 4d5d78ed..31f00b54 100644 --- a/javascript/anime/src/en/sudatchi.js +++ b/javascript/anime/src/en/sudatchi.js @@ -6,7 +6,7 @@ const mangayomiSources = [{ "iconUrl": "https://www.google.com/s2/favicons?sz=128&domain=https://sudatchi.com", "typeSource": "single", "isManga": null, - "version": "0.0.2", + "version": "1.0.0", "dateFormat": "", "dateFormatLocale": "", "pkgPath": "anime/src/en/sudatchi.js" @@ -24,7 +24,7 @@ class DefaultExtension extends MProvider { return preferences.get(key); } - getImgUrl(slug) { + getUrl(slug) { return `https://ipfs.sudatchi.com/ipfs/${slug}` } @@ -54,7 +54,7 @@ class DefaultExtension extends MProvider { } var link = details.slug - var imageUrl = this.getImgUrl(details.imgUrl) + var imageUrl = this.getUrl(details.imgUrl) list.push({ name, imageUrl, @@ -123,6 +123,7 @@ class DefaultExtension extends MProvider { }[status] ?? 5; } + async getDetail(url) { var link = `https://sudatchi.com/anime/${url}` var jsonData = await this.extractFromUrl(link); @@ -141,7 +142,7 @@ class DefaultExtension extends MProvider { } var description = details.synopsis var status = this.statusCode(details.Status.name) - var imageUrl = this.getImgUrl(details.imgUrl) + var imageUrl = this.getUrl(details.imgUrl) var genre = [] var animeGenres = details.AnimeGenres for (var gObj of animeGenres) { @@ -150,12 +151,18 @@ class DefaultExtension extends MProvider { var chapters = [] var episodes = details.Episodes - - for (var eObj of episodes) { - var name = eObj.title - var number = eObj.number + var typeId = details.typeId + if (typeId == 6) { + var number = episodes[0].number var epUrl = `${url}/${number}` - chapters.push({ name, url: epUrl }) + chapters.push({ name: "Movie", url: epUrl }) + } else { + for (var eObj of episodes) { + var name = eObj.title + var number = eObj.number + var epUrl = `${url}/${number}` + chapters.push({ name, url: epUrl }) + } } @@ -170,9 +177,72 @@ class DefaultExtension extends MProvider { async cleanHtmlContent(html) { throw new Error("cleanHtmlContent not implemented"); } + + async extractStreams(url) { + const response = await new Client().get(url); + const body = response.body; + const lines = body.split('\n'); + var audios = [] + + var streams = [{ + url: url, + originalUrl: url, + quality: "auto", + }]; + + for (let i = 0; i < lines.length; i++) { + var currentLine = lines[i] + if (currentLine.startsWith('#EXT-X-STREAM-INF:')) { + var resolution = currentLine.match(/RESOLUTION=(\d+x\d+)/)[1]; + var m3u8Url = lines[i + 1].trim(); + m3u8Url = m3u8Url.replace("./", `${url}/`) + streams.push({ + url: m3u8Url, + originalUrl: m3u8Url, + quality: resolution, + }); + } else if (currentLine.startsWith('#EXT-X-MEDIA:TYPE=AUDIO')) { + var attributesString = currentLine.split(",") + var attributeRegex = /([A-Z-]+)=("([^"]*)"|[^,]*)/g; + let match; + var trackInfo = {}; + while ((match = attributeRegex.exec(attributesString)) !== null) { + var key = match[1]; + var value = match[3] || match[2]; + if (key === "NAME") { + trackInfo.label = value + } else if (key === "URI") { + trackInfo.file = value + } + } + audios.push(trackInfo); + } + } + streams[0].audios = audios + return streams + } + // For anime episode video list async getVideoList(url) { - throw new Error("getVideoList not implemented"); + var link = `https://sudatchi.com/watch/${url}` + var jsonData = await this.extractFromUrl(link); + var episodeData = jsonData.episodeData.episode; + var epId = episodeData.id; + + var epLink = `https://sudatchi.com/videos/m3u8/episode-${epId}.m3u8` + var streams = await this.extractStreams(epLink); + + var subs = JSON.parse(jsonData.episodeData.subtitlesJson) + var subtitles = []; + for (var sub of subs) { + var file = this.getUrl(sub.url) + var label = sub.SubtitlesName.name; + subtitles.push({ file: file, label: label }); + } + streams[0].subtitles = subtitles + + return streams; + } // For manga chapter pages async getPageList() { From 812844950b4a4ddcefd9946979892ad0b96228fc Mon Sep 17 00:00:00 2001 From: Swakshan Date: Sun, 12 Jan 2025 15:29:36 +0530 Subject: [PATCH 5/6] extension(sudatchi): reverse chapters --- javascript/anime/src/en/sudatchi.js | 1 + 1 file changed, 1 insertion(+) diff --git a/javascript/anime/src/en/sudatchi.js b/javascript/anime/src/en/sudatchi.js index 31f00b54..6ae1a489 100644 --- a/javascript/anime/src/en/sudatchi.js +++ b/javascript/anime/src/en/sudatchi.js @@ -165,6 +165,7 @@ class DefaultExtension extends MProvider { } } + chapters.reverse() return { name, description, status, imageUrl, genre, chapters } From e5d1b0502a68585bc08455cbe16a5da841fa361b Mon Sep 17 00:00:00 2001 From: Swakshan Date: Sun, 12 Jan 2025 15:36:24 +0530 Subject: [PATCH 6/6] extension(sudatchi): bug fixes --- javascript/anime/src/en/sudatchi.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/javascript/anime/src/en/sudatchi.js b/javascript/anime/src/en/sudatchi.js index 6ae1a489..8248102b 100644 --- a/javascript/anime/src/en/sudatchi.js +++ b/javascript/anime/src/en/sudatchi.js @@ -6,7 +6,7 @@ const mangayomiSources = [{ "iconUrl": "https://www.google.com/s2/favicons?sz=128&domain=https://sudatchi.com", "typeSource": "single", "isManga": null, - "version": "1.0.0", + "version": "1.0.1", "dateFormat": "", "dateFormatLocale": "", "pkgPath": "anime/src/en/sudatchi.js" @@ -158,16 +158,16 @@ class DefaultExtension extends MProvider { chapters.push({ name: "Movie", url: epUrl }) } else { for (var eObj of episodes) { - var name = eObj.title + var epName = eObj.title var number = eObj.number var epUrl = `${url}/${number}` - chapters.push({ name, url: epUrl }) + chapters.push({ name:epName, url: epUrl }) } } chapters.reverse() - return { name, description, status, imageUrl, genre, chapters } + return { name, description, status, imageUrl, genre, chapters ,link} } // For novel html content