diff --git a/javascript/anime/src/all/soaper.js b/javascript/anime/src/all/soaper.js new file mode 100644 index 00000000..ba4adac1 --- /dev/null +++ b/javascript/anime/src/all/soaper.js @@ -0,0 +1,263 @@ +const mangayomiSources = [{ + "name": "Soaper", + "lang": "all", + "baseUrl": "https://soaper.cc", + "apiUrl": "", + "iconUrl": "https://www.google.com/s2/favicons?sz=128&domain=https://soaper.cc/", + "typeSource": "multi", + "isManga": false, + "version": "1.0.2", + "dateFormat": "", + "dateFormatLocale": "", + "pkgPath": "anime/src/all/soaper.js" +}]; + +class DefaultExtension extends MProvider { + getHeaders(url) { + return { + "Referer": url, + "Origin": url + } + } + + getPreference(key) { + return new SharedPreferences().get(key); + } + + getBasueUrl() { + return this.getPreference("soaper_override_base_url") + } + + async request(slug) { + const baseUrl = this.getBasueUrl() + var url = `${baseUrl}/${slug}` + var res = await new Client().get(url, this.getHeaders(baseUrl)); + var doc = new Document(res.body); + return doc + } + + async requestJSON(slug, data) { + const baseUrl = this.getBasueUrl() + var url = `${baseUrl}/${slug}` + var res = await new Client().post(url, this.getHeaders(baseUrl), data); + return JSON.parse(res.body); + } + + async formatList(slug, page) { + const baseUrl = this.getPreference("soaper_override_base_url") + slug = parseInt(page) > 1 ? `${slug}?page=${page}` : slug + var doc = await this.request(slug); + var list = []; + var movies = doc.select(".thumbnail.text-center") + + for (var movie of movies) { + var linkSection = movie.selectFirst("div.img-group > a") + var link = linkSection.getHref.substring(1,); + var poster = linkSection.selectFirst("img").getSrc + var imageUrl = `${baseUrl}${poster}` + var name = movie.selectFirst("h5").selectFirst("a").text; + + list.push({ name, imageUrl, link }); + } + + var hasNextPage = false + if (slug.indexOf("search.html?") == -1) { + var pagination = doc.select("ul.pagination > li") + var last_page_num = parseInt(pagination[pagination.length - 2].text); + hasNextPage = page < last_page_num ? true : false; + } + return { list, hasNextPage } + } + + async filterList(year = "all", genre = "all", sort = "new", page = 1) { + year = year == "all" ? "" : `/year/${year}` + genre = genre == "all" ? "" : `/cat/${genre}` + sort = sort == "new" ? "" : `/sort/${sort}` + + var slug = `${sort}${year}${genre}` + var movieList = await this.formatList(`movielist${slug}`, page); + var seriesList = await this.formatList(`tvlist${slug}`, page); + + var list = []; + var priority = this.getPreference("soaper_content_priority"); + if (priority === "series") { + list = [...seriesList.list, ...movieList.list]; + } else { + list = [...movieList.list, ...seriesList.list]; + } + + var hasNextPage = seriesList.hasNextPage || movieList.hasNextPage; + + return { list, hasNextPage } + } + + async getPopular(page) { + return await this.filterList("all", "all", "hot", page); + } + get supportsLatest() { + throw new Error("supportsLatest not implemented"); + } + async getLatestUpdates(page) { + return await this.filterList("all", "all", "new", page); + } + + async search(query, page, filters) { + var seriesList = [] + var movieList = [] + var list = []; + + var res = await this.formatList(`search.html?keyword=${query}`, 1) + var movies = res["list"] + + for (var movie of movies) { + var link = movie.link + if (link.indexOf("tv_") != -1) { + seriesList.push(movie); + } else { + movieList.push(movie); + } + } + + var priority = this.getPreference("soaper_content_priority"); + if (priority === "series") { + list = [...seriesList, ...movieList]; + } else { + list = [...movieList, ...seriesList]; + } + + return { list, hasNextPage: false } + } + + async getDetail(url) { + var doc = await this.request(url); + + const baseUrl = this.getPreference("soaper_override_base_url") + var name = doc.selectFirst(".col-sm-12.col-lg-12.text-center").selectFirst("h4").text.trim() + var poster = doc.selectFirst(".thumbnail.text-center").selectFirst("img").getSrc + var imageUrl = `${baseUrl}${poster}` + + var description = doc.selectFirst("p#wrap").text.trim() + var link = `${baseUrl}/${url}` + + var chapters = [] + if (url.indexOf("tv_") != -1) { + var seasonList = doc.select(".alert.alert-info-ex.col-sm-12") + var seasonCount = seasonList.length + for (var season of seasonList) { + var eps = season.select(".col-sm-12.col-md-6.col-lg-4.myp1") + for (var ep of eps) { + var epLinkSection = ep.selectFirst("a") + var epLink = epLinkSection.getHref.substring(1) + var epName = epLinkSection.text + + chapters.push({ + name: `S${seasonCount}E${epName}`, + url: epLink, + }) + } + seasonCount--; + } + + } else { + chapters.push({ + name: "Movie", + url: url, + }) + } + + return { name, imageUrl, description, link, chapters } + } + // For anime episode video list + async getVideoList(url) { + var body = await this.request(url) + var baseUrl = this.getBasueUrl() + var streams = [] + + // Traditional servers + var eId = body.selectFirst("#hId").attr('value') + var hIsW = body.selectFirst("#hIsW").attr('value') + var apiType = url[0].toUpperCase() + + var servers = [0, 1] + for (var serverNum of servers) { + var serverName = body.selectFirst(`#server_button_${serverNum}`).text + if (serverName.length < 1) continue; + var data = { + pass: eId, + param: "", + extra: "1", + e2: hIsW, + server: "" + serverNum, + } + var res = await this.requestJSON(`home/index/Get${apiType}InfoAjax`, data) + + var streamUrl = baseUrl + res.val + var subs = [] + for (var sub of res.subs) { + subs.push({ + file: baseUrl + sub.path, + label: sub.name + }) + } + streams.push({ + url: streamUrl, + originalUrl: streamUrl, + quality: serverName, + subtitles: subs + }); + } + + // Download servers + var modal_footer = body.select(".modal-footer > a") + if (modal_footer.length > 0) { + + modal_footer.reverse() + for (var item of modal_footer) { + var dSlug = item.getHref + var dBody = await this.request(dSlug) + + var res = dBody.selectFirst("#res").attr('value') + var mb = dBody.selectFirst("#mb").attr('value') + var streamLink = dBody.selectFirst("#link").attr('value') + + streams.push({ + url: streamLink, + originalUrl: streamLink, + quality: `Download Server: ${res} [${mb}]`, + }); + } + } + return streams + + } + // For manga chapter pages + async getPageList() { + throw new Error("getPageList not implemented"); + } + getFilterList() { + throw new Error("getFilterList not implemented"); + } + + getSourcePreferences() { + return [{ + "key": "soaper_override_base_url", + editTextPreference: { + title: "Override base url", + summary: "Default: https://soaper.cc", + value: "https://soaper.cc", + dialogTitle: "Override base url", + dialogMessage: "", + } + }, { + key: 'soaper_content_priority', + listPreference: { + title: 'Preferred content priority', + summary: 'Choose which type of content to show first', + valueIndex: 0, + entries: ["Movies", "Series"], + entryValues: ["movies", "series"] + } + }, + ]; + } +}