import axios from 'axios' import { SHOW_PLUGIN_PAGE_MENU } from '#/events/constants' import { IRPCActionType } from '~/universal/types/enum' import { getConfig, invokeRPC, openURL, saveConfig, sendRPC, sendToMain } from '@/utils/dataSender' import { ipc } from '@/utils/bridge' import type { ProviderPluginConfig } from '@/components/main/providers/types' import { normalizePluginConfigSchema } from '@/components/common/normalize-plugin-schema' export type IRefreshConfigSchemaArgs = | { target: 'plugin', pluginFullName: string, draftValues: Record } | { target: 'transformer', pluginFullName: string, draftValues: Record } | { target: 'uploader', uploaderName: string, draftValues: Record } interface PluginInstallResult { success: boolean body: string errMsg: string } interface NpmSearchResultObject { package: { name: string version: string description: string keywords?: string[] maintainers?: Array<{ username: string }> links?: { homepage?: string } } } function streamlinePluginName (fullName: string) { return fullName.replace(/^picgo-plugin-/, '') } const README_FILE_CANDIDATES = ['README.md', 'readme.md', 'Readme.md'] as const export const pluginsAdapter = { getInstalledPlugins (): Promise { return new Promise((resolve, reject) => { const cleanup = ipc.once('pluginList', (list: IPicGoPlugin[]) => { resolve(list) }) try { sendToMain('getPluginList') } catch (error) { cleanup() reject(error) } }) }, installPlugin (fullName: string): Promise { return invokeRPC(IRPCActionType.INSTALL_PLUGIN, fullName).then((result: IRPCResult) => ({ success: result.success, body: fullName, errMsg: result.success ? '' : (result.error || '') })) }, importLocalPlugin () { return invokeRPC(IRPCActionType.IMPORT_LOCAL_PLUGIN) }, uninstallPlugin (fullName: string) { return invokeRPC(IRPCActionType.UNINSTALL_PLUGIN, fullName) }, updatePlugin (fullName: string) { return invokeRPC(IRPCActionType.UPDATE_PLUGIN, fullName) }, togglePluginEnabled (fullName: string, enabled: boolean) { return invokeRPC( enabled ? IRPCActionType.ENABLE_PLUGIN : IRPCActionType.DISABLE_PLUGIN, fullName ) }, async refreshConfigSchema (payload: IRefreshConfigSchemaArgs): Promise { const result = await invokeRPC(IRPCActionType.REFRESH_CONFIG_SCHEMA, payload) if (!result.success) { throw new Error(result.error || 'Failed to refresh plugin config schema') } return normalizePluginConfigSchema(result.data) }, async saveTransformer (transformer: string) { await saveConfig({ 'picBed.transformer': transformer }) }, async fetchPluginReadme (fullName: string, options?: { installed?: boolean }) { // For installed plugins (including locally imported ones not on npm), // read README from disk via the main process — jsdelivr 404s on // unpublished plugins. Non-installed plugins (e.g. search results) fall // back to the CDN. if (options?.installed) { const result = await invokeRPC( IRPCActionType.GET_INSTALLED_PLUGIN_README, fullName ) if (!result.success) { throw new Error(result.error || 'Failed to read local plugin readme') } return result.data ?? '' } let lastError: unknown = null for (const readmeFileName of README_FILE_CANDIDATES) { try { const response = await axios.get( `https://cdn.jsdelivr.net/npm/${fullName}/${readmeFileName}`, { responseType: 'text' } ) return response.data || '' } catch (error) { lastError = error } } throw lastError instanceof Error ? lastError : new Error('Failed to fetch plugin readme') }, async fetchPluginDeprecation ( fullName: string, version: string ): Promise<{ isDeprecated: boolean, message: string }> { const encodedName = fullName.replace('/', '%2F') const encodedVersion = encodeURIComponent(version) const response = await axios.get<{ deprecated?: string | boolean }>( `https://registry.npmjs.com/${encodedName}/${encodedVersion}` ) const rawDeprecated = response.data?.deprecated if (typeof rawDeprecated === 'string') { return { isDeprecated: true, message: rawDeprecated } } if (rawDeprecated === true) { return { isDeprecated: true, message: '' } } return { isDeprecated: false, message: '' } }, openPluginMenu (plugin: IPicGoPlugin) { sendToMain(SHOW_PLUGIN_PAGE_MENU, plugin) }, reloadApp () { sendRPC(IRPCActionType.RELOAD_APP) }, async getNeedReload () { return (await getConfig('needReload')) || false }, async setNeedReload (value: boolean) { await saveConfig({ needReload: value }) }, async getPluginConfigValues (currentType: 'plugin' | 'transformer' | 'uploader', configName: string) { if (currentType === 'plugin') { return (await getConfig(configName)) || {} } if (currentType === 'uploader') { return (await getConfig(`picBed.${configName}`)) || {} } return (await getConfig(`transformer.${configName}`)) || {} }, async savePluginConfig (currentType: 'plugin' | 'transformer' | 'uploader', configName: string, values: IStringKeyMap) { if (currentType === 'plugin') { await saveConfig({ [configName]: values }) return } if (currentType === 'uploader') { await saveConfig({ [`picBed.${configName}`]: values }) return } await saveConfig({ [`transformer.${configName}`]: values }) }, async searchPlugins (searchText: string, installedPlugins: IPicGoPlugin[]) { const response = await axios.get<{ objects: NpmSearchResultObject[] }>(`https://registry.npmjs.com/-/v1/search?text=${searchText}`) const installedNames = new Set(installedPlugins.map((item) => item.fullName)) return response.data.objects .filter((item) => item.package.name.includes('picgo-plugin-')) .filter((item) => { const description = item.package.description || '' return !description.includes('picgo.net') && !description.includes('PicGo官方') }) .map((item) => ({ name: streamlinePluginName(item.package.name), fullName: item.package.name, author: item.package.maintainers?.[0]?.username || '', description: item.package.description || '', logo: `https://cdn.jsdelivr.net/npm/${item.package.name}/logo.png`, config: {}, homepage: item.package.links?.homepage || '', hasInstall: installedNames.has(item.package.name), version: item.package.version, gui: Boolean(item.package.keywords?.includes('picgo-gui-plugin')), ing: false } as IPicGoPlugin)) }, openPluginHomepage (url: string) { if (!url) { return } openURL(url) }, openAwesomeList () { openURL('https://github.com/PicGo/Awesome-PicGo') } }