refactor: 整体重构 (#1381)

* feat: pnpm new

* Refactor build and release workflows, update dependencies

Switch build scripts and workflows from npm to pnpm, update build and artifact paths, and simplify release workflow by removing version detection and changelog steps. Add new dependencies (silk-wasm, express, ws, node-pty-prebuilt-multiarch), update exports in package.json files, and add vite config for napcat-framework. Also, rename manifest.json for framework package and fix static asset copying in shell build config.
This commit is contained in:
手瓜一十雪
2025-11-13 15:39:42 +08:00
committed by GitHub
parent c3d1892545
commit ed19c52f25
778 changed files with 2356 additions and 26391 deletions

View File

@@ -0,0 +1,221 @@
import toast from 'react-hot-toast';
import { serverRequest } from '@/utils/request';
export interface FileInfo {
name: string;
isDirectory: boolean;
size: number;
mtime: Date;
}
export default class FileManager {
public static async listFiles (path: string = '/') {
const { data } = await serverRequest.get<ServerResponse<FileInfo[]>>(
`/File/list?path=${encodeURIComponent(path)}`
);
return data.data;
}
// 新增:按目录获取
public static async listDirectories (path: string = '/') {
const { data } = await serverRequest.get<ServerResponse<FileInfo[]>>(
`/File/list?path=${encodeURIComponent(path)}&onlyDirectory=true`
);
return data.data;
}
public static async createDirectory (path: string): Promise<boolean> {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/File/mkdir',
{ path }
);
return data.data;
}
public static async delete (path: string) {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/File/delete',
{ path }
);
return data.data;
}
public static async readFile (path: string) {
const { data } = await serverRequest.get<ServerResponse<string>>(
`/File/read?path=${encodeURIComponent(path)}`
);
return data.data;
}
public static async writeFile (path: string, content: string) {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/File/write',
{ path, content }
);
return data.data;
}
public static async createFile (path: string): Promise<boolean> {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/File/create',
{ path }
);
return data.data;
}
public static async batchDelete (paths: string[]) {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/File/batchDelete',
{ paths }
);
return data.data;
}
public static async rename (oldPath: string, newPath: string) {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/File/rename',
{ oldPath, newPath }
);
return data.data;
}
public static async move (sourcePath: string, targetPath: string) {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/File/move',
{ sourcePath, targetPath }
);
return data.data;
}
public static async batchMove (
items: { sourcePath: string; targetPath: string; }[]
) {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/File/batchMove',
{ items }
);
return data.data;
}
public static download (path: string) {
const downloadUrl = `/File/download?path=${encodeURIComponent(path)}`;
toast
.promise(
serverRequest
.post(downloadUrl, undefined, {
responseType: 'blob',
})
.catch((e) => {
console.error(e);
throw new Error('下载失败');
}),
{
loading: '正在下载文件...',
success: '下载成功',
error: '下载失败',
}
)
.then((response) => {
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
let fileName = path.split('/').pop() || '';
if (path.split('.').length === 1) {
fileName += '.zip';
}
link.setAttribute('download', fileName);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
})
.catch((e) => {
console.error(e);
});
}
public static async batchDownload (paths: string[]) {
const downloadUrl = '/File/batchDownload';
toast
.promise(
serverRequest
.post(
downloadUrl,
{ paths },
{
responseType: 'blob',
}
)
.catch((e) => {
console.error(e);
throw new Error('下载失败');
}),
{
loading: '正在下载文件...',
success: '下载成功',
error: '下载失败',
}
)
.then((response) => {
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
const fileName = 'files.zip';
link.setAttribute('download', fileName);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
})
.catch((e) => {
console.error(e);
});
}
public static async downloadToURL (path: string) {
const downloadUrl = `/File/download?path=${encodeURIComponent(path)}`;
const response = await serverRequest.post(downloadUrl, undefined, {
responseType: 'blob',
});
return window.URL.createObjectURL(new Blob([response.data]));
}
public static async upload (path: string, files: File[]) {
const formData = new FormData();
files.forEach((file) => {
formData.append('files', file);
});
const { data } = await serverRequest.post<ServerResponse<boolean>>(
`/File/upload?path=${encodeURIComponent(path)}`,
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
},
}
);
return data.data;
}
public static async uploadWebUIFont (file: File) {
const formData = new FormData();
formData.append('file', file);
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/File/font/upload/webui',
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
},
}
);
return data.data;
}
public static async deleteWebUIFont () {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/File/font/delete/webui'
);
return data.data;
}
}

View File

@@ -0,0 +1,89 @@
import { serverRequest } from '@/utils/request';
import { SelfInfo } from '@/types/user';
export default class QQManager {
public static async getOB11Config () {
const data = await serverRequest.post<ServerResponse<OneBotConfig>>(
'/OB11Config/GetConfig'
);
return data.data.data;
}
public static async setOB11Config (config: OneBotConfig) {
await serverRequest.post<ServerResponse<null>>('/OB11Config/SetConfig', {
config: JSON.stringify(config),
});
}
public static async checkQQLoginStatus () {
const data = await serverRequest.post<
ServerResponse<{
isLogin: string
qrcodeurl: string
}>
>('/QQLogin/CheckLoginStatus');
return data.data.data;
}
public static async checkQQLoginStatusWithQrcode () {
const data = await serverRequest.post<
ServerResponse<{ qrcodeurl: string; isLogin: string }>
>('/QQLogin/CheckLoginStatus');
return data.data.data;
}
public static async getQQLoginQrcode () {
const data = await serverRequest.post<
ServerResponse<{
qrcode: string
}>
>('/QQLogin/GetQQLoginQrcode');
return data.data.data.qrcode;
}
public static async getQQQuickLoginList () {
const data = await serverRequest.post<ServerResponse<string[]>>(
'/QQLogin/GetQuickLoginList'
);
return data.data.data;
}
public static async getQQQuickLoginListNew () {
const data = await serverRequest.post<ServerResponse<LoginListItem[]>>(
'/QQLogin/GetQuickLoginListNew'
);
return data.data.data;
}
public static async setQuickLogin (uin: string) {
await serverRequest.post<ServerResponse<null>>('/QQLogin/SetQuickLogin', {
uin,
});
}
public static async getQQLoginInfo () {
const data = await serverRequest.post<ServerResponse<SelfInfo>>(
'/QQLogin/GetQQLoginInfo'
);
return data.data.data;
}
public static async getQuickLoginQQ () {
const { data } = await serverRequest.post<ServerResponse<string>>(
'/QQLogin/GetQuickLoginQQ'
);
return data.data;
}
public static async setQuickLoginQQ (uin: string) {
await serverRequest.post<ServerResponse<null>>('/QQLogin/SetQuickLoginQQ', {
uin,
});
}
}

View File

@@ -0,0 +1,133 @@
import { serverRequest } from '@/utils/request';
type TerminalCallback = (data: string) => void;
interface TerminalConnection {
ws: WebSocket
callbacks: Set<TerminalCallback>
isConnected: boolean
buffer: string[] // 添加缓存数组
}
export interface TerminalSession {
id: string
}
export interface TerminalInfo {
id: string
}
class TerminalManager {
private connections: Map<string, TerminalConnection> = new Map();
private readonly MAX_BUFFER_SIZE = 1000; // 限制缓存大小
async createTerminal (cols: number, rows: number): Promise<TerminalSession> {
const { data } = await serverRequest.post<ServerResponse<TerminalSession>>(
'/Log/terminal/create',
{ cols, rows }
);
return data.data;
}
async closeTerminal (id: string): Promise<void> {
await serverRequest.post(`/Log/terminal/${id}/close`);
}
async getTerminalList (): Promise<TerminalInfo[]> {
const { data } =
await serverRequest.get<ServerResponse<TerminalInfo[]>>(
'/Log/terminal/list'
);
return data.data;
}
connectTerminal (
id: string,
callback: TerminalCallback,
config?: {
cols?: number
rows?: number
}
): WebSocket {
let conn = this.connections.get(id);
const { cols = 80, rows = 24 } = config || {};
if (!conn) {
const url = new URL(window.location.href);
url.protocol = url.protocol.replace('http', 'ws');
url.pathname = '/api/ws/terminal';
url.searchParams.set('id', id);
const token = JSON.parse(localStorage.getItem('token') || '');
if (!token) {
throw new Error('No token found');
}
url.searchParams.set('token', token);
const ws = new WebSocket(url.toString());
conn = {
ws,
callbacks: new Set([callback]),
isConnected: false,
buffer: [], // 初始化缓存
};
ws.onmessage = (event) => {
const data = event.data;
// 保存到缓存
conn?.buffer.push(data);
if ((conn?.buffer.length ?? 0) > this.MAX_BUFFER_SIZE) {
conn?.buffer.shift();
}
conn?.callbacks.forEach((cb) => cb(data));
};
ws.onopen = () => {
if (conn) conn.isConnected = true;
this.sendResize(id, cols, rows);
};
ws.onclose = () => {
if (conn) conn.isConnected = false;
};
this.connections.set(id, conn);
} else {
conn.callbacks.add(callback);
// 恢复历史内容
conn.buffer.forEach((data) => callback(data));
}
return conn.ws;
}
disconnectTerminal (id: string, callback: TerminalCallback) {
const conn = this.connections.get(id);
if (!conn) return;
conn.callbacks.delete(callback);
}
removeTerminal (id: string) {
const conn = this.connections.get(id);
if (conn?.ws.readyState === WebSocket.OPEN) {
conn.ws.close();
}
this.connections.delete(id);
}
sendInput (id: string, data: string) {
const conn = this.connections.get(id);
if (conn?.ws.readyState === WebSocket.OPEN) {
conn.ws.send(JSON.stringify({ type: 'input', data }));
}
}
sendResize (id: string, cols: number, rows: number) {
const conn = this.connections.get(id);
if (conn?.ws.readyState === WebSocket.OPEN) {
conn.ws.send(JSON.stringify({ type: 'resize', cols, rows }));
}
}
}
const terminalManager = new TerminalManager();
export default terminalManager;

View File

@@ -0,0 +1,200 @@
import CryptoJS from 'crypto-js';
import { EventSourcePolyfill } from 'event-source-polyfill';
import { LogLevel } from '@/const/enum';
import { serverRequest } from '@/utils/request';
export interface Log {
level: LogLevel;
message: string;
}
export default class WebUIManager {
public static async checkWebUiLogined () {
const { data } =
await serverRequest.post<ServerResponse<boolean>>('/auth/check');
return data.data;
}
public static async loginWithToken (token: string) {
const sha256 = CryptoJS.SHA256(token + '.napcat').toString();
const { data } = await serverRequest.post<ServerResponse<AuthResponse>>(
'/auth/login',
{ hash: sha256 }
);
return data.data.Credential;
}
public static async changePassword (oldToken: string, newToken: string) {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/auth/update_token',
{ oldToken, newToken }
);
return data.data;
}
public static async proxy<T> (url = '') {
const data = await serverRequest.get<ServerResponse<string>>(
'/base/proxy?url=' + encodeURIComponent(url)
);
data.data.data = JSON.parse(data.data.data);
return data.data as ServerResponse<T>;
}
public static async getPackageInfo () {
const { data } =
await serverRequest.get<ServerResponse<PackageInfo>>('/base/PackageInfo');
return data.data;
}
public static async getQQVersion () {
const { data } =
await serverRequest.get<ServerResponse<string>>('/base/QQVersion');
return data.data;
}
public static async getThemeConfig () {
const { data } =
await serverRequest.get<ServerResponse<ThemeConfig>>('/base/Theme');
return data.data;
}
public static async setThemeConfig (theme: ThemeConfig) {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/base/SetTheme',
{ theme }
);
return data.data;
}
public static async getLogList () {
const { data } =
await serverRequest.get<ServerResponse<string[]>>('/Log/GetLogList');
return data.data;
}
public static async getLogContent (logName: string) {
const { data } = await serverRequest.get<ServerResponse<string>>(
`/Log/GetLog?id=${logName}`
);
return data.data;
}
public static getRealTimeLogs (writer: (data: Log[]) => void) {
const token = localStorage.getItem('token');
if (!token) {
throw new Error('未登录');
}
const _token = JSON.parse(token);
const eventSource = new EventSourcePolyfill('/api/Log/GetLogRealTime', {
headers: {
Authorization: `Bearer ${_token}`,
Accept: 'text/event-stream',
},
withCredentials: true,
});
eventSource.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
data.message = data.message.replace(/\n/g, '\r\n');
writer([data]);
} catch (error) {
console.error(error);
}
};
eventSource.onerror = (error) => {
console.error('SSE连接出错:', error);
eventSource.close();
};
return eventSource;
}
public static getSystemStatus (writer: (data: SystemStatus) => void) {
const token = localStorage.getItem('token');
if (!token) {
throw new Error('未登录');
}
const _token = JSON.parse(token);
const eventSource = new EventSourcePolyfill(
'/api/base/GetSysStatusRealTime',
{
headers: {
Authorization: `Bearer ${_token}`,
Accept: 'text/event-stream',
},
withCredentials: true,
}
);
eventSource.onmessage = (event) => {
try {
const data = JSON.parse(event.data) as SystemStatus;
writer(data);
} catch (error) {
console.error(error);
}
};
eventSource.onerror = (error) => {
console.error('SSE连接出错:', error);
eventSource.close();
};
return eventSource;
}
// 获取WebUI基础配置
public static async getWebUIConfig () {
const { data } = await serverRequest.get<ServerResponse<WebUIConfig>>(
'/WebUIConfig/GetConfig'
);
return data.data;
}
// 更新WebUI基础配置
public static async updateWebUIConfig (config: Partial<WebUIConfig>) {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/WebUIConfig/UpdateConfig',
config
);
return data.data;
}
// 获取是否禁用WebUI
public static async getDisableWebUI () {
const { data } = await serverRequest.get<ServerResponse<boolean>>(
'/WebUIConfig/GetDisableWebUI'
);
return data.data;
}
// 更新是否禁用WebUI
public static async updateDisableWebUI (disable: boolean) {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/WebUIConfig/UpdateDisableWebUI',
{ disable }
);
return data.data;
}
// 获取是否禁用非局域网访问
public static async getDisableNonLANAccess () {
const { data } = await serverRequest.get<ServerResponse<boolean>>(
'/WebUIConfig/GetDisableNonLANAccess'
);
return data.data;
}
// 更新是否禁用非局域网访问
public static async updateDisableNonLANAccess (disable: boolean) {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/WebUIConfig/UpdateDisableNonLANAccess',
{ disable }
);
return data.data;
}
}