mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-03-03 09:10:25 +00:00
Add plugin icon support and caching
Introduce support for plugin icons across backend and frontend. Updates include: - napcat-onebot: add optional `icon` field to PluginPackageJson. - Backend (api/Plugin, PluginStore, router): add handlers/utilities to locate and serve plugin icons (`GetPluginIconHandler`, getPluginIconUrl, findPluginIconPath) and wire the route `/api/Plugin/Icon/:pluginId`. - Cache logic: implement `cachePluginIcon` to fetch GitHub user avatars and store as `data/icon.png` when package.json lacks an icon; invoked after plugin install (regular and SSE flows). - Frontend: add `icon` to PluginItem, prefer backend-provided icon URL in plugin card (via new getPluginIconUrl util that appends webui_token query param), and add the util to handle token-based image requests. - Plugin store UI: add a Random category (shuffled), client-side pagination, and reset page on tab/search changes. These changes let the UI display plugin icons (falling back to author/avatar or Vercel avatars) and cache icons for better UX, while handling auth by passing the token as a query parameter for img src requests.
This commit is contained in:
@@ -8,6 +8,49 @@ import path from 'path';
|
||||
import fs from 'fs';
|
||||
import compressing from 'compressing';
|
||||
|
||||
/**
|
||||
* 获取插件图标 URL
|
||||
* 优先使用 package.json 中的 icon 字段,否则检查缓存的图标文件
|
||||
*/
|
||||
function getPluginIconUrl (pluginId: string, pluginPath: string, iconField?: string): string | undefined {
|
||||
// 1. 检查 package.json 中指定的 icon 文件
|
||||
if (iconField) {
|
||||
const iconPath = path.join(pluginPath, iconField);
|
||||
if (fs.existsSync(iconPath)) {
|
||||
return `/api/Plugin/Icon/${encodeURIComponent(pluginId)}`;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 检查 config 目录中缓存的图标 (固定 icon.png)
|
||||
const cachedIcon = path.join(webUiPathWrapper.configPath, 'plugins', pluginId, 'icon.png');
|
||||
if (fs.existsSync(cachedIcon)) {
|
||||
return `/api/Plugin/Icon/${encodeURIComponent(pluginId)}`;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找插件图标文件的实际路径
|
||||
*/
|
||||
function findPluginIconPath (pluginId: string, pluginPath: string, iconField?: string): string | undefined {
|
||||
// 1. 优先使用 package.json 中指定的 icon
|
||||
if (iconField) {
|
||||
const iconPath = path.join(pluginPath, iconField);
|
||||
if (fs.existsSync(iconPath)) {
|
||||
return iconPath;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 检查 config 目录中缓存的图标 (固定 icon.png)
|
||||
const cachedIcon = path.join(webUiPathWrapper.configPath, 'plugins', pluginId, 'icon.png');
|
||||
if (fs.existsSync(cachedIcon)) {
|
||||
return cachedIcon;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Helper to get the plugin manager adapter
|
||||
const getPluginManager = (): OB11PluginMangerAdapter | null => {
|
||||
const ob11 = WebUiDataRuntime.getOneBotContext() as NapCatOneBot11Adapter;
|
||||
@@ -77,6 +120,7 @@ export const GetPluginListHandler: RequestHandler = async (_req, res) => {
|
||||
hasPages: boolean;
|
||||
homepage?: string;
|
||||
repository?: string;
|
||||
icon?: string;
|
||||
}> = new Array();
|
||||
|
||||
// 收集所有插件的扩展页面
|
||||
@@ -117,7 +161,8 @@ export const GetPluginListHandler: RequestHandler = async (_req, res) => {
|
||||
homepage: p.packageJson?.homepage,
|
||||
repository: typeof p.packageJson?.repository === 'string'
|
||||
? p.packageJson.repository
|
||||
: p.packageJson?.repository?.url
|
||||
: p.packageJson?.repository?.url,
|
||||
icon: getPluginIconUrl(p.id, p.pluginPath, p.packageJson?.icon),
|
||||
});
|
||||
|
||||
// 收集插件的扩展页面
|
||||
@@ -600,3 +645,24 @@ export const ImportLocalPluginHandler: RequestHandler = async (req, res) => {
|
||||
return sendError(res, 'Failed to import plugin: ' + e.message);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取插件图标
|
||||
*/
|
||||
export const GetPluginIconHandler: RequestHandler = async (req, res) => {
|
||||
const pluginId = req.params['pluginId'];
|
||||
if (!pluginId) return sendError(res, 'Plugin ID is required');
|
||||
|
||||
const pluginManager = getPluginManager();
|
||||
if (!pluginManager) return sendError(res, 'Plugin Manager not found');
|
||||
|
||||
const plugin = pluginManager.getPluginInfo(pluginId);
|
||||
if (!plugin) return sendError(res, 'Plugin not found');
|
||||
|
||||
const iconPath = findPluginIconPath(pluginId, plugin.pluginPath, plugin.packageJson?.icon);
|
||||
if (!iconPath) {
|
||||
return res.status(404).json({ code: -1, message: 'Icon not found' });
|
||||
}
|
||||
|
||||
return res.sendFile(iconPath);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user