Skip to content

Npplication Store 实现 ​

商店系统概述 ​

Npplication Store 是一个插件发现和管理系统,允许浏览、安装和更新插件。它具有以下特性:

  • 支持多个商店源
  • 按分类浏览插件 (自动合并相同分类)
  • 插件详情展示 (插件细节页: 包括截图、描述、依赖等)

商店源管理 ​

nitaiPage 支持用户在商店管理页添加多个商店源。默认商店源配置如下:

javascript
const STORE_SOURCES_KEY = 'storeSources';
const STORE_SOURCES_DEFAULT = ['https://nppdb.nitai.cc/store'];

插件数据获取与处理 ​

核心功能:从商店源获取插件数据并合并相同分类。

javascript
// 加载并合并所有商店源并将结果写入 window.storeData 后返回
export async function loadStoreData() {
    const sources = getStoreSources();
    const responses = await Promise.all(sources.map(url =>
        fetch(url)
            .then(res => res.json().then(data => ({ url, data })))
            .catch(err => {
                console.error(`加载商店源 ${url} 失败:`, err);
                return null;
            })
    ));

    const validData = responses.filter(data => data !== null);
    const mergedData = mergeStoreData(validData);
    window.storeData = mergedData;
    return mergedData;
}

分类及分类内的插件的合并规则

javascript
// 多源合并
export function mergeStoreData(sources) {
    const merged = { category: {} };
    sources.forEach(({ url, data }) => {
        if (data.category && data.category[0]) {
            Object.entries(data.category[0]).forEach(([key, name]) => {
                if (!merged.category[key]) merged.category[key] = name;
            });
        }
        Object.entries(data).forEach(([key, value]) => {
            if (key === 'category') return;
            if (!merged[key]) merged[key] = [];
            if (Array.isArray(value) && value[0] && typeof value[0] === 'object') {
                const plugins = Object.values(value[0]).map(plugin => ({
                    url: plugin.url,
                    screenshots: plugin.screenshots,
                    source: url
                }));
                merged[key].push(...plugins);
            }
        });
    });
    return merged;
}

商店源的数据格式见构建部署自定义商店源

插件安装 ​

javascript
export async function installNpplication(url) {
    // 1. 解析元数据
    const { metadata } = await getNpp({ url });

    // 2. 验证是否为有效的 JS 文件
    if (!await verifyJSUrl(url)) { /* 提示安装失败 */ return; }

    // 3. 检查依赖(翻译插件跳过)
    const dependencies = parseDependencies(metadata.dependencies || '');
    const dependencyCheckResult = await checkDependencies(dependencies);
    if (metadata.type !== 'translate' && !dependencyCheckResult.status) return;

    // 4. 核心插件只能从白名单源安装,不允许全新安装
    if (metadata.type === 'coreNpp' && !isAllowedCoreSource(url)) return;
    const existing = (await getPluginsList()).find(p => p.id === metadata.id);
    if (metadata.type === 'coreNpp' && !existing) return;

    // 5. 已安装插件则对比版本
    if (existing) {
        const versionComparison = compareVersions(existing.version, metadata.version);
        if (versionComparison === 0) {
            showUpdateDialog(metadata);        // 版本相同,询问是否重装
        } else if (versionComparison < 0) {
            checkUpdates(metadata.id);         // 有新版,走更新流程
        }
        return;
    }

    // 6. 全新安装:登记元数据 + 下载源码 + 提示用户刷新
    if (!['head', 'body'].includes(metadata.time)) return;
    await savePluginMetadata(metadata);
    await saveJSFile(metadata.id, url);
    showRefreshDialog();
}

插件存储 ​

javascript
// 下载 JS 文件内容并存入 nppstore/Npp
export async function saveJSFile(id, url) {
    const response = await fetch(url, { cache: 'no-store' });
    if (!response.ok) return false;

    const content = await response.text();
    await dbPut('nppstore', 'Npp', { id, content });
    return true;
}

Released under the Apache-2.0 License.