mirror of
https://github.com/koodo-reader/koodo-reader.git
synced 2025-12-23 23:17:55 -05:00
817 lines
26 KiB
JavaScript
817 lines
26 KiB
JavaScript
const {
|
|
app,
|
|
BrowserWindow,
|
|
WebContentsView,
|
|
Menu,
|
|
ipcMain,
|
|
dialog,
|
|
powerSaveBlocker,
|
|
nativeTheme,
|
|
protocol
|
|
} = require("electron");
|
|
const path = require("path");
|
|
const isDev = require("electron-is-dev");
|
|
const Store = require("electron-store");
|
|
const os = require("os");
|
|
const store = new Store();
|
|
const fs = require("fs");
|
|
const configDir = app.getPath("userData");
|
|
const dirPath = path.join(configDir, "uploads");
|
|
const packageJson = require("./package.json");
|
|
let mainWin;
|
|
let readerWindow;
|
|
let urlWindow;
|
|
let mainView;
|
|
let chatView;
|
|
let dbConnection = {};
|
|
let syncUtilCache = {};
|
|
let pickerUtilCache = {};
|
|
let isChatExpand = false;
|
|
const singleInstance = app.requestSingleInstanceLock();
|
|
var filePath = null;
|
|
if (process.platform != "darwin" && process.argv.length >= 2) {
|
|
filePath = process.argv[1];
|
|
}
|
|
store.set(
|
|
"appVersion", packageJson.version,
|
|
);
|
|
store.set(
|
|
"appPlatform", os.platform() + " " + os.release(),
|
|
);
|
|
let options = {
|
|
width: 1050,
|
|
height: 660,
|
|
backgroundColor: '#fff',
|
|
webPreferences: {
|
|
webSecurity: false,
|
|
nodeIntegration: true,
|
|
contextIsolation: false,
|
|
nativeWindowOpen: true,
|
|
nodeIntegrationInSubFrames: false,
|
|
allowRunningInsecureContent: false,
|
|
enableRemoteModule: true,
|
|
sandbox: false,
|
|
},
|
|
};
|
|
const Database = require("better-sqlite3");
|
|
if (os.platform() === 'linux') {
|
|
options = Object.assign({}, options, {
|
|
icon: path.join(__dirname, "./build/assets/icon.png"),
|
|
});
|
|
}
|
|
// Single Instance Lock
|
|
if (!singleInstance) {
|
|
app.quit();
|
|
} else {
|
|
app.on("second-instance", (event, argv, workingDir) => {
|
|
if (mainWin) {
|
|
if (!mainWin.isVisible()) mainWin.show();
|
|
mainWin.focus();
|
|
}
|
|
});
|
|
}
|
|
if (filePath) {
|
|
// Make sure the directory exists
|
|
if (!fs.existsSync(dirPath)) {
|
|
fs.mkdirSync(dirPath, { recursive: true });
|
|
}
|
|
fs.writeFileSync(
|
|
path.join(dirPath, "log.json"),
|
|
JSON.stringify({ filePath }),
|
|
"utf-8"
|
|
);
|
|
}
|
|
const getDBConnection = (dbName, storagePath, sqlStatement) => {
|
|
if (!dbConnection[dbName]) {
|
|
if (!fs.existsSync(path.join(storagePath, "config"))) {
|
|
fs.mkdirSync(path.join(storagePath, "config"), { recursive: true });
|
|
}
|
|
dbConnection[dbName] = new Database(path.join(storagePath, "config", `${dbName}.db`), { verbose: console.log });
|
|
dbConnection[dbName].pragma('journal_mode = WAL');
|
|
dbConnection[dbName].exec(sqlStatement["createTableStatement"][dbName]);
|
|
}
|
|
return dbConnection[dbName];
|
|
}
|
|
const getSyncUtil = async (config, isUseCache = true) => {
|
|
if (!isUseCache || !syncUtilCache[config.service]) {
|
|
const { SyncUtil, TokenService, ConfigService, ThirdpartyRequest } = await import('./src/assets/lib/kookit-extra.min.mjs');
|
|
let thirdpartyRequest = new ThirdpartyRequest(TokenService, ConfigService);
|
|
|
|
syncUtilCache[config.service] = new SyncUtil(config.service, config, config.storagePath, thirdpartyRequest);
|
|
}
|
|
return syncUtilCache[config.service];
|
|
}
|
|
const removeSyncUtil = (config) => {
|
|
if (syncUtilCache[config.service]) {
|
|
syncUtilCache[config.service] = null;
|
|
}
|
|
}
|
|
const getPickerUtil = async (config, isUseCache = true) => {
|
|
if (!isUseCache || !pickerUtilCache[config.service]) {
|
|
const { SyncUtil, TokenService, ThirdpartyRequest, ConfigService } = await import('./src/assets/lib/kookit-extra.min.mjs');
|
|
let thirdpartyRequest = new ThirdpartyRequest(TokenService, ConfigService);
|
|
|
|
pickerUtilCache[config.service] = new SyncUtil(config.service, config, config.storagePath, thirdpartyRequest);
|
|
}
|
|
return pickerUtilCache[config.service];
|
|
}
|
|
const removePickerUtil = (config) => {
|
|
if (pickerUtilCache[config.service]) {
|
|
pickerUtilCache[config.service] = null;
|
|
}
|
|
}
|
|
// Simple encryption function
|
|
const encrypt = (text, key) => {
|
|
let result = "";
|
|
for (let i = 0; i < text.length; i++) {
|
|
const charCode = text.charCodeAt(i) ^ key.charCodeAt(i % key.length);
|
|
result += String.fromCharCode(charCode);
|
|
}
|
|
return Buffer.from(result).toString("base64");
|
|
}
|
|
|
|
// Simple decryption function
|
|
const decrypt = (encryptedText, key) => {
|
|
const buff = Buffer.from(encryptedText, "base64").toString();
|
|
let result = "";
|
|
for (let i = 0; i < buff.length; i++) {
|
|
const charCode = buff.charCodeAt(i) ^ key.charCodeAt(i % key.length);
|
|
result += String.fromCharCode(charCode);
|
|
}
|
|
return result;
|
|
}
|
|
const createMainWin = () => {
|
|
mainWin = new BrowserWindow(options);
|
|
|
|
if (!isDev) {
|
|
Menu.setApplicationMenu(null);
|
|
}
|
|
|
|
const urlLocation = isDev
|
|
? "http://localhost:3000"
|
|
: `file://${path.join(__dirname, "./build/index.html")}`;
|
|
mainWin.loadURL(urlLocation);
|
|
|
|
mainWin.on("close", () => {
|
|
mainWin = null;
|
|
});
|
|
mainWin.on("resize", () => {
|
|
if (mainView) {
|
|
if (!mainWin) return
|
|
let { width, height } = mainWin.getContentBounds()
|
|
mainView.setBounds({ x: 0, y: 0, width: width, height: height })
|
|
}
|
|
if (chatView) {
|
|
if (!mainWin) return
|
|
let { width, height } = mainWin.getContentBounds()
|
|
chatView.webContents.executeJavaScript(`
|
|
window.$chatwoot.toggle('close');
|
|
`)
|
|
chatView.setBounds({ x: width - 80, y: height - 100, width: 80, height: 80 })
|
|
isChatExpand = false;
|
|
}
|
|
});
|
|
mainWin.on("maximize", () => {
|
|
if (mainView) {
|
|
let { width, height } = mainWin.getContentBounds()
|
|
mainView.setBounds({ x: 0, y: 0, width: width, height: height })
|
|
}
|
|
});
|
|
mainWin.on("unmaximize", () => {
|
|
if (mainView) {
|
|
let { width, height } = mainWin.getContentBounds()
|
|
mainView.setBounds({ x: 0, y: 0, width: width, height: height })
|
|
}
|
|
});
|
|
|
|
ipcMain.handle("open-book", (event, config) => {
|
|
let { url, isMergeWord, isAutoFullscreen, isPreventSleep } = config;
|
|
options.webPreferences.nodeIntegrationInSubFrames = true;
|
|
if (isMergeWord) {
|
|
delete options.backgroundColor
|
|
}
|
|
store.set({
|
|
url,
|
|
isMergeWord: isMergeWord || "no",
|
|
isAutoFullscreen: isAutoFullscreen || "no",
|
|
isPreventSleep: isPreventSleep || "no",
|
|
});
|
|
let id;
|
|
if (isPreventSleep === "yes") {
|
|
id = powerSaveBlocker.start("prevent-display-sleep");
|
|
console.log(powerSaveBlocker.isStarted(id));
|
|
}
|
|
|
|
if (isAutoFullscreen === "yes") {
|
|
readerWindow = new BrowserWindow(options);
|
|
readerWindow.loadURL(url);
|
|
readerWindow.maximize();
|
|
} else {
|
|
readerWindow = new BrowserWindow({
|
|
...options,
|
|
width: parseInt(store.get("windowWidth") || 1050),
|
|
height: parseInt(store.get("windowHeight") || 660),
|
|
x: parseInt(store.get("windowX")),
|
|
y: parseInt(store.get("windowY")),
|
|
frame: isMergeWord === "yes" ? false : true,
|
|
hasShadow: isMergeWord === "yes" ? false : true,
|
|
transparent: isMergeWord === "yes" ? true : false,
|
|
});
|
|
readerWindow.loadURL(url);
|
|
// readerWindow.webContents.openDevTools();
|
|
}
|
|
readerWindow.on("close", (event) => {
|
|
if (!readerWindow.isDestroyed()) {
|
|
let bounds = readerWindow.getBounds();
|
|
if (bounds.width > 0 && bounds.height > 0) {
|
|
store.set({
|
|
windowWidth: bounds.width,
|
|
windowHeight: bounds.height,
|
|
windowX: bounds.x,
|
|
windowY: bounds.y,
|
|
});
|
|
}
|
|
}
|
|
if (isPreventSleep && !readerWindow.isDestroyed()) {
|
|
id && powerSaveBlocker.stop(id);
|
|
}
|
|
// readerWindow && readerWindow.destroy();
|
|
// readerWindow = null;
|
|
});
|
|
|
|
|
|
event.returnValue = "success";
|
|
});
|
|
ipcMain.handle("generate-tts", async (event, voiceConfig) => {
|
|
let { text, speed, plugin, config } = voiceConfig;
|
|
let voiceFunc = plugin.script
|
|
// eslint-disable-next-line no-eval
|
|
eval(voiceFunc);
|
|
return global.getAudioPath(text, speed, dirPath, config);
|
|
|
|
});
|
|
ipcMain.handle("cloud-upload", async (event, config) => {
|
|
let syncUtil = await getSyncUtil(config, config.isUseCache);
|
|
let result = await syncUtil.uploadFile(config.fileName, config.fileName, config.type);
|
|
return result;
|
|
});
|
|
|
|
ipcMain.handle("cloud-download", async (event, config) => {
|
|
let syncUtil = await getSyncUtil(config);
|
|
let result = await syncUtil.downloadFile(config.fileName, (config.isTemp ? "temp-" : "") + config.fileName, config.type);
|
|
return result;
|
|
});
|
|
ipcMain.handle("picker-download", async (event, config) => {
|
|
let pickerUtil = await getPickerUtil(config);
|
|
let result = await pickerUtil.remote.downloadFile(config.sourcePath, config.destPath);
|
|
return result;
|
|
});
|
|
ipcMain.handle("cloud-reset", async (event, config) => {
|
|
let syncUtil = await getSyncUtil(config);
|
|
let result = syncUtil.resetCounters();
|
|
return result;
|
|
});
|
|
ipcMain.handle("cloud-stats", async (event, config) => {
|
|
let syncUtil = await getSyncUtil(config);
|
|
let result = syncUtil.getStats();
|
|
return result;
|
|
});
|
|
ipcMain.handle("cloud-delete", async (event, config) => {
|
|
let syncUtil = await getSyncUtil(config, config.isUseCache);
|
|
let result = await syncUtil.deleteFile(config.fileName, config.type);
|
|
return result;
|
|
});
|
|
|
|
ipcMain.handle("cloud-list", async (event, config) => {
|
|
let syncUtil = await getSyncUtil(config);
|
|
let result = await syncUtil.listFiles(config.type);
|
|
return result;
|
|
});
|
|
ipcMain.handle("picker-list", async (event, config) => {
|
|
let pickerUtil = await getPickerUtil(config);
|
|
let result = await pickerUtil.listFiles(config.currentPath);
|
|
return result;
|
|
});
|
|
ipcMain.handle("cloud-exist", async (event, config) => {
|
|
let syncUtil = await getSyncUtil(config);
|
|
let result = await syncUtil.isExist(config.fileName, config.type);
|
|
return result;
|
|
});
|
|
ipcMain.handle("cloud-close", async (event, config) => {
|
|
removeSyncUtil(config);
|
|
return "pong";
|
|
});
|
|
|
|
ipcMain.handle("clear-tts", async (event, config) => {
|
|
if (!fs.existsSync(path.join(dirPath, "tts"))) {
|
|
return "pong";
|
|
} else {
|
|
const fsExtra = require("fs-extra");
|
|
try {
|
|
await fsExtra.remove(path.join(dirPath, "tts"));
|
|
await fsExtra.mkdir(path.join(dirPath, "tts"));
|
|
return "pong";
|
|
} catch (err) {
|
|
console.error(err);
|
|
return "pong";
|
|
}
|
|
}
|
|
});
|
|
ipcMain.handle("select-path", async (event) => {
|
|
var path = await dialog.showOpenDialog({
|
|
properties: ["openDirectory"],
|
|
});
|
|
return path.filePaths[0];
|
|
});
|
|
ipcMain.handle("encrypt-data", async (event, config) => {
|
|
const { TokenService } = await import('./src/assets/lib/kookit-extra.min.mjs');
|
|
let fingerprint = await TokenService.getFingerprint();
|
|
let encrypted = encrypt(config.token, fingerprint);
|
|
store.set("encryptedToken", encrypted);
|
|
return "pong";
|
|
});
|
|
ipcMain.handle("decrypt-data", async (event) => {
|
|
let encrypted = store.get("encryptedToken");
|
|
if (!encrypted) return "";
|
|
const { TokenService } = await import('./src/assets/lib/kookit-extra.min.mjs');
|
|
let fingerprint = await TokenService.getFingerprint();
|
|
let decrypted = decrypt(encrypted, fingerprint);
|
|
if (decrypted.startsWith("{") && decrypted.endsWith("}")) {
|
|
return decrypted
|
|
} else {
|
|
const { safeStorage } = require("electron")
|
|
decrypted = safeStorage.decryptString(Buffer.from(encrypted, "base64"));
|
|
let newEncrypted = encrypt(decrypted, fingerprint);
|
|
store.set("encryptedToken", newEncrypted);
|
|
return decrypted;
|
|
}
|
|
|
|
});
|
|
ipcMain.handle("get-mac", async (event, config) => {
|
|
const { machineIdSync } = require('node-machine-id');
|
|
return machineIdSync();
|
|
});
|
|
ipcMain.handle("get-store-value", async (event, config) => {
|
|
return store.get(config.key);
|
|
});
|
|
|
|
ipcMain.handle("reset-reader-position", async (event) => {
|
|
store.delete("windowX");
|
|
store.delete("windowY");
|
|
return "success"
|
|
|
|
});
|
|
|
|
ipcMain.handle("select-file", async (event, config) => {
|
|
const result = await dialog.showOpenDialog({
|
|
properties: ['openFile'],
|
|
filters: [{ name: 'Zip Files', extensions: ['zip'] }]
|
|
});
|
|
|
|
if (result.canceled) {
|
|
console.log('User canceled the file selection');
|
|
return "";
|
|
} else {
|
|
const filePath = result.filePaths[0];
|
|
console.log('Selected file path:', filePath);
|
|
return filePath;
|
|
}
|
|
});
|
|
|
|
ipcMain.handle("select-book", async (event, config) => {
|
|
const result = await dialog.showOpenDialog({
|
|
properties: ['openFile', 'multiSelections'],
|
|
filters: [{
|
|
name: 'Books', extensions: ["epub", "pdf", "txt", "mobi", "azw3", "azw", "htm", "html", "xml", "xhtml", "mhtml", "docx", "md", "fb2", "cbz", "cbt", "cbr", "cb7",]
|
|
}]
|
|
});
|
|
|
|
if (result.canceled) {
|
|
console.log('User canceled the file selection');
|
|
return [];
|
|
} else {
|
|
const filePaths = result.filePaths;
|
|
console.log('Selected file path:', filePaths);
|
|
return filePaths;
|
|
}
|
|
});
|
|
ipcMain.handle("database-command", async (event, config) => {
|
|
const { SqlStatement } = await import('./src/assets/lib/kookit-extra.min.mjs');
|
|
let { statement, statementType, executeType, dbName, data, storagePath } = config;
|
|
let db = getDBConnection(dbName, storagePath, SqlStatement.sqlStatement);
|
|
let sql = ""
|
|
if (statementType === "string") {
|
|
sql = SqlStatement.sqlStatement[statement][dbName];
|
|
} else if (statementType === "function") {
|
|
sql = SqlStatement.sqlStatement[statement][dbName](data);
|
|
}
|
|
const row = db.prepare(sql);
|
|
let result;
|
|
if (data) {
|
|
if (statement.startsWith("save") || statement.startsWith("update")) {
|
|
data = SqlStatement.jsonToSqlite[dbName](data)
|
|
}
|
|
result = row[executeType](data);
|
|
} else {
|
|
result = row[executeType]();
|
|
}
|
|
if (executeType === 'all') {
|
|
return result.map(item => SqlStatement.sqliteToJson[dbName](item));
|
|
} else if (executeType === 'get') {
|
|
return SqlStatement.sqliteToJson[dbName](result);
|
|
} else {
|
|
return result;
|
|
}
|
|
});
|
|
ipcMain.handle("close-database", async (event, config) => {
|
|
const { SqlStatement } = await import('./src/assets/lib/kookit-extra.min.mjs');
|
|
let { dbName, storagePath } = config;
|
|
let db = getDBConnection(dbName, storagePath, SqlStatement.sqlStatement);
|
|
delete dbConnection[dbName];
|
|
db.close();
|
|
});
|
|
|
|
ipcMain.on("user-data", (event, arg) => {
|
|
event.returnValue = dirPath;
|
|
});
|
|
ipcMain.handle("hide-reader", (event, arg) => {
|
|
if (!readerWindow.isDestroyed() && readerWindow && readerWindow.isFocused()) {
|
|
readerWindow.minimize();
|
|
event.returnvalue = true;
|
|
} else if (mainWin && mainWin.isFocused()) {
|
|
mainWin.minimize();
|
|
event.returnvalue = true;
|
|
} else {
|
|
event.returnvalue = false;
|
|
}
|
|
});
|
|
ipcMain.handle("open-console", (event, arg) => {
|
|
mainWin.webContents.openDevTools();
|
|
event.returnvalue = true;
|
|
});
|
|
ipcMain.handle("reload-reader", (event, arg) => {
|
|
if (readerWindow) {
|
|
readerWindow.reload();
|
|
}
|
|
});
|
|
ipcMain.handle("reload-main", (event, arg) => {
|
|
if (mainWin) {
|
|
mainWin.reload();
|
|
}
|
|
});
|
|
ipcMain.handle("focus-on-main", (event, arg) => {
|
|
if (mainWin) {
|
|
if (!mainWin.isVisible()) mainWin.show();
|
|
mainWin.focus();
|
|
}
|
|
});
|
|
ipcMain.handle("create-new-main", (event, arg) => {
|
|
if (!mainWin) {
|
|
createMainWin();
|
|
}
|
|
});
|
|
ipcMain.handle("new-chat", (event, config) => {
|
|
if (mainWin && !chatView) {
|
|
chatView = new WebContentsView({ ...options, transparent: true })
|
|
mainWin.contentView.addChildView(chatView)
|
|
let { width, height } = mainWin.getContentBounds()
|
|
chatView.setBounds({ x: width - 80, y: height - 100, width: 80, height: 80 })
|
|
chatView.setBackgroundColor("#00000000");
|
|
chatView.webContents.loadURL(config.url)
|
|
chatView.webContents.insertCSS(`
|
|
html, body {
|
|
overflow: hidden;
|
|
background: transparent;
|
|
}
|
|
#cw-widget-holder {
|
|
width: calc(100% - 20px) !important;
|
|
height: calc(100% - 20px) !important;
|
|
margin: 0 !important;
|
|
border-radius: 10px;
|
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
|
|
overflow: hidden !important;
|
|
right: 10px !important;
|
|
top: 10px !important;
|
|
}
|
|
`);
|
|
|
|
chatView.webContents.once('did-navigate', () => {
|
|
console.log("Main view logs this no problem....");
|
|
chatView.webContents.once('dom-ready', () => {
|
|
// Add the chat SDK script
|
|
chatView.webContents.executeJavaScript(`
|
|
// Define the API for renderer to communicate with main
|
|
window.electronAPI = {
|
|
mouseEnterChat: function() {
|
|
const { ipcRenderer } = require('electron');
|
|
ipcRenderer.send('chat-mouse-enter');
|
|
},
|
|
mouseLeaveChat: function() {
|
|
const { ipcRenderer } = require('electron');
|
|
ipcRenderer.send('chat-mouse-leave');
|
|
},
|
|
};
|
|
const script = document.createElement('script');
|
|
script.type = 'text/javascript';
|
|
script.text = \`
|
|
(function (d, t) {
|
|
var BASE_URL = "https://app.chatwoot.com";
|
|
var g = d.createElement(t),
|
|
s = d.getElementsByTagName(t)[0];
|
|
g.src = BASE_URL + "/packs/js/sdk.js";
|
|
g.defer = true;
|
|
g.async = true;
|
|
s.parentNode.insertBefore(g, s);
|
|
g.onload = function () {
|
|
window.chatwootSDK.run({
|
|
websiteToken: "svaD5wxfU5UY1r5ZzpMtLqv2",
|
|
baseUrl: BASE_URL,
|
|
});
|
|
window.addEventListener('chatwoot:ready', function () {
|
|
window.$chatwoot.setLocale('${config.locale}');
|
|
window.$chatwoot.setCustomAttributes({
|
|
version: '${packageJson.version}',
|
|
client: 'desktop',
|
|
});
|
|
});
|
|
window.addEventListener('chatwoot:on-message', function(e) {
|
|
window.electronAPI.mouseEnterChat();
|
|
});
|
|
window.addEventListener('chatwoot:on-close', function(e) {
|
|
window.electronAPI.mouseLeaveChat();
|
|
});
|
|
};
|
|
})(document, "script");
|
|
\`;
|
|
document.head.appendChild(script);
|
|
|
|
// Add mouse event handlers
|
|
document.body.addEventListener('mouseenter', function() {
|
|
window.electronAPI.mouseEnterChat();
|
|
});
|
|
document.addEventListener('mouseup', function() {
|
|
window.electronAPI.mouseLeaveChat();
|
|
});
|
|
|
|
|
|
|
|
|
|
// Add a slight delay to catch any initial mouse position
|
|
setTimeout(() => {
|
|
const rect = document.body.getBoundingClientRect();
|
|
const mouseX = window.event ? window.event.clientX : 0;
|
|
const mouseY = window.event ? window.event.clientY : 0;
|
|
|
|
if (mouseX >= rect.left && mouseX <= rect.right &&
|
|
mouseY >= rect.top && mouseY <= rect.bottom) {
|
|
window.electronAPI.mouseEnterChat();
|
|
}
|
|
}, 1000);
|
|
`);
|
|
|
|
event.returnvalue = true;
|
|
});
|
|
|
|
});
|
|
chatView.webContents.on('blur', () => {
|
|
console.log("leave");
|
|
if (!mainWin) return;
|
|
let { width, height } = mainWin.getContentBounds();
|
|
|
|
// Add a small delay to prevent flickering on quick mouse movements
|
|
setTimeout(() => {
|
|
chatView.setBounds({ x: width - 80, y: height - 100, width: 80, height: 80 });
|
|
chatView.webContents.executeJavaScript(`
|
|
window.$chatwoot && window.$chatwoot.toggle('close');
|
|
`);
|
|
isChatExpand = false;
|
|
}, 300);
|
|
});
|
|
// Register IPC listeners for mouse events
|
|
ipcMain.on('chat-mouse-enter', () => {
|
|
if (!mainWin || isChatExpand) return;
|
|
let { width, height } = mainWin.getContentBounds();
|
|
chatView.setBounds({ x: width - 400, y: height - 520, width: 400, height: 500 });
|
|
chatView.webContents.executeJavaScript(`
|
|
window.$chatwoot && window.$chatwoot.toggle('open');
|
|
`);
|
|
isChatExpand = true;
|
|
});
|
|
ipcMain.on('chat-mouse-leave', () => {
|
|
if (!mainWin) return;
|
|
let { width, height } = mainWin.getContentBounds();
|
|
|
|
// Add a small delay to prevent flickering on quick mouse movements
|
|
setTimeout(() => {
|
|
chatView.setBounds({ x: width - 80, y: height - 100, width: 80, height: 80 });
|
|
chatView.webContents.executeJavaScript(`
|
|
window.$chatwoot && window.$chatwoot.toggle('close');
|
|
`);
|
|
}, 300);
|
|
});
|
|
}
|
|
});
|
|
ipcMain.handle("exit-chat", (event, config) => {
|
|
if (mainWin && chatView) {
|
|
// Remove the IPC listeners
|
|
ipcMain.removeAllListeners('chat-mouse-enter');
|
|
ipcMain.removeAllListeners('chat-mouse-leave');
|
|
|
|
mainWin.contentView.removeChildView(chatView);
|
|
chatView = null;
|
|
isChatExpand = false;
|
|
}
|
|
});
|
|
|
|
|
|
ipcMain.handle("new-tab", (event, config) => {
|
|
if (mainWin) {
|
|
mainView = new WebContentsView(options)
|
|
mainWin.contentView.addChildView(mainView)
|
|
let { width, height } = mainWin.getContentBounds()
|
|
mainView.setBounds({ x: 0, y: 0, width: width, height: height })
|
|
mainView.webContents.loadURL(config.url)
|
|
}
|
|
});
|
|
ipcMain.handle("reload-tab", (event, config) => {
|
|
if (mainWin && mainView) {
|
|
mainView.webContents.reload()
|
|
}
|
|
});
|
|
ipcMain.handle("adjust-tab-size", (event, config) => {
|
|
if (mainWin && mainView) {
|
|
let { width, height } = mainWin.getContentBounds()
|
|
mainView.setBounds({ x: 0, y: 0, width: width, height: height })
|
|
}
|
|
});
|
|
ipcMain.handle("exit-tab", (event, message) => {
|
|
if (mainWin && mainView) {
|
|
mainWin.contentView.removeChildView(mainView)
|
|
}
|
|
});
|
|
ipcMain.handle("enter-tab-fullscreen", () => {
|
|
if (mainWin && mainView) {
|
|
mainWin.setFullScreen(true);
|
|
console.log("enter full");
|
|
}
|
|
});
|
|
ipcMain.handle("exit-tab-fullscreen", () => {
|
|
if (mainWin && mainView) {
|
|
mainWin.setFullScreen(false);
|
|
console.log("exit full");
|
|
}
|
|
});
|
|
ipcMain.handle("enter-fullscreen", () => {
|
|
if (readerWindow) {
|
|
readerWindow.setFullScreen(true);
|
|
console.log("enter full");
|
|
}
|
|
});
|
|
ipcMain.handle("exit-fullscreen", () => {
|
|
if (readerWindow) {
|
|
readerWindow.setFullScreen(false);
|
|
console.log("exit full");
|
|
}
|
|
});
|
|
ipcMain.handle("open-url", (event, config) => {
|
|
if (!urlWindow || urlWindow.isDestroyed()) {
|
|
urlWindow = new BrowserWindow();
|
|
}
|
|
urlWindow.loadURL(config.url);
|
|
});
|
|
ipcMain.handle("switch-moyu", (event, arg) => {
|
|
let id;
|
|
if (store.get("isPreventSleep") === "yes") {
|
|
id = powerSaveBlocker.start("prevent-display-sleep");
|
|
console.log(powerSaveBlocker.isStarted(id));
|
|
}
|
|
if (readerWindow) {
|
|
readerWindow.close();
|
|
if (store.get("isMergeWord") === "yes") {
|
|
delete options.backgroundColor
|
|
}
|
|
Object.assign(options, {
|
|
width: parseInt(store.get("windowWidth") || 1050),
|
|
height: parseInt(store.get("windowHeight") || 660),
|
|
x: parseInt(store.get("windowX")),
|
|
y: parseInt(store.get("windowY")),
|
|
frame: store.get("isMergeWord") !== "yes" ? false : true,
|
|
hasShadow: store.get("isMergeWord") !== "yes" ? false : true,
|
|
transparent: store.get("isMergeWord") !== "yes" ? true : false,
|
|
});
|
|
options.webPreferences.nodeIntegrationInSubFrames = true;
|
|
|
|
store.set(
|
|
"isMergeWord",
|
|
store.get("isMergeWord") !== "yes" ? "yes" : "no"
|
|
);
|
|
readerWindow = new BrowserWindow(options);
|
|
readerWindow.loadURL(store.get("url"));
|
|
readerWindow.on("close", (event) => {
|
|
if (!readerWindow.isDestroyed()) {
|
|
let bounds = readerWindow.getBounds();
|
|
if (bounds.width > 0 && bounds.height > 0) {
|
|
store.set({
|
|
windowWidth: bounds.width,
|
|
windowHeight: bounds.height,
|
|
windowX: bounds.x,
|
|
windowY: bounds.y,
|
|
});
|
|
}
|
|
}
|
|
if (store.get("isPreventSleep") && !readerWindow.isDestroyed()) {
|
|
id && powerSaveBlocker.stop(id);
|
|
}
|
|
// readerWindow && readerWindow.destroy();
|
|
// readerWindow = null;
|
|
});
|
|
}
|
|
event.returnvalue = false;
|
|
});
|
|
ipcMain.on("storage-location", (event, config) => {
|
|
event.returnValue = path.join(dirPath, "data");
|
|
});
|
|
ipcMain.on("get-dirname", (event, arg) => {
|
|
event.returnValue = __dirname;
|
|
});
|
|
ipcMain.on("system-color", (event, arg) => {
|
|
event.returnValue = nativeTheme.shouldUseDarkColors || false;
|
|
});
|
|
ipcMain.on("check-main-open", (event, arg) => {
|
|
event.returnValue = mainWin ? true : false;
|
|
});
|
|
ipcMain.on("get-file-data", function (event) {
|
|
if (fs.existsSync(path.join(dirPath, "log.json"))) {
|
|
const _data = JSON.parse(
|
|
fs.readFileSync(path.join(dirPath, "log.json"), "utf-8") || "{}"
|
|
);
|
|
if (_data && _data.filePath) {
|
|
filePath = _data.filePath;
|
|
fs.writeFileSync(path.join(dirPath, "log.json"), "", "utf-8");
|
|
}
|
|
}
|
|
|
|
event.returnValue = filePath;
|
|
filePath = null;
|
|
});
|
|
ipcMain.on("check-file-data", function (event) {
|
|
if (fs.existsSync(path.join(dirPath, "log.json"))) {
|
|
const _data = JSON.parse(
|
|
fs.readFileSync(path.join(dirPath, "log.json"), "utf-8") || "{}"
|
|
);
|
|
if (_data && _data.filePath) {
|
|
filePath = _data.filePath;
|
|
}
|
|
}
|
|
|
|
event.returnValue = filePath;
|
|
filePath = null;
|
|
});
|
|
};
|
|
|
|
app.on("ready", () => {
|
|
createMainWin();
|
|
});
|
|
app.on("window-all-closed", () => {
|
|
app.quit();
|
|
});
|
|
app.on("open-file", (e, pathToFile) => {
|
|
filePath = pathToFile;
|
|
});
|
|
// Register protocol handler
|
|
app.setAsDefaultProtocolClient('koodo-reader');
|
|
// Handle deep linking
|
|
app.on('second-instance', (event, commandLine) => {
|
|
const url = commandLine.pop();
|
|
if (url) {
|
|
handleCallback(url);
|
|
}
|
|
});
|
|
|
|
// Handle MacOS deep linking
|
|
app.on('open-url', (event, url) => {
|
|
event.preventDefault();
|
|
handleCallback(url);
|
|
});
|
|
|
|
const handleCallback = (url) => {
|
|
try {
|
|
// 检查 URL 是否有效
|
|
if (!url.startsWith('koodo-reader://')) {
|
|
console.error('Invalid URL format:', url);
|
|
return;
|
|
}
|
|
|
|
// 解析 URL
|
|
const parsedUrl = new URL(url);
|
|
const code = parsedUrl.searchParams.get('code');
|
|
const state = parsedUrl.searchParams.get('state');
|
|
|
|
if (code && mainWin) {
|
|
mainWin.webContents.send('oauth-callback', { code, state });
|
|
}
|
|
} catch (error) {
|
|
console.error('Error handling callback URL:', error);
|
|
console.log('Problematic URL:', url);
|
|
}
|
|
}; |