const { app, BrowserWindow, dialog, ipcMain, shell, Tray, Menu, clipboard, } = require("electron"); const {autoUpdater} = require("electron-updater"); process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = "true"; const fs = require("fs"); const path = require("path"); autoUpdater.autoDownload = false; /**@type {BrowserWindow} */ let win = null; let secondaryWindow = null; let tray = null; let isQuiting = false; let indexIsOpen = true; let trayEnabled = false; const configFile = path.join(app.getPath("userData"), "config.json"); function createWindow() { const bounds = JSON.parse((getItem("bounds", configFile) || "{}")); console.log("bounds:", bounds) win = new BrowserWindow({ autoHideMenuBar: true, show: false, icon: __dirname + "/assets/images/icon.png", webPreferences: { nodeIntegration: true, contextIsolation: false, spellcheck: false, }, }); win.setBounds(bounds) win.on("close", (event) => { if (!isQuiting && trayEnabled) { event.preventDefault(); win.hide(); if (app.dock) app.dock.hide(); } return false; }); win.on("resize", (event) => { setItem("bounds", JSON.stringify(win.getBounds()), configFile); }); win.loadFile("html/index.html"); // win.setMenu(null) win.show(); autoUpdater.checkForUpdates().then(result => { // Removing unnecesary files for windows if (result && process.platform === "win32") { if (result.updateInfo.version === app.getVersion()) { fs.readdir(path.join(process.env.LOCALAPPDATA, "ytdownloader-updater"), {encoding: "utf-8", withFileTypes: true}, (err, files) => { if (err) { console.log("No update directory to clear") } else { files.forEach(file => { if (file.isFile()) { fs.rm(path.join(file.path, file.name), (_err) => { console.log("Removed file:", file.name) }) } else { fs.rm(path.join(file.path, file.name), { recursive: true}, (err) => { console.log("Removed directory:", file.name) }) } }) } }) } } }); } let loadedLanguage; const gotTheLock = app.requestSingleInstanceLock(); if (!gotTheLock) { app.quit(); } else { app.on("second-instance", (event, commandLine, workingDirectory) => { if (win) { win.show(); } }); } app.whenReady().then(() => { // Logging console.log("Locale:" + app.getLocale()); console.log("Version: " + app.getVersion()); let locale = app.getLocale(); if (fs.existsSync(path.join(__dirname, "translations", locale + ".json"))) { loadedLanguage = JSON.parse( fs.readFileSync( path.join(__dirname, "translations", locale + ".json"), "utf8" ) ); } else { loadedLanguage = JSON.parse( fs.readFileSync( path.join(__dirname, "translations", "en.json"), "utf8" ) ); } // Tray context menu const contextMenu = Menu.buildFromTemplate([ { label: i18n("Open app"), click() { win.show(); if (app.dock) app.dock.show(); }, }, { label: i18n("Paste video link"), click() { const text = clipboard.readText(); if (indexIsOpen) { win.show(); if (app.dock) app.dock.show(); win.webContents.send("link", text); } else { win.loadFile("html/index.html"); win.show(); indexIsOpen = true; let sent = false; ipcMain.on("ready-for-links", () => { if (!sent) { win.webContents.send("link", text); sent = true; } }); } }, }, { label: i18n("Download playlist"), click() { indexIsOpen = false; win.loadFile("html/playlist.html"); win.show(); if (app.dock) app.dock.show(); }, }, { label: i18n("Quit"), click() { isQuiting = true; app.quit(); }, }, ]); let trayInUse = false; // TODO: Find why tray icon isn't showing properly on gnome let icon; if (process.platform == "win32") { icon = path.join(__dirname, "resources/icon.ico"); } else if (process.platform == "darwin") { icon = path.join(__dirname, "resources/icons/16x16.png"); } else { icon = path.join(__dirname, "resources/icons/256x256.png"); } ipcMain.on("useTray", (_, enabled) => { if (enabled && !trayInUse) { trayEnabled = true; trayInUse = true; tray = new Tray(icon); tray.setToolTip("ytDownloader"); tray.setContextMenu(contextMenu); tray.on("click", () => { win.show(); if (app.dock) app.dock.show(); }); } else if (!enabled) { trayEnabled = false; } }); createWindow(); app.on("activate", () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); } }); if (process.platform === "win32") { app.setAppUserModelId(app.name); } }); ipcMain.on("reload", () => { if (win) { win.reload(); } if (secondaryWindow) { secondaryWindow.reload(); } }); ipcMain.on("get-version", () => { const version = app.getVersion(); secondaryWindow.webContents.send("version", version); }); ipcMain.on("load-win", (event, file) => { if (file.includes("playlist.html")) { indexIsOpen = false; } else { indexIsOpen = true; } win.loadFile(file); }); ipcMain.on("load-page", (event, file) => { secondaryWindow = new BrowserWindow({ webPreferences: { nodeIntegration: true, contextIsolation: false, }, parent: win, modal: true, show: false, }); secondaryWindow.loadFile(file); secondaryWindow.setMenu(null); // secondaryWindow.maximize(); secondaryWindow.show(); }); ipcMain.on("close-secondary", () => { secondaryWindow.close(); secondaryWindow = null; }); ipcMain.on("select-location-main", () => { const location = dialog.showOpenDialogSync({ properties: ["openDirectory"], }); if (location) { win.webContents.send("downloadPath", location); } }); ipcMain.on("select-location-secondary", () => { const location = dialog.showOpenDialogSync({ properties: ["openDirectory"], }); if (location) { secondaryWindow.webContents.send("downloadPath", location); } }); ipcMain.on("get-directory", () => { const location = dialog.showOpenDialogSync({ properties: ["openDirectory"], }); if (location) { win.webContents.send("directory-path", location); } }); ipcMain.on("select-config", () => { const location = dialog.showOpenDialogSync({ properties: ["openFile"], }); if (location) { secondaryWindow.webContents.send("configPath", location); } }); ipcMain.on("quit", () => { isQuiting = true; app.quit(); }); // Auto update let autoUpdate = false; ipcMain.on("autoUpdate", (event, status) => { autoUpdate = status; console.log("Auto update:", status); if (autoUpdate === true) { // Auto updater events autoUpdater.on( "update-available", (_event, releaseNotes, releaseName) => { // For macOS if (process.platform === "darwin") { /** * @type {Electron.MessageBoxOptions} */ const dialogOpts = { type: "info", buttons: [i18n("Download"), i18n("No")], title: "Update Available", detail: releaseName, message: i18n( "A new version is available, do you want to download it?" ), }; dialog.showMessageBox(dialogOpts).then((returnValue) => { if (returnValue.response === 0) { if (process.arch === 'x64') { shell.openExternal( "https://github.com/aandrew-me/ytDownloader/releases/latest/download/YTDownloader_Mac_x64.dmg" ); } else { shell.openExternal( "https://github.com/aandrew-me/ytDownloader/releases/latest/download/YTDownloader_Mac_arm64.dmg" ); } } }); } // For Windows and Linux else { /** * @type {Electron.MessageBoxOptions} */ const dialogOpts = { type: "info", buttons: [i18n("Update"), i18n("No")], title: "Update Available", detail: process.platform === "win32" ? releaseNotes : releaseName, message: i18n( "A new version is available, do you want to update?" ), }; dialog.showMessageBox(dialogOpts).then((returnValue) => { if (returnValue.response === 0) { autoUpdater.downloadUpdate(); } }); } } ); } }); ipcMain.on("progress", (_event, percentage) => { if (win) { win.setProgressBar(percentage) } }) ipcMain.on("error_dialog", (_event, message) => { dialog.showMessageBox(win, { type: "error", title: "Error", message: message, buttons: [ "Ok", "Copy error" ] }).then((result) => { if (result.response == 1) { clipboard.writeText(message) } }) }) autoUpdater.on("update-downloaded", (_event, releaseNotes, releaseName) => { /** * @type {Electron.MessageBoxOptions} */ const dialogOpts = { type: "info", buttons: [i18n("Restart"), i18n("Later")], title: "Update Ready", message: i18n("Install and restart now?"), }; dialog.showMessageBox(dialogOpts).then((returnValue) => { if (returnValue.response === 0) { autoUpdater.quitAndInstall(); } else { autoUpdater.autoInstallOnAppQuit; } }); }); // Translation function i18n(phrase) { let translation = loadedLanguage[phrase]; if (translation === undefined) { translation = phrase; } return translation; } /** * @param {string} itemName * @param {string} itemContent * @param {string} configPath */ function setItem(itemName, itemContent, configPath) { let config = {}; if (fs.existsSync(configPath)) { const fileContent = fs.readFileSync(configPath).toString(); try { config = fileContent ? JSON.parse(fileContent) : {}; config[itemName] = itemContent; } catch (error) { console.log("Error has occured trying to save window info", error) } } else { config[itemName] = itemContent; } fs.writeFileSync(configPath, JSON.stringify(config)); } /** * @param {string} item * @param {string} configPath * @returns {string} */ function getItem(item, configPath) { if (fs.existsSync(configPath)) { try { const configData = JSON.parse(fs.readFileSync(configPath, "utf8")); return configData[item] || ""; } catch (err) { return ""; } } else { return ""; } }