mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
35 Commits
v4.7.76
...
base-rpc-s
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8243abaf0c | ||
![]() |
25be976fc9 | ||
![]() |
a8d8a94309 | ||
![]() |
eb9209cffb | ||
![]() |
06bc761dd3 | ||
![]() |
8994d3af14 | ||
![]() |
5eefd3dbe8 | ||
![]() |
2be014a9f2 | ||
![]() |
ee4c9e95ad | ||
![]() |
3e25172450 | ||
![]() |
ac51c50046 | ||
![]() |
a2cae1734b | ||
![]() |
88e9caddfa | ||
![]() |
f576cd9417 | ||
![]() |
9cfd224b74 | ||
![]() |
c12f8de8b4 | ||
![]() |
ed9a7c52e2 | ||
![]() |
38fcaaa28b | ||
![]() |
5317a1c1a9 | ||
![]() |
4bc5933ea2 | ||
![]() |
6a6bd33fe5 | ||
![]() |
8256942a3d | ||
![]() |
697632eee8 | ||
![]() |
6bbf5b254d | ||
![]() |
5831898c4a | ||
![]() |
2cc413bec1 | ||
![]() |
0af36e89d9 | ||
![]() |
b2c0f5d2e5 | ||
![]() |
80b74c7da9 | ||
![]() |
f14f13b158 | ||
![]() |
9dda00b6fa | ||
![]() |
a29debb738 | ||
![]() |
b990fc43df | ||
![]() |
915e9552ee | ||
![]() |
c522e0a386 |
@@ -4,7 +4,7 @@
|
||||
"name": "NapCatQQ",
|
||||
"slug": "NapCat.Framework",
|
||||
"description": "高性能的 OneBot 11 协议实现",
|
||||
"version": "4.7.75",
|
||||
"version": "4.7.78",
|
||||
"icon": "./logo.png",
|
||||
"authors": [
|
||||
{
|
||||
|
@@ -26,7 +26,7 @@ const itemVariants = {
|
||||
opacity: 1,
|
||||
scale: 1,
|
||||
y: 0,
|
||||
transition: { type: 'spring', stiffness: 300, damping: 20 }
|
||||
transition: { type: 'spring' as const, stiffness: 300, damping: 20 }
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -24,9 +24,7 @@ const oneBotHttpApiGroup = {
|
||||
},
|
||||
'/get_group_system_msg': {
|
||||
description: '获取群系统消息',
|
||||
request: z.object({
|
||||
group_id: z.union([z.string(), z.number()]).describe('群号')
|
||||
}),
|
||||
request: z.object({}),
|
||||
response: baseResponseSchema.extend({
|
||||
data: z.object({
|
||||
InvitedRequest: z
|
||||
@@ -37,6 +35,7 @@ const oneBotHttpApiGroup = {
|
||||
invitor_uin: z.string().describe('邀请人 QQ 号'),
|
||||
invitor_nick: z.string().describe('邀请人昵称'),
|
||||
group_id: z.string().describe('群号'),
|
||||
message: z.string().describe('入群回答'),
|
||||
group_name: z.string().describe('群名称'),
|
||||
checked: z.boolean().describe('是否已处理'),
|
||||
actor: z.string().describe('处理人 QQ 号')
|
||||
@@ -50,6 +49,7 @@ const oneBotHttpApiGroup = {
|
||||
requester_uin: z.string().describe('请求人 QQ 号'),
|
||||
requester_nick: z.string().describe('请求人昵称'),
|
||||
group_id: z.string().describe('群号'),
|
||||
message: z.string().describe('入群回答'),
|
||||
group_name: z.string().describe('群名称'),
|
||||
checked: z.boolean().describe('是否已处理'),
|
||||
actor: z.string().describe('处理人 QQ 号')
|
||||
@@ -604,7 +604,7 @@ const oneBotHttpApiGroup = {
|
||||
response: baseResponseSchema.extend({
|
||||
data: z
|
||||
.object({
|
||||
group_id: z.string().describe('群号'),
|
||||
group_id: z.number().describe('群号'),
|
||||
current_talkative: z
|
||||
.object({
|
||||
user_id: z.number().describe('QQ 号'),
|
||||
|
16
package.json
16
package.json
@@ -2,7 +2,7 @@
|
||||
"name": "napcat",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "4.7.75",
|
||||
"version": "4.7.78",
|
||||
"scripts": {
|
||||
"build:universal": "npm run build:webui && vite build --mode universal || exit 1",
|
||||
"build:framework": "npm run build:webui && vite build --mode framework || exit 1",
|
||||
@@ -41,29 +41,29 @@
|
||||
"ajv": "^8.13.0",
|
||||
"async-mutex": "^0.5.0",
|
||||
"commander": "^13.0.0",
|
||||
"compressing": "^1.10.1",
|
||||
"cors": "^2.8.5",
|
||||
"esbuild": "0.25.4",
|
||||
"esbuild": "0.25.5",
|
||||
"eslint": "^9.14.0",
|
||||
"eslint-import-resolver-typescript": "^4.0.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"express-rate-limit": "^7.5.0",
|
||||
"fast-xml-parser": "^4.3.6",
|
||||
"file-type": "^20.0.0",
|
||||
"file-type": "^21.0.0",
|
||||
"globals": "^16.0.0",
|
||||
"json5": "^2.2.3",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"multer": "^2.0.1",
|
||||
"napcat.protobuf": "^1.1.4",
|
||||
"typescript": "^5.3.3",
|
||||
"typescript-eslint": "^8.13.0",
|
||||
"vite": "^6.0.1",
|
||||
"vite-plugin-cp": "^6.0.0",
|
||||
"vite-tsconfig-paths": "^5.1.0",
|
||||
"napcat.protobuf": "^1.1.4",
|
||||
"winston": "^3.17.0",
|
||||
"compressing": "^1.10.1"
|
||||
"winston": "^3.17.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^5.0.0",
|
||||
"silk-wasm": "^3.6.1",
|
||||
"ws": "^8.18.0"
|
||||
}
|
||||
}
|
||||
}
|
@@ -10,9 +10,9 @@ interface InternalMapKey {
|
||||
checker: ((...args: any[]) => boolean) | undefined;
|
||||
}
|
||||
|
||||
type EnsureFunc<T> = T extends (...args: any) => any ? T : never;
|
||||
export type EnsureFunc<T> = T extends (...args: any) => any ? T : never;
|
||||
|
||||
type FuncKeys<T> = Extract<
|
||||
export type FuncKeys<T> = Extract<
|
||||
{
|
||||
[K in keyof T]: EnsureFunc<T[K]> extends never ? never : K;
|
||||
}[keyof T],
|
||||
|
@@ -20,3 +20,23 @@ export function proxyHandlerOf(logger: LogWrapper) {
|
||||
export function proxiedListenerOf<T extends object>(listener: T, logger: LogWrapper) {
|
||||
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());
|
||||
}
|
@@ -1 +1 @@
|
||||
export const napCatVersion = '4.7.75';
|
||||
export const napCatVersion = '4.7.78';
|
||||
|
@@ -142,7 +142,6 @@ export class NTQQMsgApi {
|
||||
}
|
||||
|
||||
async queryFirstMsgBySender(peer: Peer, SendersUid: string[]) {
|
||||
console.log(peer, SendersUid);
|
||||
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', '0', {
|
||||
chatInfo: peer,
|
||||
filterMsgType: [],
|
||||
|
@@ -264,7 +264,7 @@ export class NTQQWebApi {
|
||||
async getGroupHonorInfo(groupCode: string, getType: WebHonorType) {
|
||||
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
|
||||
let HonorInfo = {
|
||||
group_id: groupCode,
|
||||
group_id: Number(groupCode),
|
||||
current_talkative: {},
|
||||
talkative_list: [],
|
||||
performer_list: [],
|
||||
|
8
src/core/external/appid.json
vendored
8
src/core/external/appid.json
vendored
@@ -306,5 +306,13 @@
|
||||
"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"
|
||||
}
|
||||
}
|
8
src/core/external/offset.json
vendored
8
src/core/external/offset.json
vendored
@@ -394,5 +394,13 @@
|
||||
"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"
|
||||
}
|
||||
}
|
@@ -30,6 +30,10 @@ import os from 'node:os';
|
||||
import { NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/core/listeners';
|
||||
import { proxiedListenerOf } from '@/common/proxy-handler';
|
||||
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 './types';
|
||||
export * from './services';
|
||||
@@ -97,9 +101,63 @@ export class NapCatCore {
|
||||
constructor(context: InstanceContext, selfInfo: SelfInfo) {
|
||||
this.selfInfo = selfInfo;
|
||||
this.context = context;
|
||||
|
||||
this.util = this.context.wrapper.NodeQQNTWrapperUtil;
|
||||
this.eventWrapper = new NTEventWrapper(context.session);
|
||||
this.configLoader = new NapCatConfigLoader(this, this.context.pathWrapper.configPath,NapcatConfigSchema);
|
||||
|
||||
// 管道服务端测试
|
||||
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.apis = {
|
||||
FileApi: new NTQQFileApi(this.context, this),
|
||||
SystemApi: new NTQQSystemApi(this.context, this),
|
||||
@@ -251,13 +309,13 @@ export async function genSessionConfig(
|
||||
}
|
||||
|
||||
export interface InstanceContext {
|
||||
readonly workingEnv: NapCatCoreWorkingEnv;
|
||||
readonly wrapper: WrapperNodeApi;
|
||||
readonly session: NodeIQQNTWrapperSession;
|
||||
readonly logger: LogWrapper;
|
||||
readonly loginService: NodeIKernelLoginService;
|
||||
readonly basicInfoWrapper: QQBasicInfoWrapper;
|
||||
readonly pathWrapper: NapCatPathWrapper;
|
||||
session: NodeIQQNTWrapperSession;
|
||||
workingEnv: NapCatCoreWorkingEnv;
|
||||
wrapper: WrapperNodeApi;
|
||||
logger: LogWrapper;
|
||||
loginService: NodeIKernelLoginService;
|
||||
basicInfoWrapper: QQBasicInfoWrapper;
|
||||
pathWrapper: NapCatPathWrapper;
|
||||
}
|
||||
|
||||
export interface StableNTApiWrapper {
|
||||
|
@@ -40,7 +40,6 @@ export class NodeIKernelBuddyListener {
|
||||
}
|
||||
|
||||
onDelBatchBuddyInfos(_arg: unknown): any {
|
||||
console.log('onDelBatchBuddyInfos not implemented', ...arguments);
|
||||
}
|
||||
|
||||
onDoubtBuddyReqChange(_arg:
|
||||
|
@@ -1,71 +1,71 @@
|
||||
import { User, UserDetailInfoListenerArg } from '@/core/types';
|
||||
import { SelfStatusInfo, User, UserDetailInfoListenerArg } from '@/core/types';
|
||||
|
||||
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(...args: unknown[]): any {
|
||||
onSelfStatusChanged(_info: SelfStatusInfo): 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 {
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -16,6 +16,16 @@ export * from './NodeIKernelDbToolsService';
|
||||
export * from './NodeIKernelTipOffService';
|
||||
export * from './NodeIKernelSearchService';
|
||||
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 {
|
||||
NodeIKernelAvatarService,
|
||||
@@ -36,8 +46,19 @@ import type {
|
||||
NodeIKernelTicketService,
|
||||
NodeIKernelTipOffService,
|
||||
} 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 = {
|
||||
NodeIKernelAlbumService: NodeIKernelAlbumService;
|
||||
NodeIKernelAvatarService: NodeIKernelAvatarService;
|
||||
NodeIKernelBuddyService: NodeIKernelBuddyService;
|
||||
NodeIKernelFileAssistantService: NodeIKernelFileAssistantService;
|
||||
@@ -53,6 +74,15 @@ export type ServiceNamingMapping = {
|
||||
NodeIKernelRichMediaService: NodeIKernelRichMediaService;
|
||||
NodeIKernelDbToolsService: NodeIKernelDbToolsService;
|
||||
NodeIKernelTipOffService: NodeIKernelTipOffService;
|
||||
NodeIKernelSearchService: NodeIKernelSearchService,
|
||||
NodeIKernelSearchService: NodeIKernelSearchService;
|
||||
NodeIKernelCollectionService: NodeIKernelCollectionService;
|
||||
NodeIKernelECDHService: NodeIKernelECDHService;
|
||||
NodeIKernelNodeMiscService: NodeIKernelNodeMiscService;
|
||||
NodeIKernelMsgBackupService: NodeIKernelMsgBackupService;
|
||||
NodeIKernelTianShuService: NodeIKernelTianShuService;
|
||||
NodeIKernelUnitedConfigService: NodeIKernelUnitedConfigService;
|
||||
NodeIkernelTestPerformanceService: NodeIkernelTestPerformanceService;
|
||||
NodeIKernelUixConvertService: NodeIKernelUixConvertService;
|
||||
NodeIKernelMSFService: NodeIKernelMSFService;
|
||||
NodeIKernelRecentContactService: NodeIKernelRecentContactService;
|
||||
};
|
||||
|
@@ -58,6 +58,7 @@ export interface GrayTipRovokeElement {
|
||||
operatorUid: string;
|
||||
operatorNick: string;
|
||||
operatorRemark: string;
|
||||
isSelfOperate: boolean; // 是否是自己撤回的
|
||||
operatorMemRemark?: string;
|
||||
wording: string; // 自定义的撤回提示语
|
||||
}
|
||||
|
@@ -4,7 +4,10 @@ import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
group_id: Type.Union([Type.Number(), Type.String()]),
|
||||
folder_name: Type.String(),
|
||||
// 兼容gocq 与name二选一
|
||||
folder_name: Type.Optional(Type.String()),
|
||||
// 兼容gocq 与folder_name二选一
|
||||
name: Type.Optional(Type.String()),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
@@ -16,6 +19,7 @@ export class CreateGroupFileFolder extends OneBotAction<Payload, ResponseType>
|
||||
override actionName = ActionName.GoCQHTTP_CreateGroupFileFolder;
|
||||
override payloadSchema = SchemaData;
|
||||
async _handle(payload: Payload) {
|
||||
return (await this.core.apis.GroupApi.creatGroupFileFolder(payload.group_id.toString(), payload.folder_name)).resultWithGroupItem;
|
||||
const folderName = payload.folder_name || payload.name;
|
||||
return (await this.core.apis.GroupApi.creatGroupFileFolder(payload.group_id.toString(), folderName!)).resultWithGroupItem;
|
||||
}
|
||||
}
|
||||
|
27
src/onebot/action/group/GetGroupDetailInfo.ts
Normal file
27
src/onebot/action/group/GetGroupDetailInfo.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
@@ -4,6 +4,7 @@ import { ActionName } from '@/onebot/action/router';
|
||||
import { Notify } from '@/onebot/types';
|
||||
|
||||
interface RetData {
|
||||
invited_requests: Notify[];
|
||||
InvitedRequest: Notify[];
|
||||
join_requests: Notify[];
|
||||
}
|
||||
@@ -13,7 +14,7 @@ export class GetGroupIgnoredNotifies extends OneBotAction<void, RetData> {
|
||||
|
||||
async _handle(): Promise<RetData> {
|
||||
const SingleScreenNotifies = await this.core.apis.GroupApi.getSingleScreenNotifies(false, 50);
|
||||
const retData: RetData = { InvitedRequest: [], join_requests: [] };
|
||||
const retData: RetData = { invited_requests: [], InvitedRequest: [], join_requests: [] };
|
||||
|
||||
const notifyPromises = SingleScreenNotifies.map(async (SSNotify) => {
|
||||
const invitorUin = SSNotify.user1?.uid ? +await this.core.apis.UserApi.getUinByUidV2(SSNotify.user1.uid) : 0;
|
||||
@@ -38,7 +39,7 @@ export class GetGroupIgnoredNotifies extends OneBotAction<void, RetData> {
|
||||
});
|
||||
|
||||
await Promise.all(notifyPromises);
|
||||
|
||||
retData.invited_requests = retData.InvitedRequest;
|
||||
return retData;
|
||||
}
|
||||
}
|
@@ -8,10 +8,16 @@ interface GroupNotice {
|
||||
notice_id: string;
|
||||
message: {
|
||||
text: string
|
||||
// 保持一段时间兼容性 防止以往版本出现问题 后续版本可考虑移除
|
||||
image: Array<{
|
||||
height: string
|
||||
width: string
|
||||
id: string
|
||||
}>,
|
||||
images: Array<{
|
||||
height: string
|
||||
width: string
|
||||
id: string
|
||||
}>
|
||||
};
|
||||
}
|
||||
@@ -40,15 +46,18 @@ export class GetGroupNotice extends OneBotAction<Payload, GroupNotice[]> {
|
||||
continue;
|
||||
}
|
||||
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 = {
|
||||
notice_id: retApiNotice.fid,
|
||||
sender_id: retApiNotice.u,
|
||||
publish_time: retApiNotice.pubt,
|
||||
message: {
|
||||
text: retApiNotice.msg.text,
|
||||
image: retApiNotice.msg.pics?.map((pic) => {
|
||||
return { id: pic.id, height: pic.h, width: pic.w };
|
||||
}) || [],
|
||||
image,
|
||||
images: image,
|
||||
},
|
||||
};
|
||||
retNotices.push(retNotice);
|
||||
|
@@ -120,10 +120,14 @@ 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) {
|
||||
|
||||
const actionHandlers = [
|
||||
new GetGroupDetailInfo(obContext, core),
|
||||
new SetGroupKickMembers(obContext, core),
|
||||
new SetGroupAddOption(obContext, core),
|
||||
new SetGroupRobotAddOption(obContext, core),
|
||||
@@ -255,6 +259,8 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
|
||||
new GetPrivateFileUrl(obContext, core),
|
||||
new GetUnidirectionalFriendList(obContext, core),
|
||||
new CleanCache(obContext, core),
|
||||
new GetGroupAddRequest(obContext, core),
|
||||
new GetCollectionList(obContext, core),
|
||||
];
|
||||
|
||||
type HandlerUnion = typeof actionHandlers[number];
|
||||
|
@@ -19,6 +19,7 @@ import { rawMsgWithSendMsg } from '@/core/packet/message/converter';
|
||||
export interface ReturnDataType {
|
||||
message_id: number;
|
||||
res_id?: string;
|
||||
forward_id?: string;
|
||||
}
|
||||
|
||||
export enum ContextMode {
|
||||
@@ -147,7 +148,10 @@ export class SendMsgBase extends OneBotAction<OB11PostSendMsg, ReturnDataType> {
|
||||
peerUid: peer.peerUid,
|
||||
chatType: peer.chatType,
|
||||
}, (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) {
|
||||
throw Error(`发送转发消息(res_id:${returnMsgAndResId.res_id} 失败`);
|
||||
}
|
||||
|
@@ -132,6 +132,7 @@ export const ActionName = {
|
||||
FetchEmojiLike: 'fetch_emoji_like',
|
||||
SetInputStatus: 'set_input_status',
|
||||
GetGroupInfoEx: 'get_group_info_ex',
|
||||
GetGroupDetailInfo: 'get_group_detail_info',
|
||||
GetGroupIgnoreAddRequest: 'get_group_ignore_add_request',
|
||||
DelGroupNotice: '_del_group_notice',
|
||||
FriendPoke: 'friend_poke',
|
||||
|
@@ -4,6 +4,7 @@ import { ActionName } from '@/onebot/action/router';
|
||||
import { Notify } from '@/onebot/types';
|
||||
|
||||
interface RetData {
|
||||
invited_requests: Notify[];
|
||||
InvitedRequest: Notify[];
|
||||
join_requests: Notify[];
|
||||
}
|
||||
@@ -13,7 +14,7 @@ export class GetGroupSystemMsg extends OneBotAction<void, RetData> {
|
||||
|
||||
async _handle(): Promise<RetData> {
|
||||
const SingleScreenNotifies = await this.core.apis.GroupApi.getSingleScreenNotifies(false, 50);
|
||||
const retData: RetData = { InvitedRequest: [], join_requests: [] };
|
||||
const retData: RetData = { invited_requests: [], InvitedRequest: [], join_requests: [] };
|
||||
|
||||
const notifyPromises = SingleScreenNotifies.map(async (SSNotify) => {
|
||||
const invitorUin = SSNotify.user1?.uid ? +await this.core.apis.UserApi.getUinByUidV2(SSNotify.user1.uid) : 0;
|
||||
@@ -39,6 +40,7 @@ export class GetGroupSystemMsg extends OneBotAction<void, RetData> {
|
||||
|
||||
await Promise.all(notifyPromises);
|
||||
|
||||
retData.invited_requests = retData.InvitedRequest;
|
||||
return retData;
|
||||
}
|
||||
}
|
@@ -270,7 +270,6 @@ export class NapCatOneBot11Adapter {
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
msgListener.onAddSendMsg = async (msg) => {
|
||||
try {
|
||||
if (msg.sendStatus == SendStatusType.KSEND_STATUS_SENDING) {
|
||||
@@ -305,8 +304,18 @@ export class NapCatOneBot11Adapter {
|
||||
peerUid: uid,
|
||||
guildId: ''
|
||||
};
|
||||
const msg = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeq(peer, msgSeq)).msgList.find(e => e.msgType == NTMsgType.KMSGTYPEGRAYTIPS);
|
||||
let 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);
|
||||
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) {
|
||||
const recallEvent = await this.emitRecallMsg(msg, element);
|
||||
try {
|
||||
@@ -317,6 +326,7 @@ export class NapCatOneBot11Adapter {
|
||||
this.context.logger.logError('处理消息撤回失败', e);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
msgListener.onKickedOffLine = async (kick) => {
|
||||
const event = new BotOfflineEvent(this.core, kick.tipsTitle, kick.tipsDesc);
|
||||
|
@@ -87,8 +87,8 @@ export class OB11HttpServerAdapter extends IOB11NetworkAdapter<HttpServerConfig>
|
||||
this.app.use(async (req, res) => {
|
||||
await this.handleRequest(req, res);
|
||||
});
|
||||
this.server.listen(this.config.port, () => {
|
||||
this.core.context.logger.log(`[OneBot] [HTTP Server Adapter] Start On Port ${this.config.port}`);
|
||||
this.server.listen(this.config.port, this.config.host, () => {
|
||||
this.core.context.logger.log(`[OneBot] [HTTP Server Adapter] Start On ${this.config.host}:${this.config.port}`);
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -1,82 +0,0 @@
|
||||
# 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
|
||||
|
536
src/remote/pipe.ts
Normal file
536
src/remote/pipe.ts
Normal file
@@ -0,0 +1,536 @@
|
||||
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 };
|
109
src/remote/remoteSession.ts
Normal file
109
src/remote/remoteSession.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
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; }
|
||||
}
|
1210
src/remote/serialize.cpp
Normal file
1210
src/remote/serialize.cpp
Normal file
File diff suppressed because it is too large
Load Diff
131
src/remote/serialize.ts
Normal file
131
src/remote/serialize.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
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 };
|
114
src/remote/service.ts
Normal file
114
src/remote/service.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
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();
|
||||
}
|
23
src/remote/wrapper.ts
Normal file
23
src/remote/wrapper.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
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,
|
||||
};
|
@@ -1,13 +1,13 @@
|
||||
import multer from 'multer';
|
||||
import { WebUiConfigWrapper } from '../helper/config';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import type { Request, Response } from 'express';
|
||||
import { WebUiConfig } from '@/webui';
|
||||
|
||||
export const webUIFontStorage = multer.diskStorage({
|
||||
destination: (_, __, cb) => {
|
||||
try {
|
||||
const fontsPath = path.dirname(WebUiConfigWrapper.GetWebUIFontPath());
|
||||
const fontsPath = path.dirname(WebUiConfig.GetWebUIFontPath());
|
||||
// 确保字体目录存在
|
||||
fs.mkdirSync(fontsPath, { recursive: true });
|
||||
cb(null, fontsPath);
|
||||
|
Reference in New Issue
Block a user