diff --git a/javascript/icon/en.novelupdates.png b/javascript/icon/en.novelupdates.png new file mode 100644 index 00000000..92ee0b75 Binary files /dev/null and b/javascript/icon/en.novelupdates.png differ diff --git a/javascript/novel/src/en/novelupdates.js b/javascript/novel/src/en/novelupdates.js new file mode 100644 index 00000000..d7d42f6e --- /dev/null +++ b/javascript/novel/src/en/novelupdates.js @@ -0,0 +1,148 @@ +const mangayomiSources = [{ + "name": "Novel Updates", + "lang": "en", + "baseUrl": "https://novelupdates.com", + "apiUrl": "", + "iconUrl": "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/main/javascript/icon/en.novelupdates.png", + "typeSource": "single", + "itemType": 2, + "version": "0.0.1", + "dateFormat": "", + "dateFormatLocale": "", + "pkgPath": "novel/src/en/novelupdates.js" +}]; + +class DefaultExtension extends MProvider { + getHeaders(url) { + return { + Referer: this.source.baseUrl + }; + } + mangaListFromPage(res) { + const doc = new Document(res.body); + const mangaElements = doc.select("div.grid > a[href]"); + const list = []; + for (const element of mangaElements) { + const name = element.selectFirst("span.block").text; + const imageUrl = element.selectFirst("img").getSrc; + const link = element.getHref; + list.push({ name, imageUrl, link }); + } + const hasNextPage = doc.selectFirst("a.flex.bg-themecolor:contains(Next)").text != ""; + return { "list": list, hasNextPage }; + } + toStatus(status) { + if (status == "Ongoing") + return 0; + else if (status == "Completed") + return 1; + else if (status == "Hiatus") + return 2; + else if (status == "Dropped") + return 3; + else + return 5; + } + parseDate(date) { + const months = { + "january": "01", "february": "02", "march": "03", "april": "04", + "may": "05", "june": "06", "july": "07", "august": "08", + "september": "09", "october": "10", "november": "11", "december": "12" + }; + date = date.toLowerCase().replace(/(st|nd|rd|th)/g, "").split(" "); + if (!(date[0] in months)) { + return String(new Date().valueOf()); + } + date[0] = months[date[0]]; + const formattedDate = `${date[2]}-${date[0]}-${date[1].padStart(2, "0")}`; // Format YYYY-MM-DD + return String(new Date(formattedDate).valueOf()); + } + + async getPopular(page) { + const baseUrl = new SharedPreferences().get("overrideBaseUrl1"); + const res = await new Client().get(`${baseUrl}/series?name=&status=-1&types=-1&order=rating&page=${page}`); + return this.mangaListFromPage(res); + } + + async getLatestUpdates(page) { + const baseUrl = new SharedPreferences().get("overrideBaseUrl1"); + const res = await new Client().get(`${baseUrl}/series?genres=&status=-1&types=-1&order=update&page=${page}`); + return this.mangaListFromPage(res); + } + async search(query, page, filters) { + const baseUrl = new SharedPreferences().get("overrideBaseUrl1"); + const res = await new Client().get(`${baseUrl}/series?name=${query}&page=${page}`); + return this.mangaListFromPage(res); + } + + async getDetail(url) { + const baseUrl = new SharedPreferences().get("overrideBaseUrl1"); + const res = await new Client().get(baseUrl + "/" + url); + const doc = new Document(res.body); + const imageUrl = doc.selectFirst("img[alt=poster]")?.getSrc; + const description = doc.selectFirst("span.font-medium.text-sm")?.text.trim(); + const author = doc.selectFirst("h3:contains('Author')").nextElementSibling.text.trim(); + const artist = doc.selectFirst("h3:contains('Artist')").nextElementSibling.text.trim(); + const status = this.toStatus(doc.selectFirst("h3:contains('Status')").nextElementSibling.text.trim()); + const genre = doc.select("div[class^=space] > div.flex > button.text-white") + .map((el) => el.text.trim()); + const chapters = []; + const chapterElements = doc.select("div.scrollbar-thumb-themecolor > div.group"); + for (const element of chapterElements) { + const url = element.selectFirst("a").getHref; + const chNumber = element.selectFirst("h3 > a").text; + const chTitle = element.select("h3 > a > span").map((span) => span.text.trim()).join(" ").trim(); + const name = chTitle == "" ? chNumber : `${chNumber} - ${chTitle}`; + + let dateUpload; + try { + const dateText = element.selectFirst("h3 + h3").text.trim(); + const cleanDateText = dateText.replace(/(\d+)(st|nd|rd|th)/, "$1"); + dateUpload = this.parseDate(cleanDateText); + } catch (_) { + dateUpload = null + } + chapters.push({ name, url, dateUpload }); + } + return { + imageUrl, + description, + genre, + author, + artist, + status, + chapters + }; + } + + + async getPageList(url) { + const baseUrl = new SharedPreferences().get("overrideBaseUrl1"); + const res = await new Client().get(baseUrl + "/series/" + url); + const scriptData = new Document(res.body).select("script:contains(self.__next_f.push)").map((e) => e.text.substringAfter("\"").substringBeforeLast("\"")).join(""); + console.log(scriptData); + const match = scriptData.match(/\\"pages\\":(\[.*?])/); + if (!match) { + throw new Error("Failed to find chapter pages"); + } + const pagesData = match[1]; + + const pageList = JSON.parse(pagesData.replace(/\\(.)/g, "$1")) + .sort((a, b) => a.order - b.order); + return pageList; + } + + getSourcePreferences() { + return [{ + "key": "overrideBaseUrl1", + "editTextPreference": { + "title": "Override BaseUrl", + "summary": "https://novelupdates.com", + "value": "https://novelupdates.com", + "dialogTitle": "Override BaseUrl", + "dialogMessage": "", + } + }]; + } + +} \ No newline at end of file