diff --git a/javascript/manga/src/en/weebcentral.js b/javascript/manga/src/en/weebcentral.js new file mode 100644 index 00000000..35153a15 --- /dev/null +++ b/javascript/manga/src/en/weebcentral.js @@ -0,0 +1,272 @@ +const mangayomiSources = [{ + "name": "Weebcentral", + "lang": "en", + "baseUrl": "https://weebcentral.com", + "apiUrl": "", + "iconUrl": "https://www.google.com/s2/favicons?sz=128&domain=https://weebcentral.com", + "typeSource": "single", + "itemType": 0, + "version": "0.0.5", + "pkgPath": "manga/src/en/weebcentral.js" +}]; + +class DefaultExtension extends MProvider { + getHeaders(url) { + throw new Error("getHeaders not implemented"); + } + + async request(slug) { + var url = `${this.source.baseUrl}/${slug}` + var res = await new Client().get(url); + return new Document(res.body); + } + + async getMangaList(slug, page = 0) { + var page = parseInt(page) + + slug = page > 0 ? `${slug}/${page}` : slug + + var doc = await this.request(slug); + var list = []; + var mangaElements = doc.select("article.bg-base-100") + for (var manga of mangaElements) { + var details = manga.selectFirst('a') + if (details.getHref.indexOf("/series/") < 0) continue; + + details = details.selectFirst("img"); + + var imageUrl = details.getSrc; + var urlSplits = imageUrl.split("/") + + var link = urlSplits[urlSplits.length - 1].split(".")[0] + var name = details.attr("alt") + + list.push({ name, imageUrl, link }); + } + var hasNextPage = page < 142 ? true : false; + return { list, hasNextPage } + } + + async getPopular(page) { + return await this.getMangaList("hot-updates") + } + get supportsLatest() { + throw new Error("supportsLatest not implemented"); + } + async getLatestUpdates(page) { + return await this.getMangaList("latest-updates", page) + } + + getImageUrl(id) { return `https://temp.compsci88.com/cover/normal/${id}.webp`; } + + async search(query, page, filters) { + var offset = 32 * (parseInt(page) - 1) + var sort = filters[0].values[filters[0].state].value + var order = filters[1].values[filters[1].state].value + var translation = filters[2].values[filters[2].state].value + var status = "" + for (var filter of filters[3].state) { + if (filter.state == true) + status += `&included_status=${filter.value}` + } + var type = "" + for (var filter of filters[4].state) { + if (filter.state == true) + type += `&included_type=${filter.value}` + } + var tags = "" + for (var filter of filters[5].state) { + if (filter.state == true) + tags += `&included_tag=${filter.value}` + } + + var slug = `search/data?limit=32&offset=${offset}&author=&text=${query}&sort=${sort}&order=${order}&official=${translation}${status}${type}${tags}&display_mode=Minimal%20Display` + var doc = await this.request(slug); + var list = [] + var mangaElements = doc.select("article.bg-base-300") + for (var manga of mangaElements) { + var details = manga.selectFirst('a') + + var mangaLink = details.getHref; + var urlSplits = mangaLink.split("/") + + var link = urlSplits[urlSplits.length - 2] + var name = details.selectFirst("h2").text + var imageUrl = this.getImageUrl(link) + + list.push({ name, imageUrl, link }); + } + + var hasNextPage = doc.selectFirst("button").text.length > 0; + return { list, hasNextPage } + + } + + statusCode(status) { + return { + "Ongoing": 0, + "Complete": 1, + "Hiatus": 2, + "Canceled": 3, + }[status] ?? 5; + } + + async getDetail(url) { + var slug = `series/${url}` + var link = `${this.source.baseUrl}/${slug}` + var doc = await this.request(slug); + var imageUrl = this.getImageUrl(url) + var title = doc.selectFirst("h1").text + var description = doc.selectFirst("p.whitespace-pre-wrap.break-words").text + + var chapters = [] + var ul = doc.select("ul.flex.flex-col.gap-4 > li") + var author = "" + var genre = [] + var status = 5 + for (var li of ul) { + var strongTxt = li.selectFirst("strong").text + if (strongTxt.indexOf("Author(s):") != -1) { + author = li.selectFirst("a").text + } else if (strongTxt.indexOf("Tags(s):") != -1) { + li.select("a").forEach(a => genre.push(a.text)) + } else if (strongTxt.indexOf("Status:") != -1) { + status = this.statusCode(li.selectFirst("a").text) + } + + } + + var chapSlug = `${slug}/full-chapter-list` + doc = await this.request(chapSlug); + var chapList = doc.select("div.flex.items-center"); + for (var chap of chapList) { + var name = chap.selectFirst("span.grow.flex.items-center.gap-2").selectFirst("span").text + var dateUpload = new Date(chap.selectFirst("time.text-datetime").text).valueOf().toString() + var url = chap.selectFirst("input").attr("value") + chapters.push({ name, url, dateUpload }) + } + return { name: title, description, link, imageUrl, author, genre, status, chapters } + + } + // 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) { + var slug = `chapters/${url}/images?current_page=1&reading_style=long_strip` + var doc = await this.request(slug); + + var urls = []; + + doc.select("section > img").forEach(page => urls.push(page.attr("src"))) + + return urls + } + + getFilterList() { + return [ + { + type_name: "SelectFilter", + name: "Sort", + state: 0, + values: [ + ["Best Match", "Best Match"], + ["Alphabet", "Alphabet"], + ["Popularity", "Popularity"], + ["Subscribers", "Subscribers"], + ["Recently Added", "Recently Added"], + ["Latest Updates", "Latest Updates"] + ].map(x => ({ type_name: 'SelectOption', name: x[0], value: x[1] })) + }, { + type_name: "SelectFilter", + name: "Order", + state: 0, + values: [ + ["Ascending", "Ascending"], + ["Descending", "Descending"] + ].map(x => ({ type_name: 'SelectOption', name: x[0], value: x[1] })) + }, { + type_name: "SelectFilter", + name: "Official Translation", + state: 0, + values: [ + ["Any", "Any"], + ["True", "True"], + ["False", "False"], + ].map(x => ({ type_name: 'SelectOption', name: x[0], value: x[1] })) + }, { + type_name: "GroupFilter", + name: "Series Status", + state: [ + ["Ongoing", "Ongoing"], + ["Complete", "Complete"], + ["Hiatus", "Hiatus"], + ["Canceled", "Canceled"], + ].map(x => ({ type_name: 'CheckBox', name: x[0], value: x[1] })) + }, { + type_name: "GroupFilter", + name: "Series Type", + state: [ + ["Manga", "Manga"], + ["Manhwa", "Manhwa"], + ["Manhua", "Manhua"], + ["OEL", "OEL"], + ].map(x => ({ type_name: 'CheckBox', name: x[0], value: x[1] })) + }, { + type_name: "GroupFilter", + name: "Tags", + state: [ + ["Action", "Action"], + ["Adventure", "Adventure"], + ["Adult", "Adult"], + ["Comedy", "Comedy"], + ["Doujinshi", "Doujinshi"], + ["Drama", "Drama"], + ["Ecchi", "Ecchi"], + ["Fantasy", "Fantasy"], + ["Gender Bender", "Gender Bender"], + ["Harem", "Harem"], + ["Hentai", "Hentai"], + ["Historical", "Historical"], + ["Horror", "Horror"], + ["Isekai", "Isekai"], + ["Josei", "Josei"], + ["Lolicon", "Lolicon"], + ["Martial Arts", "Martial Arts"], + ["Mature", "Mature"], + ["Mecha", "Mecha"], + ["Mystery", "Mystery"], + ["Psychological", "Psychological"], + ["Romance", "Romance"], + ["School Life", "School Life"], + ["Sci-Fi", "Sci-Fi"], + ["Seinen", "Seinen"], + ["Shotacon", "Shotacon"], + ["Shoujo", "Shoujo"], + ["Shoujo Ai", "Shoujo Ai"], + ["Shounen", "Shounen"], + ["Slice of Life", "Slice of Life"], + ["Smut", "Smut"], + ["Sports", "Sports"], + ["Supernatural", "Supernatural"], + ["Tragedy", "Tragedy"], + ["Yaoi", "Yaoi"], + ["Yuri", "Yuri"], + ["Other", "Other"] + ].map(x => ({ type_name: 'CheckBox', name: x[0], value: x[1] })) + } + ] + } + getSourcePreferences() { + throw new Error("getSourcePreferences not implemented"); + } +}