Compare commits

...

51 Commits

Author SHA1 Message Date
手瓜一十雪
5e0b3b2f35 fix: fluent-ffmpeg 2024-11-14 15:43:11 +08:00
pk5ls20
6829fad5bd chore: workflow build check 2024-11-14 14:30:56 +08:00
pk5ls20
7af0d9e87b refactor: simplify code 2024-11-14 14:29:38 +08:00
Mlikiowa
c089ebea99 release: v4.0.0 2024-11-14 06:00:34 +00:00
手瓜一十雪
d2a2c1c39c fix: #50 2024-11-14 13:58:25 +08:00
手瓜一十雪
ce9b09e8d1 fix: 简化代码 2024-11-14 13:49:37 +08:00
手瓜一十雪
2f6dfe51f5 fix: error 2024-11-14 13:43:39 +08:00
手瓜一十雪
bd227cd0b8 refactor: getGroupHonorInfo 2024-11-14 13:42:03 +08:00
手瓜一十雪
96003724ab refactor: nc shell login 2024-11-14 13:40:01 +08:00
pk5ls20
6a08b15095 chore: eslint 2024-11-14 13:29:39 +08:00
手瓜一十雪
dab0f9ab45 refactor: getImageUrl 2024-11-14 13:26:12 +08:00
手瓜一十雪
e733a6b69a fix: error 2024-11-14 13:22:58 +08:00
手瓜一十雪
9aca98bf13 fix: type 2024-11-14 13:19:50 +08:00
手瓜一十雪
b7c95e53dc fix: unuse import 2024-11-14 13:14:27 +08:00
手瓜一十雪
f762c450ca fix: error 2024-11-14 13:10:52 +08:00
手瓜一十雪
d58bbe53da Merge pull request #521 from abc1763613206/main
feat: add `emoji_package_id` for MarketFace
2024-11-14 13:10:04 +08:00
abc1763613206
f32edd8af7 feat: add emoji_package_id for MarketFace 2024-11-14 13:07:57 +08:00
手瓜一十雪
c747a86e5b fix 2024-11-14 13:07:10 +08:00
手瓜一十雪
abfda0dd58 fix: || -> ?? 2024-11-14 13:00:25 +08:00
手瓜一十雪
f66d7b11a8 fix: error throw 2024-11-14 12:54:58 +08:00
手瓜一十雪
f425c9478e fix 2024-11-14 12:48:19 +08:00
手瓜一十雪
756dea71fc remove: todo -> work 2024-11-14 12:44:21 +08:00
手瓜一十雪
71a6c4ccc5 fix: error handle 2024-11-14 12:37:16 +08:00
手瓜一十雪
ae2f4777ec fix 2024-11-14 12:32:48 +08:00
手瓜一十雪
dcd9b8168a feat: any listener 2024-11-14 12:28:08 +08:00
手瓜一十雪
4bb03ae5ba fix 2024-11-14 12:22:28 +08:00
手瓜一十雪
8bd6f8397b fix: assertion is unnecessary 2024-11-14 12:07:26 +08:00
手瓜一十雪
096e52d93e fix: promise async 2024-11-14 12:06:45 +08:00
手瓜一十雪
037065291d fix: 代码精简 2024-11-14 12:02:24 +08:00
手瓜一十雪
4cf52e1b13 fix: error 2024-11-14 11:53:55 +08:00
手瓜一十雪
21b228552d fix 2024-11-14 11:46:37 +08:00
手瓜一十雪
76b404cdd8 rename: WsPacketClient 2024-11-14 11:40:16 +08:00
手瓜一十雪
937c594ff7 fix 2024-11-14 11:35:10 +08:00
手瓜一十雪
b463140de7 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-11-14 11:30:56 +08:00
手瓜一十雪
f518fb9214 fix: readonly 2024-11-14 11:28:06 +08:00
手瓜一十雪
1092831718 Merge pull request #520 from NapNeko/refactor-4.0.0
refactor: 4.0.0
2024-11-14 11:24:57 +08:00
手瓜一十雪
6b377416da refactor: fix 2024-11-14 11:24:00 +08:00
手瓜一十雪
8f5baa47ec refactor: log最佳实践 2024-11-14 11:10:26 +08:00
手瓜一十雪
5494ff0553 refactor: build 2024-11-14 11:03:11 +08:00
手瓜一十雪
7a4805b464 refactor: registerListen 2024-11-14 10:57:57 +08:00
手瓜一十雪
8435375810 style: lint 2024-11-14 10:53:50 +08:00
手瓜一十雪
c893ec6030 refactor: apiInit Refactor 2024-11-14 10:52:03 +08:00
手瓜一十雪
e8bf6fa0a6 style: lint 2024-11-14 10:45:16 +08:00
手瓜一十雪
f228129c19 refactor: Init Core 2024-11-14 10:43:37 +08:00
手瓜一十雪
cbf98ffb89 refactor: async Init 2024-11-14 10:36:51 +08:00
手瓜一十雪
f6067b002f refactor: Shell Init 2024-11-14 10:34:38 +08:00
手瓜一十雪
636d1103e3 refactor: 删除反撤回模块 未来合并到MoeHoo 2024-11-14 10:30:01 +08:00
手瓜一十雪
bede517f7e refactor: package 2024-11-14 10:27:10 +08:00
手瓜一十雪
16e4891b7d fix 2024-11-13 22:54:56 +08:00
手瓜一十雪
3bcd79fbb7 docs: 文档精简 2024-11-13 22:46:48 +08:00
Mlikiowa
aacf6c2917 release: v3.7.0 2024-11-13 09:53:56 +00:00
106 changed files with 912 additions and 1250 deletions

View File

@@ -1,5 +1,8 @@
name: "Build Action"
on:
push:
branches:
- main
workflow_dispatch:
permissions: write-all

View File

@@ -5,18 +5,16 @@
</div>
---
## 欢迎回
NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
## 欢迎回
NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现
## 猫猫技能
- [x] **启动方式**支持以无头、LiteLoader 插件、仅 QQ GUI 三种方式启动
- [x] **覆盖平台**: 覆盖 Windows / Linux (可选 Docker) / Android Termux / MacOS
- [x] **安装简单**: 支持一键脚本/程序自动部署/镜像部署等多种覆盖范围
- [x] **超低占用**无头模式占用资源极低,适合在服务器上运行
- [x] **超多接口**:实现大部分 OneBot 和 go-cqhttp 接口,超多扩展 API
- [x] **远程管理**:自带 WebUI 支持,远程管理更加便捷
## 碎碎叨叨
- [x] **安装简单**:就算是笨蛋也能使用
- [x] **性能友好**:就算是低内存也能使用
- [x] **接口丰富**:就算是没有也能使用
- [x] **稳定好用**就算是被捉也能使用
## 使用猫猫
## 使用框架
可前往 [Release](https://github.com/NapNeko/NapCatQQ/releases/) 页面下载最新版本
@@ -38,19 +36,15 @@ NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
## 回家旅途
[QQ Group](https://qm.qq.com/q/VfjAq5HIMS)
[Telegram Link](https://t.me/+nLZEnpne-pQ1OWFl)
## 猫猫朋友
## 感谢他们
感谢 [LLOneBot](https://github.com/LLOneBot/LLOneBot)
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持 参考部分代码 已获授权
不过最最重要的 还是需要感谢屏幕前的你哦~
---
## 约法三章
> [!CAUTION]\
> **请不要在 QQ 官方群聊和任何影响力较大的简中互联网平台(包括但不限于: 哔哩哔哩,微博,知乎,抖音等)发布和讨论*任何*与本项目存在相关性的信息**
## 开源附加
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经授权二次分发或基于 NapCat 代码开发。**
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经仓库主作者授权二次分发或基于 NapCat 代码开发。**

View File

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

View File

@@ -2,7 +2,7 @@
"name": "napcat",
"private": true,
"type": "module",
"version": "3.6.17",
"version": "4.0.0",
"scripts": {
"build:framework": "vite build --mode framework",
"build:shell": "vite build --mode shell",
@@ -48,9 +48,9 @@
},
"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",
"fluent-ffmpeg": "^2.1.2"
}
}

View File

@@ -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,14 +181,12 @@ export class NTEventWrapper {
callbackTimesToWait = 1,
timeout = 5000,
) {
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() {
function sendDataCallback(resolve: any, reject: any) {
if (complete == 0) {
reject(
new Error(
@@ -210,7 +208,9 @@ export class NTEventWrapper {
const ListenerMainName = ListenerNameList[0];
const ListenerSubName = ListenerNameList[1];
const timeoutRef = setTimeout(sendDataCallback, timeout);
return new Promise<[EventRet: Awaited<ReturnType<EventType>>, ...Parameters<ListenerType>]>(
(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,8 +233,10 @@ export class NTEventWrapper {
}
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallback);
this.createListenerFunction(ListenerMainName);
const eventFunction = this.createEventFunction(serviceAndMethod);
retEvent = await eventFunction!(...(args));
this.createEventFunction(serviceAndMethod)!(...(args))
.then((eventResult: any) => {
retEvent = eventResult;
if (!checkerEvent(retEvent) && timeoutRef.hasRef()) {
clearTimeout(timeoutRef);
reject(
@@ -249,7 +251,8 @@ export class NTEventWrapper {
),
);
}
})
.catch(reject);
},
);
}

View File

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

View File

@@ -74,6 +74,12 @@ export class LogWrapper {
}
files.forEach(file => {
const filePath = path.join(logDir, file);
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);
@@ -83,18 +89,16 @@ export class LogWrapper {
fs.unlink(filePath, err => {
if (err) {
if (err.code === 'ENOENT') {
this.logger.warn(`File already deleted: ${file}`);
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: ${file}`);
this.logger.info(`Deleted old log file: ${filePath}`);
}
});
}
});
});
});
}
setFileAndConsoleLogLevel(fileLogLevel: LogLevel, consoleLogLevel: LogLevel) {
@@ -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,28 +210,20 @@ 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})`;
for (const element of msg.elements) {
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) {
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})`
}]`;
return replyElementToText(element.replyElement, msg, recursiveLevel);
}
if (element.picElement) {
@@ -271,11 +267,28 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
}
return `[未实现 (ElementType = ${element.elementType})]`;
}
for (const element of msg.elements) {
tokens.push(msgElementToText(element));
}
return tokens.join(' ');
}
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})`
}]`;
}

View File

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

View File

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

View File

@@ -8,34 +8,40 @@ export class RequestUtil {
const client = url.startsWith('https') ? https : http;
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);
const cookies: { [key: string]: string } = {};
res.on('data', () => { }); // Necessary to consume the stream
res.on('end', () => {
this.handleRedirect(res, url, cookies)
.then(resolve)
.catch(reject);
});
if (res.headers['set-cookie']) {
this.extractCookies(res.headers['set-cookie'], cookies);
}
});
req.on('error', (error: Error) => {
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);
RequestUtil.HttpsGetCookies(redirectUrl.href).then((redirectCookies) => {
const redirectCookies = await this.HttpsGetCookies(redirectUrl.href);
// 合并重定向过程中的cookies
cookies = { ...cookies, ...redirectCookies };
resolve(cookies);
}).catch((err) => {
reject(err);
});
} else {
resolve(cookies);
return { ...cookies, ...redirectCookies };
}
} else {
resolve(cookies);
}
};
res.on('data', () => {
}); // Necessary to consume the stream
res.on('end', () => {
handleRedirect(res);
});
if (res.headers['set-cookie']) {
//console.log(res.headers['set-cookie']);
res.headers['set-cookie'].forEach((cookie) => {
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];
@@ -44,13 +50,6 @@ export class RequestUtil {
}
});
}
});
req.on('error', (error: any) => {
reject(error);
});
});
}
// 请求和回复都是JSON data传原始内容 自动编码json
static async HttpGetJson<T>(url: string, method: string = 'GET', data?: any, headers: {
@@ -88,13 +87,13 @@ export class RequestUtil {
} else {
reject(new Error(`Unexpected status code: ${res.statusCode}`));
}
} catch (parseError) {
reject(parseError);
} catch (parseError: unknown) {
reject(new Error((parseError as Error).message));
}
});
});
req.on('error', (error: any) => {
req.on('error', (error: Error) => {
reject(error);
});
if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
@@ -133,62 +132,4 @@ export class RequestUtil {
Buffer.from(footer, 'utf8'),
]);
}
static async uploadImageForOpenPlatform(filePath: string, cookies: string): Promise<string> {
return new Promise(async (resolve, reject) => {
type retType = { retcode: number, result?: { url: string } };
try {
const options = {
hostname: 'cgi.connect.qq.com',
port: 443,
path: '/qqconnectopen/upload_share_image',
method: 'POST',
headers: {
'Referer': 'https://cgi.connect.qq.com',
'Cookie': cookies,
'Accept': '*/*',
'Connection': 'keep-alive',
'Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW',
},
};
const req = https.request(options, async (res) => {
let responseBody = '';
res.on('data', (chunk: string | Buffer) => {
responseBody += chunk.toString();
});
res.on('end', () => {
try {
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
const responseJson = JSON.parse(responseBody) as retType;
resolve(responseJson.result!.url!);
} else {
reject(new Error(`Unexpected status code: ${res.statusCode}`));
}
} catch (parseError) {
reject(parseError);
}
});
});
req.on('error', (error) => {
reject(error);
console.log('Error during upload:', error);
});
const body = await RequestUtil.createFormData('WebKitFormBoundary7MA4YWxkTrZu0gW', filePath);
// req.setHeader('Content-Length', Buffer.byteLength(body));
// console.log(`Prepared data size: ${Buffer.byteLength(body)} bytes`);
req.write(body);
req.end();
return;
} catch (error) {
reject(error);
}
return undefined;
});
}
}

View File

@@ -1 +1 @@
export const napCatVersion = '3.6.17';
export const napCatVersion = '4.0.0';

View File

@@ -20,7 +20,7 @@ export async function getVideoInfo(filePath: string, logger: LogWrapper) {
ffmpeg.setFfmpegPath(ffmpegPath);
ffmpeg(filePath).ffprobe((err: any, metadata: ffmpeg.FfprobeData) => {
if (err) {
reject(err);
reject(new Error('无法获取视频信息。'));
} else {
const videoStream = metadata.streams.find((s: FfprobeStream) => s.codec_type === 'video');
if (videoStream) {

View File

@@ -6,8 +6,10 @@ export class NodeIDependsAdapter {
}
onMSFSsoError(args: unknown) {
}
getGroupCode(args: unknown) {
}
}

View File

@@ -357,16 +357,14 @@ 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);
} else {
if (!dimensions) {
reject(new Error(err.message));
} else if (!dimensions) {
reject(new Error('获取图片尺寸失败'));
} else {
resolve(dimensions);
}
}
});
});
}
@@ -408,26 +406,31 @@ export class NTQQFileApi {
return fileData.filePath!;
}
async getImageUrl(element: PicElement) {
async getImageUrl(element: PicElement): Promise<string> {
if (!element) {
return '';
}
const url: string = element.originImageUrl ?? '';
const md5HexStr = element.md5HexStr;
const fileMd5 = element.md5HexStr;
if (url) {
const parsedUrl = new URL(IMAGE_HTTP_HOST + url);
const urlRkey = parsedUrl.searchParams.get('rkey');
const imageAppid = parsedUrl.searchParams.get('appid');
const isNTV2 = imageAppid && ['1406', '1407'].includes(imageAppid);
const imageFileId = parsedUrl.searchParams.get('fileid');
const rkeyData = await this.getRkeyData();
return this.getImageUrlFromParsedUrl(parsedUrl, rkeyData);
}
return this.getImageUrlFromMd5(fileMd5, md5HexStr);
}
private async getRkeyData() {
const rkeyData = {
private_rkey: 'CAQSKAB6JWENi5LM_xp9vumLbuThJSaYf-yzMrbZsuq7Uz2qEc3Rbib9LP4',
group_rkey: 'CAQSKAB6JWENi5LM_xp9vumLbuThJSaYf-yzMrbZsuq7Uz2qffcqm614gds',
online_rkey: false
};
try {
if (this.core.apis.PacketApi.available) {
const rkey_expired_private = !this.packetRkey || this.packetRkey[0].time + Number(this.packetRkey[0].ttl) < Date.now() / 1000;
@@ -455,23 +458,35 @@ export class NTQQFileApi {
this.context.logger.logError.bind(this.context.logger)('获取rkey失败 Fallback Old Mode', e);
}
}
return rkeyData;
}
private getImageUrlFromParsedUrl(parsedUrl: URL, rkeyData: any): string {
const urlRkey = parsedUrl.searchParams.get('rkey');
const imageAppid = parsedUrl.searchParams.get('appid');
const isNTV2 = imageAppid && ['1406', '1407'].includes(imageAppid);
const imageFileId = parsedUrl.searchParams.get('fileid');
if (isNTV2 && urlRkey) {
return IMAGE_HTTP_HOST_NT + urlRkey;
} else if (isNTV2 && rkeyData.online_rkey) {
const rkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey;
return IMAGE_HTTP_HOST_NT + url + `&rkey=${rkey}`;
return IMAGE_HTTP_HOST_NT + parsedUrl.pathname + `&rkey=${rkey}`;
} else if (isNTV2 && imageFileId) {
const rkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey;
return IMAGE_HTTP_HOST + `/download?appid=${imageAppid}&fileid=${imageFileId}&rkey=${rkey}`;
}
return '';
}
//到这里说明可能是旧客户端
private getImageUrlFromMd5(fileMd5: string | undefined, md5HexStr: string | undefined): string {
if (fileMd5 || md5HexStr) {
return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 ?? md5HexStr)!.toUpperCase()}/0`;
}
this.context.logger.logDebug('图片url获取失败', element);
this.context.logger.logDebug('图片url获取失败', { fileMd5, md5HexStr });
return '';
}
}

View File

@@ -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',
@@ -41,14 +41,10 @@ export class NTQQFriendApi {
tempBothDel: tempBothDel
});
}
async getBuddyV2ExWithCate(refresh = false) {
const categoryMap: Map<string, any> = new Map();
async getBuddyV2ExWithCate() {
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 });
});
return item.buddyUids;
});
const data = await this.core.eventWrapper.callNoListenerEvent(

View File

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

View File

@@ -4,5 +4,4 @@ export * from './group';
export * from './msg';
export * from './user';
export * from './webapi';
export * from './sign';
export * from './system';

View File

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

View File

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

View File

@@ -1,5 +1,3 @@
import { RequestUtil } from '@/common/request';
import { MiniAppLuaJsonType } from '@/core';
import { InstanceContext, NapCatCore } from '..';
export class NTQQMusicSignApi {
@@ -10,210 +8,6 @@ export class NTQQMusicSignApi {
this.context = context;
this.core = core;
}
async signMiniApp(CardData: MiniAppLuaJsonType) {
// {
// "app": "com.tencent.miniapp.lua",
// "bizsrc": "tianxuan.imgJumpArk",
// "view": "miniapp",
// "prompt": "hi! 这里有我的日常故事,只想讲给你听",
// "config": {
// "type": "normal",
// "forward": 1,
// "autosize": 0
// },
// "meta": {
// "miniapp": {
// "title": "hi! 这里有我的日常故事,只想讲给你听",
// "preview": "https:\/\/tianquan.gtimg.cn\/qqAIAgent\/item\/7\/square.png",
// "jumpUrl": "https:\/\/club.vip.qq.com\/transfer?open_kuikly_info=%7B%22version%22%3A%20%221%22%2C%22src_type%22%3A%20%22web%22%2C%22kr_turbo_display%22%3A%20%221%22%2C%22page_name%22%3A%20%22vas_ai_persona_moments%22%2C%22bundle_name%22%3A%20%22vas_ai_persona_moments%22%7D&page_name=vas_ai_persona_moments&enteranceId=share&robot_uin=3889008584",
// "tag": "QQ智能体",
// "tagIcon": "https:\/\/tianquan.gtimg.cn\/shoal\/qqAIAgent\/3e9d70c9-d98c-45b8-80b4-79d82971b514.png",
// "source": "QQ智能体",
// "sourcelogo": "https:\/\/tianquan.gtimg.cn\/shoal\/qqAIAgent\/3e9d70c9-d98c-45b8-80b4-79d82971b514.png"
// }
// }
// }
// token : function(url,skey){
// var str = skey || cookie('skey') || cookie('rv2') || '',
// hash = 5381;
// if(url){
// var hostname = uri(url).hostname;
// if(hostname.indexOf('qun.qq.com') > -1 || (hostname.indexOf('qzone.qq.com') > -1 && hostname.indexOf('qun.qzone.qq.com') === -1)){
// str = cookie('p_skey') || str;
// }
// }
// for(var i = 0, len = str.length; i < len; ++i){
// hash += (hash << 5) + str.charAt(i).charCodeAt();
// }
// return hash & 0x7fffffff;
// },
//
// function signToken(skey: string) {
// let hash = 5381;
// for (let i = 0, len = skey.length; i < len; ++i) {
// hash += (hash << 5) + skey.charCodeAt(i);
// }
// return hash & 0x7fffffff;
// }
const signCard = {
'app': 'com.tencent.miniapp.lua',
'bizsrc': 'tianxuan.imgJumpArk',
'view': 'miniapp',
'prompt': CardData.prompt,
'config': {
'type': 'normal',
'forward': 1,
'autosize': 0,
},
'meta': {
'miniapp': {
'title': CardData.title,
'preview': (CardData.preview as string).replace(/\\/g, '\\/\\/'),
'jumpUrl': (CardData.jumpUrl as string).replace(/\\/g, '\\/\\/'),
'tag': CardData.tag,
'tagIcon': (CardData.tagIcon as string).replace(/\\/g, '\\/\\/'),
'source': CardData.source,
'sourcelogo': (CardData.sourcelogo as string).replace(/\\/g, '\\/\\/'),
},
},
};
// let signCard = {
// "app": "com.tencent.eventshare.lua",
// "prompt": "Bot Test",
// "bizsrc": "tianxuan.business",
// "meta": {
// "eventshare": {
// "button1URL": "https://www.bilibili.com",
// "button1disable": false,
// "button1title": "点我前往",
// "button2URL": "",
// "button2disable": false,
// "button2title": "",
// "buttonNum": 1,
// "jumpURL": "https://www.bilibili.com",
// "preview": "https://tianquan.gtimg.cn/shoal/card/9930bc4e-4a92-4da3-814f-8094a2421d9c.png",
// "tag": "QQ集卡",
// "tagIcon": "https://tianquan.gtimg.cn/shoal/card/c034854b-102d-40be-a545-5ca90a7c49c9.png",
// "title": "Bot Test"
// }
// },
// "config": {
// "autosize": 0,
// "collect": 0,
// "ctime": 1716568575,
// "forward": 1,
// "height": 336,
// "reply": 0,
// "round": 1,
// "type": "normal",
// "width": 263
// },
// "view": "eventshare",
// "ver": "0.0.0.1"
// };
const data = (await this.core.apis.UserApi.getQzoneCookies());
const Bkn = this.core.apis.WebApi.getBknFromCookie(data.p_skey);
const CookieValue = 'p_skey=' + data.p_skey + '; skey=' + data.skey + '; p_uin=o' + this.core.selfInfo.uin + '; uin=o' + this.core.selfInfo.uin;
const signurl = 'https://h5.qzone.qq.com/v2/vip/tx/trpc/ark-share/GenNewSignedArk?g_tk=' + Bkn + '&ark=' + encodeURIComponent(JSON.stringify(signCard));
let signed_ark = '';
try {
const retData = await RequestUtil.HttpGetJson<{
code: number,
data: { signed_ark: string }
}>(signurl, 'GET', undefined, { Cookie: CookieValue });
//logDebug('MiniApp JSON 消息生成成功', retData);
signed_ark = retData.data.signed_ark;
} catch (error) {
this.context.logger.logDebug('MiniApp JSON 消息生成失败', error);
}
return signed_ark;
}
async signInternal(songname: string, singer: string, cover: string, songmid: string, songmusic: string) {
//curl -X POST 'https://mqq.reader.qq.com/api/mqq/share/card?accessToken&_csrfToken&source=c0003' -H 'Content-Type: application/json' -H 'Cookie: uin=o10086' -d '{"app":"com.tencent.qqreader.share","config":{"ctime":1718634110,"forward":1,"token":"9a63343c32d5a16bcde653eb97faa25d","type":"normal"},"extra":{"app_type":1,"appid":100497308,"msg_seq":14386738075403815000.0,"uin":1733139081},"meta":{"music":{"action":"","android_pkg_name":"","app_type":1,"appid":100497308,"ctime":1718634110,"desc":"周杰伦","jumpUrl":"https://i.y.qq.com/v8/playsong.html?songmid=0039MnYb0qxYhV&type=0","musicUrl":"http://ws.stream.qqmusic.qq.com/http://isure6.stream.qqmusic.qq.com/M800002202B43Cq4V4.mp3?fromtag=810033622&guid=br_xzg&trace=23fe7bcbe2336bbf&uin=553&vkey=CF0F5CE8B0FA16F3001F8A88D877A217EB5E4F00BDCEF1021EB6C48969CA33C6303987AEECE9CC840122DD2F917A59D6130D8A8CA4577C87","preview":"https://y.qq.com/music/photo_new/T002R800x800M000000MkMni19ClKG.jpg","cover":"https://y.qq.com/music/photo_new/T002R800x800M000000MkMni19ClKG.jpg","sourceMsgId":"0","source_icon":"https://p.qpic.cn/qqconnect/0/app_100497308_1626060999/100?max-age=2592000&t=0","source_url":"","tag":"QQ音乐","title":"晴天","uin":10086}},"prompt":"[分享]晴天","ver":"0.0.0.1","view":"music"}'
const signurl = 'https://mqq.reader.qq.com/api/mqq/share/card?accessToken&_csrfToken&source=c0003';
//let = "https://y.qq.com/music/photo_new/T002R800x800M000000MkMni19ClKG.jpg";
const signCard = {
app: 'com.tencent.qqreader.share',
config: {
ctime: 1718634110,
forward: 1,
token: '9a63343c32d5a16bcde653eb97faa25d',
type: 'normal',
},
extra: {
app_type: 1,
appid: 100497308,
msg_seq: 14386738075403815000,
uin: 1733139081,
},
meta: {
music: {
action: '',
android_pkg_name: '',
app_type: 1,
appid: 100497308,
ctime: 1718634110,
desc: singer,
jumpUrl: 'https://i.y.qq.com/v8/playsong.html?songmid=' + songmid + '&type=0',
musicUrl: songmusic,
preview: cover,
cover: cover,
sourceMsgId: '0',
source_icon: 'https://p.qpic.cn/qqconnect/0/app_100497308_1626060999/100?max-age=2592000&t=0',
source_url: '',
tag: 'QQ音乐',
title: songname,
uin: 10086,
},
},
prompt: '[分享]' + songname,
ver: '0.0.0.1',
view: 'music',
};
//console.log(JSON.stringify(signCard, null, 2));
const data = await RequestUtil.HttpGetJson<{ code: number, data: { arkResult: string } }>
(signurl, 'POST', signCard, { 'Cookie': 'uin=o10086', 'Content-Type': 'application/json' });
return data;
}
//注意处理错误
async signWay03(id: string = '', mid: string = '') {
let signedMid;
if (mid == '') {
const MusicInfo = await RequestUtil.HttpGetJson<{
songinfo?: {
data?: {
track_info: {
mid: string
}
}
}
}>(
'https://u.y.qq.com/cgi-bin/musicu.fcg?format=json&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq.json&needNewCode=0&data={"comm":{"ct":24,"cv":0},"songinfo":{"method":"get_song_detail_yqq","param":{"song_type":0,"song_mid":"","song_id":' + id + '},"module":"music.pf_song_detail_svr"}}',
'GET',
undefined,
);
signedMid = MusicInfo.songinfo?.data?.track_info.mid;
}
//第三方接口 存在速率限制 现在勉强用
const MusicReal = await RequestUtil.HttpGetJson<{
code: number,
data?: {
name: string,
singer: string,
url: string,
cover: string
}
}>('https://api.leafone.cn/api/qqmusic?id=' + signedMid + '&type=8', 'GET');
//console.log(MusicReal);
return { ...MusicReal.data, mid: signedMid };
}
//转换外域名为 https://qq.ugcimg.cn/v1/cpqcbu4b8870i61bde6k7cbmjgejq8mr3in82qir4qi7ielffv5slv8ck8g42novtmev26i233ujtuab6tvu2l2sjgtupfr389191v00s1j5oh5325j5eqi40774jv1i/khovifoh7jrqd6eahoiv7koh8o
//https://cgi.connect.qq.com/qqconnectopen/openapi/change_image_url?url=https://th.bing.com/th?id=OSK.b8ed36f1fb1889de6dc84fd81c187773&w=46&h=46&c=11&rs=1&qlt=80&o=6&dpr=2&pid=SANGAM
@@ -227,10 +21,5 @@ export class NTQQMusicSignApi {
//https://y.gtimg.cn/music/photo_new/T002R800x800M000000y5gq7449K9I.jpg?max_age=2592000
//还有一处公告上传可以上传高质量图片 持久为qq域名
async SignMusicWrapper(id: string = '') {
const MusicInfo = await this.signWay03(id)!;
return await this.signInternal(MusicInfo.name!, MusicInfo.singer!, MusicInfo.cover!, MusicInfo.mid!, 'https://ws.stream.qqmusic.qq.com/' + MusicInfo.url!);
}
}

View File

@@ -212,15 +212,13 @@ export class NTQQWebApi {
}
}
async getGroupHonorInfo(groupCode: string, getType: WebHonorType) {
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
const getDataInternal = async (Internal_groupCode: string, Internal_type: number) => {
private async getDataInternal(cookieObject: any, groupCode: string, type: number) {
let resJson;
try {
const res = await RequestUtil.HttpGetText(
`https://qun.qq.com/interactive/honorlist?${new URLSearchParams({
gc: Internal_groupCode,
type: Internal_type.toString(),
gc: groupCode,
type: type.toString(),
}).toString()}`,
'GET',
'',
@@ -230,90 +228,49 @@ export class NTQQWebApi {
if (match) {
resJson = JSON.parse(match[1].trim());
}
if (Internal_type === 1) {
return resJson?.talkativeList;
} else {
return resJson?.actorList;
}
return type === 1 ? resJson?.talkativeList : resJson?.actorList;
} catch (e) {
this.context.logger.logDebug('获取当前群荣耀失败', e);
}
return undefined;
};
}
}
private async getHonorList(cookieObject: any, groupCode: string, type: number) {
const data = await this.getDataInternal(cookieObject, groupCode, type);
if (!data) {
this.context.logger.logError(`获取类型 ${type} 的荣誉信息失败`);
return [];
}
return data.map((item: any) => ({
user_id: item?.uin,
nickname: item?.name,
avatar: item?.avatar,
description: item?.desc,
}));
}
async getGroupHonorInfo(groupCode: string, getType: WebHonorType) {
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
const HonorInfo: any = { group_id: groupCode };
if (getType === WebHonorType.TALKATIVE || getType === WebHonorType.ALL) {
const RetInternal = await getDataInternal(groupCode, 1);
if (RetInternal) {
HonorInfo.current_talkative = {
user_id: RetInternal[0]?.uin,
avatar: RetInternal[0]?.avatar,
nickname: RetInternal[0]?.name,
day_count: 0,
description: RetInternal[0]?.desc,
};
HonorInfo.talkative_list = [];
for (const talkative_ele of RetInternal) {
HonorInfo.talkative_list.push({
user_id: talkative_ele?.uin,
avatar: talkative_ele?.avatar,
description: talkative_ele?.desc,
day_count: 0,
nickname: talkative_ele?.name,
});
}
} else {
this.context.logger.logError.bind(this.context.logger)('获取龙王信息失败');
const talkativeList = await this.getHonorList(cookieObject, groupCode, 1);
if (talkativeList.length > 0) {
HonorInfo.current_talkative = talkativeList[0];
HonorInfo.talkative_list = talkativeList;
}
}
if (getType === WebHonorType.PERFORMER || getType === WebHonorType.ALL) {
const RetInternal = await getDataInternal(groupCode, 2);
if (RetInternal) {
HonorInfo.performer_list = [];
for (const performer_ele of RetInternal) {
HonorInfo.performer_list.push({
user_id: performer_ele?.uin,
nickname: performer_ele?.name,
avatar: performer_ele?.avatar,
description: performer_ele?.desc,
});
}
} else {
this.context.logger.logError.bind(this.context.logger)('获取群聊之火失败');
}
HonorInfo.performer_list = await this.getHonorList(cookieObject, groupCode, 2);
}
if (getType === WebHonorType.LEGEND || getType === WebHonorType.ALL) {
const RetInternal = await getDataInternal(groupCode, 3);
if (RetInternal) {
HonorInfo.legend_list = [];
for (const legend_ele of RetInternal) {
HonorInfo.legend_list.push({
user_id: legend_ele?.uin,
nickname: legend_ele?.name,
avatar: legend_ele?.avatar,
desc: legend_ele?.description,
});
}
} else {
this.context.logger.logError.bind(this.context.logger)('获取群聊炽焰失败');
}
HonorInfo.legend_list = await this.getHonorList(cookieObject, groupCode, 3);
}
if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) {
const RetInternal = await getDataInternal(groupCode, 6);
if (RetInternal) {
HonorInfo.emotion_list = [];
for (const emotion_ele of RetInternal) {
HonorInfo.emotion_list.push({
user_id: emotion_ele.uin,
nickname: emotion_ele.name,
avatar: emotion_ele.avatar,
desc: emotion_ele.description,
});
}
} else {
this.context.logger.logError.bind(this.context.logger)('获取快乐源泉失败');
}
HonorInfo.emotion_list = await this.getHonorList(cookieObject, groupCode, 6);
}
// 冒尖小春笋好像已经被tx扬了 R.I.P.

View File

@@ -23,7 +23,7 @@ export interface ChatCacheList {
export interface ChatCacheListItem {
chatType: ChatType;
basicChatCacheInfo: ChatCacheListItemBasic;
guildChatCacheInfo: unknown[]; // TODO: 没用过频道所以不知道这里边的详细内容
guildChatCacheInfo: unknown[]; // work: 没用过频道所以不知道这里边的详细内容
}
export interface ChatCacheListItemBasic {

View File

@@ -117,7 +117,7 @@ export enum GroupMemberRole {
}
export interface GroupMember {
memberRealLevel: string | undefined;
memberRealLevel: number | undefined;
memberSpecialTitle?: string;
avatarPath: string;
cardName: string;

View File

@@ -175,8 +175,8 @@ export interface SimpleInfo {
status: UserStatus | null;
vasInfo: VasInfo | null;
relationFlags: RelationFlags | null;
otherFlags: any | null;
intimate: any | null;
otherFlags: any;
intimate: any;
}
export type FriendV2 = SimpleInfo;

View File

@@ -1,4 +1,4 @@
// TODO: further refactor in NapCat.Packet v2
// work:further refactor in NapCat.Packet v2
import { NapProtoMsg, ProtoField, ScalarType } from "@napneko/nap-proto-core";
const LikeDetail = {

View File

@@ -1,4 +1,4 @@
// TODO: further refactor in NapCat.Packet v2
// work:further refactor in NapCat.Packet v2
import { NapProtoMsg, ProtoField, ScalarType } from "@napneko/nap-proto-core";
const BodyInner = {

View File

@@ -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) {
@@ -204,7 +210,7 @@ export class NapCatCore {
});
};
groupListener.onMemberListChange = (arg) => {
// todo: 应该加一个内部自己维护的成员变动callback用于判断成员变化通知
// work:应该加一个内部自己维护的成员变动callback用于判断成员变化通知
const groupCode = arg.sceneId.split('_')[0];
if (this.apis.GroupApi.groupMemberCache.has(groupCode)) {
const existMembers = this.apis.GroupApi.groupMemberCache.get(groupCode)!;
@@ -214,7 +220,7 @@ export class NapCatCore {
if (existMember) {
Object.assign(existMember, member);
} else {
existMembers!.set(uid, member);
existMembers.set(uid, member);
}
//移除成员
if (member.isDelete) {

View File

@@ -3,57 +3,57 @@ import { BuddyCategoryType, FriendRequestNotify } from '@/core/entities';
export type OnBuddyChangeParams = BuddyCategoryType[];
export class NodeIKernelBuddyListener {
onBuddyListChangedV2(arg: unknown): void {
onBuddyListChangedV2(arg: unknown): any {
}
onAddBuddyNeedVerify(arg: unknown) {
onAddBuddyNeedVerify(arg: unknown): any {
}
onAddMeSettingChanged(arg: unknown) {
onAddMeSettingChanged(arg: unknown): any {
}
onAvatarUrlUpdated(arg: unknown) {
onAvatarUrlUpdated(arg: unknown): any {
}
onBlockChanged(arg: unknown) {
onBlockChanged(arg: unknown): any {
}
onBuddyDetailInfoChange(arg: unknown) {
onBuddyDetailInfoChange(arg: unknown): any {
}
onBuddyInfoChange(arg: unknown) {
onBuddyInfoChange(arg: unknown): any {
}
onBuddyListChange(arg: OnBuddyChangeParams): void {
onBuddyListChange(arg: OnBuddyChangeParams): any {
}
onBuddyRemarkUpdated(arg: unknown): void {
onBuddyRemarkUpdated(arg: unknown): any {
}
onBuddyReqChange(arg: FriendRequestNotify): void {
onBuddyReqChange(arg: FriendRequestNotify): any {
}
onBuddyReqUnreadCntChange(arg: unknown): void {
onBuddyReqUnreadCntChange(arg: unknown): any {
}
onCheckBuddySettingResult(arg: unknown): void {
onCheckBuddySettingResult(arg: unknown): any {
}
onDelBatchBuddyInfos(arg: unknown): void {
onDelBatchBuddyInfos(arg: unknown): any {
}
onDoubtBuddyReqChange(arg: unknown): void {
onDoubtBuddyReqChange(arg: unknown): any {
}
onDoubtBuddyReqUnreadNumChange(arg: unknown): void {
onDoubtBuddyReqUnreadNumChange(arg: unknown): any {
}
onNickUpdated(arg: unknown): void {
onNickUpdated(arg: unknown): any {
}
onSmartInfos(arg: unknown): void {
onSmartInfos(arg: unknown): any {
}
onSpacePermissionInfos(arg: unknown): void {
onSpacePermissionInfos(arg: unknown): any {
}
}

View File

@@ -7,19 +7,19 @@ export class NodeIKernelFileAssistantListener {
fileSpeed: number,
thumbPath: string | null,
filePath: string | null,
}) {
}): any {
}
onSessionListChanged(...args: unknown[]) {
onSessionListChanged(...args: unknown[]): any {
}
onSessionChanged(...args: unknown[]) {
onSessionChanged(...args: unknown[]): any {
}
onFileListChanged(...args: unknown[]) {
onFileListChanged(...args: unknown[]): any {
}
onFileSearch(searchResult: SearchResultWrapper) {
onFileSearch(searchResult: SearchResultWrapper): any {
}
}

View File

@@ -1,70 +1,70 @@
import { DataSource, Group, GroupListUpdateType, GroupMember, GroupNotify, ShutUpGroupMember } from '@/core/entities';
export class NodeIKernelGroupListener {
onGroupListInited(listEmpty: boolean): void { }
onGroupListInited(listEmpty: boolean): any { }
// 发现于Win 9.9.9 23159
onGroupMemberLevelInfoChange(...args: unknown[]): void {
onGroupMemberLevelInfoChange(...args: unknown[]): any {
}
onGetGroupBulletinListResult(...args: unknown[]) {
onGetGroupBulletinListResult(...args: unknown[]): any {
}
onGroupAllInfoChange(...args: unknown[]) {
onGroupAllInfoChange(...args: unknown[]): any {
}
onGroupBulletinChange(...args: unknown[]) {
onGroupBulletinChange(...args: unknown[]): any {
}
onGroupBulletinRemindNotify(...args: unknown[]) {
onGroupBulletinRemindNotify(...args: unknown[]): any {
}
onGroupArkInviteStateResult(...args: unknown[]) {
onGroupArkInviteStateResult(...args: unknown[]): any {
}
onGroupBulletinRichMediaDownloadComplete(...args: unknown[]) {
onGroupBulletinRichMediaDownloadComplete(...args: unknown[]): any {
}
onGroupConfMemberChange(...args: unknown[]) {
onGroupConfMemberChange(...args: unknown[]): any {
}
onGroupDetailInfoChange(...args: unknown[]) {
onGroupDetailInfoChange(...args: unknown[]): any {
}
onGroupExtListUpdate(...args: unknown[]) {
onGroupExtListUpdate(...args: unknown[]): any {
}
onGroupFirstBulletinNotify(...args: unknown[]) {
onGroupFirstBulletinNotify(...args: unknown[]): any {
}
onGroupListUpdate(updateType: GroupListUpdateType, groupList: Group[]) {
onGroupListUpdate(updateType: GroupListUpdateType, groupList: Group[]): any {
}
onGroupNotifiesUpdated(dboubt: boolean, notifies: GroupNotify[]) {
onGroupNotifiesUpdated(dboubt: boolean, notifies: GroupNotify[]): any {
}
onGroupBulletinRichMediaProgressUpdate(...args: unknown[]) {
onGroupBulletinRichMediaProgressUpdate(...args: unknown[]): any {
}
onGroupNotifiesUnreadCountUpdated(...args: unknown[]) {
onGroupNotifiesUnreadCountUpdated(...args: unknown[]): any {
}
onGroupSingleScreenNotifies(doubt: boolean, seq: string, notifies: GroupNotify[]) {
onGroupSingleScreenNotifies(doubt: boolean, seq: string, notifies: GroupNotify[]): any {
}
onGroupsMsgMaskResult(...args: unknown[]) {
onGroupsMsgMaskResult(...args: unknown[]): any {
}
onGroupStatisticInfoChange(...args: unknown[]) {
onGroupStatisticInfoChange(...args: unknown[]): any {
}
onJoinGroupNotify(...args: unknown[]) {
onJoinGroupNotify(...args: unknown[]): any {
}
onJoinGroupNoVerifyFlag(...args: unknown[]) {
onJoinGroupNoVerifyFlag(...args: unknown[]): any {
}
onMemberInfoChange(groupCode: string, dateSource: DataSource, members: Map<string, GroupMember>) {
onMemberInfoChange(groupCode: string, dateSource: DataSource, members: Map<string, GroupMember>): any {
}
onMemberListChange(arg: {
@@ -74,12 +74,12 @@ export class NodeIKernelGroupListener {
hasPrev: boolean,
hasNext: boolean,
hasRobot: boolean
}) {
}): any {
}
onSearchMemberChange(...args: unknown[]) {
onSearchMemberChange(...args: unknown[]): any {
}
onShutUpMemberListChanged(groupCode: string, members: Array<ShutUpGroupMember>) {
onShutUpMemberListChanged(groupCode: string, members: Array<ShutUpGroupMember>): any {
}
}

View File

@@ -1,57 +1,57 @@
export class NodeIKernelLoginListener {
onLoginConnected(...args: any[]): void {
onLoginConnected(...args: any[]): any {
}
onLoginDisConnected(...args: any[]): void {
onLoginDisConnected(...args: any[]): any {
}
onLoginConnecting(...args: any[]): void {
onLoginConnecting(...args: any[]): any {
}
onQRCodeGetPicture(arg: { pngBase64QrcodeData: string, qrcodeUrl: string }): void {
onQRCodeGetPicture(arg: { pngBase64QrcodeData: string, qrcodeUrl: string }): any {
// let base64Data: string = arg.pngBase64QrcodeData
// base64Data = base64Data.split("data:image/png;base64,")[1]
// let buffer = Buffer.from(base64Data, 'base64')
// console.log("onQRCodeGetPicture", arg);
}
onQRCodeLoginPollingStarted(...args: any[]): void {
onQRCodeLoginPollingStarted(...args: any[]): any {
}
onQRCodeSessionUserScaned(...args: any[]): void {
onQRCodeSessionUserScaned(...args: any[]): any {
}
onQRCodeLoginSucceed(arg: QRCodeLoginSucceedResult): void {
onQRCodeLoginSucceed(arg: QRCodeLoginSucceedResult): any {
}
onQRCodeSessionFailed(...args: any[]): void {
onQRCodeSessionFailed(...args: any[]): any {
}
onLoginFailed(...args: any[]): void {
onLoginFailed(...args: any[]): any {
}
onLogoutSucceed(...args: any[]): void {
onLogoutSucceed(...args: any[]): any {
}
onLogoutFailed(...args: any[]): void {
onLogoutFailed(...args: any[]): any {
}
onUserLoggedIn(...args: any[]): void {
onUserLoggedIn(...args: any[]): any {
}
onQRCodeSessionQuickLoginFailed(...args: any[]): void {
onQRCodeSessionQuickLoginFailed(...args: any[]): any {
}
onPasswordLoginFailed(...args: any[]): void {
onPasswordLoginFailed(...args: any[]): any {
}
OnConfirmUnusualDeviceFailed(...args: any[]): void {
OnConfirmUnusualDeviceFailed(...args: any[]): any {
}
onQQLoginNumLimited(...args: any[]): void {
onQQLoginNumLimited(...args: any[]): any {
}
onLoginState(...args: any[]): void {
onLoginState(...args: any[]): any {
}
}

View File

@@ -20,8 +20,8 @@ export interface OnRichMediaDownloadCompleteParams {
fileSrvErrCode: string,
clientMsg: string,
businessId: number,
userTotalSpacePerDay: unknown | null,
userUsedSpacePerDay: unknown | null
userTotalSpacePerDay: unknown,
userUsedSpacePerDay: unknown
}
export interface GroupFileInfoUpdateParamType {
@@ -94,108 +94,108 @@ export interface TempOnRecvParams {
}
export class NodeIKernelMsgListener {
onAddSendMsg(msgRecord: RawMessage) {
onAddSendMsg(msgRecord: RawMessage): any {
}
onBroadcastHelperDownloadComplete(broadcastHelperTransNotifyInfo: unknown) {
onBroadcastHelperDownloadComplete(broadcastHelperTransNotifyInfo: unknown): any {
}
onBroadcastHelperProgressUpdate(broadcastHelperTransNotifyInfo: unknown) {
onBroadcastHelperProgressUpdate(broadcastHelperTransNotifyInfo: unknown): any {
}
onChannelFreqLimitInfoUpdate(contact: unknown, z: unknown, freqLimitInfo: unknown) {
onChannelFreqLimitInfoUpdate(contact: unknown, z: unknown, freqLimitInfo: unknown): any {
}
onContactUnreadCntUpdate(hashMap: unknown) {
onContactUnreadCntUpdate(hashMap: unknown): any {
}
onCustomWithdrawConfigUpdate(customWithdrawConfig: unknown) {
onCustomWithdrawConfigUpdate(customWithdrawConfig: unknown): any {
}
onDraftUpdate(contact: unknown, arrayList: unknown, j2: unknown) {
onDraftUpdate(contact: unknown, arrayList: unknown, j2: unknown): any {
}
onEmojiDownloadComplete(emojiNotifyInfo: unknown) {
onEmojiDownloadComplete(emojiNotifyInfo: unknown): any {
}
onEmojiResourceUpdate(emojiResourceInfo: unknown) {
onEmojiResourceUpdate(emojiResourceInfo: unknown): any {
}
onFeedEventUpdate(firstViewDirectMsgNotifyInfo: unknown) {
onFeedEventUpdate(firstViewDirectMsgNotifyInfo: unknown): any {
}
onFileMsgCome(arrayList: unknown) {
onFileMsgCome(arrayList: unknown): any {
}
onFirstViewDirectMsgUpdate(firstViewDirectMsgNotifyInfo: unknown) {
onFirstViewDirectMsgUpdate(firstViewDirectMsgNotifyInfo: unknown): any {
}
onFirstViewGroupGuildMapping(arrayList: unknown) {
onFirstViewGroupGuildMapping(arrayList: unknown): any {
}
onGrabPasswordRedBag(i2: unknown, str: unknown, i3: unknown, recvdOrder: unknown, msgRecord: unknown) {
onGrabPasswordRedBag(i2: unknown, str: unknown, i3: unknown, recvdOrder: unknown, msgRecord: unknown): any {
}
onGroupFileInfoAdd(groupItem: unknown) {
onGroupFileInfoAdd(groupItem: unknown): any {
}
onGroupFileInfoUpdate(groupFileListResult: GroupFileInfoUpdateParamType) {
onGroupFileInfoUpdate(groupFileListResult: GroupFileInfoUpdateParamType): any {
}
onGroupGuildUpdate(groupGuildNotifyInfo: unknown) {
onGroupGuildUpdate(groupGuildNotifyInfo: unknown): any {
}
onGroupTransferInfoAdd(groupItem: unknown) {
onGroupTransferInfoAdd(groupItem: unknown): any {
}
onGroupTransferInfoUpdate(groupFileListResult: unknown) {
onGroupTransferInfoUpdate(groupFileListResult: unknown): any {
}
onGuildInteractiveUpdate(guildInteractiveNotificationItem: unknown) {
onGuildInteractiveUpdate(guildInteractiveNotificationItem: unknown): any {
}
onGuildMsgAbFlagChanged(guildMsgAbFlag: unknown) {
onGuildMsgAbFlagChanged(guildMsgAbFlag: unknown): any {
}
onGuildNotificationAbstractUpdate(guildNotificationAbstractInfo: unknown) {
onGuildNotificationAbstractUpdate(guildNotificationAbstractInfo: unknown): any {
}
onHitCsRelatedEmojiResult(downloadRelateEmojiResultInfo: unknown) {
onHitCsRelatedEmojiResult(downloadRelateEmojiResultInfo: unknown): any {
}
onHitEmojiKeywordResult(hitRelatedEmojiWordsResult: unknown) {
onHitEmojiKeywordResult(hitRelatedEmojiWordsResult: unknown): any {
}
onHitRelatedEmojiResult(relatedWordEmojiInfo: unknown) {
onHitRelatedEmojiResult(relatedWordEmojiInfo: unknown): any {
}
onImportOldDbProgressUpdate(importOldDbMsgNotifyInfo: unknown) {
onImportOldDbProgressUpdate(importOldDbMsgNotifyInfo: unknown): any {
}
@@ -208,176 +208,176 @@ export class NodeIKernelMsgListener {
statusText: string;
timestamp: string;
toUin: string;
}) {
}): any {
}
onKickedOffLine(kickedInfo: KickedOffLineInfo) {
onKickedOffLine(kickedInfo: KickedOffLineInfo): any {
}
onLineDev(arrayList: unknown) {
onLineDev(arrayList: unknown): any {
}
onLogLevelChanged(j2: unknown) {
onLogLevelChanged(j2: unknown): any {
}
onMsgAbstractUpdate(arrayList: unknown) {
onMsgAbstractUpdate(arrayList: unknown): any {
}
onMsgBoxChanged(arrayList: unknown) {
onMsgBoxChanged(arrayList: unknown): any {
}
onMsgDelete(contact: unknown, arrayList: unknown) {
onMsgDelete(contact: unknown, arrayList: unknown): any {
}
onMsgEventListUpdate(hashMap: unknown) {
onMsgEventListUpdate(hashMap: unknown): any {
}
onMsgInfoListAdd(arrayList: unknown) {
onMsgInfoListAdd(arrayList: unknown): any {
}
onMsgInfoListUpdate(msgList: RawMessage[]) {
onMsgInfoListUpdate(msgList: RawMessage[]): any {
}
onMsgQRCodeStatusChanged(i2: unknown) {
onMsgQRCodeStatusChanged(i2: unknown): any {
}
onMsgRecall(i2: unknown, str: unknown, j2: unknown) {
onMsgRecall(i2: unknown, str: unknown, j2: unknown): any {
}
onMsgSecurityNotify(msgRecord: unknown) {
onMsgSecurityNotify(msgRecord: unknown): any {
}
onMsgSettingUpdate(msgSetting: unknown) {
onMsgSettingUpdate(msgSetting: unknown): any {
}
onNtFirstViewMsgSyncEnd() {
onNtFirstViewMsgSyncEnd(): any {
}
onNtMsgSyncEnd() {
onNtMsgSyncEnd(): any {
}
onNtMsgSyncStart() {
onNtMsgSyncStart(): any {
}
onReadFeedEventUpdate(firstViewDirectMsgNotifyInfo: unknown) {
onReadFeedEventUpdate(firstViewDirectMsgNotifyInfo: unknown): any {
}
onRecvGroupGuildFlag(i2: unknown) {
onRecvGroupGuildFlag(i2: unknown): any {
}
onRecvMsg(arrayList: RawMessage[]) {
onRecvMsg(arrayList: RawMessage[]): any {
}
onRecvMsgSvrRspTransInfo(j2: unknown, contact: unknown, i2: unknown, i3: unknown, str: unknown, bArr: unknown) {
onRecvMsgSvrRspTransInfo(j2: unknown, contact: unknown, i2: unknown, i3: unknown, str: unknown, bArr: unknown): any {
}
onRecvOnlineFileMsg(arrayList: unknown) {
onRecvOnlineFileMsg(arrayList: unknown): any {
}
onRecvS2CMsg(arrayList: unknown) {
onRecvS2CMsg(arrayList: unknown): any {
}
onRecvSysMsg(arrayList: Array<number>) {
onRecvSysMsg(arrayList: Array<number>): any {
}
onRecvUDCFlag(i2: unknown) {
onRecvUDCFlag(i2: unknown): any {
}
onRichMediaDownloadComplete(fileTransNotifyInfo: OnRichMediaDownloadCompleteParams) {
onRichMediaDownloadComplete(fileTransNotifyInfo: OnRichMediaDownloadCompleteParams): any {
}
onRichMediaProgerssUpdate(fileTransNotifyInfo: unknown) {
onRichMediaProgerssUpdate(fileTransNotifyInfo: unknown): any {
}
onRichMediaUploadComplete(fileTransNotifyInfo: unknown) {
onRichMediaUploadComplete(fileTransNotifyInfo: unknown): any {
}
onSearchGroupFileInfoUpdate(searchGroupFileResult: unknown) {
onSearchGroupFileInfoUpdate(searchGroupFileResult: unknown): any {
}
onSendMsgError(j2: unknown, contact: unknown, i2: unknown, str: unknown) {
onSendMsgError(j2: unknown, contact: unknown, i2: unknown, str: unknown): any {
}
onSysMsgNotification(i2: unknown, j2: unknown, j3: unknown, arrayList: unknown) {
onSysMsgNotification(i2: unknown, j2: unknown, j3: unknown, arrayList: unknown): any {
}
onTempChatInfoUpdate(tempChatInfo: TempOnRecvParams) {
onTempChatInfoUpdate(tempChatInfo: TempOnRecvParams): any {
}
onUnreadCntAfterFirstView(hashMap: unknown) {
onUnreadCntAfterFirstView(hashMap: unknown): any {
}
onUnreadCntUpdate(hashMap: unknown) {
onUnreadCntUpdate(hashMap: unknown): any {
}
onUserChannelTabStatusChanged(z: unknown) {
onUserChannelTabStatusChanged(z: unknown): any {
}
onUserOnlineStatusChanged(z: unknown) {
onUserOnlineStatusChanged(z: unknown): any {
}
onUserTabStatusChanged(arrayList: unknown) {
onUserTabStatusChanged(arrayList: unknown): any {
}
onlineStatusBigIconDownloadPush(i2: unknown, j2: unknown, str: unknown) {
onlineStatusBigIconDownloadPush(i2: unknown, j2: unknown, str: unknown): any {
}
onlineStatusSmallIconDownloadPush(i2: unknown, j2: unknown, str: unknown) {
onlineStatusSmallIconDownloadPush(i2: unknown, j2: unknown, str: unknown): any {
}
// 第一次发现于Linux
onUserSecQualityChanged(...args: unknown[]) {
onUserSecQualityChanged(...args: unknown[]): any {
}
onMsgWithRichLinkInfoUpdate(...args: unknown[]) {
onMsgWithRichLinkInfoUpdate(...args: unknown[]): any {
}
onRedTouchChanged(...args: unknown[]) {
onRedTouchChanged(...args: unknown[]): any {
}
// 第一次发现于Win 9.9.9-23159
onBroadcastHelperProgerssUpdate(...args: unknown[]) {
onBroadcastHelperProgerssUpdate(...args: unknown[]): any {
}
}

View File

@@ -5,67 +5,67 @@ export class NodeIKernelProfileListener {
}
onProfileSimpleChanged(...args: unknown[]) {
onProfileSimpleChanged(...args: unknown[]): any {
}
onProfileDetailInfoChanged(profile: User) {
onProfileDetailInfoChanged(profile: User): any {
}
onStatusUpdate(...args: unknown[]) {
onStatusUpdate(...args: unknown[]): any {
}
onSelfStatusChanged(...args: unknown[]) {
onSelfStatusChanged(...args: unknown[]): any {
}
onStrangerRemarkChanged(...args: unknown[]) {
onStrangerRemarkChanged(...args: unknown[]): any {
}
onMemberListChange(...args: unknown[]) {
onMemberListChange(...args: unknown[]): any {
}
onMemberInfoChange(...args: unknown[]) {
onMemberInfoChange(...args: unknown[]): any {
}
onGroupListUpdate(...args: unknown[]) {
onGroupListUpdate(...args: unknown[]): any {
}
onGroupAllInfoChange(...args: unknown[]) {
onGroupAllInfoChange(...args: unknown[]): any {
}
onGroupDetailInfoChange(...args: unknown[]) {
onGroupDetailInfoChange(...args: unknown[]): any {
}
onGroupConfMemberChange(...args: unknown[]) {
onGroupConfMemberChange(...args: unknown[]): any {
}
onGroupExtListUpdate(...args: unknown[]) {
onGroupExtListUpdate(...args: unknown[]): any {
}
onGroupNotifiesUpdated(...args: unknown[]) {
onGroupNotifiesUpdated(...args: unknown[]): any {
}
onGroupNotifiesUnreadCountUpdated(...args: unknown[]) {
onGroupNotifiesUnreadCountUpdated(...args: unknown[]): any {
}
onGroupMemberLevelInfoChange(...args: unknown[]) {
onGroupMemberLevelInfoChange(...args: unknown[]): any {
}
onGroupBulletinChange(...args: unknown[]) {
onGroupBulletinChange(...args: unknown[]): any {
}
}

View File

@@ -1,25 +1,25 @@
export class NodeIKernelRecentContactListener {
onDeletedContactsNotify(...args: unknown[]) {
onDeletedContactsNotify(...args: unknown[]): any {
}
onRecentContactNotification(msgList: any, arg0: { msgListUnreadCnt: string }, arg1: number) {
onRecentContactNotification(msgList: any, arg0: { msgListUnreadCnt: string }, arg1: number): any {
}
onMsgUnreadCountUpdate(...args: unknown[]) {
onMsgUnreadCountUpdate(...args: unknown[]): any {
}
onGuildDisplayRecentContactListChanged(...args: unknown[]) {
onGuildDisplayRecentContactListChanged(...args: unknown[]): any {
}
onRecentContactListChanged(...args: unknown[]) {
onRecentContactListChanged(...args: unknown[]): any {
}
onRecentContactListChangedVer2(...args: unknown[]) {
onRecentContactListChangedVer2(...args: unknown[]): any {
}
}

View File

@@ -1,13 +1,13 @@
export class NodeIKernelRobotListener {
onRobotFriendListChanged(...args: unknown[]) {
onRobotFriendListChanged(...args: unknown[]): any {
}
onRobotListChanged(...args: unknown[]) {
onRobotListChanged(...args: unknown[]): any {
}
onRobotProfileChanged(...args: unknown[]) {
onRobotProfileChanged(...args: unknown[]): any {
}
}

View File

@@ -57,7 +57,7 @@ export interface GroupSearchResult {
}
export interface NodeIKernelSearchListener {
onSearchGroupResult(params: GroupSearchResult): void;
onSearchGroupResult(params: GroupSearchResult): any;
onSearchFileKeywordsResult(params: {
searchId: string,
@@ -93,5 +93,5 @@ export interface NodeIKernelSearchListener {
end: number
}[]
}[]
}): void;
}): any;
}

View File

@@ -1,25 +1,25 @@
export class NodeIKernelSessionListener {
onNTSessionCreate(args: unknown) {
onNTSessionCreate(args: unknown): any {
}
onGProSessionCreate(args: unknown) {
onGProSessionCreate(args: unknown): any {
}
onSessionInitComplete(args: unknown) {
onSessionInitComplete(args: unknown): any {
}
onOpentelemetryInit(args: unknown) {
onOpentelemetryInit(args: unknown): any {
}
onUserOnlineResult(args: unknown) {
onUserOnlineResult(args: unknown): any {
}
onGetSelfTinyId(args: unknown) {
onGetSelfTinyId(args: unknown): any {
}
}

View File

@@ -1,21 +1,21 @@
export class NodeIKernelStorageCleanListener {
onCleanCacheProgressChanged(args: unknown) {
onCleanCacheProgressChanged(args: unknown): any {
}
onScanCacheProgressChanged(args: unknown) {
onScanCacheProgressChanged(args: unknown): any {
}
onCleanCacheStorageChanged(args: unknown) {
onCleanCacheStorageChanged(args: unknown): any {
}
onFinishScan(args: unknown) {
onFinishScan(args: unknown): any {
}
onChatCleanDone(args: unknown) {
onChatCleanDone(args: unknown): any {
}
}

View File

@@ -1,2 +1,5 @@
export class NodeIKernelTicketListener {
listener(): any {
}
}

View File

@@ -1,5 +1,5 @@
export class NodeIO3MiscListener {
getOnAmgomDataPiece(...arg: unknown[]) {
getOnAmgomDataPiece(...arg: unknown[]): any {
}
}

View File

@@ -16,8 +16,8 @@ export interface NativePacketExportType {
export class NativePacketClient extends IPacketClient {
private readonly supportedPlatforms = ['win32.x64', 'linux.x64', 'linux.arm64', 'darwin.x64', 'darwin.arm64'];
private MoeHooExport: { exports: NativePacketExportType } = { exports: {} };
private sendEvent = new LRUCache<number, string>(500); // seq->trace_id
private readonly MoeHooExport: { exports: NativePacketExportType } = { exports: {} };
private readonly sendEvent = new LRUCache<number, string>(500); // seq->trace_id
constructor(context: PacketContext, logStack: LogStack) {
super(context, logStack);

View File

@@ -1,9 +1,9 @@
import { Data, WebSocket } from "ws";
import { Data, WebSocket, ErrorEvent } from "ws";
import { IPacketClient, RecvPacket } from "@/core/packet/client/baseClient";
import { PacketContext } from "@/core/packet/context/packetContext";
import { LogStack } from "@/core/packet/context/clientContext";
export class wsPacketClient extends IPacketClient {
export class WsPacketClient extends IPacketClient {
private websocket: WebSocket | null = null;
private reconnectAttempts: number = 0;
private readonly maxReconnectAttempts: number = 60; // 现在暂时不可配置
@@ -85,11 +85,11 @@ export class wsPacketClient extends IPacketClient {
this.websocket.onmessage = (event) => this.handleMessage(event.data).catch(err => {
this.context.logger.error(`处理消息时出错: ${err}`);
});
this.websocket.onerror = (error) => {
this.websocket.onerror = (event: ErrorEvent) => {
this.available = false;
this.context.logger.error(`WebSocket 出错: ${error.message}`);
this.context.logger.error(`WebSocket 出错: ${event.message}`);
this.websocket?.close();
reject(error);
reject(new Error(`WebSocket 出错: ${event.message}`));
};
});
}

View File

@@ -24,7 +24,7 @@ export class PacketClientSession {
return this.context.operation;
}
// TODO: global message element adapter (?
// work: global message element adapter (?
get msgConverter() {
return this.context.msgConverter;
}

View File

@@ -1,7 +1,7 @@
import { PacketContext } from "@/core/packet/context/packetContext";
import { IPacketClient } from "@/core/packet/client/baseClient";
import { NativePacketClient } from "@/core/packet/client/nativeClient";
import { wsPacketClient } from "@/core/packet/client/wsClient";
import { WsPacketClient } from "@/core/packet/client/wsClient";
import { OidbPacket } from "@/core/packet/transformer/base";
import { PacketLogger } from "@/core/packet/context/loggerContext";
@@ -11,12 +11,12 @@ type clientPriority = {
const clientPriority: clientPriority = {
10: (context: PacketContext, logStack: LogStack) => new NativePacketClient(context, logStack),
1: (context: PacketContext, logStack: LogStack) => new wsPacketClient(context, logStack),
1: (context: PacketContext, logStack: LogStack) => new WsPacketClient(context, logStack),
};
export class LogStack {
private stack: string[] = [];
private logger: PacketLogger;
private readonly logger: PacketLogger;
constructor(logger: PacketLogger) {
this.logger = logger;
@@ -88,7 +88,7 @@ export class PacketClientContext {
break;
case "frida":
this.context.logger.info("[Core] [Packet] 使用指定的 FridaPacketClient 作为后端");
client = new wsPacketClient(this.context, this.logStack);
client = new WsPacketClient(this.context, this.logStack);
break;
case "auto":
case undefined:
@@ -98,9 +98,12 @@ export class PacketClientContext {
this.context.logger.error(`未知的PacketBackend ${prefer},请检查配置文件!`);
client = null;
}
if (!(client && client.check())) {
if (!client?.check()) {
throw new Error("[Core] [Packet] 无可用的后端NapCat.Packet将不会加载");
}
if (!client) {
throw new Error("[Core] [Packet] 后端异常NapCat.Packet将不会加载");
}
return client;
}

View File

@@ -1,7 +1,7 @@
import { LogLevel, LogWrapper } from "@/common/log";
import { PacketContext } from "@/core/packet/context/packetContext";
// TODO: check bind?
// work: check bind?
export class PacketLogger {
private readonly napLogger: LogWrapper;

View File

@@ -15,7 +15,7 @@ import { NapProtoDecodeStructType, NapProtoEncodeStructType } from "@napneko/nap
import { IndexNode, MsgInfo } from "@/core/packet/transformer/proto";
export class PacketOperationContext {
private context: PacketContext;
private readonly context: PacketContext;
constructor(context: PacketContext) {
this.context = context;
}
@@ -65,35 +65,22 @@ export class PacketOperationContext {
}
async UploadResources(msg: PacketMsg[], groupUin: number = 0) {
const reqList = [];
for (const m of msg) {
for (const e of m.msg) {
const chatType = groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C;
const peerUid = groupUin ? String(groupUin) : this.context.napcore.basicInfo.uid;
const reqList = msg.flatMap(m =>
m.msg.map(e => {
if (e instanceof PacketMsgPicElement) {
reqList.push(this.context.highway.uploadImage({
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
peerUid: groupUin ? String(groupUin) : this.context.napcore.basicInfo.uid
}, e));
}
if (e instanceof PacketMsgVideoElement) {
reqList.push(this.context.highway.uploadVideo({
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
peerUid: groupUin ? String(groupUin) : this.context.napcore.basicInfo.uid
}, e));
}
if (e instanceof PacketMsgPttElement) {
reqList.push(this.context.highway.uploadPtt({
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
peerUid: groupUin ? String(groupUin) : this.context.napcore.basicInfo.uid
}, e));
}
if (e instanceof PacketMsgFileElement) {
reqList.push(this.context.highway.uploadFile({
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
peerUid: groupUin ? String(groupUin) : this.context.napcore.basicInfo.uid
}, e));
}
}
return this.context.highway.uploadImage({ chatType, peerUid }, e);
} else if (e instanceof PacketMsgVideoElement) {
return this.context.highway.uploadVideo({ chatType, peerUid }, e);
} else if (e instanceof PacketMsgPttElement) {
return this.context.highway.uploadPtt({ chatType, peerUid }, e);
} else if (e instanceof PacketMsgFileElement) {
return this.context.highway.uploadFile({ chatType, peerUid }, e);
}
return null;
}).filter(Boolean)
);
const res = await Promise.allSettled(reqList);
this.context.logger.info(`上传资源${res.length}个,失败${res.filter(r => r.status === 'rejected').length}`);
res.forEach((result, index) => {

View File

@@ -33,7 +33,7 @@ export interface PacketHighwaySig {
}
export class PacketHighwayContext {
private context: PacketContext;
private readonly context: PacketContext;
protected sig: PacketHighwaySig;
protected logger: PacketLogger;
protected hwClient: PacketHighwayClient;
@@ -223,12 +223,12 @@ export class PacketHighwayContext {
msgInfoBody: preRespData.upload.msgInfo.msgInfoBody,
blockSize: BlockSize,
hash: {
fileSha1: await calculateSha1StreamBytes(video.filePath!)
fileSha1: await calculateSha1StreamBytes(video.filePath)
}
});
await this.hwClient.upload(
1005,
fs.createReadStream(video.filePath!, { highWaterMark: BlockSize }),
fs.createReadStream(video.filePath, { highWaterMark: BlockSize }),
+video.fileSize!,
md5,
extend
@@ -256,7 +256,7 @@ export class PacketHighwayContext {
});
await this.hwClient.upload(
1006,
fs.createReadStream(video.thumbPath!, { highWaterMark: BlockSize }),
fs.createReadStream(video.thumbPath, { highWaterMark: BlockSize }),
+video.thumbSize!,
md5,
extend
@@ -288,12 +288,12 @@ export class PacketHighwayContext {
msgInfoBody: preRespData.upload.msgInfo.msgInfoBody,
blockSize: BlockSize,
hash: {
fileSha1: await calculateSha1StreamBytes(video.filePath!)
fileSha1: await calculateSha1StreamBytes(video.filePath)
}
});
await this.hwClient.upload(
1001,
fs.createReadStream(video.filePath!, { highWaterMark: BlockSize }),
fs.createReadStream(video.filePath, { highWaterMark: BlockSize }),
+video.fileSize!,
md5,
extend
@@ -321,7 +321,7 @@ export class PacketHighwayContext {
});
await this.hwClient.upload(
1002,
fs.createReadStream(video.thumbPath!, { highWaterMark: BlockSize }),
fs.createReadStream(video.thumbPath, { highWaterMark: BlockSize }),
+video.thumbSize!,
md5,
extend

View File

@@ -64,11 +64,11 @@ export class HighwayHttpUploader extends IHighwayUploader {
});
});
req.write(frame);
req.on('error', (error) => {
req.on('error', (error: Error) => {
reject(error);
});
} catch (error) {
reject(error);
} catch (error: unknown) {
reject(new Error((error as Error).message));
}
});
}

View File

@@ -7,12 +7,12 @@ export const int32ip2str = (ip: number) => {
return [ip & 0xff, (ip & 0xff00) >> 8, (ip & 0xff0000) >> 16, ((ip & 0xff000000) >> 24) & 0xff].join('.');
};
export const oidbIpv4s2HighwayIpv4s = (ipv4s: NapProtoEncodeStructType<typeof proto.IPv4>[]): NapProtoEncodeStructType<typeof proto.NTHighwayIPv4>[] =>{
export const oidbIpv4s2HighwayIpv4s = (ipv4s: NapProtoEncodeStructType<typeof proto.IPv4>[]): NapProtoEncodeStructType<typeof proto.NTHighwayIPv4>[] => {
return ipv4s.map((ip) => {
return {
domain: {
isEnable: true,
ip: int32ip2str(ip.outIP!),
ip: int32ip2str(ip.outIP ?? 0),
},
port: ip.outPort!
} as NapProtoEncodeStructType<typeof proto.NTHighwayIPv4>;

View File

@@ -16,7 +16,7 @@ export class PacketMsgBuilder {
return element.map((node): NapProtoEncodeStructType<typeof PushMsgBody> => {
const avatar = `https://q.qlogo.cn/headimg_dl?dst_uin=${node.senderUin}&spec=640&img_type=jpg`;
const msgContent = node.msg.reduceRight((acc: undefined | Uint8Array, msg: IPacketMsgElement<PacketSendMsgElement>) => {
return acc !== undefined ? acc : msg.buildContent();
return acc ?? msg.buildContent();
}, undefined);
const msgElement = node.msg.flatMap(msg => msg.buildElement() ?? []);
if (!msgContent && !msgElement.length) {

View File

@@ -76,13 +76,13 @@ export type rawMsgWithSendMsg = {
msg: PacketSendMsgElement[]
}
// TODO: make it become adapter?
// work:make it become adapter?
export class PacketMsgConverter {
private isValidElementType(type: ElementType): type is keyof ElementToPacketMsgConverters {
return SupportedElementTypes.includes(type);
}
private rawToPacketMsgConverters: ElementToPacketMsgConverters = {
private readonly rawToPacketMsgConverters: ElementToPacketMsgConverters = {
[ElementType.TEXT]: (element) => {
if (element.textElement?.atType) {
return new PacketMsgAtElement(element as SendTextElement);
@@ -116,7 +116,7 @@ export class PacketMsgConverter {
[ElementType.MARKDOWN]: (element) => {
return new PacketMsgMarkDownElement(element as SendMarkdownElement);
},
// TODO: check this logic, move it in arkElement?
// work:check this logic, move it in arkElement?
[ElementType.STRUCTLONGMSG]: (element) => {
return new PacketMultiMsgElement(element as SendStructLongMsgElement);
}

View File

@@ -32,7 +32,7 @@ import { ForwardMsgBuilder } from "@/common/forward-msg-builder";
import { PacketMsg, PacketSendMsgElement } from "@/core/packet/message/message";
// raw <-> packet
// TODO: SendStructLongMsgElement
// work:SendStructLongMsgElement
export abstract class IPacketMsgElement<T extends PacketSendMsgElement> {
protected constructor(rawElement: T) {
}
@@ -118,7 +118,7 @@ export class PacketMsgReplyElement extends IPacketMsgElement<SendReplyElement> {
this.targetUin = +(element.replyElement.senderUin ?? 0);
this.targetUid = element.replyElement.senderUidStr ?? '';
this.time = +(element.replyElement.replyMsgTime ?? 0);
this.elems = []; // TODO: in replyElement.sourceMsgTextElems
this.elems = []; // work:in replyElement.sourceMsgTextElems
}
get isGroupReply(): boolean {
@@ -131,7 +131,7 @@ export class PacketMsgReplyElement extends IPacketMsgElement<SendReplyElement> {
origSeqs: [this.isGroupReply ? this.messageClientSeq : this.messageSeq],
senderUin: BigInt(this.targetUin),
time: this.time,
elems: [], // TODO: in replyElement.sourceMsgTextElems
elems: [], // work:in replyElement.sourceMsgTextElems
pbReserve: {
messageId: this.messageId,
},
@@ -346,9 +346,9 @@ export class PacketMsgPttElement extends IPacketMsgElement<SendPttElement> {
constructor(element: SendPttElement) {
super(element);
this.filePath = element.pttElement.filePath;
this.fileSize = +element.pttElement.fileSize; // TODO: cc
this.fileSize = +element.pttElement.fileSize; // work:cc
this.fileMd5 = element.pttElement.md5HexStr;
this.fileDuration = Math.round(element.pttElement.duration); // TODO: cc
this.fileDuration = Math.round(element.pttElement.duration); // work:cc
}
get valid(): boolean {

View File

@@ -25,7 +25,7 @@ class DownloadOfflineFile extends PacketTransformer<typeof proto.OidbSvcTrpcTcp0
return OidbBase.build(0xE37, 800, body, false, false);
}
// TODO: check
// work:check
parse(data: Buffer) {
const oidbBody = OidbBase.parse(data).body;
return new NapProtoMsg(proto.OidbSvcTrpcTcp0XE37Response).decode(oidbBody);

View File

@@ -16,7 +16,7 @@ class FetchSessionKey extends PacketTransformer<typeof proto.HttpConn0x6ff_501Re
field4: 1,
field6: 3,
serviceTypes: [1, 5, 10, 21],
// tgt: "", // TODO: do we really need tgt? seems not
// tgt: "", // work:do we really need tgt? seems not
field9: 2,
field10: 9,
field11: 8,

View File

@@ -16,7 +16,7 @@ class UploadGroupFile extends PacketTransformer<typeof proto.OidbSvcTrpcTcp0x6D6
appId: 4,
busId: 102,
entrance: 6,
targetDirectory: '/', // TODO:
targetDirectory: '/', // work:
fileName: file.fileName,
localDirectory: `/${file.fileName}`,
fileSize: BigInt(file.fileSize),

View File

@@ -40,7 +40,7 @@ class UploadGroupImage extends PacketTransformer<typeof proto.NTV2RichMediaResp>
fileName: img.name,
type: {
type: 1,
picFormat: img.picType, //TODO: extend NapCat imgType /cc @MliKiowa
picFormat: img.picType, //work:extend NapCat imgType /cc @MliKiowa
videoFormat: 0,
voiceFormat: 0,
},
@@ -59,7 +59,7 @@ class UploadGroupImage extends PacketTransformer<typeof proto.NTV2RichMediaResp>
extBizInfo: {
pic: {
bytesPbReserveTroop: Buffer.from("0800180020004200500062009201009a0100a2010c080012001800200028003a00", 'hex'),
textSummary: "Nya~", // TODO:
textSummary: "Nya~", // work:
},
video: {
bytesPbReserve: Buffer.alloc(0),

View File

@@ -40,7 +40,7 @@ class UploadPrivateImage extends PacketTransformer<typeof proto.NTV2RichMediaRes
fileName: img.name,
type: {
type: 1,
picFormat: img.picType, //TODO: extend NapCat imgType /cc @MliKiowa
picFormat: img.picType, //work:extend NapCat imgType /cc @MliKiowa
videoFormat: 0,
voiceFormat: 0,
},
@@ -59,7 +59,7 @@ class UploadPrivateImage extends PacketTransformer<typeof proto.NTV2RichMediaRes
extBizInfo: {
pic: {
bytesPbReserveTroop: Buffer.from("0800180020004200500062009201009a0100a2010c080012001800200028003a00", 'hex'),
textSummary: "Nya~", // TODO:
textSummary: "Nya~", // work:
},
video: {
bytesPbReserve: Buffer.alloc(0),

View File

@@ -5,17 +5,17 @@ import * as fs from 'fs';
import { CalculateStreamBytesTransform } from "@/core/packet/utils/crypto/sha1StreamBytesTransform";
function sha1Stream(readable: stream.Readable) {
return new Promise((resolve, reject) => {
return new Promise<Buffer>((resolve, reject) => {
readable.on('error', reject);
readable.pipe(crypto.createHash('sha1').on('error', reject).on('data', resolve));
}) as Promise<Buffer>;
});
}
function md5Stream(readable: stream.Readable) {
return new Promise((resolve, reject) => {
return new Promise<Buffer>((resolve, reject) => {
readable.on('error', reject);
readable.pipe(crypto.createHash('md5').on('error', reject).on('data', resolve));
}) as Promise<Buffer>;
});
}
export function calculateSha1(filePath: string): Promise<Buffer> {
@@ -39,7 +39,7 @@ export function calculateSha1StreamBytes(filePath: string): Promise<Buffer[]> {
calculateStreamBytes.on('end', () => {
resolve(byteArrayList);
});
calculateStreamBytes.on('error', (err) => {
calculateStreamBytes.on('error', (err: Error) => {
reject(err);
});
readable.pipe(calculateStreamBytes);

View File

@@ -45,11 +45,17 @@ export class Sha1Stream {
let e = this._state[4];
for (let i = 0; i < 80; i++) {
const [f, k] = (i < 20) ? [(b & c) | ((~b) & d), 0x5A827999] :
(i < 40) ? [b ^ c ^ d, 0x6ED9EBA1] :
(i < 60) ? [(b & c) | (b & d) | (c & d), 0x8F1BBCDC] :
[b ^ c ^ d, 0xCA62C1D6];
const temp = (this.rotateLeft(a, 5) + f + k + e + w[i]) >>> 0;
let temp;
if (i < 20) {
temp = ((b & c) | (~b & d)) + 0x5A827999;
} else if (i < 40) {
temp = (b ^ c ^ d) + 0x6ED9EBA1;
} else if (i < 60) {
temp = ((b & c) | (b & d) | (c & d)) + 0x8F1BBCDC;
} else {
temp = (b ^ c ^ d) + 0xCA62C1D6;
}
temp += ((this.rotateLeft(a, 5) + e + w[i]) >>> 0);
e = d;
d = c;
c = this.rotateLeft(b, 30) >>> 0;

View File

@@ -3,7 +3,7 @@ import { Sha1Stream } from "@/core/packet/utils/crypto/sha1Stream";
export class CalculateStreamBytesTransform extends stream.Transform {
private readonly blockSize = 1024 * 1024;
private sha1: Sha1Stream;
private readonly sha1: Sha1Stream;
private buffer: Buffer;
private bytesRead: number;
private readonly byteArrayList: Buffer[];

View File

@@ -9,10 +9,10 @@ import {
type MiniAppTemplateNameList = "bili" | "weibo";
export abstract class MiniAppInfo {
static sdkId: string = "V1_PC_MINISDK_99.99.99_1_APP_A";
static readonly sdkId: string = "V1_PC_MINISDK_99.99.99_1_APP_A";
template: MiniAppReqTemplateParams;
private static appMap = new Map<MiniAppTemplateNameList, MiniAppInfo>();
private static readonly appMap = new Map<MiniAppTemplateNameList, MiniAppInfo>();
protected constructor(template: MiniAppReqTemplateParams) {
this.template = template;
@@ -22,7 +22,7 @@ export abstract class MiniAppInfo {
return this.appMap.get(name);
}
static Bili = new class extends MiniAppInfo {
static readonly Bili = new class extends MiniAppInfo {
constructor() {
super({
sdkId: MiniAppInfo.sdkId,
@@ -40,7 +40,7 @@ export abstract class MiniAppInfo {
}
};
static WeiBo = new class extends MiniAppInfo {
static readonly WeiBo = new class extends MiniAppInfo {
constructor() {
super({
sdkId: MiniAppInfo.sdkId,

View File

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

View File

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

View File

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

Binary file not shown.

View File

@@ -1,40 +0,0 @@
import { constants } from "node:os";
import path from "path";
import { dlopen } from "process";
import fs from "fs";
export class Native {
platform: string;
supportedPlatforms = [''];
MoeHooExport: any = { exports: {} };
recallHookEnabled: boolean = false;
inited = true;
constructor(nodePath: string, platform: string = process.platform) {
this.platform = platform;
try {
if (!this.supportedPlatforms.includes(this.platform)) {
throw new Error(`Platform ${this.platform} is not supported`);
}
const nativeNode = path.join(nodePath, './native/MoeHoo.win32.node');
if (fs.existsSync(nativeNode)) {
dlopen(this.MoeHooExport, nativeNode, constants.dlopen.RTLD_LAZY);
}
} catch (error) {
this.inited = false;
}
}
isSetReCallEnabled(): boolean {
return this.recallHookEnabled && this.inited;
}
registerRecallCallback(callback: (hex: string) => any): void {
try {
if (!this.inited) throw new Error('Native Not Init');
if (this.MoeHooExport.exports?.registMsgPush) {
this.MoeHooExport.exports.registMsgPush(callback);
this.recallHookEnabled = true;
}
} catch (error) {
this.recallHookEnabled = false;
}
}
}

View File

@@ -29,7 +29,7 @@ abstract class BaseAction<PayloadType, ReturnDataType> {
});
return {
valid: false,
message: errorMessages.join('\n') as string || '未知错误',
message: errorMessages.join('\n') ?? '未知错误',
};
}
return {

View File

@@ -6,7 +6,7 @@ export class GetFriendWithCategory extends BaseAction<void, any> {
actionName = ActionName.GetFriendsWithCategory;
async _handle(payload: void) {
return (await this.core.apis.FriendApi.getBuddyV2ExWithCate(true)).map(category => ({
return (await this.core.apis.FriendApi.getBuddyV2ExWithCate()).map(category => ({
...category,
buddyList: OB11Entities.friendsV2(category.buddyList),
}));

View File

@@ -60,7 +60,7 @@ export class GetMiniAppArk extends GetPacketStatusDepends<Payload, {
if (payload.type) {
reqParam = MiniAppInfoHelper.generateReq(customParams, MiniAppInfo.get(payload.type)!.template);
} else {
const { appId, scene, iconUrl, templateType, businessType, verType, shareType, versionId, withShareTicket } = payload as Required<Payload>;
const { appId, scene, iconUrl, templateType, businessType, verType, shareType, versionId, withShareTicket } = payload;
reqParam = MiniAppInfoHelper.generateReq(
customParams,
{

View File

@@ -1,5 +1,5 @@
import BaseAction from '../BaseAction';
import { ActionName, BaseCheckResult } from '../types';
import { ActionName } from '../types';
interface Payload {
start: number,
@@ -13,9 +13,9 @@ export class GetProfileLike extends BaseAction<Payload, any> {
const start = payload.start ? Number(payload.start) : 0;
const count = payload.count ? Number(payload.count) : 10;
const ret = await this.core.apis.UserApi.getProfileLike(this.core.selfInfo.uid, start, count);
const listdata: any[] = ret.info.userLikeInfos[0].voteInfo.userInfos;
for (let i = 0; i < listdata.length; i++) {
listdata[i].uin = parseInt((await this.core.apis.UserApi.getUinByUidV2(listdata[i].uid)) || '');
const listdata = ret.info.userLikeInfos[0].voteInfo.userInfos;
for (const item of listdata) {
item.uin = parseInt((await this.core.apis.UserApi.getUinByUidV2(item.uid)) || '');
}
return ret.info.userLikeInfos[0].voteInfo;
}

View File

@@ -21,7 +21,7 @@ export class OCRImage extends BaseAction<Payload, any> {
async _handle(payload: Payload) {
const { path, success } = (await uri2local(this.core.NapCatTempPath, payload.image));
if (!success) {
throw `OCR ${payload.image}失败,image字段可能格式不正确`;
throw new Error(`OCR ${payload.image}失败,image字段可能格式不正确`);
}
if (path) {
await checkFileReceived(path, 5000); // 文件不存在QQ会崩溃需要提前判断
@@ -29,12 +29,12 @@ export class OCRImage extends BaseAction<Payload, any> {
fs.unlink(path, () => { });
if (!ret) {
throw `OCR ${payload.file}失败`;
throw new Error(`OCR ${payload.file}失败`);
}
return ret.result;
}
fs.unlink(path, () => { });
throw `OCR ${payload.file}失败,文件可能不存在`;
throw new Error(`OCR ${payload.file}失败,文件可能不存在`);
}
}

View File

@@ -1,7 +1,7 @@
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import BaseAction from '../BaseAction';
import { ActionName, BaseCheckResult } from '../types';
import { ChatType, Peer } from '@/core';
import { ActionName } from '../types';
import { ChatType } from '@/core';
const SchemaData = {
type: 'object',
@@ -9,7 +9,7 @@ const SchemaData = {
event_type: { type: 'number' },
user_id: { type: ['number', 'string'] },
},
required: ['event_type','user_id'],
required: ['event_type', 'user_id'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;

View File

@@ -26,7 +26,7 @@ export default class SetAvatar extends BaseAction<Payload, null> {
async _handle(payload: Payload): Promise<null> {
const { path, success } = (await uri2local(this.core.NapCatTempPath, payload.file));
if (!success) {
throw `头像${payload.file}设置失败,file字段可能格式不正确`;
throw new Error(`头像${payload.file}设置失败,file字段可能格式不正确`);
}
if (path) {
await checkFileReceived(path, 5000); // 文件不存在QQ会崩溃需要提前判断
@@ -35,18 +35,18 @@ export default class SetAvatar extends BaseAction<Payload, null> {
});
if (!ret) {
throw `头像${payload.file}设置失败,api无返回`;
throw new Error(`头像${payload.file}设置失败,api无返回`);
}
// log(`头像设置返回:${JSON.stringify(ret)}`)
if (ret.result as number == 1004022) {
throw `头像${payload.file}设置失败,文件可能不是图片格式`;
throw new Error(`头像${payload.file}设置失败,文件可能不是图片格式`);
} else if (ret.result != 0) {
throw `头像${payload.file}设置失败,未知的错误,${ret.result}:${ret.errMsg}`;
throw new Error(`头像${payload.file}设置失败,未知的错误,${ret.result}:${ret.errMsg}`);
}
} else {
fs.unlink(path, () => { });
throw `头像${payload.file}设置失败,无法获取头像,文件可能不存在`;
throw new Error(`头像${payload.file}设置失败,无法获取头像,文件可能不存在`);
}
return null;
}

View File

@@ -61,7 +61,7 @@ export class GoCQHTTPGetForwardMsgAction extends BaseAction<Payload, any> {
}
const rootMsgId = MessageUnique.getShortIdByMsgId(msgId);
const rootMsg = MessageUnique.getMsgIdAndPeerByShortId(rootMsgId || parseInt(msgId));
const rootMsg = MessageUnique.getMsgIdAndPeerByShortId(rootMsgId ?? +msgId);
if (!rootMsg) {
throw new Error('msg not found');
}

View File

@@ -31,14 +31,14 @@ export default class GetFriendMsgHistory extends BaseAction<Payload, Response> {
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
const MsgCount = +(payload.count ?? 20);
const isReverseOrder = typeof payload.reverseOrder === 'string' ? payload.reverseOrder === 'true' : !!payload.reverseOrder;
if (!uid) throw `记录${payload.user_id}不存在`;
if (!uid) throw new Error(`记录${payload.user_id}不存在`);
const friend = await this.core.apis.FriendApi.isBuddy(uid);
const peer = { chatType: friend ? ChatType.KCHATTYPEC2C : ChatType.KCHATTYPETEMPC2CFROMGROUP, peerUid: uid };
const hasMessageSeq = !payload.message_seq ? !!payload.message_seq : !(payload.message_seq?.toString() === '' || payload.message_seq?.toString() === '0');
const startMsgId = hasMessageSeq ? (MessageUnique.getMsgIdAndPeerByShortId(+payload.message_seq!)?.MsgId ?? payload.message_seq!.toString()) : '0';
const msgList = hasMessageSeq ?
(await this.core.apis.MsgApi.getMsgHistory(peer, startMsgId, MsgCount)).msgList : (await this.core.apis.MsgApi.getAioFirstViewLatestMsgs(peer, MsgCount)).msgList;
if (msgList.length === 0) throw `消息${payload.message_seq}不存在`;
if (msgList.length === 0) throw new Error(`消息${payload.message_seq}不存在`);
//翻转消息
if (isReverseOrder) msgList.reverse();
//转换序号

View File

@@ -15,7 +15,7 @@ type Payload = FromSchema<typeof SchemaData>;
export class GetGroupFileSystemInfo extends BaseAction<Payload, {
file_count: number,
limit_count: number, // unimplemented
used_space: number, // todo: unimplemented, but can be implemented later
used_space: number, // work:unimplemented, but can be implemented later
total_space: number, // unimplemented, 10 GB by default
}> {
actionName = ActionName.GoCQHTTP_GetGroupFileSystemInfo;

View File

@@ -36,7 +36,7 @@ export default class GoCQHTTPGetGroupMsgHistory extends BaseAction<Payload, Resp
const startMsgId = hasMessageSeq ? (MessageUnique.getMsgIdAndPeerByShortId(+payload.message_seq!)?.MsgId ?? payload.message_seq!.toString()) : '0';
const msgList = hasMessageSeq ?
(await this.core.apis.MsgApi.getMsgHistory(peer, startMsgId, MsgCount)).msgList : (await this.core.apis.MsgApi.getAioFirstViewLatestMsgs(peer, MsgCount)).msgList;
if (msgList.length === 0) throw `消息${payload.message_seq}不存在`;
if (msgList.length === 0) throw new Error(`消息${payload.message_seq}不存在`);
//翻转消息
if (isReverseOrder) msgList.reverse();
//转换序号

View File

@@ -34,15 +34,15 @@ export class SendGroupNotice extends BaseAction<Payload, null> {
success,
} = (await uri2local(this.core.NapCatTempPath, payload.image));
if (!success) {
throw `群公告${payload.image}设置失败,image字段可能格式不正确`;
throw new Error(`群公告${payload.image}设置失败,image字段可能格式不正确`);
}
if (!path) {
throw `群公告${payload.image}设置失败,获取资源失败`;
throw new Error(`群公告${payload.image}设置失败,获取资源失败`);
}
await checkFileReceived(path, 5000); // 文件不存在QQ会崩溃需要提前判断
const ImageUploadResult = await this.core.apis.GroupApi.uploadGroupBulletinPic(payload.group_id.toString(), path);
if (ImageUploadResult.errCode != 0) {
throw `群公告${payload.image}设置失败,图片上传失败`;
throw new Error(`群公告${payload.image}设置失败,图片上传失败`);
}
unlink(path, () => {

View File

@@ -27,24 +27,24 @@ export default class SetGroupPortrait extends BaseAction<Payload, any> {
async _handle(payload: Payload): Promise<any> {
const { path, success } = (await uri2local(this.core.NapCatTempPath, payload.file));
if (!success) {
throw `头像${payload.file}设置失败,file字段可能格式不正确`;
throw new Error(`头像${payload.file}设置失败,file字段可能格式不正确`);
}
if (path) {
await checkFileReceived(path, 5000); // 文件不存在QQ会崩溃需要提前判断
const ret = await this.core.apis.GroupApi.setGroupAvatar(payload.group_id.toString(), path);
fs.unlink(path, () => { });
if (!ret) {
throw `头像${payload.file}设置失败,api无返回`;
throw new Error(`头像${payload.file}设置失败,api无返回`);
}
if (ret.result as number == 1004022) {
throw `头像${payload.file}设置失败,文件可能不是图片格式或权限不足`;
throw new Error(`头像${payload.file}设置失败,文件可能不是图片格式或权限不足`);
} else if (ret.result != 0) {
throw `头像${payload.file}设置失败,未知的错误,${ret.result}:${ret.errMsg}`;
throw new Error(`头像${payload.file}设置失败,未知的错误,${ret.result}:${ret.errMsg}`);
}
return ret;
} else {
fs.unlink(path, () => {});
throw `头像${payload.file}设置失败,无法获取头像,文件可能不存在`;
fs.unlink(path, () => { });
throw new Error(`头像${payload.file}设置失败,无法获取头像,文件可能不存在`);
}
}
}

View File

@@ -14,7 +14,7 @@ const SchemaData = {
type Payload = FromSchema<typeof SchemaData>;
export class SetQQProfile extends BaseAction<Payload, any | null> {
export class SetQQProfile extends BaseAction<Payload, any> {
actionName = ActionName.SetQQProfile;
payloadSchema = SchemaData;

View File

@@ -27,7 +27,7 @@ export default class GoCQHTTPUploadPrivateFile extends BaseAction<Payload, null>
if (payload.user_id) {
const peerUid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
if (!peerUid) {
throw `私聊${payload.user_id}不存在`;
throw new Error( `私聊${payload.user_id}不存在`);
}
const isBuddy = await this.core.apis.FriendApi.isBuddy(peerUid);
return { chatType: isBuddy ? ChatType.KCHATTYPEC2C : ChatType.KCHATTYPETEMPC2CFROMGROUP, peerUid };

View File

@@ -3,7 +3,6 @@ import { OB11Entities } from '@/onebot/entities';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { GroupMember } from '@/core';
const SchemaData = {
type: 'object',
@@ -44,7 +43,7 @@ class GetGroupMemberInfo extends BaseAction<Payload, OB11GroupMember> {
} else {
this.core.context.logger.logDebug(`获取群成员详细信息失败, 只能返回基础信息`);
}
return OB11Entities.groupMember(payload.group_id.toString(), member as GroupMember);
return OB11Entities.groupMember(payload.group_id.toString(), member);
}
}

View File

@@ -1,4 +1,3 @@
import { OB11Group } from '@/onebot';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';

View File

@@ -26,7 +26,7 @@ export class SendGroupAiRecord extends GetPacketStatusDepends<Payload, {
async _handle(payload: Payload) {
const rawRsp = await this.core.apis.PacketApi.pkt.operation.GetAiVoice(+payload.group_id, payload.character, payload.text, AIVoiceChatType.Sound);
const url = await this.core.apis.PacketApi.pkt.operation.GetGroupPttUrl(+payload.group_id, rawRsp.msgInfoBody[0].index);
const { path, fileName, errMsg, success } = (await uri2local(this.core.NapCatTempPath, url));
const { path, errMsg, success } = (await uri2local(this.core.NapCatTempPath, url));
if (!success) {
throw new Error(errMsg);
}

View File

@@ -28,7 +28,7 @@ class GetMsg extends BaseAction<Payload, OB11Message> {
throw Error('参数message_id不能为空');
}
const MsgShortId = MessageUnique.getShortIdByMsgId(payload.message_id.toString());
const msgIdWithPeer = MessageUnique.getMsgIdAndPeerByShortId(MsgShortId || parseInt(payload.message_id.toString()));
const msgIdWithPeer = MessageUnique.getMsgIdAndPeerByShortId(MsgShortId ?? +payload.message_id);
if (!msgIdWithPeer) {
throw new Error('消息不存在');
}

View File

@@ -30,7 +30,7 @@ class MarkMsgAsRead extends BaseAction<PlayloadType, null> {
if (payload.user_id) {
const peerUid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
if (!peerUid) {
throw `私聊${payload.user_id}不存在`;
throw new Error( `私聊${payload.user_id}不存在`);
}
const isBuddy = await this.core.apis.FriendApi.isBuddy(peerUid);
return { chatType: isBuddy ? ChatType.KCHATTYPEC2C : ChatType.KCHATTYPETEMPC2CFROMGROUP, peerUid };

View File

@@ -68,7 +68,7 @@ export async function createContext(core: NapCatCore, payload: OB11PostContext,
}
return {
chatType: ChatType.KCHATTYPEC2C,
peerUid: Uid!,
peerUid: Uid,
guildId: '',
};
}
@@ -133,7 +133,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
guildId: '',
peerUid: peer.peerUid,
chatType: peer.chatType,
}, (returnMsgAndResId.message)!.msgId);
}, (returnMsgAndResId.message).msgId);
return { message_id: msgShortId!, res_id: returnMsgAndResId.res_id };
} else if (returnMsgAndResId.res_id && !returnMsgAndResId.message) {
throw Error(`发送转发消息res_id${returnMsgAndResId.res_id} 失败`);
@@ -150,7 +150,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
const { sendElements, deleteAfterSentFiles } = await this.obContext.apis.MsgApi
.createSendElements(messages, peer);
const returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(peer, sendElements, deleteAfterSentFiles);
return { message_id: returnMsg!.id! };
return { message_id: returnMsg.id! };
}
private async uploadForwardedNodesPacket(msgPeer: Peer, messageNodes: OB11MessageNode[], source?: string, news?: {
@@ -175,7 +175,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
if (getSpecialMsgNum({ message: OB11Data }, OB11MessageDataType.node)) {
const uploadReturnData = await this.uploadForwardedNodesPacket(msgPeer, OB11Data as OB11MessageNode[], node.data.source, node.data.news, node.data.summary, node.data.prompt, {
user_id: (node.data.user_id || node.data.uin)?.toString() ?? parentMeta?.user_id ?? this.core.selfInfo.uin,
user_id: (node.data.user_id ?? node.data.uin)?.toString() ?? parentMeta?.user_id ?? this.core.selfInfo.uin,
nickname: (node.data.nickname || node.data.name) ?? parentMeta?.nickname ?? "QQ用户",
}, dp + 1);
sendElements = uploadReturnData?.finallySendElements ? [uploadReturnData.finallySendElements] : [];
@@ -185,7 +185,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
}
const packetMsgElements: rawMsgWithSendMsg = {
senderUin: Number((node.data.user_id || node.data.uin) ?? parentMeta?.user_id) || +this.core.selfInfo.uin,
senderUin: Number((node.data.user_id ?? node.data.uin) ?? parentMeta?.user_id) || +this.core.selfInfo.uin,
senderName: (node.data.nickname || node.data.name) ?? parentMeta?.nickname ?? "QQ用户",
groupId: msgPeer.chatType === ChatType.KCHATTYPEGROUP ? +msgPeer.peerUid : undefined,
time: Number(node.data.time) || Date.now(),
@@ -194,7 +194,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
logger.logDebug(`handleForwardedNodesPacket[SendRaw] 开始转换 ${stringifyWithBigInt(packetMsgElements)}`);
const transformedMsg = this.core.apis.PacketApi.pkt.msgConverter.rawMsgWithSendMsgToPacketMsg(packetMsgElements);
logger.logDebug(`handleForwardedNodesPacket[SendRaw] 转换为 ${stringifyWithBigInt(transformedMsg)}`);
packetMsg.push(transformedMsg!);
packetMsg.push(transformedMsg);
} else if (node.data.id) {
const id = node.data.id;
const nodeMsg = MessageUnique.getMsgIdAndPeerByShortId(+id) || MessageUnique.getPeerByMsgId(id);
@@ -207,7 +207,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
await this.core.apis.FileApi.downloadRawMsgMedia([msg]);
const transformedMsg = this.core.apis.PacketApi.pkt.msgConverter.rawMsgToPacketMsg(msg, msgPeer);
logger.logDebug(`handleForwardedNodesPacket[PureRaw] 转换为 ${stringifyWithBigInt(transformedMsg)}`);
packetMsg.push(transformedMsg!);
packetMsg.push(transformedMsg);
} else {
logger.logDebug(`handleForwardedNodesPacket 跳过元素 ${stringifyWithBigInt(node)}`);
}

View File

@@ -7,7 +7,7 @@ export abstract class GetPacketStatusDepends<PT, RT> extends BaseAction<PT, RT>
protected async check(payload: PT): Promise<BaseCheckResult>{
if (!this.core.apis.PacketApi.available) {
// TODO: add error stack?
// work:add error stack?
return {
valid: false,
message: "packetBackend不可用请参照文档 https://napneko.github.io/config/advanced 和启动日志检查packetBackend状态或进行配置" +

View File

@@ -18,13 +18,11 @@ export default class SendLike extends BaseAction<Payload, null> {
payloadSchema = SchemaData;
async _handle(payload: Payload): Promise<null> {
//logDebug('点赞参数', payload);
const qq = payload.user_id.toString();
const uid: string = await this.core.apis.UserApi.getUidByUinV2(qq) || '';
const uid: string = await this.core.apis.UserApi.getUidByUinV2(qq) ?? '';
const result = await this.core.apis.UserApi.like(uid, parseInt(payload.times?.toString()) || 1);
//logDebug('点赞结果', result);
if (result.result !== 0) {
throw `点赞失败 ${result.errMsg}`;
throw new Error(`点赞失败 ${result.errMsg}`);
}
return null;
}

View File

@@ -40,7 +40,7 @@ export class OneBotGroupApi {
if (msg.senderUin && msg.senderUin !== '0') {
const member = await this.core.apis.GroupApi.getGroupMember(msg.peerUid, msg.senderUin);
if (member && member.cardName !== msg.sendMemberName) {
const newCardName = msg.sendMemberName || '';
const newCardName = msg.sendMemberName ?? '';
const event = new OB11GroupCardEvent(this.core, parseInt(msg.peerUid), parseInt(msg.senderUin), newCardName, member.cardName);
member.cardName = newCardName;
return event;
@@ -48,7 +48,7 @@ export class OneBotGroupApi {
}
for (const element of msg.elements) {
if (element.grayTipElement && element.grayTipElement.groupElement) {
if (element.grayTipElement?.groupElement) {
const groupElement = element.grayTipElement.groupElement;
if (groupElement.type == TipGroupElementType.memberIncrease) {
const MemberIncreaseEvent = await this.obContext.apis.GroupApi.parseGroupMemberIncreaseEvent(msg.peerUid, element.grayTipElement);
@@ -83,7 +83,7 @@ export class OneBotGroupApi {
url: pathToFileURL(element.fileElement.filePath).href,
name: element.fileElement.fileName,
size: parseInt(element.fileElement.fileSize),
busid: element.fileElement.fileBizId || 0,
busid: element.fileElement.fileBizId ?? 0,
},
);
}
@@ -109,8 +109,8 @@ export class OneBotGroupApi {
return new OB11GroupPokeEvent(
this.core,
parseInt(msg.peerUid),
parseInt((await this.core.apis.UserApi.getUinByUidV2(poke_uid[0].uid))!),
parseInt((await this.core.apis.UserApi.getUinByUidV2(poke_uid[1].uid))!),
+await this.core.apis.UserApi.getUinByUidV2(poke_uid[0].uid),
+await this.core.apis.UserApi.getUinByUidV2(poke_uid[1].uid),
pokedetail,
);
}
@@ -165,13 +165,13 @@ export class OneBotGroupApi {
async parseGroupBanEvent(GroupCode: string, grayTipElement: GrayTipElement) {
const groupElement = grayTipElement?.groupElement;
if (!groupElement?.shutUp) return undefined;
const memberUid = groupElement.shutUp!.member.uid;
const adminUid = groupElement.shutUp!.admin.uid;
const memberUid = groupElement.shutUp.member.uid;
const adminUid = groupElement.shutUp.admin.uid;
let memberUin: string;
let duration = parseInt(groupElement.shutUp!.duration);
let duration = parseInt(groupElement.shutUp.duration);
const subType: 'ban' | 'lift_ban' = duration > 0 ? 'ban' : 'lift_ban';
if (memberUid) {
memberUin = (await this.core.apis.GroupApi.getGroupMember(GroupCode, memberUid))?.uin || '';
memberUin = (await this.core.apis.GroupApi.getGroupMember(GroupCode, memberUid))?.uin ?? '';
} else {
memberUin = '0'; // 0表示全员禁言
if (duration > 0) {
@@ -225,7 +225,7 @@ export class OneBotGroupApi {
const memberUin = member?.uin;
const adminMember = await this.core.apis.GroupApi.getGroupMember(GroupCode, groupElement.adminUid);
if (memberUin) {
const operatorUin = adminMember?.uin || memberUin;
const operatorUin = adminMember?.uin ?? memberUin;
return new OB11GroupIncreaseEvent(
this.core,
parseInt(GroupCode),
@@ -240,7 +240,7 @@ export class OneBotGroupApi {
async parseGroupKickEvent(GroupCode: string, grayTipElement: GrayTipElement) {
const groupElement = grayTipElement?.groupElement;
if (!groupElement) return undefined;
const adminUin = (await this.core.apis.GroupApi.getGroupMember(GroupCode, groupElement.adminUid))?.uin || (await this.core.apis.UserApi.getUidByUinV2(groupElement.adminUid));
const adminUin = (await this.core.apis.GroupApi.getGroupMember(GroupCode, groupElement.adminUid))?.uin ?? (await this.core.apis.UserApi.getUidByUinV2(groupElement.adminUid));
if (adminUin) {
return new OB11GroupDecreaseEvent(
this.core,

View File

@@ -187,6 +187,7 @@ export class OneBotMsgApi {
url: url,
key: _.key,
emoji_id: _.emojiId,
emoji_package_id: _.emojiPackageId,
file_unique: _.key
},
};
@@ -699,16 +700,16 @@ export class OneBotMsgApi {
//跳过空消息
const resMsg: OB11Message = {
self_id: parseInt(this.core.selfInfo.uin),
user_id: parseInt(msg.senderUin!),
user_id: parseInt(msg.senderUin),
time: parseInt(msg.msgTime) || Date.now(),
message_id: msg.id!,
message_seq: msg.id!,
real_id: msg.id!,
message_type: msg.chatType == ChatType.KCHATTYPEGROUP ? 'group' : 'private',
sender: {
user_id: parseInt(msg.senderUin || '0'),
user_id: +(msg.senderUin ?? 0),
nickname: msg.sendNickName,
card: msg.sendMemberName || '',
card: msg.sendMemberName ?? '',
},
raw_message: '',
font: 14,

View File

@@ -25,7 +25,7 @@ export class OB11Entities {
user_id: parseInt(rawFriend.coreInfo.uin),
nickname: rawFriend.coreInfo.nick,
remark: rawFriend.coreInfo.remark ?? rawFriend.coreInfo.nick,
sex: this.sex(rawFriend.baseInfo.sex!),
sex: this.sex(rawFriend.baseInfo.sex),
level: 0,
}));
}
@@ -66,7 +66,7 @@ export class OB11Entities {
sex: OB11Entities.sex(member.sex!),
age: member.age ?? 0,
area: '',
level: member.memberRealLevel ?? '0',
level: member.memberRealLevel?.toString() ?? '0',
qq_level: member.qqLevel && calcQQLevel(member.qqLevel) || 0,
join_time: +member.joinTime,
last_sent_time: +member.lastSpeakTime,
@@ -76,7 +76,7 @@ export class OB11Entities {
is_robot: member.isRobot,
shut_up_timestamp: member.shutUpTime,
role: OB11Entities.groupMemberRole(member.role),
title: member.memberSpecialTitle || '',
title: member.memberSpecialTitle ?? '',
};
}

View File

@@ -11,11 +11,11 @@ export class OB11HeartbeatEvent extends OB11BaseMetaEvent {
status: HeartbeatStatus;
interval: number;
public constructor(core: NapCatCore, interval: number, isOnline: boolean | undefined, isGood: boolean) {
public constructor(core: NapCatCore, interval: number, isOnline: boolean, isGood: boolean) {
super(core);
this.interval = interval;
this.status = {
online: isOnline && true,
online: isOnline,
good: isGood,
};
}

View File

@@ -5,7 +5,7 @@ export type GroupDecreaseSubType = 'leave' | 'kick' | 'kick_me';
export class OB11GroupDecreaseEvent extends OB11GroupNoticeEvent {
notice_type = 'group_decrease';
sub_type: GroupDecreaseSubType = 'leave'; // TODO: 实现其他几种子类型的识别 ("leave" | "kick" | "kick_me")
sub_type: GroupDecreaseSubType = 'leave'; // work:实现其他几种子类型的识别 ("leave" | "kick" | "kick_me")
operator_id: number;
constructor(core: NapCatCore, groupId: number, userId: number, operatorId: number, subType: GroupDecreaseSubType = 'leave') {

View File

@@ -1,7 +1,7 @@
import { OB11BaseNoticeEvent } from './OB11BaseNoticeEvent';
import { NapCatCore } from '@/core';
//输入状态事件 初步完成 Mlikio wa Todo 需要做一些过滤
//work: 输入状态事件 初步完成 Mlikiowa 需要做一些过滤
export class OB11InputStatusEvent extends OB11BaseNoticeEvent {
notice_type = 'notify';
sub_type = 'input_status';

View File

@@ -25,7 +25,7 @@ export class OB11GroupPokeEvent extends OB11PokeEvent {
raw_info: any;
//raw_message nb等框架标准为string
constructor(core: NapCatCore, group_id: number, user_id: number = 0, target_id: number = 0, raw_message: any) {
constructor(core: NapCatCore, group_id: number, user_id: number, target_id: number, raw_message: any) {
super(core);
this.group_id = group_id;
this.target_id = target_id;

View File

@@ -6,7 +6,6 @@ import {
GroupNotifyMsgStatus,
GroupNotifyMsgType,
InstanceContext,
MsgSourceType,
NapCatCore,
NodeIKernelBuddyListener,
NodeIKernelGroupListener,
@@ -45,8 +44,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,8 +55,7 @@ export class NapCatOneBot11Adapter {
apis: StableOneBotApiWrapper;
networkManager: OB11NetworkManager;
actions: ActionMap;
nativeCore: Native | undefined;
private bootTime = Date.now() / 1000;
private readonly bootTime = Date.now() / 1000;
recallMsgCache = new LRUCache<string, RawMessage>(100);
constructor(core: NapCatCore, context: InstanceContext, pathWrapper: NapCatPathWrapper) {
@@ -75,41 +71,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;
@@ -211,8 +174,7 @@ export class NapCatOneBot11Adapter {
} else {
await this.networkManager.closeAdapterByPredicate(adapter => adapter instanceof OB11ActiveHttpAdapter);
}
} else {
if (now.http.enablePost) {
} else if (now.http.enablePost) {
const { added, removed } = this.findDifference<string>(prev.http.postUrls, now.http.postUrls);
await this.networkManager.closeAdapterByPredicate(
adapter => adapter instanceof OB11ActiveHttpAdapter && removed.includes(adapter.url),
@@ -223,7 +185,7 @@ export class NapCatOneBot11Adapter {
));
}
}
}
// check difference in passive websocket (Ws)
if (prev.ws.enable !== now.ws.enable) {
@@ -251,8 +213,7 @@ export class NapCatOneBot11Adapter {
adapter => adapter instanceof OB11ActiveWebSocketAdapter,
);
}
} else {
if (now.reverseWs.enable) {
} else if (now.reverseWs.enable) {
const { added, removed } = this.findDifference<string>(prev.reverseWs.urls, now.reverseWs.urls);
await this.networkManager.closeAdapterByPredicate(
adapter => adapter instanceof OB11ActiveWebSocketAdapter && removed.includes(adapter.url),
@@ -263,7 +224,7 @@ export class NapCatOneBot11Adapter {
));
}
}
}
}
private findDifference<T>(prev: T[], now: T[]): { added: T[], removed: T[] } {
@@ -305,10 +266,8 @@ export class NapCatOneBot11Adapter {
},
m.msgId,
);
// if (m.sourceType == MsgSourceType.K_DOWN_SOURCETYPE_AIOINNER) {
await this.emitMsg(m)
.catch(e => this.context.logger.logError.bind(this.context.logger)('处理消息失败', e));
// }
}
};
@@ -368,7 +327,7 @@ export class NapCatOneBot11Adapter {
const requesterUin = await this.core.apis.UserApi.getUinByUidV2(req.friendUid);
await this.networkManager.emitEvent(new OB11FriendRequestEvent(
this.core,
parseInt(requesterUin!),
+requesterUin,
req.extWords,
req.friendUid + '|' + req.reqTime,
));
@@ -432,7 +391,7 @@ export class NapCatOneBot11Adapter {
}
} else if (notify.type == GroupNotifyMsgType.MEMBER_LEAVE_NOTIFY_ADMIN || notify.type == GroupNotifyMsgType.KICK_MEMBER_NOTIFY_ADMIN) {
this.context.logger.logDebug('有成员退出通知', notify);
const member1Uin = (await this.core.apis.UserApi.getUinByUidV2(notify.user1.uid))!;
const member1Uin = (await this.core.apis.UserApi.getUinByUidV2(notify.user1.uid));
let operatorId = member1Uin;
let subType: GroupDecreaseSubType = 'leave';
if (notify.user2.uid) {
@@ -458,7 +417,7 @@ export class NapCatOneBot11Adapter {
].includes(notify.type) && notify.status == GroupNotifyMsgStatus.KUNHANDLE) {
this.context.logger.logDebug('有加群请求');
try {
let requestUin = (await this.core.apis.UserApi.getUinByUidV2(notify.user1.uid))!;
let requestUin = (await this.core.apis.UserApi.getUinByUidV2(notify.user1.uid));
if (isNaN(parseInt(requestUin))) {
requestUin = (await this.core.apis.UserApi.getUserDetailInfo(notify.user1.uid)).uin;
}
@@ -540,10 +499,9 @@ export class NapCatOneBot11Adapter {
this.context.logger.logDebug('转化为 OB11Message', ob11Msg);
if (debug) {
ob11Msg.raw = message;
} else {
if (ob11Msg.message.length === 0) {
} else if (ob11Msg.message.length === 0) {
return;
}
}
const isSelfMsg = ob11Msg.user_id.toString() == this.core.selfInfo.uin;
if (isSelfMsg && !reportSelfMessage) {
@@ -603,7 +561,7 @@ export class NapCatOneBot11Adapter {
for (const message of msgList) {
// log("message update", message.sendStatus, message.msgId, message.msgSeq)
const peer: Peer = { chatType: message.chatType, peerUid: message.peerUid, guildId: '' };
if (message.recallTime != '0' && !cache.get(message.msgId)) { //todo: 这个判断方法不太好,应该使用灰色消息元素来判断?
if (message.recallTime != '0' && !cache.get(message.msgId)) { //work:这个判断方法不太好,应该使用灰色消息元素来判断?
cache.put(message.msgId, true);
// 撤回消息上报
let oriMessageId = MessageUnique.getShortIdByMsgId(message.msgId);
@@ -613,7 +571,7 @@ export class NapCatOneBot11Adapter {
if (message.chatType == ChatType.KCHATTYPEC2C) {
const friendRecallEvent = new OB11FriendRecallNoticeEvent(
this.core,
parseInt(message!.senderUin),
+message.senderUin,
oriMessageId,
);
this.networkManager.emitEvent(friendRecallEvent)
@@ -624,13 +582,13 @@ export class NapCatOneBot11Adapter {
const operatorUid = element.grayTipElement?.revokeElement.operatorUid;
if (!operatorUid) return;
const operator = await this.core.apis.GroupApi.getGroupMember(message.peerUin, operatorUid);
operatorId = operator?.uin || message.senderUin;
operatorId = operator?.uin ?? message.senderUin;
}
const groupRecallEvent = new OB11GroupRecallNoticeEvent(
this.core,
parseInt(message.peerUin),
parseInt(message.senderUin),
parseInt(operatorId),
+message.peerUin,
+message.senderUin,
+operatorId,
oriMessageId
);
this.networkManager.emitEvent(groupRecallEvent)

View File

@@ -18,7 +18,7 @@ export class OB11ActiveWebSocketAdapter implements IOB11NetworkAdapter {
public url: string,
public reconnectIntervalInMillis: number,
public heartbeatIntervalInMillis: number,
private token: string,
private readonly token: string,
public core: NapCatCore,
public actions: ActionMap,
) {
@@ -37,7 +37,7 @@ export class OB11ActiveWebSocketAdapter implements IOB11NetworkAdapter {
}
this.heartbeatRef = setInterval(() => {
if (this.connection && this.connection.readyState === WebSocket.OPEN) {
this.connection.send(JSON.stringify(new OB11HeartbeatEvent(this.core, this.heartbeatIntervalInMillis, this.core.selfInfo.online, true)));
this.connection.send(JSON.stringify(new OB11HeartbeatEvent(this.core, this.heartbeatIntervalInMillis, this.core.selfInfo.online ?? true, true)));
}
}, this.heartbeatIntervalInMillis);
await this.tryConnect();
@@ -148,7 +148,6 @@ export class OB11ActiveWebSocketAdapter implements IOB11NetworkAdapter {
return;
}
const retdata = await action.websocketHandle(receiveData.params, echo ?? '');
const packet = Object.assign({}, retdata);
this.checkStateAndReply<any>(packet);
this.checkStateAndReply<any>({ ...retdata });
}
}

View File

@@ -145,7 +145,7 @@ export class OB11PassiveWebSocketAdapter implements IOB11NetworkAdapter {
this.wsClientsMutex.runExclusive(async () => {
this.wsClientWithEvent.forEach((wsClient) => {
if (wsClient.readyState === WebSocket.OPEN) {
wsClient.send(JSON.stringify(new OB11HeartbeatEvent(this.core, this.heartbeatInterval, this.core.selfInfo.online, true)));
wsClient.send(JSON.stringify(new OB11HeartbeatEvent(this.core, this.heartbeatInterval, this.core.selfInfo.online ?? true, true)));
}
});
});
@@ -189,8 +189,7 @@ export class OB11PassiveWebSocketAdapter implements IOB11NetworkAdapter {
return;
}
const retdata = await action.websocketHandle(receiveData.params, echo ?? '');
const packet = Object.assign({}, retdata);
this.checkStateAndReply<any>(packet, wsClient);
this.checkStateAndReply<any>({ ...retdata }, wsClient);
}
}

View File

@@ -34,33 +34,16 @@ program.option('-q, --qq [number]', 'QQ号').parse(process.argv);
const cmdOptions = program.opts();
// NapCat Shell App ES 入口文件
export async function NCoreInitShell() {
console.log('NapCat Shell App Loading...');
async function handleUncaughtExceptions(logger: LogWrapper) {
process.on('uncaughtException', (err) => {
console.log('[NapCat] [Error] Unhandled Exception:', err.message);
logger.logError('[NapCat] [Error] Unhandled Exception:', err.message);
});
process.on('unhandledRejection', (reason, promise) => {
console.log('[NapCat] [Error] unhandledRejection:', reason);
logger.logError('[NapCat] [Error] unhandledRejection:', reason);
});
const pathWrapper = new NapCatPathWrapper();
const logger = new LogWrapper(pathWrapper.logsPath);
const basicInfoWrapper = new QQBasicInfoWrapper({ logger });
const wrapper = loadQQWrapper(basicInfoWrapper.getFullQQVesion());
}
const o3Service = wrapper.NodeIO3MiscService.get();
o3Service.addO3MiscListener(new NodeIO3MiscListener());
logger.log(`[NapCat] [Core] NapCat.Core Version: ` + napCatVersion);
InitWebUi(logger, pathWrapper).then().catch(logger.logError.bind(logger));
// from constructor
const engine = wrapper.NodeIQQNTWrapperEngine.get();
//const util = wrapper.NodeQQNTWrapperUtil.get();
const loginService = wrapper.NodeIKernelLoginService.get();
const session = wrapper.NodeIQQNTWrapperSession.create();
// from get dataPath
const [dataPath, dataPathGlobal] = (() => {
function getDataPaths(wrapper: WrapperNodeApi): [string, string] {
if (os.platform() === 'darwin') {
const userPath = os.homedir();
const appDataPath = path.resolve(userPath, './Library/Application Support/QQ');
@@ -73,15 +56,24 @@ export async function NCoreInitShell() {
}
const dataPathGlobal = path.resolve(dataPath, './nt_qq/global');
return [dataPath, dataPathGlobal];
})();
}
function getPlatformType(): PlatformType {
const platformMapping: Partial<Record<NodeJS.Platform, PlatformType>> = {
win32: PlatformType.KWINDOWS,
darwin: PlatformType.KMAC,
linux: PlatformType.KLINUX,
};
const systemPlatform = platformMapping[os.platform()] ?? PlatformType.KWINDOWS;
if (!basicInfoWrapper.QQVersionAppid || !basicInfoWrapper.QQVersionQua) throw new Error('QQVersionAppid or QQVersionQua is not defined');
// from initConfig
return platformMapping[os.platform()] ?? PlatformType.KWINDOWS;
}
async function initializeEngine(
engine: any,
basicInfoWrapper: QQBasicInfoWrapper,
dataPathGlobal: string,
systemPlatform: PlatformType,
systemVersion: string
) {
engine.initWithDeskTopConfig(
{
base_path_prefix: '',
@@ -98,33 +90,38 @@ export async function NCoreInitShell() {
},
new NodeIGlobalAdapter(),
);
}
async function initializeLoginService(
loginService: NodeIKernelLoginService,
basicInfoWrapper: QQBasicInfoWrapper,
dataPathGlobal: string,
systemVersion: string,
hostname: string
) {
loginService.initConfig({
machineId: '',
appid: basicInfoWrapper.QQVersionAppid,
appid: basicInfoWrapper.QQVersionAppid ?? '',
platVer: systemVersion,
commonPath: dataPathGlobal,
clientVer: basicInfoWrapper.getFullQQVesion(),
hostName: hostname,
});
}
let quickLoginUin = cmdOptions.qq; // undefined | 'true' | string
const historyLoginList = (await loginService.getLoginList()).LocalLoginInfoList;
if (quickLoginUin == 'true') {
if (historyLoginList.length > 0) {
quickLoginUin = historyLoginList[0].uin;
logger.log(`-q 指令指定使用最近的 QQ ${quickLoginUin} 进行快速登录`);
} else {
quickLoginUin = '';
}
}
const dataTimestape = new Date().getTime().toString();
o3Service.reportAmgomWeather('login', 'a1', [dataTimestape, '0', '0']);
const selfInfo = await new Promise<SelfInfo>((resolve) => {
async function handleLogin(
loginService: NodeIKernelLoginService,
logger: LogWrapper,
pathWrapper: NapCatPathWrapper,
quickLoginUin: string | undefined,
historyLoginList: any[]
): Promise<SelfInfo> {
return new Promise<SelfInfo>((resolve) => {
const loginListener = new NodeIKernelLoginListener();
let isLogined = false;
// from constructor
loginListener.onUserLoggedIn = (userid: string) => {
logger.logError.bind(logger)(`当前账号(${userid})已登录,无法重复登录`);
logger.logError(`当前账号(${userid})已登录,无法重复登录`);
};
loginListener.onQRCodeLoginSucceed = async (loginResult) => {
@@ -132,13 +129,12 @@ export async function NCoreInitShell() {
resolve({
uid: loginResult.uid,
uin: loginResult.uin,
nick: '', // 获取不到
nick: '',
online: true,
});
};
loginListener.onQRCodeGetPicture = ({ pngBase64QrcodeData, qrcodeUrl }) => {
//设置WebuiQrcode
WebUiDataRuntime.setQQLoginQrcodeURL(qrcodeUrl);
const realBase64 = pngBase64QrcodeData.replace(/^data:image\/\w+;base64,/, '');
@@ -157,50 +153,46 @@ export async function NCoreInitShell() {
});
});
};
loginListener.onQRCodeSessionFailed = (errType: number, errCode: number, errMsg: string) => {
if (!isLogined) {
logger.logError.bind(logger)('[Core] [Login] Login Error,ErrCode: ', errCode, ' ErrMsg:', errMsg);
logger.logError('[Core] [Login] Login Error,ErrCode: ', errCode, ' ErrMsg:', errMsg);
if (errType == 1 && errCode == 3) {
// 二维码过期刷新
}
loginService.getQRCodePicture();
}
};
loginListener.onLoginFailed = (args) => {
logger.logError.bind(logger)('[Core] [Login] Login Error , ErrInfo: ', args);
logger.logError('[Core] [Login] Login Error , ErrInfo: ', args);
};
loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger));
const isConnect = loginService.connect();
if (!isConnect) {
logger.logError.bind(logger)('核心登录服务连接失败!');
logger.logError('核心登录服务连接失败!');
return;
}
logger.log('核心登录服务连接成功!');
// 实现WebUi快速登录
loginService.getLoginList().then((res) => {
// 遍历 res.LocalLoginInfoList[x].isQuickLogin是否可以 res.LocalLoginInfoList[x].uin 转为string 加入string[] 最后遍历完成调用WebUiDataRuntime.setQQQuickLoginList
WebUiDataRuntime.setQQQuickLoginList(res.LocalLoginInfoList.filter((item) => item.isQuickLogin).map((item) => item.uin.toString()));
});
if (basicInfoWrapper.QQVersionConfig?.curVersion) {
loginService.getLoginMiscData('hotUpdateSign').then((res) => {
if (res.result === 0) {
loginService.setLoginMiscData('hotUpdateSign', res.value);
}
});
session.getNodeMiscService().writeVersionToRegistry(basicInfoWrapper.QQVersionConfig?.curVersion);
}
WebUiDataRuntime.setQuickLoginCall(async (uin: string) => {
return await new Promise((resolve) => {
if (uin) {
logger.log.bind(logger)('正在快速登录 ', uin);
logger.log('正在快速登录 ', uin);
loginService.quickLoginWithUin(uin).then(res => {
if (res.loginErrorInfo.errMsg) {
resolve({ result: false, message: res.loginErrorInfo.errMsg });
}
resolve({ result: true, message: '' });
}).catch((e) => {
logger.logError.bind(logger)(e);
logger.logError(e);
resolve({ result: false, message: '快速登录发生错误' });
});
} else {
@@ -216,14 +208,14 @@ export async function NCoreInitShell() {
loginService.quickLoginWithUin(quickLoginUin)
.then(result => {
if (result.loginErrorInfo.errMsg) {
logger.logError.bind(logger)('快速登录错误:', result.loginErrorInfo.errMsg);
logger.logError('快速登录错误:', result.loginErrorInfo.errMsg);
if (!isLogined) loginService.getQRCodePicture();
}
})
.catch();
}, 1000);
} else {
logger.logError.bind(logger)('快速登录失败,未找到该 QQ 历史登录记录,将使用二维码登录方式');
logger.logError('快速登录失败,未找到该 QQ 历史登录记录,将使用二维码登录方式');
if (!isLogined) loginService.getQRCodePicture();
}
} else {
@@ -237,37 +229,20 @@ export async function NCoreInitShell() {
loginService.getQRCodePicture();
}
});
// BEFORE LOGGING IN
const amgomDataPiece = 'eb1fd6ac257461580dc7438eb099f23aae04ca679f4d88f53072dc56e3bb1129';
o3Service.setAmgomDataPiece(basicInfoWrapper.QQVersionAppid, new Uint8Array(Buffer.from(amgomDataPiece, 'hex')));
// AFTER LOGGING IN
//99b15bdb4c984fc69d5aa1feb9aa16xx --> 99b15bdb-4c98-4fc6-9d5a-a1feb9aa16xx
//把guid从左向右转换为guid格式 loginService.getMachineGuid()
}
let guid = loginService.getMachineGuid();
guid = guid.slice(0, 8) + '-' + guid.slice(8, 12) + '-' + guid.slice(12, 16) + '-' + guid.slice(16, 20) + '-' + guid.slice(20);
//console.log('guid:', guid);
//NodeIO3MiscService/reportAmgomWeather login a6 [ '1726748166943', '184', '329' ]
o3Service.reportAmgomWeather('login', 'a6', [dataTimestape, '184', '329']);
// if(session.getUnitedConfigService()){
// session.getUnitedConfigService().fetchUnitedCommendConfig([]);
// }
// from initSession
await new Promise<void>(async (resolve, reject) => {
const sessionConfig = await genSessionConfig(
guid,
basicInfoWrapper.QQVersionAppid!,
basicInfoWrapper.getFullQQVesion(),
selfInfo.uin,
selfInfo.uid,
dataPath,
);
async function initializeSession(
session: NodeIQQNTWrapperSession,
sessionConfig: any,
logger: LogWrapper
) {
return new Promise<void>((resolve, reject) => {
const sessionListener = new NodeIKernelSessionListener();
sessionListener.onSessionInitComplete = (r: unknown) => {
if (r === 0) {
resolve();
} else {
reject(r);
reject(new Error('登录异常' + r?.toString()));
}
};
session.init(
@@ -278,21 +253,74 @@ export async function NCoreInitShell() {
);
try {
session.startNT(0);
} catch (_) { /* Empty */
} catch (_) {
try {
session.startNT();
} catch (e) {
reject('init failed ' + e);
} catch (e: unknown) {
reject(new Error('init failed ' + (e as Error).message));
}
}
});
// Initialization end!
}
export async function NCoreInitShell() {
console.log('NapCat Shell App Loading...');
const pathWrapper = new NapCatPathWrapper();
const logger = new LogWrapper(pathWrapper.logsPath);
handleUncaughtExceptions(logger);
const basicInfoWrapper = new QQBasicInfoWrapper({ logger });
const wrapper = loadQQWrapper(basicInfoWrapper.getFullQQVesion());
const o3Service = wrapper.NodeIO3MiscService.get();
o3Service.addO3MiscListener(new NodeIO3MiscListener());
logger.log(`[NapCat] [Core] NapCat.Core Version: ` + napCatVersion);
InitWebUi(logger, pathWrapper).then().catch(logger.logError.bind(logger));
const engine = wrapper.NodeIQQNTWrapperEngine.get();
const loginService = wrapper.NodeIKernelLoginService.get();
const session = wrapper.NodeIQQNTWrapperSession.create();
const [dataPath, dataPathGlobal] = getDataPaths(wrapper);
const systemPlatform = getPlatformType();
if (!basicInfoWrapper.QQVersionAppid || !basicInfoWrapper.QQVersionQua) throw new Error('QQVersionAppid or QQVersionQua is not defined');
await initializeEngine(engine, basicInfoWrapper, dataPathGlobal, systemPlatform, systemVersion);
await initializeLoginService(loginService, basicInfoWrapper, dataPathGlobal, systemVersion, hostname);
const quickLoginUin = cmdOptions.qq;
const historyLoginList = (await loginService.getLoginList()).LocalLoginInfoList;
const dataTimestape = new Date().getTime().toString();
o3Service.reportAmgomWeather('login', 'a1', [dataTimestape, '0', '0']);
const selfInfo = await handleLogin(loginService, logger, pathWrapper, quickLoginUin, historyLoginList);
const amgomDataPiece = 'eb1fd6ac257461580dc7438eb099f23aae04ca679f4d88f53072dc56e3bb1129';
o3Service.setAmgomDataPiece(basicInfoWrapper.QQVersionAppid, new Uint8Array(Buffer.from(amgomDataPiece, 'hex')));
let guid = loginService.getMachineGuid();
guid = guid.slice(0, 8) + '-' + guid.slice(8, 12) + '-' + guid.slice(12, 16) + '-' + guid.slice(16, 20) + '-' + guid.slice(20);
o3Service.reportAmgomWeather('login', 'a6', [dataTimestape, '184', '329']);
const sessionConfig = await genSessionConfig(
guid,
basicInfoWrapper.QQVersionAppid,
basicInfoWrapper.getFullQQVesion(),
selfInfo.uin,
selfInfo.uid,
dataPath,
);
await initializeSession(session, sessionConfig, logger);
const accountDataPath = path.resolve(dataPath, './NapCat/data');
fs.mkdirSync(dataPath, { recursive: true });
logger.logDebug('本账号数据/缓存目录:', accountDataPath);
new NapCatShell(
await new NapCatShell(
wrapper,
session,
logger,
@@ -300,9 +328,10 @@ export async function NCoreInitShell() {
selfInfo,
basicInfoWrapper,
pathWrapper,
);
).InitNapCat();
}
export class NapCatShell {
readonly core: NapCatCore;
readonly context: InstanceContext;
@@ -327,8 +356,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));
}
}

Some files were not shown because too many files have changed in this diff Show More