mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
49234ea5c7 | ||
![]() |
c474158a09 | ||
![]() |
813a541e7f | ||
![]() |
efd489bfd4 | ||
![]() |
9c88fcb610 | ||
![]() |
c1cac8de19 | ||
![]() |
b03fa54e63 | ||
![]() |
9785731f25 | ||
![]() |
566afde18b | ||
![]() |
bae8dabc5c | ||
![]() |
0a9dfea20a | ||
![]() |
50498aa52d |
@@ -53,7 +53,7 @@ NapCat 在设计理念下遵守 OneBot 规范大多数要求并且积极改进
|
||||
## 感谢他们
|
||||
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持 参考部分代码 已获授权
|
||||
|
||||
感谢 Tencent Tdesign / Vue3 强力驱动 NapCat.WebUi
|
||||
感谢 React 强力驱动 NapCat.WebUi
|
||||
|
||||
不过最最重要的 还是需要感谢屏幕前的你哦~
|
||||
|
||||
|
@@ -4,7 +4,7 @@
|
||||
"name": "NapCatQQ",
|
||||
"slug": "NapCat.Framework",
|
||||
"description": "高性能的 OneBot 11 协议实现",
|
||||
"version": "4.4.4",
|
||||
"version": "4.4.8",
|
||||
"icon": "./logo.png",
|
||||
"authors": [
|
||||
{
|
||||
|
@@ -88,6 +88,34 @@ const AddButton: React.FC<AddButtonProps> = (props) => {
|
||||
</Tooltip>
|
||||
</div>
|
||||
</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
|
||||
key="httpClients"
|
||||
textValue="httpClients"
|
||||
|
@@ -24,6 +24,14 @@ export default class WebUIManager {
|
||||
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() {
|
||||
const { data } =
|
||||
await serverRequest.get<ServerResponse<PackageInfo>>('/base/PackageInfo')
|
||||
|
@@ -1,12 +1,14 @@
|
||||
import { Tab, Tabs } from '@heroui/tabs'
|
||||
import { useLocalStorage } from '@uidotdev/usehooks'
|
||||
import { useEffect } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import toast from 'react-hot-toast'
|
||||
import { useMediaQuery } from 'react-responsive'
|
||||
|
||||
import key from '@/const/key'
|
||||
|
||||
import PageLoading from '@/components/page_loading'
|
||||
|
||||
import useConfig from '@/hooks/use-config'
|
||||
import useMusic from '@/hooks/use-music'
|
||||
|
||||
@@ -15,6 +17,7 @@ import WebUIConfigCard from './webui'
|
||||
|
||||
export default function ConfigPage() {
|
||||
const { config, saveConfigWithoutNetwork, refreshConfig } = useConfig()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const {
|
||||
control: onebotControl,
|
||||
handleSubmit: handleOnebotSubmit,
|
||||
@@ -82,13 +85,16 @@ export default function ConfigPage() {
|
||||
}
|
||||
})
|
||||
|
||||
const onRefresh = async () => {
|
||||
const onRefresh = async (shotTip = true) => {
|
||||
try {
|
||||
setLoading(true)
|
||||
await refreshConfig()
|
||||
toast.success('刷新成功')
|
||||
if (shotTip) toast.success('刷新成功')
|
||||
} catch (error) {
|
||||
const msg = (error as Error).message
|
||||
toast.error(`刷新失败: ${msg}`)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +103,10 @@ export default function ConfigPage() {
|
||||
resetWebUI()
|
||||
}, [config])
|
||||
|
||||
useEffect(() => {
|
||||
onRefresh(false)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<section className="w-[1000px] max-w-full md:mx-auto gap-4 py-8 px-2 md:py-10">
|
||||
<Tabs
|
||||
@@ -106,12 +116,13 @@ export default function ConfigPage() {
|
||||
isVertical={isMediumUp}
|
||||
classNames={{
|
||||
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',
|
||||
cursor: 'bg-opacity-60 backdrop-blur-sm'
|
||||
}}
|
||||
>
|
||||
<Tab title="OneBot配置" key="onebot">
|
||||
<PageLoading loading={loading} />
|
||||
<OneBotConfigCard
|
||||
isSubmitting={isOnebotSubmitting}
|
||||
onRefresh={onRefresh}
|
||||
|
@@ -294,6 +294,13 @@ export default function NetworkPage() {
|
||||
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)) {
|
||||
return renderCard(
|
||||
'httpClients',
|
||||
|
@@ -1,14 +1,14 @@
|
||||
/* HarmonyOS Sans SC */
|
||||
@font-face {
|
||||
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-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
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-style: normal;
|
||||
}
|
||||
@@ -16,56 +16,56 @@
|
||||
/* Ubuntu */
|
||||
@font-face {
|
||||
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-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
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-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
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-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
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-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
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-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
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-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
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-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
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-style: italic;
|
||||
}
|
||||
@@ -73,21 +73,21 @@
|
||||
/* LibreBaskerville */
|
||||
@font-face {
|
||||
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-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
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-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
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-style: italic;
|
||||
}
|
||||
@@ -95,17 +95,17 @@
|
||||
/* NotoSerifSC */
|
||||
@font-face {
|
||||
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 */
|
||||
@font-face {
|
||||
font-family: 'Outfit';
|
||||
src: url('/fonts/Outfit-VariableFont_wght.ttf') format('truetype');
|
||||
src: url('/webui/fonts/Outfit-VariableFont_wght.ttf') format('truetype');
|
||||
}
|
||||
|
||||
/* FiraCode */
|
||||
@font-face {
|
||||
font-family: 'Fira Code';
|
||||
src: url('/fonts/FiraCode-VariablFont_wght.ttf') format('truetype');
|
||||
src: url('/webui/fonts/FiraCode-VariableFont_wght.ttf') format('truetype');
|
||||
}
|
||||
|
@@ -6,17 +6,17 @@ import type {
|
||||
Music163URLResponse
|
||||
} from '@/types/music'
|
||||
|
||||
import { request } from './request'
|
||||
|
||||
import WebUIManager from '@/controllers/webui_manager'
|
||||
/**
|
||||
* 获取网易云音乐歌单
|
||||
* @param id 歌单id
|
||||
* @returns 歌单信息
|
||||
*/
|
||||
export const get163MusicList = async (id: string) => {
|
||||
const res = await request.get<Music163ListResponse>(
|
||||
`https://wavesgame.top/playlist/track/all?id=${id}`
|
||||
)
|
||||
let res = await WebUIManager.proxy<Music163ListResponse>('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) {
|
||||
throw new Error('获取歌曲列表失败')
|
||||
}
|
||||
@@ -39,7 +39,7 @@ export const getSongsURL = async (ids: number[]) => {
|
||||
}, [] as number[][])
|
||||
const res = await Promise.all(
|
||||
_ids.map(async (id) => {
|
||||
const res = await request.get<Music163URLResponse>(
|
||||
const res = await WebUIManager.proxy<Music163URLResponse>(
|
||||
`https://wavesgame.top/song/url?id=${id.join(',')}`
|
||||
)
|
||||
if (res?.data?.code !== 200) {
|
||||
|
@@ -2,7 +2,7 @@
|
||||
"name": "napcat",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "4.4.4",
|
||||
"version": "4.4.8",
|
||||
"scripts": {
|
||||
"build:universal": "npm run build:webui && vite build --mode universal || 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"
|
||||
},
|
||||
"devDependencies": {
|
||||
"json5": "^2.2.3",
|
||||
"esbuild": "0.24.0",
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"@eslint/compat": "^1.2.2",
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import type { NapCatCore } from '@/core';
|
||||
import json5 from 'json5';
|
||||
|
||||
export abstract class ConfigBase<T> {
|
||||
name: string;
|
||||
@@ -46,7 +47,7 @@ export abstract class ConfigBase<T> {
|
||||
fs.writeFileSync(configPath, '{}');
|
||||
}
|
||||
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);
|
||||
return this.configData;
|
||||
} catch (e: any) {
|
||||
|
@@ -25,7 +25,6 @@ export class Fallback<T> {
|
||||
return data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
errors.push(error instanceof Error ? error : new Error(String(error)));
|
||||
}
|
||||
}
|
||||
|
@@ -181,28 +181,28 @@ export async function uriToLocalFile(dir: string, uri: string, filename: string
|
||||
const filePath = path.join(dir, filename);
|
||||
|
||||
switch (UriType) {
|
||||
case FileUriType.Local: {
|
||||
const fileExt = path.extname(HandledUri);
|
||||
const localFileName = path.basename(HandledUri, fileExt) + fileExt;
|
||||
const tempFilePath = path.join(dir, filename + fileExt);
|
||||
fs.copyFileSync(HandledUri, tempFilePath);
|
||||
return { success: true, errMsg: '', fileName: localFileName, path: tempFilePath };
|
||||
}
|
||||
case FileUriType.Local: {
|
||||
const fileExt = path.extname(HandledUri);
|
||||
const localFileName = path.basename(HandledUri, fileExt) + fileExt;
|
||||
const tempFilePath = path.join(dir, filename + fileExt);
|
||||
fs.copyFileSync(HandledUri, tempFilePath);
|
||||
return { success: true, errMsg: '', fileName: localFileName, path: tempFilePath };
|
||||
}
|
||||
|
||||
case FileUriType.Remote: {
|
||||
const buffer = await httpDownload({ url: HandledUri, headers: headers });
|
||||
fs.writeFileSync(filePath, buffer);
|
||||
return { success: true, errMsg: '', fileName: filename, path: filePath };
|
||||
}
|
||||
case FileUriType.Remote: {
|
||||
const buffer = await httpDownload({ url: HandledUri, headers: headers });
|
||||
fs.writeFileSync(filePath, buffer);
|
||||
return { success: true, errMsg: '', fileName: filename, path: filePath };
|
||||
}
|
||||
|
||||
case FileUriType.Base64: {
|
||||
const base64 = HandledUri.replace(/^base64:\/\//, '');
|
||||
const base64Buffer = Buffer.from(base64, 'base64');
|
||||
fs.writeFileSync(filePath, base64Buffer);
|
||||
return { success: true, errMsg: '', fileName: filename, path: filePath };
|
||||
}
|
||||
case FileUriType.Base64: {
|
||||
const base64 = HandledUri.replace(/^base64:\/\//, '');
|
||||
const base64Buffer = Buffer.from(base64, 'base64');
|
||||
fs.writeFileSync(filePath, base64Buffer);
|
||||
return { success: true, errMsg: '', fileName: filename, path: filePath };
|
||||
}
|
||||
|
||||
default:
|
||||
return { success: false, errMsg: `识别URL失败, uri= ${uri}`, fileName: '', path: '' };
|
||||
default:
|
||||
return { success: false, errMsg: `识别URL失败, uri= ${uri}`, fileName: '', path: '' };
|
||||
}
|
||||
}
|
||||
|
@@ -1 +1 @@
|
||||
export const napCatVersion = '4.4.4';
|
||||
export const napCatVersion = '4.4.8';
|
||||
|
@@ -67,7 +67,7 @@ export class NTQQGroupApi {
|
||||
1,
|
||||
1000
|
||||
).then((data) => {
|
||||
resolve(data[1])
|
||||
resolve(data[1]);
|
||||
}).catch(reject);
|
||||
|
||||
onCancel(() => {
|
||||
@@ -78,9 +78,9 @@ export class NTQQGroupApi {
|
||||
const task = new CancelableTask(executor);
|
||||
this.context.session.getGroupService().getGroupShutUpMemberList(groupCode).then(e => {
|
||||
if (e.result !== 0) {
|
||||
task.cancel()
|
||||
task.cancel();
|
||||
}
|
||||
})
|
||||
});
|
||||
return await task.catch(() => []);
|
||||
}
|
||||
|
||||
|
@@ -905,16 +905,16 @@ export class OneBotMsgApi {
|
||||
const calculateTotalSize = async (elements: SendMessageElement[]): Promise<number> => {
|
||||
const sizePromises = elements.map(async element => {
|
||||
switch (element.elementType) {
|
||||
case ElementType.PTT:
|
||||
return (await fsPromise.stat(element.pttElement.filePath)).size;
|
||||
case ElementType.FILE:
|
||||
return (await fsPromise.stat(element.fileElement.filePath)).size;
|
||||
case ElementType.VIDEO:
|
||||
return (await fsPromise.stat(element.videoElement.filePath)).size;
|
||||
case ElementType.PIC:
|
||||
return (await fsPromise.stat(element.picElement.sourcePath)).size;
|
||||
default:
|
||||
return 0;
|
||||
case ElementType.PTT:
|
||||
return (await fsPromise.stat(element.pttElement.filePath)).size;
|
||||
case ElementType.FILE:
|
||||
return (await fsPromise.stat(element.fileElement.filePath)).size;
|
||||
case ElementType.VIDEO:
|
||||
return (await fsPromise.stat(element.videoElement.filePath)).size;
|
||||
case ElementType.PIC:
|
||||
return (await fsPromise.stat(element.picElement.sourcePath)).size;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
const sizes = await Promise.all(sizePromises);
|
||||
@@ -1007,14 +1007,14 @@ export class OneBotMsgApi {
|
||||
}
|
||||
groupChangDecreseType2String(type: number): GroupDecreaseSubType {
|
||||
switch (type) {
|
||||
case 130:
|
||||
return 'leave';
|
||||
case 131:
|
||||
return 'kick';
|
||||
case 3:
|
||||
return 'kick_me';
|
||||
default:
|
||||
return 'kick';
|
||||
case 130:
|
||||
return 'leave';
|
||||
case 131:
|
||||
return 'kick';
|
||||
case 3:
|
||||
return 'kick_me';
|
||||
default:
|
||||
return 'kick';
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -98,7 +98,7 @@ export type NetworkConfigKey = keyof OneBotConfig['network'];
|
||||
|
||||
|
||||
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 valid = validate(config);
|
||||
if (!valid) {
|
||||
|
@@ -53,6 +53,6 @@ export const OB11SetConfigHandler: RequestHandler = async (req, res) => {
|
||||
await WebUiDataRuntime.setOB11Config(JSON.parse(req.body.config));
|
||||
return sendSuccess(res, null);
|
||||
} catch (e) {
|
||||
return sendError(res, 'Config Set Error');
|
||||
return sendError(res, 'Error: ' + e);
|
||||
}
|
||||
};
|
||||
|
14
src/webui/src/api/Proxy.ts
Normal file
14
src/webui/src/api/Proxy.ts
Normal 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参数不合法'));
|
||||
}
|
||||
};
|
@@ -1,11 +1,12 @@
|
||||
import { Router } from 'express';
|
||||
import { PackageInfoHandler, QQVersionHandler } from '../api/BaseInfo';
|
||||
import { StatusRealTimeHandler } from "@webapi/api/Status";
|
||||
import { GetProxyHandler } from '../api/Proxy';
|
||||
|
||||
const router = Router();
|
||||
// router: 获取nc的package.json信息
|
||||
router.get('/QQVersion', QQVersionHandler);
|
||||
router.get('/PackageInfo', PackageInfoHandler);
|
||||
router.get('/GetSysStatusRealTime', StatusRealTimeHandler);
|
||||
|
||||
router.get('/proxy', GetProxyHandler);
|
||||
export { router as BaseRouter };
|
||||
|
Reference in New Issue
Block a user