diff --git a/electron/main/main.ts b/electron/main/main.ts index 1755b0b0..275c0a12 100644 --- a/electron/main/main.ts +++ b/electron/main/main.ts @@ -7,7 +7,15 @@ import { registerLnproxyProtocol } from './protocols/lnproxy.js'; app.commandLine.appendSwitch('disable-features', 'PartitionedCookies'); protocol.registerSchemesAsPrivileged([ - { scheme: 'lnproxy', privileges: { standard: true, secure: true, supportFetchAPI: true, corsEnabled: true } } + { + scheme: 'lnproxy', + privileges: { + standard: true, + secure: true, + supportFetchAPI: true, + corsEnabled: true, + }, + }, ]); const __filename = fileURLToPath(import.meta.url); diff --git a/electron/main/protocols/lnproxy.ts b/electron/main/protocols/lnproxy.ts index d5ca5c49..3dd9c7e3 100644 --- a/electron/main/protocols/lnproxy.ts +++ b/electron/main/protocols/lnproxy.ts @@ -3,7 +3,7 @@ import { performNetRequest } from '../ipc/fetch-handler.js'; import { customSession } from '../main.js'; export function registerLnproxyProtocol() { - customSession.protocol.handle('lnproxy', async (req) => { + customSession.protocol.handle('lnproxy', async req => { // Handle OPTIONS request for preflight just in case, though Custom Protocol with corsEnabled shouldn't need it. if (req.method === 'OPTIONS') { return new Response(null, { @@ -11,8 +11,8 @@ export function registerLnproxyProtocol() { headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': '*', - 'Access-Control-Allow-Headers': '*' - } + 'Access-Control-Allow-Headers': '*', + }, }); } @@ -46,15 +46,15 @@ export function registerLnproxyProtocol() { const responseData = await performNetRequest(targetUrl, { method: req.method, headers: headers, - body: bodyData + body: bodyData, }); const resHeaders = new Headers(responseData.headers); - + // Node fetch/net automatically decompresses but keeps original headers, causing issues when piped to Chromium resHeaders.delete('content-encoding'); resHeaders.delete('content-length'); - + // Always allow CORS resHeaders.set('Access-Control-Allow-Origin', '*'); resHeaders.set('Access-Control-Allow-Headers', '*'); @@ -67,7 +67,6 @@ export function registerLnproxyProtocol() { statusText: responseData.statusMessage, headers: resHeaders, }); - } catch (error) { console.error('[lnproxy] Request error:', error); return new Response(String(error), { status: 500 }); diff --git a/plugins/vietnamese/ZumiNovel.ts b/plugins/vietnamese/ZumiNovel.ts index cdd5c9ae..d42a9d8b 100644 --- a/plugins/vietnamese/ZumiNovel.ts +++ b/plugins/vietnamese/ZumiNovel.ts @@ -127,7 +127,7 @@ class ZumiNovelPlugin implements Plugin.PluginBase { name = 'ZumiNovel'; icon = 'src/vi/zuminovel/icon.png'; site = SITE; - version = '1.0.3'; + version = '1.0.4'; pluginSettings: Plugin.PluginSettings = { showRaw: { @@ -181,10 +181,6 @@ class ZumiNovelPlugin implements Plugin.PluginBase { if (opts.type) { params.set('type', opts.type); - } else if (!opts.showRaw) { - params.append('type', 'original'); - params.append('type', 'translated'); - params.append('type', 'ai_translated'); } params.set('includeDescription', 'false'); diff --git a/plugins/vietnamese/jukanovel.ts b/plugins/vietnamese/jukanovel.ts index 05154f03..922112fb 100644 --- a/plugins/vietnamese/jukanovel.ts +++ b/plugins/vietnamese/jukanovel.ts @@ -12,7 +12,7 @@ class JukaNovelPlugin implements Plugin.PluginBase { name = 'JukaNovel'; icon = 'src/vi/jukanovel/icon.png'; site = 'https://jukaza.site'; - version = '1.0.5'; + version = '1.0.6'; pluginSettings: Plugin.PluginSettings = { preferRaw: { @@ -102,15 +102,43 @@ class JukaNovelPlugin implements Plugin.PluginBase { }; } async parseChapter(chapterPath: string): Promise { + const chapterIdMatch = chapterPath.match(/\/(\d+)/); + if (!chapterIdMatch) { + throw new Error('Không thể tìm thấy ID chương.'); + } + const chapterId = chapterIdMatch[1]; const response = await fetchText(`${this.site}${chapterPath}`); const $ = loadCheerio(response); this.checkLogin($); const scriptContent = $('script:contains("__READER_DATA__")').html() || ''; - const match = scriptContent.match(/window\.__READER_DATA__\s*=\s*(.*?});/); - if (!match) throw new Error('Không tìm thấy dữ liệu chương.'); + const tokenMatch = scriptContent.match( + new RegExp(`"chapter_id":${chapterId},"token":"([^"]+)"`), + ); + const token = tokenMatch ? tokenMatch[1] : null; + + const cipherKeyMatch = scriptContent.match(/"cipherKey":"([^"]+)"/); + const cipherKey = cipherKeyMatch ? cipherKeyMatch[1] : ''; + + if (!token) throw new Error('Không tìm thấy token cho chương này.'); + + const getDataContent = await fetchText( + `${this.site}/api/reader/chapter/${chapterId}`, + { + headers: { + 'accept': 'application/json', + 'x-requested-with': 'XMLHttpRequest', + 'referer': `${this.site}${chapterPath}`, + 'x-reader-token': token, + }, + }, + ); try { - const readerData = JSON.parse(match[1]); + const chapterData = JSON.parse(getDataContent); + const readerData = { + cipherKey: cipherKey, + chapter: chapterData, + }; const chapterContent = this.decryptJukaNovel(readerData); if (!chapterContent) throw new Error('chapterContent = null'); return chapterContent; @@ -155,10 +183,11 @@ class JukaNovelPlugin implements Plugin.PluginBase { if (this.preferRaw) { content = contentCipher(chapter.raw_content); } else { - content = - contentCipher(chapter.published_content) || - contentCipher(chapter.translated_content) || - contentCipher(chapter.raw_content); + content = contentCipher( + chapter.published_content || + chapter.translated_content || + chapter.raw_content, + ); } if (!content) return '';