mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-02-05 07:01:16 +00:00
Added the ability to pass a JWT secret key when restarting the worker process by updating environment variable handling and message passing. Improved port retry logic in the backend to allow multiple attempts on the same port before incrementing. Also refactored process API to use getter for pid property. Ensure Electron app is ready before creating process manager Adds a check to await electron.app.whenReady() if the Electron app is not yet ready before instantiating the ElectronProcessManager. This prevents potential issues when accessing Electron APIs before the app is fully initialized. Add mirror selection support for version updates Introduces the ability to specify and select GitHub mirror sources for fetching tags, releases, and action artifacts throughout the backend and frontend. Updates API endpoints, internal helper functions, and UI components to allow users to choose a mirror for version queries and updates, improving reliability in regions with limited GitHub access. Also enhances version comparison logic and improves artifact metadata display. Refactor artifact fetching to use HTML parsing only Removed all GitHub API dependencies for fetching workflow runs and artifacts. Now, workflow runs are parsed directly from the HTML of the Actions page, and artifact URLs are constructed using nightly.link. Also added workflow title and mirror fields to ActionArtifact, and simplified mirror list without latency comments.
181 lines
6.0 KiB
TypeScript
181 lines
6.0 KiB
TypeScript
import { RequestHandler } from 'express';
|
||
import { WebUiDataRuntime } from '@/napcat-webui-backend/src/helper/Data';
|
||
|
||
import { sendSuccess } from '@/napcat-webui-backend/src/utils/response';
|
||
import { WebUiConfig } from '@/napcat-webui-backend/index';
|
||
import { getLatestTag, getAllTags, compareSemVer } from 'napcat-common/src/helper';
|
||
import { getLatestActionArtifacts, getMirrorConfig } from '@/napcat-common/src/mirror';
|
||
import { NapCatCoreWorkingEnv } from '@/napcat-webui-backend/src/types';
|
||
|
||
export const GetNapCatVersion: RequestHandler = (_, res) => {
|
||
const data = WebUiDataRuntime.GetNapCatVersion();
|
||
sendSuccess(res, { version: data });
|
||
};
|
||
|
||
export const getLatestTagHandler: RequestHandler = async (_, res) => {
|
||
try {
|
||
const latestTag = await getLatestTag();
|
||
sendSuccess(res, latestTag);
|
||
} catch (error) {
|
||
res.status(500).json({ error: 'Failed to fetch latest tag', details: (error as Error).message });
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 版本信息接口
|
||
*/
|
||
export interface VersionInfo {
|
||
tag: string;
|
||
type: 'release' | 'prerelease' | 'action';
|
||
/** Action artifact 专用字段 */
|
||
artifactId?: number;
|
||
artifactName?: string;
|
||
createdAt?: string;
|
||
expiresAt?: string;
|
||
size?: number;
|
||
workflowRunId?: number;
|
||
headSha?: string;
|
||
workflowTitle?: string;
|
||
}
|
||
|
||
/**
|
||
* 获取所有可用的版本(release + action artifacts)
|
||
* 支持分页,懒加载:根据 type 参数只获取需要的版本类型
|
||
*/
|
||
export const getAllReleasesHandler: RequestHandler = async (req, res) => {
|
||
try {
|
||
const page = parseInt(req.query['page'] as string) || 1;
|
||
const pageSize = parseInt(req.query['pageSize'] as string) || 20;
|
||
const typeFilter = req.query['type'] as string | undefined; // 'release' | 'action' | 'all'
|
||
const searchQuery = (req.query['search'] as string || '').toLowerCase().trim();
|
||
const mirror = req.query['mirror'] as string | undefined;
|
||
|
||
let versions: VersionInfo[] = [];
|
||
let actionVersions: VersionInfo[] = [];
|
||
let usedMirror = '';
|
||
|
||
// If mirror is specified, report it as used (will be confirmed by actual fetching response)
|
||
if (mirror) {
|
||
usedMirror = mirror;
|
||
}
|
||
|
||
// 懒加载:只获取需要的版本类型
|
||
const needReleases = !typeFilter || typeFilter === 'all' || typeFilter === 'release';
|
||
const needActions = typeFilter === 'action' || typeFilter === 'all';
|
||
|
||
// 获取正式版本(仅当需要时)
|
||
if (needReleases) {
|
||
try {
|
||
const result = await getAllTags(mirror);
|
||
// 如果没有指定镜像,使用实际上使用的镜像
|
||
if (!mirror) {
|
||
usedMirror = result.mirror;
|
||
}
|
||
|
||
versions = result.tags.map(tag => {
|
||
const isPrerelease = /-(alpha|beta|rc|dev|pre|snapshot)/i.test(tag);
|
||
return {
|
||
tag,
|
||
type: isPrerelease ? 'prerelease' : 'release',
|
||
} as VersionInfo;
|
||
});
|
||
|
||
// 使用语义化版本排序(最新的在前)
|
||
versions.sort((a, b) => -compareSemVer(a.tag, b.tag));
|
||
} catch {
|
||
// 如果获取 tags 失败,返回空列表而不是抛出错误
|
||
versions = [];
|
||
}
|
||
}
|
||
|
||
// 获取 Action Artifacts(仅当需要时)
|
||
if (needActions) {
|
||
try {
|
||
const { artifacts, mirror: actionMirror } = await getLatestActionArtifacts('NapNeko', 'NapCatQQ', 'build.yml', 'main', 10, mirror);
|
||
|
||
// 根据当前工作环境自动过滤对应的 artifact 类型
|
||
const isFramework = WebUiDataRuntime.getWorkingEnv() === NapCatCoreWorkingEnv.Framework;
|
||
const targetArtifactName = isFramework ? 'NapCat.Framework' : 'NapCat.Shell';
|
||
|
||
// 如果没有指定镜像,且 action 实际上用了一个镜像(自动选择的),更新 usedMirror
|
||
if (!mirror && actionMirror) {
|
||
usedMirror = actionMirror;
|
||
}
|
||
|
||
actionVersions = artifacts
|
||
.filter(a => a && a.name === targetArtifactName)
|
||
.map(a => ({
|
||
tag: `action-${a.id}`,
|
||
type: 'action' as const,
|
||
artifactId: a.id,
|
||
artifactName: a.name,
|
||
createdAt: a.created_at,
|
||
expiresAt: a.expires_at,
|
||
size: a.size_in_bytes,
|
||
workflowRunId: a.workflow_run_id,
|
||
headSha: a.head_sha,
|
||
workflowTitle: a.workflow_title,
|
||
}));
|
||
} catch {
|
||
// 获取失败时返回空列表
|
||
actionVersions = [];
|
||
}
|
||
}
|
||
|
||
// 合并版本列表(action 在最前面)
|
||
let allVersions = [...actionVersions, ...versions];
|
||
|
||
// 搜索过滤
|
||
if (searchQuery) {
|
||
allVersions = allVersions.filter(v => {
|
||
const tagMatch = v.tag.toLowerCase().includes(searchQuery);
|
||
const nameMatch = v.artifactName?.toLowerCase().includes(searchQuery);
|
||
const titleMatch = v.workflowTitle?.toLowerCase().includes(searchQuery);
|
||
const shaMatch = v.headSha?.toLowerCase().includes(searchQuery);
|
||
return tagMatch || nameMatch || titleMatch || shaMatch;
|
||
});
|
||
}
|
||
|
||
// 分页
|
||
const total = allVersions.length;
|
||
const totalPages = Math.ceil(total / pageSize);
|
||
const start = (page - 1) * pageSize;
|
||
const end = start + pageSize;
|
||
const paginatedVersions = allVersions.slice(start, end);
|
||
|
||
sendSuccess(res, {
|
||
versions: paginatedVersions,
|
||
pagination: {
|
||
page,
|
||
pageSize,
|
||
total,
|
||
totalPages,
|
||
},
|
||
mirror: usedMirror
|
||
});
|
||
} catch (error) {
|
||
res.status(500).json({ error: 'Failed to fetch releases' });
|
||
}
|
||
};
|
||
|
||
export const QQVersionHandler: RequestHandler = (_, res) => {
|
||
const data = WebUiDataRuntime.getQQVersion();
|
||
sendSuccess(res, data);
|
||
};
|
||
|
||
export const GetThemeConfigHandler: RequestHandler = async (_, res) => {
|
||
const data = await WebUiConfig.GetTheme();
|
||
sendSuccess(res, data);
|
||
};
|
||
|
||
export const SetThemeConfigHandler: RequestHandler = async (req, res) => {
|
||
const { theme } = req.body;
|
||
await WebUiConfig.UpdateTheme(theme);
|
||
sendSuccess(res, { message: '更新成功' });
|
||
};
|
||
|
||
export const GetMirrorsHandler: RequestHandler = (_, res) => {
|
||
const config = getMirrorConfig();
|
||
sendSuccess(res, { mirrors: config.fileMirrors });
|
||
};
|