Compare commits

...

16 Commits

Author SHA1 Message Date
手瓜一十雪
daf42c8203 release: 2.4.2 2024-09-09 15:04:19 +08:00
手瓜一十雪
0a18bae3b5 fix: #351 2024-09-09 15:04:00 +08:00
手瓜一十雪
919705966c build: 2.4.1 2024-09-09 09:19:23 +08:00
手瓜一十雪
2c54aee63e build: test 2024-09-08 21:39:53 +08:00
手瓜一十雪
3f80bdf2a3 fix 2024-09-08 18:29:21 +08:00
手瓜一十雪
1c429b8dd3 fix: type 2024-09-08 18:26:07 +08:00
手瓜一十雪
5669e2b0b7 fix: 跟进实际逻辑 2024-09-08 18:21:55 +08:00
手瓜一十雪
1a6a43babf release: 2.4.0 2024-09-08 10:50:30 +08:00
手瓜一十雪
2650db5ddc fix: 字段V2 2024-09-08 10:48:33 +08:00
手瓜一十雪
255491a107 fix: hex计算问题 2024-09-08 10:41:30 +08:00
手瓜一十雪
5c64147dfa fix: encodeFile 2024-09-08 10:34:49 +08:00
手瓜一十雪
39f4118577 fix: #347 2024-09-08 10:30:30 +08:00
手瓜一十雪
f7f6e4736a fix #349 2024-09-08 10:24:36 +08:00
手瓜一十雪
c635da7ebb style: lint 2024-09-08 10:10:47 +08:00
手瓜一十雪
58124b006a Merge pull request #346 from NapNeko/Refactor-2.4.x
refactor: v2.4.0
2024-09-08 10:08:41 +08:00
手瓜一十雪
563aeccd0f refactor: fileNameEncode 2024-09-08 10:07:49 +08:00
26 changed files with 124 additions and 64 deletions

View File

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

View File

@@ -2,7 +2,7 @@
"name": "napcat",
"private": true,
"type": "module",
"version": "2.3.7",
"version": "2.4.2",
"scripts": {
"build:framework": "vite build --mode framework",
"build:shell": "vite build --mode shell",

View File

@@ -215,6 +215,10 @@ export async function checkUriType(Uri: string) {
}
return { Uri: filePath, Type: FileUriType.Local };
}
if (uri.startsWith('data:')) {
const data = uri.split(',')[1];
if (data) return { Uri: data, Type: FileUriType.Base64 };
}
}, Uri);
if (OtherFileRet) return OtherFileRet;
@@ -231,7 +235,8 @@ export async function uri2local(dir: string, uri: string, filename: string | und
//解析File协议和本地文件
if (UriType == FileUriType.Local) {
const fileExt = path.extname(HandledUri);
const filename = path.basename(HandledUri, fileExt);
let filename = path.basename(HandledUri, fileExt);
filename += fileExt;
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: HandledUri, isLocal: true };
}
//接下来都要有文件名
@@ -251,7 +256,7 @@ export async function uri2local(dir: string, uri: string, filename: string | und
const filePath = path.join(dir, filename);
const buffer = await httpDownload(HandledUri);
fs.writeFileSync(filePath, buffer);
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath, isLocal: true };
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath, isLocal: false };
}
//解析Base64
if (UriType == FileUriType.Base64) {
@@ -266,7 +271,7 @@ export async function uri2local(dir: string, uri: string, filename: string | und
fileExt = ext;
filename = filename + '.' + ext;
}
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath, isLocal: true };
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath, isLocal: false };
}
return { success: false, errMsg: '未知文件类型', fileName: '', ext: '', path: '', isLocal: false };
}

View File

@@ -25,8 +25,13 @@ export async function solveAsyncProblem<T extends (...args: any[]) => Promise<an
}
export class FileNapCatOneBotUUID {
static encodeModelId(peer: Peer, modelId: string, fileId: string): string {
return `NapCatOneBot|ModelIdFile|${peer.chatType}|${peer.peerUid}|${modelId}|${fileId}`;
static encodeModelId(peer: Peer, modelId: string, fileId: string, endString: string = ""): string {
const data = `NapCatOneBot|ModelIdFile|${peer.chatType}|${peer.peerUid}|${modelId}|${fileId}`;
//前四个字节塞data长度
const length = Buffer.alloc(4 + data.length);
length.writeUInt32BE(data.length * 2, 0);//储存data的hex长度
length.write(data, 4);
return length.toString('hex') + endString;
}
static decodeModelId(uuid: string): undefined | {
@@ -34,8 +39,14 @@ export class FileNapCatOneBotUUID {
modelId: string,
fileId: string
} {
if (!uuid.startsWith('NapCatOneBot|ModelIdFile|')) return undefined;
const data = uuid.split('|');
//前四个字节是data长度
const length = Buffer.from(uuid.slice(0, 8), 'hex').readUInt32BE(0);
//根据length计算需要读取的长度
const dataId = uuid.slice(8, 8 + length);
//hex还原为string
const realData = Buffer.from(dataId, 'hex').toString();
if (!realData.startsWith('NapCatOneBot|ModelIdFile|')) return undefined;
const data = realData.split('|');
if (data.length !== 6) return undefined;
const [, , chatType, peerUid, modelId, fileId] = data;
return {
@@ -48,8 +59,14 @@ export class FileNapCatOneBotUUID {
};
}
static encode(peer: Peer, msgId: string, elementId: string): string {
return `NapCatOneBot|MsgFile|${peer.chatType}|${peer.peerUid}|${msgId}|${elementId}`;
static encode(peer: Peer, msgId: string, elementId: string, endString: string = ""): string {
const data = `NapCatOneBot|MsgFile|${peer.chatType}|${peer.peerUid}|${msgId}|${elementId}`;
//前四个字节塞data长度
//一个字节8位 一个ascii字符1字节 一个hex字符4位 表示一个ascii字符需要两个hex字符
const length = Buffer.alloc(4 + data.length);
length.writeUInt32BE(data.length * 2, 0);
length.write(data, 4);
return length.toString('hex') + endString;
}
static decode(uuid: string): undefined | {
@@ -57,8 +74,14 @@ export class FileNapCatOneBotUUID {
msgId: string,
elementId: string
} {
if (!uuid.startsWith('NapCatOneBot|MsgFile|')) return undefined;
const data = uuid.split('|');
//前四个字节是data长度
const length = Buffer.from(uuid.slice(0, 8), 'hex').readUInt32BE(0);
//根据length计算需要读取的长度
const dataId = uuid.slice(8, 8 + length);
//hex还原为string
const realData = Buffer.from(dataId, 'hex').toString();
if (!realData.startsWith('NapCatOneBot|MsgFile|')) return undefined;
const data = realData.split('|');
if (data.length !== 6) return undefined;
const [, , chatType, peerUid, msgId, elementId] = data;
return {

View File

@@ -53,23 +53,23 @@ export class QQBasicInfoWrapper {
//此方法不要直接使用
getQUAInternal() {
switch (systemPlatform) {
case 'linux':
return `V1_LNX_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
case 'darwin':
return `V1_MAC_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
default:
return `V1_WIN_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
case 'linux':
return `V1_LNX_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
case 'darwin':
return `V1_MAC_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
default:
return `V1_WIN_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
}
}
getAppidInternal() {
switch (systemPlatform) {
case 'linux':
return '537243600';
case 'darwin':
return '537243441';
default:
return '537243538';
case 'linux':
return '537243600';
case 'darwin':
return '537243441';
default:
return '537243538';
}
}

View File

@@ -1 +1 @@
export const napCatVersion = '2.3.7';
export const napCatVersion = '2.4.2';

View File

@@ -32,6 +32,10 @@ export class NTQQGroupApi {
for (const group of this.groups) {
this.groupCache.set(group.groupCode, group);
}
// let text = await this.context.session.getMsgService().sendSsoCmdReqByContend(
// 'LightAppSvc.mini_app_share.AdaptShareInfo',
// JSON.stringify({ data: 'test' }));
// console.log(text);
this.context.logger.logDebug(`加载${this.groups.length}个群组缓存完成`);
}
@@ -263,7 +267,7 @@ export class NTQQGroupApi {
return member;
}
async getGroupMemberEx(GroupCode: string, uid: string, forced = false, retry = 2) {
let data = await solveAsyncProblem((eventWrapper: NTEventWrapper, GroupCode: string, uid: string, forced = false) => {
const data = await solveAsyncProblem((eventWrapper: NTEventWrapper, GroupCode: string, uid: string, forced = false) => {
return eventWrapper.callNormalEventV2(
'NodeIKernelGroupService/getMemberInfo',
'NodeIKernelGroupListener/onMemberInfoChange',
@@ -278,7 +282,7 @@ export class NTQQGroupApi {
return data[3].get(uid);
}
if (retry > 0) {
let trydata = await this.getGroupMemberEx(GroupCode, uid, true, retry - 1) as GroupMember | undefined;
const trydata = await this.getGroupMemberEx(GroupCode, uid, true, retry - 1) as GroupMember | undefined;
if (trydata) return trydata;
}
return undefined;

View File

@@ -157,7 +157,7 @@ export class NTQQWebApi {
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
try {
let settings = JSON.stringify({
const settings = JSON.stringify({
is_show_edit_card: is_show_edit_card,
tip_window_type: tip_window_type,
confirm_required: confirm_required
@@ -167,7 +167,7 @@ export class NTQQWebApi {
imgWidth: imgWidth.toString(),
imgHeight: imgHeight.toString(),
};
let ret: SetNoticeRetSuccess = await RequestUtil.HttpGetJson<SetNoticeRetSuccess>(
const ret: SetNoticeRetSuccess = await RequestUtil.HttpGetJson<SetNoticeRetSuccess>(
`https://web.qun.qq.com/cgi-bin/announce/add_qun_notice?${new URLSearchParams({
bkn: this.getBknFromCookie(cookieObject),
qid: GroupCode,

View File

@@ -153,7 +153,10 @@ interface CommonExt {
labels: any[];
qqLevel: QQLevel;
}
export enum BuddyListReqType {
KNOMAL,
KLETTER
}
interface Pic {
picId: string;
picTime: number;
@@ -375,8 +378,4 @@ export enum ProfileBizType {
KVAS,
KQZONE,
KOTHER
}export enum BuddyListReqType {
KNOMAL,
KLETTER
}
}

View File

@@ -145,8 +145,10 @@ export class NapCatCore {
if (Info.status == 20) {
this.selfInfo.online = false;
this.context.logger.log("账号状态变更为离线");
return;
} else {
this.selfInfo.online = true;
}
this.selfInfo.online = true;
};
this.context.session.getProfileService().addKernelProfileListener(
proxiedListenerOf(profileListener, this.context.logger),

View File

@@ -14,7 +14,7 @@ export interface NodeIKernelBuddyService {
}>
}>;
getBuddyListFromCache(callFrom: string): Promise<Array<
getBuddyListFromCache(reqType: BuddyListReqType): Promise<Array<
{
categoryId: number,//9999应该跳过 那是兜底数据吧
categorySortId: number,//排序方式
@@ -23,7 +23,7 @@ export interface NodeIKernelBuddyService {
onlineCount: number,//在线数目
buddyUids: Array<string>//Uids
}>>;
addKernelBuddyListener(listener: NodeIKernelBuddyListener): number;
getAllBuddyCount(): number;

View File

@@ -59,6 +59,7 @@ export interface QuickLoginResult {
}
export interface NodeIKernelLoginService {
connect(): boolean;
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(): NodeIKernelLoginService;

View File

@@ -3,6 +3,11 @@ import { BizKey, ModifyProfileParams, NodeIKernelProfileListener, ProfileBizType
import { GeneralCallResult } from '@/core/services/common';
export interface NodeIKernelProfileService {
getOtherFlag(callfrom: string, uids: string[]): Promise<Map<string, any>>;
getVasInfo(callfrom: string, uids: string[]): Promise<Map<string, any>>;
getRelationFlag(callfrom: string, uids: string[]): Promise<Map<string, any>>;
getUidByUin(callfrom: string, uin: Array<string>): Promise<Map<string, string>>;

View File

@@ -43,12 +43,12 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
const fileName = mixElementInner.fileName ?? '';
let url = '';
if (mixElement?.picElement && rawMessage) {
let tempData =
const tempData =
await this.obContext.apis.MsgApi.rawToOb11Converters.picElement?.(mixElement?.picElement, rawMessage, mixElement) as OB11MessageImage | undefined;
url = tempData?.data.url ?? '';
}
if (mixElement?.videoElement && rawMessage) {
let tempData =
const tempData =
await this.obContext.apis.MsgApi.rawToOb11Converters.videoElement?.(mixElement?.videoElement, rawMessage, mixElement) as OB11MessageVideo | undefined;
url = tempData?.data.url ?? '';
}

View File

@@ -28,6 +28,7 @@ export default class GoCQHTTPGetStrangerInfo extends BaseAction<Payload, OB11Use
...extendData.detail.commonExt,
...extendData.detail.simpleInfo.baseInfo,
...extendData.detail.simpleInfo.relationFlags,
...extendData.detail.simpleInfo.status,
user_id: parseInt(extendData.detail.uin) || 0,
nickname: extendData.detail.simpleInfo.coreInfo.nick,
sex: OB11UserSex.unknown,

View File

@@ -51,7 +51,7 @@ export class GetGroupEssence extends BaseAction<Payload, any> {
operator_nick: msg.add_digest_nick,
message_id: message_id,
operator_time: msg.add_digest_time,
content: (await this.obContext.apis.MsgApi.parseMessage(rawMessage, 'array'))?.message
content: (await this.obContext.apis.MsgApi.parseMessage(rawMessage))?.message
};
}
const msgTempData = JSON.stringify({

View File

@@ -35,7 +35,7 @@ class GetMsg extends BaseAction<Payload, OB11Message> {
const msg = await this.core.apis.MsgApi.getMsgsByMsgId(
peer,
[msgIdWithPeer?.MsgId || payload.message_id.toString()]);
const retMsg = await this.obContext.apis.MsgApi.parseMessage(msg.msgList[0], 'array');
const retMsg = await this.obContext.apis.MsgApi.parseMessage(msg.msgList[0]);
if (!retMsg) throw Error('消息为空');
try {
retMsg.message_id = MessageUnique.createUniqueMsgId(peer, msg.msgList[0].msgId)!;

View File

@@ -21,7 +21,7 @@ export default class GetRecentContact extends BaseAction<Payload, any> {
const FastMsg = await this.core.apis.MsgApi.getMsgsByMsgId({ chatType: t.chatType, peerUid: t.peerUid }, [t.msgId]);
if (FastMsg.msgList.length > 0) {
//扩展ret.info.changedList
const lastestMsg = await this.obContext.apis.MsgApi.parseMessage(FastMsg.msgList[0], 'array');
const lastestMsg = await this.obContext.apis.MsgApi.parseMessage(FastMsg.msgList[0]);
return {
lastestMsg: lastestMsg,
peerUin: t.peerUin,

View File

@@ -19,6 +19,7 @@ import { OB11GroupPokeEvent } from '@/onebot/event/notice/OB11PokeEvent';
import { OB11GroupEssenceEvent } from '@/onebot/event/notice/OB11GroupEssenceEvent';
import { OB11GroupTitleEvent } from '@/onebot/event/notice/OB11GroupTitleEvent';
import { FileNapCatOneBotUUID } from '@/common/helper';
import { pathToFileURL } from 'node:url';
export class OneBotGroupApi {
obContext: NapCatOneBot11Adapter;
@@ -77,7 +78,8 @@ export class OneBotGroupApi {
id: FileNapCatOneBotUUID.encode({
chatType: ChatType.KCHATTYPEGROUP,
peerUid: msg.peerUid,
}, msg.msgId, element.elementId),
}, msg.msgId, element.elementId, "." + element.fileElement.fileName),
url: pathToFileURL(element.fileElement.filePath).href,
name: element.fileElement.fileName,
size: parseInt(element.fileElement.fileSize),
busid: element.fileElement.fileBizId || 0,

View File

@@ -1,5 +1,6 @@
import { FileNapCatOneBotUUID } from '@/common/helper';
import { MessageUnique } from '@/common/message-unique';
import { pathToFileURL } from 'node:url';
import {
AtType,
ChatType,
@@ -106,7 +107,7 @@ export class OneBotMsgApi {
peerUid: msg.peerUid,
guildId: '',
};
const encodedFileId = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId);
const encodedFileId = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, "."+element.fileName);
return {
type: OB11MessageDataType.image,
data: {
@@ -114,6 +115,7 @@ export class OneBotMsgApi {
sub_type: element.picSubType,
file_id: encodedFileId,
url: await this.core.apis.FileApi.getImageUrl(element),
path: element.filePath,
file_size: element.fileSize,
file_unique: element.fileName
},
@@ -135,8 +137,8 @@ export class OneBotMsgApi {
data: {
file: element.fileName,
path: element.filePath,
url: element.filePath,
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId),
url: pathToFileURL(element.filePath).href,
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, "."+element.fileName),
file_size: element.fileSize,
file_unique: element.fileName,
},
@@ -175,13 +177,16 @@ export class OneBotMsgApi {
peerUid: msg.peerUid,
guildId: '',
};
const { emojiId } = _;
const dir = emojiId.substring(0, 2);
const url = `https://gxh.vip.qq.com/club/item/parcel/item/${dir}/${emojiId}/raw300.gif`;
return {
type: OB11MessageDataType.image,
data: {
file: 'marketface',
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId),
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, "."+_.key+".jpg"),
path: elementWrapper.elementId,
url: elementWrapper.elementId,
url: url,
file_unique: _.key
},
};
@@ -209,7 +214,7 @@ export class OneBotMsgApi {
if (records.peerUin === '284840486') {
return createReplyData(records.msgId);
}
let replyMsg = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV2(peer, element.replayMsgSeq, element.replyMsgTime, [element.senderUidStr]))
const replyMsg = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV2(peer, element.replayMsgSeq, element.replyMsgTime, [element.senderUidStr]))
.msgList.find(msg => msg.msgRandom === records.msgRandom);
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
@@ -257,14 +262,14 @@ export class OneBotMsgApi {
if (!videoDownUrl) {
videoDownUrl = element.filePath;
}
const fileCode = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, "."+element.fileName);
return {
type: OB11MessageDataType.video,
data: {
file: element.fileName,
file: fileCode,
path: videoDownUrl,
url: videoDownUrl,
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId),
url: videoDownUrl ?? pathToFileURL(element.filePath).href,
file_id: fileCode,
file_size: element.fileSize,
file_unique: element.fileName,
},
@@ -277,13 +282,16 @@ export class OneBotMsgApi {
peerUid: msg.peerUid,
guildId: '',
};
const fileCode = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, "."+element.fileName);
return {
type: OB11MessageDataType.voice,
data: {
file: element.fileName,
file: fileCode,
path: element.filePath,
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId),
url: pathToFileURL(element.filePath).href,
file_id: fileCode,
file_size: element.fileSize,
file_unique: element.fileName
},
};
},
@@ -792,18 +800,20 @@ export class OneBotMsgApi {
) {
const isBlankUrl = !inputdata.url || inputdata.url === '';
const isBlankFile = !inputdata.file || inputdata.file === '';
const isBlankPath = !inputdata.path || inputdata.path === '';
if (isBlankUrl && isBlankFile) {
this.core.context.logger.logError('文件消息缺少参数', inputdata);
throw Error('文件消息缺少参数');
}
const fileOrUrl = (isBlankUrl ? inputdata.file : inputdata.url) ?? '';
//path->url->file
const realUri = (!isBlankUrl ? inputdata.url :(!isBlankPath ? inputdata.path:inputdata.file ))??'';
const {
path,
isLocal,
fileName,
errMsg,
success,
} = (await uri2local(this.core.NapCatTempPath, fileOrUrl));
} = (await uri2local(this.core.NapCatTempPath, realUri));
if (!success) {
this.core.context.logger.logError('文件下载失败', errMsg);

View File

@@ -109,7 +109,7 @@ export class OB11Entities {
static file(peerId: string, file: Exclude<GroupFileInfoUpdateParamType['item'][0]['fileInfo'], undefined>): OB11GroupFile {
return {
group_id: parseInt(peerId),
file_id: FileNapCatOneBotUUID.encodeModelId({ chatType: 2, peerUid: peerId }, file.fileModelId, file.fileId),
file_id: FileNapCatOneBotUUID.encodeModelId({ chatType: 2, peerUid: peerId }, file.fileModelId, file.fileId, file.fileName),
file_name: file.fileName,
busid: file.busId,
size: parseInt(file.fileSize),

View File

@@ -6,6 +6,7 @@ export interface GroupUploadFile {
name: string,
size: number,
busid: number,
url:string;
}
export class OB11GroupUploadNoticeEvent extends OB11GroupNoticeEvent {

View File

@@ -84,6 +84,8 @@ export interface OB11MessageText {
export interface OB11MessageFileBase {
data: {
file_unique?:string,
path?: string;
thumb?: string;
name?: string;
file: string,

View File

@@ -150,7 +150,12 @@ export async function NCoreInitShell() {
};
loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger) as any);
const isConnect = loginService.connect();
if (!isConnect) {
logger.logError('核心登录服务连接失败!');
return;
}
logger.log('核心登录服务连接成功!');
// 实现WebUi快速登录
loginService.getLoginList().then((res) => {
// 遍历 res.LocalLoginInfoList[x].isQuickLogin是否可以 res.LocalLoginInfoList[x].uin 转为string 加入string[] 最后遍历完成调用WebUiDataRuntime.setQQQuickLoginList
@@ -199,7 +204,7 @@ export async function NCoreInitShell() {
logger.log(`可用于快速登录的 QQ\n${historyLoginList
.map((u, index) => `${index + 1}. ${u.uin} ${u.nickName}`)
.join('\n')
}`);
}`);
}
loginService.getQRCodePicture();
}

View File

@@ -30,7 +30,7 @@ async function onSettingWindowCreated(view: Element) {
SettingItem(
'<span id="napcat-update-title">Napcat</span>',
undefined,
SettingButton('V2.3.7', 'napcat-update-button', 'secondary'),
SettingButton('V2.4.2', 'napcat-update-button', 'secondary'),
),
]),
SettingList([

View File

@@ -164,7 +164,7 @@ async function onSettingWindowCreated(view) {
SettingItem(
'<span id="napcat-update-title">Napcat</span>',
void 0,
SettingButton("V2.3.7", "napcat-update-button", "secondary")
SettingButton("V2.4.2", "napcat-update-button", "secondary")
)
]),
SettingList([