feat: 集成漫画源并支持在线阅读
This commit is contained in:
@@ -108,6 +108,8 @@ const annotations = require('./src/reader/annotations');
|
||||
const noteAssets = require('./src/reader/note-assets');
|
||||
const readerWindow = require('./src/reader/window');
|
||||
const noteWindow = require('./src/reader/note-window');
|
||||
const mangaOnlineWindow = require('./src/manga-online/window');
|
||||
const mangaOnlineSessions = require('./src/manga-online/session');
|
||||
const rangeSessions = require('./src/reader/range-sessions');
|
||||
const aiConfig = require('./src/reader/ai-config');
|
||||
const aiClient = require('./src/reader/ai-client');
|
||||
@@ -276,7 +278,7 @@ noteWindow.setChangeListener(notifyNoteWindowsChanged);
|
||||
function applyWindowIcons(theme) {
|
||||
currentUiTheme = theme === 'light' ? 'light' : 'dark';
|
||||
const icon = iconForTheme(currentUiTheme);
|
||||
const windows = [mainWindow, ...readerWindow.all(), ...noteWindow.all()];
|
||||
const windows = [mainWindow, ...readerWindow.all(), ...noteWindow.all(), ...mangaOnlineWindow.all()];
|
||||
for (const win of windows) {
|
||||
if (!win || win.isDestroyed()) continue;
|
||||
try { win.setIcon(icon); } catch (e) { /* 平台不支持动态图标时保留创建时图标 */ }
|
||||
@@ -284,7 +286,7 @@ function applyWindowIcons(theme) {
|
||||
}
|
||||
|
||||
function notifyUiThemeChanged() {
|
||||
const windows = [mainWindow, ...readerWindow.all(), ...noteWindow.all()];
|
||||
const windows = [mainWindow, ...readerWindow.all(), ...noteWindow.all(), ...mangaOnlineWindow.all()];
|
||||
for (const win of windows) {
|
||||
if (win && !win.isDestroyed()) {
|
||||
win.webContents.send('ui:themeChanged', currentUiTheme);
|
||||
@@ -314,6 +316,7 @@ app.on('before-quit', () => {
|
||||
for (const download of downloadSessions.values()) discardDownloadSession(download);
|
||||
coverGenerator.close();
|
||||
rangeSessions.closeAll().catch(() => {});
|
||||
mangaOnlineSessions.reset();
|
||||
});
|
||||
|
||||
// fn 同步抛出时也必须变成 { ok:false },否则 invoke 直接 reject,
|
||||
@@ -332,6 +335,70 @@ ipcMain.handle('source:search', (_e, sourceId, keyword, page) => wrap(() => sour
|
||||
ipcMain.handle('source:detail', (_e, sourceId, postId) => wrap(() => sources.getSource(sourceId).detail(postId)));
|
||||
ipcMain.handle('source:download', (_e, sourceId, postId) => wrap(() => sources.getSource(sourceId).download(postId)));
|
||||
|
||||
// 按章节下载的源不复用 download:file;后者只处理单个直链文件。
|
||||
ipcMain.handle('source:chapters', (_e, sourceId, postId, page, options) => wrap(() => {
|
||||
const source = sources.getSource(sourceId);
|
||||
if (typeof source.chapters !== 'function') throw new Error('该数据源不支持章节列表');
|
||||
return source.chapters(postId, page, options || {});
|
||||
}));
|
||||
ipcMain.handle('source:readOnline', (event, sourceId, input) => wrap(() => {
|
||||
if (!mainWindow || mainWindow.isDestroyed() || event.sender.id !== mainWindow.webContents.id) {
|
||||
throw new Error('只有主窗口可以打开在线漫画');
|
||||
}
|
||||
const source = sources.getSource(sourceId);
|
||||
const win = mangaOnlineWindow.create(__dirname, currentUiTheme);
|
||||
const ownerId = win.webContents.id;
|
||||
win.webContents.once('destroyed', () => mangaOnlineSessions.closeOwner(ownerId));
|
||||
try {
|
||||
const value = mangaOnlineSessions.create(ownerId, source, input || {});
|
||||
mangaOnlineWindow.load(__dirname, value.sessionId);
|
||||
return value;
|
||||
} catch (error) {
|
||||
win.destroy();
|
||||
throw error;
|
||||
}
|
||||
}));
|
||||
ipcMain.handle('source:downloadChapter', async (event, sourceId, payload, requestId) => {
|
||||
const progressId = typeof requestId === 'string' ? requestId.slice(0, 100) : '';
|
||||
const sendProgress = (done, total) => {
|
||||
if (!progressId || event.sender.isDestroyed()) return;
|
||||
event.sender.send('source:chapterProgress', { requestId: progressId, done, total });
|
||||
};
|
||||
return wrap(async () => {
|
||||
const source = sources.getSource(sourceId);
|
||||
if (typeof source.downloadChapter !== 'function') throw new Error('该数据源不支持章节下载');
|
||||
// library.add()/attachFile() 内部已经在 commit() 里触发变更通知,这里不需要再发一次
|
||||
const result = await source.downloadChapter(library, payload || {}, sendProgress);
|
||||
return { entryId: result.entry ? result.entry.id : null, chapterLabel: result.chapterLabel, created: result.created };
|
||||
});
|
||||
});
|
||||
|
||||
function requireMangaOnlineSender(event) {
|
||||
if (!mangaOnlineWindow.fromWebContents(event.sender)) throw new Error('只有在线漫画阅读器可以读取内容');
|
||||
return event.sender.id;
|
||||
}
|
||||
|
||||
ipcMain.handle('mangaOnline:meta', (event, sessionId) => wrap(() => (
|
||||
mangaOnlineSessions.meta(requireMangaOnlineSender(event), sessionId)
|
||||
)));
|
||||
ipcMain.handle('mangaOnline:chapters', (event, sessionId, page) => wrap(() => (
|
||||
mangaOnlineSessions.chapters(requireMangaOnlineSender(event), sessionId, page)
|
||||
)));
|
||||
ipcMain.handle('mangaOnline:chapterManifest', (event, sessionId, chapterId) => wrap(() => (
|
||||
mangaOnlineSessions.chapterManifest(requireMangaOnlineSender(event), sessionId, chapterId)
|
||||
)));
|
||||
ipcMain.handle('mangaOnline:image', (event, sessionId, manifestId, index) => wrap(() => (
|
||||
mangaOnlineSessions.image(
|
||||
requireMangaOnlineSender(event),
|
||||
sessionId,
|
||||
manifestId,
|
||||
index
|
||||
)
|
||||
)));
|
||||
ipcMain.handle('mangaOnline:close', (event, sessionId) => wrap(() => (
|
||||
mangaOnlineSessions.close(requireMangaOnlineSender(event), sessionId)
|
||||
)));
|
||||
|
||||
// 代理配置:全局生效,影响所有数据源的 HTTP 请求与文件下载
|
||||
ipcMain.handle('proxy:get', () => wrap(() => getProxy()));
|
||||
ipcMain.handle('proxy:set', (_e, url) => {
|
||||
|
||||
Reference in New Issue
Block a user