Feature(custom): add new script system

ISSUES CLOSED: #462
This commit is contained in:
Kuingsmile
2026-01-26 17:57:44 +08:00
parent a10e701cc9
commit 9c8698907e
34 changed files with 1359 additions and 143 deletions

View File

@@ -26,6 +26,7 @@ import { configPaths } from '~/utils/configPaths'
import { IPasteStyle, IWindowList } from '~/utils/enum'
import { isMacOSVersionGreaterThanOrEqualTo } from '~/utils/getMacOSVersion'
import pasteTemplate from '~/utils/pasteTemplate'
import { runScriptInStage } from '~/utils/runScript'
import { hideMiniWindow, openMainWindow, openMiniWindow } from '~/utils/windowHelper'
import menubarPng from '../../../../../resources/menubar.png?asset&asarUnpack'
@@ -308,8 +309,8 @@ export function createTray(tooltip: string) {
const rawInput = cloneDeep(files)
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
const res = await uploader.setWebContents(trayWindow?.webContents).uploadReturnCtx(files)
const imgs = res[0] ? res[0] : false
const backImgs = res[1] ? res[1] : false
const imgs = res.ctx?.output ? res.ctx.output : false
const backImgs = res.backupCtx?.output ? res.backupCtx.output : false
const deleteLocalFile = allConfig.settings?.deleteLocalFile || false
if (imgs !== false) {
const pasteText: string[] = []
@@ -334,7 +335,8 @@ export function createTray(tooltip: string) {
notification.show()
}, i * 100)
}
await GalleryDB.getInstance().insert(imgs[i])
const inserted = await GalleryDB.getInstance().insert(imgs[i])
runScriptInStage('onUploadSuccess', res.ctx || picgo, { galleryItem: inserted })
}
handleCopyUrl(pasteText.join('\n'))
trayWindow?.webContents.send('dragFiles', imgs)

View File

@@ -1,46 +1,56 @@
import path from 'node:path'
import { themesDir } from '@core/datastore/dirs'
import * as fsWalk from '@nodelib/fs.walk'
import AdmZip from 'adm-zip'
import axios from 'axios'
import fs from 'fs-extra'
import { randomStringGenerator } from '@/manage/utils/common'
import logger from '~/apis/core/picgo/logger'
import { IWindowList } from '~/utils/enum'
import windowManager from '../window/windowManager'
export async function resolveThemes(): Promise<{ key: string; label: string }[]> {
const files = fsWalk.walkSync(themesDir(), {
followSymbolicLinks: true,
fs,
stats: true,
throwErrorOnBrokenSymbolicLink: false,
})
const result: string[] = []
files.forEach(item => {
if (item.stats?.isFile()) {
result.push(path.basename(item.path))
}
})
const themes = await Promise.all(
result
.filter(file => file.endsWith('.css'))
.map(async file => {
const css = (await fs.readFile(path.join(themesDir(), file), 'utf-8')) || ''
let name = file
if (css.startsWith('/*')) {
name = css.split('\n')[0].replace('/*', '').replace('*/', '').trim() || file
const dir = themesDir()
const entries = await fs.readdir(dir, { withFileTypes: true })
const themePromises = entries
.filter(entry => entry.isFile() && entry.name.endsWith('.css'))
.map(async entry => {
const filePath = path.join(dir, entry.name)
let label = entry.name
let fd
try {
fd = await fs.open(filePath, 'r')
const buffer = Buffer.alloc(256)
const { bytesRead } = await fs.read(fd, buffer, 0, 256, 0)
const content = buffer.toString('utf8', 0, bytesRead)
const match = content.match(/^\/\*\s*(.*?)\s*\*\//)
if (match && match[1]) {
label = match[1].trim()
}
return { key: file, label: name }
}),
)
if (themes.find(theme => theme.key === 'default.css')) {
return themes
} catch (e: any) {
logger.error(e)
} finally {
if (fd !== undefined) await fs.close(fd)
}
return { key: entry.name, label }
})
const themes = await Promise.all(themePromises)
const hasDefault = themes.some(t => t.key === 'default.css')
if (!hasDefault) {
themes.unshift({ key: 'default.css', label: '默认' })
} else {
return [{ key: 'default.css', label: '默认' }, ...themes]
const idx = themes.findIndex(t => t.key === 'default.css')
const [defaultTheme] = themes.splice(idx, 1)
themes.unshift(defaultTheme)
}
return themes
}
export async function fetchThemes(): Promise<boolean> {

View File

@@ -11,8 +11,9 @@ import { handleCopyUrl, handleUrlEncodeWithSetting } from '~/utils/common'
import { configPaths } from '~/utils/configPaths'
import { IPasteStyle, IWindowList } from '~/utils/enum'
import pasteTemplate from '~/utils/pasteTemplate'
import { runScriptInStage } from '~/utils/runScript'
const handleClipboardUploadingReturnCtx = async (img?: IUploadOption): Promise<(ImgInfo[] | false)[]> => {
const handleClipboardUploadingReturnCtx = async (img?: IUploadOption): Promise<IuploadReturnCtxResult> => {
const useBuiltinClipboardConfig = picgo.getConfig<boolean | undefined>(configPaths.settings.useBuiltinClipboard)
const useBuiltinClipboard = useBuiltinClipboardConfig === undefined ? true : !!useBuiltinClipboardConfig
const win = windowManager.getAvailableWindow()
@@ -26,8 +27,8 @@ export const uploadClipboardFiles = async (): Promise<IStringKeyMap> => {
let img: ImgInfo[] | false = false
let backImg: ImgInfo[] | false = false
const res = await handleClipboardUploadingReturnCtx()
img = res[0] ? res[0] : false
backImg = res[1] ? res[1] : false
img = res.ctx?.output ? res.ctx.output : false
backImg = res.backupCtx?.output ? res.backupCtx.output : false
const allConfig = picgo.getConfig<any>() || {}
if (img !== false) {
if (img.length > 0) {
@@ -50,6 +51,7 @@ export const uploadClipboardFiles = async (): Promise<IStringKeyMap> => {
}, 100)
}
const inserted = await GalleryDB.getInstance().insert(img[0])
runScriptInStage('onUploadSuccess', res.ctx || picgo, { galleryItem: inserted })
// trayWindow just be created in mac/windows, not in linux
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
trayWindow?.webContents?.send('clipboardFiles', [])
@@ -93,8 +95,8 @@ export const uploadChoosedFiles = async (
let imgs: ImgInfo[] | false = false
let backImgs: ImgInfo[] | false = false
const res = await uploader.setWebContents(webContents).uploadReturnCtx(input)
imgs = res[0] ? res[0] : false
backImgs = res[1] ? res[1] : false
imgs = res.ctx?.output ? res.ctx.output : false
backImgs = res.backupCtx?.output ? res.backupCtx.output : false
const result = []
const allConfig = picgo.getConfig<any>() || {}
if (imgs !== false) {
@@ -140,6 +142,7 @@ export const uploadChoosedFiles = async (
}
}
const inserted = await GalleryDB.getInstance().insert(imgs[i])
runScriptInStage('onUploadSuccess', res.ctx || picgo, { galleryItem: inserted })
result.push({
url: handleUrlEncodeWithSetting(inserted.imgUrl!),
fullResult: inserted,

View File

@@ -118,15 +118,15 @@ class Uploader {
return filePath
}
async uploadWithBuildInClipboardReturnCtx(img?: IUploadOption): Promise<(ImgInfo[] | false)[]> {
async uploadWithBuildInClipboardReturnCtx(img?: IUploadOption): Promise<IuploadReturnCtxResult> {
let imgPath: string | false = false
try {
imgPath = await this.getClipboardImagePath()
if (!imgPath) return [false, false]
if (!imgPath) return { ctx: undefined, backupCtx: undefined }
return await this.uploadReturnCtx(img ?? [imgPath])
} catch (e: any) {
logger.error(e)
return [false, false]
return { ctx: undefined, backupCtx: undefined }
} finally {
if (imgPath && imgPath.startsWith(path.join(picgo.baseDir, CLIPBOARD_IMAGE_FOLDER))) {
fs.remove(imgPath)
@@ -134,24 +134,24 @@ class Uploader {
}
}
async uploadReturnCtx(img?: IUploadOption): Promise<(ImgInfo[] | false)[]> {
async uploadReturnCtx(img?: IUploadOption): Promise<IuploadReturnCtxResult> {
try {
const result = [false, false] as (ImgInfo[] | false)[]
const result = { ctx: undefined, backupCtx: undefined } as IuploadReturnCtxResult
const res = await picgo.uploadReturnCtx(img)
const allConfig = picgo.getConfig<any>() || {}
if (Array.isArray(res.output) && res.output.some((item: ImgInfo) => item.imgUrl)) {
res.output.forEach((item: ImgInfo) => {
if (Array.isArray(res.ctx?.output) && res.ctx?.output.some((item: ImgInfo) => item.imgUrl)) {
res.ctx.output.forEach((item: ImgInfo) => {
item.config = JSON.parse(JSON.stringify(allConfig.picBed?.[item.type!]))
})
result[0] = res.output
result.ctx = res.ctx
}
if (Array.isArray(res.backupOutput) && res.backupOutput.some((item: ImgInfo) => item.imgUrl)) {
res.backupOutput.forEach((item: ImgInfo) => {
if (Array.isArray(res.backupCtx?.output) && res.backupCtx?.output.some((item: ImgInfo) => item.imgUrl)) {
res.backupCtx.output.forEach((item: ImgInfo) => {
item.config = JSON.parse(JSON.stringify(allConfig.picBed?.[item.type!]))
})
result[1] = res.backupOutput
result.backupCtx = res.backupCtx
}
return result
} catch (e: any) {
@@ -163,7 +163,7 @@ class Uploader {
clickToCopy: true,
})
}, 500)
return [false, false]
return { ctx: undefined, backupCtx: undefined } as IuploadReturnCtxResult
} finally {
ipcMain.removeAllListeners(GET_RENAME_FILE_NAME)
}

View File

@@ -43,6 +43,10 @@ export function defaultDir() {
return userDataDir()
}
export function scriptsDir() {
return path.join(dataDir(), 'scripts')
}
export function defaultConfigPath() {
if (isPortable()) {
return path.join(exeDir(), 'data', 'data.json')

View File

@@ -13,6 +13,7 @@ import { T as $t } from '~/i18n'
import { handleCopyUrl } from '~/utils/common'
import { IPasteStyle } from '~/utils/enum'
import pasteTemplate from '~/utils/pasteTemplate'
import { runScriptInStage } from '~/utils/runScript'
// Cross-process support may be required in the future
class GuiApi implements IGuiApi {
@@ -74,8 +75,8 @@ class GuiApi implements IGuiApi {
const webContents = this.getWebcontentsByWindowId(this.windowId)
const rawInput = cloneDeep(input)
const res = await uploader.setWebContents(webContents!).uploadReturnCtx(input)
const imgs = res[0] ? res[0] : false
const backImgs = res[1] ? res[1] : false
const imgs = res.ctx?.output ? res.ctx.output : false
const backImgs = res.backupCtx?.output ? res.backupCtx.output : false
let result: ImgInfo[] = []
const allConfig = picgo.getConfig<any>() || {}
if (imgs !== false) {
@@ -103,7 +104,8 @@ class GuiApi implements IGuiApi {
notification.show()
}, i * 100)
}
await GalleryDB.getInstance().insert(imgs[i])
const inserted = await GalleryDB.getInstance().insert(imgs[i])
runScriptInStage('onUploadSuccess', res.ctx || picgo, { galleryItem: inserted })
}
handleCopyUrl(pasteText.join('\n'))
webContents?.send('uploadFiles')