import { dialog, shell, IpcMainEvent, ipcMain, clipboard } from 'electron' import fs from 'fs-extra' import path from 'path' import { IGuiMenuItem, PicGo as PicGoCore } from 'piclist' import { IObject, IFilter } from '@picgo/store/dist/types' import picgo from '@core/picgo' import { GalleryDB } from '@core/datastore' import { dbPathChecker } from '@core/datastore/dbChecker' import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler' import windowManager from 'apis/app/window/windowManager' import GuiApi from 'apis/gui' import { showNotification } from '~/utils/common' import pasteTemplate from '~/utils/pasteTemplate' import { i18nManager, T } from '~/i18n' import { rpcServer } from '~/events/rpc' import { PICGO_SAVE_CONFIG, PICGO_GET_CONFIG, PICGO_GET_DB, PICGO_INSERT_DB, PICGO_INSERT_MANY_DB, PICGO_UPDATE_BY_ID_DB, PICGO_GET_BY_ID_DB, PICGO_REMOVE_BY_ID_DB, PICGO_OPEN_FILE, PICGO_OPEN_DIRECTORY, PASTE_TEXT, OPEN_WINDOW, GET_LANGUAGE_LIST, SET_CURRENT_LANGUAGE, GET_CURRENT_LANGUAGE, PICGO_GET_CONFIG_SYNC } from '#/events/constants' import { configPaths } from '#/utils/configPaths' import { IPasteStyle, IPicGoHelperType, IWindowList } from '#/types/enum' import { handleStreamlinePluginName, simpleClone } from '#/utils/common' // eslint-disable-next-line const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require // const PluginHandler = requireFunc('picgo/lib/PluginHandler').default const STORE_PATH = path.dirname(dbPathChecker()) // const CONFIG_PATH = path.join(STORE_PATH, '/data.json') interface GuiMenuItem { label: string handle: (arg0: PicGoCore, arg1: GuiApi) => Promise } // get uploader or transformer config const getConfig = (name: string, type: IPicGoHelperType, ctx: PicGoCore) => { let config: any[] = [] if (name === '') { return config } else { const handler = ctx.helper[type].get(name) if (handler) { if (handler.config) { config = handler.config(ctx) } } return config } } const handleConfigWithFunction = (config: any[]) => { for (const i in config) { if (typeof config[i].default === 'function') { config[i].default = config[i].default() } if (typeof config[i].choices === 'function') { config[i].choices = config[i].choices() } } return config } const getPluginList = (): IPicGoPlugin[] => { const pluginList = picgo.pluginLoader.getFullList() const list = [] for (const i in pluginList) { const plugin = picgo.pluginLoader.getPlugin(pluginList[i])! const pluginPath = path.join(STORE_PATH, `/node_modules/${pluginList[i]}`) const pluginPKG = requireFunc(path.join(pluginPath, 'package.json')) const uploaderName = plugin.uploader || '' const transformerName = plugin.transformer || '' let menu: Omit[] = [] if (plugin.guiMenu) { menu = plugin.guiMenu(picgo).map(item => ({ label: item.label })) } let gui = false if (pluginPKG.keywords && pluginPKG.keywords.length > 0) { if (pluginPKG.keywords.includes('picgo-gui-plugin')) { gui = true } } const obj: IPicGoPlugin = { name: handleStreamlinePluginName(pluginList[i]), fullName: pluginList[i], author: pluginPKG.author.name || pluginPKG.author, description: pluginPKG.description, logo: 'file://' + path.join(pluginPath, 'logo.png').split(path.sep).join('/'), version: pluginPKG.version, gui, config: { plugin: { fullName: pluginList[i], name: handleStreamlinePluginName(pluginList[i]), config: plugin.config ? handleConfigWithFunction(plugin.config(picgo)) : [] }, uploader: { name: uploaderName, config: handleConfigWithFunction(getConfig(uploaderName, IPicGoHelperType.uploader, picgo)) }, transformer: { name: transformerName, config: handleConfigWithFunction(getConfig(uploaderName, IPicGoHelperType.transformer, picgo)) } }, enabled: picgo.getConfig(`picgoPlugins.${pluginList[i]}`), homepage: pluginPKG.homepage ? pluginPKG.homepage : '', guiMenu: menu, ing: false } list.push(obj) } return list } const handleGetPluginList = () => { ipcMain.on('getPluginList', (event: IpcMainEvent) => { try { const list = simpleClone(getPluginList()) // here can just send JS Object not function // or will cause [Failed to serialize arguments] error event.sender.send('pluginList', list) } catch (e: any) { event.sender.send('pluginList', []) showNotification({ title: T('TIPS_GET_PLUGIN_LIST_FAILED'), body: e.message }) picgo.log.error(e) } }) } const handlePluginInstall = () => { ipcMain.on('installPlugin', async (event: IpcMainEvent, fullName: string) => { const dispose = handleNPMError() const res = await picgo.pluginHandler.install([fullName]) event.sender.send('installPlugin', { success: res.success, body: fullName, errMsg: res.success ? '' : res.body }) if (res.success) { shortKeyHandler.registerPluginShortKey(res.body[0]) } else { showNotification({ title: T('PLUGIN_INSTALL_FAILED'), body: res.body as string }) } event.sender.send('hideLoading') dispose() }) } const handlePluginUninstall = async (fullName: string) => { const window = windowManager.get(IWindowList.SETTING_WINDOW)! const dispose = handleNPMError() const res = await picgo.pluginHandler.uninstall([fullName]) if (res.success) { window.webContents.send('uninstallSuccess', res.body[0]) shortKeyHandler.unregisterPluginShortKey(res.body[0]) } else { showNotification({ title: T('PLUGIN_UNINSTALL_FAILED'), body: res.body as string }) } window.webContents.send('hideLoading') dispose() } const handlePluginUpdate = async (fullName: string | string[]) => { const window = windowManager.get(IWindowList.SETTING_WINDOW)! const dispose = handleNPMError() const res = await picgo.pluginHandler.update(typeof fullName === 'string' ? [fullName] : fullName) if (res.success) { window.webContents.send('updateSuccess', res.body[0]) } else { showNotification({ title: T('PLUGIN_UPDATE_FAILED'), body: res.body as string }) } window.webContents.send('hideLoading') dispose() } const handleUpdateAllPlugin = () => { ipcMain.on('updateAllPlugin', async (_: IpcMainEvent, list: string[]) => { handlePluginUpdate(list) }) } const handleNPMError = (): IDispose => { const handler = (msg: string) => { if (msg === 'NPM is not installed') { dialog.showMessageBox({ title: T('TIPS_ERROR'), message: T('TIPS_INSTALL_NODE_AND_RELOAD_PICGO'), buttons: ['Yes'] }).then((res) => { if (res.response === 0) { shell.openExternal('https://nodejs.org/') } }) } } picgo.once('failed', handler) return () => picgo.off('failed', handler) } const handleGetPicBedConfig = () => { ipcMain.on('getPicBedConfig', (event: IpcMainEvent, type: string) => { const name = picgo.helper.uploader.get(type)?.name || type if (picgo.helper.uploader.get(type)?.config) { const _config = picgo.helper.uploader.get(type)!.config!(picgo) const config = handleConfigWithFunction(_config) event.sender.send('getPicBedConfig', config, name) } else { event.sender.send('getPicBedConfig', [], name) } }) } // TODO: remove it const handlePluginActions = () => { ipcMain.on('pluginActions', (_: IpcMainEvent, name: string, label: string) => { const plugin = picgo.pluginLoader.getPlugin(name) if (plugin?.guiMenu?.(picgo)?.length) { const menu: GuiMenuItem[] = plugin.guiMenu(picgo) menu.forEach(item => { if (item.label === label) { item.handle(picgo, GuiApi.getInstance()) } }) } }) } const handleRemoveFiles = () => { ipcMain.on('removeFiles', (_: IpcMainEvent, files: ImgInfo[]) => { setTimeout(() => { picgo.emit('remove', files, GuiApi.getInstance()) }, 500) }) } const handlePicGoSaveConfig = () => { ipcMain.on(PICGO_SAVE_CONFIG, (_: IpcMainEvent, data: IObj) => { picgo.saveConfig(data) }) } const handlePicGoGetConfig = () => { ipcMain.handle(PICGO_GET_CONFIG, (_, key: string | undefined) => { return picgo.getConfig(key) }) } const handlePicGoGetConfigSync = () => { ipcMain.on(PICGO_GET_CONFIG_SYNC, (event: IpcMainEvent, key: string | undefined) => { const result = picgo.getConfig(key) event.returnValue = result }) } const handleImportLocalPlugin = () => { ipcMain.on('importLocalPlugin', async (event: IpcMainEvent) => { const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)! const res = await dialog.showOpenDialog(settingWindow, { properties: ['openDirectory'] }) const filePaths = res.filePaths if (filePaths.length > 0) { const res = await picgo.pluginHandler.install(filePaths) if (res.success) { try { const list = simpleClone(getPluginList()) event.sender.send('pluginList', list) } catch (e: any) { event.sender.send('pluginList', []) showNotification({ title: T('TIPS_GET_PLUGIN_LIST_FAILED'), body: e.message }) } showNotification({ title: T('PLUGIN_IMPORT_SUCCEED'), body: '' }) } else { showNotification({ title: T('PLUGIN_IMPORT_FAILED'), body: res.body as string }) } } event.sender.send('hideLoading') }) } const handlePicGoGalleryDB = () => { ipcMain.on(PICGO_GET_DB, async (event: IpcMainEvent, filter: IFilter, callbackId: string) => { const dbStore = GalleryDB.getInstance() const res = await dbStore.get(filter) event.sender.send(PICGO_GET_DB, res, callbackId) }) ipcMain.on(PICGO_INSERT_DB, async (event: IpcMainEvent, value: IObject, callbackId: string) => { const dbStore = GalleryDB.getInstance() const res = await dbStore.insert(value) event.sender.send(PICGO_INSERT_DB, res, callbackId) }) ipcMain.on(PICGO_INSERT_MANY_DB, async (event: IpcMainEvent, value: IObject[], callbackId: string) => { const dbStore = GalleryDB.getInstance() const res = await dbStore.insertMany(value) event.sender.send(PICGO_INSERT_MANY_DB, res, callbackId) }) ipcMain.on(PICGO_UPDATE_BY_ID_DB, async (event: IpcMainEvent, id: string, value: IObject[], callbackId: string) => { const dbStore = GalleryDB.getInstance() const res = await dbStore.updateById(id, value) event.sender.send(PICGO_UPDATE_BY_ID_DB, res, callbackId) }) ipcMain.on(PICGO_GET_BY_ID_DB, async (event: IpcMainEvent, id: string, callbackId: string) => { const dbStore = GalleryDB.getInstance() const res = await dbStore.getById(id) event.sender.send(PICGO_GET_BY_ID_DB, res, callbackId) }) ipcMain.on(PICGO_REMOVE_BY_ID_DB, async (event: IpcMainEvent, id: string, callbackId: string) => { const dbStore = GalleryDB.getInstance() const res = await dbStore.removeById(id) event.sender.send(PICGO_REMOVE_BY_ID_DB, res, callbackId) }) ipcMain.handle(PASTE_TEXT, async (_, item: ImgInfo, copy = true) => { const pasteStyle = picgo.getConfig(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN const customLink = picgo.getConfig(configPaths.settings.customLink) const txt = await pasteTemplate(pasteStyle, item, customLink) if (copy) { clipboard.writeText(txt) } return txt }) } const handleOpenFile = () => { ipcMain.on(PICGO_OPEN_FILE, (event: IpcMainEvent, fileName: string) => { const abFilePath = path.join(STORE_PATH, fileName) if (!fs.existsSync(abFilePath)) { fs.writeFileSync(abFilePath, '') } shell.openPath(abFilePath) }) } const handleOpenDirectory = () => { ipcMain.on(PICGO_OPEN_DIRECTORY, (event: IpcMainEvent, dirPath?: string, inStorePath: boolean = true) => { if (inStorePath) { dirPath = path.join(STORE_PATH, dirPath || '') } if (!dirPath || !fs.existsSync(dirPath)) { return } shell.openPath(dirPath) }) } const handleOpenWindow = () => { ipcMain.on(OPEN_WINDOW, (event: IpcMainEvent, windowName: IWindowList) => { const window = windowManager.get(windowName) if (window) { window.show() } }) } const handleI18n = () => { ipcMain.on(GET_LANGUAGE_LIST, (event: IpcMainEvent) => { event.sender.send(GET_LANGUAGE_LIST, i18nManager.languageList) }) ipcMain.on(SET_CURRENT_LANGUAGE, (event: IpcMainEvent, language: string) => { i18nManager.setCurrentLanguage(language) const { lang, locales } = i18nManager.getCurrentLocales() picgo.i18n.setLanguage(lang) if (process.platform === 'darwin') { const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW) trayWindow?.webContents.send(SET_CURRENT_LANGUAGE, lang, locales) } const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW) settingWindow?.webContents.send(SET_CURRENT_LANGUAGE, lang, locales) if (windowManager.has(IWindowList.MINI_WINDOW)) { const miniWindow = windowManager.get(IWindowList.MINI_WINDOW) miniWindow?.webContents.send(SET_CURRENT_LANGUAGE, lang, locales) } // event.sender.send(SET_CURRENT_LANGUAGE, lang, locales) }) ipcMain.on(GET_CURRENT_LANGUAGE, (event: IpcMainEvent) => { const { lang, locales } = i18nManager.getCurrentLocales() event.sender.send(GET_CURRENT_LANGUAGE, lang, locales) }) } const handleRPCActions = () => { rpcServer.start() } export default { listen () { handleGetPluginList() handlePluginInstall() handleGetPicBedConfig() handlePluginActions() handleRemoveFiles() handlePicGoSaveConfig() handlePicGoGetConfig() handlePicGoGetConfigSync() handlePicGoGalleryDB() handleImportLocalPlugin() handleUpdateAllPlugin() handleOpenFile() handleOpenDirectory() handleOpenWindow() handleI18n() handleRPCActions() }, // TODO: separate to single file handlePluginUninstall, handlePluginUpdate }