Compare commits

..

2 Commits

Author SHA1 Message Date
手瓜一十雪
63902d440f Merge branch 'main' into ai-qun 2025-04-27 15:34:55 +08:00
手瓜一十雪
cea900ca2a publish 2025-02-17 22:49:20 +08:00
96 changed files with 682 additions and 4513 deletions

View File

@@ -1,9 +1,8 @@
<img src="https://napneko.github.io/assets/newnewlogo.png" width = "305" height = "411" alt="NapCat" align=right />
<div align="center"> <div align="center">
# NapCat # NapCat
![NapCatQQ](https://socialify.git.ci/NapNeko/NapCatQQ/image?font=Jost&logo=https%3A%2F%2Fnapneko.github.io%2Fassets%2Fnewlogo.png&name=1&owner=1&pattern=Diagonal+Stripes&stargazers=1&theme=Auto)
_Modern protocol-side framework implemented based on NTQQ._ _Modern protocol-side framework implemented based on NTQQ._
@@ -41,20 +40,13 @@ _Modern protocol-side framework implemented based on NTQQ._
| Docs | [![Cloudflare.Pages](https://img.shields.io/badge/docs%20on-Cloudflare.Pages-blue)](https://napneko.pages.dev/) | [![Server.Other](https://img.shields.io/badge/docs%20on-Server.Other-green)](https://napcat.cyou/) | [![NapCat.Wiki](https://img.shields.io/badge/docs%20on-NapCat.Wiki-red)](https://www.napcat.wiki) | | Docs | [![Cloudflare.Pages](https://img.shields.io/badge/docs%20on-Cloudflare.Pages-blue)](https://napneko.pages.dev/) | [![Server.Other](https://img.shields.io/badge/docs%20on-Server.Other-green)](https://napcat.cyou/) | [![NapCat.Wiki](https://img.shields.io/badge/docs%20on-NapCat.Wiki-red)](https://www.napcat.wiki) |
|:-:|:-:|:-:|:-:| |:-:|:-:|:-:|:-:|
| QQ Group | [![QQ Group#4](https://img.shields.io/badge/QQ%20Group%234-Join-blue)](https://qm.qq.com/q/CMmPbGw0jA) | [![QQ Group#3](https://img.shields.io/badge/QQ%20Group%233-Join-blue)](https://qm.qq.com/q/8zJMLjqy2Y) | [![QQ Group#2](https://img.shields.io/badge/QQ%20Group%232-Join-blue)](https://qm.qq.com/q/HaRcfrHpUk) | [![QQ Group#1](https://img.shields.io/badge/QQ%20Group%231-Join-blue)](https://qm.qq.com/q/I6LU87a0Yq) | | Contact | [![QQ Group#1](https://img.shields.io/badge/QQ%20Group%231-Join-blue)](https://qm.qq.com/q/I6LU87a0Yq) | [![QQ Group#2](https://img.shields.io/badge/QQ%20Group%232-Join-blue)](https://qm.qq.com/q/HaRcfrHpUk) | [![Telegram](https://img.shields.io/badge/Telegram-MelodicMoonlight-blue)](https://t.me/MelodicMoonlight) |
|:-:|:-:|:-:|:-:|:-:| |:-:|:-:|:-:|:-:|
| Telegram | [![Telegram](https://img.shields.io/badge/Telegram-MelodicMoonlight-blue)](https://t.me/MelodicMoonlight) |
|:-:|:-:|
## Thanks ## Thanks
+ [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持 参考部分代码 已获授权 + [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持 参考部分代码 已获授权
+ [AstrBot](https://github.com/AstrBotDevs/AstrBot) 是完美适配本项目的LLM Bot框架 在此推荐一下
+ [MaiBot](https://github.com/MaiM-with-u/MaiBot) 一只赛博群友 麦麦 Bot框架 在此推荐一下
+ 不过最最重要的 还是需要感谢屏幕前的你哦~ + 不过最最重要的 还是需要感谢屏幕前的你哦~
--- ---
@@ -66,3 +58,7 @@ _Modern protocol-side framework implemented based on NTQQ._
2. 项目其余逻辑代码采用[本仓库开源许可](./LICENSE). 2. 项目其余逻辑代码采用[本仓库开源许可](./LICENSE).
**本仓库仅用于提高易用性,实现消息推送类功能,此外,禁止任何项目未经仓库主作者授权基于 NapCat 代码开发。使用请遵守当地法律法规,由此造成的问题由使用者和提供违规使用教程者负责。** **本仓库仅用于提高易用性,实现消息推送类功能,此外,禁止任何项目未经仓库主作者授权基于 NapCat 代码开发。使用请遵守当地法律法规,由此造成的问题由使用者和提供违规使用教程者负责。**
## Warnings
[某框架抄袭部分分析](https://napneko.github.io/other/about-copy)

Binary file not shown.

BIN
external/logo.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 250 KiB

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

View File

@@ -1,9 +1,9 @@
{ {
"name": "qq-chat", "name": "qq-chat",
"version": "9.9.19-34740", "version": "9.9.18-32869",
"verHash": "f31348f2", "verHash": "e735296c",
"linuxVersion": "3.2.17-34740", "linuxVersion": "3.2.16-32869",
"linuxVerHash": "5aa2d8d6", "linuxVerHash": "4c192ba9",
"private": true, "private": true,
"description": "QQ", "description": "QQ",
"productName": "QQ", "productName": "QQ",
@@ -16,8 +16,25 @@
"bin": { "bin": {
"qd": "externals/devtools/cli/index.js" "qd": "externals/devtools/cli/index.js"
}, },
"appid": {
"win32": "537258389",
"darwin": "537258412",
"linux": "537258424"
},
"main": "./loadNapCat.js", "main": "./loadNapCat.js",
"buildVersion": "34740", "peerDependenciesMeta": {
"*": {
"optional": true
}
},
"pnpm": {
"patchedDependencies": {
"@vue/runtime-dom@3.5.12": "patches/@vue__runtime-dom@3.5.12.patch",
"@swc/helpers@0.5.3": "patches/@swc__helpers@0.5.3.patch",
"vuex@4.1.0": "patches/vuex@4.1.0.patch"
}
},
"buildVersion": "32869",
"isPureShell": true, "isPureShell": true,
"isByteCodeShell": true, "isByteCodeShell": true,
"platform": "win32", "platform": "win32",

View File

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

View File

@@ -92,9 +92,7 @@ const MusicInsert = () => {
className="w-96" className="w-96"
fullWidth fullWidth
selectedKey={mode} selectedKey={mode}
onSelectionChange={(key) => { onSelectionChange={setMode}
if (key !== null) setMode(key)
}}
> >
<Tab title="主流平台" key="default" className="flex flex-col gap-2"> <Tab title="主流平台" key="default" className="flex flex-col gap-2">
<Select <Select

View File

@@ -26,7 +26,7 @@ const itemVariants = {
opacity: 1, opacity: 1,
scale: 1, scale: 1,
y: 0, y: 0,
transition: { type: 'spring' as const, stiffness: 300, damping: 20 } transition: { type: 'spring', stiffness: 300, damping: 20 }
} }
} }

View File

@@ -24,7 +24,9 @@ const oneBotHttpApiGroup = {
}, },
'/get_group_system_msg': { '/get_group_system_msg': {
description: '获取群系统消息', description: '获取群系统消息',
request: z.object({}), request: z.object({
group_id: z.union([z.string(), z.number()]).describe('群号')
}),
response: baseResponseSchema.extend({ response: baseResponseSchema.extend({
data: z.object({ data: z.object({
InvitedRequest: z InvitedRequest: z
@@ -35,7 +37,6 @@ const oneBotHttpApiGroup = {
invitor_uin: z.string().describe('邀请人 QQ 号'), invitor_uin: z.string().describe('邀请人 QQ 号'),
invitor_nick: z.string().describe('邀请人昵称'), invitor_nick: z.string().describe('邀请人昵称'),
group_id: z.string().describe('群号'), group_id: z.string().describe('群号'),
message: z.string().describe('入群回答'),
group_name: z.string().describe('群名称'), group_name: z.string().describe('群名称'),
checked: z.boolean().describe('是否已处理'), checked: z.boolean().describe('是否已处理'),
actor: z.string().describe('处理人 QQ 号') actor: z.string().describe('处理人 QQ 号')
@@ -49,7 +50,6 @@ const oneBotHttpApiGroup = {
requester_uin: z.string().describe('请求人 QQ 号'), requester_uin: z.string().describe('请求人 QQ 号'),
requester_nick: z.string().describe('请求人昵称'), requester_nick: z.string().describe('请求人昵称'),
group_id: z.string().describe('群号'), group_id: z.string().describe('群号'),
message: z.string().describe('入群回答'),
group_name: z.string().describe('群名称'), group_name: z.string().describe('群名称'),
checked: z.boolean().describe('是否已处理'), checked: z.boolean().describe('是否已处理'),
actor: z.string().describe('处理人 QQ 号') actor: z.string().describe('处理人 QQ 号')
@@ -604,7 +604,7 @@ const oneBotHttpApiGroup = {
response: baseResponseSchema.extend({ response: baseResponseSchema.extend({
data: z data: z
.object({ .object({
group_id: z.number().describe('群号'), group_id: z.string().describe('群号'),
current_talkative: z current_talkative: z
.object({ .object({
user_id: z.number().describe('QQ 号'), user_id: z.number().describe('QQ 号'),

View File

@@ -56,9 +56,9 @@ export default function TerminalPage() {
setTabs((prev) => [...prev, newTab]) setTabs((prev) => [...prev, newTab])
setSelectedTab(id) setSelectedTab(id)
} catch (error: unknown) { } catch (error) {
console.error('Failed to create terminal:', error) console.error('Failed to create terminal:', error)
toast.error((error as Error).message) toast.error('创建终端失败')
} }
} }

View File

@@ -2,7 +2,7 @@
"name": "napcat", "name": "napcat",
"private": true, "private": true,
"type": "module", "type": "module",
"version": "4.7.78", "version": "4.7.43",
"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",
@@ -41,28 +41,33 @@
"ajv": "^8.13.0", "ajv": "^8.13.0",
"async-mutex": "^0.5.0", "async-mutex": "^0.5.0",
"commander": "^13.0.0", "commander": "^13.0.0",
"compressing": "^1.10.1", "esbuild": "0.25.0",
"cors": "^2.8.5",
"esbuild": "0.25.5",
"eslint": "^9.14.0", "eslint": "^9.14.0",
"eslint-import-resolver-typescript": "^4.0.0", "eslint-import-resolver-typescript": "^4.0.0",
"eslint-plugin-import": "^2.29.1", "eslint-plugin-import": "^2.29.1",
"express-rate-limit": "^7.5.0", "express-rate-limit": "^7.5.0",
"fast-xml-parser": "^4.3.6", "fast-xml-parser": "^4.3.6",
"file-type": "^21.0.0", "file-type": "^20.0.0",
"globals": "^16.0.0", "globals": "^16.0.0",
"image-size": "^1.1.1",
"json5": "^2.2.3", "json5": "^2.2.3",
"multer": "^2.0.1", "multer": "^1.4.5-lts.1",
"napcat.protobuf": "^1.1.4",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"typescript-eslint": "^8.13.0", "typescript-eslint": "^8.13.0",
"vite": "^6.0.1", "vite": "^6.0.1",
"vite-plugin-cp": "^6.0.0", "vite-plugin-cp": "^6.0.0",
"vite-tsconfig-paths": "^5.1.0", "vite-tsconfig-paths": "^5.1.0",
"winston": "^3.17.0" "napcat.protobuf": "^1.1.4",
"winston": "^3.17.0",
"compressing": "^1.10.1"
}, },
"dependencies": { "dependencies": {
"@ffmpeg.wasm/core-mt": "^0.13.2",
"cors": "^2.8.5",
"compressing": "^1.10.1",
"express": "^5.0.0", "express": "^5.0.0",
"openai": "^4.85.1",
"piscina": "^4.7.0",
"silk-wasm": "^3.6.1", "silk-wasm": "^3.6.1",
"ws": "^8.18.0" "ws": "^8.18.0"
} }

View File

@@ -8,12 +8,11 @@ import { pipeline } from 'stream/promises';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import { LogWrapper } from './log'; import { LogWrapper } from './log';
const downloadOri = "https://github.com/NapNeko/ffmpeg-build/releases/download/v1.0.0/ffmpeg-7.1.1-win64.zip" const downloadOri = "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2025-04-16-12-54/ffmpeg-n7.1.1-6-g48c0f071d4-win64-lgpl-7.1.zip"
const urls = [ const urls = [
"https://github.moeyy.xyz/" + downloadOri, "https://github.moeyy.xyz/" + downloadOri,
"https://ghp.ci/" + downloadOri, "https://ghp.ci/" + downloadOri,
"https://gh.api.99988866.xyz/" + downloadOri, "https://gh.api.99988866.xyz/" + downloadOri,
"https://gh.api.99988866.xyz/" + downloadOri,
downloadOri downloadOri
]; ];
@@ -337,16 +336,9 @@ export async function downloadFFmpegIfNotExists(log: LogWrapper) {
const ffprobe_exist = fs.existsSync(path.join(currentPath, 'ffmpeg', 'ffprobe.exe')); const ffprobe_exist = fs.existsSync(path.join(currentPath, 'ffmpeg', 'ffprobe.exe'));
if (!ffmpeg_exist || !ffprobe_exist) { if (!ffmpeg_exist || !ffprobe_exist) {
let url = await downloadFFmpeg(path.join(currentPath, 'ffmpeg'), path.join(currentPath, 'cache'), (percentage: number, message: string) => { await downloadFFmpeg(path.join(currentPath, 'ffmpeg'), path.join(currentPath, 'cache'), (percentage: number, message: string) => {
log.log(`[FFmpeg] [Download] ${percentage}% - ${message}`); log.log(`[FFmpeg] [Download] ${percentage}% - ${message}`);
}); });
if (!url) {
log.log('[FFmpeg] [Error] 下载FFmpeg失败');
return {
path: null,
reset: false
};
}
return { return {
path: path.join(currentPath, 'ffmpeg'), path: path.join(currentPath, 'ffmpeg'),
reset: true reset: true

View File

@@ -10,9 +10,9 @@ interface InternalMapKey {
checker: ((...args: any[]) => boolean) | undefined; checker: ((...args: any[]) => boolean) | undefined;
} }
export type EnsureFunc<T> = T extends (...args: any) => any ? T : never; type EnsureFunc<T> = T extends (...args: any) => any ? T : never;
export type FuncKeys<T> = Extract< type FuncKeys<T> = Extract<
{ {
[K in keyof T]: EnsureFunc<T[K]> extends never ? never : K; [K in keyof T]: EnsureFunc<T[K]> extends never ? never : K;
}[keyof T], }[keyof T],

View File

@@ -4,10 +4,10 @@ import { execFile } from 'child_process';
import { promisify } from 'util'; import { promisify } from 'util';
import type { VideoInfo } from './video'; import type { VideoInfo } from './video';
import { fileTypeFromFile } from 'file-type'; import { fileTypeFromFile } from 'file-type';
import imageSize from 'image-size';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { platform } from 'node:os'; import { platform } from 'node:os';
import { LogWrapper } from './log'; import { LogWrapper } from './log';
import { imageSizeFallBack } from '@/image-size';
const currentPath = dirname(fileURLToPath(import.meta.url)); const currentPath = dirname(fileURLToPath(import.meta.url));
const execFileAsync = promisify(execFile); const execFileAsync = promisify(execFile);
const getFFmpegPath = (tool: string): string => { const getFFmpegPath = (tool: string): string => {
@@ -157,7 +157,7 @@ export class FFmpegService {
try { try {
await this.extractThumbnail(videoPath, thumbnailPath); await this.extractThumbnail(videoPath, thumbnailPath);
// 获取图片尺寸 // 获取图片尺寸
const dimensions = await imageSizeFallBack(thumbnailPath); const dimensions = imageSize(thumbnailPath);
return { return {
format: fileType?.ext ?? 'mp4', format: fileType?.ext ?? 'mp4',

View File

@@ -145,8 +145,8 @@ export enum FileUriType {
export async function checkUriType(Uri: string) { export async function checkUriType(Uri: string) {
const LocalFileRet = await solveProblem((uri: string) => { const LocalFileRet = await solveProblem((uri: string) => {
if (fs.existsSync(path.normalize(uri))) { if (fs.existsSync(uri)) {
return { Uri: path.normalize(uri), Type: FileUriType.Local }; return { Uri: uri, Type: FileUriType.Local };
} }
return undefined; return undefined;
}, Uri); }, Uri);

View File

@@ -13,15 +13,11 @@ export class NapCatPathWrapper {
constructor(mainPath: string = dirname(fileURLToPath(import.meta.url))) { constructor(mainPath: string = dirname(fileURLToPath(import.meta.url))) {
this.binaryPath = mainPath; this.binaryPath = mainPath;
let writePath: string; let writePath: string;
if (os.platform() === 'darwin') {
if (process.env['NAPCAT_WORKDIR']) {
writePath = process.env['NAPCAT_WORKDIR'];
} else if (os.platform() === 'darwin') {
writePath = path.join(os.homedir(), 'Library', 'Application Support', 'QQ', 'NapCat'); writePath = path.join(os.homedir(), 'Library', 'Application Support', 'QQ', 'NapCat');
} else { } else {
writePath = this.binaryPath; writePath = this.binaryPath;
} }
this.logsPath = path.join(writePath, 'logs'); this.logsPath = path.join(writePath, 'logs');
this.configPath = path.join(writePath, 'config'); this.configPath = path.join(writePath, 'config');
this.cachePath = path.join(writePath, 'cache'); this.cachePath = path.join(writePath, 'cache');

View File

@@ -20,23 +20,3 @@ export function proxyHandlerOf(logger: LogWrapper) {
export function proxiedListenerOf<T extends object>(listener: T, logger: LogWrapper) { export function proxiedListenerOf<T extends object>(listener: T, logger: LogWrapper) {
return new Proxy<T>(listener, proxyHandlerOf(logger)); return new Proxy<T>(listener, proxyHandlerOf(logger));
} }
export function proxyHandlerOfWithoutLogger() {
return {
get(target: any, prop: any, receiver: any) {
if (typeof target[prop] === 'undefined') {
// 如果方法不存在返回一个函数这个函数调用existentMethod
// eslint-disable-next-line @typescript-eslint/no-unused-vars
return (..._args: unknown[]) => {
console.log(`${target.constructor.name} has no method ${prop}`);
};
}
// 如果方法存在,正常返回
return Reflect.get(target, prop, receiver);
},
};
}
export function proxiedListenerOfWithoutLogger<T extends object>(listener: T) {
return new Proxy<T>(listener, proxyHandlerOfWithoutLogger());
}

View File

@@ -1 +1 @@
export const napCatVersion = '4.7.78'; export const napCatVersion = '4.7.43';

View File

@@ -17,6 +17,8 @@ import fs from 'fs';
import fsPromises from 'fs/promises'; import fsPromises from 'fs/promises';
import { InstanceContext, NapCatCore, SearchResultItem } from '@/core'; import { InstanceContext, NapCatCore, SearchResultItem } from '@/core';
import { fileTypeFromFile } from 'file-type'; import { fileTypeFromFile } from 'file-type';
import imageSize from 'image-size';
import { ISizeCalculationResult } from 'image-size/dist/types/interface';
import { RkeyManager } from '@/core/helper/rkey'; import { RkeyManager } from '@/core/helper/rkey';
import { calculateFileMD5 } from '@/common/file'; import { calculateFileMD5 } from '@/common/file';
import pathLib from 'node:path'; import pathLib from 'node:path';
@@ -26,9 +28,6 @@ import { SendMessageContext } from '@/onebot/api';
import { getFileTypeForSendType } from '../helper/msg'; import { getFileTypeForSendType } from '../helper/msg';
import { FFmpegService } from '@/common/ffmpeg'; import { FFmpegService } from '@/common/ffmpeg';
import { rkeyDataType } from '../types/file'; import { rkeyDataType } from '../types/file';
import { NapProtoMsg } from '@napneko/nap-proto-core';
import { FileId } from '../packet/transformer/proto/misc/fileid';
import { imageSizeFallBack } from '@/image-size';
export class NTQQFileApi { export class NTQQFileApi {
context: InstanceContext; context: InstanceContext;
@@ -64,76 +63,6 @@ export class NTQQFileApi {
} }
} }
async getFileUrl(chatType: ChatType, peer: string, fileUUID?: string, file10MMd5?: string | undefined) {
if (this.core.apis.PacketApi.available) {
try {
if (chatType === ChatType.KCHATTYPEGROUP && fileUUID) {
return this.core.apis.PacketApi.pkt.operation.GetGroupFileUrl(+peer, fileUUID);
} else if (file10MMd5 && fileUUID) {
return this.core.apis.PacketApi.pkt.operation.GetPrivateFileUrl(peer, fileUUID, file10MMd5);
}
} catch (error) {
this.context.logger.logError('获取文件URL失败', (error as Error).message);
}
}
throw new Error('fileUUID or file10MMd5 is undefined');
}
async getPttUrl(peer: string, fileUUID?: string) {
if (this.core.apis.PacketApi.available && fileUUID) {
let appid = new NapProtoMsg(FileId).decode(Buffer.from(fileUUID.replaceAll('-', '+').replaceAll('_', '/'), 'base64')).appid;
try {
if (appid && appid === 1403) {
return this.core.apis.PacketApi.pkt.operation.GetGroupPttUrl(+peer, {
fileUuid: fileUUID,
storeId: 1,
uploadTime: 0,
ttl: 0,
subType: 0,
});
} else if (fileUUID) {
return this.core.apis.PacketApi.pkt.operation.GetPttUrl(peer, {
fileUuid: fileUUID,
storeId: 1,
uploadTime: 0,
ttl: 0,
subType: 0,
});
}
} catch (error) {
this.context.logger.logError('获取文件URL失败', (error as Error).message);
}
}
throw new Error('packet cant get ptt url');
}
async getVideoUrlPacket(peer: string, fileUUID?: string) {
if (this.core.apis.PacketApi.available && fileUUID) {
let appid = new NapProtoMsg(FileId).decode(Buffer.from(fileUUID.replaceAll('-', '+').replaceAll('_', '/'), 'base64')).appid;
try {
if (appid && appid === 1415) {
return this.core.apis.PacketApi.pkt.operation.GetGroupVideoUrl(+peer, {
fileUuid: fileUUID,
storeId: 1,
uploadTime: 0,
ttl: 0,
subType: 0,
});
} else if (fileUUID) {
return this.core.apis.PacketApi.pkt.operation.GetVideoUrl(peer, {
fileUuid: fileUUID,
storeId: 1,
uploadTime: 0,
ttl: 0,
subType: 0,
});
}
} catch (error) {
this.context.logger.logError('获取文件URL失败', (error as Error).message);
}
}
throw new Error('packet cant get video url');
}
async copyFile(filePath: string, destPath: string) { async copyFile(filePath: string, destPath: string) {
await this.core.util.copyFile(filePath, destPath); await this.core.util.copyFile(filePath, destPath);
@@ -208,7 +137,7 @@ export class NTQQFileApi {
if (fileSize === 0) { if (fileSize === 0) {
throw new Error('文件异常大小为0'); throw new Error('文件异常大小为0');
} }
const imageSize = await imageSizeFallBack(picPath); const imageSize = await this.core.apis.FileApi.getImageSize(picPath);
context.deleteAfterSentFiles.push(path); context.deleteAfterSentFiles.push(path);
return { return {
elementType: ElementType.PIC, elementType: ElementType.PIC,
@@ -396,7 +325,6 @@ export class NTQQFileApi {
} }
}); });
}); });
return res.flat();
} }
async downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, timeout = 1000 * 60 * 2, force: boolean = false) { async downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, timeout = 1000 * 60 * 2, force: boolean = false) {
@@ -436,6 +364,19 @@ export class NTQQFileApi {
return completeRetData.filePath; return completeRetData.filePath;
} }
async getImageSize(filePath: string): Promise<ISizeCalculationResult> {
return new Promise((resolve, reject) => {
imageSize(filePath, (err: Error | null, dimensions) => {
if (err) {
reject(new Error(err.message));
} else if (!dimensions) {
reject(new Error('获取图片尺寸失败'));
} else {
resolve(dimensions);
}
});
});
}
async searchForFile(keys: string[]): Promise<SearchResultItem | undefined> { async searchForFile(keys: string[]): Promise<SearchResultItem | undefined> {
const randomResultId = 100000 + Math.floor(Math.random() * 10000); const randomResultId = 100000 + Math.floor(Math.random() * 10000);

View File

@@ -10,14 +10,11 @@ import {
GroupNotify, GroupNotify,
GroupInfoSource, GroupInfoSource,
ShutUpGroupMember, ShutUpGroupMember,
Peer,
ChatType,
} from '@/core'; } from '@/core';
import { isNumeric, solveAsyncProblem } from '@/common/helper'; import { isNumeric, solveAsyncProblem } from '@/common/helper';
import { LimitedHashTable } from '@/common/message-unique'; import { LimitedHashTable } from '@/common/message-unique';
import { NTEventWrapper } from '@/common/event'; import { NTEventWrapper } from '@/common/event';
import { CancelableTask, TaskExecutor } from '@/common/cancel-task'; import { CancelableTask, TaskExecutor } from '@/common/cancel-task';
import { createGroupDetailInfoV2Param, createGroupExtFilter, createGroupExtInfo } from '../data';
export class NTQQGroupApi { export class NTQQGroupApi {
context: InstanceContext; context: InstanceContext;
@@ -50,22 +47,6 @@ export class NTQQGroupApi {
this.initCache().then().catch(e => this.context.logger.logError(e)); this.initCache().then().catch(e => this.context.logger.logError(e));
} }
async createGrayTip(groupCode: string, tip: string) {
return this.context.session.getMsgService().addLocalJsonGrayTipMsg(
{
chatType: ChatType.KCHATTYPEGROUP,
peerUid: groupCode,
} as Peer,
{
busiId: 2201,
jsonStr: JSON.stringify({ "align": "center", "items": [{ "txt": tip, "type": "nor" }] }),
recentAbstract: tip,
isServer: false
},
true,
true
)
}
async initCache() { async initCache() {
for (const group of await this.getGroups(true)) { for (const group of await this.getGroups(true)) {
this.refreshGroupMemberCache(group.groupCode, false).then().catch(e => this.context.logger.logError(e)); this.refreshGroupMemberCache(group.groupCode, false).then().catch(e => this.context.logger.logError(e));
@@ -114,58 +95,6 @@ export class NTQQGroupApi {
return this.context.session.getGroupService().setHeader(groupCode, filePath); return this.context.session.getGroupService().setHeader(groupCode, filePath);
} }
// 0 0 无需管理员审核
// 0 2 需要管理员审核
// 1 2 禁止Bot入群( 最好只传一个1 )
async setGroupRobotAddOption(groupCode: string, robotMemberSwitch?: number, robotMemberExamine?: number) {
let extInfo = createGroupExtInfo(groupCode);
let groupExtFilter = createGroupExtFilter();
if (robotMemberSwitch !== undefined) {
extInfo.extInfo.inviteRobotMemberSwitch = robotMemberSwitch;
groupExtFilter.inviteRobotMemberSwitch = 1;
}
if (robotMemberExamine !== undefined) {
extInfo.extInfo.inviteRobotMemberExamine = robotMemberExamine;
groupExtFilter.inviteRobotMemberExamine = 1;
}
return this.context.session.getGroupService().modifyGroupExtInfoV2(extInfo, groupExtFilter);
}
async setGroupAddOption(groupCode: string, option: {
addOption: number;
groupQuestion?: string;
groupAnswer?: string;
}) {
let param = createGroupDetailInfoV2Param(groupCode);
// 设置要修改的目标
param.filter.addOption = 1;
if (option.addOption == 4 || option.addOption == 5) {
// 4 问题进入答案 5 问题管理员批准
param.filter.groupQuestion = 1;
param.filter.groupAnswer = option.addOption == 4 ? 1 : 0;
param.modifyInfo.groupQuestion = option.groupQuestion || '';
param.modifyInfo.groupAnswer = option.addOption == 4 ? option.groupAnswer || '' : '';
}
param.modifyInfo.addOption = option.addOption;
return this.context.session.getGroupService().modifyGroupDetailInfoV2(param, 0);
}
async setGroupSearch(groupCode: string, option: {
noCodeFingerOpenFlag?: number;
noFingerOpenFlag?: number;
}) {
let param = createGroupDetailInfoV2Param(groupCode);
if (option.noCodeFingerOpenFlag) {
param.filter.noCodeFingerOpenFlag = 1;
param.modifyInfo.noCodeFingerOpenFlag = option.noCodeFingerOpenFlag;
}
if (option.noFingerOpenFlag) {
param.filter.noFingerOpenFlag = 1;
param.modifyInfo.noFingerOpenFlag = option.noFingerOpenFlag;
}
return this.context.session.getGroupService().modifyGroupDetailInfoV2(param, 0);
}
async getGroups(forced: boolean = false) { async getGroups(forced: boolean = false) {
const [, , groupList] = await this.core.eventWrapper.callNormalEventV2( const [, , groupList] = await this.core.eventWrapper.callNormalEventV2(
'NodeIKernelGroupService/getGroupList', 'NodeIKernelGroupService/getGroupList',

View File

@@ -71,7 +71,6 @@ export class NTQQMsgApi {
async queryMsgsWithFilterExWithSeq(peer: Peer, msgSeq: string) { async queryMsgsWithFilterExWithSeq(peer: Peer, msgSeq: string) {
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, { return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
chatInfo: peer, chatInfo: peer,
//searchFields: 3,
filterMsgType: [], filterMsgType: [],
filterSendersUid: [], filterSendersUid: [],
filterMsgToTime: '0', filterMsgToTime: '0',
@@ -85,7 +84,6 @@ export class NTQQMsgApi {
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, { return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
chatInfo: peer, chatInfo: peer,
filterMsgType: [], filterMsgType: [],
//searchFields: 3,
filterSendersUid: SendersUid, filterSendersUid: SendersUid,
filterMsgToTime: MsgTime, filterMsgToTime: MsgTime,
filterMsgFromTime: MsgTime, filterMsgFromTime: MsgTime,
@@ -102,7 +100,6 @@ export class NTQQMsgApi {
filterMsgToTime: '0', filterMsgToTime: '0',
filterMsgFromTime: '0', filterMsgFromTime: '0',
isReverseOrder: false, isReverseOrder: false,
//searchFields: 3,
isIncludeCurrent: true, isIncludeCurrent: true,
pageLimit: 1, pageLimit: 1,
}); });
@@ -113,7 +110,6 @@ export class NTQQMsgApi {
filterMsgType: [], filterMsgType: [],
filterSendersUid: [], filterSendersUid: [],
filterMsgToTime: '0', filterMsgToTime: '0',
//searchFields: 3,
filterMsgFromTime: '0', filterMsgFromTime: '0',
isReverseOrder: true, isReverseOrder: true,
isIncludeCurrent: true, isIncludeCurrent: true,
@@ -132,7 +128,6 @@ export class NTQQMsgApi {
chatInfo: peer,//此处为Peer 为关键查询参数 没有啥也没有 by mlik iowa chatInfo: peer,//此处为Peer 为关键查询参数 没有啥也没有 by mlik iowa
filterMsgType: [], filterMsgType: [],
filterSendersUid: [], filterSendersUid: [],
//searchFields: 3,
filterMsgToTime: filterMsgToTime, filterMsgToTime: filterMsgToTime,
filterMsgFromTime: filterMsgFromTime, filterMsgFromTime: filterMsgFromTime,
isReverseOrder: false, isReverseOrder: false,
@@ -142,11 +137,11 @@ export class NTQQMsgApi {
} }
async queryFirstMsgBySender(peer: Peer, SendersUid: string[]) { async queryFirstMsgBySender(peer: Peer, SendersUid: string[]) {
console.log(peer, SendersUid);
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', '0', { return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', '0', {
chatInfo: peer, chatInfo: peer,
filterMsgType: [], filterMsgType: [],
filterSendersUid: SendersUid, filterSendersUid: SendersUid,
//searchFields: 3,
filterMsgToTime: '0', filterMsgToTime: '0',
filterMsgFromTime: '0', filterMsgFromTime: '0',
isReverseOrder: true, isReverseOrder: true,

View File

@@ -264,7 +264,7 @@ export class NTQQWebApi {
async getGroupHonorInfo(groupCode: string, getType: WebHonorType) { async getGroupHonorInfo(groupCode: string, getType: WebHonorType) {
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com'); const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
let HonorInfo = { let HonorInfo = {
group_id: Number(groupCode), group_id: groupCode,
current_talkative: {}, current_talkative: {},
talkative_list: [], talkative_list: [],
performer_list: [], performer_list: [],

View File

@@ -1,245 +0,0 @@
import { GroupDetailInfoV2Param, GroupExtInfo, GroupExtFilter } from "../types";
export function createGroupDetailInfoV2Param(group_code: string): GroupDetailInfoV2Param {
return {
groupCode: group_code,
filter:
{
noCodeFingerOpenFlag: 0,
noFingerOpenFlag: 0,
groupName: 0,
classExt: 0,
classText: 0,
fingerMemo: 0,
richFingerMemo: 0,
tagRecord: 0,
groupGeoInfo:
{
ownerUid: 0,
setTime: 0,
cityId: 0,
longitude: 0,
latitude: 0,
geoContent: 0,
poiId: 0
},
groupExtAdminNum: 0,
flag: 0,
groupMemo: 0,
groupAioSkinUrl: 0,
groupBoardSkinUrl: 0,
groupCoverSkinUrl: 0,
groupGrade: 0,
activeMemberNum: 0,
certificationType: 0,
certificationText: 0,
groupNewGuideLines:
{
enabled: 0,
content: 0
},
groupFace: 0,
addOption: 0,
shutUpTime: 0,
groupTypeFlag: 0,
appPrivilegeFlag: 0,
appPrivilegeMask: 0,
groupExtOnly:
{
tribeId: 0,
moneyForAddGroup: 0
}, groupSecLevel: 0,
groupSecLevelInfo: 0,
subscriptionUin: 0,
subscriptionUid: "",
allowMemberInvite: 0,
groupQuestion: 0,
groupAnswer: 0,
groupFlagExt3: 0,
groupFlagExt3Mask: 0,
groupOpenAppid: 0,
rootId: 0,
msgLimitFrequency: 0,
hlGuildAppid: 0,
hlGuildSubType: 0,
hlGuildOrgId: 0,
groupFlagExt4: 0,
groupFlagExt4Mask: 0,
groupSchoolInfo: {
location: 0,
grade: 0,
school: 0
},
groupCardPrefix:
{
introduction: 0,
rptPrefix: 0
}, allianceId: 0,
groupFlagPro1: 0,
groupFlagPro1Mask: 0
},
modifyInfo: {
noCodeFingerOpenFlag: 0,
noFingerOpenFlag: 0,
groupName: "",
classExt: 0,
classText: "",
fingerMemo: "",
richFingerMemo: "",
tagRecord: [],
groupGeoInfo: {
ownerUid: "",
SetTime: 0,
CityId: 0,
Longitude: "",
Latitude: "",
GeoContent: "",
poiId: ""
},
groupExtAdminNum: 0,
flag: 0,
groupMemo: "",
groupAioSkinUrl: "",
groupBoardSkinUrl: "",
groupCoverSkinUrl: "",
groupGrade: 0,
activeMemberNum: 0,
certificationType: 0,
certificationText: "",
groupNewGuideLines: {
enabled: false,
content: ""
}, groupFace: 0,
addOption: 0,
shutUpTime: 0,
groupTypeFlag: 0,
appPrivilegeFlag: 0,
appPrivilegeMask: 0,
groupExtOnly: {
tribeId: 0,
moneyForAddGroup: 0
},
groupSecLevel: 0,
groupSecLevelInfo: 0,
subscriptionUin: "",
subscriptionUid: "",
allowMemberInvite: 0,
groupQuestion: "",
groupAnswer: "",
groupFlagExt3: 0,
groupFlagExt3Mask: 0,
groupOpenAppid: 0,
rootId: "",
msgLimitFrequency: 0,
hlGuildAppid: 0,
hlGuildSubType: 0,
hlGuildOrgId: 0,
groupFlagExt4: 0,
groupFlagExt4Mask: 0,
groupSchoolInfo: {
location: "",
grade: 0,
school: ""
},
groupCardPrefix:
{
introduction: "",
rptPrefix: []
},
allianceId: "",
groupFlagPro1: 0,
groupFlagPro1Mask: 0
}
}
}
export function createGroupExtInfo(group_code: string): GroupExtInfo {
return {
groupCode: group_code,
resultCode: 0,
extInfo: {
groupInfoExtSeq: 0,
reserve: 0,
luckyWordId: '',
lightCharNum: 0,
luckyWord: '',
starId: 0,
essentialMsgSwitch: 0,
todoSeq: 0,
blacklistExpireTime: 0,
isLimitGroupRtc: 0,
companyId: 0,
hasGroupCustomPortrait: 0,
bindGuildId: '',
groupOwnerId: {
memberUin: '',
memberUid: '',
memberQid: '',
},
essentialMsgPrivilege: 0,
msgEventSeq: '',
inviteRobotSwitch: 0,
gangUpId: '',
qqMusicMedalSwitch: 0,
showPlayTogetherSwitch: 0,
groupFlagPro1: '',
groupBindGuildIds: {
guildIds: [],
},
viewedMsgDisappearTime: '',
groupExtFlameData: {
switchState: 0,
state: 0,
dayNums: [],
version: 0,
updateTime: '',
isDisplayDayNum: false,
},
groupBindGuildSwitch: 0,
groupAioBindGuildId: '',
groupExcludeGuildIds: {
guildIds: [],
},
fullGroupExpansionSwitch: 0,
fullGroupExpansionSeq: '',
inviteRobotMemberSwitch: 0,
inviteRobotMemberExamine: 0,
groupSquareSwitch: 0,
}
}
}
export function createGroupExtFilter(): GroupExtFilter {
return {
groupInfoExtSeq: 0,
reserve: 0,
luckyWordId: 0,
lightCharNum: 0,
luckyWord: 0,
starId: 0,
essentialMsgSwitch: 0,
todoSeq: 0,
blacklistExpireTime: 0,
isLimitGroupRtc: 0,
companyId: 0,
hasGroupCustomPortrait: 0,
bindGuildId: 0,
groupOwnerId: 0,
essentialMsgPrivilege: 0,
msgEventSeq: 0,
inviteRobotSwitch: 0,
gangUpId: 0,
qqMusicMedalSwitch: 0,
showPlayTogetherSwitch: 0,
groupFlagPro1: 0,
groupBindGuildIds: 0,
viewedMsgDisappearTime: 0,
groupExtFlameData: 0,
groupBindGuildSwitch: 0,
groupAioBindGuildId: 0,
groupExcludeGuildIds: 0,
fullGroupExpansionSwitch: 0,
fullGroupExpansionSeq: 0,
inviteRobotMemberSwitch: 0,
inviteRobotMemberExamine: 0,
groupSquareSwitch: 0,
}
};

View File

@@ -1 +0,0 @@
export * from "./group";

View File

@@ -274,45 +274,5 @@
"9.9.19-34606": { "9.9.19-34606": {
"appid": 537282307, "appid": 537282307,
"qua": "V1_WIN_NQ_9.9.19_34606_GW_B" "qua": "V1_WIN_NQ_9.9.19_34606_GW_B"
},
"9.9.19-34740": {
"appid": 537290691,
"qua": "V1_WIN_NQ_9.9.19_34740_GW_B"
},
"3.2.17-34740": {
"appid": 537290727,
"qua": "V1_LNX_NQ_3.2.17_34740_GW_B"
},
"9.9.19-34958": {
"appid": 537290742,
"qua": "V1_WIN_NQ_9.9.19_34958_GW_B"
},
"3.2.17-35184": {
"appid": 537291084,
"qua": "V1_LNX_NQ_3.2.17_35184_GW_B"
},
"9.9.19-35184": {
"appid": 537291048,
"qua": "V1_WIN_NQ_9.9.19_35184_GW_B"
},
"3.2.17-35341": {
"appid": 537291383,
"qua": "V1_LNX_NQ_3.2.17_35341_GW_B"
},
"9.9.19-35341": {
"appid": 537291347,
"qua": "V1_WIN_NQ_9.9.19_35341_GW_B"
},
"9.9.19-35469": {
"appid": 537291398,
"qua": "V1_WIN_NQ_9.9.19_35469_GW_B"
},
"3.2.18-35951": {
"appid": 537296013,
"qua": "V1_LNX_NQ_3.2.18_35951_GW_B"
},
"9.9.20-35951": {
"appid": 537295977,
"qua": "V1_WIN_NQ_9.9.20_35951_GW_B"
} }
} }

View File

@@ -350,57 +350,5 @@
"3.2.17-34606-arm64": { "3.2.17-34606-arm64": {
"send": "7711270", "send": "7711270",
"recv": "7714BA0" "recv": "7714BA0"
},
"9.9.19-34740-x64": {
"send": "3BDD8D0",
"recv": "3BE20D0"
},
"3.2.17-34740-x64": {
"send": "ADDF0A0",
"recv": "ADE2AC0"
},
"3.2.17-34740-arm64": {
"send": "7753BB8",
"recv": "77574E8"
},
"9.9.19-34958-x64": {
"send": "3BDD8D0",
"recv": "3BE20D0"
},
"3.2.17-35184-x64": {
"send": "AE0DDE0",
"recv": "AE11800"
},
"3.2.17-35184-arm64": {
"send": "7776028",
"recv": "7779958"
},
"9.9.19-35184-x64": {
"send": "3BE5A10",
"recv": "3BEA210"
},
"9.9.19-35341-x64": {
"send": "3BF1D50",
"recv": "3BF6550"
},
"9.9.19-35469-x64": {
"send": "3BF1D50",
"recv": "3BF6550"
},
"3.2.17-35341-x64": {
"send": "AE2F700",
"recv": "AE33120"
},
"3.2.17-35341-arm64": {
"send": "778D840",
"recv": "7791170"
},
"9.9.20-35951-x64": {
"send": "3034BAC",
"recv": "3038354"
},
"3.2.18-35951-x64": {
"send": "AFBBB00",
"recv": "AFBF520"
} }
} }

View File

@@ -30,10 +30,6 @@ import os from 'node:os';
import { NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/core/listeners'; import { NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/core/listeners';
import { proxiedListenerOf } from '@/common/proxy-handler'; import { proxiedListenerOf } from '@/common/proxy-handler';
import { NTQQPacketApi } from './apis/packet'; import { NTQQPacketApi } from './apis/packet';
import { handleServiceServerOnce, receiverServiceListener, ServiceMethodCommand } from '@/remote/service';
import { rpc_decode, rpc_encode } from '@/remote/serialize';
import { PipeClient, PipeServer } from '@/remote/pipe';
import { RemoteWrapperSession } from '@/remote/remoteSession';
export * from './wrapper'; export * from './wrapper';
export * from './types'; export * from './types';
export * from './services'; export * from './services';
@@ -101,62 +97,8 @@ export class NapCatCore {
constructor(context: InstanceContext, selfInfo: SelfInfo) { constructor(context: InstanceContext, selfInfo: SelfInfo) {
this.selfInfo = selfInfo; this.selfInfo = selfInfo;
this.context = context; this.context = context;
this.util = this.context.wrapper.NodeQQNTWrapperUtil; this.util = this.context.wrapper.NodeQQNTWrapperUtil;
this.eventWrapper = new NTEventWrapper(context.session); this.eventWrapper = new NTEventWrapper(context.session);
// 管道服务端测试
let pipe_server = new PipeServer('//./pipe/napcat');
pipe_server.registerHandler(async (packet, helper) => {
if (packet.type !== 'event_request') {
return helper.error('Invalid packet type');
}
let event_rpc_data = rpc_decode<{ params: any[] }>(JSON.parse(packet.data));
let event_rpc_trace = packet.trace;
let event_rpc_command = packet.command as ServiceMethodCommand;
let event_rpc_result = await handleServiceServerOnce(event_rpc_command,
async (listenerCommand: string, ...args: any[]) => {
let listener_data = rpc_encode<{ params: any[] }>({ params: args });
helper.sendListenerCallback(listenerCommand, JSON.stringify(rpc_encode(listener_data)));
},
this.eventWrapper,
...event_rpc_data.params
);
return helper.sendEventResponse(event_rpc_trace, JSON.stringify(rpc_encode(event_rpc_result)));
});
pipe_server.start().then(() => {
this.context.logger.log('Pipe server started successfully');
let pipe_client = new PipeClient('//./pipe/napcat');
let trace_callback_map = new Map<string, (trace: string, data: any) => void>();
pipe_client.registerHandler(async (packet, _helper) => {
if (packet.type == 'event_response') {
let event_rpc_data = rpc_decode<Array<any>>(JSON.parse(packet.data));
trace_callback_map.get(packet.trace)?.(packet.trace, event_rpc_data);
} else if (packet.type == 'listener_callback') {
let event_rpc_data = rpc_decode<Array<any>>(JSON.parse(packet.data));
await receiverServiceListener(packet.command, ...event_rpc_data);
}
});
this.context.session = new RemoteWrapperSession(async (_serviceClient, serviceCommand, ...args) => {
let trace = crypto.randomUUID();
return await new Promise((resolve, _reject) => {
trace_callback_map.set(trace, (_trace, data) => {
//console.log('Received response for trace:', _trace, 'with data:', data);
resolve(data);
});
pipe_client.sendRequest(serviceCommand, JSON.stringify(rpc_encode({ params: args })), trace);
});
});
pipe_client.connect().then(() => {
this.context.logger.log('Pipe client connected successfully');
}).catch((e) => {
this.context.logger.logError('Pipe client connection failed: ' + e.message);
});
}).catch((e) => {
this.context.logger.logError('Pipe server start failed: ' + e.message);
});
this.configLoader = new NapCatConfigLoader(this, this.context.pathWrapper.configPath,NapcatConfigSchema); this.configLoader = new NapCatConfigLoader(this, this.context.pathWrapper.configPath,NapcatConfigSchema);
this.apis = { this.apis = {
FileApi: new NTQQFileApi(this.context, this), FileApi: new NTQQFileApi(this.context, this),
@@ -309,13 +251,13 @@ export async function genSessionConfig(
} }
export interface InstanceContext { export interface InstanceContext {
session: NodeIQQNTWrapperSession; readonly workingEnv: NapCatCoreWorkingEnv;
workingEnv: NapCatCoreWorkingEnv; readonly wrapper: WrapperNodeApi;
wrapper: WrapperNodeApi; readonly session: NodeIQQNTWrapperSession;
logger: LogWrapper; readonly logger: LogWrapper;
loginService: NodeIKernelLoginService; readonly loginService: NodeIKernelLoginService;
basicInfoWrapper: QQBasicInfoWrapper; readonly basicInfoWrapper: QQBasicInfoWrapper;
pathWrapper: NapCatPathWrapper; readonly pathWrapper: NapCatPathWrapper;
} }
export interface StableNTApiWrapper { export interface StableNTApiWrapper {

View File

@@ -3,43 +3,44 @@ import { BuddyCategoryType, FriendRequestNotify } from '@/core/types';
export type OnBuddyChangeParams = BuddyCategoryType[]; export type OnBuddyChangeParams = BuddyCategoryType[];
export class NodeIKernelBuddyListener { export class NodeIKernelBuddyListener {
onBuddyListChangedV2(_arg: unknown): any { onBuddyListChangedV2(arg: unknown): any {
} }
onAddBuddyNeedVerify(_arg: unknown): any { onAddBuddyNeedVerify(arg: unknown): any {
} }
onAddMeSettingChanged(_arg: unknown): any { onAddMeSettingChanged(arg: unknown): any {
} }
onAvatarUrlUpdated(_arg: unknown): any { onAvatarUrlUpdated(arg: unknown): any {
} }
onBlockChanged(_arg: unknown): any { onBlockChanged(arg: unknown): any {
} }
onBuddyDetailInfoChange(_arg: unknown): any { onBuddyDetailInfoChange(arg: unknown): any {
} }
onBuddyInfoChange(_arg: unknown): any { onBuddyInfoChange(arg: unknown): any {
} }
onBuddyListChange(_arg: OnBuddyChangeParams): any { onBuddyListChange(arg: OnBuddyChangeParams): any {
} }
onBuddyRemarkUpdated(_arg: unknown): any { onBuddyRemarkUpdated(arg: unknown): any {
} }
onBuddyReqChange(_arg: FriendRequestNotify): any { onBuddyReqChange(arg: FriendRequestNotify): any {
} }
onBuddyReqUnreadCntChange(_arg: unknown): any { onBuddyReqUnreadCntChange(arg: unknown): any {
} }
onCheckBuddySettingResult(_arg: unknown): any { onCheckBuddySettingResult(arg: unknown): any {
} }
onDelBatchBuddyInfos(_arg: unknown): any { onDelBatchBuddyInfos(arg: unknown): any {
console.log('onDelBatchBuddyInfos not implemented', ...arguments);
} }
onDoubtBuddyReqChange(_arg: onDoubtBuddyReqChange(_arg:
@@ -65,12 +66,12 @@ export class NodeIKernelBuddyListener {
onDoubtBuddyReqUnreadNumChange(_num: number): void | Promise<void> { onDoubtBuddyReqUnreadNumChange(_num: number): void | Promise<void> {
} }
onNickUpdated(_arg: unknown): any { onNickUpdated(arg: unknown): any {
} }
onSmartInfos(_arg: unknown): any { onSmartInfos(arg: unknown): any {
} }
onSpacePermissionInfos(_arg: unknown): any { onSpacePermissionInfos(arg: unknown): any {
} }
} }

View File

@@ -1,71 +1,71 @@
import { SelfStatusInfo, User, UserDetailInfoListenerArg } from '@/core/types'; import { User, UserDetailInfoListenerArg } from '@/core/types';
export class NodeIKernelProfileListener { export class NodeIKernelProfileListener {
onUserDetailInfoChanged(_arg: UserDetailInfoListenerArg): void { onUserDetailInfoChanged(arg: UserDetailInfoListenerArg): void {
} }
onProfileSimpleChanged(..._args: unknown[]): any { onProfileSimpleChanged(...args: unknown[]): any {
} }
onProfileDetailInfoChanged(_profile: User): any { onProfileDetailInfoChanged(profile: User): any {
} }
onStatusUpdate(..._args: unknown[]): any { onStatusUpdate(...args: unknown[]): any {
} }
onSelfStatusChanged(_info: SelfStatusInfo): any { onSelfStatusChanged(...args: unknown[]): any {
} }
onStrangerRemarkChanged(..._args: unknown[]): any { onStrangerRemarkChanged(...args: unknown[]): any {
} }
onMemberListChange(..._args: unknown[]): any { onMemberListChange(...args: unknown[]): any {
} }
onMemberInfoChange(..._args: unknown[]): any { onMemberInfoChange(...args: unknown[]): any {
} }
onGroupListUpdate(..._args: unknown[]): any { onGroupListUpdate(...args: unknown[]): any {
} }
onGroupAllInfoChange(..._args: unknown[]): any { onGroupAllInfoChange(...args: unknown[]): any {
} }
onGroupDetailInfoChange(..._args: unknown[]): any { onGroupDetailInfoChange(...args: unknown[]): any {
} }
onGroupConfMemberChange(..._args: unknown[]): any { onGroupConfMemberChange(...args: unknown[]): any {
} }
onGroupExtListUpdate(..._args: unknown[]): any { onGroupExtListUpdate(...args: unknown[]): any {
} }
onGroupNotifiesUpdated(..._args: unknown[]): any { onGroupNotifiesUpdated(...args: unknown[]): any {
} }
onGroupNotifiesUnreadCountUpdated(..._args: unknown[]): any { onGroupNotifiesUnreadCountUpdated(...args: unknown[]): any {
} }
onGroupMemberLevelInfoChange(..._args: unknown[]): any { onGroupMemberLevelInfoChange(...args: unknown[]): any {
} }
onGroupBulletinChange(..._args: unknown[]): any { onGroupBulletinChange(...args: unknown[]): any {
} }
} }

View File

@@ -6,14 +6,13 @@ import {
PacketMsgFileElement, PacketMsgFileElement,
PacketMsgPicElement, PacketMsgPicElement,
PacketMsgPttElement, PacketMsgPttElement,
PacketMsgReplyElement, PacketMsgVideoElement
PacketMsgVideoElement,
} from '@/core/packet/message/element'; } from '@/core/packet/message/element';
import { ChatType, MsgSourceType, NTMsgType, RawMessage } from '@/core'; import { ChatType, MsgSourceType, NTMsgType, RawMessage } from '@/core';
import { MiniAppRawData, MiniAppReqParams } from '@/core/packet/entities/miniApp'; import { MiniAppRawData, MiniAppReqParams } from '@/core/packet/entities/miniApp';
import { AIVoiceChatType } from '@/core/packet/entities/aiChat'; import { AIVoiceChatType } from '@/core/packet/entities/aiChat';
import { NapProtoDecodeStructType, NapProtoEncodeStructType, NapProtoMsg } from '@napneko/nap-proto-core'; import { NapProtoDecodeStructType, NapProtoEncodeStructType, NapProtoMsg } from '@napneko/nap-proto-core';
import { IndexNode, LongMsgResult, MsgInfo, PushMsgBody } from '@/core/packet/transformer/proto'; import { IndexNode, LongMsgResult, MsgInfo } from '@/core/packet/transformer/proto';
import { OidbPacket } from '@/core/packet/transformer/base'; import { OidbPacket } from '@/core/packet/transformer/base';
import { ImageOcrResult } from '@/core/packet/entities/ocrResult'; import { ImageOcrResult } from '@/core/packet/entities/ocrResult';
import { gunzipSync } from 'zlib'; import { gunzipSync } from 'zlib';
@@ -30,8 +29,13 @@ export class PacketOperationContext {
return await this.context.client.sendOidbPacket(pkt, rsp); return await this.context.client.sendOidbPacket(pkt, rsp);
} }
async SendPoke(is_group: boolean, peer: number, target?: number) { async GroupPoke(groupUin: number, uin: number) {
const req = trans.SendPoke.build(is_group, peer, target ?? peer); const req = trans.SendPoke.build(uin, groupUin);
await this.context.client.sendOidbPacket(req);
}
async FriendPoke(uin: number) {
const req = trans.SendPoke.build(uin);
await this.context.client.sendOidbPacket(req); await this.context.client.sendOidbPacket(req);
} }
@@ -72,9 +76,8 @@ export class PacketOperationContext {
async UploadResources(msg: PacketMsg[], groupUin: number = 0) { async UploadResources(msg: PacketMsg[], groupUin: number = 0) {
const chatType = groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C; const chatType = groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C;
const peerUid = groupUin ? String(groupUin) : this.context.napcore.basicInfo.uid; const peerUid = groupUin ? String(groupUin) : this.context.napcore.basicInfo.uid;
const reqList = msg.flatMap((m) => const reqList = msg.flatMap(m =>
m.msg m.msg.map(e => {
.map((e) => {
if (e instanceof PacketMsgPicElement) { if (e instanceof PacketMsgPicElement) {
return this.context.highway.uploadImage({ chatType, peerUid }, e); return this.context.highway.uploadImage({ chatType, peerUid }, e);
} else if (e instanceof PacketMsgVideoElement) { } else if (e instanceof PacketMsgVideoElement) {
@@ -85,11 +88,10 @@ export class PacketOperationContext {
return this.context.highway.uploadFile({ chatType, peerUid }, e); return this.context.highway.uploadFile({ chatType, peerUid }, e);
} }
return null; return null;
}) }).filter(Boolean)
.filter(Boolean)
); );
const res = await Promise.allSettled(reqList); const res = await Promise.allSettled(reqList);
this.context.logger.info(`上传资源${res.length}个,失败${res.filter((r) => r.status === 'rejected').length}`); this.context.logger.info(`上传资源${res.length}个,失败${res.filter(r => r.status === 'rejected').length}`);
res.forEach((result, index) => { res.forEach((result, index) => {
if (result.status === 'rejected') { if (result.status === 'rejected') {
this.context.logger.error(`上传第${index + 1}个资源失败:${result.reason.stack}`); this.context.logger.error(`上传第${index + 1}个资源失败:${result.reason.stack}`);
@@ -98,13 +100,10 @@ export class PacketOperationContext {
} }
async UploadImage(img: PacketMsgPicElement) { async UploadImage(img: PacketMsgPicElement) {
await this.context.highway.uploadImage( await this.context.highway.uploadImage({
{
chatType: ChatType.KCHATTYPEC2C, chatType: ChatType.KCHATTYPEC2C,
peerUid: this.context.napcore.basicInfo.uid, peerUid: this.context.napcore.basicInfo.uid
}, }, img);
img
);
const index = img.msgInfo?.msgInfoBody?.at(0)?.index; const index = img.msgInfo?.msgInfoBody?.at(0)?.index;
if (!index) { if (!index) {
throw new Error('img.msgInfo?.msgInfoBody![0].index! is undefined'); throw new Error('img.msgInfo?.msgInfoBody![0].index! is undefined');
@@ -119,20 +118,6 @@ export class PacketOperationContext {
return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`; return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`;
} }
async GetPttUrl(selfUid: string, node: NapProtoEncodeStructType<typeof IndexNode>) {
const req = trans.DownloadPtt.build(selfUid, node);
const resp = await this.context.client.sendOidbPacket(req, true);
const res = trans.DownloadPtt.parse(resp);
return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`;
}
async GetVideoUrl(selfUid: string, node: NapProtoEncodeStructType<typeof IndexNode>) {
const req = trans.DownloadVideo.build(selfUid, node);
const resp = await this.context.client.sendOidbPacket(req, true);
const res = trans.DownloadVideo.parse(resp);
return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`;
}
async GetGroupImageUrl(groupUin: number, node: NapProtoEncodeStructType<typeof IndexNode>) { async GetGroupImageUrl(groupUin: number, node: NapProtoEncodeStructType<typeof IndexNode>) {
const req = trans.DownloadGroupImage.build(groupUin, node); const req = trans.DownloadGroupImage.build(groupUin, node);
const resp = await this.context.client.sendOidbPacket(req, true); const resp = await this.context.client.sendOidbPacket(req, true);
@@ -140,21 +125,6 @@ export class PacketOperationContext {
return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`; return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`;
} }
async GetGroupPttUrl(groupUin: number, node: NapProtoEncodeStructType<typeof IndexNode>) {
const req = trans.DownloadGroupPtt.build(groupUin, node);
const resp = await this.context.client.sendOidbPacket(req, true);
const res = trans.DownloadImage.parse(resp);
return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`;
}
async GetGroupVideoUrl(groupUin: number, node: NapProtoEncodeStructType<typeof IndexNode>) {
const req = trans.DownloadGroupVideo.build(groupUin, node);
const resp = await this.context.client.sendOidbPacket(req, true);
const res = trans.DownloadImage.parse(resp);
return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`;
}
async ImageOCR(imgUrl: string) { async ImageOCR(imgUrl: string) {
const req = trans.ImageOCR.build(imgUrl); const req = trans.ImageOCR.build(imgUrl);
const resp = await this.context.client.sendOidbPacket(req, true); const resp = await this.context.client.sendOidbPacket(req, true);
@@ -167,66 +137,24 @@ export class PacketOperationContext {
coordinates: item.polygon.coordinates.map((c) => { coordinates: item.polygon.coordinates.map((c) => {
return { return {
x: c.x, x: c.x,
y: c.y, y: c.y
}; };
}), }),
}; };
}), }),
language: res.ocrRspBody.language, language: res.ocrRspBody.language
} as ImageOcrResult; } as ImageOcrResult;
} }
private async SendPreprocess(msg: PacketMsg[], groupUin: number = 0) {
const ps = msg.map((m) => {
return m.msg.map(async (e) => {
if (e instanceof PacketMsgReplyElement && !e.targetElems) {
this.context.logger.debug(`Cannot find reply element's targetElems, prepare to fetch it...`);
if (!e.targetPeer?.peerUid) {
this.context.logger.error(`targetPeer is undefined!`);
}
let targetMsg: NapProtoEncodeStructType<typeof PushMsgBody>[] | undefined;
if (e.isGroupReply) {
targetMsg = await this.FetchGroupMessage(+(e.targetPeer?.peerUid ?? 0), e.targetMessageSeq, e.targetMessageSeq);
} else {
targetMsg = await this.FetchC2CMessage(await this.context.napcore.basicInfo.uin2uid(e.targetUin), e.targetMessageSeq, e.targetMessageSeq);
}
e.targetElems = targetMsg.at(0)?.body?.richText?.elems;
e.targetSourceMsg = targetMsg.at(0);
}
});
}).flat();
await Promise.all(ps)
await this.UploadResources(msg, groupUin);
}
async FetchGroupMessage(groupUin: number, startSeq: number, endSeq: number): Promise<NapProtoDecodeStructType<typeof PushMsgBody>[]> {
const req = trans.FetchGroupMessage.build(groupUin, startSeq, endSeq);
const resp = await this.context.client.sendOidbPacket(req, true);
const res = trans.FetchGroupMessage.parse(resp);
return res.body.messages
}
async FetchC2CMessage(targetUid: string, startSeq: number, endSeq: number): Promise<NapProtoDecodeStructType<typeof PushMsgBody>[]> {
const req = trans.FetchC2CMessage.build(targetUid, startSeq, endSeq);
const resp = await this.context.client.sendOidbPacket(req, true);
const res = trans.FetchC2CMessage.parse(resp);
return res.messages
}
async UploadForwardMsg(msg: PacketMsg[], groupUin: number = 0) { async UploadForwardMsg(msg: PacketMsg[], groupUin: number = 0) {
await this.SendPreprocess(msg, groupUin); await this.UploadResources(msg, groupUin);
const req = trans.UploadForwardMsg.build(this.context.napcore.basicInfo.uid, msg, groupUin); const req = trans.UploadForwardMsg.build(this.context.napcore.basicInfo.uid, msg, groupUin);
const resp = await this.context.client.sendOidbPacket(req, true); const resp = await this.context.client.sendOidbPacket(req, true);
const res = trans.UploadForwardMsg.parse(resp); const res = trans.UploadForwardMsg.parse(resp);
return res.result.resId; return res.result.resId;
} }
async MoveGroupFile( async MoveGroupFile(groupUin: number, fileUUID: string, currentParentDirectory: string, targetParentDirectory: string) {
groupUin: number,
fileUUID: string,
currentParentDirectory: string,
targetParentDirectory: string
) {
const req = trans.MoveGroupFile.build(groupUin, fileUUID, currentParentDirectory, targetParentDirectory); const req = trans.MoveGroupFile.build(groupUin, fileUUID, currentParentDirectory, targetParentDirectory);
const resp = await this.context.client.sendOidbPacket(req, true); const resp = await this.context.client.sendOidbPacket(req, true);
const res = trans.MoveGroupFile.parse(resp); const res = trans.MoveGroupFile.parse(resp);
@@ -246,7 +174,6 @@ export class PacketOperationContext {
const res = trans.DownloadGroupFile.parse(resp); const res = trans.DownloadGroupFile.parse(resp);
return `https://${res.download.downloadDns}/ftn_handler/${Buffer.from(res.download.downloadUrl).toString('hex')}/?fname=`; return `https://${res.download.downloadDns}/ftn_handler/${Buffer.from(res.download.downloadUrl).toString('hex')}/?fname=`;
} }
async GetPrivateFileUrl(self_id: string, fileUUID: string, md5: string) { async GetPrivateFileUrl(self_id: string, fileUUID: string, md5: string) {
const req = trans.DownloadPrivateFile.build(self_id, fileUUID, md5); const req = trans.DownloadPrivateFile.build(self_id, fileUUID, md5);
const resp = await this.context.client.sendOidbPacket(req, true); const resp = await this.context.client.sendOidbPacket(req, true);
@@ -254,6 +181,13 @@ export class PacketOperationContext {
return `http://${res.body?.result?.server}:${res.body?.result?.port}${res.body?.result?.url?.slice(8)}&isthumb=0`; return `http://${res.body?.result?.server}:${res.body?.result?.port}${res.body?.result?.url?.slice(8)}&isthumb=0`;
} }
async GetGroupPttUrl(groupUin: number, node: NapProtoEncodeStructType<typeof IndexNode>) {
const req = trans.DownloadGroupPtt.build(groupUin, node);
const resp = await this.context.client.sendOidbPacket(req, true);
const res = trans.DownloadGroupPtt.parse(resp);
return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`;
}
async GetMiniAppAdaptShareInfo(param: MiniAppReqParams) { async GetMiniAppAdaptShareInfo(param: MiniAppReqParams) {
const req = trans.GetMiniAppAdaptShareInfo.build(param); const req = trans.GetMiniAppAdaptShareInfo.build(param);
const resp = await this.context.client.sendOidbPacket(req, true); const resp = await this.context.client.sendOidbPacket(req, true);
@@ -269,17 +203,12 @@ export class PacketOperationContext {
return res.content.map((item) => { return res.content.map((item) => {
return { return {
category: item.category, category: item.category,
voices: item.voices, voices: item.voices
}; };
}); });
} }
async GetAiVoice( async GetAiVoice(groupUin: number, voiceId: string, text: string, chatType: AIVoiceChatType): Promise<NapProtoDecodeStructType<typeof MsgInfo>> {
groupUin: number,
voiceId: string,
text: string,
chatType: AIVoiceChatType
): Promise<NapProtoDecodeStructType<typeof MsgInfo>> {
let reqTime = 0; let reqTime = 0;
const reqMaxTime = 30; const reqMaxTime = 30;
const sessionId = crypto.randomBytes(4).readUInt32BE(0); const sessionId = crypto.randomBytes(4).readUInt32BE(0);
@@ -307,7 +236,6 @@ export class PacketOperationContext {
if (!main?.actionData.msgBody) { if (!main?.actionData.msgBody) {
throw new Error('msgBody is empty'); throw new Error('msgBody is empty');
} }
this.context.logger.debug('rawChains ', inflate.toString('hex'));
const messagesPromises = main.actionData.msgBody.map(async (msg) => { const messagesPromises = main.actionData.msgBody.map(async (msg) => {
if (!msg?.body?.richText?.elems) { if (!msg?.body?.richText?.elems) {
@@ -323,12 +251,12 @@ export class PacketOperationContext {
const groupUin = msg?.responseHead.grp?.groupUin ?? 0; const groupUin = msg?.responseHead.grp?.groupUin ?? 0;
element.picElement = { element.picElement = {
...element.picElement, ...element.picElement,
originImageUrl: await this.GetGroupImageUrl(groupUin, index!), originImageUrl: await this.GetGroupImageUrl(groupUin, index!)
}; };
} else { } else {
element.picElement = { element.picElement = {
...element.picElement, ...element.picElement,
originImageUrl: await this.GetImageUrl(this.context.napcore.basicInfo.uid, index!), originImageUrl: await this.GetImageUrl(this.context.napcore.basicInfo.uid, index!)
}; };
} }
return element; return element;

View File

@@ -24,15 +24,12 @@ export class PacketMsgBuilder {
} }
return { return {
responseHead: { responseHead: {
fromUin: node.senderUin,
type: 0,
sigMap: 0,
toUin: 0,
fromUid: '', fromUid: '',
fromUin: node.senderUin,
toUid: node.groupId ? undefined : selfUid,
forward: node.groupId ? undefined : { forward: node.groupId ? undefined : {
friendName: node.senderName, friendName: node.senderName,
}, },
toUid: node.groupId ? undefined : selfUid,
grp: node.groupId ? { grp: node.groupId ? {
groupUin: node.groupId, groupUin: node.groupId,
memberName: node.senderName, memberName: node.senderName,
@@ -43,13 +40,16 @@ export class PacketMsgBuilder {
type: node.groupId ? 82 : 9, type: node.groupId ? 82 : 9,
subType: node.groupId ? undefined : 4, subType: node.groupId ? undefined : 4,
divSeq: node.groupId ? undefined : 4, divSeq: node.groupId ? undefined : 4,
autoReply: 0, msgId: crypto.randomBytes(4).readUInt32LE(0),
sequence: crypto.randomBytes(4).readUInt32LE(0), sequence: crypto.randomBytes(4).readUInt32LE(0),
timeStamp: +node.time.toString().substring(0, 10), timeStamp: +node.time.toString().substring(0, 10),
field7: BigInt(1),
field8: 0,
field9: 0,
forward: { forward: {
field1: 0, field1: 0,
field2: 0, field2: 0,
field3: node.groupId ? 1 : 2, field3: node.groupId ? 0 : 2,
unknownBase64: avatar, unknownBase64: avatar,
avatar: avatar avatar: avatar
} }

View File

@@ -10,7 +10,6 @@ import {
MsgInfo, MsgInfo,
NotOnlineImage, NotOnlineImage,
OidbSvcTrpcTcp0XE37_800Response, OidbSvcTrpcTcp0XE37_800Response,
PushMsgBody,
QBigFaceExtra, QBigFaceExtra,
QSmallFaceExtra, QSmallFaceExtra,
} from '@/core/packet/transformer/proto'; } from '@/core/packet/transformer/proto';
@@ -30,8 +29,7 @@ import {
SendReplyElement, SendReplyElement,
SendMultiForwardMsgElement, SendMultiForwardMsgElement,
SendTextElement, SendTextElement,
SendVideoElement, SendVideoElement
Peer
} from '@/core'; } from '@/core';
import {ForwardMsgBuilder} from '@/common/forward-msg-builder'; import {ForwardMsgBuilder} from '@/common/forward-msg-builder';
import {PacketMsg, PacketSendMsgElement} from '@/core/packet/message/message'; import {PacketMsg, PacketSendMsgElement} from '@/core/packet/message/message';
@@ -148,40 +146,41 @@ export class PacketMsgAtElement extends PacketMsgTextElement {
} }
export class PacketMsgReplyElement extends IPacketMsgElement<SendReplyElement> { export class PacketMsgReplyElement extends IPacketMsgElement<SendReplyElement> {
time: number; messageId: bigint;
targetMessageId: bigint; messageSeq: number;
targetMessageSeq: number; messageClientSeq: number;
targetMessageClientSeq: number;
targetUin: number; targetUin: number;
targetUid: string; targetUid: string;
targetElems?: NapProtoEncodeStructType<typeof Elem>[]; time: number;
targetSourceMsg?: NapProtoEncodeStructType<typeof PushMsgBody>; elems: PacketMsg[];
targetPeer?: Peer;
constructor(element: SendReplyElement) { constructor(element: SendReplyElement) {
super(element); super(element);
this.time = +(element.replyElement.replyMsgTime ?? Math.floor(Date.now() / 1000)); this.messageId = BigInt(element.replyElement.replayMsgId ?? 0);
this.targetMessageId = BigInt(element.replyElement.replayMsgId ?? 0); this.messageSeq = +(element.replyElement.replayMsgSeq ?? 0);
this.targetMessageSeq = +(element.replyElement.replayMsgSeq ?? 0); this.messageClientSeq = +(element.replyElement.replyMsgClientSeq ?? 0);
this.targetMessageClientSeq = +(element.replyElement.replyMsgClientSeq ?? 0);
this.targetUin = +(element.replyElement.senderUin ?? 0); this.targetUin = +(element.replyElement.senderUin ?? 0);
this.targetUid = element.replyElement.senderUidStr ?? ''; this.targetUid = element.replyElement.senderUidStr ?? '';
this.targetPeer = element.replyElement._replyMsgPeer; this.time = +(element.replyElement.replyMsgTime ?? 0);
this.elems = []; // TODO: in replyElement.sourceMsgTextElems
} }
get isGroupReply(): boolean { get isGroupReply(): boolean {
return this.targetMessageClientSeq === 0; return this.messageClientSeq === 0;
} }
override buildElement(): NapProtoEncodeStructType<typeof Elem>[] { override buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
return [{ return [{
srcMsg: { srcMsg: {
origSeqs: [this.isGroupReply ? this.targetMessageSeq : this.targetMessageClientSeq], origSeqs: [this.isGroupReply ? this.messageClientSeq : this.messageSeq],
senderUin: BigInt(this.targetUin), senderUin: BigInt(this.targetUin),
time: this.time, time: this.time,
elems: this.targetElems ?? [], elems: [], // TODO: in replyElement.sourceMsgTextElems
sourceMsg: new NapProtoMsg(PushMsgBody).encode(this.targetSourceMsg ?? {}), pbReserve: {
toUin: BigInt(0), messageId: this.messageId,
},
toUin: BigInt(this.targetUin),
type: 1,
} }
}]; }];
} }

View File

@@ -8,13 +8,13 @@ class SendPoke extends PacketTransformer<typeof proto.OidbSvcTrpcTcpBase> {
super(); super();
} }
build(is_group: boolean, peer: number, target: number): OidbPacket { build(peer: number, group?: number): OidbPacket {
const payload = { const data = new NapProtoMsg(proto.OidbSvcTrpcTcp0XED3_1).encode({
uin: target, uin: peer,
ext: 0, groupUin: group,
...(is_group ? { groupUin: peer } : { friendUin: peer }) friendUin: group ?? peer,
}; ext: 0
const data = new NapProtoMsg(proto.OidbSvcTrpcTcp0XED3_1).encode(payload); });
return OidbBase.build(0xED3, 1, data); return OidbBase.build(0xED3, 1, data);
} }

View File

@@ -1,50 +0,0 @@
import * as proto from '@/core/packet/transformer/proto';
import { NapProtoEncodeStructType, NapProtoMsg } from '@napneko/nap-proto-core';
import { OidbPacket, PacketTransformer } from '@/core/packet/transformer/base';
import OidbBase from '@/core/packet/transformer/oidb/oidbBase';
import { IndexNode } from '@/core/packet/transformer/proto';
class DownloadGroupVideo extends PacketTransformer<typeof proto.NTV2RichMediaResp> {
constructor() {
super();
}
build(groupUin: number, node: NapProtoEncodeStructType<typeof IndexNode>): OidbPacket {
const body = new NapProtoMsg(proto.NTV2RichMediaReq).encode({
reqHead: {
common: {
requestId: 1,
command: 200
},
scene: {
requestType: 2,
businessType: 2,
sceneType: 2,
group: {
groupUin: groupUin
}
},
client: {
agentType: 2,
}
},
download: {
node: node,
download: {
video: {
busiType: 0,
sceneType: 0
}
}
}
});
return OidbBase.build(0x11EA, 200, body, true, false);
}
parse(data: Buffer) {
const oidbBody = OidbBase.parse(data).body;
return new NapProtoMsg(proto.NTV2RichMediaResp).decode(oidbBody);
}
}
export default new DownloadGroupVideo();

View File

@@ -1,51 +0,0 @@
import * as proto from '@/core/packet/transformer/proto';
import { NapProtoEncodeStructType, NapProtoMsg } from '@napneko/nap-proto-core';
import { OidbPacket, PacketTransformer } from '@/core/packet/transformer/base';
import OidbBase from '@/core/packet/transformer/oidb/oidbBase';
import { IndexNode } from '@/core/packet/transformer/proto';
class DownloadPtt extends PacketTransformer<typeof proto.NTV2RichMediaResp> {
constructor() {
super();
}
build(selfUid: string, node: NapProtoEncodeStructType<typeof IndexNode>): OidbPacket {
const body = new NapProtoMsg(proto.NTV2RichMediaReq).encode({
reqHead: {
common: {
requestId: 1,
command: 200
},
scene: {
requestType: 1,
businessType: 3,
sceneType: 1,
c2C: {
accountType: 2,
targetUid: selfUid
},
},
client: {
agentType: 2,
}
},
download: {
node: node,
download: {
video: {
busiType: 0,
sceneType: 0
}
}
}
});
return OidbBase.build(0x126D, 200, body, true, false);
}
parse(data: Buffer) {
const oidbBody = OidbBase.parse(data).body;
return new NapProtoMsg(proto.NTV2RichMediaResp).decode(oidbBody);
}
}
export default new DownloadPtt();

View File

@@ -1,51 +0,0 @@
import * as proto from '@/core/packet/transformer/proto';
import { NapProtoEncodeStructType, NapProtoMsg } from '@napneko/nap-proto-core';
import { OidbPacket, PacketTransformer } from '@/core/packet/transformer/base';
import OidbBase from '@/core/packet/transformer/oidb/oidbBase';
import { IndexNode } from '@/core/packet/transformer/proto';
class DownloadVideo extends PacketTransformer<typeof proto.NTV2RichMediaResp> {
constructor() {
super();
}
build(selfUid: string, node: NapProtoEncodeStructType<typeof IndexNode>): OidbPacket {
const body = new NapProtoMsg(proto.NTV2RichMediaReq).encode({
reqHead: {
common: {
requestId: 1,
command: 200
},
scene: {
requestType: 2,
businessType: 2,
sceneType: 1,
c2C: {
accountType: 2,
targetUid: selfUid
},
},
client: {
agentType: 2,
}
},
download: {
node: node,
download: {
video: {
busiType: 0,
sceneType: 0
}
}
}
});
return OidbBase.build(0x11E9, 200, body, true, false);
}
parse(data: Buffer) {
const oidbBody = OidbBase.parse(data).body;
return new NapProtoMsg(proto.NTV2RichMediaResp).decode(oidbBody);
}
}
export default new DownloadVideo();

View File

@@ -13,6 +13,3 @@ export { default as UploadPrivatePtt } from './UploadPrivatePtt';
export { default as UploadPrivateVideo } from './UploadPrivateVideo'; export { default as UploadPrivateVideo } from './UploadPrivateVideo';
export { default as DownloadImage } from './DownloadImage'; export { default as DownloadImage } from './DownloadImage';
export { default as DownloadGroupImage } from './DownloadGroupImage'; export { default as DownloadGroupImage } from './DownloadGroupImage';
export { default as DownloadVideo } from './DownloadVideo';
export { default as DownloadGroupVideo } from './DownloadGroupVideo';
export { default as DownloadPtt } from './DownloadPtt';

View File

@@ -1,27 +0,0 @@
import * as proto from '@/core/packet/transformer/proto';
import { NapProtoMsg } from '@napneko/nap-proto-core';
import { OidbPacket, PacketHexStrBuilder, PacketTransformer } from '@/core/packet/transformer/base';
class FetchC2CMessage extends PacketTransformer<typeof proto.SsoGetC2cMsgResponse> {
constructor() {
super();
}
build(targetUid: string, startSeq: number, endSeq: number): OidbPacket {
const req = new NapProtoMsg(proto.SsoGetC2cMsg).encode({
friendUid: targetUid,
startSequence: startSeq,
endSequence: endSeq,
});
return {
cmd: 'trpc.msg.register_proxy.RegisterProxy.SsoGetC2cMsg',
data: PacketHexStrBuilder(req)
};
}
parse(data: Buffer) {
return new NapProtoMsg(proto.SsoGetC2cMsgResponse).decode(data);
}
}
export default new FetchC2CMessage();

View File

@@ -1,30 +0,0 @@
import * as proto from '@/core/packet/transformer/proto';
import { NapProtoMsg } from '@napneko/nap-proto-core';
import { OidbPacket, PacketHexStrBuilder, PacketTransformer } from '@/core/packet/transformer/base';
class FetchGroupMessage extends PacketTransformer<typeof proto.SsoGetGroupMsgResponse> {
constructor() {
super();
}
build(groupUin: number, startSeq: number, endSeq: number): OidbPacket {
const req = new NapProtoMsg(proto.SsoGetGroupMsg).encode({
info: {
groupUin: groupUin,
startSequence: startSeq,
endSequence: endSeq
},
direction: true
});
return {
cmd: 'trpc.msg.register_proxy.RegisterProxy.SsoGetGroupMsg',
data: PacketHexStrBuilder(req)
};
}
parse(data: Buffer) {
return new NapProtoMsg(proto.SsoGetGroupMsgResponse).decode(data);
}
}
export default new FetchGroupMessage();

View File

@@ -1,4 +1,2 @@
export { default as UploadForwardMsg } from './UploadForwardMsg'; export { default as UploadForwardMsg } from './UploadForwardMsg';
export { default as FetchGroupMessage } from './FetchGroupMessage';
export { default as FetchC2CMessage } from './FetchC2CMessage';
export { default as DownloadForwardMsg } from './DownloadForwardMsg'; export { default as DownloadForwardMsg } from './DownloadForwardMsg';

View File

@@ -7,7 +7,7 @@ class OidbBase extends PacketTransformer<typeof proto.OidbSvcTrpcTcpBase> {
super(); super();
} }
build(cmd: number, subCmd: number, body: Uint8Array, isUid: boolean = true, _isLafter: boolean = false): OidbPacket { build(cmd: number, subCmd: number, body: Uint8Array, isUid: boolean = true, isLafter: boolean = false): OidbPacket {
const data = new NapProtoMsg(proto.OidbSvcTrpcTcpBase).encode({ const data = new NapProtoMsg(proto.OidbSvcTrpcTcpBase).encode({
command: cmd, command: cmd,
subCommand: subCmd, subCommand: subCmd,

View File

@@ -13,15 +13,13 @@ import {
export const ContentHead = { export const ContentHead = {
type: ProtoField(1, ScalarType.UINT32), type: ProtoField(1, ScalarType.UINT32),
subType: ProtoField(2, ScalarType.UINT32, true), subType: ProtoField(2, ScalarType.UINT32, true),
c2cCmd: ProtoField(3, ScalarType.UINT32, true), divSeq: ProtoField(3, ScalarType.UINT32, true),
ranDom: ProtoField(4, ScalarType.UINT32, true), msgId: ProtoField(4, ScalarType.UINT32, true),
sequence: ProtoField(5, ScalarType.UINT32, true), sequence: ProtoField(5, ScalarType.UINT32, true),
timeStamp: ProtoField(6, ScalarType.UINT32, true), timeStamp: ProtoField(6, ScalarType.UINT32, true),
pkgNum: ProtoField(7, ScalarType.UINT64, true), field7: ProtoField(7, ScalarType.UINT64, true),
pkgIndex: ProtoField(8, ScalarType.UINT32, true), field8: ProtoField(8, ScalarType.UINT32, true),
divSeq: ProtoField(9, ScalarType.UINT32, true), field9: ProtoField(9, ScalarType.UINT32, true),
autoReply: ProtoField(10, ScalarType.UINT32),
ntMsgSeq: ProtoField(10, ScalarType.UINT32, true),
newId: ProtoField(12, ScalarType.UINT64, true), newId: ProtoField(12, ScalarType.UINT64, true),
forward: ProtoField(15, () => ForwardHead, true), forward: ProtoField(15, () => ForwardHead, true),
}; };

View File

@@ -1,6 +0,0 @@
import { ProtoField, ScalarType } from '@napneko/nap-proto-core';
export const FileId = {
appid: ProtoField(4, ScalarType.UINT32, true),
ttl: ProtoField(10, ScalarType.UINT32, true),
};

View File

@@ -8,22 +8,10 @@ import {
GroupNotifyMsgType, GroupNotifyMsgType,
NTGroupRequestOperateTypes, NTGroupRequestOperateTypes,
KickMemberV2Req, KickMemberV2Req,
GroupDetailInfoV2Param,
GroupExtInfo,
GroupExtFilter,
} from '@/core/types'; } from '@/core/types';
import { GeneralCallResult } from '@/core/services/common'; import { GeneralCallResult } from '@/core/services/common';
export interface NodeIKernelGroupService { export interface NodeIKernelGroupService {
modifyGroupExtInfoV2(groupExtInfo: GroupExtInfo, groupExtFilter: GroupExtFilter): Promise<GeneralCallResult &
{
result: {
groupCode: string,
result: number
}
}>;
// ---> // --->
// 待启用 For Next Version 3.2.0 // 待启用 For Next Version 3.2.0
// isTroopMember ? 0 : 111 // isTroopMember ? 0 : 111
@@ -181,9 +169,6 @@ export interface NodeIKernelGroupService {
modifyGroupDetailInfo(groupCode: string, arg: unknown): void; modifyGroupDetailInfo(groupCode: string, arg: unknown): void;
// 第二个参数在大多数情况为0 设置群成员权限 例如上传群文件权限和群成员付费/加入邀请加入时为8
modifyGroupDetailInfoV2(param: GroupDetailInfoV2Param, arg: number): Promise<GeneralCallResult>;
setGroupMsgMask(groupCode: string, arg: unknown): void; setGroupMsgMask(groupCode: string, arg: unknown): void;
changeGroupShieldSettingTemp(groupCode: string, arg: unknown): void; changeGroupShieldSettingTemp(groupCode: string, arg: unknown): void;
@@ -264,7 +249,7 @@ export interface NodeIKernelGroupService {
reqToJoinGroup(groupCode: string, arg: unknown): void; reqToJoinGroup(groupCode: string, arg: unknown): void;
setGroupShutUp(groupCode: string, shutUp: boolean): Promise<GeneralCallResult>; setGroupShutUp(groupCode: string, shutUp: boolean): void;
getGroupShutUpMemberList(groupCode: string): Promise<GeneralCallResult>; getGroupShutUpMemberList(groupCode: string): Promise<GeneralCallResult>;

View File

@@ -148,11 +148,10 @@ export interface NodeIKernelMsgService {
msgList: RawMessage[] msgList: RawMessage[]
}>; }>;
// getMsgService/getMsgs { chatType: 2, peerUid: '975206796', privilegeFlag: 336068800 } 0 20 true //@deprecated
getMsgs(peer: Peer & { privilegeFlag: number }, msgId: string, count: number, queryOrder: boolean): Promise<GeneralCallResult & { getMsgs(peer: Peer, msgId: string, count: unknown, queryOrder: boolean): Promise<unknown>;
msgList: RawMessage[]
}>;
//@deprecated
getMsgsIncludeSelf(peer: Peer, msgId: string, count: number, queryOrder: boolean): Promise<GeneralCallResult & { getMsgsIncludeSelf(peer: Peer, msgId: string, count: number, queryOrder: boolean): Promise<GeneralCallResult & {
msgList: RawMessage[] msgList: RawMessage[]
}>; }>;

View File

@@ -16,16 +16,6 @@ export * from './NodeIKernelDbToolsService';
export * from './NodeIKernelTipOffService'; export * from './NodeIKernelTipOffService';
export * from './NodeIKernelSearchService'; export * from './NodeIKernelSearchService';
export * from './NodeIKernelCollectionService'; export * from './NodeIKernelCollectionService';
export * from './NodeIKernelAlbumService';
export * from './NodeIKernelECDHService';
export * from './NodeIKernelNodeMiscService';
export * from './NodeIKernelMsgBackupService';
export * from './NodeIKernelTianShuService';
export * from './NodeIKernelUnitedConfigService';
export * from './NodeIkernelTestPerformanceService';
export * from './NodeIKernelUixConvertService';
export * from './NodeIKernelMSFService';
export * from './NodeIKernelRecentContactService';
import type { import type {
NodeIKernelAvatarService, NodeIKernelAvatarService,
@@ -46,19 +36,8 @@ import type {
NodeIKernelTicketService, NodeIKernelTicketService,
NodeIKernelTipOffService, NodeIKernelTipOffService,
} from '.'; } from '.';
import { NodeIKernelAlbumService } from './NodeIKernelAlbumService';
import { NodeIKernelECDHService } from './NodeIKernelECDHService';
import { NodeIKernelNodeMiscService } from './NodeIKernelNodeMiscService';
import { NodeIKernelMsgBackupService } from './NodeIKernelMsgBackupService';
import { NodeIKernelTianShuService } from './NodeIKernelTianShuService';
import { NodeIKernelUnitedConfigService } from './NodeIKernelUnitedConfigService';
import { NodeIkernelTestPerformanceService } from './NodeIkernelTestPerformanceService';
import { NodeIKernelUixConvertService } from './NodeIKernelUixConvertService';
import { NodeIKernelMSFService } from './NodeIKernelMSFService';
import { NodeIKernelRecentContactService } from './NodeIKernelRecentContactService';
export type ServiceNamingMapping = { export type ServiceNamingMapping = {
NodeIKernelAlbumService: NodeIKernelAlbumService;
NodeIKernelAvatarService: NodeIKernelAvatarService; NodeIKernelAvatarService: NodeIKernelAvatarService;
NodeIKernelBuddyService: NodeIKernelBuddyService; NodeIKernelBuddyService: NodeIKernelBuddyService;
NodeIKernelFileAssistantService: NodeIKernelFileAssistantService; NodeIKernelFileAssistantService: NodeIKernelFileAssistantService;
@@ -74,15 +53,6 @@ export type ServiceNamingMapping = {
NodeIKernelRichMediaService: NodeIKernelRichMediaService; NodeIKernelRichMediaService: NodeIKernelRichMediaService;
NodeIKernelDbToolsService: NodeIKernelDbToolsService; NodeIKernelDbToolsService: NodeIKernelDbToolsService;
NodeIKernelTipOffService: NodeIKernelTipOffService; NodeIKernelTipOffService: NodeIKernelTipOffService;
NodeIKernelSearchService: NodeIKernelSearchService; NodeIKernelSearchService: NodeIKernelSearchService,
NodeIKernelCollectionService: NodeIKernelCollectionService; NodeIKernelCollectionService: NodeIKernelCollectionService;
NodeIKernelECDHService: NodeIKernelECDHService;
NodeIKernelNodeMiscService: NodeIKernelNodeMiscService;
NodeIKernelMsgBackupService: NodeIKernelMsgBackupService;
NodeIKernelTianShuService: NodeIKernelTianShuService;
NodeIKernelUnitedConfigService: NodeIKernelUnitedConfigService;
NodeIkernelTestPerformanceService: NodeIkernelTestPerformanceService;
NodeIKernelUixConvertService: NodeIKernelUixConvertService;
NodeIKernelMSFService: NodeIKernelMSFService;
NodeIKernelRecentContactService: NodeIKernelRecentContactService;
}; };

View File

@@ -1,15 +1,4 @@
import { import { ElementType, MessageElement, NTGrayTipElementSubTypeV2, PicSubType, PicType, TipAioOpGrayTipElement, TipGroupElement, NTVideoType, FaceType } from './msg';
ElementType,
MessageElement,
NTGrayTipElementSubTypeV2,
PicSubType,
PicType,
TipAioOpGrayTipElement,
TipGroupElement,
NTVideoType,
FaceType,
Peer
} from './msg';
type ElementFullBase = Omit<MessageElement, 'elementType' | 'elementId' | 'extBufForUI'>; type ElementFullBase = Omit<MessageElement, 'elementType' | 'elementId' | 'extBufForUI'>;
@@ -58,7 +47,6 @@ export interface GrayTipRovokeElement {
operatorUid: string; operatorUid: string;
operatorNick: string; operatorNick: string;
operatorRemark: string; operatorRemark: string;
isSelfOperate: boolean; // 是否是自己撤回的
operatorMemRemark?: string; operatorMemRemark?: string;
wording: string; // 自定义的撤回提示语 wording: string; // 自定义的撤回提示语
} }
@@ -225,9 +213,6 @@ export interface ReplyElement {
senderUidStr?: string; senderUidStr?: string;
replyMsgTime?: string; replyMsgTime?: string;
replyMsgClientSeq?: string; replyMsgClientSeq?: string;
// HACK: Attributes that were not originally available,
// but were added due to NTQQ and NapCat's internal implementation, are used to supplement NapCat
_replyMsgPeer?: Peer;
} }
export interface CalendarElement { export interface CalendarElement {

View File

@@ -1,97 +1,4 @@
import { QQLevel, NTSex } from './user'; import { QQLevel, NTSex } from './user';
export interface GroupExtInfo {
groupCode: string;
resultCode: number;
extInfo: EXTInfo;
}
export interface GroupExtFilter {
groupInfoExtSeq: number;
reserve: number;
luckyWordId: number;
lightCharNum: number;
luckyWord: number;
starId: number;
essentialMsgSwitch: number;
todoSeq: number;
blacklistExpireTime: number;
isLimitGroupRtc: number;
companyId: number;
hasGroupCustomPortrait: number;
bindGuildId: number;
groupOwnerId: number;
essentialMsgPrivilege: number;
msgEventSeq: number;
inviteRobotSwitch: number;
gangUpId: number;
qqMusicMedalSwitch: number;
showPlayTogetherSwitch: number;
groupFlagPro1: number;
groupBindGuildIds: number;
viewedMsgDisappearTime: number;
groupExtFlameData: number;
groupBindGuildSwitch: number;
groupAioBindGuildId: number;
groupExcludeGuildIds: number;
fullGroupExpansionSwitch: number;
fullGroupExpansionSeq: number;
inviteRobotMemberSwitch: number;
inviteRobotMemberExamine: number;
groupSquareSwitch: number;
};
export interface EXTInfo {
groupInfoExtSeq: number;
reserve: number;
luckyWordId: string;
lightCharNum: number;
luckyWord: string;
starId: number;
essentialMsgSwitch: number;
todoSeq: number;
blacklistExpireTime: number;
isLimitGroupRtc: number;
companyId: number;
hasGroupCustomPortrait: number;
bindGuildId: string;
groupOwnerId: GroupOwnerID;
essentialMsgPrivilege: number;
msgEventSeq: string;
inviteRobotSwitch: number;
gangUpId: string;
qqMusicMedalSwitch: number;
showPlayTogetherSwitch: number;
groupFlagPro1: string;
groupBindGuildIds: GroupGuildIDS;
viewedMsgDisappearTime: string;
groupExtFlameData: GroupEXTFlameData;
groupBindGuildSwitch: number;
groupAioBindGuildId: string;
groupExcludeGuildIds: GroupGuildIDS;
fullGroupExpansionSwitch: number;
fullGroupExpansionSeq: string;
inviteRobotMemberSwitch: number;
inviteRobotMemberExamine: number;
groupSquareSwitch: number;
}
export interface GroupGuildIDS {
guildIds: any[];
}
export interface GroupEXTFlameData {
switchState: number;
state: number;
dayNums: any[];
version: number;
updateTime: string;
isDisplayDayNum: boolean;
}
export interface GroupOwnerID {
memberUin: string;
memberUid: string;
memberQid: string;
}
export interface KickMemberInfo { export interface KickMemberInfo {
optFlag: number; optFlag: number;
@@ -100,185 +7,6 @@ export interface KickMemberInfo {
optBytesMsg: string; optBytesMsg: string;
} }
export interface GroupDetailInfoV2Param {
groupCode: string;
filter: Filter;
modifyInfo: ModifyInfo;
}
export interface Filter {
noCodeFingerOpenFlag: number;
noFingerOpenFlag: number;
groupName: number;
classExt: number;
classText: number;
fingerMemo: number;
richFingerMemo: number;
tagRecord: number;
groupGeoInfo: FilterGroupGeoInfo;
groupExtAdminNum: number;
flag: number;
groupMemo: number;
groupAioSkinUrl: number;
groupBoardSkinUrl: number;
groupCoverSkinUrl: number;
groupGrade: number;
activeMemberNum: number;
certificationType: number;
certificationText: number;
groupNewGuideLines: FilterGroupNewGuideLines;
groupFace: number;
addOption: number;
shutUpTime: number;
groupTypeFlag: number;
appPrivilegeFlag: number;
appPrivilegeMask: number;
groupExtOnly: GroupEXTOnly;
groupSecLevel: number;
groupSecLevelInfo: number;
subscriptionUin: number;
subscriptionUid: string;
allowMemberInvite: number;
groupQuestion: number;
groupAnswer: number;
groupFlagExt3: number;
groupFlagExt3Mask: number;
groupOpenAppid: number;
rootId: number;
msgLimitFrequency: number;
hlGuildAppid: number;
hlGuildSubType: number;
hlGuildOrgId: number;
groupFlagExt4: number;
groupFlagExt4Mask: number;
groupSchoolInfo: FilterGroupSchoolInfo;
groupCardPrefix: FilterGroupCardPrefix;
allianceId: number;
groupFlagPro1: number;
groupFlagPro1Mask: number;
}
export interface FilterGroupCardPrefix {
introduction: number;
rptPrefix: number;
}
export interface GroupEXTOnly {
tribeId: number;
moneyForAddGroup: number;
}
export interface FilterGroupGeoInfo {
ownerUid: number;
setTime: number;
cityId: number;
longitude: number;
latitude: number;
geoContent: number;
poiId: number;
}
export interface FilterGroupNewGuideLines {
enabled: number;
content: number;
}
export interface FilterGroupSchoolInfo {
location: number;
grade: number;
school: number;
}
export interface ModifyInfo {
noCodeFingerOpenFlag: number;
noFingerOpenFlag: number;
groupName: string;
classExt: number;
classText: string;
fingerMemo: string;
richFingerMemo: string;
tagRecord: any[];
groupGeoInfo: ModifyInfoGroupGeoInfo;
groupExtAdminNum: number;
flag: number;
groupMemo: string;
groupAioSkinUrl: string;
groupBoardSkinUrl: string;
groupCoverSkinUrl: string;
groupGrade: number;
activeMemberNum: number;
certificationType: number;
certificationText: string;
groupNewGuideLines: ModifyInfoGroupNewGuideLines;
groupFace: number;
addOption: number;// 0 空设置 1 任何人都可以进入 2 需要管理员批准 3 不允许任何人入群 4 问题进入答案 5 问题管理员批准
shutUpTime: number;
groupTypeFlag: number;
appPrivilegeFlag: number;
// 需要管理员审核
// 0000 0000 0000 0000 0000 0000 0000
// 无需审核入群
// 0000 0001 0000 0000 0000 0000 0000
// 成员数100内无审核
// 0100 0000 0000 0000 0000 0000 0000
// 禁用 群成员邀请好友
// 0100 0000 0000 0000 0000 0000 0000
appPrivilegeMask: number;
// 0110 0001 0000 0000 0000 0000 0000
// 101711872
groupExtOnly: GroupEXTOnly;
groupSecLevel: number;
groupSecLevelInfo: number;
subscriptionUin: string;
subscriptionUid: string;
allowMemberInvite: number;
groupQuestion: string;
groupAnswer: string;
groupFlagExt3: number;
groupFlagExt3Mask: number;
groupOpenAppid: number;
rootId: string;
msgLimitFrequency: number;
hlGuildAppid: number;
hlGuildSubType: number;
hlGuildOrgId: number;
groupFlagExt4: number;
groupFlagExt4Mask: number;
groupSchoolInfo: ModifyInfoGroupSchoolInfo;
groupCardPrefix: ModifyInfoGroupCardPrefix;
allianceId: string;
groupFlagPro1: number;
groupFlagPro1Mask: number;
}
export interface ModifyInfoGroupCardPrefix {
introduction: string;
rptPrefix: any[];
}
export interface ModifyInfoGroupGeoInfo {
ownerUid: string;
SetTime: number;
CityId: number;
Longitude: string;
Latitude: string;
GeoContent: string;
poiId: string;
}
export interface ModifyInfoGroupNewGuideLines {
enabled: boolean;
content: string;
}
export interface ModifyInfoGroupSchoolInfo {
location: string;
grade: number;
school: string;
}
// 获取群详细信息的来源类型 // 获取群详细信息的来源类型
export enum GroupInfoSource { export enum GroupInfoSource {
KUNSPECIFIED, KUNSPECIFIED,

View File

@@ -501,15 +501,13 @@ export interface RawMessage {
elements: MessageElement[];// 消息元素 elements: MessageElement[];// 消息元素
sourceType: MsgSourceType;// 消息来源类型 sourceType: MsgSourceType;// 消息来源类型
isOnlineMsg: boolean;// 是否为在线消息 isOnlineMsg: boolean;// 是否为在线消息
clientSeq?: string;
} }
/** /**
* 查询消息参数接口 * 查询消息参数接口
*/ */
export interface QueryMsgsParams { export interface QueryMsgsParams {
chatInfo: Peer & { privilegeFlag?: number }; chatInfo: Peer;
//searchFields: number;
filterMsgType: Array<{ type: NTMsgType, subType: Array<number> }>; filterMsgType: Array<{ type: NTMsgType, subType: Array<number> }>;
filterSendersUid: string[]; filterSendersUid: string[];
filterMsgFromTime: string; filterMsgFromTime: string;

View File

@@ -132,26 +132,18 @@ export enum BuddyReqType {
KMEINITIATORWAITPEERCONFIRM = 13 KMEINITIATORWAITPEERCONFIRM = 13
} }
// 其中 ? 代表新版本参数
export interface FriendRequest { export interface FriendRequest {
isBuddy?: boolean;
isInitiator?: boolean; isInitiator?: boolean;
isDecide: boolean; isDecide: boolean;
friendUid: string; friendUid: string;
reqType: BuddyReqType, reqType: BuddyReqType,
reqTime: string; // 时间戳 秒 reqTime: string; // 时间戳 秒
flag?: number; // 0
preGroupingId?: number; // 0
commFriendNum?: number; // 共同好友数
extWords: string; // 申请人填写的验证消息 extWords: string; // 申请人填写的验证消息
isUnread: boolean; isUnread: boolean;
isDoubt?: boolean; // 是否是可疑的好友请求
nameMore?: string;
friendNick: string; friendNick: string;
sourceId: number; sourceId: number;
groupCode: string; groupCode: string
isBuddy?: boolean;
isAgreed?: boolean;
relation?: number;
} }
export interface FriendRequestNotify { export interface FriendRequestNotify {

View File

@@ -48,12 +48,6 @@ export async function NCoreInitFramework(
}); });
} }
//直到登录成功后,执行下一步 //直到登录成功后,执行下一步
// const selfInfo = {
// uid: 'u_FUSS0_x06S_9Tf4na_WpUg',
// uin: '3684714082',
// nick: '',
// online: true
// }
const selfInfo = await new Promise<SelfInfo>((resolveSelfInfo) => { const selfInfo = await new Promise<SelfInfo>((resolveSelfInfo) => {
const loginListener = new NodeIKernelLoginListener(); const loginListener = new NodeIKernelLoginListener();
loginListener.onQRCodeLoginSucceed = async (loginResult) => { loginListener.onQRCodeLoginSucceed = async (loginResult) => {

View File

@@ -1,26 +0,0 @@
const fs = require('fs');
const path = require('path');
async function initializeNapCat(session, loginService, registerCallback) {
//const logFile = path.join(currentPath, 'napcat.log');
console.log('[NapCat] [Info] 开始初始化NapCat');
//fs.writeFileSync(logFile, '', { flag: 'w' });
//fs.writeFileSync(logFile, '[NapCat] [Info] NapCat 初始化成功\n', { flag: 'a' });
try {
const currentPath = path.dirname(__filename);
const { NCoreInitFramework } = await import('file://' + path.join(currentPath, './napcat.mjs'));
await NCoreInitFramework(session, loginService, (callback) => { registerCallback(callback) });
} catch (error) {
console.log('[NapCat] [Error] 初始化NapCat', error);
//fs.writeFileSync(logFile, `[NapCat] [Error] 初始化NapCat失败: ${error.message}\n`, { flag: 'a' });
}
}
module.exports = {
initializeNapCat: initializeNapCat
};

View File

@@ -1,426 +0,0 @@
import * as fs from 'fs';
import { ReadStream } from 'fs';
export interface ImageSize {
width: number;
height: number;
}
export enum ImageType {
JPEG = 'jpeg',
PNG = 'png',
BMP = 'bmp',
GIF = 'gif',
WEBP = 'webp',
UNKNOWN = 'unknown',
}
interface ImageParser {
readonly type: ImageType;
canParse(buffer: Buffer): boolean;
parseSize(stream: ReadStream): Promise<ImageSize | undefined>;
}
// 魔术匹配
function matchMagic(buffer: Buffer, magic: number[], offset = 0): boolean {
if (buffer.length < offset + magic.length) {
return false;
}
for (let i = 0; i < magic.length; i++) {
if (buffer[offset + i] !== magic[i]) {
return false;
}
}
return true;
}
// PNG解析器
class PngParser implements ImageParser {
readonly type = ImageType.PNG;
// PNG 魔术头89 50 4E 47 0D 0A 1A 0A
private readonly PNG_SIGNATURE = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
canParse(buffer: Buffer): boolean {
return matchMagic(buffer, this.PNG_SIGNATURE);
}
async parseSize(stream: ReadStream): Promise<ImageSize | undefined> {
return new Promise((resolve, reject) => {
stream.once('error', reject);
stream.once('readable', () => {
const buf = stream.read(24) as Buffer;
if (!buf || buf.length < 24) {
return resolve(undefined);
}
if (this.canParse(buf)) {
const width = buf.readUInt32BE(16);
const height = buf.readUInt32BE(20);
resolve({ width, height });
} else {
resolve(undefined);
}
});
});
}
}
// JPEG解析器
class JpegParser implements ImageParser {
readonly type = ImageType.JPEG;
// JPEG 魔术头FF D8
private readonly JPEG_SIGNATURE = [0xFF, 0xD8];
// JPEG标记常量
private readonly SOF_MARKERS = {
SOF0: 0xC0, // 基线DCT
SOF1: 0xC1, // 扩展顺序DCT
SOF2: 0xC2, // 渐进式DCT
SOF3: 0xC3, // 无损
} as const;
// 非SOF标记
private readonly NON_SOF_MARKERS: number[] = [
0xC4, // DHT
0xC8, // JPEG扩展
0xCC, // DAC
] as const;
canParse(buffer: Buffer): boolean {
return matchMagic(buffer, this.JPEG_SIGNATURE);
}
isSOFMarker(marker: number): boolean {
return (
marker === this.SOF_MARKERS.SOF0 ||
marker === this.SOF_MARKERS.SOF1 ||
marker === this.SOF_MARKERS.SOF2 ||
marker === this.SOF_MARKERS.SOF3
);
}
isNonSOFMarker(marker: number): boolean {
return this.NON_SOF_MARKERS.includes(marker);
}
async parseSize(stream: ReadStream): Promise<ImageSize | undefined> {
return new Promise<ImageSize | undefined>((resolve, reject) => {
const BUFFER_SIZE = 1024; // 读取块大小,可以根据需要调整
let buffer = Buffer.alloc(0);
let offset = 0;
let found = false;
// 处理错误
stream.on('error', (err) => {
stream.destroy();
reject(err);
});
// 处理数据块
stream.on('data', (chunk: Buffer | string) => {
// 追加新数据到缓冲区
const chunkBuffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
buffer = Buffer.concat([buffer.subarray(offset), chunkBuffer]);
offset = 0;
// 保持缓冲区在合理大小内,只保留最后的部分用于跨块匹配
const bufferSize = buffer.length;
const MIN_REQUIRED_BYTES = 10; // SOF段最低字节数
// 从JPEG头部后开始扫描
while (offset < bufferSize - MIN_REQUIRED_BYTES) {
// 寻找FF标记
if (buffer[offset] === 0xFF && buffer[offset + 1]! >= 0xC0 && buffer[offset + 1]! <= 0xCF) {
const marker = buffer[offset + 1];
if (!marker) {
break;
}
// 跳过非SOF标记
if (this.isNonSOFMarker(marker)) {
offset += 2;
continue;
}
// 处理SOF标记 (包含尺寸信息)
if (this.isSOFMarker(marker)) {
// 确保缓冲区中有足够数据读取尺寸
if (offset + 9 < bufferSize) {
// 解析尺寸: FF XX YY YY PP HH HH WW WW ...
// XX = 标记, YY YY = 段长度, PP = 精度, HH HH = 高, WW WW = 宽
const height = buffer.readUInt16BE(offset + 5);
const width = buffer.readUInt16BE(offset + 7);
found = true;
stream.destroy();
resolve({ width, height });
return;
} else {
// 如果缓冲区内数据不够,保留当前位置等待更多数据
break;
}
}
}
offset++;
}
// 缓冲区管理: 如果处理了许多数据但没找到标记,
// 保留最后N字节用于跨块匹配丢弃之前的数据
if (offset > BUFFER_SIZE) {
const KEEP_BYTES = 20; // 保留足够数据以处理跨块边界的情况
if (offset > KEEP_BYTES) {
buffer = buffer.subarray(offset - KEEP_BYTES);
offset = KEEP_BYTES;
}
}
});
// 处理流结束
stream.on('end', () => {
if (!found) {
resolve(undefined);
}
});
});
}
}
// BMP解析器
class BmpParser implements ImageParser {
readonly type = ImageType.BMP;
// BMP 魔术头42 4D (BM)
private readonly BMP_SIGNATURE = [0x42, 0x4D];
canParse(buffer: Buffer): boolean {
return matchMagic(buffer, this.BMP_SIGNATURE);
}
async parseSize(stream: ReadStream): Promise<ImageSize | undefined> {
return new Promise((resolve, reject) => {
stream.once('error', reject);
stream.once('readable', () => {
const buf = stream.read(26) as Buffer;
if (!buf || buf.length < 26) {
return resolve(undefined);
}
if (this.canParse(buf)) {
const width = buf.readUInt32LE(18);
const height = buf.readUInt32LE(22);
resolve({ width, height });
} else {
resolve(undefined);
}
});
});
}
}
// GIF解析器
class GifParser implements ImageParser {
readonly type = ImageType.GIF;
// GIF87a 魔术头47 49 46 38 37 61
private readonly GIF87A_SIGNATURE = [0x47, 0x49, 0x46, 0x38, 0x37, 0x61];
// GIF89a 魔术头47 49 46 38 39 61
private readonly GIF89A_SIGNATURE = [0x47, 0x49, 0x46, 0x38, 0x39, 0x61];
canParse(buffer: Buffer): boolean {
return (
matchMagic(buffer, this.GIF87A_SIGNATURE) ||
matchMagic(buffer, this.GIF89A_SIGNATURE)
);
}
async parseSize(stream: ReadStream): Promise<ImageSize | undefined> {
return new Promise((resolve, reject) => {
stream.once('error', reject);
stream.once('readable', () => {
const buf = stream.read(10) as Buffer;
if (!buf || buf.length < 10) {
return resolve(undefined);
}
if (this.canParse(buf)) {
const width = buf.readUInt16LE(6);
const height = buf.readUInt16LE(8);
resolve({ width, height });
} else {
resolve(undefined);
}
});
});
}
}
// WEBP解析器 - 完整支持VP8, VP8L, VP8X格式
class WebpParser implements ImageParser {
readonly type = ImageType.WEBP;
// WEBP RIFF 头52 49 46 46 (RIFF)
private readonly RIFF_SIGNATURE = [0x52, 0x49, 0x46, 0x46];
// WEBP 魔术头57 45 42 50 (WEBP)
private readonly WEBP_SIGNATURE = [0x57, 0x45, 0x42, 0x50];
// WEBP 块头
private readonly CHUNK_VP8 = [0x56, 0x50, 0x38, 0x20]; // "VP8 "
private readonly CHUNK_VP8L = [0x56, 0x50, 0x38, 0x4C]; // "VP8L"
private readonly CHUNK_VP8X = [0x56, 0x50, 0x38, 0x58]; // "VP8X"
canParse(buffer: Buffer): boolean {
return (
buffer.length >= 12 &&
matchMagic(buffer, this.RIFF_SIGNATURE, 0) &&
matchMagic(buffer, this.WEBP_SIGNATURE, 8)
);
}
isChunkType(buffer: Buffer, offset: number, chunkType: number[]): boolean {
return buffer.length >= offset + 4 && matchMagic(buffer, chunkType, offset);
}
async parseSize(stream: ReadStream): Promise<ImageSize | undefined> {
return new Promise((resolve, reject) => {
// 需要读取足够的字节来检测所有三种格式
const MAX_HEADER_SIZE = 32;
let totalBytes = 0;
let buffer = Buffer.alloc(0);
stream.on('error', reject);
stream.on('data', (chunk: Buffer | string) => {
const chunkBuffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
buffer = Buffer.concat([buffer, chunkBuffer]);
totalBytes += chunk.length;
// 检查是否有足够的字节进行格式检测
if (totalBytes >= MAX_HEADER_SIZE) {
stream.destroy();
// 检查基本的WEBP签名
if (!this.canParse(buffer)) {
return resolve(undefined);
}
// 检查chunk头部位于字节12-15
if (this.isChunkType(buffer, 12, this.CHUNK_VP8)) {
// VP8格式 - 标准WebP
// 宽度和高度在帧头中
const width = buffer.readUInt16LE(26) & 0x3FFF;
const height = buffer.readUInt16LE(28) & 0x3FFF;
return resolve({ width, height });
} else if (this.isChunkType(buffer, 12, this.CHUNK_VP8L)) {
// VP8L格式 - 无损WebP
// 1字节标记后是14位宽度和14位高度
const bits = buffer.readUInt32LE(21);
const width = 1 + (bits & 0x3FFF);
const height = 1 + ((bits >> 14) & 0x3FFF);
return resolve({ width, height });
} else if (this.isChunkType(buffer, 12, this.CHUNK_VP8X)) {
// VP8X格式 - 扩展WebP
// 24位宽度和高度(减去1)
if (!buffer[24] || !buffer[25] || !buffer[26] || !buffer[27] || !buffer[28] || !buffer[29]) {
return resolve(undefined);
}
const width = 1 + ((buffer[24] | (buffer[25] << 8) | (buffer[26] << 16)) & 0xFFFFFF);
const height = 1 + ((buffer[27] | (buffer[28] << 8) | (buffer[29] << 16)) & 0xFFFFFF);
return resolve({ width, height });
} else {
// 未知的WebP子格式
return resolve(undefined);
}
}
});
stream.on('end', () => {
// 如果没有读到足够的字节
if (totalBytes < MAX_HEADER_SIZE) {
resolve(undefined);
}
});
});
}
}
const parsers: ReadonlyArray<ImageParser> = [
new PngParser(),
new JpegParser(),
new BmpParser(),
new GifParser(),
new WebpParser(),
];
export async function detectImageType(filePath: string): Promise<ImageType> {
return new Promise((resolve, reject) => {
const stream = fs.createReadStream(filePath, {
highWaterMark: 64, // 优化读取buffer大小
start: 0,
end: 63
});
let buffer: Buffer | null = null;
stream.once('error', (err) => {
stream.destroy();
reject(err);
});
stream.once('readable', () => {
buffer = stream.read(64) as Buffer;
stream.destroy();
if (!buffer) {
return resolve(ImageType.UNKNOWN);
}
for (const parser of parsers) {
if (parser.canParse(buffer)) {
return resolve(parser.type);
}
}
resolve(ImageType.UNKNOWN);
});
stream.once('end', () => {
if (!buffer) {
resolve(ImageType.UNKNOWN);
}
});
});
}
export async function imageSizeFromFile(filePath: string): Promise<ImageSize | undefined> {
try {
// 先检测类型
const type = await detectImageType(filePath);
const parser = parsers.find(p => p.type === type);
if (!parser) {
return undefined;
}
// 用流式方式解析尺寸
const stream = fs.createReadStream(filePath);
try {
return await parser.parseSize(stream);
} catch (err) {
console.error(`解析图片尺寸出错: ${err}`);
return undefined;
} finally {
if (!stream.destroyed) {
stream.destroy();
}
}
} catch (err) {
console.error(`检测图片类型出错: ${err}`);
return undefined;
}
}
export async function imageSizeFallBack(
filePath: string,
fallback: ImageSize = {
width: 1024,
height: 1024,
}
): Promise<ImageSize> {
return await imageSizeFromFile(filePath) ?? fallback;
}

View File

@@ -1,28 +0,0 @@
import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '@/onebot/action/router';
import { Static, Type } from '@sinclair/typebox';
const SchemaData = Type.Object({
group_id: Type.String(),
add_type: Type.Number(),
group_question: Type.Optional(Type.String()),
group_answer: Type.Optional(Type.String()),
});
type Payload = Static<typeof SchemaData>;
export default class SetGroupAddOption extends OneBotAction<Payload, null> {
override actionName = ActionName.SetGroupAddOption;
override payloadSchema = SchemaData;
async _handle(payload: Payload): Promise<null> {
let ret = await this.core.apis.GroupApi.setGroupAddOption(payload.group_id, {
addOption: payload.add_type,
groupQuestion: payload.group_question,
groupAnswer: payload.group_answer,
});
if (ret.result != 0) {
throw new Error(`设置群添加选项失败, ${ret.result}:${ret.errMsg}`);
}
return null;
}
}

View File

@@ -1,23 +0,0 @@
import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '@/onebot/action/router';
import { Static, Type } from '@sinclair/typebox';
const SchemaData = Type.Object({
group_id: Type.String(),
user_id: Type.Array(Type.String()),
reject_add_request: Type.Optional(Type.Union([Type.Boolean(), Type.String()])),
});
type Payload = Static<typeof SchemaData>;
export default class SetGroupKickMembers extends OneBotAction<Payload, null> {
override actionName = ActionName.SetGroupKickMembers;
override payloadSchema = SchemaData;
async _handle(payload: Payload): Promise<null> {
const rejectReq = payload.reject_add_request?.toString() == 'true';
const uids: string[] = await Promise.all(payload.user_id.map(async uin => await this.core.apis.UserApi.getUidByUinV2(uin)));
await this.core.apis.GroupApi.kickMember(payload.group_id.toString(), uids.filter(uid => !!uid), rejectReq);
return null;
}
}

View File

@@ -1,27 +0,0 @@
import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '@/onebot/action/router';
import { Static, Type } from '@sinclair/typebox';
const SchemaData = Type.Object({
group_id: Type.String(),
robot_member_switch: Type.Optional(Type.Number()),
robot_member_examine: Type.Optional(Type.Number()),
});
type Payload = Static<typeof SchemaData>;
export default class SetGroupRobotAddOption extends OneBotAction<Payload, null> {
override actionName = ActionName.SetGroupRobotAddOption;
override payloadSchema = SchemaData;
async _handle(payload: Payload): Promise<null> {
let ret = await this.core.apis.GroupApi.setGroupRobotAddOption(
payload.group_id,
payload.robot_member_switch,
payload.robot_member_examine,
);
if (ret.result != 0) {
throw new Error(`设置群机器人添加选项失败, ${ret.result}:${ret.errMsg}`);
}
return null;
}
}

View File

@@ -1,26 +0,0 @@
import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '@/onebot/action/router';
import { Static, Type } from '@sinclair/typebox';
const SchemaData = Type.Object({
group_id: Type.String(),
no_code_finger_open: Type.Optional(Type.Number()),
no_finger_open: Type.Optional(Type.Number()),
});
type Payload = Static<typeof SchemaData>;
export default class SetGroupSearch extends OneBotAction<Payload, null> {
override actionName = ActionName.SetGroupSearch;
override payloadSchema = SchemaData;
async _handle(payload: Payload): Promise<null> {
let ret = await this.core.apis.GroupApi.setGroupSearch(payload.group_id, {
noCodeFingerOpenFlag: payload.no_code_finger_open,
noFingerOpenFlag: payload.no_finger_open,
});
if (ret.result != 0) {
throw new Error(`设置群搜索失败, ${ret.result}:${ret.errMsg}`);
}
return null;
}
}

View File

@@ -4,10 +4,7 @@ import { Static, Type } from '@sinclair/typebox';
const SchemaData = Type.Object({ const SchemaData = Type.Object({
group_id: Type.Union([Type.Number(), Type.String()]), group_id: Type.Union([Type.Number(), Type.String()]),
// 兼容gocq 与name二选一 folder_name: Type.String(),
folder_name: Type.Optional(Type.String()),
// 兼容gocq 与folder_name二选一
name: Type.Optional(Type.String()),
}); });
type Payload = Static<typeof SchemaData>; type Payload = Static<typeof SchemaData>;
@@ -19,7 +16,6 @@ export class CreateGroupFileFolder extends OneBotAction<Payload, ResponseType>
override actionName = ActionName.GoCQHTTP_CreateGroupFileFolder; override actionName = ActionName.GoCQHTTP_CreateGroupFileFolder;
override payloadSchema = SchemaData; override payloadSchema = SchemaData;
async _handle(payload: Payload) { async _handle(payload: Payload) {
const folderName = payload.folder_name || payload.name; return (await this.core.apis.GroupApi.creatGroupFileFolder(payload.group_id.toString(), payload.folder_name)).resultWithGroupItem;
return (await this.core.apis.GroupApi.creatGroupFileFolder(payload.group_id.toString(), folderName!)).resultWithGroupItem;
} }
} }

View File

@@ -1,4 +1,4 @@
import { ContextMode, normalize, ReturnDataType, SendMsgBase } from '@/onebot/action/msg/SendMsg'; import { normalize, SendMsgBase } from '@/onebot/action/msg/SendMsg';
import { OB11PostSendMsg } from '@/onebot/types'; import { OB11PostSendMsg } from '@/onebot/types';
import { ActionName } from '@/onebot/action/router'; import { ActionName } from '@/onebot/action/router';
@@ -19,14 +19,8 @@ export class GoCQHTTPSendForwardMsg extends GoCQHTTPSendForwardMsgBase {
} }
export class GoCQHTTPSendPrivateForwardMsg extends GoCQHTTPSendForwardMsgBase { export class GoCQHTTPSendPrivateForwardMsg extends GoCQHTTPSendForwardMsgBase {
override actionName = ActionName.GoCQHTTP_SendPrivateForwardMsg; override actionName = ActionName.GoCQHTTP_SendPrivateForwardMsg;
override async _handle(payload: OB11PostSendMsg): Promise<ReturnDataType> {
return this.base_handle(payload, ContextMode.Private);
}
} }
export class GoCQHTTPSendGroupForwardMsg extends GoCQHTTPSendForwardMsgBase { export class GoCQHTTPSendGroupForwardMsg extends GoCQHTTPSendForwardMsgBase {
override actionName = ActionName.GoCQHTTP_SendGroupForwardMsg; override actionName = ActionName.GoCQHTTP_SendGroupForwardMsg;
override async _handle(payload: OB11PostSendMsg): Promise<ReturnDataType> {
return this.base_handle(payload, ContextMode.Group);
}
} }

View File

@@ -1,27 +0,0 @@
import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '@/onebot/action/router';
import { Static, Type } from '@sinclair/typebox';
const SchemaData = Type.Object({
group_id: Type.Union([Type.Number(), Type.String()]),
});
type Payload = Static<typeof SchemaData>;
export class GetGroupDetailInfo extends OneBotAction<Payload, unknown> {
override actionName = ActionName.GetGroupDetailInfo;
override payloadSchema = SchemaData;
async _handle(payload: Payload) {
const data = await this.core.apis.GroupApi.fetchGroupDetail(payload.group_id.toString());
return {
...data,
group_all_shut: data.shutUpAllTimestamp > 0 ? -1 : 0,
group_remark: '',
group_id: +payload.group_id,
group_name: data.groupName,
member_count: data.memberNum,
max_member_count: data.maxMemberNum,
};
}
}

View File

@@ -4,7 +4,6 @@ import { ActionName } from '@/onebot/action/router';
import { Notify } from '@/onebot/types'; import { Notify } from '@/onebot/types';
interface RetData { interface RetData {
invited_requests: Notify[];
InvitedRequest: Notify[]; InvitedRequest: Notify[];
join_requests: Notify[]; join_requests: Notify[];
} }
@@ -14,7 +13,7 @@ export class GetGroupIgnoredNotifies extends OneBotAction<void, RetData> {
async _handle(): Promise<RetData> { async _handle(): Promise<RetData> {
const SingleScreenNotifies = await this.core.apis.GroupApi.getSingleScreenNotifies(false, 50); const SingleScreenNotifies = await this.core.apis.GroupApi.getSingleScreenNotifies(false, 50);
const retData: RetData = { invited_requests: [], InvitedRequest: [], join_requests: [] }; const retData: RetData = { InvitedRequest: [], join_requests: [] };
const notifyPromises = SingleScreenNotifies.map(async (SSNotify) => { const notifyPromises = SingleScreenNotifies.map(async (SSNotify) => {
const invitorUin = SSNotify.user1?.uid ? +await this.core.apis.UserApi.getUinByUidV2(SSNotify.user1.uid) : 0; const invitorUin = SSNotify.user1?.uid ? +await this.core.apis.UserApi.getUinByUidV2(SSNotify.user1.uid) : 0;
@@ -39,7 +38,7 @@ export class GetGroupIgnoredNotifies extends OneBotAction<void, RetData> {
}); });
await Promise.all(notifyPromises); await Promise.all(notifyPromises);
retData.invited_requests = retData.InvitedRequest;
return retData; return retData;
} }
} }

View File

@@ -8,16 +8,10 @@ interface GroupNotice {
notice_id: string; notice_id: string;
message: { message: {
text: string text: string
// 保持一段时间兼容性 防止以往版本出现问题 后续版本可考虑移除
image: Array<{ image: Array<{
height: string height: string
width: string width: string
id: string id: string
}>,
images: Array<{
height: string
width: string
id: string
}> }>
}; };
} }
@@ -46,18 +40,15 @@ export class GetGroupNotice extends OneBotAction<Payload, GroupNotice[]> {
continue; continue;
} }
const retApiNotice: WebApiGroupNoticeFeed = ret.feeds[key]; const retApiNotice: WebApiGroupNoticeFeed = ret.feeds[key];
const image = retApiNotice.msg.pics?.map((pic) => {
return { id: pic.id, height: pic.h, width: pic.w };
}) || [];
const retNotice: GroupNotice = { const retNotice: GroupNotice = {
notice_id: retApiNotice.fid, notice_id: retApiNotice.fid,
sender_id: retApiNotice.u, sender_id: retApiNotice.u,
publish_time: retApiNotice.pubt, publish_time: retApiNotice.pubt,
message: { message: {
text: retApiNotice.msg.text, text: retApiNotice.msg.text,
image, image: retApiNotice.msg.pics?.map((pic) => {
images: image, return { id: pic.id, height: pic.h, width: pic.w };
}) || [],
}, },
}; };
retNotices.push(retNotice); retNotices.push(retNotice);

View File

@@ -0,0 +1,19 @@
import { ActionName } from '@/onebot/action/router';
import { GetPacketStatusDepends } from '@/onebot/action/packet/GetPacketStatus';
import { Static, Type } from '@sinclair/typebox';
const SchemaData = Type.Object({
group_id: Type.Union([Type.Number(), Type.String()]),
user_id: Type.Union([Type.Number(), Type.String()]),
});
type Payload = Static<typeof SchemaData>;
export class GroupPoke extends GetPacketStatusDepends<Payload, void> {
override actionName = ActionName.GroupPoke;
override payloadSchema = SchemaData;
async _handle(payload: Payload) {
await this.core.apis.PacketApi.pkt.operation.GroupPoke(+payload.group_id, +payload.user_id);
}
}

View File

@@ -1,19 +1,17 @@
import { ContextMode, ReturnDataType, SendMsgBase } from '@/onebot/action/msg/SendMsg'; import { ContextMode, SendMsgBase } from '@/onebot/action/msg/SendMsg';
import { ActionName, BaseCheckResult } from '@/onebot/action/router'; import { ActionName, BaseCheckResult } from '@/onebot/action/router';
import { OB11PostSendMsg } from '@/onebot/types'; import { OB11PostSendMsg } from '@/onebot/types';
// 未检测参数 // 未检测参数
class SendGroupMsg extends SendMsgBase { class SendGroupMsg extends SendMsgBase {
override actionName = ActionName.SendGroupMsg; override actionName = ActionName.SendGroupMsg;
override contextMode: ContextMode = ContextMode.Group;
protected override async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> { protected override async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> {
delete payload.user_id; delete payload.user_id;
payload.message_type = 'group'; payload.message_type = 'group';
return super.check(payload); return super.check(payload);
} }
override async _handle(payload: OB11PostSendMsg): Promise<ReturnDataType> {
return this.base_handle(payload, ContextMode.Group);
}
} }
export default SendGroupMsg; export default SendGroupMsg;

View File

@@ -15,10 +15,7 @@ export default class SetGroupWholeBan extends OneBotAction<Payload, null> {
async _handle(payload: Payload): Promise<null> { async _handle(payload: Payload): Promise<null> {
const enable = payload.enable?.toString() !== 'false'; const enable = payload.enable?.toString() !== 'false';
let res = await this.core.apis.GroupApi.banGroup(payload.group_id.toString(), enable); await this.core.apis.GroupApi.banGroup(payload.group_id.toString(), enable);
if (res.result !== 0) {
throw new Error(`SetGroupWholeBan failed: ${res.errMsg} ${res.result}`);
}
return null; return null;
} }
} }

View File

@@ -78,6 +78,7 @@ import { GetGroupFileSystemInfo } from '@/onebot/action/go-cqhttp/GetGroupFileSy
import { GetGroupRootFiles } from '@/onebot/action/go-cqhttp/GetGroupRootFiles'; import { GetGroupRootFiles } from '@/onebot/action/go-cqhttp/GetGroupRootFiles';
import { GetGroupFilesByFolder } from '@/onebot/action/go-cqhttp/GetGroupFilesByFolder'; import { GetGroupFilesByFolder } from '@/onebot/action/go-cqhttp/GetGroupFilesByFolder';
import { GetGroupSystemMsg } from './system/GetSystemMsg'; import { GetGroupSystemMsg } from './system/GetSystemMsg';
import { GroupPoke } from './group/GroupPoke';
import { GetUserStatus } from './extends/GetUserStatus'; import { GetUserStatus } from './extends/GetUserStatus';
import { GetRkey } from './extends/GetRkey'; import { GetRkey } from './extends/GetRkey';
import { SetSpecialTitle } from './extends/SetSpecialTitle'; import { SetSpecialTitle } from './extends/SetSpecialTitle';
@@ -85,6 +86,7 @@ import { GetGroupShutList } from './group/GetGroupShutList';
import { GetGroupMemberList } from './group/GetGroupMemberList'; import { GetGroupMemberList } from './group/GetGroupMemberList';
import { GetGroupFileUrl } from '@/onebot/action/file/GetGroupFileUrl'; import { GetGroupFileUrl } from '@/onebot/action/file/GetGroupFileUrl';
import { GetPacketStatus } from '@/onebot/action/packet/GetPacketStatus'; import { GetPacketStatus } from '@/onebot/action/packet/GetPacketStatus';
import { FriendPoke } from '@/onebot/action/user/FriendPoke';
import { GetCredentials } from './system/GetCredentials'; import { GetCredentials } from './system/GetCredentials';
import { SendGroupSign, SetGroupSign } from './extends/SetGroupSign'; import { SendGroupSign, SetGroupSign } from './extends/SetGroupSign';
import { GoCQHTTPGetGroupAtAllRemain } from './go-cqhttp/GetGroupAtAllRemain'; import { GoCQHTTPGetGroupAtAllRemain } from './go-cqhttp/GetGroupAtAllRemain';
@@ -100,7 +102,7 @@ import { GetGuildList } from './guild/GetGuildList';
import { GetGuildProfile } from './guild/GetGuildProfile'; import { GetGuildProfile } from './guild/GetGuildProfile';
import { GetClientkey } from './extends/GetClientkey'; import { GetClientkey } from './extends/GetClientkey';
import { SendPacket } from './extends/SendPacket'; import { SendPacket } from './extends/SendPacket';
import { FriendPoke, GroupPoke, SendPoke } from '@/onebot/action/packet/SendPoke'; import { SendPoke } from '@/onebot/action/packet/SendPoke';
import { SetDiyOnlineStatus } from './extends/SetDiyOnlineStatus'; import { SetDiyOnlineStatus } from './extends/SetDiyOnlineStatus';
import { BotExit } from './extends/BotExit'; import { BotExit } from './extends/BotExit';
import { ClickInlineKeyboardButton } from './extends/ClickInlineKeyboardButton'; import { ClickInlineKeyboardButton } from './extends/ClickInlineKeyboardButton';
@@ -116,22 +118,10 @@ import { CleanCache } from './system/CleanCache';
import SetFriendRemark from './user/SetFriendRemark'; import SetFriendRemark from './user/SetFriendRemark';
import { SetDoubtFriendsAddRequest } from './new/SetDoubtFriendsAddRequest'; import { SetDoubtFriendsAddRequest } from './new/SetDoubtFriendsAddRequest';
import { GetDoubtFriendsAddRequest } from './new/GetDoubtFriendsAddRequest'; import { GetDoubtFriendsAddRequest } from './new/GetDoubtFriendsAddRequest';
import SetGroupAddOption from './extends/SetGroupAddOption';
import SetGroupSearch from './extends/SetGroupSearch';
import SetGroupRobotAddOption from './extends/SetGroupRobotAddOption';
import SetGroupKickMembers from './extends/SetGroupKickMembers';
import { GetGroupDetailInfo } from './group/GetGroupDetailInfo';
import GetGroupAddRequest from './extends/GetGroupAddRequest';
import { GetCollectionList } from './extends/GetCollectionList';
export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCore) { export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCore) {
const actionHandlers = [ const actionHandlers = [
new GetGroupDetailInfo(obContext, core),
new SetGroupKickMembers(obContext, core),
new SetGroupAddOption(obContext, core),
new SetGroupRobotAddOption(obContext, core),
new SetGroupSearch(obContext, core),
new SetDoubtFriendsAddRequest(obContext, core), new SetDoubtFriendsAddRequest(obContext, core),
new GetDoubtFriendsAddRequest(obContext, core), new GetDoubtFriendsAddRequest(obContext, core),
new SetFriendRemark(obContext, core), new SetFriendRemark(obContext, core),
@@ -259,8 +249,6 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
new GetPrivateFileUrl(obContext, core), new GetPrivateFileUrl(obContext, core),
new GetUnidirectionalFriendList(obContext, core), new GetUnidirectionalFriendList(obContext, core),
new CleanCache(obContext, core), new CleanCache(obContext, core),
new GetGroupAddRequest(obContext, core),
new GetCollectionList(obContext, core),
]; ];
type HandlerUnion = typeof actionHandlers[number]; type HandlerUnion = typeof actionHandlers[number];

View File

@@ -19,7 +19,6 @@ import { rawMsgWithSendMsg } from '@/core/packet/message/converter';
export interface ReturnDataType { export interface ReturnDataType {
message_id: number; message_id: number;
res_id?: string; res_id?: string;
forward_id?: string;
} }
export enum ContextMode { export enum ContextMode {
@@ -105,6 +104,8 @@ function getSpecialMsgNum(payload: OB11PostSendMsg, msgType: OB11MessageDataType
} }
export class SendMsgBase extends OneBotAction<OB11PostSendMsg, ReturnDataType> { export class SendMsgBase extends OneBotAction<OB11PostSendMsg, ReturnDataType> {
contextMode = ContextMode.Normal;
protected override async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> { protected override async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> {
const messages = normalize(payload.message); const messages = normalize(payload.message);
const nodeElementLength = getSpecialMsgNum(payload, OB11MessageDataType.node); const nodeElementLength = getSpecialMsgNum(payload, OB11MessageDataType.node);
@@ -116,13 +117,12 @@ export class SendMsgBase extends OneBotAction<OB11PostSendMsg, ReturnDataType> {
} }
return { valid: true }; return { valid: true };
} }
async _handle(payload: OB11PostSendMsg): Promise<ReturnDataType> { async _handle(payload: OB11PostSendMsg): Promise<ReturnDataType> {
return this.base_handle(payload); this.contextMode = ContextMode.Normal;
} if (payload.message_type === 'group') this.contextMode = ContextMode.Group;
async base_handle(payload: OB11PostSendMsg, contextMode: ContextMode = ContextMode.Normal): Promise<ReturnDataType> { if (payload.message_type === 'private') this.contextMode = ContextMode.Private;
if (payload.message_type === 'group') contextMode = ContextMode.Group; const peer = await createContext(this.core, payload, this.contextMode);
if (payload.message_type === 'private') contextMode = ContextMode.Private;
const peer = await createContext(this.core, payload, contextMode);
const messages = normalize( const messages = normalize(
payload.message, payload.message,
@@ -148,10 +148,7 @@ export class SendMsgBase extends OneBotAction<OB11PostSendMsg, ReturnDataType> {
peerUid: peer.peerUid, peerUid: peer.peerUid,
chatType: peer.chatType, chatType: peer.chatType,
}, (returnMsgAndResId.message).msgId); }, (returnMsgAndResId.message).msgId);
return { message_id: msgShortId!, res_id: returnMsgAndResId.res_id! };
// 对gocq的forward_id进行兼容
const resId = returnMsgAndResId.res_id!;
return { message_id: msgShortId!, res_id: resId, forward_id: resId };
} else if (returnMsgAndResId.res_id && !returnMsgAndResId.message) { } else if (returnMsgAndResId.res_id && !returnMsgAndResId.message) {
throw Error(`发送转发消息res_id${returnMsgAndResId.res_id} 失败`); throw Error(`发送转发消息res_id${returnMsgAndResId.res_id} 失败`);
} }
@@ -177,11 +174,9 @@ export class SendMsgBase extends OneBotAction<OB11PostSendMsg, ReturnDataType> {
nickname: string, nickname: string,
}, dp: number = 0): Promise<{ }, dp: number = 0): Promise<{
finallySendElements: SendArkElement, finallySendElements: SendArkElement,
res_id?: string, res_id?: string
deleteAfterSentFiles: string[],
} | null> { } | null> {
const packetMsg: PacketMsg[] = []; const packetMsg: PacketMsg[] = [];
let delFiles: string[] = [];
for (const node of messageNodes) { for (const node of messageNodes) {
if (dp >= 3) { if (dp >= 3) {
this.core.context.logger.logWarn('转发消息深度超过3层将停止解析'); this.core.context.logger.logWarn('转发消息深度超过3层将停止解析');
@@ -197,11 +192,9 @@ export class SendMsgBase extends OneBotAction<OB11PostSendMsg, ReturnDataType> {
nickname: (node.data.nickname || node.data.name) ?? parentMeta?.nickname ?? 'QQ用户', nickname: (node.data.nickname || node.data.name) ?? parentMeta?.nickname ?? 'QQ用户',
}, dp + 1); }, dp + 1);
sendElements = uploadReturnData?.finallySendElements ? [uploadReturnData.finallySendElements] : []; sendElements = uploadReturnData?.finallySendElements ? [uploadReturnData.finallySendElements] : [];
delFiles.push(...(uploadReturnData?.deleteAfterSentFiles || []));
} else { } else {
const sendElementsCreateReturn = await this.obContext.apis.MsgApi.createSendElements(OB11Data, msgPeer); const sendElementsCreateReturn = await this.obContext.apis.MsgApi.createSendElements(OB11Data, msgPeer);
sendElements = sendElementsCreateReturn.sendElements; sendElements = sendElementsCreateReturn.sendElements;
delFiles.push(...sendElementsCreateReturn.deleteAfterSentFiles);
} }
const packetMsgElements: rawMsgWithSendMsg = { const packetMsgElements: rawMsgWithSendMsg = {
@@ -225,8 +218,7 @@ export class SendMsgBase extends OneBotAction<OB11PostSendMsg, ReturnDataType> {
const msg = (await this.core.apis.MsgApi.getMsgsByMsgId(nodeMsg.Peer, [nodeMsg.MsgId])).msgList[0]; const msg = (await this.core.apis.MsgApi.getMsgsByMsgId(nodeMsg.Peer, [nodeMsg.MsgId])).msgList[0];
this.core.context.logger.logDebug(`handleForwardedNodesPacket[PureRaw] 开始转换 ${stringifyWithBigInt(msg)}`); this.core.context.logger.logDebug(`handleForwardedNodesPacket[PureRaw] 开始转换 ${stringifyWithBigInt(msg)}`);
if (msg) { if (msg) {
let msgCache = await this.core.apis.FileApi.downloadRawMsgMedia([msg]); await this.core.apis.FileApi.downloadRawMsgMedia([msg]);
delFiles.push(...msgCache);
const transformedMsg = this.core.apis.PacketApi.pkt.msgConverter.rawMsgToPacketMsg(msg, msgPeer); const transformedMsg = this.core.apis.PacketApi.pkt.msgConverter.rawMsgToPacketMsg(msg, msgPeer);
this.core.context.logger.logDebug(`handleForwardedNodesPacket[PureRaw] 转换为 ${stringifyWithBigInt(transformedMsg)}`); this.core.context.logger.logDebug(`handleForwardedNodesPacket[PureRaw] 转换为 ${stringifyWithBigInt(transformedMsg)}`);
packetMsg.push(transformedMsg); packetMsg.push(transformedMsg);
@@ -242,7 +234,6 @@ export class SendMsgBase extends OneBotAction<OB11PostSendMsg, ReturnDataType> {
const resid = await this.core.apis.PacketApi.pkt.operation.UploadForwardMsg(packetMsg, msgPeer.chatType === ChatType.KCHATTYPEGROUP ? +msgPeer.peerUid : 0); const resid = await this.core.apis.PacketApi.pkt.operation.UploadForwardMsg(packetMsg, msgPeer.chatType === ChatType.KCHATTYPEGROUP ? +msgPeer.peerUid : 0);
const forwardJson = ForwardMsgBuilder.fromPacketMsg(resid, packetMsg, source, news, summary, prompt); const forwardJson = ForwardMsgBuilder.fromPacketMsg(resid, packetMsg, source, news, summary, prompt);
return { return {
deleteAfterSentFiles: delFiles,
finallySendElements: { finallySendElements: {
elementType: ElementType.ARK, elementType: ElementType.ARK,
elementId: '', elementId: '',
@@ -264,7 +255,7 @@ export class SendMsgBase extends OneBotAction<OB11PostSendMsg, ReturnDataType> {
const res_id = uploadReturnData?.res_id; const res_id = uploadReturnData?.res_id;
const finallySendElements = uploadReturnData?.finallySendElements; const finallySendElements = uploadReturnData?.finallySendElements;
if (!finallySendElements) throw Error('转发消息失败,生成节点为空'); if (!finallySendElements) throw Error('转发消息失败,生成节点为空');
const returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(msgPeer, [finallySendElements], uploadReturnData.deleteAfterSentFiles || []).catch(() => undefined); const returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(msgPeer, [finallySendElements], []).catch(() => undefined);
return { message: returnMsg ?? null, res_id: res_id! }; return { message: returnMsg ?? null, res_id: res_id! };
} }

View File

@@ -1,18 +1,16 @@
import { ContextMode, ReturnDataType, SendMsgBase } from './SendMsg'; import { ContextMode, SendMsgBase } from './SendMsg';
import { ActionName, BaseCheckResult } from '@/onebot/action/router'; import { ActionName, BaseCheckResult } from '@/onebot/action/router';
import { OB11PostSendMsg } from '@/onebot/types'; import { OB11PostSendMsg } from '@/onebot/types';
// 未检测参数 // 未检测参数
class SendPrivateMsg extends SendMsgBase { class SendPrivateMsg extends SendMsgBase {
override actionName = ActionName.SendPrivateMsg; override actionName = ActionName.SendPrivateMsg;
override contextMode: ContextMode = ContextMode.Private;
protected override async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> { protected override async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> {
payload.message_type = 'private'; payload.message_type = 'private';
return super.check(payload); return super.check(payload);
} }
override async _handle(payload: OB11PostSendMsg): Promise<ReturnDataType> {
return this.base_handle(payload, ContextMode.Private);
}
} }
export default SendPrivateMsg; export default SendPrivateMsg;

View File

@@ -3,36 +3,21 @@ import { GetPacketStatusDepends } from '@/onebot/action/packet/GetPacketStatus';
import { Static, Type } from '@sinclair/typebox'; import { Static, Type } from '@sinclair/typebox';
const SchemaData = Type.Object({ const SchemaData = Type.Object({
group_id: Type.Optional(Type.String()), group_id: Type.Optional(Type.Union([Type.Number(), Type.String()])),
user_id: Type.Optional(Type.String()), user_id: Type.Union([Type.Number(), Type.String()]),
target_id: Type.Optional(Type.String()),
}); });
type Payload = Static<typeof SchemaData>; type Payload = Static<typeof SchemaData>;
export class SendPokeBase extends GetPacketStatusDepends<Payload, void> {
export class SendPoke extends GetPacketStatusDepends<Payload, void> {
override actionName = ActionName.SendPoke;
override payloadSchema = SchemaData; override payloadSchema = SchemaData;
async _handle(payload: Payload) { async _handle(payload: Payload) {
// 这里的 !! 可以传入空字符串 忽略这些数据有利用接口统一接口 if (payload.group_id) {
const target_id = !!payload.target_id ? payload.target_id : payload.user_id; await this.core.apis.PacketApi.pkt.operation.GroupPoke(+payload.group_id, +payload.user_id);
const peer_id = !!payload.group_id ? payload.group_id : payload.user_id; } else {
await this.core.apis.PacketApi.pkt.operation.FriendPoke(+payload.user_id);
const is_group = !!payload.group_id;
if (!target_id || !peer_id) {
throw new Error('请检查参数,缺少 user_id 或 group_id');
}
await this.core.apis.PacketApi.pkt.operation.SendPoke(is_group, +peer_id, +target_id);
} }
} }
export class SendPoke extends SendPokeBase {
override actionName = ActionName.SendPoke;
}
export class GroupPoke extends SendPokeBase {
override actionName = ActionName.GroupPoke;
}
export class FriendPoke extends SendPokeBase {
override actionName = ActionName.FriendPoke;
} }

View File

@@ -10,10 +10,6 @@ export interface InvalidCheckResult {
} }
export const ActionName = { export const ActionName = {
SetGroupKickMembers: 'set_group_kick_members',
SetGroupRobotAddOption: 'set_group_robot_add_option',
SetGroupAddOption: 'set_group_add_option',
SetGroupSearch: 'set_group_search',
// new extends 完全差异OneBot类别 // new extends 完全差异OneBot类别
GetDoubtFriendsAddRequest: 'get_doubt_friends_add_request', GetDoubtFriendsAddRequest: 'get_doubt_friends_add_request',
SetDoubtFriendsAddRequest: 'set_doubt_friends_add_request', SetDoubtFriendsAddRequest: 'set_doubt_friends_add_request',
@@ -132,7 +128,6 @@ export const ActionName = {
FetchEmojiLike: 'fetch_emoji_like', FetchEmojiLike: 'fetch_emoji_like',
SetInputStatus: 'set_input_status', SetInputStatus: 'set_input_status',
GetGroupInfoEx: 'get_group_info_ex', GetGroupInfoEx: 'get_group_info_ex',
GetGroupDetailInfo: 'get_group_detail_info',
GetGroupIgnoreAddRequest: 'get_group_ignore_add_request', GetGroupIgnoreAddRequest: 'get_group_ignore_add_request',
DelGroupNotice: '_del_group_notice', DelGroupNotice: '_del_group_notice',
FriendPoke: 'friend_poke', FriendPoke: 'friend_poke',

View File

@@ -4,7 +4,6 @@ import { ActionName } from '@/onebot/action/router';
import { Notify } from '@/onebot/types'; import { Notify } from '@/onebot/types';
interface RetData { interface RetData {
invited_requests: Notify[];
InvitedRequest: Notify[]; InvitedRequest: Notify[];
join_requests: Notify[]; join_requests: Notify[];
} }
@@ -14,7 +13,7 @@ export class GetGroupSystemMsg extends OneBotAction<void, RetData> {
async _handle(): Promise<RetData> { async _handle(): Promise<RetData> {
const SingleScreenNotifies = await this.core.apis.GroupApi.getSingleScreenNotifies(false, 50); const SingleScreenNotifies = await this.core.apis.GroupApi.getSingleScreenNotifies(false, 50);
const retData: RetData = { invited_requests: [], InvitedRequest: [], join_requests: [] }; const retData: RetData = { InvitedRequest: [], join_requests: [] };
const notifyPromises = SingleScreenNotifies.map(async (SSNotify) => { const notifyPromises = SingleScreenNotifies.map(async (SSNotify) => {
const invitorUin = SSNotify.user1?.uid ? +await this.core.apis.UserApi.getUinByUidV2(SSNotify.user1.uid) : 0; const invitorUin = SSNotify.user1?.uid ? +await this.core.apis.UserApi.getUinByUidV2(SSNotify.user1.uid) : 0;
@@ -40,7 +39,6 @@ export class GetGroupSystemMsg extends OneBotAction<void, RetData> {
await Promise.all(notifyPromises); await Promise.all(notifyPromises);
retData.invited_requests = retData.InvitedRequest;
return retData; return retData;
} }
} }

View File

@@ -0,0 +1,18 @@
import { ActionName } from '@/onebot/action/router';
import { GetPacketStatusDepends } from '@/onebot/action/packet/GetPacketStatus';
import { Static, Type } from '@sinclair/typebox';
const SchemaData = Type.Object({
user_id: Type.Union([Type.Number(), Type.String()])
});
type Payload = Static<typeof SchemaData>;
export class FriendPoke extends GetPacketStatusDepends<Payload, void> {
override actionName = ActionName.FriendPoke;
override payloadSchema = SchemaData;
async _handle(payload: Payload) {
await this.core.apis.PacketApi.pkt.operation.FriendPoke(+payload.user_id);
}
}

View File

@@ -85,6 +85,9 @@ export class OneBotMsgApi {
textElement: async element => { textElement: async element => {
if (element.atType === NTMsgAtType.ATTYPEUNKNOWN) { if (element.atType === NTMsgAtType.ATTYPEUNKNOWN) {
let text = element.content; let text = element.content;
if (!text.trim()) {
return null;
}
// 兼容 9.7.x 换行符 // 兼容 9.7.x 换行符
if (text.indexOf('\n') === -1 && text.indexOf('\r\n') === -1) { if (text.indexOf('\n') === -1 && text.indexOf('\r\n') === -1) {
text = text.replace(/\r/g, '\n'); text = text.replace(/\r/g, '\n');
@@ -97,7 +100,7 @@ export class OneBotMsgApi {
let qq: string = 'all'; let qq: string = 'all';
if (element.atType !== NTMsgAtType.ATTYPEALL) { if (element.atType !== NTMsgAtType.ATTYPEALL) {
const { atNtUid, atUid } = element; const { atNtUid, atUid } = element;
qq = !atUid || atUid === '0' ? await this.core.apis.UserApi.getUinByUidV2(atNtUid) : String(Number(atUid) >>> 0); qq = !atUid || atUid === '0' ? await this.core.apis.UserApi.getUinByUidV2(atNtUid) : atUid;
} }
return { return {
type: OB11MessageDataType.at, type: OB11MessageDataType.at,
@@ -147,31 +150,12 @@ export class OneBotMsgApi {
}; };
FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, element.fileUuid, element.fileUuid); FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, element.fileUuid, element.fileUuid);
FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, element.fileUuid, element.fileName); FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, element.fileUuid, element.fileName);
if (this.core.apis.PacketApi.available) {
let url;
try {
url = await this.core.apis.FileApi.getFileUrl(msg.chatType, msg.peerUid, element.fileUuid, element.file10MMd5)
} catch (error) {
url = '';
}
if (url) {
return { return {
type: OB11MessageDataType.file, type: OB11MessageDataType.file,
data: { data: {
file: element.fileName, file: element.fileName,
file_id: element.fileUuid, file_id: element.fileUuid,
file_size: element.fileSize, file_size: element.fileSize,
url: url,
},
}
}
}
return {
type: OB11MessageDataType.file,
data: {
file: element.fileName,
file_id: element.fileUuid,
file_size: element.fileSize
}, },
}; };
}, },
@@ -241,13 +225,17 @@ export class OneBotMsgApi {
}, },
replyElement: async (element, msg) => { replyElement: async (element, msg) => {
const records = msg.records.find(msgRecord => msgRecord.msgId === element?.sourceMsgIdInRecords);
const peer = { const peer = {
chatType: msg.chatType, chatType: msg.chatType,
peerUid: msg.peerUid, peerUid: msg.peerUid,
guildId: '', guildId: '',
}; };
if (!records || !element.replyMsgTime || !element.senderUidStr) {
this.core.context.logger.logError('似乎是旧版客户端,获取不到引用的消息', element.replayMsgSeq);
return null;
}
// 创建回复数据的通用方法
const createReplyData = (msgId: string): OB11MessageData => ({ const createReplyData = (msgId: string): OB11MessageData => ({
type: OB11MessageDataType.reply, type: OB11MessageDataType.reply,
data: { data: {
@@ -255,96 +243,48 @@ export class OneBotMsgApi {
}, },
}); });
// 查找记录 if (records.peerUin === '284840486' || records.peerUin === '1094950020') {
const records = msg.records.find(msgRecord => msgRecord.msgId === element?.sourceMsgIdInRecords);
// 特定账号的特殊处理
if (records && (records.peerUin === '284840486' || records.peerUin === '1094950020')) {
return createReplyData(records.msgId); return createReplyData(records.msgId);
} }
let replyMsgList = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV2(peer, element.replayMsgSeq, records.msgTime, [element.senderUidStr])).msgList;
let replyMsg = replyMsgList.find(msg => msg.msgRandom === records.msgRandom);
// 获取消息的通用方法组 if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
const tryFetchMethods = async (msgSeq: string, senderUid?: string, msgTime?: string, msgRandom?: string): Promise<RawMessage | undefined> => { this.core.context.logger.logError(
try { '筛选结果,筛选消息失败,将使用Fallback-1 Seq: ',
// 方法1通过序号和时间筛选
if (senderUid && msgTime) {
const replyMsgList = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV2(
peer, msgSeq, msgTime, [senderUid]
)).msgList;
const replyMsg = msgRandom
? replyMsgList.find(msg => msg.msgRandom === msgRandom)
: replyMsgList.find(msg => msg.msgSeq === msgSeq);
if (replyMsg) return replyMsg;
this.core.context.logger.logWarn(`方法1查询失败序号: ${msgSeq}, 消息数: ${replyMsgList.length}`);
}
// 方法2直接通过序号获取
const replyMsgList = (await this.core.apis.MsgApi.getMsgsBySeqAndCount(
peer, msgSeq, 1, true, true
)).msgList;
const replyMsg = msgRandom
? replyMsgList.find(msg => msg.msgRandom === msgRandom)
: replyMsgList.find(msg => msg.msgSeq === msgSeq);
if (replyMsg) return replyMsg;
this.core.context.logger.logWarn(`方法2查询失败序号: ${msgSeq}, 消息数: ${replyMsgList.length}`);
// 方法3另一种筛选方式
if (senderUid) {
const replyMsgList = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV3(
peer, msgSeq, [senderUid]
)).msgList;
const replyMsg = msgRandom
? replyMsgList.find(msg => msg.msgRandom === msgRandom)
: replyMsgList.find(msg => msg.msgSeq === msgSeq);
if (replyMsg) return replyMsg;
this.core.context.logger.logWarn(`方法3查询失败序号: ${msgSeq}, 消息数: ${replyMsgList.length}`);
}
return undefined;
} catch (error) {
this.core.context.logger.logError('查询回复消息出错', error);
return undefined;
}
};
// 有记录情况下,使用完整信息查询
if (records && element.replyMsgTime && element.senderUidStr) {
const replyMsg = await tryFetchMethods(
element.replayMsgSeq, element.replayMsgSeq,
element.senderUidStr, ',消息长度:',
records.msgTime, replyMsgList.length
records.msgRandom
); );
replyMsgList = (await this.core.apis.MsgApi.getMsgsBySeqAndCount(peer, element.replayMsgSeq, 1, true, true)).msgList;
if (replyMsg) { replyMsg = replyMsgList.find(msg => msg.msgRandom === records.msgRandom);
return createReplyData(replyMsg.msgId);
} }
this.core.context.logger.logError('所有查找方法均失败,获取不到带记录的引用消息', element.replayMsgSeq); if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
} else { this.core.context.logger.logWarn(
// 旧版客户端或不完整记录的情况,也尝试使用相同流程 '筛选消息失败,将使用Fallback-2 Seq:',
this.core.context.logger.logWarn('似乎是旧版客户端,尝试仅通过序号获取引用消息', element.replayMsgSeq); element.replayMsgSeq,
',消息长度:',
const replyMsg = await tryFetchMethods(element.replayMsgSeq); replyMsgList.length
);
if (replyMsg) { replyMsgList = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV3(peer, element.replayMsgSeq, [element.senderUidStr])).msgList;
return createReplyData(replyMsg.msgId); replyMsg = replyMsgList.find(msg => msg.msgRandom === records.msgRandom);
} }
this.core.context.logger.logError('所有查找方法均失败,获取不到旧客户端的引用消息', element.replayMsgSeq);
}
// 丢弃该消息段
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
this.core.context.logger.logError(
'最终筛选结果,筛选消息失败,获取不到引用的消息 Seq: ',
element.replayMsgSeq,
',消息长度:',
replyMsgList.length
);
return null; return null;
}
return createReplyData(replyMsg.msgId);
}, },
videoElement: async (element, msg, elementWrapper) => { videoElement: async (element, msg, elementWrapper) => {
const peer = { const peer = {
chatType: msg.chatType, chatType: msg.chatType,
@@ -391,18 +331,8 @@ export class OneBotMsgApi {
//开始兜底 //开始兜底
if (!videoDownUrl) { if (!videoDownUrl) {
if (this.core.apis.PacketApi.available) {
try {
videoDownUrl = await this.core.apis.FileApi.getVideoUrlPacket(msg.peerUid, element.fileUuid);
} catch (e) {
this.core.context.logger.logError('获取视频url失败', (e as Error).stack);
videoDownUrl = element.filePath; videoDownUrl = element.filePath;
} }
} else {
videoDownUrl = element.filePath;
}
}
const fileCode = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, element.fileUuid, element.fileName); const fileCode = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, element.fileUuid, element.fileName);
return { return {
type: OB11MessageDataType.video, type: OB11MessageDataType.video,
@@ -421,28 +351,6 @@ export class OneBotMsgApi {
guildId: '', guildId: '',
}; };
const fileCode = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, '', element.fileName); const fileCode = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, '', element.fileName);
let pttUrl = '';
if (this.core.apis.PacketApi.available) {
try {
pttUrl = await this.core.apis.FileApi.getPttUrl(msg.peerUid, element.fileUuid);
} catch (e) {
this.core.context.logger.logError('获取语音url失败', (e as Error).stack);
pttUrl = element.filePath;
}
} else {
pttUrl = element.filePath;
}
if (pttUrl) {
return {
type: OB11MessageDataType.voice,
data: {
file: fileCode,
path: element.filePath,
url: pttUrl,
file_size: element.fileSize,
},
}
}
return { return {
type: OB11MessageDataType.voice, type: OB11MessageDataType.voice,
data: { data: {
@@ -559,8 +467,6 @@ export class OneBotMsgApi {
replayMsgId: replyMsg.msgId, // raw.msgId replayMsgId: replyMsg.msgId, // raw.msgId
senderUin: replyMsg.senderUin, senderUin: replyMsg.senderUin,
senderUinStr: replyMsg.senderUin, senderUinStr: replyMsg.senderUin,
replyMsgClientSeq: replyMsg.clientSeq,
_replyMsgPeer: replyMsgM.Peer
}, },
} : } :
undefined; undefined;
@@ -1209,6 +1115,7 @@ export class OneBotMsgApi {
async waitGroupNotify(groupUin: string, memberUid?: string, operatorUid?: string) { async waitGroupNotify(groupUin: string, memberUid?: string, operatorUid?: string) {
const groupRole = this.core.apis.GroupApi.groupMemberCache.get(groupUin)?.get(this.core.selfInfo.uid.toString())?.role; const groupRole = this.core.apis.GroupApi.groupMemberCache.get(groupUin)?.get(this.core.selfInfo.uid.toString())?.role;
const isAdminOrOwner = groupRole === 3 || groupRole === 4; const isAdminOrOwner = groupRole === 3 || groupRole === 4;
if (isAdminOrOwner && !operatorUid) { if (isAdminOrOwner && !operatorUid) {
let dataNotify: GroupNotify | undefined; let dataNotify: GroupNotify | undefined;
await this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onGroupNotifiesUpdated', await this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onGroupNotifiesUpdated',
@@ -1238,7 +1145,7 @@ export class OneBotMsgApi {
const operatorUid = await this.waitGroupNotify( const operatorUid = await this.waitGroupNotify(
groupChange.groupUin.toString(), groupChange.groupUin.toString(),
groupChange.memberUid, groupChange.memberUid,
groupChange.operatorInfo ? new TextDecoder('utf-8').decode(groupChange.operatorInfo) : undefined groupChange.operatorInfo ? Buffer.from(groupChange.operatorInfo).toString() : ''
); );
return new OB11GroupIncreaseEvent( return new OB11GroupIncreaseEvent(
this.core, this.core,
@@ -1250,42 +1157,13 @@ export class OneBotMsgApi {
} else if (SysMessage.contentHead.type == 34 && SysMessage.body?.msgContent) { } else if (SysMessage.contentHead.type == 34 && SysMessage.body?.msgContent) {
const groupChange = new NapProtoMsg(GroupChange).decode(SysMessage.body.msgContent); const groupChange = new NapProtoMsg(GroupChange).decode(SysMessage.body.msgContent);
// 自身被踢出时operatorInfo会是一个protobuf 否则大多数情况为一个string
let operator_uid_parse: string | undefined = undefined;
if (groupChange.operatorInfo) {
// 先判断是否可能是protobuf自身被踢出或以0a开头
if (groupChange.decreaseType === 3 || Buffer.from(groupChange.operatorInfo).toString('hex').startsWith('0a')) {
// 可能是protobuf尝试解析
try {
operator_uid_parse = new NapProtoMsg(GroupChangeInfo).decode(groupChange.operatorInfo).operator?.operatorUid;
} catch (error) {
// protobuf解析失败fallback到字符串解析
try {
const decoded = new TextDecoder('utf-8').decode(groupChange.operatorInfo);
// 检查是否包含非ASCII字符如果包含则丢弃
const isAsciiOnly = [...decoded].every(char => char.charCodeAt(0) >= 32 && char.charCodeAt(0) <= 126);
operator_uid_parse = isAsciiOnly ? decoded : '';
} catch (e2) {
operator_uid_parse = '';
}
}
} else {
// 直接进行字符串解析
try {
const decoded = new TextDecoder('utf-8').decode(groupChange.operatorInfo);
// 检查是否包含非ASCII字符如果包含则丢弃
const isAsciiOnly = [...decoded].every(char => char.charCodeAt(0) >= 32 && char.charCodeAt(0) <= 126);
operator_uid_parse = isAsciiOnly ? decoded : '';
} catch (e) {
operator_uid_parse = '';
}
}
}
const operatorUid = await this.waitGroupNotify( const operatorUid = await this.waitGroupNotify(
groupChange.groupUin.toString(), groupChange.groupUin.toString(),
groupChange.memberUid, groupChange.memberUid,
operator_uid_parse groupChange.decreaseType === 3 && groupChange.operatorInfo ?
new NapProtoMsg(GroupChangeInfo).decode(groupChange.operatorInfo).operator?.operatorUid :
groupChange.operatorInfo?.toString()
); );
if (groupChange.memberUid === this.core.selfInfo.uid) { if (groupChange.memberUid === this.core.selfInfo.uid) {
setTimeout(() => { setTimeout(() => {

View File

@@ -2,5 +2,4 @@ import { EventType, OneBotEvent } from '@/onebot/event/OneBotEvent';
export abstract class OB11BaseNoticeEvent extends OneBotEvent { export abstract class OB11BaseNoticeEvent extends OneBotEvent {
post_type = EventType.NOTICE; post_type = EventType.NOTICE;
abstract notice_type: string;
} }

View File

@@ -1,6 +0,0 @@
import { EventType, OneBotEvent } from '@/onebot/event/OneBotEvent';
export abstract class OB11BaseRequestEvent extends OneBotEvent {
readonly post_type = EventType.REQUEST;
abstract request_type: string;
}

View File

@@ -1,8 +1,10 @@
import { OB11BaseNoticeEvent } from '@/onebot/event/notice/OB11BaseNoticeEvent';
import { EventType } from '@/onebot/event/OneBotEvent';
import { NapCatCore } from '@/core'; import { NapCatCore } from '@/core';
import { OB11BaseRequestEvent } from './OB11BaseRequestEvent';
export class OB11FriendRequestEvent extends OB11BaseRequestEvent { export class OB11FriendRequestEvent extends OB11BaseNoticeEvent {
override request_type = 'friend'; override post_type = EventType.REQUEST;
request_type = 'friend';
user_id: number; user_id: number;
comment: string; comment: string;

View File

@@ -1,18 +1,18 @@
import { OB11GroupNoticeEvent } from '@/onebot/event/notice/OB11GroupNoticeEvent';
import { EventType } from '@/onebot/event/OneBotEvent';
import { NapCatCore } from '@/core'; import { NapCatCore } from '@/core';
import { OB11BaseRequestEvent } from './OB11BaseRequestEvent';
export class OB11GroupRequestEvent extends OB11BaseRequestEvent { export class OB11GroupRequestEvent extends OB11GroupNoticeEvent {
override readonly request_type = 'group' as const; override post_type = EventType.REQUEST;
request_type = 'group';
group_id: number; override user_id: number;
user_id: number;
comment: string; comment: string;
flag: string; flag: string;
sub_type: string; sub_type: string;
constructor(core: NapCatCore, groupId: number, userId: number, sub_type: string, comment: string, flag: string) { constructor(core: NapCatCore, groupId: number, userId: number, sub_type: string, comment: string, flag: string) {
super(core); super(core, groupId, userId);
this.group_id = groupId;
this.user_id = userId; this.user_id = userId;
this.sub_type = sub_type; this.sub_type = sub_type;
this.comment = comment; this.comment = comment;

View File

@@ -50,6 +50,7 @@ import {
import { OB11Message } from './types'; import { OB11Message } from './types';
import { IOB11NetworkAdapter } from '@/onebot/network/adapter'; import { IOB11NetworkAdapter } from '@/onebot/network/adapter';
import { OB11HttpSSEServerAdapter } from './network/http-server-sse'; import { OB11HttpSSEServerAdapter } from './network/http-server-sse';
import { OB11PluginAdapter } from './network/plugin';
//OneBot实现类 //OneBot实现类
export class NapCatOneBot11Adapter { export class NapCatOneBot11Adapter {
@@ -113,9 +114,9 @@ export class NapCatOneBot11Adapter {
//创建NetWork服务 //创建NetWork服务
// 注册Plugin 如果需要基于NapCat进行快速开发 // 注册Plugin 如果需要基于NapCat进行快速开发
// this.networkManager.registerAdapter( this.networkManager.registerAdapter(
// new OB11PluginAdapter('myPlugin', this.core, this,this.actions) new OB11PluginAdapter('myPlugin', this.core, this,this.actions)
// ); );
for (const key of ob11Config.network.httpServers) { for (const key of ob11Config.network.httpServers) {
if (key.enable) { if (key.enable) {
this.networkManager.registerAdapter( this.networkManager.registerAdapter(
@@ -270,6 +271,7 @@ export class NapCatOneBot11Adapter {
); );
} }
}; };
msgListener.onAddSendMsg = async (msg) => { msgListener.onAddSendMsg = async (msg) => {
try { try {
if (msg.sendStatus == SendStatusType.KSEND_STATUS_SENDING) { if (msg.sendStatus == SendStatusType.KSEND_STATUS_SENDING) {
@@ -281,8 +283,7 @@ export class NapCatOneBot11Adapter {
}, 1, 10 * 60 * 1000); }, 1, 10 * 60 * 1000);
// 10分钟 超时 // 10分钟 超时
const updatemsg = updatemsgs.find((e) => e.msgId === msg.msgId); const updatemsg = updatemsgs.find((e) => e.msgId === msg.msgId);
// updatemsg?.sendStatus == SendStatusType.KSEND_STATUS_SUCCESS_NOSEQ NOSEQ一般是服务器未下发SEQ 这意味着这条消息不应该推送network if (updatemsg?.sendStatus == SendStatusType.KSEND_STATUS_SUCCESS || updatemsg?.sendStatus == SendStatusType.KSEND_STATUS_SUCCESS_NOSEQ) {
if (updatemsg?.sendStatus == SendStatusType.KSEND_STATUS_SUCCESS) {
updatemsg.id = MessageUnique.createUniqueMsgId( updatemsg.id = MessageUnique.createUniqueMsgId(
{ {
chatType: updatemsg.chatType, chatType: updatemsg.chatType,
@@ -304,18 +305,8 @@ export class NapCatOneBot11Adapter {
peerUid: uid, peerUid: uid,
guildId: '' guildId: ''
}; };
let msg = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeq(peer, msgSeq)).msgList.find(e => e.msgType == NTMsgType.KMSGTYPEGRAYTIPS); const msg = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeq(peer, msgSeq)).msgList.find(e => e.msgType == NTMsgType.KMSGTYPEGRAYTIPS);
const element = msg?.elements.find(e => !!e.grayTipElement?.revokeElement); const element = msg?.elements.find(e => !!e.grayTipElement?.revokeElement);
if (element?.grayTipElement?.revokeElement.isSelfOperate && msg) {
await this.core.eventWrapper.registerListen('NodeIKernelMsgListener/onMsgRecall',
(chatType: ChatType, uid: string, msgSeq: string) => {
return chatType === msg?.chatType && uid === msg?.peerUid && msgSeq === msg?.msgSeq;
}
).catch(() => {
msg = undefined;
this.context.logger.logDebug('自操作消息撤回事件');
});
}
if (msg && element) { if (msg && element) {
const recallEvent = await this.emitRecallMsg(msg, element); const recallEvent = await this.emitRecallMsg(msg, element);
try { try {
@@ -326,7 +317,6 @@ export class NapCatOneBot11Adapter {
this.context.logger.logError('处理消息撤回失败', e); this.context.logger.logError('处理消息撤回失败', e);
} }
} }
}; };
msgListener.onKickedOffLine = async (kick) => { msgListener.onKickedOffLine = async (kick) => {
const event = new BotOfflineEvent(this.core, kick.tipsTitle, kick.tipsDesc); const event = new BotOfflineEvent(this.core, kick.tipsTitle, kick.tipsDesc);
@@ -345,7 +335,7 @@ export class NapCatOneBot11Adapter {
for (let i = 0; i < reqs.unreadNums; i++) { for (let i = 0; i < reqs.unreadNums; i++) {
const req = reqs.buddyReqs[i]; const req = reqs.buddyReqs[i];
if (!req) continue; if (!req) continue;
if (!!req.isInitiator || (req.isDecide && req.reqType !== BuddyReqType.KMEINITIATORWAITPEERCONFIRM) || !req.isUnread) { if (!!req.isInitiator || (req.isDecide && req.reqType !== BuddyReqType.KMEINITIATORWAITPEERCONFIRM)) {
continue; continue;
} }
try { try {
@@ -363,6 +353,7 @@ export class NapCatOneBot11Adapter {
} }
} }
}; };
this.context.session this.context.session
.getBuddyService() .getBuddyService()
.addKernelBuddyListener(proxiedListenerOf(buddyListener, this.context.logger)); .addKernelBuddyListener(proxiedListenerOf(buddyListener, this.context.logger));

View File

@@ -87,8 +87,8 @@ export class OB11HttpServerAdapter extends IOB11NetworkAdapter<HttpServerConfig>
this.app.use(async (req, res) => { this.app.use(async (req, res) => {
await this.handleRequest(req, res); await this.handleRequest(req, res);
}); });
this.server.listen(this.config.port, this.config.host, () => { this.server.listen(this.config.port, () => {
this.core.context.logger.log(`[OneBot] [HTTP Server Adapter] Start On ${this.config.host}:${this.config.port}`); this.core.context.logger.log(`[OneBot] [HTTP Server Adapter] Start On Port ${this.config.port}`);
}); });
} }

View File

@@ -1,5 +1,5 @@
import { OB11EmitEventContent, OB11NetworkReloadType } from './index'; import { OB11EmitEventContent, OB11NetworkReloadType } from './index';
import { NapCatOneBot11Adapter, OB11Message } from '@/onebot'; import { NapCatOneBot11Adapter, OB11ArrayMessage, OB11Message } from '@/onebot';
import { NapCatCore } from '@/core'; import { NapCatCore } from '@/core';
import { PluginConfig } from '../config/config'; import { PluginConfig } from '../config/config';
import { plugin_onmessage } from '@/plugin'; import { plugin_onmessage } from '@/plugin';
@@ -22,7 +22,7 @@ export class OB11PluginAdapter extends IOB11NetworkAdapter<PluginConfig> {
onEvent<T extends OB11EmitEventContent>(event: T) { onEvent<T extends OB11EmitEventContent>(event: T) {
if (event.post_type === 'message') { if (event.post_type === 'message') {
plugin_onmessage(this.config.name, this.core, this.obContext, event as OB11Message, this.actions, this).then().catch(); plugin_onmessage(this.config.name, this.core, this.obContext, event as OB11ArrayMessage, this.actions, this).then().catch();
} }
} }

View File

@@ -39,11 +39,8 @@ export class OB11WebSocketServerAdapter extends IOB11NetworkAdapter<WebsocketSer
wsClient.close(); wsClient.close();
return; return;
} }
// 鉴权 close 不会立刻销毁 当前返回可避免挂载message事件 close 并未立刻关闭 而是存在timer操作后关闭 //鉴权
// 引发高危漏洞 this.authorize(this.config.token, wsClient, wsReq);
if (!this.authorize(this.config.token, wsClient, wsReq)) {
return;
}
const paramUrl = wsReq.url?.indexOf('?') !== -1 ? wsReq.url?.substring(0, wsReq.url?.indexOf('?')) : wsReq.url; const paramUrl = wsReq.url?.indexOf('?') !== -1 ? wsReq.url?.substring(0, wsReq.url?.indexOf('?')) : wsReq.url;
const isApiConnect = paramUrl === '/api' || paramUrl === '/api/'; const isApiConnect = paramUrl === '/api' || paramUrl === '/api/';
if (!isApiConnect) { if (!isApiConnect) {
@@ -148,16 +145,15 @@ export class OB11WebSocketServerAdapter extends IOB11NetworkAdapter<WebsocketSer
} }
private authorize(token: string | undefined, wsClient: WebSocket, wsReq: IncomingMessage) { private authorize(token: string | undefined, wsClient: WebSocket, wsReq: IncomingMessage) {
if (!token || token.length == 0) return true;//客户端未设置密钥 if (!token || token.length == 0) return;//客户端未设置密钥
const QueryClientToken = urlParse.parse(wsReq?.url || '', true).query['access_token']; const QueryClientToken = urlParse.parse(wsReq?.url || '', true).query['access_token'];
const HeaderClientToken = wsReq.headers.authorization?.split('Bearer ').pop() || ''; const HeaderClientToken = wsReq.headers.authorization?.split('Bearer ').pop() || '';
const ClientToken = typeof (QueryClientToken) === 'string' && QueryClientToken !== '' ? QueryClientToken : HeaderClientToken; const ClientToken = typeof (QueryClientToken) === 'string' && QueryClientToken !== '' ? QueryClientToken : HeaderClientToken;
if (ClientToken === token) { if (ClientToken === token) {
return true; return;
} }
wsClient.send(JSON.stringify(OB11Response.res(null, 'failed', 1403, 'token验证失败'))); wsClient.send(JSON.stringify(OB11Response.res(null, 'failed', 1403, 'token验证失败')));
wsClient.close(); wsClient.close();
return false;
} }
private checkStateAndReply<T>(data: T, wsClient: WebSocket) { private checkStateAndReply<T>(data: T, wsClient: WebSocket) {

View File

@@ -31,6 +31,10 @@ export interface OB11Message {
post_type?: EventType; post_type?: EventType;
raw?: RawMessage; raw?: RawMessage;
} }
export interface OB11ArrayMessage extends OB11Message {
message_format: 'array';
message: OB11MessageData[];
}
// 合并转发消息接口定义 // 合并转发消息接口定义
export interface OB11ForwardMessage extends OB11Message { export interface OB11ForwardMessage extends OB11Message {

View File

@@ -1,11 +1,217 @@
import { NapCatOneBot11Adapter, OB11Message } from '@/onebot'; import { NapCatOneBot11Adapter, OB11ArrayMessage, OB11MessageDataType } from '@/onebot';
import { NapCatCore } from '@/core'; import { ChatType, NapCatCore, Peer, RawMessage } from '@/core';
import { ActionMap } from '@/onebot/action'; import { ActionMap } from '@/onebot/action';
import { OB11PluginAdapter } from '@/onebot/network/plugin'; import { OB11PluginAdapter } from '@/onebot/network/plugin';
import { OpenAI } from 'openai';
import { RequestUtil } from '@/common/request';
import { randomBytes } from 'node:crypto';
const client = new OpenAI({
apiKey: '',//必填多模态
baseURL: 'https://api.bili2233.work/v1'
});
export const plugin_onmessage = async (adapter: string, _core: NapCatCore, _obCtx: NapCatOneBot11Adapter, message: OB11Message, action: ActionMap, instance: OB11PluginAdapter) => { async function handleMessageArray2String(messages: RawMessage[]): Promise<string[]> {
if (message.raw_message === 'ping') { const result = [];
const ret = await action.get('send_group_msg')?.handle({ group_id: String(message.group_id), message: 'pong' }, adapter, instance.config); let data = '';
console.log(ret); for (let i = 0; i < messages.length; i++) {
try {
if (messages[i]) {
data += await handleMessage2String(messages[i]!) + '\n';
} }
} catch {
continue;
}
if ((i + 1) % 1000 === 0 || i === messages.length - 1) {
result.push(data);
data = '';
}
}
return result;
}
async function handleMessage2String(message: RawMessage): Promise<string> {
let data = '';
for (let element of message.elements) {
if (element.textElement) {
data += element.textElement.content.replaceAll('->', '').replaceAll('<-', '');
}
if (element.replyElement) {
const records = message.records.find(msgRecord => msgRecord.msgId === element.replyElement?.sourceMsgIdInRecords);
if (records) {
data += '[Reply] 回应别人的消息 ->' + await handleMessage2String(records) + '<-';
}
}
}
if (data.length === 0) throw new Error('消息为空');
return (message.sendMemberName || message.sendNickName) + ' 说: ->' + data + '<- ';
}
async function generateChatCompletion(content_data: string): Promise<string> {
const chatCompletion = await client.chat.completions.create({
messages: [{ role: 'user', content: content_data }],
model: 'gemini-2.0-flash-thinking-exp'
});
console.log(chatCompletion);
return chatCompletion.choices[0]?.message.content || '';
}
async function generateChatCompletionWithImg(content_data: string, url: string): Promise<string> {
const chatCompletion = await client.chat.completions.create({
messages: [
{
role: 'user', content: [
{
type: 'text',
text: content_data
}, {
type: 'image_url',
image_url: {
url: url
}
}
]
},
],
model: 'gemini-2.0-flash-thinking-exp'
});
return chatCompletion.choices[0]?.message.content || '';
}
export const plugin_onmessage = async (
adapter: string,
core: NapCatCore,
_obCtx: NapCatOneBot11Adapter,
message: OB11ArrayMessage,
action: ActionMap,
instance: OB11PluginAdapter
) => {
if (!message.message.find(m => m.type === 'text' && m.data.text.includes('#画像'))) {
return;
}
const user_id = message.message.find(m => m.type === 'at')?.data.qq ?? message.sender.user_id;
const user_uid = await core.apis.UserApi.getUidByUinV2(user_id.toString());
if (!user_uid) {
return;
}
const peer: Peer = { chatType: ChatType.KCHATTYPEGROUP, peerUid: message.group_id?.toString() ?? '' };
const msg = await core.apis.MsgApi.queryFirstMsgBySender(peer, [user_uid]);
if (msg.msgList.length < 1) {
return;
}
let msg_tag = '根据下面图片提取该图片的描述的描述,回应只用给出头像描述即可不要给出 好的 等等无关句子也不得提及该提示词,下面为图片内容。';
let avater_info = '头像仅供参考,Ta的头像描述: ' + await generateChatCompletionWithImg(msg_tag, `https://thirdqq.qlogo.cn/g?b=sdk&nk=${user_id}&s=100`);
console.log(`Final avater_info ret: ${avater_info}`)
const msg_string_all = await handleMessageArray2String(msg.msgList);
const user_info = await action.get('get_group_member_info')?.handle({ group_id: message.group_id?.toString()!, user_id: user_id }, adapter, instance.config);
if (msg_string_all.length > 1) {
const summaryPromises = msg_string_all.map(async (msg_string, i) => {
const content_data = `请根据下面聊天内容,分析 ${user_info?.data?.card || user_info?.data?.nickname} 的聊天风格分析其性格特点和一些有趣的信息和好笑的信息,为其建立用户画像,并加以幽默风趣的吐槽,下面是聊天内容,通过-><-字符区分结构。注意回复内容只用输出内容,不要提及此段话,注意一定不要使用markdown,请采用纯文本回复。附加提示信息:${avater_info} \n精选聊天记录: ${msg_string}`;
try {
const data = await generateChatCompletion(content_data);
if (data) {
msg_string_all[i] = '[总结(此消息过长Ai已压缩改为总结)] ->' + data + '<- ';
console.log(`Summary for part ${i + 1}: ${data}`);
}
} catch (error) {
msg_string_all[i] = '';
}
});
await action.get('send_group_msg')?.handle({
group_id: String(message.group_id),
message: [
{
type: OB11MessageDataType.reply,
data: {
id: message.message_id.toString()
}
},
{
type: OB11MessageDataType.text,
data: {
text: `消息过长,共` + msg.msgList.length + `条消息,预计时间` + Math.round(10 * msg.msgList.length / 1000) + `秒,请稍等...`
}
}]
}, adapter, instance.config);
await Promise.all(summaryPromises);
}
const msg_string = msg_string_all.join('\n');
const content_data =
`请根据下面聊天内容,分析 ${user_info?.data?.card || user_info?.data?.nickname} 的聊天风格分析其性格特点和一些有趣的信息和好笑的信息,为其建立用户画像,并加以幽默风趣的吐槽,下面是聊天内容,通过-><-字符区分结构。注意回复内容只用输出内容,不要提及此段话,注意一定不要使用markdown,请采用纯文本回复。附加信息:${avater_info} \n精选聊天记录:${msg_string}`;
console.log(`Final content data: ${content_data}`);
const msg_ret = await generateChatCompletion(content_data);
console.log(`Final content ret: ${msg_ret}`)
let pic_tag = `请根据下面对该人物性格的分析,并虚构想象一个场景,生成如 (1 cute girl with (cat ear and cat tail:1.2) stands in the garden:1.1), (cute:1.35), (detailed beautiful eyes:1.3), (beautiful face:1.3), casual, silver hair, silver ear, (blue hair:0.8), (blue ear:0.8), long hair, coat, short skirt, hair blowing with the wind, (blue eye:1.2), flowers, (little girl:0.65), butterflys flying around 格式的文本用于描述人物,注意格式为英文加空格加逗号进行区分,请务必多的描述人物和想象和场景,至少50个描述Tag,风格是可爱动漫二次元风,注意一定要是人为主体描述,不要好的什么的回应,只用给出要求格式的文本,不需要 好的 的回应,也不要提及此段话,下面为该人物性格分析.附加信息:${avater_info}.下面是人物分析.\n${msg_ret}`;
let pic_tag_ret = await generateChatCompletion(pic_tag);
let pic = `https://thirdqq.qlogo.cn/g?b=sdk&nk=${user_id}&s=100`;
try {
let pic_generate = await RequestUtil.HttpGetJson<{ images?: Array<{ url: string }> }>
('https://api.siliconflow.cn/v1/images/generations', 'POST', {
"model": "stabilityai/stable-diffusion-xl-base-1.0",
"prompt": 'original, (masterpiece), (illustration), (extremely fine and beautiful), perfect detailed, photorealistic, (beautiful and clear background:1.25), (depth of field:0.7),' + pic_tag_ret,
"seed": randomBytes(4).readUInt32LE(0),
"negative_prompt": "(copyright name:1.5),logo,(watermark:1.5),character_watermark,lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry, bad feet, ((cowboy)),(((pubic))), ((((pubic_hair))))sketch, duplicate, ugly, huge eyes, text, logo, monochrome, worst face, (bad and mutated hands:1.3), (worst quality:2.0), (low quality:2.0), (blurry:2.0), horror, geometry, bad_prompt, (bad hands), (missing fingers), multiple limbs, bad anatomy, (interlocked fingers:1.2), Ugly Fingers, (extra digit and hands and fingers and legs and arms:1.4), crown braid, ((2girl)), (deformed fingers:1.2), (long fingers:1.2),succubus wings,horn,succubus horn,succubus hairstyle, (bad-artist-anime), bad-artist, bad hand"
}, {
Authorization: 'Bearer ',//必填
'Content-Type': 'application/json'
});
if (pic_generate?.images?.[0]) {
pic = pic_generate?.images?.[0].url;
}
} catch (error) {
}
await action.get('send_group_msg')?.handle({
group_id: String(message.group_id),
message: [{
type: OB11MessageDataType.node,
data: {
user_id: user_id,
nickname: user_info?.data?.card || user_info?.data?.nickname || '',
content: [
{
type: OB11MessageDataType.text,
data: { text: msg_ret }
}
]
}
},
{
type: OB11MessageDataType.node,
data: {
user_id: user_id,
nickname: user_info?.data?.card || user_info?.data?.nickname || '',
content: [
{
type: OB11MessageDataType.text,
data: { text: 'Tag: ' + pic_tag_ret }
}
]
}
},
{
type: OB11MessageDataType.node,
data: {
user_id: user_id,
nickname: user_info?.data?.card || user_info?.data?.nickname || '',
content: [
{
type: OB11MessageDataType.image,
data: {
file: pic
}
}
]
}
}
]
}, adapter, instance.config);
}; };

82
src/qrcode/README.md Normal file
View File

@@ -0,0 +1,82 @@
# QRCode Terminal Edition [![Build Status][travis-ci-img]][travis-ci-url]
> Going where no QRCode has gone before.
![Basic Example][basic-example-img]
# Node Library
## Install
Can be installed with:
$ npm install qrcode-terminal
and used:
var qrcode = require('qrcode-terminal');
## Usage
To display some data to the terminal just call:
qrcode.generate('This will be a QRCode, eh!');
You can even specify the error level (default is 'L'):
qrcode.setErrorLevel('Q');
qrcode.generate('This will be a QRCode with error level Q!');
If you don't want to display to the terminal but just want to string you can provide a callback:
qrcode.generate('http://github.com', function (qrcode) {
console.log(qrcode);
});
If you want to display small output, provide `opts` with `small`:
qrcode.generate('This will be a small QRCode, eh!', {small: true});
qrcode.generate('This will be a small QRCode, eh!', {small: true}, function (qrcode) {
console.log(qrcode)
});
# Command-Line
## Install
$ npm install -g qrcode-terminal
## Usage
$ qrcode-terminal --help
$ qrcode-terminal 'http://github.com'
$ echo 'http://github.com' | qrcode-terminal
# Support
- OS X
- Linux
- Windows
# Server-side
[node-qrcode][node-qrcode-url] is a popular server-side QRCode generator that
renders to a `canvas` object.
# Developing
To setup the development envrionment run `npm install`
To run tests run `npm test`
# Contributers
Gord Tanner <gtanner@gmail.com>
Micheal Brooks <michael@michaelbrooks.ca>
[travis-ci-img]: https://travis-ci.org/gtanner/qrcode-terminal.png
[travis-ci-url]: https://travis-ci.org/gtanner/qrcode-terminal
[basic-example-img]: https://raw.github.com/gtanner/qrcode-terminal/master/example/basic.png
[node-qrcode-url]: https://github.com/soldair/node-qrcode

View File

@@ -1,536 +0,0 @@
import * as net from 'net';
import { randomUUID } from 'crypto';
import { EventEmitter } from 'events';
export interface Packet<T = any> {
command: string;
trace: string;
data: T;
type: 'listener_callback' | 'event_response' | 'event_request' | 'default';
}
// 协议常量
const PROTOCOL_MAGIC = 0x4E415043; // 'NAPC'
const PROTOCOL_VERSION = 0x01;
const HEADER_SIZE = 12;
const MAX_PACKET_SIZE = 16 * 1024 * 1024; // 降低到16MB
const BUFFER_HIGH_WATER_MARK = 2 * 1024 * 1024; // 2MB背压阈值
const BUFFER_LOW_WATER_MARK = 512 * 1024; // 512KB恢复阈值
// 高效缓冲区管理器
class BufferManager {
private buffers: Buffer[] = [];
private totalSize: number = 0;
private readOffset: number = 0;
private isHighWaterMark: boolean = false;
// 添加数据
append(data: Buffer): void {
this.buffers.push(data);
this.totalSize += data.length;
// 检查背压
if (!this.isHighWaterMark && this.totalSize > BUFFER_HIGH_WATER_MARK) {
this.isHighWaterMark = true;
}
}
// 消费数据
consume(length: number): Buffer {
if (length > this.available) {
throw new Error('消费长度超过可用数据');
}
const result = Buffer.allocUnsafe(length);
let resultOffset = 0;
let remaining = length;
while (remaining > 0 && this.buffers.length > 0) {
const currentBuffer = this.buffers[0];
if (!currentBuffer?.[0]) continue;
const availableInCurrent = currentBuffer.length - this.readOffset;
const toCopy = Math.min(remaining, availableInCurrent);
currentBuffer.copy(result, resultOffset, this.readOffset, this.readOffset + toCopy);
resultOffset += toCopy;
remaining -= toCopy;
this.readOffset += toCopy;
// 如果当前buffer用完了移除它
if (this.readOffset >= currentBuffer.length) {
this.buffers.shift();
this.readOffset = 0;
}
}
this.totalSize -= length;
// 检查是否可以恢复读取
if (this.isHighWaterMark && this.totalSize < BUFFER_LOW_WATER_MARK) {
this.isHighWaterMark = false;
}
return result;
}
// 预览数据(不消费)
peek(length: number): Buffer | null {
if (length > this.available) {
return null;
}
const result = Buffer.allocUnsafe(length);
let resultOffset = 0;
let remaining = length;
let bufferIndex = 0;
let currentReadOffset = this.readOffset;
while (remaining > 0 && bufferIndex < this.buffers.length) {
const currentBuffer = this.buffers[bufferIndex];
if (!currentBuffer) continue;
const availableInCurrent = currentBuffer.length - currentReadOffset;
const toCopy = Math.min(remaining, availableInCurrent);
currentBuffer.copy(result, resultOffset, currentReadOffset, currentReadOffset + toCopy);
resultOffset += toCopy;
remaining -= toCopy;
if (currentReadOffset + toCopy >= currentBuffer.length) {
bufferIndex++;
currentReadOffset = 0;
} else {
currentReadOffset += toCopy;
}
}
return result;
}
get available(): number {
return this.totalSize;
}
get shouldPause(): boolean {
return this.isHighWaterMark;
}
reset(): void {
this.buffers = [];
this.totalSize = 0;
this.readOffset = 0;
this.isHighWaterMark = false;
}
}
// 简化的数据包管理器
class PacketManager {
static pack(packet: Packet): Buffer {
const jsonStr = JSON.stringify(packet);
const jsonBuffer = Buffer.from(jsonStr, 'utf8');
if (jsonBuffer.length > MAX_PACKET_SIZE - HEADER_SIZE) {
throw new Error(`数据包过大: ${jsonBuffer.length}`);
}
const buffer = Buffer.allocUnsafe(HEADER_SIZE + jsonBuffer.length);
buffer.writeUInt32BE(PROTOCOL_MAGIC, 0);
buffer.writeUInt32BE(jsonBuffer.length, 4);
buffer.writeUInt32BE(PROTOCOL_VERSION, 8);
jsonBuffer.copy(buffer, HEADER_SIZE);
return buffer;
}
static unpack(bufferManager: BufferManager): Packet[] {
const packets: Packet[] = [];
while (bufferManager.available >= HEADER_SIZE) {
// 检查魔数
const header = bufferManager.peek(HEADER_SIZE);
if (!header) break;
const magic = header.readUInt32BE(0);
if (magic !== PROTOCOL_MAGIC) {
// 简单的同步恢复:跳过一个字节
bufferManager.consume(1);
continue;
}
const dataLength = header.readUInt32BE(4);
//const version = header.readUInt32BE(8);
// 基本验证
if (dataLength <= 0 || dataLength > MAX_PACKET_SIZE - HEADER_SIZE) {
bufferManager.consume(1);
continue;
}
// 检查完整包
const totalSize = HEADER_SIZE + dataLength;
if (bufferManager.available < totalSize) {
break;
}
// 消费完整包
bufferManager.consume(HEADER_SIZE);
const jsonBuffer = bufferManager.consume(dataLength);
try {
const packet = JSON.parse(jsonBuffer.toString('utf8')) as Packet;
if (this.isValidPacket(packet)) {
packets.push(packet);
}
} catch (error) {
console.error('JSON解析失败:', error);
}
}
return packets;
}
private static isValidPacket(packet: any): packet is Packet {
return packet &&
typeof packet.command === 'string' &&
typeof packet.trace === 'string' &&
packet.data !== undefined &&
['listener_callback', 'event_response', 'event_request', 'default'].includes(packet.type);
}
static createRequest<T = any>(command: string, data: T, trace?: string): Packet<T> {
return {
command,
trace: trace || randomUUID(),
data,
type: 'event_request'
};
}
static createResponse<T = any>(trace: string, data: T, command = ''): Packet<T> {
return {
command,
trace,
data,
type: 'event_response'
};
}
static createCallback<T = any>(command: string, data: T, trace?: string): Packet<T> {
return {
command,
trace: trace || randomUUID(),
data,
type: 'listener_callback'
};
}
}
// 响应助手类
class ResponseHelper {
private responseSent = false;
constructor(private socket: net.Socket, private trace: string, private command: string = '') { }
success<T = any>(data: T): void {
if (this.responseSent) return;
const response = PacketManager.createResponse(this.trace, data, this.command);
this.writePacket(response);
this.responseSent = true;
}
error(message: string, code = 500): void {
if (this.responseSent) return;
const response = PacketManager.createResponse(this.trace, { error: message, code }, this.command);
this.writePacket(response);
this.responseSent = true;
}
sendEventResponse<T = any>(trace: string, data: T): void {
const response = PacketManager.createResponse(trace, data, this.command);
this.writePacket(response);
}
sendListenerCallback<T = any>(command: string, data: T): void {
const callback = PacketManager.createCallback(command, data);
this.writePacket(callback);
}
private writePacket(packet: Packet): void {
console.log(`发送数据包: ${packet.command}, trace: ${packet.trace} (${packet.type}) `);
if (!this.socket.destroyed) {
const buffer = PacketManager.pack(packet);
this.socket.write(buffer);
}
}
get hasResponseSent(): boolean {
return this.responseSent;
}
}
// 带背压控制的Socket包装器
class ManagedSocket {
private bufferManager = new BufferManager();
private isPaused = false;
constructor(private socket: net.Socket, private onPacket: (packet: Packet) => void) {
this.setupSocket();
}
private setupSocket(): void {
this.socket.on('data', (chunk) => {
this.bufferManager.append(chunk);
// 背压控制
if (this.bufferManager.shouldPause && !this.isPaused) {
this.socket.pause();
this.isPaused = true;
console.warn('Socket暂停读取 - 缓冲区过大');
}
this.processPackets();
});
this.socket.on('drain', () => {
// 当socket的写缓冲区有空间时检查是否可以恢复读取
if (this.isPaused && !this.bufferManager.shouldPause) {
this.socket.resume();
this.isPaused = false;
console.log('Socket恢复读取');
}
});
}
private processPackets(): void {
try {
const packets = PacketManager.unpack(this.bufferManager);
packets.forEach(packet => this.onPacket(packet));
// 处理完包后检查是否可以恢复读取
if (this.isPaused && !this.bufferManager.shouldPause) {
this.socket.resume();
this.isPaused = false;
console.log('Socket恢复读取');
}
} catch (error) {
console.error('处理数据包失败:', error);
this.bufferManager.reset();
if (this.isPaused) {
this.socket.resume();
this.isPaused = false;
}
}
}
write(buffer: Buffer): boolean {
return this.socket.write(buffer);
}
destroy(): void {
this.socket.destroy();
}
get destroyed(): boolean {
return this.socket.destroyed;
}
}
type PacketHandler = (packet: Packet, helper: ResponseHelper) => Promise<any> | any;
// 简化的管道服务端
class PipeServer extends EventEmitter {
private server: net.Server;
private clients: Map<net.Socket, ManagedSocket> = new Map();
private handler: PacketHandler | null = null;
constructor(private pipeName: string) {
super();
this.server = net.createServer();
this.setupServer();
}
private setupServer(): void {
this.server.on('connection', (socket) => {
console.log('客户端连接');
const managedSocket = new ManagedSocket(socket, (packet) => {
this.handlePacket(packet, socket);
});
this.clients.set(socket, managedSocket);
socket.on('close', () => {
console.log('客户端断开');
this.clients.delete(socket);
});
socket.on('error', (error) => {
console.error('Socket错误:', error);
this.clients.delete(socket);
});
});
}
registerHandler(handler: PacketHandler): void {
this.handler = handler;
}
private async handlePacket(packet: Packet, socket: net.Socket): Promise<void> {
if (packet.type === 'event_response' || packet.type === 'listener_callback') {
this.emit(packet.type, packet);
return;
}
const helper = new ResponseHelper(socket, packet.trace, packet.command);
if (!this.handler) {
helper.error('未注册处理器');
return;
}
try {
const result = await this.handler(packet, helper);
if (result !== undefined && !helper.hasResponseSent) {
helper.success(result);
}
} catch (error) {
if (!helper.hasResponseSent) {
const message = error instanceof Error ? error.message : String(error);
helper.error(message);
}
}
}
async start(): Promise<void> {
return new Promise((resolve, reject) => {
this.server.listen(this.pipeName, () => {
console.log(`管道服务器启动: ${this.pipeName}`);
resolve();
});
this.server.on('error', reject);
});
}
async stop(): Promise<void> {
return new Promise((resolve) => {
this.clients.forEach((managedSocket) => managedSocket.destroy());
this.clients.clear();
this.server.close(() => {
console.log('管道服务器停止');
resolve();
});
});
}
broadcast<T = any>(command: string, data: T, type: Packet['type'] = 'default'): void {
const packet: Packet<T> = {
command,
trace: randomUUID(),
data,
type
};
const buffer = PacketManager.pack(packet);
this.clients.forEach((managedSocket) => {
if (!managedSocket.destroyed) {
managedSocket.write(buffer);
}
});
}
get clientCount(): number {
return this.clients.size;
}
}
// 简化的管道客户端
class PipeClient extends EventEmitter {
private socket: net.Socket | null = null;
private managedSocket: ManagedSocket | null = null;
private isConnected = false;
private handler: PacketHandler | null = null;
constructor(private pipeName: string) {
super();
}
registerHandler(handler: PacketHandler): void {
this.handler = handler;
}
async connect(): Promise<void> {
return new Promise((resolve, reject) => {
this.socket = net.createConnection(this.pipeName);
this.managedSocket = new ManagedSocket(this.socket, (packet) => {
this.handlePacket(packet);
});
this.socket.on('connect', () => {
console.log('连接到管道服务器');
this.isConnected = true;
resolve();
});
this.socket.on('close', () => {
console.log('与服务器断开连接');
this.isConnected = false;
this.emit('disconnect');
});
this.socket.on('error', (error) => {
console.error('Socket错误:', error);
this.isConnected = false;
reject(error);
});
});
}
private async handlePacket(packet: Packet): Promise<void> {
if (this.handler && this.socket) {
const helper = new ResponseHelper(this.socket, packet.trace, packet.command);
try {
await this.handler(packet, helper);
} catch (error) {
console.error('处理数据包失败:', error);
}
}
}
sendRequest<T = any>(command: string, data: T, trace?: string): void {
if (!this.isConnected || !this.managedSocket) {
throw new Error('未连接到服务器');
}
const packet = PacketManager.createRequest(command, data, trace);
const buffer = PacketManager.pack(packet);
this.managedSocket.write(buffer);
}
sendResponse<T = any>(trace: string, data: T, command = ''): void {
if (!this.isConnected || !this.managedSocket) {
throw new Error('未连接到服务器');
}
const packet = PacketManager.createResponse(trace, data, command);
const buffer = PacketManager.pack(packet);
this.managedSocket.write(buffer);
}
disconnect(): void {
if (this.managedSocket) {
this.managedSocket.destroy();
this.managedSocket = null;
}
this.socket = null;
this.isConnected = false;
}
get connected(): boolean {
return this.isConnected;
}
}
export { PipeServer, PipeClient, PacketManager, ResponseHelper, BufferManager };

View File

@@ -1,109 +0,0 @@
import { createRemoteServiceClient } from "@/remote/service";
import {
NodeIQQNTWrapperSession,
WrapperSessionInitConfig
} from "../core/wrapper";
import { NodeIKernelSessionListener } from "../core/listeners/NodeIKernelSessionListener";
import {
NodeIDependsAdapter,
NodeIDispatcherAdapter
} from "../core/adapters";
import { ServiceNamingMapping } from "@/core";
class RemoteServiceManager {
private services: Map<string, any> = new Map();
private handler;
constructor(handler: (client: any, listenerCommand: string, ...args: any[]) => Promise<any>) {
this.handler = handler;
}
private createRemoteService<T extends keyof ServiceNamingMapping>(
serviceName: T
): ServiceNamingMapping[T] {
if (this.services.has(serviceName)) {
return this.services.get(serviceName);
}
let serviceClient: any;
serviceClient = createRemoteServiceClient(serviceName, async (serviceCommand, ...args) => {
return await this.handler(serviceClient, serviceCommand, ...args);
});
this.services.set(serviceName, serviceClient.object);
return serviceClient.object;
}
getService<T extends keyof ServiceNamingMapping>(
serviceName: T
): ServiceNamingMapping[T] {
return this.createRemoteService(serviceName);
}
}
export class RemoteWrapperSession implements NodeIQQNTWrapperSession {
private serviceManager: RemoteServiceManager;
constructor(handler: (client: { object: keyof ServiceNamingMapping, receiverListener: (command: string, ...args: any[]) => void }, listenerCommand: string, ...args: any[]) => Promise<void>) {
this.serviceManager = new RemoteServiceManager(handler);
}
create(): RemoteWrapperSession {
return this;
}
init(
_wrapperSessionInitConfig: WrapperSessionInitConfig,
_nodeIDependsAdapter: NodeIDependsAdapter,
_nodeIDispatcherAdapter: NodeIDispatcherAdapter,
_nodeIKernelSessionListener: NodeIKernelSessionListener,
): void {
}
startNT(_session?: number): void {
}
getBdhUploadService() { return null; }
getECDHService() { return this.serviceManager.getService('NodeIKernelECDHService'); }
getMsgService() { return this.serviceManager.getService('NodeIKernelMsgService'); }
getProfileService() { return this.serviceManager.getService('NodeIKernelProfileService'); }
getProfileLikeService() { return this.serviceManager.getService('NodeIKernelProfileLikeService'); }
getGroupService() { return this.serviceManager.getService('NodeIKernelGroupService'); }
getStorageCleanService() { return this.serviceManager.getService('NodeIKernelStorageCleanService'); }
getBuddyService() { return this.serviceManager.getService('NodeIKernelBuddyService'); }
getRobotService() { return this.serviceManager.getService('NodeIKernelRobotService'); }
getTicketService() { return this.serviceManager.getService('NodeIKernelTicketService'); }
getTipOffService() { return this.serviceManager.getService('NodeIKernelTipOffService'); }
getNodeMiscService() { return this.serviceManager.getService('NodeIKernelNodeMiscService'); }
getRichMediaService() { return this.serviceManager.getService('NodeIKernelRichMediaService'); }
getMsgBackupService() { return this.serviceManager.getService('NodeIKernelMsgBackupService'); }
getAlbumService() { return this.serviceManager.getService('NodeIKernelAlbumService'); }
getTianShuService() { return this.serviceManager.getService('NodeIKernelTianShuService'); }
getUnitedConfigService() { return this.serviceManager.getService('NodeIKernelUnitedConfigService'); }
getSearchService() { return this.serviceManager.getService('NodeIKernelSearchService'); }
getDirectSessionService() { return null; }
getRDeliveryService() { return null; }
getAvatarService() { return this.serviceManager.getService('NodeIKernelAvatarService'); }
getFeedChannelService() { return null; }
getYellowFaceService() { return null; }
getCollectionService() { return this.serviceManager.getService('NodeIKernelCollectionService'); }
getSettingService() { return null; }
getQiDianService() { return null; }
getFileAssistantService() { return this.serviceManager.getService('NodeIKernelFileAssistantService'); }
getGuildService() { return null; }
getSkinService() { return null; }
getTestPerformanceService() { return this.serviceManager.getService('NodeIkernelTestPerformanceService'); }
getQQPlayService() { return null; }
getDbToolsService() { return this.serviceManager.getService('NodeIKernelDbToolsService'); }
getUixConvertService() { return this.serviceManager.getService('NodeIKernelUixConvertService'); }
getOnlineStatusService() { return this.serviceManager.getService('NodeIKernelOnlineStatusService'); }
getRemotingService() { return null; }
getGroupTabService() { return null; }
getGroupSchoolService() { return null; }
getLiteBusinessService() { return null; }
getGuildMsgService() { return null; }
getLockService() { return null; }
getMSFService() { return this.serviceManager.getService('NodeIKernelMSFService'); }
getGuildHotUpdateService() { return null; }
getAVSDKService() { return null; }
getRecentContactService() { return this.serviceManager.getService('NodeIKernelRecentContactService'); }
getConfigMgrService() { return null; }
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,131 +0,0 @@
interface EncodedValue {
$type: string;
$value?: unknown;
}
interface EncodedNull {
$type: "null";
}
interface EncodedUndefined {
$type: "undefined";
}
interface EncodedPrimitive {
$type: "number" | "string" | "boolean";
$value: number | string | boolean;
}
interface EncodedBuffer {
$type: "Buffer";
$value: string;
}
interface EncodedMap {
$type: "Map";
$value: [EncodedValue, EncodedValue][];
}
interface EncodedArray {
$type: "Array";
$value: EncodedValue[];
}
interface EncodedObject {
$type: "Object";
$value: { [key: string]: EncodedValue };
}
type SerializedValue = EncodedNull | EncodedUndefined | EncodedPrimitive | EncodedBuffer | EncodedMap | EncodedArray | EncodedObject;
function rpc_encode<T>(value: T): SerializedValue {
if (value === null) return { $type: "null" };
if (value === undefined) return { $type: "undefined" };
if (typeof value === "number") return { $type: "number", $value: value };
if (typeof value === "string") return { $type: "string", $value: value };
if (typeof value === "boolean") return { $type: "boolean", $value: value };
if (Buffer.isBuffer(value) || value instanceof Uint8Array) {
// Buffer和Uint8Array都转成base64字符串
let base64: string = Buffer.from(value).toString("base64");
return { $type: "Buffer", $value: base64 };
}
if (value instanceof Map) {
let arr: [SerializedValue, SerializedValue][] = [];
for (let [k, v] of value.entries()) {
arr.push([rpc_encode(k), rpc_encode(v)]);
}
return { $type: "Map", $value: arr };
}
if (Array.isArray(value) || (typeof value === "object" && value !== null && typeof (value as unknown as ArrayLike<unknown>).length === "number")) {
// ArrayLike也认为是Array
let arr: SerializedValue[] = [];
const arrayLike = value as unknown as ArrayLike<unknown>;
for (let i = 0; i < arrayLike.length; i++) {
arr.push(rpc_encode(arrayLike[i]));
}
return { $type: "Array", $value: arr };
}
if (typeof value === "object" && value !== null) {
let obj: { [key: string]: SerializedValue } = {};
for (let k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
obj[k] = rpc_encode((value as Record<string, unknown>)[k]);
}
}
return { $type: "Object", $value: obj };
}
throw new Error("Unsupported type");
}
function rpc_decode<T = unknown>(obj: EncodedValue): T {
if (obj == null || typeof obj !== "object" || !("$type" in obj)) {
throw new Error("Invalid encoded object");
}
switch (obj.$type) {
case "null": return null as T;
case "undefined": return undefined as T;
case "number": return (obj as EncodedPrimitive).$value as T;
case "string": return (obj as EncodedPrimitive).$value as T;
case "boolean": return (obj as EncodedPrimitive).$value as T;
case "Buffer":
return Buffer.from((obj as EncodedBuffer).$value, "base64") as T;
case "Map":
{
let map = new Map();
for (let [k, v] of (obj as EncodedMap).$value) {
map.set(rpc_decode(k), rpc_decode(v));
}
return map as T;
}
case "Array":
{
let arr: unknown[] = [];
for (let item of (obj as EncodedArray).$value) {
arr.push(rpc_decode(item));
}
return arr as T;
}
case "Object":
{
let out: Record<string, unknown> = {};
for (let k in (obj as EncodedObject).$value) {
const value = (obj as EncodedObject).$value[k];
if (value !== undefined) {
out[k] = rpc_decode(value);
}
}
return out as T;
}
default:
throw new Error("Unknown $type: " + obj.$type);
}
}
export { rpc_encode, rpc_decode };
export type { SerializedValue };

View File

@@ -1,114 +0,0 @@
import { FuncKeys, NTEventWrapper } from "@/common/event";
import { ServiceNamingMapping } from "@/core";
export type ServiceMethodCommand = {
[Service in keyof ServiceNamingMapping]: `${Service}/${FuncKeys<ServiceNamingMapping[Service]>}`
}[keyof ServiceNamingMapping];
const LISTENER_COMMAND_PATTERN = /\/addKernel\w*Listener$/;
function isListenerCommand(command: ServiceMethodCommand): boolean {
return LISTENER_COMMAND_PATTERN.test(command);
}
export function createRemoteServiceServer<T extends keyof ServiceNamingMapping>(
serviceName: T,
ntevent: NTEventWrapper,
callback: (command: ServiceMethodCommand, ...args: any[]) => Promise<any>
): ServiceNamingMapping[T] {
return new Proxy(() => { }, {
get: (_target: any, functionName: string) => {
const command = `${serviceName}/${functionName}` as ServiceMethodCommand;
if (isListenerCommand(command)) {
return async (..._args: any[]) => {
const listener = new Proxy(new class { }(), {
apply: (_target, _thisArg, _arguments) => {
return callback(command, ..._arguments);
}
});
return await (ntevent.callNoListenerEvent as any)(command, listener);
};
}
return async (...args: any[]) => {
return await (ntevent.callNoListenerEvent as any)(command, ...args);
};
}
});
}
// 避免重复远程注册 多份传输会消耗很大
export const listenerCmdRegisted = new Map<ServiceMethodCommand, boolean>();
// 已经注册的Listener实例托管
export const clientCallback = new Map<string, Array<(...args: any[]) => Promise<any>>>();
export async function handleServiceServerOnce(
command: ServiceMethodCommand,// 服务注册命令
recvListener: (command: string, ...args: any[]) => Promise<any>,//listener监听器
ntevent: NTEventWrapper,// 事件处理器
...args: any[]//实际参数
) {
if (isListenerCommand(command)) {
if (!listenerCmdRegisted.has(command)) {
listenerCmdRegisted.set(command, true);
return (ntevent.callNoListenerEvent as any)(command, new Proxy(new class { }(), {
get: (_target: any, prop: string) => {
return async (..._args: any[]) => {
let listenerCmd = `${command.split('/')[0]}/${prop}`;
recvListener(listenerCmd, ..._args);
};
}
}));
}
return 0;
}
return await (ntevent.callNoListenerEvent as (command: ServiceMethodCommand, ...args: any[]) => Promise<any>)(command, ...args);
}
export function createRemoteServiceClient<T extends keyof ServiceNamingMapping>(
serviceName: T,
receiverEvent: (command: ServiceMethodCommand, ...args: any[]) => Promise<any>
) {
const object = new Proxy(() => { }, {
get: (_target: any, functionName: string) => {
const command = `${serviceName}/${functionName}` as ServiceMethodCommand;
if (isListenerCommand(command)) {
return async (listener: Record<string, any>) => {
for (const key in listener) {
if (typeof listener[key] === 'function') {
const listenerCmd = `${command.split('/')[0]}/${key}`;
if (!clientCallback.has(listenerCmd)) {
clientCallback.set(listenerCmd, [listener[key].bind(listener)]);
} else {
clientCallback.get(listenerCmd)?.push(listener[key].bind(listener));
}
}
}
return await receiverEvent(command);
};
}
return async (...args: any[]) => {
return await receiverEvent(command, ...args);
};
}
});
const receiverListener = async function (command: string, ...args: any[]) {
return clientCallback.get(command)?.forEach(async (callback) => await callback(...args));
};
return { receiverListener: receiverListener, object: object as ServiceNamingMapping[T] };
}
export async function receiverServiceListener(
command: string,
...args: any[]
) {
if (clientCallback.has(command)) {
return clientCallback.get(command)?.forEach(async (callback) => await callback(...args));
}
return 0;
}
export function clearServiceState() {
listenerCmdRegisted.clear();
clientCallback.clear();
}

View File

@@ -1,23 +0,0 @@
import { NodeIKernelLoginService, NodeIQQNTWrapperEngine, NodeIQQNTWrapperSession, NodeQQNTWrapperUtil, WrapperNodeApi } from "@/core";
import { NodeIO3MiscService } from "@/core/services/NodeIO3MiscService";
import { dirname } from "path";
import { fileURLToPath } from "url";
export const LocalVirtualWrapper: WrapperNodeApi = {
NodeIO3MiscService: {
get: () => LocalVirtualWrapper.NodeIO3MiscService,
addO3MiscListener: () => 0,
setAmgomDataPiece: () => { },
reportAmgomWeather: () => { },
} as NodeIO3MiscService,
NodeQQNTWrapperUtil: {
get: () => LocalVirtualWrapper.NodeQQNTWrapperUtil,
getNTUserDataInfoConfig: function (): string {
let current_path = dirname(fileURLToPath(import.meta.url));
return current_path;
}
} as NodeQQNTWrapperUtil,
NodeIQQNTWrapperSession: {} as NodeIQQNTWrapperSession,
NodeIQQNTWrapperEngine: {} as NodeIQQNTWrapperEngine,
NodeIKernelLoginService: {} as NodeIKernelLoginService,
};

View File

@@ -1,7 +1,6 @@
import { LogWrapper } from '@/common/log'; import { LogWrapper } from '@/common/log';
import * as net from 'net'; import * as net from 'net';
import * as process from 'process'; import * as process from 'process';
import { Writable } from 'stream';
/** /**
* 连接到命名管道并重定向stdout * 连接到命名管道并重定向stdout
@@ -12,6 +11,7 @@ import { Writable } from 'stream';
export function connectToNamedPipe(logger: LogWrapper, timeoutMs: number = 5000): Promise<{ disconnect: () => void }> { export function connectToNamedPipe(logger: LogWrapper, timeoutMs: number = 5000): Promise<{ disconnect: () => void }> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (process.platform !== 'win32') { if (process.platform !== 'win32') {
logger.log('只有Windows平台支持命名管道');
// 非Windows平台不reject而是返回一个空的disconnect函数 // 非Windows平台不reject而是返回一个空的disconnect函数
return resolve({ disconnect: () => { } }); return resolve({ disconnect: () => { } });
} }
@@ -25,50 +25,12 @@ export function connectToNamedPipe(logger: LogWrapper, timeoutMs: number = 5000)
}, timeoutMs); }, timeoutMs);
try { try {
const originalStdoutWrite = process.stdout.write.bind(process.stdout); let originalStdoutWrite = process.stdout.write.bind(process.stdout);
const pipeSocket = net.connect(pipePath, () => { const pipeSocket = net.connect(pipePath, () => {
// 清除超时 // 清除超时
clearTimeout(timeoutId); clearTimeout(timeoutId);
// 优化网络性能设置
pipeSocket.setNoDelay(true); // 减少延迟
// 设置更高的高水位线,允许更多数据缓冲
logger.log(`[StdOut] 已重定向到命名管道: ${pipePath}`); logger.log(`[StdOut] 已重定向到命名管道: ${pipePath}`);
// 创建拥有更优雅背压处理的 Writable 流
const pipeWritable = new Writable({
highWaterMark: 1024 * 64, // 64KB 高水位线
write(chunk, encoding, callback) {
if (!pipeSocket.writable) {
// 如果管道不可写退回到原始stdout
logger.log('[StdOut] 管道不可写,回退到控制台输出');
return originalStdoutWrite(chunk, encoding, callback);
}
// 尝试写入数据到管道
const canContinue = pipeSocket.write(chunk, encoding, () => {
// 数据已被发送或放入内部缓冲区
});
if (canContinue) {
// 如果返回true表示可以继续写入更多数据
// 立即通知写入流可以继续
process.nextTick(callback);
} else {
// 如果返回false表示内部缓冲区已满
// 等待drain事件再恢复写入
pipeSocket.once('drain', () => {
callback();
});
}
// 明确返回true表示写入已处理
return true;
}
});
// 重定向stdout
process.stdout.write = ( process.stdout.write = (
chunk: any, chunk: any,
encoding?: BufferEncoding | (() => void), encoding?: BufferEncoding | (() => void),
@@ -78,11 +40,8 @@ export function connectToNamedPipe(logger: LogWrapper, timeoutMs: number = 5000)
cb = encoding; cb = encoding;
encoding = undefined; encoding = undefined;
} }
return pipeSocket.write(chunk, encoding as BufferEncoding, cb);
// 使用优化的writable流处理写入
return pipeWritable.write(chunk, encoding as BufferEncoding, cb as () => void);
}; };
// 提供断开连接的方法 // 提供断开连接的方法
const disconnect = () => { const disconnect = () => {
process.stdout.write = originalStdoutWrite; process.stdout.write = originalStdoutWrite;
@@ -94,7 +53,6 @@ export function connectToNamedPipe(logger: LogWrapper, timeoutMs: number = 5000)
resolve({ disconnect }); resolve({ disconnect });
}); });
// 管道错误处理
pipeSocket.on('error', (err) => { pipeSocket.on('error', (err) => {
clearTimeout(timeoutId); clearTimeout(timeoutId);
process.stdout.write = originalStdoutWrite; process.stdout.write = originalStdoutWrite;
@@ -102,18 +60,11 @@ export function connectToNamedPipe(logger: LogWrapper, timeoutMs: number = 5000)
reject(err); reject(err);
}); });
// 管道关闭处理
pipeSocket.on('end', () => { pipeSocket.on('end', () => {
process.stdout.write = originalStdoutWrite; process.stdout.write = originalStdoutWrite;
logger.log('命名管道连接已关闭'); logger.log('命名管道连接已关闭');
}); });
// 确保在连接意外关闭时恢复stdout
pipeSocket.on('close', () => {
process.stdout.write = originalStdoutWrite;
logger.log('命名管道连接已关闭');
});
} catch (error) { } catch (error) {
clearTimeout(timeoutId); clearTimeout(timeoutId);
logger.log(`尝试连接命名管道 ${pipePath} 时发生异常:`, error); logger.log(`尝试连接命名管道 ${pipePath} 时发生异常:`, error);

View File

@@ -122,9 +122,7 @@ export async function InitWebUi(logger: LogWrapper, pathWrapper: NapCatPathWrapp
// ------------挂载路由------------ // ------------挂载路由------------
// 挂载静态路由(前端),路径为 /webui // 挂载静态路由(前端),路径为 /webui
app.use('/webui', express.static(pathWrapper.staticPath, { app.use('/webui', express.static(pathWrapper.staticPath));
maxAge: '1d'
}));
// 初始化WebSocket服务器 // 初始化WebSocket服务器
const sslCerts = await checkCertificates(logger); const sslCerts = await checkCertificates(logger);
const isHttps = !!sslCerts; const isHttps = !!sslCerts;

View File

@@ -47,9 +47,6 @@ export const CreateTerminalHandler: RequestHandler = async (req, res) => {
if (isMacOS) { if (isMacOS) {
return sendError(res, 'MacOS不支持终端'); return sendError(res, 'MacOS不支持终端');
} }
if ((await WebUiConfig.GetWebUIConfig()).token === 'napcat') {
return sendError(res, '默认密码禁止创建终端');
}
try { try {
const { cols, rows } = req.body; const { cols, rows } = req.body;
const { id } = terminalManager.createTerminal(cols, rows); const { id } = terminalManager.createTerminal(cols, rows);

View File

@@ -9,7 +9,7 @@ Object.defineProperty(global, '__dirname', {
// 注意:堆栈格式可能不同,请根据实际环境调整索引及正则表达式 // 注意:堆栈格式可能不同,请根据实际环境调整索引及正则表达式
for (const line of stack) { for (const line of stack) {
const match = line.match(/\((.*):\d+:\d+\)/); const match = line.match(/\((.*):\d+:\d+\)/);
if (match?.[1]) { if (match) {
callerFile = match[1]; callerFile = match[1];
if (!callerFile.includes('init-dynamic-dirname.ts')) { if (!callerFile.includes('init-dynamic-dirname.ts')) {
break; break;

View File

@@ -1,13 +1,13 @@
import multer from 'multer'; import multer from 'multer';
import { WebUiConfigWrapper } from '../helper/config';
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs';
import type { Request, Response } from 'express'; import type { Request, Response } from 'express';
import { WebUiConfig } from '@/webui';
export const webUIFontStorage = multer.diskStorage({ export const webUIFontStorage = multer.diskStorage({
destination: (_, __, cb) => { destination: (_, __, cb) => {
try { try {
const fontsPath = path.dirname(WebUiConfig.GetWebUIFontPath()); const fontsPath = path.dirname(WebUiConfigWrapper.GetWebUIFontPath());
// 确保字体目录存在 // 确保字体目录存在
fs.mkdirSync(fontsPath, { recursive: true }); fs.mkdirSync(fontsPath, { recursive: true });
cb(null, fontsPath); cb(null, fontsPath);

View File

@@ -7,7 +7,8 @@ import { builtinModules } from 'module';
const external = [ const external = [
'silk-wasm', 'silk-wasm',
'ws', 'ws',
'express' 'express',
'openai'
]; ];
const nodeModules = [...builtinModules, builtinModules.map((m) => `node:${m}`)].flat(); const nodeModules = [...builtinModules, builtinModules.map((m) => `node:${m}`)].flat();
@@ -53,7 +54,6 @@ const FrameworkBaseConfigPlugin: PluginOption[] = [
{ src: './napcat.webui/dist/', dest: 'dist/static/', flatten: false }, { src: './napcat.webui/dist/', dest: 'dist/static/', flatten: false },
{ src: './src/framework/liteloader.cjs', dest: 'dist' }, { src: './src/framework/liteloader.cjs', dest: 'dist' },
{ src: './src/framework/napcat.cjs', dest: 'dist' }, { src: './src/framework/napcat.cjs', dest: 'dist' },
{ src: './src/framework/nativeLoader.cjs', dest: 'dist' },
{ src: './src/framework/preload.cjs', dest: 'dist' }, { src: './src/framework/preload.cjs', dest: 'dist' },
{ src: './src/framework/renderer.js', dest: 'dist' }, { src: './src/framework/renderer.js', dest: 'dist' },
{ src: './package.json', dest: 'dist' }, { src: './package.json', dest: 'dist' },