diff --git a/README.md b/README.md index 600860d..fa7cd41 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,8 @@ Not being maintained and uncertain whether they will be updated. ### **Limits** -- Only local `assets://`, `github://`, `gitee://` config is supported, and http config is not available. -- Only video&cloud disk&audio book module. +- Only local `assets://`, `github://`, `gitee://`, `http(s)://user:pwd@xxx` config is supported, and http config without basic auth is not available. +- Only video & cloud disk & audio book & comic & txt novel module. - Not supporting sniffing. - Basic JS interface support. - No builtin maccms api support. diff --git a/open/13bqg_open.js b/open/13bqg_open.js new file mode 100644 index 0000000..b26dc33 --- /dev/null +++ b/open/13bqg_open.js @@ -0,0 +1,165 @@ +import { _, load } from './lib/cat.js'; + +let key = '13bqg'; +let url = 'https://m.13bqg.com'; +let siteKey = ''; +let siteType = 0; + +const MOBILE_UA = 'Mozilla/5.0 (Linux; Android 11; M2007J3SC Build/RKQ1.200826.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.120 MQQBrowser/6.2 TBS/045714 Mobile Safari/537.36'; + +async function request(reqUrl) { + let resp = await req(reqUrl, { + headers: { + 'Accept-Language': 'zh-CN,zh;q=0.8', + 'User-Agent': MOBILE_UA, + }, + }); + return resp.content; +} + +// cfg = {skey: siteKey, ext: extend} +async function init(cfg) { + siteKey = cfg.skey; + siteType = cfg.stype; +} + +async function home(filter) { + var html = await request(url); + const $ = load(html); + let classes = []; + for (const a of $('div.nav > ul > li > a[href!="/"]')) { + classes.push({ + type_id: a.attribs.href.replace(/\//g, ''), + type_name: a.children[0].data.trim(), + tline: 2, + }); + } + return { + class: classes, + }; +} + +async function category(tid, pg, filter, extend) { + if (pg == 0) pg = 1; + var html = await request(url + `/${tid}/${pg}.html`); + const $ = load(html); + let books = []; + for (const item of $('div.item')) { + const a = $(item).find('a:first')[0]; + const img = $(a).find('img:first')[0]; + const span = $(item).find('span:first')[0]; + books.push({ + book_id: a.attribs.href, + book_name: img.attribs.alt, + book_pic: img.attribs.src, + book_remarks: span.children[0].data.trim(), + }); + } + return { + page: pg, + pagecount: $('div.page > a:contains(>)').length > 0 ? pg + 1 : pg, + list: books, + }; +} + +async function detail(id) { + var html = await request(url + id); + var $ = load(html); + let book = { + book_name: $('[property$=book_name]')[0].attribs.content, + book_year: $('[property$=update_time]')[0].attribs.content, + book_director: $('[property$=author]')[0].attribs.content, + book_content: $('[property$=description]')[0].attribs.content, + }; + html = await request(url + id + `list.html`); + $ = load(html); + let urls = []; + const links = $('dl>dd>a[href*="/html/"]'); + for (const l of links) { + var name = $(l).text().trim(); + var link = l.attribs.href; + urls.push(name + '$' + link); + } + book.volumes = '全卷'; + book.urls = urls.join('#'); + + return { + list: [book], + }; +} + +async function play(flag, id, flags) { + try { + var content = ''; + while (true) { + var html = await request(url + id); + var $ = load(html); + content += $('#chaptercontent') + .html() + .replace(/
|请收藏.*?<\/p>/g, '\n') + .trim(); + id = $('a.Readpage_down')[0].attribs.href; + if (id.indexOf('_') < 0) break; + } + return { + content: content + '\n\n', + }; + } catch (e) { + return { + content: '', + }; + } +} + +async function search(wd, quick, pg) { + const cook = await req(`${url}/user/hm.html?q=${encodeURIComponent(wd)}`, { + headers: { + accept: 'application/json', + 'User-Agent': MOBILE_UA, + Referer: `${url}/s?q=${encodeURIComponent(wd)}`, + }, + }); + const set_cookie = _.isArray(cook.headers['set-cookie']) ? cook.headers['set-cookie'].join(';;;') : cook.headers['set-cookie']; + const cks = set_cookie.split(';;;'); + const cookie = {}; + for (const c of cks) { + const tmp = c.trim(); + const idx = tmp.indexOf('='); + const k = tmp.substr(0, idx); + const v = tmp.substr(idx + 1, tmp.indexOf(';') - idx - 1); + cookie[k] = v; + } + const resp = await req(`${url}/user/search.html?q=${encodeURIComponent(wd)}&so=undefined`, { + headers: { + accept: 'application/json', + 'User-Agent': MOBILE_UA, + cookie: 'hm=' + cookie['hm'], + Referer: `${url}/s?q=${encodeURIComponent(wd)}`, + }, + }); + var data = JSON.parse(resp.content); + let books = []; + for (const book of data) { + books.push({ + book_id: book.url_list, + book_name: book.articlename, + book_pic: book.url_img, + book_remarks: book.author, + }); + } + return { + tline: 2, + list: books, + }; +} + +export function __jsEvalReturn() { + return { + init: init, + home: home, + category: category, + detail: detail, + play: play, + search: search, + }; +} diff --git a/open/bookan_open.js b/open/bookan_open.js index 21999fb..b348b92 100644 --- a/open/bookan_open.js +++ b/open/bookan_open.js @@ -64,6 +64,7 @@ async function detail(id) { let data = JSON.parse(content).data; let book = { + audio: 1, book_id: id, type_name: '', book_year: '', diff --git a/open/config_open.json b/open/config_open.json index 0065324..2cad3cc 100644 --- a/open/config_open.json +++ b/open/config_open.json @@ -21,6 +21,12 @@ }, "read": { "sites": [ + { + "key": "13bqg", + "name": "笔趣阁", + "type": 10, + "api": "assets://js/13bqg_open.js" + }, { "key": "bookan", "name": "博看听书", @@ -29,6 +35,16 @@ } ] }, + "comic": { + "sites": [ + { + "key": "copymanga", + "name": "拷贝漫画", + "type": 20, + "api": "assets://js/copymanga_open.js" + } + ] + }, "pan": { "sites": [ { diff --git a/open/copymanga_open.js b/open/copymanga_open.js new file mode 100644 index 0000000..f3445ce --- /dev/null +++ b/open/copymanga_open.js @@ -0,0 +1,204 @@ +import { Crypto, _, load } from './lib/cat.js'; + +let key = 'copymanga'; +let url = 'https://www.copymanga.tv'; + +let siteKey = ''; +let siteType = 0; + +const PC_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36'; + +async function request(reqUrl) { + let resp = await req(reqUrl, { + headers: { + 'User-Agent': PC_UA, + }, + }); + return resp.content; +} + +// cfg = {skey: siteKey, ext: extend} +async function init(cfg) { + siteKey = cfg.skey; + siteType = cfg.stype; +} + +async function home(filter) { + var html = await request(url + '/comics'); + const $ = load(html); + let filterObj = {}; + + let region = { + key: 'region', + name: '地區', + init: '', + }; + let regionValues = []; + regionValues.push({ n: '全部', v: '' }); + regionValues.push({ n: '日漫', v: '0' }); + regionValues.push({ n: '韓漫', v: '1' }); + regionValues.push({ n: '美漫', v: '2' }); + region['value'] = regionValues; + + let ordering = { + key: 'ordering', + name: '排序', + init: '-datetime_updated', + }; + let orderingValues = []; + orderingValues.push({ n: '更新時間↓', v: '-datetime_updated' }); + orderingValues.push({ n: '更新時間↑', v: 'datetime_updated' }); + orderingValues.push({ n: '熱門↓', v: '-popular' }); + orderingValues.push({ n: '熱門↑', v: 'popular' }); + ordering['value'] = orderingValues; + + let status = { + key: 'sort', + name: '狀態', + init: '', + }; + let statusValues = []; + statusValues.push({ n: '全部', v: '' }); + statusValues.push({ n: '連載中', v: '0' }); + statusValues.push({ n: '已完結', v: '1' }); + statusValues.push({ n: '短篇', v: '2' }); + status['value'] = statusValues; + + filterObj['c1'] = []; + + let themeValues = [{ n: '全部', v: '' }]; + for (const a of $('div.classify-right>a[href*="theme="]')) { + themeValues.push({ + n: $(a).text().trim(), + v: a.attribs.href.match(/.*?theme=(.*)&/)[1], + }); + } + filterObj['c1'].push({ + key: 'theme', + name: '', + init: '', + wrap: 1, + value: themeValues, + }); + + filterObj['c1'].push(region); + filterObj['c1'].push(status); + filterObj['c1'].push(ordering); + + return { + class: [{ type_name: 'all', type_id: 'c1' }], + filters: filterObj, + }; +} + +async function category(tid, pg, filter, extend) { + if (pg == 0) pg = 1; + let link = url + `/comics?theme=${extend.theme || ''}®ion=${extend.region || ''}&status=${extend.status || ''}&ordering=${extend.ordering || '-datetime_updated'}`; + if (pg > 1) { + link += '&offset=' + (pg - 1) * 50 + '&limit=50'; + } + var html = await request(link); + const $ = load(html); + const list = eval($('div[class="row exemptComic-box"]')[0].attribs.list); + let books = []; + for (const book of list) { + books.push({ + book_id: book.path_word, + book_name: book.name, + book_pic: book.cover, + book_remarks: book.author ? book.author[0].name : '', + }); + } + return { + page: pg, + pagecount: list.length == 50 ? pg + 1 : pg, + list: books, + }; +} + +async function detail(id) { + var html = await request(url + `/comic/${id}`); + const $ = load(html); + let book = { + book_name: $('h6').text().trim(), + book_director: _.map($('span.comicParticulars-right-txt>a[href*="/author/"]'), (a) => $(a).text().trim()).join('/'), + book_content: $('p.intro').text().trim(), + }; + + const data = JSON.parse(await request(url + `/comicdetail/${id}/chapters`)).results; + var key = Crypto.enc.Utf8.parse('xxxmanga.woo.key'); + var iv = Crypto.enc.Utf8.parse(data.substr(0, 16)); + var src = Crypto.enc.Hex.parse(data.substr(16)); + var dst = Crypto.AES.decrypt({ ciphertext: src }, key, { iv: iv, padding: Crypto.pad.Pkcs7 }); + dst = Crypto.enc.Utf8.stringify(dst); + + const groups = JSON.parse(dst).groups; + + let urls = _.map(groups.default.chapters, (c) => { + return c.name + '$' + id + '|' + c.id; + }).join('#'); + book.volumes = '默認'; + book.urls = urls; + + return { + list: [book], + }; +} + +async function play(flag, id, flags) { + try { + var info = id.split('|'); + var html = await request(url + `/comic/${info[0]}/chapter/${info[1]}`); + const $ = load(html); + const data = $('div.imageData')[0].attribs.contentkey; + var key = Crypto.enc.Utf8.parse('xxxmanga.woo.key'); + var iv = Crypto.enc.Utf8.parse(data.substr(0, 16)); + var src = Crypto.enc.Hex.parse(data.substr(16)); + var dst = Crypto.AES.decrypt({ ciphertext: src }, key, { iv: iv, padding: Crypto.pad.Pkcs7 }); + dst = Crypto.enc.Utf8.stringify(dst); + const list = JSON.parse(dst); + var content = []; + for (let index = 0; index < list.length; index++) { + const element = list[index]; + content[index] = element.url; + } + return { + content: content, + }; + } catch (e) { + return { + content: '', + }; + } +} + +async function search(wd, quick, pg) { + if (pg == 0) pg = 1; + const link = `${url}/api/kb/web/searcha/comics?offset=${pg > 1 ? ((pg - 1) * 12).toString() : ''}&platform=2&limit=12&q=${wd}&q_type=`; + var list = JSON.parse(await request(link)).results.list; + const books = []; + for (const book of list) { + books.push({ + book_id: book.path_word, + book_name: book.name, + book_pic: book.cover, + book_remarks: book.author ? book.author[0].name : '', + }); + } + return { + page: pg, + pagecount: list.length == 12 ? pg + 1 : pg, + list: books, + }; +} + +export function __jsEvalReturn() { + return { + init: init, + home: home, + category: category, + detail: detail, + play: play, + search: search, + }; +} diff --git a/open/wrapper/index.js b/open/wrapper/index.js index 2a599b2..ff2cb30 100644 --- a/open/wrapper/index.js +++ b/open/wrapper/index.js @@ -386,6 +386,13 @@ globalThis.JSFile = function (path) { this.flush = async function () { return; }; + + /** + * File to sharedBuffer. + */ + this.shared = async function () { + return; + }; /** * Closes the file descriptor.