Compare commits

...

12 Commits

Author SHA1 Message Date
手瓜一十雪
49234ea5c7 fix: music proxy 2025-01-25 18:34:11 +08:00
Mlikiowa
c474158a09 release: v4.4.8 2025-01-25 05:40:09 +00:00
bietiaop
813a541e7f fix: config首次加载 2025-01-25 13:36:56 +08:00
Mlikiowa
efd489bfd4 release: v4.4.7 2025-01-25 04:56:25 +00:00
手瓜一十雪
9c88fcb610 style: lint 2025-01-25 12:56:02 +08:00
手瓜一十雪
c1cac8de19 fix: #736 2025-01-25 12:54:39 +08:00
手瓜一十雪
b03fa54e63 fix: fonts 2025-01-25 12:40:01 +08:00
bietiaop
9785731f25 fix: 字体路径 2025-01-25 10:50:47 +08:00
手瓜一十雪
566afde18b fix 2025-01-25 10:41:15 +08:00
手瓜一十雪
bae8dabc5c fix: 兼容JSON配置 异常检查 2025-01-25 10:24:39 +08:00
手瓜一十雪
0a9dfea20a fix 2025-01-25 01:21:06 +08:00
Mlikiowa
50498aa52d release: v4.4.6 2025-01-24 16:41:07 +00:00
19 changed files with 146 additions and 76 deletions

View File

@@ -53,7 +53,7 @@ NapCat 在设计理念下遵守 OneBot 规范大多数要求并且积极改进
## 感谢他们 ## 感谢他们
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持 参考部分代码 已获授权 感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持 参考部分代码 已获授权
感谢 Tencent Tdesign / Vue3 强力驱动 NapCat.WebUi 感谢 React 强力驱动 NapCat.WebUi
不过最最重要的 还是需要感谢屏幕前的你哦~ 不过最最重要的 还是需要感谢屏幕前的你哦~

View File

@@ -4,7 +4,7 @@
"name": "NapCatQQ", "name": "NapCatQQ",
"slug": "NapCat.Framework", "slug": "NapCat.Framework",
"description": "高性能的 OneBot 11 协议实现", "description": "高性能的 OneBot 11 协议实现",
"version": "4.4.4", "version": "4.4.8",
"icon": "./logo.png", "icon": "./logo.png",
"authors": [ "authors": [
{ {

View File

@@ -88,6 +88,34 @@ const AddButton: React.FC<AddButtonProps> = (props) => {
</Tooltip> </Tooltip>
</div> </div>
</DropdownItem> </DropdownItem>
<DropdownItem
key="httpSseServers"
textValue="httpSseServers"
startContent={
<div className="w-6 h-6">
<HTTPServerIcon />
</div>
}
>
<div className="flex gap-1 items-center">
HTTP SSE服务器
<Tooltip
content="「由NapCat建立」一个HTTP SSE服务器你可以「使用框架连接」此服务器或者「自己构造请求发送」至此服务器。NapCat会根据你配置的IP和端口等建立一个地址你或者你的框架应该连接到这个地址。"
showArrow
className="max-w-64"
>
<Button
isIconOnly
radius="full"
size="sm"
variant="light"
className="w-4 h-4 min-w-0"
>
<FaRegCircleQuestion />
</Button>
</Tooltip>
</div>
</DropdownItem>
<DropdownItem <DropdownItem
key="httpClients" key="httpClients"
textValue="httpClients" textValue="httpClients"

View File

@@ -24,6 +24,14 @@ export default class WebUIManager {
return data.data.Credential return data.data.Credential
} }
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() { public static async getPackageInfo() {
const { data } = const { data } =
await serverRequest.get<ServerResponse<PackageInfo>>('/base/PackageInfo') await serverRequest.get<ServerResponse<PackageInfo>>('/base/PackageInfo')

View File

@@ -1,12 +1,14 @@
import { Tab, Tabs } from '@heroui/tabs' import { Tab, Tabs } from '@heroui/tabs'
import { useLocalStorage } from '@uidotdev/usehooks' import { useLocalStorage } from '@uidotdev/usehooks'
import { useEffect } from 'react' import { useEffect, useState } from 'react'
import { useForm } from 'react-hook-form' import { useForm } from 'react-hook-form'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { useMediaQuery } from 'react-responsive' import { useMediaQuery } from 'react-responsive'
import key from '@/const/key' import key from '@/const/key'
import PageLoading from '@/components/page_loading'
import useConfig from '@/hooks/use-config' import useConfig from '@/hooks/use-config'
import useMusic from '@/hooks/use-music' import useMusic from '@/hooks/use-music'
@@ -15,6 +17,7 @@ import WebUIConfigCard from './webui'
export default function ConfigPage() { export default function ConfigPage() {
const { config, saveConfigWithoutNetwork, refreshConfig } = useConfig() const { config, saveConfigWithoutNetwork, refreshConfig } = useConfig()
const [loading, setLoading] = useState(false)
const { const {
control: onebotControl, control: onebotControl,
handleSubmit: handleOnebotSubmit, handleSubmit: handleOnebotSubmit,
@@ -82,13 +85,16 @@ export default function ConfigPage() {
} }
}) })
const onRefresh = async () => { const onRefresh = async (shotTip = true) => {
try { try {
setLoading(true)
await refreshConfig() await refreshConfig()
toast.success('刷新成功') if (shotTip) toast.success('刷新成功')
} catch (error) { } catch (error) {
const msg = (error as Error).message const msg = (error as Error).message
toast.error(`刷新失败: ${msg}`) toast.error(`刷新失败: ${msg}`)
} finally {
setLoading(false)
} }
} }
@@ -97,6 +103,10 @@ export default function ConfigPage() {
resetWebUI() resetWebUI()
}, [config]) }, [config])
useEffect(() => {
onRefresh(false)
}, [])
return ( return (
<section className="w-[1000px] max-w-full md:mx-auto gap-4 py-8 px-2 md:py-10"> <section className="w-[1000px] max-w-full md:mx-auto gap-4 py-8 px-2 md:py-10">
<Tabs <Tabs
@@ -106,12 +116,13 @@ export default function ConfigPage() {
isVertical={isMediumUp} isVertical={isMediumUp}
classNames={{ classNames={{
tabList: 'sticky flex top-14 bg-opacity-50 backdrop-blur-sm', tabList: 'sticky flex top-14 bg-opacity-50 backdrop-blur-sm',
panel: 'w-full', panel: 'w-full relative',
base: 'md:!w-auto flex-grow-0 flex-shrink-0 mr-0', base: 'md:!w-auto flex-grow-0 flex-shrink-0 mr-0',
cursor: 'bg-opacity-60 backdrop-blur-sm' cursor: 'bg-opacity-60 backdrop-blur-sm'
}} }}
> >
<Tab title="OneBot配置" key="onebot"> <Tab title="OneBot配置" key="onebot">
<PageLoading loading={loading} />
<OneBotConfigCard <OneBotConfigCard
isSubmitting={isOnebotSubmitting} isSubmitting={isOnebotSubmitting}
onRefresh={onRefresh} onRefresh={onRefresh}

View File

@@ -294,6 +294,13 @@ export default function NetworkPage() {
true true
) )
} }
if (httpSseServers.find((i) => i.name === item.name)) {
return renderCard(
'httpSseServers',
item as OneBotConfig['network']['httpSseServers'][0],
true
)
}
if (httpClients.find((i) => i.name === item.name)) { if (httpClients.find((i) => i.name === item.name)) {
return renderCard( return renderCard(
'httpClients', 'httpClients',

View File

@@ -1,14 +1,14 @@
/* HarmonyOS Sans SC */ /* HarmonyOS Sans SC */
@font-face { @font-face {
font-family: 'Harmony'; font-family: 'Harmony';
src: url('/fonts/harmony/HarmonyOS_Sans_SC_Bold.ttf') format('truetype'); src: url('/webui/fonts/harmony/HarmonyOS_Sans_SC_Bold.ttf') format('truetype');
font-weight: bold; font-weight: bold;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: 'Harmony'; font-family: 'Harmony';
src: url('/fonts/harmony/HarmonyOS_Sans_SC_Regular.ttf') format('truetype'); src: url('/webui/fonts/harmony/HarmonyOS_Sans_SC_Regular.ttf') format('truetype');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }
@@ -16,56 +16,56 @@
/* Ubuntu */ /* Ubuntu */
@font-face { @font-face {
font-family: 'Ubuntu'; font-family: 'Ubuntu';
src: url('/fonts/ubuntu/Ubuntu-Bold.ttf') format('truetype'); src: url('/webui/fonts/ubuntu/Ubuntu-Bold.ttf') format('truetype');
font-weight: bold; font-weight: bold;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: 'Ubuntu'; font-family: 'Ubuntu';
src: url('/fonts/ubuntu/Ubuntu-Regular.ttf') format('truetype'); src: url('/webui/fonts/ubuntu/Ubuntu-Regular.ttf') format('truetype');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: 'Ubuntu'; font-family: 'Ubuntu';
src: url('/fonts/ubuntu/Ubuntu-Light.ttf') format('truetype'); src: url('/webui/fonts/ubuntu/Ubuntu-Light.ttf') format('truetype');
font-weight: 300; font-weight: 300;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: 'Ubuntu'; font-family: 'Ubuntu';
src: url('/fonts/ubuntu/Ubuntu-BoldItalic.ttf') format('truetype'); src: url('/webui/fonts/ubuntu/Ubuntu-BoldItalic.ttf') format('truetype');
font-weight: bold; font-weight: bold;
font-style: italic; font-style: italic;
} }
@font-face { @font-face {
font-family: 'Ubuntu'; font-family: 'Ubuntu';
src: url('/fonts/ubuntu/Ubuntu-Italic.ttf') format('truetype'); src: url('/webui/fonts/ubuntu/Ubuntu-Italic.ttf') format('truetype');
font-weight: normal; font-weight: normal;
font-style: italic; font-style: italic;
} }
@font-face { @font-face {
font-family: 'Ubuntu'; font-family: 'Ubuntu';
src: url('/fonts/ubuntu/Ubuntu-LightItalic.ttf') format('truetype'); src: url('/webui/fonts/ubuntu/Ubuntu-LightItalic.ttf') format('truetype');
font-weight: 300; font-weight: 300;
font-style: italic; font-style: italic;
} }
@font-face { @font-face {
font-family: 'Ubuntu'; font-family: 'Ubuntu';
src: url('/fonts/pingfang/Ubuntu-Medium.ttf') format('truetype'); src: url('/webui/fonts/ubuntu/Ubuntu-Medium.ttf') format('truetype');
font-weight: 500; font-weight: 500;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: 'Ubuntu'; font-family: 'Ubuntu';
src: url('/fonts/pingfang/Ubuntu-MediumItalic.ttf') format('truetype'); src: url('/webui/fonts/ubuntu/Ubuntu-MediumItalic.ttf') format('truetype');
font-weight: 500; font-weight: 500;
font-style: italic; font-style: italic;
} }
@@ -73,21 +73,21 @@
/* LibreBaskerville */ /* LibreBaskerville */
@font-face { @font-face {
font-family: 'Libre Baskerville'; font-family: 'Libre Baskerville';
src: url('/fonts/LibreBaskerville/LibreBaskerville-Bold.ttf') format('truetype'); src: url('/webui/fonts/LibreBaskerville/LibreBaskerville-Bold.ttf') format('truetype');
font-weight: bold; font-weight: bold;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: 'Libre Baskerville'; font-family: 'Libre Baskerville';
src: url('/fonts/LibreBaskerville/LibreBaskerville-Regular.ttf') format('truetype'); src: url('/webui/fonts/LibreBaskerville/LibreBaskerville-Regular.ttf') format('truetype');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: 'Libre Baskerville'; font-family: 'Libre Baskerville';
src: url('/fonts/LibreBaskerville/LibreBaskerville-Italic.ttf') format('truetype'); src: url('/webui/fonts/LibreBaskerville/LibreBaskerville-Italic.ttf') format('truetype');
font-weight: normal; font-weight: normal;
font-style: italic; font-style: italic;
} }
@@ -95,17 +95,17 @@
/* NotoSerifSC */ /* NotoSerifSC */
@font-face { @font-face {
font-family: 'Noto Serif SC'; font-family: 'Noto Serif SC';
src: url('/fonts/NotoSerifSC-VariableFont_wght.ttf') format('truetype'); src: url('/webui/fonts/NotoSerifSC-VariableFont_wght.ttf') format('truetype');
} }
/* Outfit */ /* Outfit */
@font-face { @font-face {
font-family: 'Outfit'; font-family: 'Outfit';
src: url('/fonts/Outfit-VariableFont_wght.ttf') format('truetype'); src: url('/webui/fonts/Outfit-VariableFont_wght.ttf') format('truetype');
} }
/* FiraCode */ /* FiraCode */
@font-face { @font-face {
font-family: 'Fira Code'; font-family: 'Fira Code';
src: url('/fonts/FiraCode-VariablFont_wght.ttf') format('truetype'); src: url('/webui/fonts/FiraCode-VariableFont_wght.ttf') format('truetype');
} }

View File

@@ -6,17 +6,17 @@ import type {
Music163URLResponse Music163URLResponse
} from '@/types/music' } from '@/types/music'
import { request } from './request' import WebUIManager from '@/controllers/webui_manager'
/** /**
* 获取网易云音乐歌单 * 获取网易云音乐歌单
* @param id 歌单id * @param id 歌单id
* @returns 歌单信息 * @returns 歌单信息
*/ */
export const get163MusicList = async (id: string) => { export const get163MusicList = async (id: string) => {
const res = await request.get<Music163ListResponse>( let res = await WebUIManager.proxy<Music163ListResponse>('https://wavesgame.top/playlist/track/all?id=' + id);
`https://wavesgame.top/playlist/track/all?id=${id}` // const res = await request.get<Music163ListResponse>(
) // `https://wavesgame.top/playlist/track/all?id=${id}`
// )
if (res?.data?.code !== 200) { if (res?.data?.code !== 200) {
throw new Error('获取歌曲列表失败') throw new Error('获取歌曲列表失败')
} }
@@ -39,7 +39,7 @@ export const getSongsURL = async (ids: number[]) => {
}, [] as number[][]) }, [] as number[][])
const res = await Promise.all( const res = await Promise.all(
_ids.map(async (id) => { _ids.map(async (id) => {
const res = await request.get<Music163URLResponse>( const res = await WebUIManager.proxy<Music163URLResponse>(
`https://wavesgame.top/song/url?id=${id.join(',')}` `https://wavesgame.top/song/url?id=${id.join(',')}`
) )
if (res?.data?.code !== 200) { if (res?.data?.code !== 200) {

View File

@@ -2,7 +2,7 @@
"name": "napcat", "name": "napcat",
"private": true, "private": true,
"type": "module", "type": "module",
"version": "4.4.4", "version": "4.4.8",
"scripts": { "scripts": {
"build:universal": "npm run build:webui && vite build --mode universal || exit 1", "build:universal": "npm run build:webui && vite build --mode universal || exit 1",
"build:framework": "npm run build:webui && vite build --mode framework || exit 1", "build:framework": "npm run build:webui && vite build --mode framework || exit 1",
@@ -17,6 +17,7 @@
"dev:depend": "npm i && cd napcat.webui && npm i" "dev:depend": "npm i && cd napcat.webui && npm i"
}, },
"devDependencies": { "devDependencies": {
"json5": "^2.2.3",
"esbuild": "0.24.0", "esbuild": "0.24.0",
"@babel/preset-typescript": "^7.24.7", "@babel/preset-typescript": "^7.24.7",
"@eslint/compat": "^1.2.2", "@eslint/compat": "^1.2.2",

View File

@@ -1,6 +1,7 @@
import path from 'node:path'; import path from 'node:path';
import fs from 'node:fs'; import fs from 'node:fs';
import type { NapCatCore } from '@/core'; import type { NapCatCore } from '@/core';
import json5 from 'json5';
export abstract class ConfigBase<T> { export abstract class ConfigBase<T> {
name: string; name: string;
@@ -46,7 +47,7 @@ export abstract class ConfigBase<T> {
fs.writeFileSync(configPath, '{}'); fs.writeFileSync(configPath, '{}');
} }
try { try {
this.configData = JSON.parse(fs.readFileSync(configPath, 'utf-8')); this.configData = json5.parse(fs.readFileSync(configPath, 'utf-8'));
this.core.context.logger.logDebug(`[Core] [Config] 配置文件${configPath}加载`, this.configData); this.core.context.logger.logDebug(`[Core] [Config] 配置文件${configPath}加载`, this.configData);
return this.configData; return this.configData;
} catch (e: any) { } catch (e: any) {

View File

@@ -25,7 +25,6 @@ export class Fallback<T> {
return data; return data;
} }
} catch (error) { } catch (error) {
console.log(error);
errors.push(error instanceof Error ? error : new Error(String(error))); errors.push(error instanceof Error ? error : new Error(String(error)));
} }
} }

View File

@@ -181,28 +181,28 @@ export async function uriToLocalFile(dir: string, uri: string, filename: string
const filePath = path.join(dir, filename); const filePath = path.join(dir, filename);
switch (UriType) { switch (UriType) {
case FileUriType.Local: { case FileUriType.Local: {
const fileExt = path.extname(HandledUri); const fileExt = path.extname(HandledUri);
const localFileName = path.basename(HandledUri, fileExt) + fileExt; const localFileName = path.basename(HandledUri, fileExt) + fileExt;
const tempFilePath = path.join(dir, filename + fileExt); const tempFilePath = path.join(dir, filename + fileExt);
fs.copyFileSync(HandledUri, tempFilePath); fs.copyFileSync(HandledUri, tempFilePath);
return { success: true, errMsg: '', fileName: localFileName, path: tempFilePath }; return { success: true, errMsg: '', fileName: localFileName, path: tempFilePath };
} }
case FileUriType.Remote: { case FileUriType.Remote: {
const buffer = await httpDownload({ url: HandledUri, headers: headers }); const buffer = await httpDownload({ url: HandledUri, headers: headers });
fs.writeFileSync(filePath, buffer); fs.writeFileSync(filePath, buffer);
return { success: true, errMsg: '', fileName: filename, path: filePath }; return { success: true, errMsg: '', fileName: filename, path: filePath };
} }
case FileUriType.Base64: { case FileUriType.Base64: {
const base64 = HandledUri.replace(/^base64:\/\//, ''); const base64 = HandledUri.replace(/^base64:\/\//, '');
const base64Buffer = Buffer.from(base64, 'base64'); const base64Buffer = Buffer.from(base64, 'base64');
fs.writeFileSync(filePath, base64Buffer); fs.writeFileSync(filePath, base64Buffer);
return { success: true, errMsg: '', fileName: filename, path: filePath }; return { success: true, errMsg: '', fileName: filename, path: filePath };
} }
default: default:
return { success: false, errMsg: `识别URL失败, uri= ${uri}`, fileName: '', path: '' }; return { success: false, errMsg: `识别URL失败, uri= ${uri}`, fileName: '', path: '' };
} }
} }

View File

@@ -1 +1 @@
export const napCatVersion = '4.4.4'; export const napCatVersion = '4.4.8';

View File

@@ -67,7 +67,7 @@ export class NTQQGroupApi {
1, 1,
1000 1000
).then((data) => { ).then((data) => {
resolve(data[1]) resolve(data[1]);
}).catch(reject); }).catch(reject);
onCancel(() => { onCancel(() => {
@@ -78,9 +78,9 @@ export class NTQQGroupApi {
const task = new CancelableTask(executor); const task = new CancelableTask(executor);
this.context.session.getGroupService().getGroupShutUpMemberList(groupCode).then(e => { this.context.session.getGroupService().getGroupShutUpMemberList(groupCode).then(e => {
if (e.result !== 0) { if (e.result !== 0) {
task.cancel() task.cancel();
} }
}) });
return await task.catch(() => []); return await task.catch(() => []);
} }

View File

@@ -905,16 +905,16 @@ export class OneBotMsgApi {
const calculateTotalSize = async (elements: SendMessageElement[]): Promise<number> => { const calculateTotalSize = async (elements: SendMessageElement[]): Promise<number> => {
const sizePromises = elements.map(async element => { const sizePromises = elements.map(async element => {
switch (element.elementType) { switch (element.elementType) {
case ElementType.PTT: case ElementType.PTT:
return (await fsPromise.stat(element.pttElement.filePath)).size; return (await fsPromise.stat(element.pttElement.filePath)).size;
case ElementType.FILE: case ElementType.FILE:
return (await fsPromise.stat(element.fileElement.filePath)).size; return (await fsPromise.stat(element.fileElement.filePath)).size;
case ElementType.VIDEO: case ElementType.VIDEO:
return (await fsPromise.stat(element.videoElement.filePath)).size; return (await fsPromise.stat(element.videoElement.filePath)).size;
case ElementType.PIC: case ElementType.PIC:
return (await fsPromise.stat(element.picElement.sourcePath)).size; return (await fsPromise.stat(element.picElement.sourcePath)).size;
default: default:
return 0; return 0;
} }
}); });
const sizes = await Promise.all(sizePromises); const sizes = await Promise.all(sizePromises);
@@ -1007,14 +1007,14 @@ export class OneBotMsgApi {
} }
groupChangDecreseType2String(type: number): GroupDecreaseSubType { groupChangDecreseType2String(type: number): GroupDecreaseSubType {
switch (type) { switch (type) {
case 130: case 130:
return 'leave'; return 'leave';
case 131: case 131:
return 'kick'; return 'kick';
case 3: case 3:
return 'kick_me'; return 'kick_me';
default: default:
return 'kick'; return 'kick';
} }
} }

View File

@@ -98,7 +98,7 @@ export type NetworkConfigKey = keyof OneBotConfig['network'];
export function loadConfig(config: Partial<OneBotConfig>): OneBotConfig { export function loadConfig(config: Partial<OneBotConfig>): OneBotConfig {
const ajv = new Ajv({ useDefaults: true }); const ajv = new Ajv({ useDefaults: true, coerceTypes: true });
const validate = ajv.compile(OneBotConfigSchema); const validate = ajv.compile(OneBotConfigSchema);
const valid = validate(config); const valid = validate(config);
if (!valid) { if (!valid) {

View File

@@ -53,6 +53,6 @@ export const OB11SetConfigHandler: RequestHandler = async (req, res) => {
await WebUiDataRuntime.setOB11Config(JSON.parse(req.body.config)); await WebUiDataRuntime.setOB11Config(JSON.parse(req.body.config));
return sendSuccess(res, null); return sendSuccess(res, null);
} catch (e) { } catch (e) {
return sendError(res, 'Config Set Error'); return sendError(res, 'Error: ' + e);
} }
}; };

View File

@@ -0,0 +1,14 @@
import { RequestHandler } from "express";
import { RequestUtil } from "@/common/request";
import { sendError, sendSuccess } from "../utils/response";
export const GetProxyHandler: RequestHandler = async (req, res) => {
let { url } = req.query;
if (url && typeof url === "string") {
url = decodeURIComponent(url);
const responseText = await RequestUtil.HttpGetText(url);
res.send(sendSuccess(res, responseText));
} else {
res.send(sendError(res, 'url参数不合法'));
}
};

View File

@@ -1,11 +1,12 @@
import { Router } from 'express'; import { Router } from 'express';
import { PackageInfoHandler, QQVersionHandler } from '../api/BaseInfo'; import { PackageInfoHandler, QQVersionHandler } from '../api/BaseInfo';
import { StatusRealTimeHandler } from "@webapi/api/Status"; import { StatusRealTimeHandler } from "@webapi/api/Status";
import { GetProxyHandler } from '../api/Proxy';
const router = Router(); const router = Router();
// router: 获取nc的package.json信息 // router: 获取nc的package.json信息
router.get('/QQVersion', QQVersionHandler); router.get('/QQVersion', QQVersionHandler);
router.get('/PackageInfo', PackageInfoHandler); router.get('/PackageInfo', PackageInfoHandler);
router.get('/GetSysStatusRealTime', StatusRealTimeHandler); router.get('/GetSysStatusRealTime', StatusRealTimeHandler);
router.get('/proxy', GetProxyHandler);
export { router as BaseRouter }; export { router as BaseRouter };