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) 对本项目的大力支持 参考部分代码 已获授权
感谢 Tencent Tdesign / Vue3 强力驱动 NapCat.WebUi
感谢 React 强力驱动 NapCat.WebUi
不过最最重要的 还是需要感谢屏幕前的你哦~

View File

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

View File

@@ -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"

View File

@@ -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')

View File

@@ -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}

View File

@@ -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',

View File

@@ -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');
}

View File

@@ -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) {

View File

@@ -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",

View File

@@ -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) {

View File

@@ -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)));
}
}

View File

@@ -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: '' };
}
}

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,
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(() => []);
}

View File

@@ -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';
}
}

View File

@@ -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) {

View File

@@ -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);
}
};

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 { 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 };