mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2024-11-16 04:45:46 +00:00
Merge branch 'main' of https://github.com/NapNeko/NapCatQQ
This commit is contained in:
commit
b463140de7
10
package.json
10
package.json
@ -44,13 +44,13 @@
|
||||
"vite": "^5.2.6",
|
||||
"vite-plugin-cp": "^4.0.8",
|
||||
"vite-tsconfig-paths": "^5.1.0",
|
||||
"winston": "^3.17.0"
|
||||
"winston": "^3.17.0",
|
||||
"fluent-ffmpeg": "^2.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^5.0.0",
|
||||
"fluent-ffmpeg": "^2.1.2",
|
||||
"qrcode-terminal": "^0.12.0",
|
||||
"silk-wasm": "^3.6.1",
|
||||
"ws": "^8.18.0"
|
||||
"ws": "^8.18.0",
|
||||
"qrcode-terminal": "^0.12.0"
|
||||
}
|
||||
}
|
||||
}
|
@ -21,9 +21,9 @@ type FuncKeys<T> = Extract<
|
||||
export type ListenerClassBase = Record<string, string>;
|
||||
|
||||
export class NTEventWrapper {
|
||||
private WrapperSession: NodeIQQNTWrapperSession | undefined; //WrapperSession
|
||||
private listenerManager: Map<string, ListenerClassBase> = new Map<string, ListenerClassBase>(); //ListenerName-Unique -> Listener实例
|
||||
private EventTask = new Map<string, Map<string, Map<string, InternalMapKey>>>(); //tasks ListenerMainName -> ListenerSubName-> uuid -> {timeout,createtime,func}
|
||||
private readonly WrapperSession: NodeIQQNTWrapperSession | undefined; //WrapperSession
|
||||
private readonly listenerManager: Map<string, ListenerClassBase> = new Map<string, ListenerClassBase>(); //ListenerName-Unique -> Listener实例
|
||||
private readonly EventTask = new Map<string, Map<string, Map<string, InternalMapKey>>>(); //tasks ListenerMainName -> ListenerSubName-> uuid -> {timeout,createtime,func}
|
||||
|
||||
constructor(
|
||||
wrapperSession: NodeIQQNTWrapperSession,
|
||||
@ -120,9 +120,9 @@ export class NTEventWrapper {
|
||||
ListenerType extends (...args: any) => any = EnsureFunc<ListenerNamingMapping[Listener][ListenerMethod]>,
|
||||
>(
|
||||
listenerAndMethod: `${Listener}/${ListenerMethod}`,
|
||||
checker: (...args: Parameters<ListenerType>) => boolean,
|
||||
waitTimes = 1,
|
||||
timeout = 5000,
|
||||
checker: (...args: Parameters<ListenerType>) => boolean,
|
||||
) {
|
||||
return new Promise<Parameters<ListenerType>>((resolve, reject) => {
|
||||
const ListenerNameList = listenerAndMethod.split('/');
|
||||
@ -181,36 +181,36 @@ export class NTEventWrapper {
|
||||
callbackTimesToWait = 1,
|
||||
timeout = 5000,
|
||||
) {
|
||||
const id = randomUUID();
|
||||
let complete = 0;
|
||||
let retData: Parameters<ListenerType> | undefined = undefined;
|
||||
let retEvent: any = {};
|
||||
|
||||
function sendDataCallback(resolve: any, reject: any) {
|
||||
if (complete == 0) {
|
||||
reject(
|
||||
new Error(
|
||||
'Timeout: NTEvent serviceAndMethod:' +
|
||||
serviceAndMethod +
|
||||
' ListenerName:' +
|
||||
listenerAndMethod +
|
||||
' EventRet:\n' +
|
||||
JSON.stringify(retEvent, null, 4) +
|
||||
'\n',
|
||||
),
|
||||
);
|
||||
} else {
|
||||
resolve([retEvent as Awaited<ReturnType<EventType>>, ...retData!]);
|
||||
}
|
||||
}
|
||||
|
||||
const ListenerNameList = listenerAndMethod.split('/');
|
||||
const ListenerMainName = ListenerNameList[0];
|
||||
const ListenerSubName = ListenerNameList[1];
|
||||
|
||||
return new Promise<[EventRet: Awaited<ReturnType<EventType>>, ...Parameters<ListenerType>]>(
|
||||
async (resolve, reject) => {
|
||||
const id = randomUUID();
|
||||
let complete = 0;
|
||||
let retData: Parameters<ListenerType> | undefined = undefined;
|
||||
let retEvent: any = {};
|
||||
|
||||
function sendDataCallback() {
|
||||
if (complete == 0) {
|
||||
reject(
|
||||
new Error(
|
||||
'Timeout: NTEvent serviceAndMethod:' +
|
||||
serviceAndMethod +
|
||||
' ListenerName:' +
|
||||
listenerAndMethod +
|
||||
' EventRet:\n' +
|
||||
JSON.stringify(retEvent, null, 4) +
|
||||
'\n',
|
||||
),
|
||||
);
|
||||
} else {
|
||||
resolve([retEvent as Awaited<ReturnType<EventType>>, ...retData!]);
|
||||
}
|
||||
}
|
||||
|
||||
const ListenerNameList = listenerAndMethod.split('/');
|
||||
const ListenerMainName = ListenerNameList[0];
|
||||
const ListenerSubName = ListenerNameList[1];
|
||||
|
||||
const timeoutRef = setTimeout(sendDataCallback, timeout);
|
||||
(resolve, reject) => {
|
||||
const timeoutRef = setTimeout(() => sendDataCallback(resolve, reject), timeout);
|
||||
|
||||
const eventCallback = {
|
||||
timeout: timeout,
|
||||
@ -221,7 +221,7 @@ export class NTEventWrapper {
|
||||
retData = args as Parameters<ListenerType>;
|
||||
if (complete >= callbackTimesToWait) {
|
||||
clearTimeout(timeoutRef);
|
||||
sendDataCallback();
|
||||
sendDataCallback(resolve, reject);
|
||||
}
|
||||
},
|
||||
};
|
||||
@ -233,23 +233,26 @@ export class NTEventWrapper {
|
||||
}
|
||||
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallback);
|
||||
this.createListenerFunction(ListenerMainName);
|
||||
const eventFunction = this.createEventFunction(serviceAndMethod);
|
||||
retEvent = await eventFunction!(...(args));
|
||||
if (!checkerEvent(retEvent) && timeoutRef.hasRef()) {
|
||||
clearTimeout(timeoutRef);
|
||||
reject(
|
||||
new Error(
|
||||
'EventChecker Failed: NTEvent serviceAndMethod:' +
|
||||
serviceAndMethod +
|
||||
' ListenerName:' +
|
||||
listenerAndMethod +
|
||||
' EventRet:\n' +
|
||||
JSON.stringify(retEvent, null, 4) +
|
||||
'\n',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
this.createEventFunction(serviceAndMethod)!(...(args))
|
||||
.then((eventResult: any) => {
|
||||
retEvent = eventResult;
|
||||
if (!checkerEvent(retEvent) && timeoutRef.hasRef()) {
|
||||
clearTimeout(timeoutRef);
|
||||
reject(
|
||||
new Error(
|
||||
'EventChecker Failed: NTEvent serviceAndMethod:' +
|
||||
serviceAndMethod +
|
||||
' ListenerName:' +
|
||||
listenerAndMethod +
|
||||
' EventRet:\n' +
|
||||
JSON.stringify(retEvent, null, 4) +
|
||||
'\n',
|
||||
),
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch(reject);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -54,11 +54,7 @@ export class ForwardMsgBuilder {
|
||||
const id = crypto.randomUUID();
|
||||
const isGroupMsg = msg.some(m => m.isGroupMsg);
|
||||
if (!source) {
|
||||
source = isGroupMsg ? "群聊的聊天记录" :
|
||||
msg.length
|
||||
? Array.from(new Set(msg.slice(0, 4).map(m => m.senderName)))
|
||||
.join('和') + '的聊天记录'
|
||||
: '聊天记录';
|
||||
source = isGroupMsg ? "群聊的聊天记录" : msg.map(m => m.senderName).filter((v, i, a) => a.indexOf(v) === i).slice(0, 4).join('和') + '的聊天记录';
|
||||
}
|
||||
if (!news) {
|
||||
news = msg.length === 0 ? [{
|
||||
@ -111,7 +107,7 @@ export class ForwardMsgBuilder {
|
||||
senderName: msg.senderName,
|
||||
isGroupMsg: msg.groupId !== undefined,
|
||||
msg: msg.msg.map(m => ({
|
||||
preview: m.valid? m.toPreview() : "[该消息类型暂不支持查看]",
|
||||
preview: m.valid ? m.toPreview() : "[该消息类型暂不支持查看]",
|
||||
}))
|
||||
})), source, news, summary, prompt);
|
||||
}
|
||||
|
@ -74,26 +74,30 @@ export class LogWrapper {
|
||||
}
|
||||
files.forEach(file => {
|
||||
const filePath = path.join(logDir, file);
|
||||
fs.stat(filePath, (err, stats) => {
|
||||
this.deleteOldLogFile(filePath, oneWeekAgo);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private deleteOldLogFile(filePath: string, oneWeekAgo: number) {
|
||||
fs.stat(filePath, (err, stats) => {
|
||||
if (err) {
|
||||
this.logger.error('Failed to get file stats', err);
|
||||
return;
|
||||
}
|
||||
if (stats.mtime.getTime() < oneWeekAgo) {
|
||||
fs.unlink(filePath, err => {
|
||||
if (err) {
|
||||
this.logger.error('Failed to get file stats', err);
|
||||
return;
|
||||
}
|
||||
if (stats.mtime.getTime() < oneWeekAgo) {
|
||||
fs.unlink(filePath, err => {
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
this.logger.warn(`File already deleted: ${file}`);
|
||||
} else {
|
||||
this.logger.error('Failed to delete old log file', err);
|
||||
}
|
||||
} else {
|
||||
this.logger.info(`Deleted old log file: ${file}`);
|
||||
}
|
||||
});
|
||||
if (err.code === 'ENOENT') {
|
||||
this.logger.warn(`File already deleted: ${filePath}`);
|
||||
} else {
|
||||
this.logger.error('Failed to delete old log file', err);
|
||||
}
|
||||
} else {
|
||||
this.logger.info(`Deleted old log file: ${filePath}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -198,7 +202,7 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
|
||||
tokens.push(`群聊 [${msg.peerName}(${msg.peerUin})]`);
|
||||
}
|
||||
if (msg.senderUin !== '0') {
|
||||
tokens.push(`[${msg.sendMemberName || msg.sendRemarkName || msg.sendNickName}(${msg.senderUin})]`);
|
||||
tokens.push(`[${msg.sendMemberName ?? msg.sendRemarkName ?? msg.sendNickName}(${msg.senderUin})]`);
|
||||
}
|
||||
} else if (msg.chatType == ChatType.KCHATTYPEDATALINE) {
|
||||
tokens.push('移动设备');
|
||||
@ -206,76 +210,85 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
|
||||
tokens.push(`临时消息 (${msg.peerUin})`);
|
||||
}
|
||||
|
||||
function msgElementToText(element: MessageElement) {
|
||||
if (element.textElement) {
|
||||
if (element.textElement.atType === AtType.notAt) {
|
||||
const originalContentLines = element.textElement.content.split('\n');
|
||||
return `${originalContentLines[0]}${originalContentLines.length > 1 ? ' ...' : ''}`;
|
||||
} else if (element.textElement.atType === AtType.atAll) {
|
||||
return `@全体成员`;
|
||||
} else if (element.textElement.atType === AtType.atUser) {
|
||||
return `${element.textElement.content} (${element.textElement.atUid})`;
|
||||
}
|
||||
}
|
||||
|
||||
if (element.replyElement) {
|
||||
const recordMsgOrNull = msg.records.find(
|
||||
record => element.replyElement!.sourceMsgIdInRecords === record.msgId,
|
||||
);
|
||||
return `[回复消息 ${recordMsgOrNull &&
|
||||
recordMsgOrNull.peerUin != '284840486' && recordMsgOrNull.peerUin != '1094950020'
|
||||
?
|
||||
rawMessageToText(recordMsgOrNull, recursiveLevel + 1) :
|
||||
`未找到消息记录 (MsgId = ${element.replyElement.sourceMsgIdInRecords})`
|
||||
}]`;
|
||||
}
|
||||
|
||||
if (element.picElement) {
|
||||
return '[图片]';
|
||||
}
|
||||
|
||||
if (element.fileElement) {
|
||||
return `[文件 ${element.fileElement.fileName}]`;
|
||||
}
|
||||
|
||||
if (element.videoElement) {
|
||||
return '[视频]';
|
||||
}
|
||||
|
||||
if (element.pttElement) {
|
||||
return `[语音 ${element.pttElement.duration}s]`;
|
||||
}
|
||||
|
||||
if (element.arkElement) {
|
||||
return '[卡片消息]';
|
||||
}
|
||||
|
||||
if (element.faceElement) {
|
||||
return `[表情 ${element.faceElement.faceText ?? ''}]`;
|
||||
}
|
||||
|
||||
if (element.marketFaceElement) {
|
||||
return element.marketFaceElement.faceName;
|
||||
}
|
||||
|
||||
if (element.markdownElement) {
|
||||
return '[Markdown 消息]';
|
||||
}
|
||||
|
||||
if (element.multiForwardMsgElement) {
|
||||
return '[转发消息]';
|
||||
}
|
||||
|
||||
if (element.elementType === ElementType.GreyTip) {
|
||||
return '[灰条消息]';
|
||||
}
|
||||
|
||||
return `[未实现 (ElementType = ${element.elementType})]`;
|
||||
}
|
||||
|
||||
for (const element of msg.elements) {
|
||||
tokens.push(msgElementToText(element));
|
||||
tokens.push(msgElementToText(element, msg, recursiveLevel));
|
||||
}
|
||||
|
||||
return tokens.join(' ');
|
||||
}
|
||||
|
||||
function msgElementToText(element: MessageElement, msg: RawMessage, recursiveLevel: number): string {
|
||||
if (element.textElement) {
|
||||
return textElementToText(element.textElement);
|
||||
}
|
||||
|
||||
if (element.replyElement) {
|
||||
return replyElementToText(element.replyElement, msg, recursiveLevel);
|
||||
}
|
||||
|
||||
if (element.picElement) {
|
||||
return '[图片]';
|
||||
}
|
||||
|
||||
if (element.fileElement) {
|
||||
return `[文件 ${element.fileElement.fileName}]`;
|
||||
}
|
||||
|
||||
if (element.videoElement) {
|
||||
return '[视频]';
|
||||
}
|
||||
|
||||
if (element.pttElement) {
|
||||
return `[语音 ${element.pttElement.duration}s]`;
|
||||
}
|
||||
|
||||
if (element.arkElement) {
|
||||
return '[卡片消息]';
|
||||
}
|
||||
|
||||
if (element.faceElement) {
|
||||
return `[表情 ${element.faceElement.faceText ?? ''}]`;
|
||||
}
|
||||
|
||||
if (element.marketFaceElement) {
|
||||
return element.marketFaceElement.faceName;
|
||||
}
|
||||
|
||||
if (element.markdownElement) {
|
||||
return '[Markdown 消息]';
|
||||
}
|
||||
|
||||
if (element.multiForwardMsgElement) {
|
||||
return '[转发消息]';
|
||||
}
|
||||
|
||||
if (element.elementType === ElementType.GreyTip) {
|
||||
return '[灰条消息]';
|
||||
}
|
||||
|
||||
return `[未实现 (ElementType = ${element.elementType})]`;
|
||||
}
|
||||
|
||||
function textElementToText(textElement: any): string {
|
||||
if (textElement.atType === AtType.notAt) {
|
||||
const originalContentLines = textElement.content.split('\n');
|
||||
return `${originalContentLines[0]}${originalContentLines.length > 1 ? ' ...' : ''}`;
|
||||
} else if (textElement.atType === AtType.atAll) {
|
||||
return `@全体成员`;
|
||||
} else if (textElement.atType === AtType.atUser) {
|
||||
return `${textElement.content} (${textElement.atUid})`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function replyElementToText(replyElement: any, msg: RawMessage, recursiveLevel: number): string {
|
||||
const recordMsgOrNull = msg.records.find(
|
||||
record => replyElement.sourceMsgIdInRecords === record.msgId,
|
||||
);
|
||||
return `[回复消息 ${recordMsgOrNull &&
|
||||
recordMsgOrNull.peerUin != '284840486' && recordMsgOrNull.peerUin != '1094950020'
|
||||
?
|
||||
rawMessageToText(recordMsgOrNull, recursiveLevel + 1) :
|
||||
`未找到消息记录 (MsgId = ${replyElement.sourceMsgIdInRecords})`
|
||||
}]`;
|
||||
}
|
@ -30,4 +30,13 @@ export class LRUCache<K, V> {
|
||||
}
|
||||
this.cache.set(key, value);
|
||||
}
|
||||
public resetCapacity(newCapacity: number): void {
|
||||
this.capacity = newCapacity;
|
||||
while (this.cache.size > this.capacity) {
|
||||
const firstKey = this.cache.keys().next().value;
|
||||
if (firstKey !== undefined) {
|
||||
this.cache.delete(firstKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,8 +2,8 @@ import { Peer } from '@/core';
|
||||
import crypto from 'crypto';
|
||||
|
||||
export class LimitedHashTable<K, V> {
|
||||
private keyToValue: Map<K, V> = new Map();
|
||||
private valueToKey: Map<V, K> = new Map();
|
||||
private readonly keyToValue: Map<K, V> = new Map();
|
||||
private readonly valueToKey: Map<V, K> = new Map();
|
||||
private maxSize: number;
|
||||
|
||||
constructor(maxSize: number) {
|
||||
@ -75,8 +75,8 @@ export class LimitedHashTable<K, V> {
|
||||
}
|
||||
|
||||
class MessageUniqueWrapper {
|
||||
private msgDataMap: LimitedHashTable<string, number>;
|
||||
private msgIdMap: LimitedHashTable<string, number>;
|
||||
private readonly msgDataMap: LimitedHashTable<string, number>;
|
||||
private readonly msgIdMap: LimitedHashTable<string, number>;
|
||||
|
||||
constructor(maxMap: number = 1000) {
|
||||
this.msgIdMap = new LimitedHashTable<string, number>(maxMap);
|
||||
|
@ -9,48 +9,47 @@ export class RequestUtil {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = client.get(url, (res) => {
|
||||
let cookies: { [key: string]: string } = {};
|
||||
const handleRedirect = (res: http.IncomingMessage) => {
|
||||
//console.log(res.headers.location);
|
||||
if (res.statusCode === 301 || res.statusCode === 302) {
|
||||
if (res.headers.location) {
|
||||
const redirectUrl = new URL(res.headers.location, url);
|
||||
RequestUtil.HttpsGetCookies(redirectUrl.href).then((redirectCookies) => {
|
||||
// 合并重定向过程中的cookies
|
||||
cookies = { ...cookies, ...redirectCookies };
|
||||
resolve(cookies);
|
||||
}).catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
} else {
|
||||
resolve(cookies);
|
||||
}
|
||||
} else {
|
||||
resolve(cookies);
|
||||
}
|
||||
};
|
||||
res.on('data', () => {
|
||||
}); // Necessary to consume the stream
|
||||
|
||||
res.on('data', () => { }); // Necessary to consume the stream
|
||||
res.on('end', () => {
|
||||
handleRedirect(res);
|
||||
this.handleRedirect(res, url, cookies)
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
|
||||
if (res.headers['set-cookie']) {
|
||||
//console.log(res.headers['set-cookie']);
|
||||
res.headers['set-cookie'].forEach((cookie) => {
|
||||
const parts = cookie.split(';')[0].split('=');
|
||||
const key = parts[0];
|
||||
const value = parts[1];
|
||||
if (key && value && key.length > 0 && value.length > 0) {
|
||||
cookies[key] = value;
|
||||
}
|
||||
});
|
||||
this.extractCookies(res.headers['set-cookie'], cookies);
|
||||
}
|
||||
});
|
||||
|
||||
req.on('error', (error: any) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static async handleRedirect(res: http.IncomingMessage, url: string, cookies: { [key: string]: string }): Promise<{ [key: string]: string }> {
|
||||
if (res.statusCode === 301 || res.statusCode === 302) {
|
||||
if (res.headers.location) {
|
||||
const redirectUrl = new URL(res.headers.location, url);
|
||||
const redirectCookies = await this.HttpsGetCookies(redirectUrl.href);
|
||||
// 合并重定向过程中的cookies
|
||||
return { ...cookies, ...redirectCookies };
|
||||
}
|
||||
}
|
||||
return cookies;
|
||||
}
|
||||
|
||||
private static extractCookies(setCookieHeaders: string[], cookies: { [key: string]: string }) {
|
||||
setCookieHeaders.forEach((cookie) => {
|
||||
const parts = cookie.split(';')[0].split('=');
|
||||
const key = parts[0];
|
||||
const value = parts[1];
|
||||
if (key && value && key.length > 0 && value.length > 0) {
|
||||
cookies[key] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 请求和回复都是JSON data传原始内容 自动编码json
|
||||
static async HttpGetJson<T>(url: string, method: string = 'GET', data?: any, headers: {
|
||||
|
@ -300,18 +300,18 @@ export class NTQQFileApi {
|
||||
element.elementType === ElementType.FILE
|
||||
) {
|
||||
switch (element.elementType) {
|
||||
case ElementType.PIC:
|
||||
case ElementType.PIC:
|
||||
element.picElement!.sourcePath = elementResults[elementIndex];
|
||||
break;
|
||||
case ElementType.VIDEO:
|
||||
break;
|
||||
case ElementType.VIDEO:
|
||||
element.videoElement!.filePath = elementResults[elementIndex];
|
||||
break;
|
||||
case ElementType.PTT:
|
||||
break;
|
||||
case ElementType.PTT:
|
||||
element.pttElement!.filePath = elementResults[elementIndex];
|
||||
break;
|
||||
case ElementType.FILE:
|
||||
break;
|
||||
case ElementType.FILE:
|
||||
element.fileElement!.filePath = elementResults[elementIndex];
|
||||
break;
|
||||
break;
|
||||
}
|
||||
elementIndex++;
|
||||
}
|
||||
@ -357,15 +357,13 @@ export class NTQQFileApi {
|
||||
|
||||
async getImageSize(filePath: string): Promise<ISizeCalculationResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
imageSize(filePath, (err, dimensions) => {
|
||||
imageSize(filePath, (err: Error | null, dimensions) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
reject(new Error(err.message));
|
||||
} else if (!dimensions) {
|
||||
reject(new Error('获取图片尺寸失败'));
|
||||
} else {
|
||||
if (!dimensions) {
|
||||
reject(new Error('获取图片尺寸失败'));
|
||||
} else {
|
||||
resolve(dimensions);
|
||||
}
|
||||
resolve(dimensions);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -15,7 +15,7 @@ export class NTQQFriendApi {
|
||||
}
|
||||
async getBuddyV2SimpleInfoMap(refresh = false) {
|
||||
const buddyService = this.context.session.getBuddyService();
|
||||
const buddyListV2 = refresh ? await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) : await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL);
|
||||
const buddyListV2 = await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL);
|
||||
const uids = buddyListV2.data.flatMap(item => item.buddyUids);
|
||||
return await this.core.eventWrapper.callNoListenerEvent(
|
||||
'NodeIKernelProfileService/getCoreAndBaseInfo',
|
||||
@ -44,7 +44,7 @@ export class NTQQFriendApi {
|
||||
async getBuddyV2ExWithCate(refresh = false) {
|
||||
const categoryMap: Map<string, any> = new Map();
|
||||
const buddyService = this.context.session.getBuddyService();
|
||||
const buddyListV2 = refresh ? (await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL)).data : (await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL)).data;
|
||||
const buddyListV2 = (await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL)).data;
|
||||
const uids = buddyListV2.flatMap(item => {
|
||||
item.buddyUids.forEach(uid => {
|
||||
categoryMap.set(uid, { categoryId: item.categoryId, categoryName: item.categroyName });
|
||||
|
@ -25,9 +25,10 @@ export class NTQQGroupApi {
|
||||
constructor(context: InstanceContext, core: NapCatCore) {
|
||||
this.context = context;
|
||||
this.core = core;
|
||||
this.initCache().then().catch(context.logger.logError.bind(context.logger));
|
||||
}
|
||||
|
||||
async initApi() {
|
||||
this.initCache().then().catch(this.context.logger.logError.bind(this.context.logger));
|
||||
}
|
||||
async initCache() {
|
||||
this.groups = await this.getGroups();
|
||||
for (const group of this.groups) {
|
||||
@ -54,7 +55,7 @@ export class NTQQGroupApi {
|
||||
}, pskey);
|
||||
}
|
||||
async getGroupShutUpMemberList(groupCode: string) {
|
||||
const data = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onShutUpMemberListChanged', 1, 1000, (group_id) => group_id === groupCode);
|
||||
const data = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onShutUpMemberListChanged', (group_id) => group_id === groupCode, 1, 1000);
|
||||
this.context.session.getGroupService().getGroupShutUpMemberList(groupCode);
|
||||
return (await data)[1];
|
||||
}
|
||||
@ -258,9 +259,9 @@ export class NTQQGroupApi {
|
||||
async getGroupMemberV2(GroupCode: string, uid: string, forced = false) {
|
||||
const Listener = this.core.eventWrapper.registerListen(
|
||||
'NodeIKernelGroupListener/onMemberInfoChange',
|
||||
(params, _, members) => params === GroupCode && members.size > 0,
|
||||
1,
|
||||
forced ? 5000 : 250,
|
||||
(params, _, members) => params === GroupCode && members.size > 0,
|
||||
);
|
||||
const retData = await (
|
||||
this.core.eventWrapper
|
||||
@ -318,13 +319,13 @@ export class NTQQGroupApi {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async tryGetGroupMembersV2(modeListener = false, groupQQ: string, num = 30, timeout = 100): Promise<{
|
||||
async tryGetGroupMembersV2(groupQQ: string, modeListener = false, num = 30, timeout = 100): Promise<{
|
||||
infos: Map<string, GroupMember>;
|
||||
finish: boolean;
|
||||
hasNext: boolean | undefined;
|
||||
}> {
|
||||
const sceneId = this.context.session.getGroupService().createMemberListScene(groupQQ, 'groupMemberList_MainWindow_1');
|
||||
const once = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onMemberListChange', 0, timeout, (params) => params.sceneId === sceneId)
|
||||
const once = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onMemberListChange', (params) => params.sceneId === sceneId, 0, timeout)
|
||||
.catch(() => { });
|
||||
const result = await this.context.session.getGroupService().getNextMemberList(sceneId, undefined, num);
|
||||
if (result.errCode !== 0) {
|
||||
@ -352,7 +353,7 @@ export class NTQQGroupApi {
|
||||
listenerMode: boolean;
|
||||
}> {
|
||||
const sceneId = this.context.session.getGroupService().createMemberListScene(groupQQ, 'groupMemberList_MainWindow_1');
|
||||
const once = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onMemberListChange', 0, timeout, (params) => params.sceneId === sceneId)
|
||||
const once = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onMemberListChange', (params) => params.sceneId === sceneId, 0, timeout)
|
||||
.catch(() => { });
|
||||
const result = await this.context.session.getGroupService().getNextMemberList(sceneId, undefined, num);
|
||||
if (result.errCode !== 0) {
|
||||
@ -371,7 +372,7 @@ export class NTQQGroupApi {
|
||||
infos: new Map([...(resMode2?.infos ?? []), ...result.result.infos]),
|
||||
finish: result.result.finish,
|
||||
hasNext: resMode2?.hasNext,
|
||||
listenerMode: resMode2?.hasNext !== undefined ? true : false
|
||||
listenerMode: resMode2?.hasNext !== undefined
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -144,7 +144,7 @@ export class NTQQMsgApi {
|
||||
params,
|
||||
],
|
||||
() => true,
|
||||
() => true, // Todo: 应当通过 groupFileListResult 判断
|
||||
() => true, // 应当通过 groupFileListResult 判断
|
||||
1,
|
||||
5000,
|
||||
);
|
||||
@ -194,7 +194,7 @@ export class NTQQMsgApi {
|
||||
async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
|
||||
//唉?!我有个想法
|
||||
if (peer.chatType === ChatType.KCHATTYPETEMPC2CFROMGROUP && peer.guildId && peer.guildId !== '') {
|
||||
const member = await this.core.apis.GroupApi.getGroupMember(peer.guildId, peer.peerUid!);
|
||||
const member = await this.core.apis.GroupApi.getGroupMember(peer.guildId, peer.peerUid);
|
||||
if (member) {
|
||||
await this.PrepareTempChat(peer.peerUid, peer.guildId, member.nick);
|
||||
}
|
||||
|
@ -26,14 +26,15 @@ export class NTQQPacketApi {
|
||||
this.context = context;
|
||||
this.core = core;
|
||||
this.logger = core.context.logger;
|
||||
this.InitSendPacket(this.context.basicInfoWrapper.getFullQQVesion())
|
||||
}
|
||||
async initApi() {
|
||||
await this.InitSendPacket(this.context.basicInfoWrapper.getFullQQVesion())
|
||||
.then()
|
||||
.catch((err) => {
|
||||
this.logger.logError.bind(this.core.context.logger);
|
||||
this.errStack.push(err);
|
||||
});
|
||||
}
|
||||
|
||||
get available(): boolean {
|
||||
return this.pkt?.available ?? false;
|
||||
}
|
||||
|
@ -84,11 +84,10 @@ export function getMajorPath(QQVersion: string): string {
|
||||
}
|
||||
export class NapCatCore {
|
||||
readonly context: InstanceContext;
|
||||
readonly apis: StableNTApiWrapper;
|
||||
readonly eventWrapper: NTEventWrapper;
|
||||
// readonly eventChannel: NTEventChannel;
|
||||
NapCatDataPath: string;
|
||||
NapCatTempPath: string;
|
||||
NapCatDataPath: string = '';
|
||||
NapCatTempPath: string = '';
|
||||
apis: StableNTApiWrapper;
|
||||
// runtime info, not readonly
|
||||
selfInfo: SelfInfo;
|
||||
util: NodeQQNTWrapperUtil;
|
||||
@ -112,6 +111,8 @@ export class NapCatCore {
|
||||
UserApi: new NTQQUserApi(this.context, this),
|
||||
GroupApi: new NTQQGroupApi(this.context, this),
|
||||
};
|
||||
}
|
||||
async initCore() {
|
||||
this.NapCatDataPath = path.join(this.dataPath, 'NapCat');
|
||||
fs.mkdirSync(this.NapCatDataPath, { recursive: true });
|
||||
this.NapCatTempPath = path.join(this.NapCatDataPath, 'temp');
|
||||
@ -119,7 +120,13 @@ export class NapCatCore {
|
||||
if (!fs.existsSync(this.NapCatTempPath)) {
|
||||
fs.mkdirSync(this.NapCatTempPath, { recursive: true });
|
||||
}
|
||||
|
||||
//遍历this.apis[i].initApi 如果存在该函数进行async 调用
|
||||
for (const apiKey in this.apis) {
|
||||
const api = this.apis[apiKey as keyof StableNTApiWrapper];
|
||||
if ('initApi' in api && typeof api.initApi === 'function') {
|
||||
await api.initApi();
|
||||
}
|
||||
}
|
||||
this.initNapCatCoreListeners().then().catch(this.context.logger.logError.bind(this.context.logger));
|
||||
|
||||
this.context.logger.setFileLogEnabled(
|
||||
@ -133,7 +140,6 @@ export class NapCatCore {
|
||||
this.configLoader.configData.consoleLogLevel as LogLevel,
|
||||
);
|
||||
}
|
||||
|
||||
get dataPath(): string {
|
||||
let result = this.context.wrapper.NodeQQNTWrapperUtil.getNTUserDataInfoConfig();
|
||||
if (!result) {
|
||||
|
@ -89,7 +89,7 @@ export interface NodeIKernelGroupService {
|
||||
|
||||
isEssenceMsg(req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown>;
|
||||
|
||||
queryCachedEssenceMsg(req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown>;
|
||||
queryCachedEssenceMsg(req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<{ items: Array<unknown> }>;
|
||||
|
||||
fetchGroupEssenceList(req: {
|
||||
groupCode: string,
|
||||
|
@ -29,10 +29,7 @@ import { NodeIKernelECDHService } from './services/NodeIKernelECDHService';
|
||||
import { NodeIO3MiscService } from './services/NodeIO3MiscService';
|
||||
|
||||
export interface NodeQQNTWrapperUtil {
|
||||
get(): unknown;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-new
|
||||
new(): NodeQQNTWrapperUtil;
|
||||
get(): NodeQQNTWrapperUtil;
|
||||
|
||||
getNTUserDataInfoConfig(): string;
|
||||
|
||||
|
@ -23,7 +23,7 @@ export async function NCoreInitFramework(
|
||||
) {
|
||||
//在进入本层前是否登录未进行判断
|
||||
console.log('NapCat Framework App Loading...');
|
||||
|
||||
|
||||
process.on('uncaughtException', (err) => {
|
||||
console.log('[NapCat] [Error] Unhandled Exception:', err.message);
|
||||
});
|
||||
@ -55,11 +55,12 @@ export async function NCoreInitFramework(
|
||||
// await sleep(2500);
|
||||
// 初始化 NapCatFramework
|
||||
const loaderObject = new NapCatFramework(wrapper, session, logger, loginService, selfInfo, basicInfoWrapper, pathWrapper);
|
||||
await loaderObject.core.initCore();
|
||||
|
||||
//启动WebUi
|
||||
InitWebUi(logger, pathWrapper).then().catch(logger.logError.bind(logger));
|
||||
//初始化LLNC的Onebot实现
|
||||
new NapCatOneBot11Adapter(loaderObject.core, loaderObject.context, pathWrapper);
|
||||
await new NapCatOneBot11Adapter(loaderObject.core, loaderObject.context, pathWrapper).InitOneBot();
|
||||
}
|
||||
|
||||
export class NapCatFramework {
|
||||
|
BIN
src/native/external/MoeHoo.win32.node
vendored
BIN
src/native/external/MoeHoo.win32.node
vendored
Binary file not shown.
@ -45,8 +45,6 @@ import { OB11FriendRecallNoticeEvent } from '@/onebot/event/notice/OB11FriendRec
|
||||
import { OB11GroupRecallNoticeEvent } from '@/onebot/event/notice/OB11GroupRecallNoticeEvent';
|
||||
import { LRUCache } from '@/common/lru-cache';
|
||||
import { NodeIKernelRecentContactListener } from '@/core/listeners/NodeIKernelRecentContactListener';
|
||||
import { Native } from '@/native';
|
||||
import { decodeMessage, decodeRecallGroup } from "@/core/helper/adaptSysMessageDecoder";
|
||||
import { BotOfflineEvent } from './event/notice/BotOfflineEvent';
|
||||
|
||||
//OneBot实现类
|
||||
@ -58,7 +56,6 @@ export class NapCatOneBot11Adapter {
|
||||
apis: StableOneBotApiWrapper;
|
||||
networkManager: OB11NetworkManager;
|
||||
actions: ActionMap;
|
||||
nativeCore: Native | undefined;
|
||||
private bootTime = Date.now() / 1000;
|
||||
recallMsgCache = new LRUCache<string, RawMessage>(100);
|
||||
|
||||
@ -75,41 +72,8 @@ export class NapCatOneBot11Adapter {
|
||||
};
|
||||
this.actions = createActionMap(this, core);
|
||||
this.networkManager = new OB11NetworkManager();
|
||||
// this.registerNative(core, context).catch(e => this.context.logger.logWarn.bind(this.context.logger)('初始化Native失败', e)).then();
|
||||
this.InitOneBot()
|
||||
.catch(e => this.context.logger.logError.bind(this.context.logger)('初始化OneBot失败', e));
|
||||
}
|
||||
|
||||
}
|
||||
async registerNative(core: NapCatCore, context: InstanceContext) {
|
||||
try {
|
||||
this.nativeCore = new Native(context.pathWrapper.binaryPath);
|
||||
if (!this.nativeCore.inited) throw new Error('Native Not Init');
|
||||
// this.nativeCore.registerRecallCallback(async (hex: string) => {
|
||||
// try {
|
||||
// const data = decodeMessage(Buffer.from(hex, 'hex'));
|
||||
// //data.MsgHead.BodyInner.MsgType SubType
|
||||
// const bodyInner = data.msgHead?.bodyInner;
|
||||
// //context.logger.log("[appNative] Parse MsgType:" + bodyInner.msgType + " / SubType:" + bodyInner.subType);
|
||||
// if (bodyInner && bodyInner.msgType == 732 && bodyInner.subType == 17 && data?.msgHead?.noifyData?.innerData) {
|
||||
// const RecallData = Buffer.from(data?.msgHead?.noifyData?.innerData);
|
||||
// //跳过 4字节 群号 + 不知道的1字节 +2字节 长度
|
||||
// const uid = RecallData.readUint32BE();
|
||||
// const buffer = Buffer.from(RecallData.toString('hex').slice(14), 'hex');
|
||||
// const seq: number = decodeRecallGroup(buffer).recallDetails.subDetail.msgSeq;
|
||||
// const peer: Peer = { chatType: ChatType.KCHATTYPEGROUP, peerUid: uid.toString() };
|
||||
// context.logger.log("[Native] 群消息撤回 Peer: " + uid.toString() + " / MsgSeq:" + seq);
|
||||
// const msgs = await core.apis.MsgApi.queryMsgsWithFilterExWithSeq(peer, seq.toString());
|
||||
// this.recallMsgCache.put(msgs.msgList[0].msgId, msgs.msgList[0]);
|
||||
// }
|
||||
// } catch (error: any) {
|
||||
// context.logger.logWarn("[Native] Error:", (error as Error).message, ' HEX:', hex);
|
||||
// }
|
||||
// });
|
||||
} catch (error) {
|
||||
context.logger.logWarn("[Native] Error:", (error as Error).message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
async InitOneBot() {
|
||||
const selfInfo = this.core.selfInfo;
|
||||
const ob11Config = this.configLoader.configData;
|
||||
|
@ -292,7 +292,7 @@ export async function NCoreInitShell() {
|
||||
fs.mkdirSync(dataPath, { recursive: true });
|
||||
logger.logDebug('本账号数据/缓存目录:', accountDataPath);
|
||||
|
||||
new NapCatShell(
|
||||
await new NapCatShell(
|
||||
wrapper,
|
||||
session,
|
||||
logger,
|
||||
@ -300,7 +300,7 @@ export async function NCoreInitShell() {
|
||||
selfInfo,
|
||||
basicInfoWrapper,
|
||||
pathWrapper,
|
||||
);
|
||||
).InitNapCat();
|
||||
}
|
||||
|
||||
export class NapCatShell {
|
||||
@ -327,8 +327,13 @@ export class NapCatShell {
|
||||
};
|
||||
this.core = new NapCatCore(this.context, selfInfo);
|
||||
|
||||
// TODO: complete ob11 adapter initialization logic
|
||||
new NapCatOneBot11Adapter(this.core, this.context, pathWrapper);
|
||||
|
||||
|
||||
}
|
||||
async InitNapCat() {
|
||||
await this.core.initCore();
|
||||
new NapCatOneBot11Adapter(this.core, this.context, this.context.pathWrapper).InitOneBot()
|
||||
.catch(e => this.context.logger.logError.bind(this.context.logger)('初始化OneBot失败', e));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ import { resolve } from 'path';
|
||||
import nodeResolve from '@rollup/plugin-node-resolve';
|
||||
import { builtinModules } from 'module';
|
||||
//依赖排除
|
||||
const external = ['silk-wasm', 'ws', 'express', 'fluent-ffmpeg', 'qrcode-terminal'];
|
||||
const external = ['silk-wasm', 'ws', 'express', 'qrcode-terminal'];
|
||||
const nodeModules = [...builtinModules, builtinModules.map(m => `node:${m}`)].flat();
|
||||
function genCpModule(module: string) {
|
||||
return { src: `./node_modules/${module}`, dest: `dist/node_modules/${module}`, flatten: false };
|
||||
@ -42,7 +42,6 @@ const FrameworkBaseConfigPlugin: PluginOption[] = [
|
||||
const ShellBaseConfigPlugin: PluginOption[] = [
|
||||
cp({
|
||||
targets: [
|
||||
{ src: './src/native/external', dest: 'dist/native', flatten: false },
|
||||
{ src: './src/native/packet', dest: 'dist/moehoo', flatten: false },
|
||||
{ src: './static/', dest: 'dist/static/', flatten: false },
|
||||
{ src: './src/core/external/napcat.json', dest: 'dist/config/' },
|
||||
|
Loading…
Reference in New Issue
Block a user