mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-02-13 00:10:27 +00:00
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:
221
packages/napcat-webui-frontend/src/controllers/file_manager.ts
Normal file
221
packages/napcat-webui-frontend/src/controllers/file_manager.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
89
packages/napcat-webui-frontend/src/controllers/qq_manager.ts
Normal file
89
packages/napcat-webui-frontend/src/controllers/qq_manager.ts
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
200
packages/napcat-webui-frontend/src/controllers/webui_manager.ts
Normal file
200
packages/napcat-webui-frontend/src/controllers/webui_manager.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user