diff --git a/javascript/manga/src/zh/77mh.js b/javascript/manga/src/zh/77mh.js
new file mode 100644
index 00000000..6edec437
--- /dev/null
+++ b/javascript/manga/src/zh/77mh.js
@@ -0,0 +1,258 @@
+const mangayomiSources = [{
+ "name": "新新漫画",
+ "lang": "zh",
+ "baseUrl": "https://www.77mh.nl",
+ "apiUrl": "",
+ "iconUrl": "https://www.77mh.nl/favicon.ico",
+ "typeSource": "single",
+ "isManga": true,
+ "isNsfw": false,
+ "version": "0.0.1",
+ "apiUrl": "",
+ "dateFormat": "",
+ "dateFormatLocale": "",
+ "pkgName": "manga/src/zh/77mh.js"
+ }];
+
+ class DefaultExtension extends MProvider {
+ StringResolve1(p, a, c, k, e, d) {
+ e = function(c) {
+ return c.toString(36)
+ };
+ if (!''.replace(/^/, String)) {
+ while (c--) {
+ d[c.toString(a)] = k[c] || c.toString(a)
+ }
+ k = [function(e) {
+ return d[e]
+ }];
+ e = function() {
+ return '\\w+'
+ };
+ c = 1
+ };
+ while (c--) {
+ if (k[c]) {
+ p = p.replace(new RegExp('\\b' + e(c) + '\\b', 'g'), k[c])
+ }
+ }
+ return p
+ }
+
+ StringResolve2(p, a, c, k, e, d) {
+ e = function(c) {
+ return (c < a ? '' : e(parseInt(c / a))) + ((c = c % a) > 35 ? String.fromCharCode(c + 29) : c.toString(36))
+ };
+ if (!''.replace(/^/, String)) {
+ while (c--) {
+ d[e(c)] = k[c] || e(c)
+ }
+ k = [function(e) {
+ return d[e]
+ }];
+ e = function() {
+ return '\\w+'
+ };
+ c = 1
+ };
+ while (c--) {
+ if (k[c]) {
+ p = p.replace(new RegExp('\\b' + e(c) + '\\b', 'g'), k[c])
+ }
+ }
+ return p
+ }
+
+ async getIndex1(url) {
+ const res = await new Client().get(url);
+ const doc = new Document(res.body);
+ const elements = doc.select("div.ar_list_co li");
+ const mangas = [];
+ for (const element of elements) {
+ const title = element.selectFirst("span a").text;
+ const url = element.selectFirst("span a").attr("href");
+ const cover = element.selectFirst("img").attr("src");
+ mangas.push({
+ name: title,
+ link: url,
+ imageUrl: cover
+ });
+ }
+ return {
+ list: mangas,
+ hasNextPage: true
+ };
+
+ }
+
+ async getIndex2(url) {
+ const res = await new Client().get(url);
+ const doc = new Document(res.body);
+ const elements = doc.select("div.ar_list_co dl");
+ const mangas = [];
+ for (const element of elements) {
+ const title = element.selectFirst("h1 a").text.replace("", "").replace("", "");
+ const url = element.selectFirst("h1 a").attr("href");
+ const cover = element.selectFirst("img").attr("src");
+ mangas.push({
+ name: title,
+ link: url,
+ imageUrl: cover
+ });
+ }
+ return {
+ list: mangas,
+ hasNextPage: true
+ };
+
+ }
+
+ async getPopular(page) {
+ return await this.getIndex1(this.source.baseUrl + "/new_coc.html");
+ }
+
+ async getLatestUpdates(page) {
+ return await this.getIndex1(`${this.source.baseUrl}/lianzai/index_${page - 1}.html`);
+ }
+
+ async search(query, page, filters) {
+ var url;
+ if (query == "") {
+ url = `${this.source.baseUrl}${filters[0]["values"][filters[0]["state"]]["value"]}/index_${page-1}.html`
+ } else {
+ url = `${this.source.baseUrl.replace("www","so")}/k.php?k=${query}&p=${page}`;
+ }
+ return await this.getIndex2(url);
+ }
+
+ async getDetail(url) {
+ const res = await new Client().get(this.source.baseUrl + url);
+ const doc = new Document(res.body);
+ const info = doc.selectFirst("div.ar_list_coc");
+ const cover = info.selectFirst("img").attr("src");
+ const title = info.selectFirst("h1").text;
+ const info_other = info.selectFirst("ul.ar_list_coc");
+ const author = info_other.selectFirst("a").text;
+ const status_str = info_other.select("a")[1].text;
+ var status;
+ if (status_str == "已完结") {
+ status = 1;
+ } else {
+ status = 0;
+ }
+ const desc = info.selectFirst("i#det").text;
+ const elements = doc.select("ul.ar_rlos_bor li a");
+ const chapters = [];
+ for (const element of elements) {
+ chapters.push({
+ name: element.text,
+ url: element.attr("href")
+ });
+ }
+ return {
+ name: title,
+ imageUrl: cover,
+ description: desc,
+ author: author,
+ status: status,
+ episodes: chapters
+ };
+ }
+
+ async getPageList(url) {
+ const preference = new SharedPreferences();
+ const image_host = preference.get("imghost");
+ const res = await new Client().get(this.source.baseUrl + url);
+ const strs = res.body.match(/return p}\('(.*?)'.split\('/)[1].split(',');
+ var result;
+ try {
+ result = this.StringResolve1(strs[0], strs[1], strs[2], strs[3].split('|'), 0, {}).replaceAll("'", "");
+ } catch {
+ result = this.StringResolve2(strs[0], strs[1], strs[2], strs[3].split('|'), 0, {}).replaceAll("'", "");
+ }
+ const url_part = result.match(/var img_s=(.*?);var preLink_b/)[1];
+ const urls = result.match(/var msg=(.*?);var maxPage/)[1].replaceAll("\\", "").split('|');
+ const pages = [];
+ for (const url of urls) {
+ pages.push(image_host + `/h${url_part}/` + url);
+ }
+ return pages;
+ }
+
+ getFilterList() {
+ return [{
+ type: "category",
+ name: "分类",
+ type_name: "SelectFilter",
+ values: [{
+ value: "/rexue",
+ name: "热血机战",
+ type_name: "SelectOption"
+ },
+ {
+ value: "/kehuan",
+ name: "科幻未来",
+ type_name: "SelectOption"
+ },
+ {
+ value: "/kongbu",
+ name: "恐怖惊悚",
+ type_name: "SelectOption"
+ },
+ {
+ value: "/xuanyi",
+ name: "推理悬疑",
+ type_name: "SelectOption"
+ },
+ {
+ value: "/gaoxiao",
+ name: "滑稽搞笑",
+ type_name: "SelectOption"
+ },
+ {
+ value: "/love",
+ name: "恋爱生活",
+ type_name: "SelectOption"
+ },
+ {
+ value: "/danmei",
+ name: "耽美人生",
+ type_name: "SelectOption"
+ },
+ {
+ value: "/tiyu",
+ name: "体育竞技",
+ type_name: "SelectOption"
+ },
+ {
+ value: "/chunqing",
+ name: "纯情少女",
+ type_name: "SelectOption"
+ },
+ {
+ value: "/qihuan",
+ name: "魔法奇幻",
+ type_name: "SelectOption"
+ },
+ {
+ value: "/wuxia",
+ name: "武侠经典",
+ type_name: "SelectOption"
+ }
+ ]
+ }];
+ }
+
+ getSourcePreferences() {
+ return [{
+ "key": "imghost",
+ "listPreference": {
+ "title": "图片服务器",
+ "summary": "",
+ "valueIndex": 0,
+ "entries": ["服务器1", "服务器2"],
+ "entryValues": ["https://picsh.77dm.top", "https://imgsh.dm365.top"],
+ }
+ }];
+ }
+}
\ No newline at end of file
diff --git a/javascript/manga/src/zh/manhuadb.js b/javascript/manga/src/zh/manhuadb.js
new file mode 100644
index 00000000..80dcb4b2
--- /dev/null
+++ b/javascript/manga/src/zh/manhuadb.js
@@ -0,0 +1,668 @@
+const mangayomiSources = [{
+ "name": "漫画DB",
+ "lang": "zh",
+ "baseUrl": "https://www.manhuadb.com",
+ "apiUrl": "",
+ "iconUrl": "https://www.manhuadb.com/assets/www/img/favicon.png",
+ "typeSource": "single",
+ "isManga": true,
+ "isNsfw": false,
+ "version": "0.0.1",
+ "dateFormat": "",
+ "dateFormatLocale": "",
+ "pkgPath": "manga/src/zh/manhuadb.js"
+ }];
+
+ class DefaultExtension extends MProvider {
+ base64decode(str) {
+ var base64DecodeChars = new Array(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1);
+ var c1, c2, c3, c4;
+ var i, len, out;
+ len = str.length;
+ i = 0;
+ out = "";
+ while (i < len) {
+ do {
+ c1 = base64DecodeChars[str.charCodeAt(i++) & 0xff]
+ } while (i < len && c1 == -1);
+ if (c1 == -1)
+ break;
+ do {
+ c2 = base64DecodeChars[str.charCodeAt(i++) & 0xff]
+ } while (i < len && c2 == -1);
+ if (c2 == -1)
+ break;
+ out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4));
+ do {
+ c3 = str.charCodeAt(i++) & 0xff;
+ if (c3 == 61)
+ return out;
+ c3 = base64DecodeChars[c3]
+ } while (i < len && c3 == -1);
+ if (c3 == -1)
+ break;
+ out += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2));
+ do {
+ c4 = str.charCodeAt(i++) & 0xff;
+ if (c4 == 61)
+ return out;
+ c4 = base64DecodeChars[c4]
+ } while (i < len && c4 == -1);
+ if (c4 == -1)
+ break;
+ out += String.fromCharCode(((c3 & 0x03) << 6) | c4)
+ }
+ return out
+ }
+
+ coverUrlConvert(cover_url) {
+ if (cover_url.search("com") == -1) {
+ return this.source.baseUrl + cover_url;
+ }
+ return cover_url;
+ }
+
+ async getMangas(url, search) {
+ const res = await new Client().get(this.source.baseUrl + url);
+ const doc = new Document(res.body);
+ var str;
+ if (search) {
+ str = "div.comicbook-index";
+ } else {
+ str = "div.media";
+ }
+ const items = doc.select(str);
+ const mangas = [];
+ for (const item of items) {
+ const cover = this.coverUrlConvert(item.selectFirst("a.d-block img").attr("src"));
+ var title;
+ if (search) {
+ title = item.selectFirst("a.d-block").attr("title");
+ } else {
+ title = item.selectFirst("a.d-block img").attr("alt");
+ title = title.replace("的封面图", "");
+ }
+ const url = item.selectFirst("a.d-block").attr("href");
+ mangas.push({
+ name: title,
+ link: url,
+ imageUrl: cover
+ });
+ }
+ return {
+ list: mangas,
+ hasNextPage: true
+ }
+ }
+
+ async getPopular(page) {
+ const res = await new Client().get(this.source.baseUrl);
+ const doc = new Document(res.body);
+ const items = doc.select("div.comicbook-index");
+ var mangas = [];
+ for (let item of items) {
+ const cover = this.coverUrlConvert(item.selectFirst("a img").attr("src"));
+ const title = item.selectFirst("a img").attr("alt");
+ const url = item.selectFirst("a").attr("href")
+ mangas.push({
+ name: title.replace("封面", ""),
+ link: url,
+ imageUrl: cover
+ });
+ }
+ return {
+ list: mangas,
+ hasNextPage: false
+ };
+ }
+
+ async getLatestUpdates(page) {
+ return await this.getMangas(`/manhua/list-page-${page}.html`, false);
+ }
+
+ async search(query, page, filters) {
+ if (query == "") {
+ var locations, readers, status, categories;
+ for (const filter of filters) {
+ if (filter["type"] == "locations") {
+ locations = filter["values"][filter["state"]]["value"];
+ } else if (filter["type"] == "readers") {
+ readers = filter["values"][filter["state"]]["value"];
+ } else if (filter["type"] == "status") {
+ status = filter["values"][filter["state"]]["value"];
+ } else if (filter["type"] == "categories") {
+ categories = filter["values"][filter["state"]]["value"];
+ }
+ }
+ const url = `/manhua/list${locations}${readers}${status}${categories}-page-${page}.html`;
+ return await this.getMangas(url.replaceAll("all", ""), false);
+ } else {
+ return await this.getMangas(`/search?q=${query}&p=${page}`, true);
+ }
+ }
+
+ async getDetail(url) {
+ const res = await new Client().get(this.source.baseUrl + url);
+ const doc = new Document(res.body);
+ const title = doc.selectFirst("h1.comic-title").text;
+ const cover = this.coverUrlConvert(doc.selectFirst("td.comic-cover img").attr("src"));
+ const desc = doc.selectFirst("p.comic_story").text;
+ const author = doc.selectFirst("ul.creators a").text;
+ var tags = doc.select("ul.tags a").map(e => e.text);
+ var status = 5;
+ if (tags[0] == "已完结") {
+ status = 1;
+ tags.shift();
+ }
+ if (tags[0] == "连载中") {
+ status = 0;
+ tags.shift();
+ }
+ const items = doc.select("ol.links-of-books");
+ const episodes = [];
+ const ep_names = doc.select("span.h3");
+ const ep_titles = [];
+ for (const ep_name of ep_names) {
+ ep_titles.push(ep_name.text);
+ }
+ var index = 0;
+ for (const lists of items) {
+ const chapters = lists.select("li");
+ for (const chapter of chapters) {
+ const name = chapter.selectFirst("a").attr("title");
+ const url = chapter.selectFirst("a").attr("href");
+ episodes.push({
+ name: `[[${ep_titles[index]}]]${name}`,
+ url: url
+ });
+ }
+ index = index + 1;
+ }
+ return {
+ name: title,
+ imageUrl: cover,
+ description: desc,
+ episodes: episodes,
+ genre: tags,
+ author: author,
+ status: status
+ };
+ }
+
+ async getPageList(url) {
+ const res = await new Client().get(this.source.baseUrl + url);
+ const html = res.body;
+ const doc = new Document(html);
+ const urls = [];
+ var script_str = html.match(/