Compare commits

...

77 Commits

Author SHA1 Message Date
手瓜一十雪
9f82007116 revert: eslint 2024-08-29 20:40:33 +08:00
手瓜一十雪
f79198a472 release: 2.2.24 2024-08-29 20:35:30 +08:00
手瓜一十雪
ce3d35d7ec fix: modelId 2024-08-29 20:34:24 +08:00
手瓜一十雪
f4d40f0466 release: 2.2.23 2024-08-29 20:14:40 +08:00
手瓜一十雪
a2fa085d5f fix: getfile 2024-08-29 20:14:20 +08:00
手瓜一十雪
a598266a6e fix: #320 2024-08-29 19:32:37 +08:00
手瓜一十雪
f5fe33cee7 fix: GroupEssenceMsg 2024-08-29 19:25:16 +08:00
手瓜一十雪
200c7226ef fix: getGroupEssenceMsgAll 2024-08-29 19:21:03 +08:00
Wesley F. Young
53475a6a0e fix: filter emoji un-like by operation 2024-08-29 18:15:52 +08:00
Wesley F. Young
b4ec1ad6c0 Merge remote-tracking branch 'origin/main' 2024-08-29 17:10:27 +08:00
Wesley F. Young
ef511a729d fix: solve export conflict 2024-08-29 17:10:14 +08:00
Wesley F. Young
275c4ce226 feat: GetGroupIgnoredNotifies 2024-08-29 17:08:36 +08:00
手瓜一十雪
45f9c029c8 Merge pull request #319 from NapNeko/dependabot/npm_and_yarn/eslint-9.9.1
build(deps-dev): bump eslint from 8.57.0 to 9.9.1
2024-08-29 17:01:34 +08:00
dependabot[bot]
db5e4ad5d9 build(deps-dev): bump eslint from 8.57.0 to 9.9.1
Bumps [eslint](https://github.com/eslint/eslint) from 8.57.0 to 9.9.1.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.57.0...v9.9.1)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-29 08:54:04 +00:00
Wesley F. Young
f05d0a9727 chore: run eslint 2024-08-29 08:37:10 +08:00
Wesley F. Young
04593e9d9a feat: code style 2024-08-29 00:28:53 +08:00
手瓜一十雪
b1ecf13f8e release: 2.2.22 2024-08-29 00:10:52 +08:00
手瓜一十雪
e91e054f20 refactor: GetGroupEssence 2024-08-29 00:10:29 +08:00
手瓜一十雪
130ff7517e refactor: parseEssence 2024-08-29 00:02:24 +08:00
手瓜一十雪
c7042d9684 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-29 00:01:48 +08:00
手瓜一十雪
5752e45dd1 refactor: GetFile 2024-08-28 23:45:33 +08:00
Alen
1a034ecb53 Merge pull request #317 from cnxysoft/upmain
fix: 转发消息报错
2024-08-28 23:30:04 +08:00
手瓜一十雪
025da8fb76 refactor: getFile 2024-08-28 23:27:29 +08:00
Alen
2027da1db5 chore: 优化代码 2024-08-28 23:24:44 +08:00
Alen
7732f28ca8 fix: reply消息转换 2024-08-28 22:51:54 +08:00
手瓜一十雪
7f9da8cc2d feat: support folder_id 2024-08-28 21:19:17 +08:00
手瓜一十雪
c6342b80a7 release: 2.2.21 2024-08-28 20:51:59 +08:00
Wesley F. Young
f99c82de4b fix: unexpected return in buddy req parsing 2024-08-28 19:32:07 +08:00
Wesley F. Young
56fa57ea02 refactor: rename createMsg -> createUniqueMsgId to prevent ambiguity 2024-08-28 19:03:19 +08:00
手瓜一十雪
cc85985d08 feat: 设置noify已读 2024-08-28 18:18:40 +08:00
手瓜一十雪
bd1751903e chore: clearGroupNotifiesUnreadCount 2024-08-28 18:01:32 +08:00
手瓜一十雪
03a298a70f release: 2.2.20 2024-08-28 17:48:51 +08:00
手瓜一十雪
2722ca2b0e feat: getGroupInfoEx 2024-08-28 17:05:00 +08:00
手瓜一十雪
179c4b800e Merge pull request #314 from NapNeko/dependabot/npm_and_yarn/typescript-eslint/parser-8.3.0
build(deps-dev): bump @typescript-eslint/parser from 7.18.0 to 8.3.0
2024-08-28 17:04:37 +08:00
dependabot[bot]
6bdf14223d build(deps-dev): bump @typescript-eslint/parser from 7.18.0 to 8.3.0
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 7.18.0 to 8.3.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.3.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-28 08:58:48 +00:00
dependabot[bot]
1b8252aa4f Merge pull request #312 from NapNeko/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-8.3.0 2024-08-28 08:27:34 +00:00
dependabot[bot]
8219889154 build(deps-dev): bump @typescript-eslint/eslint-plugin
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 7.18.0 to 8.3.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.3.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-28 08:26:00 +00:00
手瓜一十雪
df4ac5dcce release: 2.2.19 2024-08-28 16:19:30 +08:00
Seijo Cecilia
738eaf9de9 refactor: directory structure of core 2024-08-28 15:29:13 +08:00
Seijo Cecilia
c483ccbbbc fix: ob11 config file location 2024-08-28 15:27:38 +08:00
Seijo Cecilia
0d65f846ae refactor: remove helper directory in onebot 2024-08-28 15:09:17 +08:00
Seijo Cecilia
f47e75c423 refactor: make methods in event.ts instance methods 2024-08-28 15:02:44 +08:00
Wesley F. Young
c008e58fb8 Merge remote-tracking branch 'origin/main' 2024-08-28 13:05:15 +08:00
Wesley F. Young
26e0f17bc5 feat: emoji like event from other to other 2024-08-28 13:05:02 +08:00
Wesley F. Young
6543f28bdb feat: proto files of messages 2024-08-28 13:04:27 +08:00
手瓜一十雪
a86851b338 feat: 提高效率 2024-08-28 13:04:05 +08:00
Wesley F. Young
3a03e455c6 refactor: extract emoji like parsing logic 2024-08-28 10:50:39 +08:00
手瓜一十雪
3d39fd1580 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-27 22:51:14 +08:00
手瓜一十雪
601b0add26 release: 2.2.18 2024-08-27 22:50:53 +08:00
Version
4f974cc913 chore:version change 2024-08-27 14:50:42 +00:00
手瓜一十雪
f691320453 fix: handleQuickOperation 2024-08-27 22:47:30 +08:00
手瓜一十雪
be39fc3a21 chore: 移除 2024-08-27 22:17:54 +08:00
手瓜一十雪
d2fafaf33a chore: rename nekodoge 2024-08-27 20:53:07 +08:00
手瓜一十雪
27ae331352 chore: new adapter 2024-08-27 20:37:23 +08:00
Seijo Cecilia
3f2dcfbacc (partially) fix: 'throw' of exception caught locally 2024-08-27 15:05:51 +08:00
Alen
8565aee8b6 Merge pull request #310 from cnxysoft/upmain
style: 规范代码
2024-08-27 14:11:33 +08:00
Alen
f983add599 style: 规范代码 2024-08-27 14:09:28 +08:00
Seijo Cecilia
030192afeb Merge remote-tracking branch 'origin/main' 2024-08-27 14:00:05 +08:00
Seijo Cecilia
c8b6a158f1 chore: optimize imports 2024-08-27 13:59:47 +08:00
Alen
e71f7849a7 Merge pull request #309 from cnxysoft/upmain
fix: 群员获取异常
2024-08-27 13:54:33 +08:00
Alen
b64d1ff4ff revert: 入群事件
因getGroupMembers切换到V2,恢复原本逻辑
2024-08-27 13:52:23 +08:00
Alen
5a0028be26 chore: 去除无用代码 2024-08-27 13:41:30 +08:00
Alen
926d7deb43 Merge branch 'main' into upmain 2024-08-27 13:39:38 +08:00
Seijo Cecilia
6384b50bae chore: run eslint 2024-08-27 13:35:25 +08:00
手瓜一十雪
9feb0f4b53 release: v2.2.16 2024-08-27 12:18:20 +08:00
手瓜一十雪
43ec1b7cfd feat: 更高效率的识别 2024-08-27 12:16:57 +08:00
Wesley F. Young
05b7a59f8d refactor: move version info into version.ts 2024-08-27 10:30:38 +08:00
Wesley F. Young
17e680f7af refactor: move all utility files to /common 2024-08-27 10:18:31 +08:00
Wesley F. Young
035d256d4e refactor: rename event-legacy -> event (currently in use) 2024-08-27 10:15:46 +08:00
Wesley F. Young
8939adf886 refactor: rename event -> event-v2 (not ready for use) 2024-08-27 10:14:55 +08:00
Wesley F. Young
027ffbffa6 chore: remove redundant eslint-disable 2024-08-27 10:05:23 +08:00
Alen
3cca06712b fix: 群成员拉取失败(实验性) 2024-08-27 02:43:27 +08:00
手瓜一十雪
2b9359dbf4 fix: Type
NodeIKernelSessionListener/onNTSessionCreate->NodeIKernelGroupListener/onGroupListInited
2024-08-26 21:33:33 +08:00
手瓜一十雪
c0f5d3bd2e release:2.2.15 2024-08-26 19:56:16 +08:00
手瓜一十雪
2a2d5382e1 Merge pull request #308 from LingLambda/main
规范了_send_group_notice接口参数命名,适当的规范了部分变量命名
2024-08-26 19:30:55 +08:00
ling
2e4986024c 规范了_send_group_notice接口参数命名,适当的规范了部分变量命名 2024-08-26 18:53:33 +08:00
手瓜一十雪
8a9c605dae release: 2.2.15 2024-08-26 18:02:21 +08:00
120 changed files with 2230 additions and 1080 deletions

View File

@@ -4,7 +4,7 @@ module.exports = {
'es2021': true,
'node': true
},
'ignorePatterns': ['src/proto/'],
'ignorePatterns': ['src/core/proto/'],
'extends': [
'eslint:recommended',
'plugin:@typescript-eslint/recommended'

View File

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

View File

@@ -2,7 +2,7 @@
"name": "napcat",
"private": true,
"type": "module",
"version": "2.2.13",
"version": "2.2.24",
"scripts": {
"build:framework": "vite build --mode framework",
"build:shell": "vite build --mode shell",
@@ -27,8 +27,8 @@
"@types/node": "^22.0.1",
"@types/qrcode-terminal": "^0.12.2",
"@types/ws": "^8.5.12",
"@typescript-eslint/eslint-plugin": "^7.4.0",
"@typescript-eslint/parser": "^7.4.0",
"@typescript-eslint/eslint-plugin": "^8.3.0",
"@typescript-eslint/parser": "^8.3.0",
"eslint": "^8.57.0",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "^2.29.1",

2
script/KillQQ.bat Normal file
View File

@@ -0,0 +1,2 @@
@echo off
taskkill /f /im QQ.exe

View File

@@ -1,4 +1,4 @@
import type { NodeIQQNTWrapperSession, WrapperNodeApi } from '@/core/wrapper/wrapper';
import type { NodeIQQNTWrapperSession, WrapperNodeApi } from '@/core/wrapper';
import EventEmitter from 'node:events';
export type ListenerClassBase = Record<string, string>;
@@ -10,7 +10,7 @@ export interface ListenerIBase {
[key: string]: any;
}
export class NTEventChannel extends EventEmitter {
export class NTEventWrapperV2 extends EventEmitter {
private wrapperApi: WrapperNodeApi;
private wrapperSession: NodeIQQNTWrapperSession;
private listenerRefStorage = new Map<string, ListenerIBase>();

View File

@@ -1,4 +1,4 @@
import { NodeIQQNTWrapperSession } from '@/core/wrapper/wrapper';
import { NodeIQQNTWrapperSession } from '@/core/wrapper';
import { randomUUID } from 'crypto';
import { ListenerNamingMapping, ServiceNamingMapping } from '@/core';
@@ -11,7 +11,7 @@ interface InternalMapKey {
export type ListenerClassBase = Record<string, string>;
export class LegacyNTEventWrapper {
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}

View File

@@ -1,7 +1,7 @@
import path from 'node:path';
import fs from 'fs';
import os from 'node:os';
import { QQLevel } from '@/core';
import { Peer, QQLevel } from '@/core';
export async function solveProblem<T extends (...arg: any[]) => any>(func: T, ...args: Parameters<T>): Promise<ReturnType<T> | undefined> {
return new Promise<ReturnType<T> | undefined>((resolve) => {
@@ -23,29 +23,48 @@ export async function solveAsyncProblem<T extends (...args: any[]) => Promise<an
});
});
}
//下面这个类是用于将uid+msgid合并的类
export class UUIDConverter {
static encode(highStr: string, lowStr: string): string {
const high = BigInt(highStr);
const low = BigInt(lowStr);
const highHex = high.toString(16).padStart(16, '0');
const lowHex = low.toString(16).padStart(16, '0');
const combinedHex = highHex + lowHex;
return `${combinedHex.substring(0, 8)}-${combinedHex.substring(8, 12)}-${combinedHex.substring(
12,
16,
)}-${combinedHex.substring(16, 20)}-${combinedHex.substring(20)}`;
export class FileNapCatOneBotUUID {
static encodeModelId(peer: Peer, modelId: string): string {
return `NapCatOneBot-ModeldFile-${peer.chatType}-${peer.peerUid}-${modelId}`;
}
static decode(uuid: string): { high: string; low: string } {
const hex = uuid.replace(/-/g, '');
const high = BigInt('0x' + hex.substring(0, 16));
const low = BigInt('0x' + hex.substring(16));
return { high: high.toString(), low: low.toString() };
static decodeModelId(uuid: string): undefined | {
peer: Peer,
modelId: string
} {
if (!uuid.startsWith('NapCatOneBot-ModeldFile-')) return undefined;
const data = uuid.split('-');
if (data.length !== 5) return undefined;
const [, , chatType, peerUid, modelId] = data;
return {
peer: {
chatType: chatType as any,
peerUid: peerUid
},
modelId,
};
}
static encode(peer: Peer, msgId: string, elementId: string): string {
return `NapCatOneBot-MsgFile-${peer.chatType}-${peer.peerUid}-${msgId}-${elementId}`;
}
static decode(uuid: string): undefined | {
peer: Peer,
msgId: string,
elementId: string
} {
if (!uuid.startsWith('NapCatOneBot-MsgFile-')) return undefined;
const data = uuid.split('-');
if (data.length !== 6) return undefined;
const [, , chatType, peerUid, msgId, elementId] = data;
return {
peer: {
chatType: chatType as any,
peerUid: peerUid
},
msgId,
elementId,
};
}
}
export function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}

View File

@@ -1,5 +1,5 @@
import log4js, { Configuration } from 'log4js';
import { truncateString } from '@/common/utils/helper';
import { truncateString } from '@/common/helper';
import path from 'node:path';
import chalk from 'chalk';
import { AtType, ChatType, ElementType, MessageElement, RawMessage, SelfInfo } from '@/core';

View File

@@ -91,7 +91,7 @@ class MessageUniqueWrapper {
return ret.map((t) => t?.MsgId).filter((t) => t !== undefined);
}
createMsg(peer: Peer, msgId: string) {
createUniqueMsgId(peer: Peer, msgId: string) {
const key = `${msgId}|${peer.chatType}|${peer.peerUid}`;
const hash = crypto.createHash('md5').update(key).digest();
//设置第一个bit为0 保证shortId为正数

View File

@@ -2,8 +2,6 @@ import path, { dirname } from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs';
export const napcat_version = '2.2.13';
export class NapCatPathWrapper {
binaryPath: string;
logsPath: string;

View File

@@ -1,6 +1,6 @@
import path from 'node:path';
import fs from 'node:fs';
import { systemPlatform } from '@/common/utils/system';
import { systemPlatform } from '@/common/system';
import { getDefaultQQVersionConfigInfo, getQQVersionConfigPath } from './helper';
import AppidTable from '@/core/external/appid.json';
import { LogWrapper } from './log';
@@ -60,20 +60,17 @@ export class QQBasicInfoWrapper {
getAppidV2(): { appid: string; qua: string } {
const appidTbale = AppidTable as unknown as QQAppidTableType;
try {
const fullVersion = this.getFullQQVesion();
if (!fullVersion) throw new Error('QQ版本获取失败');
const fullVersion = this.getFullQQVesion();
if (fullVersion) {
const data = appidTbale[fullVersion];
if (data) {
return data;
}
} catch (e) {
this.context.logger.log(`[QQ版本兼容性检测] 获取Appid异常 请检测NapCat/QQNT是否正常`);
}
// 以下是兜底措施
this.context.logger.log(
`[QQ版本兼容性检测] ${this.getFullQQVesion()} 版本兼容性不佳,可能会导致一些功能无法正常使用`,
);
// else
this.context.logger.log(`[QQ版本兼容性检测] 获取Appid异常 请检测NapCat/QQNT是否正常`);
this.context.logger.log(`[QQ版本兼容性检测] ${fullVersion} 版本兼容性不佳,可能会导致一些功能无法正常使用`,);
return { appid: systemPlatform === 'linux' ? '537240795' : '537240709', qua: this.getQUAInternal() };
}
}

1
src/common/version.ts Normal file
View File

@@ -0,0 +1 @@
export const napCatVersion = '2.2.24';

View File

@@ -21,14 +21,13 @@ import { InstanceContext, NapCatCore } from '@/core';
import * as fileType from 'file-type';
import imageSize from 'image-size';
import { ISizeCalculationResult } from 'image-size/dist/types/interface';
import { NodeIKernelSearchService } from '../services/NodeIKernelSearchService';
import { RkeyManager } from '../helper/rkey';
import { calculateFileMD5, isGIF } from '@/common/utils/file';
import { calculateFileMD5, isGIF } from '@/common/file';
import pathLib from 'node:path';
import { defaultVideoThumbB64, getVideoInfo } from '@/common/utils/video';
import { defaultVideoThumbB64, getVideoInfo } from '@/common/video';
import ffmpeg from 'fluent-ffmpeg';
import fsnormal from 'node:fs';
import { encodeSilk } from '@/common/utils/audio';
import { encodeSilk } from '@/common/audio';
export class NTQQFileApi {
@@ -303,7 +302,21 @@ export class NTQQFileApi {
async downloadMediaByUuid() {
//napCatCore.session.getRichMediaService().downloadFileForFileUuid();
}
async downloadFileForModelId(peer: Peer, modelId: string, unknown: string, timeout = 1000 * 60 * 2) {
const [, fileTransNotifyInfo] = await this.core.eventWrapper.callNormalEventV2(
'NodeIKernelRichMediaService/downloadFileForModelId',
'NodeIKernelMsgListener/onRichMediaDownloadComplete',
[peer, [modelId], unknown],
() => true,
(arg) => {
console.log(arg);
return arg?.commonFileInfo?.fileModelId === modelId
},
1,
timeout,
);
return fileTransNotifyInfo.filePath;
}
async downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, timeout = 1000 * 60 * 2, force: boolean = false) {
//logDebug('receive downloadMedia task', msgId, chatType, peerUid, elementId, thumbPath, sourcePath, timeout, force);
// 用于下载收到的消息中的图片等
@@ -346,17 +359,15 @@ export class NTQQFileApi {
if (msg.msgList.length === 0) {
return fileTransNotifyInfo.filePath;
}
//获取原始消息
const FileElements = msg?.msgList[0]?.elements?.find(e => e.elementId === elementId);
if (!FileElements) {
//失败则就乱来 Todo
return fileTransNotifyInfo.filePath;
const mixElement = msg.msgList.find((msg) => msg.msgId === msgId)?.elements.find((e) => e.elementId === elementId);
const mixElementInner = mixElement?.videoElement ?? mixElement?.fileElement ?? mixElement?.pttElement ?? mixElement?.picElement;
let realPath = mixElementInner?.filePath;
if (!realPath) {
let picThumbPath: Map<number, string> = (mixElementInner as any)?.picThumbPath;
let picThumbPathList = Array.from(picThumbPath.values());
if (picThumbPathList.length > 0) realPath = picThumbPathList[0];
}
//从原始消息获取文件路径
return FileElements?.fileElement?.filePath ??
FileElements?.pttElement?.filePath ??
FileElements?.videoElement?.filePath ??
FileElements?.picElement?.sourcePath;
return realPath;
}
async getImageSize(filePath: string): Promise<ISizeCalculationResult | undefined> {

View File

@@ -1,6 +1,6 @@
import { FriendV2 } from '@/core/entities';
import { BuddyListReqType, InstanceContext, NapCatCore, NodeIKernelProfileService } from '@/core';
import { LimitedHashTable } from '@/common/utils/message-unique';
import { BuddyListReqType, InstanceContext, NapCatCore } from '@/core';
import { LimitedHashTable } from '@/common/message-unique';
export class NTQQFriendApi {
context: InstanceContext;

View File

@@ -2,6 +2,7 @@ import {
ChatType,
GeneralCallResult,
Group,
GroupInfoSource,
GroupMember,
GroupMemberRole,
GroupRequestOperateTypes,
@@ -9,9 +10,9 @@ import {
KickMemberV2Req,
MemberExtSourceType,
NapCatCore,
NodeIKernelGroupService,
} from '@/core';
import { isNumeric, runAllWithTimeout, sleep } from '@/common/utils/helper';
import { isNumeric, runAllWithTimeout } from '@/common/helper';
import { LimitedHashTable } from '@/common/message-unique';
export class NTQQGroupApi {
context: InstanceContext;
@@ -19,25 +20,32 @@ export class NTQQGroupApi {
groupCache: Map<string, Group> = new Map<string, Group>();
groupMemberCache: Map<string, Map<string, GroupMember>> = new Map<string, Map<string, GroupMember>>();
groups: Group[] = [];
essenceLRU = new LimitedHashTable<number, string>(1000);
constructor(context: InstanceContext, core: NapCatCore) {
this.context = context;
this.core = core;
sleep(1000).then(() => {
this.initCache().then().catch(context.logger.logError);
});
this.initCache().then().catch(context.logger.logError);
}
async initCache() {
this.groups = await this.getGroups();
for (const group of this.groups) {
this.groupCache.set(group.groupCode, group);
const data = await this.getGroupMembers(group.groupCode, 3000);
this.groupMemberCache.set(group.groupCode, data);
}
this.context.logger.logDebug(`加载${this.groups.length}个群组缓存完成`);
}
async fetchGroupEssenceList(groupCode: string) {
const pskey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!;
return this.context.session.getGroupService().fetchGroupEssenceList({
groupCode: groupCode,
pageStart: 0,
pageLimit: 300
}, pskey);
}
async clearGroupNotifiesUnreadCount(unk: boolean) {
return this.context.session.getGroupService().clearGroupNotifiesUnreadCount(unk);
}
async setGroupAvatar(gc: string, filePath: string) {
return this.context.session.getGroupService().setHeader(gc, filePath);
}
@@ -175,7 +183,7 @@ export class NTQQGroupApi {
let members = this.groupMemberCache.get(groupCodeStr);
if (!members) {
try {
members = await this.getGroupMembers(groupCodeStr);
members = await this.getGroupMembersV2(groupCodeStr);
// 更新群成员列表
this.groupMemberCache.set(groupCodeStr, members);
} catch (e) {
@@ -196,7 +204,7 @@ export class NTQQGroupApi {
let member = getMember();
if (!member) {
members = await this.getGroupMembers(groupCodeStr);
members = await this.getGroupMembersV2(groupCodeStr);
member = getMember();
}
return member;
@@ -275,7 +283,15 @@ export class NTQQGroupApi {
//应该是直接返回不需要Listener的 未经测试 需测试再发布
return this.context.session.getGroupService().quitGroupV2(param);
}
async removeGroupEssenceBySeq(GroupCode: string, msgRandom: string, msgSeq: string) {
const param = {
groupCode: GroupCode,
msgRandom: parseInt(msgRandom),
msgSeq: parseInt(msgSeq),
};
// GetMsgByShoretID(ShoretID); -> MsgService.getMsgs(Peer,MsgId,1,false); -> 组出参数
return this.context.session.getGroupService().removeGroupEssence(param);
}
async removeGroupEssence(GroupCode: string, msgId: string) {
// 代码没测过
// 需要 ob11msgid->msgId + (peer) -> msgSeq + msgRandom
@@ -293,12 +309,12 @@ export class NTQQGroupApi {
return this.context.session.getGroupService().removeGroupEssence(param);
}
async getSingleScreenNotifies(num: number) {
async getSingleScreenNotifies(doubt: boolean, num: number) {
const [, , , notifies] = await this.core.eventWrapper.callNormalEventV2(
'NodeIKernelGroupService/getSingleScreenNotifies',
'NodeIKernelGroupListener/onGroupSingleScreenNotifies',
[
false,
doubt,
'',
num,
],
@@ -307,7 +323,6 @@ export class NTQQGroupApi {
}
async getGroupMemberV2(GroupCode: string, uid: string, forced = false) {
type EventType = NodeIKernelGroupService['getMemberInfo'];
const Listener = this.core.eventWrapper.registerListen(
'NodeIKernelGroupListener/onMemberInfoChange',
1,
@@ -330,6 +345,38 @@ export class NTQQGroupApi {
return member;
}
async getGroupMembersV2(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
const groupService = this.context.session.getGroupService();
const sceneId = groupService.createMemberListScene(groupQQ, 'groupMemberList_MainWindow');
const listener = this.core.eventWrapper.registerListen(
'NodeIKernelGroupListener/onMemberListChange',
1,
500,
(params) => params.sceneId === sceneId,
);
try {
const [membersFromFunc, membersFromListener] = await Promise.allSettled([
groupService.getNextMemberList(sceneId, undefined, num),
listener,
]);
if (membersFromFunc.status === 'fulfilled' && membersFromListener.status === 'fulfilled') {
return new Map([
...membersFromFunc.value.result.infos,
...membersFromListener.value[0].infos
]);
}
if (membersFromFunc.status === 'fulfilled') {
return membersFromFunc.value.result.infos;
}
if (membersFromListener.status === 'fulfilled') {
return membersFromListener.value[0].infos;
}
throw new Error('获取群成员列表失败');
} finally {
groupService.destroyMemberListScene(sceneId);
}
}
async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
const groupService = this.context.session.getGroupService();
const sceneId = groupService.createMemberListScene(groupQQ, 'groupMemberList_MainWindow');

View File

@@ -3,6 +3,10 @@ import { InstanceContext, NapCatCore } from '@/core';
import { GeneralCallResult } from '@/core/services/common';
export class NTQQMsgApi {
// nt_qq//global//nt_data//Emoji//emoji-resource//sysface_res/apng/ 下可以看到所有QQ表情预览
// nt_qq\global\nt_data\Emoji\emoji-resource\face_config.json 里面有所有表情的id, 自带表情id是QSid, 标准emoji表情id是QCid
// 其实以官方文档为准是最好的https://bot.q.qq.com/wiki/develop/api-v2/openapi/emoji/model.html#EmojiType
context: InstanceContext;
core: NapCatCore;
@@ -10,7 +14,12 @@ export class NTQQMsgApi {
this.context = context;
this.core = core;
}
async getAioFirstViewLatestMsgs(peer: Peer, MsgCount: number) {
return this.context.session.getMsgService().getAioFirstViewLatestMsgs(peer, MsgCount);
}
async getLatestDbMsgs(peer: Peer, MsgCount: number) {
return this.context.session.getMsgService().getLatestDbMsgs(peer, MsgCount);
}
async FetchLongMsg(peer: Peer, msgId: string) {
return this.context.session.getMsgService().fetchLongMsg(peer, msgId);
}
@@ -191,7 +200,7 @@ export class NTQQMsgApi {
msgElements,
new Map(),
],
() => true,
(ret) => ret.result === 0,
msgRecords => {
for (const msgRecord of msgRecords) {
if (msgRecord.guildId === msgId && msgRecord.sendStatus === SendStatusType.KSEND_STATUS_SUCCESS) {

View File

@@ -1,4 +1,4 @@
import { RequestUtil } from '@/common/utils/request';
import { RequestUtil } from '@/common/request';
import { MiniAppLuaJsonType } from '@/core';
import { InstanceContext, NapCatCore } from '..';

View File

@@ -1,4 +1,4 @@
import { GeneralCallResult, InstanceContext, NapCatCore } from '@/core';
import { InstanceContext, NapCatCore } from '@/core';
export class NTQQSystemApi {
context: InstanceContext;

View File

@@ -1,8 +1,8 @@
import type { ModifyProfileParams, User, UserDetailInfoByUinV2 } from '@/core/entities';
import { RequestUtil } from '@/common/utils/request';
import type { ModifyProfileParams, User } from '@/core/entities';
import { RequestUtil } from '@/common/request';
import { ProfileBizType, UserDetailSource } from '@/core/services';
import { InstanceContext, NapCatCore } from '..';
import { solveAsyncProblem } from '@/common/utils/helper';
import { solveAsyncProblem } from '@/common/helper';
export class NTQQUserApi {
context: InstanceContext;

View File

@@ -1,4 +1,4 @@
import { RequestUtil } from '@/common/utils/request';
import { RequestUtil } from '@/common/request';
import {
GroupEssenceMsgRet,
InstanceContext,
@@ -27,27 +27,40 @@ export class NTQQWebApi {
msg_random: msgRandom,
target_group_code: targetGroupCode,
}).toString()
}`;
}`;
try {
return RequestUtil.HttpGetText(url, 'GET', '', { 'Cookie': this.cookieToString(cookieObject) });
} catch (e) {
return undefined;
}
}
async getGroupEssenceMsgAll(GroupCode: string) {
let ret: GroupEssenceMsgRet[] = [];
for (let i = 0; i < 4; i++) {
let data = await this.getGroupEssenceMsg(GroupCode, i, 50);
if (!data) break;
if (data.data.is_end) {
ret.push(data);
break;
}
ret.push(data);
async getGroupEssenceMsg(GroupCode: string, page_start: string) {
}
return ret;
}
async getGroupEssenceMsg(GroupCode: string, page_start: number = 0, page_limit: number = 50) {
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
const url = `https://qun.qq.com/cgi-bin/group_digest/digest_list?${new URLSearchParams({
bkn: this.getBknFromCookie(cookieObject),
page_start: page_start.toString(),
page_limit: page_limit.toString(),
group_code: GroupCode,
page_start,
page_limit: '20',
}).toString()
}`;
}`;
let ret;
try {
ret = await RequestUtil.HttpGetJson<GroupEssenceMsgRet>
(url, 'GET', '', { 'Cookie': this.cookieToString(cookieObject) });
(url, 'GET', '', { 'Cookie': this.cookieToString(cookieObject) });
} catch {
return undefined;
}
@@ -63,14 +76,14 @@ export class NTQQWebApi {
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
const retList: Promise<WebApiGroupMemberRet>[] = [];
const fastRet = await RequestUtil.HttpGetJson<WebApiGroupMemberRet>
(`https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?${new URLSearchParams({
st: '0',
end: '40',
sort: '1',
gc: GroupCode,
bkn: this.getBknFromCookie(cookieObject),
}).toString()
}`, 'POST', '', { 'Cookie': this.cookieToString(cookieObject) });
(`https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?${new URLSearchParams({
st: '0',
end: '40',
sort: '1',
gc: GroupCode,
bkn: this.getBknFromCookie(cookieObject),
}).toString()
}`, 'POST', '', { 'Cookie': this.cookieToString(cookieObject) });
if (!fastRet?.count || fastRet?.errcode !== 0 || !fastRet?.mems) {
return [];
} else {
@@ -83,14 +96,14 @@ export class NTQQWebApi {
//遍历批量请求
for (let i = 2; i <= PageNum; i++) {
const ret = RequestUtil.HttpGetJson<WebApiGroupMemberRet>
(`https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?${new URLSearchParams({
st: ((i - 1) * 40).toString(),
end: (i * 40).toString(),
sort: '1',
gc: GroupCode,
bkn: this.getBknFromCookie(cookieObject),
}).toString()
}`, 'POST', '', { 'Cookie': this.cookieToString(cookieObject) });
(`https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?${new URLSearchParams({
st: ((i - 1) * 40).toString(),
end: (i * 40).toString(),
sort: '1',
gc: GroupCode,
bkn: this.getBknFromCookie(cookieObject),
}).toString()
}`, 'POST', '', { 'Cookie': this.cookieToString(cookieObject) });
retList.push(ret);
}
//批量等待
@@ -123,15 +136,15 @@ export class NTQQWebApi {
let ret: any = undefined;
try {
ret = await RequestUtil.HttpGetJson<any>
(`https://web.qun.qq.com/cgi-bin/announce/add_qun_notice${new URLSearchParams({
bkn: this.getBknFromCookie(cookieObject),
qid: GroupCode,
text: Content,
pinned: '0',
type: '1',
settings: '{"is_show_edit_card":1,"tip_window_type":1,"confirm_required":1}',
}).toString()
}`, 'GET', '', { 'Cookie': this.cookieToString(cookieObject) });
(`https://web.qun.qq.com/cgi-bin/announce/add_qun_notice${new URLSearchParams({
bkn: this.getBknFromCookie(cookieObject),
qid: GroupCode,
text: Content,
pinned: '0',
type: '1',
settings: '{"is_show_edit_card":1,"tip_window_type":1,"confirm_required":1}',
}).toString()
}`, 'GET', '', { 'Cookie': this.cookieToString(cookieObject) });
return ret;
} catch (e) {
return undefined;
@@ -162,7 +175,7 @@ export class NTQQWebApi {
gc: Internal_groupCode,
type: Internal_type.toString(),
}).toString()
}`;
}`;
let resJson;
try {
const res = await RequestUtil.HttpGetText(url, 'GET', '', { 'Cookie': this.cookieToString(cookieObject) });
@@ -184,11 +197,8 @@ export class NTQQWebApi {
const HonorInfo: any = { group_id: groupCode };
if (getType === WebHonorType.TALKATIVE || getType === WebHonorType.ALL) {
try {
const RetInternal = await getDataInternal(groupCode, 1);
if (!RetInternal) {
throw new Error('获取龙王信息失败');
}
const RetInternal = await getDataInternal(groupCode, 1);
if (RetInternal) {
HonorInfo.current_talkative = {
user_id: RetInternal[0]?.uin,
avatar: RetInternal[0]?.avatar,
@@ -206,16 +216,13 @@ export class NTQQWebApi {
nickname: talkative_ele?.name,
});
}
} catch (e) {
this.context.logger.logDebug(e);
} else {
this.context.logger.logError('获取龙王信息失败');
}
}
if (getType === WebHonorType.PERFORMER || getType === WebHonorType.ALL) {
try {
const RetInternal = await getDataInternal(groupCode, 2);
if (!RetInternal) {
throw new Error('获取群聊之火失败');
}
const RetInternal = await getDataInternal(groupCode, 2);
if (RetInternal) {
HonorInfo.performer_list = [];
for (const performer_ele of RetInternal) {
HonorInfo.performer_list.push({
@@ -225,16 +232,13 @@ export class NTQQWebApi {
description: performer_ele?.desc,
});
}
} catch (e) {
this.context.logger.logDebug(e);
} else {
this.context.logger.logError('获取群聊之火失败');
}
}
if (getType === WebHonorType.PERFORMER || getType === WebHonorType.ALL) {
try {
const RetInternal = await getDataInternal(groupCode, 3);
if (!RetInternal) {
throw new Error('获取群聊炽焰失败');
}
const RetInternal = await getDataInternal(groupCode, 3);
if (RetInternal) {
HonorInfo.legend_list = [];
for (const legend_ele of RetInternal) {
HonorInfo.legend_list.push({
@@ -244,16 +248,13 @@ export class NTQQWebApi {
desc: legend_ele?.description,
});
}
} catch (e) {
this.context.logger.logDebug('获取群聊炽焰失败', e);
} else {
this.context.logger.logError('获取群聊炽焰失败');
}
}
if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) {
try {
const RetInternal = await getDataInternal(groupCode, 6);
if (!RetInternal) {
throw new Error('获取快乐源泉失败');
}
const RetInternal = await getDataInternal(groupCode, 6);
if (RetInternal) {
HonorInfo.emotion_list = [];
for (const emotion_ele of RetInternal) {
HonorInfo.emotion_list.push({
@@ -263,11 +264,11 @@ export class NTQQWebApi {
desc: emotion_ele.description,
});
}
} catch (e) {
this.context.logger.logDebug('获取快乐源泉失败', e);
} else {
this.context.logger.logError('获取快乐源泉失败');
}
}
//冒尖小春笋好像已经被tx扬了
// 冒尖小春笋好像已经被tx扬了 R.I.P.
if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) {
HonorInfo.strong_newbie_list = [];
}

View File

@@ -1,206 +0,0 @@
import { NodeQQNTWrapperUtil, StableNTApiWrapper, WrapperNodeApi } from '@/core/wrapper';
import path from 'node:path';
import fs from 'node:fs';
import { InstanceContext } from './wrapper';
import { proxiedListenerOf } from '@/common/utils/proxy-handler';
import { NodeIKernelGroupListener, NodeIKernelMsgListener, NodeIKernelProfileListener } from './listeners';
import { DataSource, GroupMember, SelfInfo } from './entities';
import { LegacyNTEventWrapper } from '@/common/framework/event-legacy';
import { NTQQFileApi, NTQQFriendApi, NTQQGroupApi, NTQQMsgApi, NTQQSystemApi, NTQQUserApi, NTQQWebApi } from './apis';
import os from 'node:os';
import { NTQQCollectionApi } from './apis/collection';
import { NapCatConfigLoader } from './helper/config';
import { LogLevel } from '@/common/utils/log';
export enum NapCatCoreWorkingEnv {
Unknown = 0,
Shell = 1,
Framework = 2,
}
export function loadQQWrapper(QQVersion: string): WrapperNodeApi {
let wrapperNodePath = path.resolve(path.dirname(process.execPath), './resources/app/wrapper.node');
if (!fs.existsSync(wrapperNodePath)) {
wrapperNodePath = path.join(path.dirname(process.execPath), `resources/app/versions/${QQVersion}/wrapper.node`);
}
const nativemodule: any = { exports: {} };
process.dlopen(nativemodule, wrapperNodePath);
return nativemodule.exports;
}
export class NapCatCore {
readonly context: InstanceContext;
readonly apis: StableNTApiWrapper;
readonly eventWrapper: LegacyNTEventWrapper;
// readonly eventChannel: NTEventChannel;
NapCatDataPath: string;
NapCatTempPath: string;
// runtime info, not readonly
selfInfo: SelfInfo;
util: NodeQQNTWrapperUtil;
configLoader: NapCatConfigLoader;
// 通过构造器递过去的 runtime info 应该尽量少
constructor(context: InstanceContext, selfInfo: SelfInfo) {
this.selfInfo = selfInfo;
this.context = context;
this.util = this.context.wrapper.NodeQQNTWrapperUtil;
this.eventWrapper = new LegacyNTEventWrapper(context.session);
this.apis = {
FileApi: new NTQQFileApi(this.context, this),
SystemApi: new NTQQSystemApi(this.context, this),
CollectionApi: new NTQQCollectionApi(this.context, this),
WebApi: new NTQQWebApi(this.context, this),
FriendApi: new NTQQFriendApi(this.context, this),
MsgApi: new NTQQMsgApi(this.context, this),
UserApi: new NTQQUserApi(this.context, this),
GroupApi: new NTQQGroupApi(this.context, this),
};
this.configLoader = new NapCatConfigLoader(this, this.context.pathWrapper.configPath);
this.NapCatDataPath = path.join(this.dataPath, 'NapCat');
fs.mkdirSync(this.NapCatDataPath, { recursive: true });
this.NapCatTempPath = path.join(this.NapCatDataPath, 'temp');
// 创建临时目录
if (!fs.existsSync(this.NapCatTempPath)) {
fs.mkdirSync(this.NapCatTempPath, { recursive: true });
}
this.initNapCatCoreListeners().then().catch(this.context.logger.logError);
this.context.logger.setFileLogEnabled(
this.configLoader.configData.fileLog,
);
this.context.logger.setConsoleLogEnabled(
this.configLoader.configData.consoleLog,
);
this.context.logger.setFileAndConsoleLogLevel(
this.configLoader.configData.fileLogLevel as LogLevel,
this.configLoader.configData.consoleLogLevel as LogLevel,
);
}
get dataPath(): string {
let result = this.context.wrapper.NodeQQNTWrapperUtil.getNTUserDataInfoConfig();
if (!result) {
result = path.resolve(os.homedir(), './.config/QQ');
fs.mkdirSync(result, { recursive: true });
}
return result;
}
// Renamed from 'InitDataListener'
async initNapCatCoreListeners() {
const msgListener = new NodeIKernelMsgListener();
msgListener.onRecvMsg = (msgs) => {
msgs.forEach(msg => this.context.logger.logMessage(msg, this.selfInfo));
};
msgListener.onAddSendMsg = (msg) => {
this.context.logger.logMessage(msg, this.selfInfo);
};
//await sleep(2500);
this.context.session.getMsgService().addKernelMsgListener(
proxiedListenerOf(msgListener, this.context.logger) as any,
);
const profileListener = new NodeIKernelProfileListener();
profileListener.onProfileDetailInfoChanged = (profile) => {
if (profile.uid === this.selfInfo.uid) {
Object.assign(this.selfInfo, profile);
}
};
profileListener.onSelfStatusChanged = (/* Info: SelfStatusInfo */) => {
// if (Info.status == 20) {
// log("账号状态变更为离线")
// }
};
this.context.session.getProfileService().addKernelProfileListener(
proxiedListenerOf(profileListener, this.context.logger),
);
// 群相关
const groupListener = new NodeIKernelGroupListener();
groupListener.onGroupListUpdate = (updateType, groupList) => {
// console.log("onGroupListUpdate", updateType, groupList)
groupList.map(g => {
const existGroup = this.apis.GroupApi.groupCache.get(g.groupCode);
//群成员数量变化 应该刷新缓存
if (existGroup && g.memberCount === existGroup.memberCount) {
Object.assign(existGroup, g);
} else {
this.apis.GroupApi.groupCache.set(g.groupCode, g);
// 获取群成员
}
const sceneId = this.context.session.getGroupService().createMemberListScene(g.groupCode, 'groupMemberList_MainWindow');
this.context.session.getGroupService().getNextMemberList(sceneId!, undefined, 3000).then( /* r => {
// console.log(`get group ${g.groupCode} members`, r);
// r.result.infos.forEach(member => {
// });
// groupMembers.set(g.groupCode, r.result.infos);
} */);
});
};
groupListener.onMemberListChange = (arg) => {
// todo: 应该加一个内部自己维护的成员变动callback用于判断成员变化通知
const groupCode = arg.sceneId.split('_')[0];
if (this.apis.GroupApi.groupMemberCache.has(groupCode)) {
const existMembers = this.apis.GroupApi.groupMemberCache.get(groupCode)!;
arg.infos.forEach((member, uid) => {
//console.log('onMemberListChange', member);
const existMember = existMembers.get(uid);
if (existMember) {
Object.assign(existMember, member);
} else {
existMembers!.set(uid, member);
}
//移除成员
if (member.isDelete) {
existMembers.delete(uid);
}
});
} else {
this.apis.GroupApi.groupMemberCache.set(groupCode, arg.infos);
}
// console.log('onMemberListChange', groupCode, arg);
};
groupListener.onMemberInfoChange = (groupCode, dataSource, members) => {
//console.log('onMemberInfoChange', groupCode, changeType, members);
if (dataSource === DataSource.LOCAL && members.get(this.selfInfo.uid)?.isDelete) {
// 自身退群或者被踢退群 5s用于Api操作 之后不再出现
setTimeout(() => {
this.apis.GroupApi.groupCache.delete(groupCode);
}, 5000);
}
const existMembers = this.apis.GroupApi.groupMemberCache.get(groupCode);
if (existMembers) {
members.forEach((member, uid) => {
const existMember = existMembers.get(uid);
if (existMember) {
// 检查管理变动
member.isChangeRole = this.checkAdminEvent(groupCode, member, existMember);
// 更新成员信息
Object.assign(existMember, member);
} else {
existMembers.set(uid, member);
}
//移除成员
if (member.isDelete) {
existMembers.delete(uid);
}
});
} else {
this.apis.GroupApi.groupMemberCache.set(groupCode, members);
}
};
this.context.session.getGroupService().addKernelGroupListener(
proxiedListenerOf(groupListener, this.context.logger) as any,
);
}
checkAdminEvent(groupCode: string, memberNew: GroupMember, memberOld: GroupMember | undefined): boolean {
if (memberNew.role !== memberOld?.role) {
this.context.logger.logDebug(`${groupCode} ${memberNew.nick} 角色变更为 ${memberNew.role === 3 ? '管理员' : '群员'}`);
return true;
}
return false;
}
}

View File

@@ -593,6 +593,7 @@ export const IMAGE_HTTP_HOST_NT = 'https://multimedia.nt.qq.com.cn';
export interface PicElement {
md5HexStr?: string;
filePath?: string;
fileSize: number | string;//number
picWidth: number;
picHeight: number;

View File

@@ -1,11 +1,9 @@
import { ConfigBase } from '@/common/utils/config-base';
import { ConfigBase } from '@/common/config-base';
import napCatDefaultConfig from '@/core/external/napcat.json';
import { NapCatCore } from '@/core';
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
export type NapCatConfig = typeof napCatDefaultConfig;
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
export class NapCatConfigLoader extends ConfigBase<NapCatConfig> {
constructor(core: NapCatCore, configPath: string) {
super('napcat', core, configPath);

View File

@@ -1,5 +1,5 @@
import { LogWrapper } from '@/common/utils/log';
import { RequestUtil } from '@/common/utils/request';
import { LogWrapper } from '@/common/log';
import { RequestUtil } from '@/common/request';
interface ServerRkeyData {
group_rkey: string;

View File

@@ -1,5 +1,299 @@
export * from './core';
import {
NTQQFileApi,
NTQQFriendApi,
NTQQGroupApi,
NTQQMsgApi,
NTQQSystemApi,
NTQQUserApi,
NTQQWebApi,
} from '@/core/apis';
import { NTQQCollectionApi } from '@/core/apis/collection';
import {
NodeIQQNTWrapperSession,
NodeQQNTWrapperUtil,
PlatformType,
VendorType,
WrapperNodeApi,
WrapperSessionInitConfig,
} from '@/core/wrapper';
import { LogLevel, LogWrapper } from '@/common/log';
import { NodeIKernelLoginService } from '@/core/services';
import { QQBasicInfoWrapper } from '@/common/qq-basic-info';
import { NapCatPathWrapper } from '@/common/path';
import path from 'node:path';
import fs from 'node:fs';
import { getMachineId, hostname, systemName, systemVersion } from '@/common/system';
import { NTEventWrapper } from '@/common/event';
import { DataSource, GroupMember, SelfInfo } from '@/core/entities';
import { NapCatConfigLoader } from '@/core/helper/config';
import os from 'node:os';
import { NodeIKernelGroupListener, NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/core/listeners';
import { proxiedListenerOf } from '@/common/proxy-handler';
export * from './wrapper';
export * from './entities';
export * from './services';
export * from './listeners';
export enum NapCatCoreWorkingEnv {
Unknown = 0,
Shell = 1,
Framework = 2,
}
export function loadQQWrapper(QQVersion: string): WrapperNodeApi {
let wrapperNodePath = path.resolve(path.dirname(process.execPath), './resources/app/wrapper.node');
if (!fs.existsSync(wrapperNodePath)) {
wrapperNodePath = path.join(path.dirname(process.execPath), `resources/app/versions/${QQVersion}/wrapper.node`);
}
const nativemodule: any = { exports: {} };
process.dlopen(nativemodule, wrapperNodePath);
return nativemodule.exports;
}
export class NapCatCore {
readonly context: InstanceContext;
readonly apis: StableNTApiWrapper;
readonly eventWrapper: NTEventWrapper;
// readonly eventChannel: NTEventChannel;
NapCatDataPath: string;
NapCatTempPath: string;
// runtime info, not readonly
selfInfo: SelfInfo;
util: NodeQQNTWrapperUtil;
configLoader: NapCatConfigLoader;
// 通过构造器递过去的 runtime info 应该尽量少
constructor(context: InstanceContext, selfInfo: SelfInfo) {
this.selfInfo = selfInfo;
this.context = context;
this.util = this.context.wrapper.NodeQQNTWrapperUtil;
this.eventWrapper = new NTEventWrapper(context.session);
this.apis = {
FileApi: new NTQQFileApi(this.context, this),
SystemApi: new NTQQSystemApi(this.context, this),
CollectionApi: new NTQQCollectionApi(this.context, this),
WebApi: new NTQQWebApi(this.context, this),
FriendApi: new NTQQFriendApi(this.context, this),
MsgApi: new NTQQMsgApi(this.context, this),
UserApi: new NTQQUserApi(this.context, this),
GroupApi: new NTQQGroupApi(this.context, this),
};
this.configLoader = new NapCatConfigLoader(this, this.context.pathWrapper.configPath);
this.NapCatDataPath = path.join(this.dataPath, 'NapCat');
fs.mkdirSync(this.NapCatDataPath, { recursive: true });
this.NapCatTempPath = path.join(this.NapCatDataPath, 'temp');
// 创建临时目录
if (!fs.existsSync(this.NapCatTempPath)) {
fs.mkdirSync(this.NapCatTempPath, { recursive: true });
}
this.initNapCatCoreListeners().then().catch(this.context.logger.logError);
this.context.logger.setFileLogEnabled(
this.configLoader.configData.fileLog,
);
this.context.logger.setConsoleLogEnabled(
this.configLoader.configData.consoleLog,
);
this.context.logger.setFileAndConsoleLogLevel(
this.configLoader.configData.fileLogLevel as LogLevel,
this.configLoader.configData.consoleLogLevel as LogLevel,
);
}
get dataPath(): string {
let result = this.context.wrapper.NodeQQNTWrapperUtil.getNTUserDataInfoConfig();
if (!result) {
result = path.resolve(os.homedir(), './.config/QQ');
fs.mkdirSync(result, { recursive: true });
}
return result;
}
// Renamed from 'InitDataListener'
async initNapCatCoreListeners() {
const msgListener = new NodeIKernelMsgListener();
msgListener.onRecvMsg = (msgs) => {
msgs.forEach(msg => this.context.logger.logMessage(msg, this.selfInfo));
};
msgListener.onAddSendMsg = (msg) => {
this.context.logger.logMessage(msg, this.selfInfo);
};
//await sleep(2500);
this.context.session.getMsgService().addKernelMsgListener(
proxiedListenerOf(msgListener, this.context.logger) as any,
);
const profileListener = new NodeIKernelProfileListener();
profileListener.onProfileDetailInfoChanged = (profile) => {
if (profile.uid === this.selfInfo.uid) {
Object.assign(this.selfInfo, profile);
}
};
profileListener.onSelfStatusChanged = (/* Info: SelfStatusInfo */) => {
// if (Info.status == 20) {
// log("账号状态变更为离线")
// }
};
this.context.session.getProfileService().addKernelProfileListener(
proxiedListenerOf(profileListener, this.context.logger),
);
// 群相关
const groupListener = new NodeIKernelGroupListener();
groupListener.onGroupListUpdate = (updateType, groupList) => {
// console.log("onGroupListUpdate", updateType, groupList)
groupList.map(g => {
const existGroup = this.apis.GroupApi.groupCache.get(g.groupCode);
//群成员数量变化 应该刷新缓存
if (existGroup && g.memberCount === existGroup.memberCount) {
Object.assign(existGroup, g);
} else {
this.apis.GroupApi.groupCache.set(g.groupCode, g);
// 获取群成员
}
const sceneId = this.context.session.getGroupService().createMemberListScene(g.groupCode, 'groupMemberList_MainWindow');
this.context.session.getGroupService().getNextMemberList(sceneId, undefined, 3000).then( /* r => {
// console.log(`get group ${g.groupCode} members`, r);
// r.result.infos.forEach(member => {
// });
// groupMembers.set(g.groupCode, r.result.infos);
} */);
this.context.session.getGroupService().destroyMemberListScene(sceneId);
});
};
groupListener.onMemberListChange = (arg) => {
// todo: 应该加一个内部自己维护的成员变动callback用于判断成员变化通知
const groupCode = arg.sceneId.split('_')[0];
if (this.apis.GroupApi.groupMemberCache.has(groupCode)) {
const existMembers = this.apis.GroupApi.groupMemberCache.get(groupCode)!;
arg.infos.forEach((member, uid) => {
//console.log('onMemberListChange', member);
const existMember = existMembers.get(uid);
if (existMember) {
Object.assign(existMember, member);
} else {
existMembers!.set(uid, member);
}
//移除成员
if (member.isDelete) {
existMembers.delete(uid);
}
});
} else {
this.apis.GroupApi.groupMemberCache.set(groupCode, arg.infos);
}
};
groupListener.onMemberInfoChange = (groupCode, dataSource, members) => {
if (dataSource === DataSource.LOCAL && members.get(this.selfInfo.uid)?.isDelete) {
// 自身退群或者被踢退群 5s用于Api操作 之后不再出现
setTimeout(() => {
this.apis.GroupApi.groupCache.delete(groupCode);
}, 5000);
}
const existMembers = this.apis.GroupApi.groupMemberCache.get(groupCode);
if (existMembers) {
members.forEach((member, uid) => {
const existMember = existMembers.get(uid);
if (existMember) {
// 检查管理变动
member.isChangeRole = this.checkAdminEvent(groupCode, member, existMember);
// 更新成员信息
Object.assign(existMember, member);
} else {
existMembers.set(uid, member);
}
//移除成员
if (member.isDelete) {
existMembers.delete(uid);
}
});
} else {
this.apis.GroupApi.groupMemberCache.set(groupCode, members);
}
};
this.context.session.getGroupService().addKernelGroupListener(
proxiedListenerOf(groupListener, this.context.logger) as any,
);
}
checkAdminEvent(groupCode: string, memberNew: GroupMember, memberOld: GroupMember | undefined): boolean {
if (memberNew.role !== memberOld?.role) {
this.context.logger.logDebug(`${groupCode} ${memberNew.nick} 角色变更为 ${memberNew.role === 3 ? '管理员' : '群员'}`);
return true;
}
return false;
}
}
export async function genSessionConfig(QQVersionAppid: string, QQVersion: string, selfUin: string, selfUid: string, account_path: string): Promise<WrapperSessionInitConfig> {
const downloadPath = path.join(account_path, 'NapCat', 'temp');
fs.mkdirSync(downloadPath, { recursive: true });
const guid: string = await getMachineId();//26702 支持JS获取guid值 在LoginService中获取 TODO mlikiow a
return {
selfUin,
selfUid,
desktopPathConfig: {
account_path, // 可以通过NodeQQNTWrapperUtil().getNTUserDataInfoConfig()获取
},
clientVer: QQVersion, // 9.9.8-22355
a2: '',
d2: '',
d2Key: '',
machineId: '',
platform: PlatformType.KWINDOWS, // 3是Windows?
platVer: systemVersion, // 系统版本号, 应该可以固定
appid: QQVersionAppid,
rdeliveryConfig: {
appKey: '',
systemId: 0,
appId: '',
logicEnvironment: '',
platform: PlatformType.KWINDOWS,
language: '',
sdkVersion: '',
userId: '',
appVersion: '',
osVersion: '',
bundleId: '',
serverUrl: '',
fixedAfterHitKeys: [''],
},
defaultFileDownloadPath: downloadPath,
deviceInfo: {
guid,
buildVer: QQVersion,
localId: 2052,
devName: hostname,
devType: systemName,
vendorName: '',
osVer: systemVersion,
vendorOsName: systemName,
setMute: false,
vendorType: VendorType.KNOSETONIOS,
},
deviceConfig: '{"appearance":{"isSplitViewMode":true},"msg":{}}',
};
}
export interface InstanceContext {
readonly workingEnv: NapCatCoreWorkingEnv;
readonly wrapper: WrapperNodeApi;
readonly session: NodeIQQNTWrapperSession;
readonly logger: LogWrapper;
readonly loginService: NodeIKernelLoginService;
readonly basicInfoWrapper: QQBasicInfoWrapper;
readonly pathWrapper: NapCatPathWrapper;
}
export interface StableNTApiWrapper {
FileApi: NTQQFileApi,
SystemApi: NTQQSystemApi,
CollectionApi: NTQQCollectionApi,
WebApi: NTQQWebApi,
FriendApi: NTQQFriendApi,
MsgApi: NTQQMsgApi,
UserApi: NTQQUserApi,
GroupApi: NTQQGroupApi
}

View File

@@ -1,6 +1,7 @@
import { DataSource, Group, GroupListUpdateType, GroupMember, GroupNotify } from '@/core/entities';
export class NodeIKernelGroupListener {
onGroupListInited(listEmpty: boolean): void { }
// 发现于Win 9.9.9 23159
onGroupMemberLevelInfoChange(...args: unknown[]): void {

View File

@@ -1,4 +1,5 @@
import { ChatType, RawMessage } from '@/core/entities';
import { CommonFileInfo } from '@/core';
export interface OnRichMediaDownloadCompleteParams {
fileModelId: string,
@@ -15,7 +16,7 @@ export interface OnRichMediaDownloadCompleteParams {
totalSize: string,
trasferStatus: number,
step: number,
commonFileInfo: unknown | null,
commonFileInfo?: CommonFileInfo,
fileSrvErrCode: string,
clientMsg: string,
businessId: number,

View File

@@ -19,10 +19,10 @@ import type {
NodeIKernelMsgListener,
NodeIKernelProfileListener,
NodeIKernelRobotListener,
NodeIKernelSearchListener_Polyfill,
NodeIKernelSessionListener,
NodeIKernelStorageCleanListener,
NodeIKernelTicketListener,
NodeIKernelSearchListener_Polyfill,
} from '.';
export type ListenerNamingMapping = {

View File

@@ -0,0 +1,31 @@
syntax = 'proto3';
package SysMessage;
message EmojiLikeToOthersWrapper1 {
EmojiLikeToOthersWrapper2 wrapper = 1;
}
message EmojiLikeToOthersWrapper2 {
EmojiLikeToOthersWrapper3 body = 1;
}
message EmojiLikeToOthersWrapper3 {
EmojiLikeToOthersMsgSpec msgSpec = 2;
EmojiLikeToOthersAttributes attributes = 3;
}
message EmojiLikeToOthersMsgSpec {
uint32 msgSeq = 1;
}
message EmojiLikeToOthersAttributes {
enum Operation {
FALLBACK = 0;
LIKE = 1;
UNLIKE = 2;
}
string emojiId = 1;
string senderUid = 4;
Operation operation = 5;
}

View File

@@ -0,0 +1,341 @@
// @generated by protobuf-ts 2.9.4
// @generated from protobuf file "EmojiLikeToOthers.proto" (package "SysMessage", syntax proto3)
// tslint:disable
import type { BinaryWriteOptions } from "@protobuf-ts/runtime";
import type { IBinaryWriter } from "@protobuf-ts/runtime";
import { WireType } from "@protobuf-ts/runtime";
import type { BinaryReadOptions } from "@protobuf-ts/runtime";
import type { IBinaryReader } from "@protobuf-ts/runtime";
import { UnknownFieldHandler } from "@protobuf-ts/runtime";
import type { PartialMessage } from "@protobuf-ts/runtime";
import { reflectionMergePartial } from "@protobuf-ts/runtime";
import { MessageType } from "@protobuf-ts/runtime";
/**
* @generated from protobuf message SysMessage.EmojiLikeToOthersWrapper1
*/
export interface EmojiLikeToOthersWrapper1 {
/**
* @generated from protobuf field: SysMessage.EmojiLikeToOthersWrapper2 wrapper = 1;
*/
wrapper?: EmojiLikeToOthersWrapper2;
}
/**
* @generated from protobuf message SysMessage.EmojiLikeToOthersWrapper2
*/
export interface EmojiLikeToOthersWrapper2 {
/**
* @generated from protobuf field: SysMessage.EmojiLikeToOthersWrapper3 body = 1;
*/
body?: EmojiLikeToOthersWrapper3;
}
/**
* @generated from protobuf message SysMessage.EmojiLikeToOthersWrapper3
*/
export interface EmojiLikeToOthersWrapper3 {
/**
* @generated from protobuf field: SysMessage.EmojiLikeToOthersMsgSpec msgSpec = 2;
*/
msgSpec?: EmojiLikeToOthersMsgSpec;
/**
* @generated from protobuf field: SysMessage.EmojiLikeToOthersAttributes attributes = 3;
*/
attributes?: EmojiLikeToOthersAttributes;
}
/**
* @generated from protobuf message SysMessage.EmojiLikeToOthersMsgSpec
*/
export interface EmojiLikeToOthersMsgSpec {
/**
* @generated from protobuf field: uint32 msgSeq = 1;
*/
msgSeq: number;
}
/**
* @generated from protobuf message SysMessage.EmojiLikeToOthersAttributes
*/
export interface EmojiLikeToOthersAttributes {
/**
* @generated from protobuf field: string emojiId = 1;
*/
emojiId: string;
/**
* @generated from protobuf field: string senderUid = 4;
*/
senderUid: string;
/**
* @generated from protobuf field: SysMessage.EmojiLikeToOthersAttributes.Operation operation = 5;
*/
operation: EmojiLikeToOthersAttributes_Operation;
}
/**
* @generated from protobuf enum SysMessage.EmojiLikeToOthersAttributes.Operation
*/
export enum EmojiLikeToOthersAttributes_Operation {
/**
* @generated from protobuf enum value: FALLBACK = 0;
*/
FALLBACK = 0,
/**
* @generated from protobuf enum value: LIKE = 1;
*/
LIKE = 1,
/**
* @generated from protobuf enum value: UNLIKE = 2;
*/
UNLIKE = 2
}
// @generated message type with reflection information, may provide speed optimized methods
class EmojiLikeToOthersWrapper1$Type extends MessageType<EmojiLikeToOthersWrapper1> {
constructor() {
super("SysMessage.EmojiLikeToOthersWrapper1", [
{ no: 1, name: "wrapper", kind: "message", T: () => EmojiLikeToOthersWrapper2 }
]);
}
create(value?: PartialMessage<EmojiLikeToOthersWrapper1>): EmojiLikeToOthersWrapper1 {
const message = globalThis.Object.create((this.messagePrototype!));
if (value !== undefined)
reflectionMergePartial<EmojiLikeToOthersWrapper1>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: EmojiLikeToOthersWrapper1): EmojiLikeToOthersWrapper1 {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* SysMessage.EmojiLikeToOthersWrapper2 wrapper */ 1:
message.wrapper = EmojiLikeToOthersWrapper2.internalBinaryRead(reader, reader.uint32(), options, message.wrapper);
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: EmojiLikeToOthersWrapper1, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* SysMessage.EmojiLikeToOthersWrapper2 wrapper = 1; */
if (message.wrapper)
EmojiLikeToOthersWrapper2.internalBinaryWrite(message.wrapper, writer.tag(1, WireType.LengthDelimited).fork(), options).join();
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message SysMessage.EmojiLikeToOthersWrapper1
*/
export const EmojiLikeToOthersWrapper1 = new EmojiLikeToOthersWrapper1$Type();
// @generated message type with reflection information, may provide speed optimized methods
class EmojiLikeToOthersWrapper2$Type extends MessageType<EmojiLikeToOthersWrapper2> {
constructor() {
super("SysMessage.EmojiLikeToOthersWrapper2", [
{ no: 1, name: "body", kind: "message", T: () => EmojiLikeToOthersWrapper3 }
]);
}
create(value?: PartialMessage<EmojiLikeToOthersWrapper2>): EmojiLikeToOthersWrapper2 {
const message = globalThis.Object.create((this.messagePrototype!));
if (value !== undefined)
reflectionMergePartial<EmojiLikeToOthersWrapper2>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: EmojiLikeToOthersWrapper2): EmojiLikeToOthersWrapper2 {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* SysMessage.EmojiLikeToOthersWrapper3 body */ 1:
message.body = EmojiLikeToOthersWrapper3.internalBinaryRead(reader, reader.uint32(), options, message.body);
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: EmojiLikeToOthersWrapper2, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* SysMessage.EmojiLikeToOthersWrapper3 body = 1; */
if (message.body)
EmojiLikeToOthersWrapper3.internalBinaryWrite(message.body, writer.tag(1, WireType.LengthDelimited).fork(), options).join();
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message SysMessage.EmojiLikeToOthersWrapper2
*/
export const EmojiLikeToOthersWrapper2 = new EmojiLikeToOthersWrapper2$Type();
// @generated message type with reflection information, may provide speed optimized methods
class EmojiLikeToOthersWrapper3$Type extends MessageType<EmojiLikeToOthersWrapper3> {
constructor() {
super("SysMessage.EmojiLikeToOthersWrapper3", [
{ no: 2, name: "msgSpec", kind: "message", T: () => EmojiLikeToOthersMsgSpec },
{ no: 3, name: "attributes", kind: "message", T: () => EmojiLikeToOthersAttributes }
]);
}
create(value?: PartialMessage<EmojiLikeToOthersWrapper3>): EmojiLikeToOthersWrapper3 {
const message = globalThis.Object.create((this.messagePrototype!));
if (value !== undefined)
reflectionMergePartial<EmojiLikeToOthersWrapper3>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: EmojiLikeToOthersWrapper3): EmojiLikeToOthersWrapper3 {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* SysMessage.EmojiLikeToOthersMsgSpec msgSpec */ 2:
message.msgSpec = EmojiLikeToOthersMsgSpec.internalBinaryRead(reader, reader.uint32(), options, message.msgSpec);
break;
case /* SysMessage.EmojiLikeToOthersAttributes attributes */ 3:
message.attributes = EmojiLikeToOthersAttributes.internalBinaryRead(reader, reader.uint32(), options, message.attributes);
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: EmojiLikeToOthersWrapper3, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* SysMessage.EmojiLikeToOthersMsgSpec msgSpec = 2; */
if (message.msgSpec)
EmojiLikeToOthersMsgSpec.internalBinaryWrite(message.msgSpec, writer.tag(2, WireType.LengthDelimited).fork(), options).join();
/* SysMessage.EmojiLikeToOthersAttributes attributes = 3; */
if (message.attributes)
EmojiLikeToOthersAttributes.internalBinaryWrite(message.attributes, writer.tag(3, WireType.LengthDelimited).fork(), options).join();
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message SysMessage.EmojiLikeToOthersWrapper3
*/
export const EmojiLikeToOthersWrapper3 = new EmojiLikeToOthersWrapper3$Type();
// @generated message type with reflection information, may provide speed optimized methods
class EmojiLikeToOthersMsgSpec$Type extends MessageType<EmojiLikeToOthersMsgSpec> {
constructor() {
super("SysMessage.EmojiLikeToOthersMsgSpec", [
{ no: 1, name: "msgSeq", kind: "scalar", T: 13 /*ScalarType.UINT32*/ }
]);
}
create(value?: PartialMessage<EmojiLikeToOthersMsgSpec>): EmojiLikeToOthersMsgSpec {
const message = globalThis.Object.create((this.messagePrototype!));
message.msgSeq = 0;
if (value !== undefined)
reflectionMergePartial<EmojiLikeToOthersMsgSpec>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: EmojiLikeToOthersMsgSpec): EmojiLikeToOthersMsgSpec {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* uint32 msgSeq */ 1:
message.msgSeq = reader.uint32();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: EmojiLikeToOthersMsgSpec, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* uint32 msgSeq = 1; */
if (message.msgSeq !== 0)
writer.tag(1, WireType.Varint).uint32(message.msgSeq);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message SysMessage.EmojiLikeToOthersMsgSpec
*/
export const EmojiLikeToOthersMsgSpec = new EmojiLikeToOthersMsgSpec$Type();
// @generated message type with reflection information, may provide speed optimized methods
class EmojiLikeToOthersAttributes$Type extends MessageType<EmojiLikeToOthersAttributes> {
constructor() {
super("SysMessage.EmojiLikeToOthersAttributes", [
{ no: 1, name: "emojiId", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
{ no: 4, name: "senderUid", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
{ no: 5, name: "operation", kind: "enum", T: () => ["SysMessage.EmojiLikeToOthersAttributes.Operation", EmojiLikeToOthersAttributes_Operation] }
]);
}
create(value?: PartialMessage<EmojiLikeToOthersAttributes>): EmojiLikeToOthersAttributes {
const message = globalThis.Object.create((this.messagePrototype!));
message.emojiId = "";
message.senderUid = "";
message.operation = 0;
if (value !== undefined)
reflectionMergePartial<EmojiLikeToOthersAttributes>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: EmojiLikeToOthersAttributes): EmojiLikeToOthersAttributes {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* string emojiId */ 1:
message.emojiId = reader.string();
break;
case /* string senderUid */ 4:
message.senderUid = reader.string();
break;
case /* SysMessage.EmojiLikeToOthersAttributes.Operation operation */ 5:
message.operation = reader.int32();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: EmojiLikeToOthersAttributes, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* string emojiId = 1; */
if (message.emojiId !== "")
writer.tag(1, WireType.LengthDelimited).string(message.emojiId);
/* string senderUid = 4; */
if (message.senderUid !== "")
writer.tag(4, WireType.LengthDelimited).string(message.senderUid);
/* SysMessage.EmojiLikeToOthersAttributes.Operation operation = 5; */
if (message.operation !== 0)
writer.tag(5, WireType.Varint).int32(message.operation);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message SysMessage.EmojiLikeToOthersAttributes
*/
export const EmojiLikeToOthersAttributes = new EmojiLikeToOthersAttributes$Type();

View File

@@ -0,0 +1,9 @@
syntax = 'proto3';
package SysMessage;
message GreyTipWrapper {
uint32 subTypeId = 1;
uint32 groupCode = 4;
uint32 subTypeIdMinusOne = 13;
bytes rest = 44;
}

View File

@@ -0,0 +1,104 @@
// @generated by protobuf-ts 2.9.4
// @generated from protobuf file "GreyTipWrapper.proto" (package "SysMessage", syntax proto3)
// tslint:disable
import type { BinaryWriteOptions } from "@protobuf-ts/runtime";
import type { IBinaryWriter } from "@protobuf-ts/runtime";
import { WireType } from "@protobuf-ts/runtime";
import type { BinaryReadOptions } from "@protobuf-ts/runtime";
import type { IBinaryReader } from "@protobuf-ts/runtime";
import { UnknownFieldHandler } from "@protobuf-ts/runtime";
import type { PartialMessage } from "@protobuf-ts/runtime";
import { reflectionMergePartial } from "@protobuf-ts/runtime";
import { MessageType } from "@protobuf-ts/runtime";
/**
* @generated from protobuf message SysMessage.GreyTipWrapper
*/
export interface GreyTipWrapper {
/**
* @generated from protobuf field: uint32 subTypeId = 1;
*/
subTypeId: number;
/**
* @generated from protobuf field: uint32 groupCode = 4;
*/
groupCode: number;
/**
* @generated from protobuf field: uint32 subTypeIdMinusOne = 13;
*/
subTypeIdMinusOne: number;
/**
* @generated from protobuf field: bytes rest = 44;
*/
rest: Uint8Array;
}
// @generated message type with reflection information, may provide speed optimized methods
class GreyTipWrapper$Type extends MessageType<GreyTipWrapper> {
constructor() {
super("SysMessage.GreyTipWrapper", [
{ no: 1, name: "subTypeId", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
{ no: 4, name: "groupCode", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
{ no: 13, name: "subTypeIdMinusOne", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
{ no: 44, name: "rest", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }
]);
}
create(value?: PartialMessage<GreyTipWrapper>): GreyTipWrapper {
const message = globalThis.Object.create((this.messagePrototype!));
message.subTypeId = 0;
message.groupCode = 0;
message.subTypeIdMinusOne = 0;
message.rest = new Uint8Array(0);
if (value !== undefined)
reflectionMergePartial<GreyTipWrapper>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GreyTipWrapper): GreyTipWrapper {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* uint32 subTypeId */ 1:
message.subTypeId = reader.uint32();
break;
case /* uint32 groupCode */ 4:
message.groupCode = reader.uint32();
break;
case /* uint32 subTypeIdMinusOne */ 13:
message.subTypeIdMinusOne = reader.uint32();
break;
case /* bytes rest */ 44:
message.rest = reader.bytes();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: GreyTipWrapper, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* uint32 subTypeId = 1; */
if (message.subTypeId !== 0)
writer.tag(1, WireType.Varint).uint32(message.subTypeId);
/* uint32 groupCode = 4; */
if (message.groupCode !== 0)
writer.tag(4, WireType.Varint).uint32(message.groupCode);
/* uint32 subTypeIdMinusOne = 13; */
if (message.subTypeIdMinusOne !== 0)
writer.tag(13, WireType.Varint).uint32(message.subTypeIdMinusOne);
/* bytes rest = 44; */
if (message.rest.length)
writer.tag(44, WireType.LengthDelimited).bytes(message.rest);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message SysMessage.GreyTipWrapper
*/
export const GreyTipWrapper = new GreyTipWrapper$Type();

View File

@@ -0,0 +1,36 @@
syntax = 'proto3';
package SysMessage;
message SysMessage {
repeated SysMessageHeader header = 1;
repeated SysMessageMsgSpec msgSpec = 2;
SysMessageBodyWrapper bodyWrapper = 3;
}
message SysMessageHeader {
uint32 PeerNumber = 1;
string PeerString = 2;
uint32 Uin = 5;
optional string Uid = 6;
}
message SysMessageMsgSpec {
uint32 msgType = 1;
uint32 subType = 2;
uint32 subSubType = 3;
uint32 msgSeq = 5;
uint32 time = 6;
uint64 msgId = 12;
uint32 other = 13;
}
message SysMessageBodyWrapper {
bytes wrappedBody = 2;
// Find the first [08], or ignore the first 7 bytes?
// And it becomes another ProtoBuf message.
}
message KeyValuePair {
string key = 1;
string value = 2;
}

View File

@@ -0,0 +1,435 @@
// @generated by protobuf-ts 2.9.4
// @generated from protobuf file "SysMessage.proto" (package "SysMessage", syntax proto3)
// tslint:disable
import type { BinaryWriteOptions } from "@protobuf-ts/runtime";
import type { IBinaryWriter } from "@protobuf-ts/runtime";
import { WireType } from "@protobuf-ts/runtime";
import type { BinaryReadOptions } from "@protobuf-ts/runtime";
import type { IBinaryReader } from "@protobuf-ts/runtime";
import { UnknownFieldHandler } from "@protobuf-ts/runtime";
import type { PartialMessage } from "@protobuf-ts/runtime";
import { reflectionMergePartial } from "@protobuf-ts/runtime";
import { MessageType } from "@protobuf-ts/runtime";
/**
* @generated from protobuf message SysMessage.SysMessage
*/
export interface SysMessage {
/**
* @generated from protobuf field: repeated SysMessage.SysMessageHeader header = 1;
*/
header: SysMessageHeader[];
/**
* @generated from protobuf field: repeated SysMessage.SysMessageMsgSpec msgSpec = 2;
*/
msgSpec: SysMessageMsgSpec[];
/**
* @generated from protobuf field: SysMessage.SysMessageBodyWrapper bodyWrapper = 3;
*/
bodyWrapper?: SysMessageBodyWrapper;
}
/**
* @generated from protobuf message SysMessage.SysMessageHeader
*/
export interface SysMessageHeader {
/**
* @generated from protobuf field: uint32 PeerNumber = 1 [json_name = "PeerNumber"];
*/
peerNumber: number;
/**
* @generated from protobuf field: string PeerString = 2 [json_name = "PeerString"];
*/
peerString: string;
/**
* @generated from protobuf field: uint32 Uin = 5 [json_name = "Uin"];
*/
uin: number;
/**
* @generated from protobuf field: optional string Uid = 6 [json_name = "Uid"];
*/
uid?: string;
}
/**
* @generated from protobuf message SysMessage.SysMessageMsgSpec
*/
export interface SysMessageMsgSpec {
/**
* @generated from protobuf field: uint32 msgType = 1;
*/
msgType: number;
/**
* @generated from protobuf field: uint32 subType = 2;
*/
subType: number;
/**
* @generated from protobuf field: uint32 subSubType = 3;
*/
subSubType: number;
/**
* @generated from protobuf field: uint32 msgSeq = 5;
*/
msgSeq: number;
/**
* @generated from protobuf field: uint32 time = 6;
*/
time: number;
/**
* @generated from protobuf field: uint64 msgId = 12;
*/
msgId: bigint;
/**
* @generated from protobuf field: uint32 other = 13;
*/
other: number;
}
/**
* @generated from protobuf message SysMessage.SysMessageBodyWrapper
*/
export interface SysMessageBodyWrapper {
/**
* @generated from protobuf field: bytes wrappedBody = 2;
*/
wrappedBody: Uint8Array; // Find the first [08], or ignore the first 7 bytes?
// And it becomes another ProtoBuf message.
}
/**
* @generated from protobuf message SysMessage.KeyValuePair
*/
export interface KeyValuePair {
/**
* @generated from protobuf field: string key = 1;
*/
key: string;
/**
* @generated from protobuf field: string value = 2;
*/
value: string;
}
// @generated message type with reflection information, may provide speed optimized methods
class SysMessage$Type extends MessageType<SysMessage> {
constructor() {
super("SysMessage.SysMessage", [
{ no: 1, name: "header", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => SysMessageHeader },
{ no: 2, name: "msgSpec", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => SysMessageMsgSpec },
{ no: 3, name: "bodyWrapper", kind: "message", T: () => SysMessageBodyWrapper }
]);
}
create(value?: PartialMessage<SysMessage>): SysMessage {
const message = globalThis.Object.create((this.messagePrototype!));
message.header = [];
message.msgSpec = [];
if (value !== undefined)
reflectionMergePartial<SysMessage>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: SysMessage): SysMessage {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* repeated SysMessage.SysMessageHeader header */ 1:
message.header.push(SysMessageHeader.internalBinaryRead(reader, reader.uint32(), options));
break;
case /* repeated SysMessage.SysMessageMsgSpec msgSpec */ 2:
message.msgSpec.push(SysMessageMsgSpec.internalBinaryRead(reader, reader.uint32(), options));
break;
case /* SysMessage.SysMessageBodyWrapper bodyWrapper */ 3:
message.bodyWrapper = SysMessageBodyWrapper.internalBinaryRead(reader, reader.uint32(), options, message.bodyWrapper);
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: SysMessage, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* repeated SysMessage.SysMessageHeader header = 1; */
for (let i = 0; i < message.header.length; i++)
SysMessageHeader.internalBinaryWrite(message.header[i], writer.tag(1, WireType.LengthDelimited).fork(), options).join();
/* repeated SysMessage.SysMessageMsgSpec msgSpec = 2; */
for (let i = 0; i < message.msgSpec.length; i++)
SysMessageMsgSpec.internalBinaryWrite(message.msgSpec[i], writer.tag(2, WireType.LengthDelimited).fork(), options).join();
/* SysMessage.SysMessageBodyWrapper bodyWrapper = 3; */
if (message.bodyWrapper)
SysMessageBodyWrapper.internalBinaryWrite(message.bodyWrapper, writer.tag(3, WireType.LengthDelimited).fork(), options).join();
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message SysMessage.SysMessage
*/
export const SysMessage = new SysMessage$Type();
// @generated message type with reflection information, may provide speed optimized methods
class SysMessageHeader$Type extends MessageType<SysMessageHeader> {
constructor() {
super("SysMessage.SysMessageHeader", [
{ no: 1, name: "PeerNumber", kind: "scalar", jsonName: "PeerNumber", T: 13 /*ScalarType.UINT32*/ },
{ no: 2, name: "PeerString", kind: "scalar", jsonName: "PeerString", T: 9 /*ScalarType.STRING*/ },
{ no: 5, name: "Uin", kind: "scalar", jsonName: "Uin", T: 13 /*ScalarType.UINT32*/ },
{ no: 6, name: "Uid", kind: "scalar", jsonName: "Uid", opt: true, T: 9 /*ScalarType.STRING*/ }
]);
}
create(value?: PartialMessage<SysMessageHeader>): SysMessageHeader {
const message = globalThis.Object.create((this.messagePrototype!));
message.peerNumber = 0;
message.peerString = "";
message.uin = 0;
if (value !== undefined)
reflectionMergePartial<SysMessageHeader>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: SysMessageHeader): SysMessageHeader {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* uint32 PeerNumber = 1 [json_name = "PeerNumber"];*/ 1:
message.peerNumber = reader.uint32();
break;
case /* string PeerString = 2 [json_name = "PeerString"];*/ 2:
message.peerString = reader.string();
break;
case /* uint32 Uin = 5 [json_name = "Uin"];*/ 5:
message.uin = reader.uint32();
break;
case /* optional string Uid = 6 [json_name = "Uid"];*/ 6:
message.uid = reader.string();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: SysMessageHeader, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* uint32 PeerNumber = 1 [json_name = "PeerNumber"]; */
if (message.peerNumber !== 0)
writer.tag(1, WireType.Varint).uint32(message.peerNumber);
/* string PeerString = 2 [json_name = "PeerString"]; */
if (message.peerString !== "")
writer.tag(2, WireType.LengthDelimited).string(message.peerString);
/* uint32 Uin = 5 [json_name = "Uin"]; */
if (message.uin !== 0)
writer.tag(5, WireType.Varint).uint32(message.uin);
/* optional string Uid = 6 [json_name = "Uid"]; */
if (message.uid !== undefined)
writer.tag(6, WireType.LengthDelimited).string(message.uid);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message SysMessage.SysMessageHeader
*/
export const SysMessageHeader = new SysMessageHeader$Type();
// @generated message type with reflection information, may provide speed optimized methods
class SysMessageMsgSpec$Type extends MessageType<SysMessageMsgSpec> {
constructor() {
super("SysMessage.SysMessageMsgSpec", [
{ no: 1, name: "msgType", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
{ no: 2, name: "subType", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
{ no: 3, name: "subSubType", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
{ no: 5, name: "msgSeq", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
{ no: 6, name: "time", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
{ no: 12, name: "msgId", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ },
{ no: 13, name: "other", kind: "scalar", T: 13 /*ScalarType.UINT32*/ }
]);
}
create(value?: PartialMessage<SysMessageMsgSpec>): SysMessageMsgSpec {
const message = globalThis.Object.create((this.messagePrototype!));
message.msgType = 0;
message.subType = 0;
message.subSubType = 0;
message.msgSeq = 0;
message.time = 0;
message.msgId = 0n;
message.other = 0;
if (value !== undefined)
reflectionMergePartial<SysMessageMsgSpec>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: SysMessageMsgSpec): SysMessageMsgSpec {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* uint32 msgType */ 1:
message.msgType = reader.uint32();
break;
case /* uint32 subType */ 2:
message.subType = reader.uint32();
break;
case /* uint32 subSubType */ 3:
message.subSubType = reader.uint32();
break;
case /* uint32 msgSeq */ 5:
message.msgSeq = reader.uint32();
break;
case /* uint32 time */ 6:
message.time = reader.uint32();
break;
case /* uint64 msgId */ 12:
message.msgId = reader.uint64().toBigInt();
break;
case /* uint32 other */ 13:
message.other = reader.uint32();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: SysMessageMsgSpec, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* uint32 msgType = 1; */
if (message.msgType !== 0)
writer.tag(1, WireType.Varint).uint32(message.msgType);
/* uint32 subType = 2; */
if (message.subType !== 0)
writer.tag(2, WireType.Varint).uint32(message.subType);
/* uint32 subSubType = 3; */
if (message.subSubType !== 0)
writer.tag(3, WireType.Varint).uint32(message.subSubType);
/* uint32 msgSeq = 5; */
if (message.msgSeq !== 0)
writer.tag(5, WireType.Varint).uint32(message.msgSeq);
/* uint32 time = 6; */
if (message.time !== 0)
writer.tag(6, WireType.Varint).uint32(message.time);
/* uint64 msgId = 12; */
if (message.msgId !== 0n)
writer.tag(12, WireType.Varint).uint64(message.msgId);
/* uint32 other = 13; */
if (message.other !== 0)
writer.tag(13, WireType.Varint).uint32(message.other);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message SysMessage.SysMessageMsgSpec
*/
export const SysMessageMsgSpec = new SysMessageMsgSpec$Type();
// @generated message type with reflection information, may provide speed optimized methods
class SysMessageBodyWrapper$Type extends MessageType<SysMessageBodyWrapper> {
constructor() {
super("SysMessage.SysMessageBodyWrapper", [
{ no: 2, name: "wrappedBody", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }
]);
}
create(value?: PartialMessage<SysMessageBodyWrapper>): SysMessageBodyWrapper {
const message = globalThis.Object.create((this.messagePrototype!));
message.wrappedBody = new Uint8Array(0);
if (value !== undefined)
reflectionMergePartial<SysMessageBodyWrapper>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: SysMessageBodyWrapper): SysMessageBodyWrapper {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* bytes wrappedBody */ 2:
message.wrappedBody = reader.bytes();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: SysMessageBodyWrapper, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* bytes wrappedBody = 2; */
if (message.wrappedBody.length)
writer.tag(2, WireType.LengthDelimited).bytes(message.wrappedBody);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message SysMessage.SysMessageBodyWrapper
*/
export const SysMessageBodyWrapper = new SysMessageBodyWrapper$Type();
// @generated message type with reflection information, may provide speed optimized methods
class KeyValuePair$Type extends MessageType<KeyValuePair> {
constructor() {
super("SysMessage.KeyValuePair", [
{ no: 1, name: "key", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
{ no: 2, name: "value", kind: "scalar", T: 9 /*ScalarType.STRING*/ }
]);
}
create(value?: PartialMessage<KeyValuePair>): KeyValuePair {
const message = globalThis.Object.create((this.messagePrototype!));
message.key = "";
message.value = "";
if (value !== undefined)
reflectionMergePartial<KeyValuePair>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: KeyValuePair): KeyValuePair {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* string key */ 1:
message.key = reader.string();
break;
case /* string value */ 2:
message.value = reader.string();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: KeyValuePair, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* string key = 1; */
if (message.key !== "")
writer.tag(1, WireType.LengthDelimited).string(message.key);
/* string value = 2; */
if (message.value !== "")
writer.tag(2, WireType.LengthDelimited).string(message.value);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message SysMessage.KeyValuePair
*/
export const KeyValuePair = new KeyValuePair$Type();

View File

@@ -2,6 +2,7 @@ import { NodeIKernelGroupListener } from '@/core/listeners/NodeIKernelGroupListe
import {
GroupExt0xEF0InfoFilter,
GroupExtParam,
GroupInfoSource,
GroupMember,
GroupMemberRole,
GroupNotifyMsgType,
@@ -13,9 +14,9 @@ import { GeneralCallResult } from '@/core/services/common';
//高版本的接口不应该随意使用 使用应该严格进行pr审核 同时部分ipc中未出现的接口不要过于依赖 应该做好数据兜底
export interface NodeIKernelGroupService {
//getGroupExt0xEF0Info(this.$enableGroupCodes, this.$bannedGroupCodes, this.$filter, this.$forceFetch
getGroupExt0xEF0Info(enableGroupCodes: string[], bannedGroupCodes: string[], filter: GroupExt0xEF0InfoFilter, forceFetch: boolean):
Promise<GeneralCallResult & { result: { groupExtInfos: Map<string, any> }}>;
Promise<GeneralCallResult & { result: { groupExtInfos: Map<string, any> } }>;
kickMemberV2(param: KickMemberV2Req): Promise<GeneralCallResult>;
quitGroupV2(param: { groupCode: string; needDeleteLocalMsg: boolean; }): Promise<GeneralCallResult>;
@@ -39,11 +40,11 @@ export interface NodeIKernelGroupService {
realSpecialTitleFlag: number
}): Promise<unknown>;
//26702
getGroupMemberLevelInfo(groupCode: string): Promise<unknown>;
//26702
getGroupHonorList(groupCodes: Array<string>): unknown;
getGroupInfoForJoinGroup(groupCode: string, needPrivilegeFlag: boolean, serviceType: number): Promise<unknown>;
getGroupHonorList(req: { groupCodes: Array<string> }): Promise<unknown>;
getUinByUids(uins: string[]): Promise<{
errCode: number,
@@ -57,13 +58,10 @@ export interface NodeIKernelGroupService {
uids: Map<string, string>
}>;
//26702(其实更早 但是我不知道)
checkGroupMemberCache(arrayList: Array<string>): Promise<unknown>;
//26702(其实更早 但是我不知道)
getGroupLatestEssenceList(groupCode: string): Promise<unknown>;
//26702(其实更早 但是我不知道)
shareDigest(Req: {
appId: string,
appType: number,
@@ -83,20 +81,17 @@ export interface NodeIKernelGroupService {
}
}): Promise<unknown>;
//26702(其实更早 但是我不知道)
isEssenceMsg(Req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown>;
//26702(其实更早 但是我不知道)
queryCachedEssenceMsg(Req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown>;
//26702(其实更早 但是我不知道)
fetchGroupEssenceList(Req: {
groupCode: string,
pageStart: number,
pageLimit: number
}, Arg: unknown): Promise<unknown>;
//26702
getAllMemberList(groupCode: string, forceFetch: boolean): Promise<{
errCode: number,
errMsg: string,
@@ -135,8 +130,6 @@ export interface NodeIKernelGroupService {
getMemberInfo(group_id: string, uids: string[], forceFetch: boolean): Promise<GeneralCallResult>;
//getMemberInfo [ '56729xxxx', [ 'u_4Nj08cwW5Hxxxxx' ], true ]
kickMember(groupCode: string, memberUids: string[], refuseForever: boolean, kickReason: string): Promise<void>;
modifyMemberRole(groupCode: string, uid: string, role: GroupMemberRole): void;
@@ -151,7 +144,7 @@ export interface NodeIKernelGroupService {
getGroupExtList(force: boolean): Promise<GeneralCallResult>;
getGroupDetailInfo(groupCode: string): unknown;
getGroupDetailInfo(groupCode: string, groupInfoSource: GroupInfoSource): Promise<unknown>;
getMemberExtInfo(param: GroupExtParam): Promise<unknown>;//req
@@ -189,14 +182,13 @@ export interface NodeIKernelGroupService {
destroyGroup(groupCode: string): void;
//获取单屏群通知列表
getSingleScreenNotifies(force: boolean, start_seq: string, num: number): Promise<GeneralCallResult>;
getSingleScreenNotifies(doubted: boolean, start_seq: string, num: number): Promise<GeneralCallResult>;
clearGroupNotifies(groupCode: string): void;
getGroupNotifiesUnreadCount(unknown: boolean): Promise<GeneralCallResult>;
clearGroupNotifiesUnreadCount(groupCode: string): void;
clearGroupNotifiesUnreadCount(unknown: boolean): void;
operateSysNotify(
doubt: boolean,

View File

@@ -167,11 +167,17 @@ export interface NodeIKernelMsgService {
getAllOnlineFileMsgs(...args: unknown[]): unknown;
getLatestDbMsgs(peer: Peer, cnt: number): Promise<unknown>;
getLatestDbMsgs(peer: Peer, cnt: number): Promise<GeneralCallResult & {
msgList: RawMessage[]
}>;
getLastMessageList(peer: Peer[]): Promise<unknown>;
getLastMessageList(peer: Peer[]): Promise<GeneralCallResult & {
msgList: RawMessage[]
}>;
getAioFirstViewLatestMsgs(peer: Peer, num: number): unknown;
getAioFirstViewLatestMsgs(peer: Peer, num: number): Promise<GeneralCallResult & {
msgList: RawMessage[]
}>;
getMsgs(peer: Peer, msgId: string, count: unknown, queryOrder: boolean): Promise<unknown>;
@@ -512,11 +518,11 @@ export interface NodeIKernelMsgService {
result: number,
errMsg: string,
emojiLikesList:
Array<{
tinyId: string,
nickName: string,
headUrl: string
}>,
Array<{
tinyId: string,
nickName: string,
headUrl: string
}>,
cookie: string,
isLastPage: boolean,
isFirstPage: boolean

View File

@@ -1,6 +1,5 @@
import { AnyCnameRecord } from 'node:dns';
import { BizKey, ModifyProfileParams, SimpleInfo, UserDetailInfoByUin, UserDetailInfoByUinV2 } from '@/core';
import { NodeIKernelProfileListener } from '@/core';
import { BizKey, ModifyProfileParams, NodeIKernelProfileListener, SimpleInfo, UserDetailInfoByUinV2 } from '@/core';
import { GeneralCallResult } from '@/core/services/common';
export enum UserDetailSource {

View File

@@ -155,7 +155,7 @@ export interface NodeIKernelRichMediaService {
}): unknown;
//arg3为“”
downloadFileForModelId(peer: Peer, ModelId: string[], arg3: string): unknown;
downloadFileForModelId(peer: Peer, ModelId: string[], unknown: string): Promise<unknown>;
//第三个参数 Array<Type>
// this.fileId = "";

View File

@@ -20,6 +20,7 @@ export * from './NodeIKernelCollectionService';
import type {
NodeIKernelAvatarService,
NodeIKernelBuddyService,
NodeIKernelCollectionService,
NodeIKernelDbToolsService,
NodeIKernelFileAssistantService,
NodeIKernelGroupService,
@@ -30,11 +31,10 @@ import type {
NodeIKernelProfileService,
NodeIKernelRichMediaService,
NodeIKernelRobotService,
NodeIKernelSearchService,
NodeIKernelStorageCleanService,
NodeIKernelTicketService,
NodeIKernelTipOffService,
NodeIKernelSearchService,
NodeIKernelCollectionService,
} from '.';
export type ServiceNamingMapping = {

View File

@@ -1,5 +1,4 @@
import { NodeIDependsAdapter, NodeIDispatcherAdapter, NodeIGlobalAdapter } from '../adapters';
import { NodeIKernelSessionListener } from '@/core';
import { NodeIDependsAdapter, NodeIDispatcherAdapter, NodeIGlobalAdapter } from './adapters';
import {
NodeIKernelAvatarService,
NodeIKernelBuddyService,
@@ -9,23 +8,24 @@ import {
NodeIKernelProfileLikeService,
NodeIKernelProfileService,
NodeIKernelRichMediaService,
NodeIKernelRobotService,
NodeIKernelSessionListener,
NodeIKernelStorageCleanService,
NodeIKernelTicketService,
NodeIKernelTipOffService,
} from '@/core';
import { NodeIKernelStorageCleanService } from '@/core';
import { NodeIKernelRobotService } from '@/core';
import { NodeIKernelNodeMiscService } from '../services/NodeIKernelNodeMiscService';
import { NodeIKernelUixConvertService } from '../services/NodeIKernelUixConvertService';
import { NodeIKernelMsgBackupService } from '../services/NodeIKernelMsgBackupService';
import { NodeIKernelAlbumService } from '../services/NodeIKernelAlbumService';
import { NodeIKernelTianShuService } from '../services/NodeIKernelTianShuService';
import { NodeIKernelUnitedConfigService } from '../services/NodeIKernelUnitedConfigService';
import { NodeIKernelSearchService } from '../services/NodeIKernelSearchService';
import { NodeIKernelCollectionService } from '../services/NodeIKernelCollectionService';
import { NodeIKernelRecentContactService } from '../services/NodeIKernelRecentContactService';
import { NodeIKernelMSFService } from '../services/NodeIKernelMSFService';
import { NodeIkernelTestPerformanceService } from '../services/NodeIkernelTestPerformanceService';
import { NodeIKernelECDHService } from '../services/NodeIKernelECDHService';
} from '@/core/index';
import { NodeIKernelNodeMiscService } from './services/NodeIKernelNodeMiscService';
import { NodeIKernelUixConvertService } from './services/NodeIKernelUixConvertService';
import { NodeIKernelMsgBackupService } from './services/NodeIKernelMsgBackupService';
import { NodeIKernelAlbumService } from './services/NodeIKernelAlbumService';
import { NodeIKernelTianShuService } from './services/NodeIKernelTianShuService';
import { NodeIKernelUnitedConfigService } from './services/NodeIKernelUnitedConfigService';
import { NodeIKernelSearchService } from './services/NodeIKernelSearchService';
import { NodeIKernelCollectionService } from './services/NodeIKernelCollectionService';
import { NodeIKernelRecentContactService } from './services/NodeIKernelRecentContactService';
import { NodeIKernelMSFService } from './services/NodeIKernelMSFService';
import { NodeIkernelTestPerformanceService } from './services/NodeIkernelTestPerformanceService';
import { NodeIKernelECDHService } from './services/NodeIKernelECDHService';
export interface NodeQQNTWrapperUtil {
get(): unknown;

View File

@@ -1,27 +0,0 @@
import { LogWrapper } from '@/common/utils/log';
import { QQBasicInfoWrapper } from '@/common/utils/qq-basic-info';
import { NapCatCoreWorkingEnv, NodeIKernelLoginService, NodeIQQNTWrapperSession, WrapperNodeApi } from '@/core';
import { NTQQFileApi, NTQQFriendApi, NTQQGroupApi, NTQQMsgApi, NTQQSystemApi, NTQQUserApi, NTQQWebApi } from '../apis';
import { NTQQCollectionApi } from '../apis/collection';
import { NapCatPathWrapper } from '@/common/framework/napcat';
export interface InstanceContext {
readonly workingEnv: NapCatCoreWorkingEnv;
readonly wrapper: WrapperNodeApi;
readonly session: NodeIQQNTWrapperSession;
readonly logger: LogWrapper;
readonly loginService: NodeIKernelLoginService;
readonly basicInfoWrapper: QQBasicInfoWrapper;
readonly pathWrapper: NapCatPathWrapper;
}
export interface StableNTApiWrapper {
FileApi: NTQQFileApi,
SystemApi: NTQQSystemApi,
CollectionApi: NTQQCollectionApi,
WebApi: NTQQWebApi,
FriendApi: NTQQFriendApi,
MsgApi: NTQQMsgApi,
UserApi: NTQQUserApi,
GroupApi: NTQQGroupApi
}

View File

@@ -1,55 +0,0 @@
import path from 'node:path';
import fs from 'node:fs';
import { PlatformType, VendorType, WrapperSessionInitConfig } from './wrapper';
import { getMachineId, hostname, systemName, systemVersion } from '@/common/utils/system';
export async function genSessionConfig(QQVersionAppid: string, QQVersion: string, selfUin: string, selfUid: string, account_path: string): Promise<WrapperSessionInitConfig> {
const downloadPath = path.join(account_path, 'NapCat', 'temp');
fs.mkdirSync(downloadPath, { recursive: true });
const guid: string = await getMachineId();//26702 支持JS获取guid值 在LoginService中获取 TODO mlikiow a
const config: WrapperSessionInitConfig = {
selfUin,
selfUid,
desktopPathConfig: {
account_path, // 可以通过NodeQQNTWrapperUtil().getNTUserDataInfoConfig()获取
},
clientVer: QQVersion, // 9.9.8-22355
a2: '',
d2: '',
d2Key: '',
machineId: '',
platform: PlatformType.KWINDOWS, // 3是Windows?
platVer: systemVersion, // 系统版本号, 应该可以固定
appid: QQVersionAppid,
rdeliveryConfig: {
appKey: '',
systemId: 0,
appId: '',
logicEnvironment: '',
platform: PlatformType.KWINDOWS,
language: '',
sdkVersion: '',
userId: '',
appVersion: '',
osVersion: '',
bundleId: '',
serverUrl: '',
fixedAfterHitKeys: [''],
},
defaultFileDownloadPath: downloadPath,
deviceInfo: {
guid,
buildVer: QQVersion,
localId: 2052,
devName: hostname,
devType: systemName,
vendorName: '',
osVer: systemVersion,
vendorOsName: systemName,
setMute: false,
vendorType: VendorType.KNOSETONIOS,
},
deviceConfig: '{"appearance":{"isSplitViewMode":true},"msg":{}}',
};
return config;
}

View File

@@ -1,3 +0,0 @@
export * from './wrapper';
export * from './helper';
export * from './context';

View File

@@ -1,13 +1,13 @@
import { NapCatPathWrapper } from '@/common/framework/napcat';
import { LogWrapper } from '@/common/utils/log';
import { proxiedListenerOf } from '@/common/utils/proxy-handler';
import { QQBasicInfoWrapper } from '@/common/utils/qq-basic-info';
import { loadQQWrapper, NapCatCore, NapCatCoreWorkingEnv } from '@/core/core';
import { NapCatPathWrapper } from '@/common/path';
import { LogWrapper } from '@/common/log';
import { proxiedListenerOf } from '@/common/proxy-handler';
import { QQBasicInfoWrapper } from '@/common/qq-basic-info';
import { loadQQWrapper, NapCatCore, NapCatCoreWorkingEnv } from '@/core';
import { InstanceContext } from '@/core';
import { SelfInfo } from '@/core/entities';
import { NodeIKernelLoginListener } from '@/core/listeners';
import { NodeIKernelLoginService } from '@/core/services';
import { NodeIQQNTWrapperSession, WrapperNodeApi } from '@/core/wrapper/wrapper';
import { NodeIQQNTWrapperSession, WrapperNodeApi } from '@/core/wrapper';
import { InitWebUi, WebUiConfig } from '@/webui';
import { NapCatOneBot11Adapter } from '@/onebot';

4
src/nekodoge/Readme.md Normal file
View File

@@ -0,0 +1,4 @@
# nekodoge
此协议为替代QQ平台 OnebotV11长期不可靠问题
# 规划路线

View File

View File

View File

0
src/nekodoge/index.ts Normal file
View File

View File

@@ -0,0 +1,18 @@
import { createServer } from 'node:net';
export class NewAdapterNetwork {
constructor(public host: number, public port: number) { }
async open() {
const server = createServer((socket) => {
socket.on('data', (data) => {
});
socket.on('end', () => {
});
socket.on('connect', () => {
})
});
server.listen(this.port, this.host);
}
}

View File

@@ -1,6 +1,6 @@
import { OB11Return } from '../types';
import { isNull } from '../../common/utils/helper';
import { isNull } from '../../common/helper';
export class OB11Response {
static res<T>(data: T, status: string, retcode: number, message: string = ''): OB11Return<T> {

View File

@@ -2,7 +2,7 @@
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { MessageUnique } from '@/common/utils/message-unique';
import { MessageUnique } from '@/common/message-unique';
const SchemaData = {
type: 'object',

View File

@@ -1,4 +1,4 @@
import { OB11Entities } from '@/onebot/helper/entities';
import { OB11Entities } from '@/onebot/entities';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';

View File

@@ -0,0 +1,24 @@
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
const SchemaData = {
type: 'object',
properties: {
group_id: { type: ['number', 'string'] },
},
required: ['group_id'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class GetGroupInfoEx extends BaseAction<Payload, any> {
actionName = ActionName.GetGroupInfoEx;
payloadSchema = SchemaData;
async _handle(payload: Payload) {
const NTQQGroupApi = this.core.apis.GroupApi;
const groupInfoEx = (await NTQQGroupApi.getGroupExtFE0Info([payload.group_id.toString()])).result.groupExtInfos.get(payload.group_id.toString());
return groupInfoEx;
}
}

View File

@@ -1,7 +1,7 @@
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { checkFileReceived, uri2local } from '@/common/utils/file';
import { checkFileReceived, uri2local } from '@/common/file';
import fs from 'fs';
const SchemaData = {

View File

@@ -1,7 +1,7 @@
import BaseAction from '../BaseAction';
import { ActionName, BaseCheckResult } from '../types';
import * as fs from 'node:fs';
import { checkFileReceived, uri2local } from '@/common/utils/file';
import { checkFileReceived, uri2local } from '@/common/file';
interface Payload {
file: string;

View File

@@ -1,8 +1,8 @@
import BaseAction from '../BaseAction';
import fs from 'fs/promises';
import { UUIDConverter } from '@/common/utils/helper';
import { FileNapCatOneBotUUID } from '@/common/helper';
import { ActionName } from '../types';
import { ChatType, ElementType, FileElement, Peer, RawMessage, VideoElement } from '@/core/entities';
import { ChatType, Peer, RawMessage } from '@/core/entities';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
export interface GetFilePayload {
@@ -29,70 +29,60 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
payloadSchema: any = GetFileBase_PayloadSchema;
async _handle(payload: GetFilePayload): Promise<GetFileResponse> {
const NTQQFriendApi = this.core.apis.FriendApi;
const NTQQUserApi = this.core.apis.UserApi;
const NTQQMsgApi = this.core.apis.MsgApi;
const NTQQGroupApi = this.core.apis.GroupApi;
const NTQQFileApi = this.core.apis.FileApi;
let UuidData: {
high: string;
low: string;
} | undefined;
try {
UuidData = UUIDConverter.decode(payload.file);
if (UuidData) {
const peerUin = UuidData.high;
const msgId = UuidData.low;
const isGroup: boolean = !!(await NTQQGroupApi.getGroups(false)).find(e => e.groupCode == peerUin);
let peer: Peer | undefined;
//识别Peer
if (isGroup) {
peer = { chatType: ChatType.KCHATTYPEGROUP, peerUid: peerUin };
const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file);
//接收消息标记模式
if (contextMsgFile) {
const { peer, msgId, elementId } = contextMsgFile;
const downloadPath = await NTQQFileApi.downloadMedia(msgId, peer.chatType, peer.peerUid, elementId, '', '');
const mixElement = (await NTQQMsgApi.getMsgsByMsgId(peer, [msgId]))?.msgList
.find(msg => msg.msgId === msgId)?.elements.find(e => e.elementId === elementId);
const mixElementInner = mixElement?.videoElement ?? mixElement?.fileElement ?? mixElement?.pttElement ?? mixElement?.picElement;
if (!mixElementInner) throw new Error('element not found');
const fileSize = mixElementInner.fileSize?.toString() ?? '';
const fileName = mixElementInner.fileName ?? '';
const res: GetFileResponse = {
file: downloadPath,
url: downloadPath,
file_size: fileSize,
file_name: fileName,
};
if (this.obContext.configLoader.configData.enableLocalFile2Url && downloadPath) {
try {
res.base64 = await fs.readFile(downloadPath, 'base64');
} catch (e) {
throw new Error('文件下载失败. ' + e);
}
const PeerUid = await NTQQUserApi.getUidByUinV2(peerUin);
if (PeerUid) {
const isBuddy = await NTQQFriendApi.isBuddy(PeerUid);
if (isBuddy) {
peer = { chatType: ChatType.KCHATTYPEC2C, peerUid: PeerUid };
} else {
peer = { chatType: ChatType.KCHATTYPETEMPC2CFROMGROUP, peerUid: PeerUid };
}
}
if (!peer) {
throw new Error('chattype not support');
}
const msgList = await NTQQMsgApi.getMsgsByMsgId(peer, [msgId]);
if (msgList.msgList.length == 0) {
throw new Error('msg not found');
}
const msg = msgList.msgList[0];
const findEle = msg.elements.find(e => e.elementType == ElementType.VIDEO || e.elementType == ElementType.FILE || e.elementType == ElementType.PTT);
if (!findEle) {
throw new Error('element not found');
}
const downloadPath = await NTQQFileApi.downloadMedia(msgId, msg.chatType, msg.peerUid, findEle.elementId, '', '');
const fileSize = findEle?.videoElement?.fileSize || findEle?.fileElement?.fileSize || findEle?.pttElement?.fileSize || '0';
const fileName = findEle?.videoElement?.fileName || findEle?.fileElement?.fileName || findEle?.pttElement?.fileName || '';
const res: GetFileResponse = {
file: downloadPath,
url: downloadPath,
file_size: fileSize,
file_name: fileName,
};
if (true/*enableLocalFile2Url*/ && downloadPath) {
try {
res.base64 = await fs.readFile(downloadPath, 'base64');
} catch (e) {
throw new Error('文件下载失败. ' + e);
}
}
//不手动删除?文件持久化了
return res;
}
} catch {
this.core.context.logger.logDebug('GetFileBase Mode - 1 Error');
return res;
}
//群文件模式
const contextModelIdFile = FileNapCatOneBotUUID.decodeModelId(payload.file);
if (contextModelIdFile) {
const { peer, modelId } = contextModelIdFile;
const downloadPath = await NTQQFileApi.downloadFileForModelId(peer, modelId,'');
const res: GetFileResponse = {
file: downloadPath,
url: downloadPath,
file_size: '',
file_name: '',
};
if (this.obContext.configLoader.configData.enableLocalFile2Url && downloadPath) {
try {
res.base64 = await fs.readFile(downloadPath, 'base64');
} catch (e) {
throw new Error('文件下载失败. ' + e);
}
}
return res;
}
//搜索名字模式
const NTSearchNameResult = (await NTQQFileApi.searchfile([payload.file])).resultItems;
if (NTSearchNameResult.length !== 0) {
const MsgId = NTSearchNameResult[0].msgId;
@@ -100,9 +90,7 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
if (NTSearchNameResult[0].chatType == ChatType.KCHATTYPEGROUP) {
peer = { chatType: ChatType.KCHATTYPEGROUP, peerUid: NTSearchNameResult[0].groupChatInfo[0].groupCode };
}
if (!peer) {
throw new Error('chattype not support');
}
if (!peer) throw new Error('chattype not support');
const msgList: RawMessage[] = (await NTQQMsgApi.getMsgsByMsgId(peer, [MsgId]))?.msgList;
if (!msgList || msgList.length == 0) {
throw new Error('msg not found');
@@ -119,77 +107,17 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
file_size: NTSearchNameResult[0].fileSize.toString(),
file_name: NTSearchNameResult[0].fileName,
};
if (true/*enableLocalFile2Url*/ && downloadPath) {
if (this.obContext.configLoader.configData.enableLocalFile2Url && downloadPath) {
try {
res.base64 = await fs.readFile(downloadPath, 'base64');
} catch (e) {
throw new Error('文件下载失败. ' + e);
}
}
//不手动删除?文件持久化了
return res;
}
throw new Error('file not found');
// let cache = await dbUtil.getFileCacheByName(payload.file);
// if (!cache) {
// cache = await dbUtil.getFileCacheByUuid(payload.file);
// }
// if (!cache) {
// throw new Error('file not found');
// }
// const { enableLocalFile2Url } = ob11Config;
// try {
// await fs.access(cache.path, fs.constants.F_OK);
// } catch (e) {
// logDebug('local file not found, start download...');
// // if (cache.url) {
// // const downloadResult = await uri2local(cache.url);
// // if (downloadResult.success) {
// // cache.path = downloadResult.path;
// // dbUtil.updateFileCache(cache).then();
// // } else {
// // throw new Error('file download failed. ' + downloadResult.errMsg);
// // }
// // } else {
// // // 没有url的可能是私聊文件或者群文件需要自己下载
// // log('需要调用 NTQQ 下载文件api');
// let peer = MessageUnique.getPeerByMsgId(cache.msgId);
// let msg = await NTQQMsgApi.getMsgsByMsgId(peer?.Peer!,cache.msgId);
// // log('文件 msg', msg);
// if (msg) {
// // 构建下载函数
// const downloadPath = await NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid,
// cache.elementId, '', '');
// // await sleep(1000);
// // log('download result', downloadPath);
// let peer = MessageUnique.getPeerByMsgId(cache.msgId);
// msg = await NTQQMsgApi.getMsgsByMsgId(peer?.Peer!,cache.msgId);
// // log('下载完成后的msg', msg);
// cache.path = downloadPath!;
// dbUtil.updateFileCache(cache).then();
// // log('下载完成后的msg', msg);
// // }
// }
// }
// // log('file found', cache);
// const res: GetFileResponse = {
// file: cache.path,
// url: cache.url,
// file_size: cache.size.toString(),
// file_name: cache.name
// };
// if (enableLocalFile2Url) {
// if (!cache.url) {
// try {
// res.base64 = await fs.readFile(cache.path, 'base64');
// } catch (e) {
// throw new Error('文件下载失败. ' + e);
// }
// }
// }
//return res;
}
}

View File

@@ -1,13 +1,15 @@
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FileNapCatOneBotUUID } from '@/common/helper';
const SchemaData = {
type: 'object',
properties: {
group_id: { type: ['string', 'number'] },
start_index: { type: 'number' },
file_count: { type: 'number' },
start_index: { type: ['string', 'number'] },
file_count: { type: ['string', 'number'] },
folder_id: { type: ['string', 'number'] },
},
required: ['group_id', 'start_index', 'file_count'],
} as const satisfies JSONSchema;
@@ -20,15 +22,29 @@ export class GetGroupFileList extends BaseAction<Payload, { FileList: Array<any>
async _handle(payload: Payload) {
const NTQQMsgApi = this.core.apis.MsgApi;
let param = {};
if (payload.folder_id) {
param = {
folderId: payload.folder_id.toString(),
};
}
const ret = await NTQQMsgApi.getGroupFileList(payload.group_id.toString(), {
sortType: 1,
fileCount: payload.file_count,
startIndex: payload.start_index,
fileCount: +payload.file_count,
startIndex: +payload.start_index,
sortOrder: 2,
showOnlinedocFolder: 0,
...param
}).catch((e) => {
return [];
});
ret.forEach((e) => {
let fileModelId = e?.fileInfo?.fileModelId;
if (fileModelId) {
e.fileModelId = fileModelId;
}
e.fileId = FileNapCatOneBotUUID.encodeModelId({ chatType: 2, peerUid: payload.group_id.toString() }, fileModelId);
});
return { FileList: ret };
}
}

View File

@@ -2,7 +2,7 @@ import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import fs from 'fs';
import { join as joinPath } from 'node:path';
import { calculateFileMD5, httpDownload } from '@/common/utils/file';
import { calculateFileMD5, httpDownload } from '@/common/file';
import { randomUUID } from 'crypto';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';

View File

@@ -2,7 +2,7 @@ import BaseAction from '../BaseAction';
import { OB11ForwardMessage } from '@/onebot';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageUnique } from '@/common/utils/message-unique';
import { MessageUnique } from '@/common/message-unique';
const SchemaData = {
type: 'object',
@@ -38,7 +38,7 @@ export class GoCQHTTPGetForwardMsgAction extends BaseAction<Payload, any> {
const resMsg = await this.obContext.apis.MsgApi
.parseMessage(msg);
if (!resMsg) return;
resMsg.message_id = MessageUnique.createMsg({
resMsg.message_id = MessageUnique.createUniqueMsgId({
guildId: '',
chatType: msg.chatType,
peerUid: msg.peerUid,

View File

@@ -1,9 +1,9 @@
import BaseAction from '../BaseAction';
import { OB11Message } from '@/onebot';
import { ActionName } from '../types';
import { ChatType, RawMessage } from '@/core/entities';
import { ChatType } from '@/core/entities';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageUnique } from '@/common/utils/message-unique';
import { MessageUnique } from '@/common/message-unique';
interface Response {
messages: OB11Message[];
@@ -13,7 +13,7 @@ const SchemaData = {
type: 'object',
properties: {
user_id: { type: ['number', 'string'] },
message_seq: { type: 'number' },
message_seq: { type: ['number', 'string'] },
count: { type: ['number', 'string'] },
reverseOrder: { type: ['boolean', 'string'] },
},
@@ -37,21 +37,19 @@ export default class GetFriendMsgHistory extends BaseAction<Payload, Response> {
if (!uid) throw `记录${payload.user_id}不存在`;
const friend = await NTQQFriendApi.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');
//拉取消息
let msgList: RawMessage[];
if (!payload.message_seq || payload.message_seq == 0) {
msgList = (await NTQQMsgApi.getLastestMsgByUids(peer, MsgCount)).msgList;
} else {
const startMsgId = MessageUnique.getMsgIdAndPeerByShortId(payload.message_seq)?.MsgId;
if (!startMsgId) throw `消息${payload.message_seq}不存在`;
msgList = (await NTQQMsgApi.getMsgHistory(peer, startMsgId, MsgCount)).msgList;
}
const startMsgId = hasMessageSeq ? (MessageUnique.getMsgIdAndPeerByShortId(+payload.message_seq!)?.MsgId ?? payload.message_seq!.toString()) : '0';
const msgList = hasMessageSeq ?
(await NTQQMsgApi.getMsgHistory(peer, startMsgId, MsgCount)).msgList : (await NTQQMsgApi.getAioFirstViewLatestMsgs(peer, MsgCount)).msgList;
if (msgList.length === 0) throw `消息${payload.message_seq}不存在`;
//翻转消息
if (isReverseOrder) msgList.reverse();
//转换序号
await Promise.all(msgList.map(async msg => {
msg.id = MessageUnique.createMsg({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId);
msg.id = MessageUnique.createUniqueMsgId({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId);
}));
//转换消息
//烘焙消息
const ob11MsgList = (await Promise.all(
msgList.map(msg => this.obContext.apis.MsgApi.parseMessage(msg)))
).filter(msg => msg !== undefined);

View File

@@ -1,9 +1,9 @@
import BaseAction from '../BaseAction';
import { OB11Message } from '@/onebot';
import { ActionName } from '../types';
import { ChatType, Peer, RawMessage } from '@/core/entities';
import { ChatType, Peer } from '@/core/entities';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageUnique } from '@/common/utils/message-unique';
import { MessageUnique } from '@/common/message-unique';
interface Response {
messages: OB11Message[];
@@ -13,7 +13,7 @@ const SchemaData = {
type: 'object',
properties: {
group_id: { type: ['number', 'string'] },
message_seq: { type: 'number' },
message_seq: { type: ['number', 'string'] },
count: { type: ['number', 'string'] },
reverseOrder: { type: ['boolean', 'string'] },
},
@@ -32,21 +32,19 @@ export default class GoCQHTTPGetGroupMsgHistory extends BaseAction<Payload, Resp
const isReverseOrder = typeof payload.reverseOrder === 'string' ? payload.reverseOrder === 'true' : !!payload.reverseOrder;
const MsgCount = +(payload.count ?? 20);
const peer: Peer = { chatType: ChatType.KCHATTYPEGROUP, peerUid: payload.group_id.toString() };
const hasMessageSeq = !payload.message_seq ? !!payload.message_seq : !(payload.message_seq?.toString() === '' || payload.message_seq?.toString() === '0');
//拉取消息
let msgList: RawMessage[];
if (!payload.message_seq || payload.message_seq == 0) {
msgList = (await NTQQMsgApi.getLastestMsgByUids(peer, MsgCount)).msgList;
} else {
const startMsgId = MessageUnique.getMsgIdAndPeerByShortId(payload.message_seq)?.MsgId;
if (!startMsgId) throw `消息${payload.message_seq}不存在`;
msgList = (await NTQQMsgApi.getMsgHistory(peer, startMsgId, MsgCount)).msgList;
}
const startMsgId = hasMessageSeq ? (MessageUnique.getMsgIdAndPeerByShortId(+payload.message_seq!)?.MsgId ?? payload.message_seq!.toString()) : '0';
const msgList = hasMessageSeq ?
(await NTQQMsgApi.getMsgHistory(peer, startMsgId, MsgCount)).msgList : (await NTQQMsgApi.getAioFirstViewLatestMsgs(peer, MsgCount)).msgList;
if (msgList.length === 0) throw `消息${payload.message_seq}不存在`;
//翻转消息
if (isReverseOrder) msgList.reverse();
//转换序号
await Promise.all(msgList.map(async msg => {
msg.id = MessageUnique.createMsg({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId);
msg.id = MessageUnique.createUniqueMsgId({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId);
}));
//转换消息
//烘焙消息
const ob11MsgList = (await Promise.all(
msgList.map(msg => this.obContext.apis.MsgApi.parseMessage(msg)))
).filter(msg => msg !== undefined);

View File

@@ -1,7 +1,7 @@
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { JSONSchema } from 'json-schema-to-ts';
import { sleep } from '@/common/utils/helper';
import { sleep } from '@/common/helper';
const SchemaData = {
type: 'object',

View File

@@ -1,9 +1,9 @@
import BaseAction from '../BaseAction';
import { OB11User, OB11UserSex } from '@/onebot';
import { OB11Entities } from '@/onebot/helper/entities';
import { OB11Entities } from '@/onebot/entities';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { calcQQLevel } from '@/common/utils/helper';
import { calcQQLevel } from '@/common/helper';
const SchemaData = {
type: 'object',

View File

@@ -13,7 +13,7 @@ export class GoCQHTTPHandleQuickAction extends BaseAction<Payload, null> {
async _handle(payload: Payload): Promise<null> {
this.obContext.apis.QuickActionApi
.handleQuickOperation(payload.context, payload.operation)
.catch(this.core.context.logger.logError);
.catch(this.core.context.logger.logError.bind(this.core.context.logger));
return null;
}
}

View File

@@ -1,4 +1,4 @@
import { checkFileReceived, uri2local } from '@/common/utils/file';
import { checkFileReceived, uri2local } from '@/common/file';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { unlink } from 'node:fs';
@@ -11,7 +11,7 @@ const SchemaData = {
content: { type: 'string' },
image: { type: 'string' },
pinned: { type: ['number', 'string'] },
confirmRequired: { type: ['number', 'string'] },
confirm_required: { type: ['number', 'string'] },
},
required: ['group_id', 'content'],
} as const satisfies JSONSchema;
@@ -27,7 +27,6 @@ export class SendGroupNotice extends BaseAction<Payload, null> {
if (payload.image) {
//公告图逻辑
const {
errMsg,
path,
isLocal,
success,
@@ -49,12 +48,12 @@ export class SendGroupNotice extends BaseAction<Payload, null> {
}
UploadImage = ImageUploadResult.picInfo;
}
let Notice_Pinned = +(payload.pinned ?? 0);
let Notice_confirmRequired = +(payload.confirmRequired ?? 0);
const PublishGroupBulletinResult = await NTQQGroupApi.publishGroupBulletin(payload.group_id.toString(), payload.content, UploadImage, Notice_Pinned, Notice_confirmRequired);
const noticePinned = +(payload.pinned ?? 0);
const noticeConfirmRequired = +(payload.confirm_required ?? 0);
const publishGroupBulletinResult = await NTQQGroupApi.publishGroupBulletin(payload.group_id.toString(), payload.content, UploadImage, noticePinned, noticeConfirmRequired);
if (PublishGroupBulletinResult.result != 0) {
throw `设置群公告失败,错误信息:${PublishGroupBulletinResult.errMsg}`;
if (publishGroupBulletinResult.result != 0) {
throw `设置群公告失败,错误信息:${publishGroupBulletinResult.errMsg}`;
}
return null;
}

View File

@@ -1,7 +1,7 @@
import BaseAction from '../BaseAction';
import { ActionName, BaseCheckResult } from '../types';
import * as fs from 'node:fs';
import { checkFileReceived, uri2local } from '@/common/utils/file';
import { checkFileReceived, uri2local } from '@/common/file';
interface Payload {
file: string,
@@ -53,6 +53,5 @@ export default class SetGroupPortrait extends BaseAction<Payload, any> {
}
throw `头像${payload.file}设置失败,无法获取头像,文件可能不存在`;
}
return null;
}
}

View File

@@ -2,7 +2,7 @@ import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { ChatType } from '@/core/entities';
import fs from 'fs';
import { uri2local } from '@/common/utils/file';
import { uri2local } from '@/common/file';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
const SchemaData = {

View File

@@ -2,7 +2,7 @@ import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { ChatType, Peer, SendFileElement } from '@/core/entities';
import fs from 'fs';
import { uri2local } from '@/common/utils/file';
import { uri2local } from '@/common/file';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
const SchemaData = {

View File

@@ -1,7 +1,7 @@
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageUnique } from '@/common/utils/message-unique';
import { MessageUnique } from '@/common/message-unique';
const SchemaData = {
type: 'object',
@@ -20,7 +20,13 @@ export default class DelEssenceMsg extends BaseAction<Payload, any> {
async _handle(payload: Payload): Promise<any> {
const NTQQGroupApi = this.core.apis.GroupApi;
const msg = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id);
if (!msg) throw new Error('msg not found');
const NTQQWebApi = this.core.apis.WebApi;
if (!msg) {
const data = NTQQGroupApi.essenceLRU.getValue(+payload.message_id);
if(!data) throw new Error('消息不存在');
const { msg_seq, msg_random, group_id } = JSON.parse(data) as { msg_seq: string, msg_random: string, group_id: string };
return await NTQQGroupApi.removeGroupEssenceBySeq(group_id, msg_seq, msg_random);
}
return await NTQQGroupApi.removeGroupEssence(
msg.Peer.peerUid,
msg.MsgId,

View File

@@ -1,29 +1,99 @@
import { GroupEssenceMsgRet } from '@/core';
import { ChatType, Peer } from '@/core';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageUnique } from '@/common/message-unique';
import crypto from 'crypto';
const SchemaData = {
type: 'object',
properties: {
group_id: { type: ['number', 'string'] },
pages: { type: ['number', 'string'] },
group_id: { type: ['number', 'string'] }
},
required: ['group_id'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class GetGroupEssence extends BaseAction<Payload, GroupEssenceMsgRet> {
export class GetGroupEssence extends BaseAction<Payload, any> {
actionName = ActionName.GoCQHTTP_GetEssenceMsg;
payloadSchema = SchemaData;
private async msgSeqToMsgId(peer: Peer, msgSeq: string, msgRandom: string) {
const replyMsgList = (await this.core.apis.MsgApi.getMsgsBySeqAndCount(peer, msgSeq, 1, true, true)).msgList.find((msg) => msg.msgSeq === msgSeq && msg.msgRandom === msgRandom);
if (!replyMsgList) {
return undefined;
}
return {
id: MessageUnique.createUniqueMsgId(peer, replyMsgList.msgId),
msg: replyMsgList
};
}
async _handle(payload: Payload) {
const NTQQWebApi = this.core.apis.WebApi;
const ret = await NTQQWebApi.getGroupEssenceMsg(payload.group_id.toString(), (+(payload.pages ?? 0)).toString());
if (!ret) {
const NTQQGroupApi = this.core.apis.GroupApi;
const msglist = (await NTQQWebApi.getGroupEssenceMsgAll(payload.group_id.toString())).flatMap((e) => e.data.msg_list);
if (!msglist) {
throw new Error('获取失败');
}
return ret;
return await Promise.all(msglist.map(async (msg) => {
const msgOriginData = await this.msgSeqToMsgId({
chatType: ChatType.KCHATTYPEGROUP,
peerUid: payload.group_id.toString(),
}, msg.msg_seq.toString(), msg.msg_random.toString());
if (msgOriginData) {
const { id: message_id, msg: rawMessage } = msgOriginData;
return {
msg_seq: msg.msg_seq,
msg_random: msg.msg_random,
sender_id: +msg.sender_uin,
sender_nick: msg.sender_nick,
operator_id: +msg.add_digest_uin,
operator_nick: msg.add_digest_nick,
message_id: message_id,
operator_time: msg.add_digest_time,
content: (await this.obContext.apis.MsgApi.parseMessage(rawMessage, 'array'))?.message
};
}
const msgTempData = JSON.stringify({
msg_seq: msg.msg_seq.toString(),
msg_random: msg.msg_random.toString(),
group_id: payload.group_id.toString(),
});
const hash = crypto.createHash('md5').update(msgTempData).digest();
//设置第一个bit为0 保证shortId为正数
hash[0] &= 0x7f;
const shortId = hash.readInt32BE(0);
NTQQGroupApi.essenceLRU.set(shortId, msgTempData);
return {
msg_seq: msg.msg_seq,
msg_random: msg.msg_random,
sender_id: +msg.sender_uin,
sender_nick: msg.sender_nick,
operator_id: +msg.add_digest_uin,
operator_nick: msg.add_digest_nick,
message_id: shortId,
operator_time: msg.add_digest_time,
content: msg.msg_content.map((msg) => {
if (msg.msg_type === 1) {
return {
type: 'text',
data: {
text: msg?.text
}
};
} else if (msg.msg_type === 3) {
return {
type: 'image',
data: {
url: msg?.image_url,
}
};
}
return undefined;
}).filter(e => e !== undefined),
};
}));
}
}

View File

@@ -0,0 +1,39 @@
import { GroupNotifyMsgStatus } from '@/core';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
const SchemaData = {
type: 'object',
properties: {
group_id: { type: ['number', 'string'] },
},
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class GetGroupIgnoredNotifies extends BaseAction<void, any> {
actionName = ActionName.GetGroupIgnoredNotifies;
async _handle(payload: void) {
const NTQQUserApi = this.core.apis.UserApi;
const NTQQGroupApi = this.core.apis.GroupApi;
const ignoredNotifies = await NTQQGroupApi.getSingleScreenNotifies(true, 10);
const retData: any = {
join_requests: await Promise.all(
ignoredNotifies
.filter(notify => notify.type === 7)
.map(async SSNotify => ({
request_id: SSNotify.seq,
requester_uin: await NTQQUserApi.getUinByUidV2(SSNotify.user1?.uid),
requester_nick: SSNotify.user1?.nickName,
group_id: SSNotify.group?.groupCode,
group_name: SSNotify.group?.groupName,
checked: SSNotify.status !== GroupNotifyMsgStatus.KUNHANDLE,
actor: await NTQQUserApi.getUinByUidV2(SSNotify.user2?.uid) || 0,
}))),
};
return retData;
}
}

View File

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

View File

@@ -1,5 +1,5 @@
import { OB11Group } from '@/onebot';
import { OB11Entities } from '@/onebot/helper/entities';
import { OB11Entities } from '@/onebot/entities';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { Group } from '@/core/entities';

View File

@@ -1,5 +1,5 @@
import { OB11GroupMember } from '@/onebot';
import { OB11Entities } from '@/onebot/helper/entities';
import { OB11Entities } from '@/onebot/entities';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';

View File

@@ -1,5 +1,5 @@
import { OB11GroupMember } from '@/onebot';
import { OB11Entities } from '@/onebot/helper/entities';
import { OB11Entities } from '@/onebot/entities';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
@@ -22,7 +22,7 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
async _handle(payload: Payload) {
const NTQQGroupApi = this.core.apis.GroupApi;
const NTQQWebApi = this.core.apis.WebApi;
const groupMembers = await NTQQGroupApi.getGroupMembers(payload.group_id.toString());
const groupMembers = await NTQQGroupApi.getGroupMembersV2(payload.group_id.toString());
const groupMembersArr = Array.from(groupMembers.values());
let _groupMembers = groupMembersArr.map(item => {

View File

@@ -1,50 +0,0 @@
import { GroupNotifyMsgStatus } from '@/core';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
const SchemaData = {
type: 'object',
properties: {
group_id: { type: ['number', 'string'] },
},
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class GetGroupSystemMsg extends BaseAction<void, any> {
actionName = ActionName.GetGroupSystemMsg;
async _handle(payload: void) {
const NTQQUserApi = this.core.apis.UserApi;
const NTQQGroupApi = this.core.apis.GroupApi;
// 默认10条 该api未完整实现 包括响应数据规范化 类型规范化
const SingleScreenNotifies = await NTQQGroupApi.getSingleScreenNotifies(10);
const retData: any = { InvitedRequest: [], join_requests: [] };
for (const SSNotify of SingleScreenNotifies) {
if (SSNotify.type == 1) {
retData.InvitedRequest.push({
request_id: SSNotify.seq,
invitor_uin: await NTQQUserApi.getUinByUidV2(SSNotify.user1?.uid),
invitor_nick: SSNotify.user1?.nickName,
group_id: SSNotify.group?.groupCode,
group_name: SSNotify.group?.groupName,
checked: SSNotify.status === GroupNotifyMsgStatus.KUNHANDLE ? false : true,
actor: await NTQQUserApi.getUinByUidV2(SSNotify.user2?.uid) || 0,
});
} else if (SSNotify.type == 7) {
retData.join_requests.push({
request_id: SSNotify.seq,
requester_uin: await NTQQUserApi.getUinByUidV2(SSNotify.user1?.uid),
requester_nick: SSNotify.user1?.nickName,
group_id: SSNotify.group?.groupCode,
group_name: SSNotify.group?.groupName,
checked: SSNotify.status === GroupNotifyMsgStatus.KUNHANDLE ? false : true,
actor: await NTQQUserApi.getUinByUidV2(SSNotify.user2?.uid) || 0,
});
}
}
return retData;
}
}

View File

@@ -1,7 +1,7 @@
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageUnique } from '@/common/utils/message-unique';
import { MessageUnique } from '@/common/message-unique';
const SchemaData = {
type: 'object',

View File

@@ -52,7 +52,7 @@ import { GetFriendWithCategory } from './extends/GetFriendWithCategory';
import { SendGroupNotice } from './go-cqhttp/SendGroupNotice';
import { GetGroupHonorInfo } from './go-cqhttp/GetGroupHonorInfo';
import { GoCQHTTPHandleQuickAction } from './go-cqhttp/QuickAction';
import { GetGroupSystemMsg } from './group/GetGroupSystemMsg';
import { GetGroupIgnoredNotifies } from './group/GetGroupIgnoredNotifies';
import { GetOnlineClient } from './go-cqhttp/GetOnlineClient';
import { IOCRImage, OCRImage } from './extends/OCRImage';
import { GetGroupFileCount } from './file/GetGroupFileCount';
@@ -81,11 +81,13 @@ import SetModelShow from './go-cqhttp/SetModelShow';
import { SetInputStatus } from './extends/SetInputStatus';
import { GetCSRF } from './system/GetCSRF';
import { DelGroupNotice } from './group/DelGroupNotice';
import { GetGroupInfoEx } from './extends/GetGroupInfoEx';
export type ActionMap = Map<string, BaseAction<any, any>>;
export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCore): ActionMap {
const actionHandlers = [
new GetGroupInfoEx(obContext, core),
new FetchEmojiLike(obContext, core),
new GetFile(obContext, core),
new SetQQProfile(obContext, core),
@@ -157,7 +159,7 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
new GoCQHTTPGetForwardMsgAction(obContext, core),
new GetFriendMsgHistory(obContext, core),
new GoCQHTTPHandleQuickAction(obContext, core),
new GetGroupSystemMsg(obContext, core),
new GetGroupIgnoredNotifies(obContext, core),
new DelEssenceMsg(obContext, core),
new SetEssenceMsg(obContext, core),
new GetRecentContact(obContext, core),

View File

@@ -1,8 +1,7 @@
import { ActionName } from '../types';
import BaseAction from '../BaseAction';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageUnique } from '@/common/utils/message-unique';
import { NodeIKernelMsgListener } from '@/core';
import { MessageUnique } from '@/common/message-unique';
const SchemaData = {
type: 'object',

View File

@@ -2,7 +2,7 @@ import BaseAction from '../BaseAction';
import { ChatType, Peer } from '@/core/entities';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageUnique } from '@/common/utils/message-unique';
import { MessageUnique } from '@/common/message-unique';
const SchemaData = {
type: 'object',

View File

@@ -2,7 +2,7 @@ import { OB11Message } from '@/onebot';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageUnique } from '@/common/utils/message-unique';
import { MessageUnique } from '@/common/message-unique';
export type ReturnDataType = OB11Message
@@ -39,7 +39,7 @@ class GetMsg extends BaseAction<Payload, OB11Message> {
const retMsg = await this.obContext.apis.MsgApi.parseMessage(msg.msgList[0], 'array');
if (!retMsg) throw Error('消息为空');
try {
retMsg.message_id = MessageUnique.createMsg(peer, msg.msgList[0].msgId)!;
retMsg.message_id = MessageUnique.createUniqueMsgId(peer, msg.msgList[0].msgId)!;
retMsg.message_seq = retMsg.message_id;
retMsg.real_id = retMsg.message_id;
} catch (e) {

View File

@@ -6,8 +6,8 @@ import {
OB11PostSendMsg,
} from '@/onebot/types';
import { ActionName, BaseCheckResult } from '@/onebot/action/types';
import { decodeCQCode } from '@/onebot/helper/cqcode';
import { MessageUnique } from '@/common/utils/message-unique';
import { decodeCQCode } from '@/onebot/cqcode';
import { MessageUnique } from '@/common/message-unique';
import { ChatType, ElementType, NapCatCore, Peer, RawMessage, SendMessageElement } from '@/core';
import BaseAction from '../BaseAction';
@@ -30,7 +30,7 @@ export function normalize(message: OB11MessageMixType, autoEscape = false): OB11
) : Array.isArray(message) ? message : [message];
}
async function createContext(core: NapCatCore, payload: OB11PostSendMsg, contextMode: ContextMode): Promise<Peer> {
export async function createContext(core: NapCatCore, payload: OB11PostSendMsg, contextMode: ContextMode): Promise<Peer> {
// This function determines the type of message by the existence of user_id / group_id,
// not message_type.
// This redundant design of Ob11 here should be blamed.
@@ -98,11 +98,6 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
message: '转发消息不能和普通消息混在一起发送,转发需要保证message只有type为node的元素',
};
}
if (payload.user_id && payload.message_type !== 'group') {
// const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
// const isBuddy = await NTQQFriendApi.isBuddy(uid!);
// if (!isBuddy) { }
}
return { valid: true };
}
@@ -119,7 +114,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
if (getSpecialMsgNum(payload, OB11MessageDataType.node)) {
const returnMsg = await this.handleForwardedNodes(peer, messages as OB11MessageNode[]);
if (returnMsg) {
const msgShortId = MessageUnique.createMsg({
const msgShortId = MessageUnique.createUniqueMsgId({
guildId: '',
peerUid: peer.peerUid,
chatType: peer.chatType,
@@ -175,7 +170,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
const nodeMsg = await this.handleForwardedNodes(selfPeer, OB11Data.filter(e => e.type === OB11MessageDataType.node));
if (nodeMsg) {
nodeMsgIds.push(nodeMsg.msgId);
MessageUnique.createMsg(selfPeer, nodeMsg.msgId);
MessageUnique.createUniqueMsgId(selfPeer, nodeMsg.msgId);
}
//完成子卡片生成跳过后续
continue;
@@ -193,7 +188,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
(await Promise.allSettled(MsgNodeList)).map((result) => {
if (result.status === 'fulfilled' && result.value) {
nodeMsgIds.push(result.value.msgId);
MessageUnique.createMsg(selfPeer, result.value.msgId);
MessageUnique.createUniqueMsgId(selfPeer, result.value.msgId);
}
});
} catch (e) {

View File

@@ -1,7 +1,7 @@
import { ActionName } from '../types';
import BaseAction from '../BaseAction';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageUnique } from '@/common/utils/message-unique';
import { MessageUnique } from '@/common/message-unique';
const SchemaData = {
type: 'object',

View File

@@ -1,5 +1,5 @@
import { OB11User } from '@/onebot';
import { OB11Entities } from '@/onebot/helper/entities';
import { OB11Entities } from '@/onebot/entities';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';

View File

@@ -1,6 +1,7 @@
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { napcat_version } from '@/common/framework/napcat';
import { napCatVersion } from '@/common/version';
export default class GetVersionInfo extends BaseAction<any, any> {
actionName = ActionName.GetVersionInfo;
@@ -9,7 +10,7 @@ export default class GetVersionInfo extends BaseAction<any, any> {
return {
app_name: 'NapCat.Onebot',
protocol_version: 'v11',
app_version: napcat_version,
app_version: napCatVersion,
};
}
}

View File

@@ -87,7 +87,7 @@ export enum ActionName {
GoCQHTTP_GetGroupMsgHistory = 'get_group_msg_history',
GoCQHTTP_GetForwardMsg = 'get_forward_msg',
GetFriendMsgHistory = 'get_friend_msg_history',
GetGroupSystemMsg = 'get_group_system_msg',
GetGroupIgnoredNotifies = 'get_group_ignored_notifies',
GetOnlineClient = 'get_online_clients',
OCRImage = 'ocr_image',
IOCRImage = '.ocr_image',
@@ -110,4 +110,5 @@ export enum ActionName {
SetInputStatus = 'set_input_status',
GetCSRF = 'get_csrf_token',
DelGroupNotice = '_del_group_notice',
GetGroupInfoEx = "get_group_info_ex"
}

View File

@@ -1,5 +1,5 @@
import { OB11User } from '@/onebot';
import { OB11Entities } from '@/onebot/helper/entities';
import { OB11Entities } from '@/onebot/entities';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';

View File

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

View File

@@ -1,11 +1,23 @@
import { ChatType, GrayTipElement, NapCatCore } from '@/core';
import {
ChatType,
GrayTipElement,
NapCatCore,
NTGrayTipElementSubTypeV2,
RawMessage,
TipGroupElementType,
} from '@/core';
import { NapCatOneBot11Adapter } from '@/onebot';
import { OB11GroupBanEvent } from '../event/notice/OB11GroupBanEvent';
import { OB11GroupIncreaseEvent } from '../event/notice/OB11GroupIncreaseEvent';
import { OB11GroupDecreaseEvent } from '../event/notice/OB11GroupDecreaseEvent';
import fastXmlParser from 'fast-xml-parser';
import { OB11GroupMsgEmojiLikeEvent } from '../event/notice/OB11MsgEmojiLikeEvent';
import { MessageUnique } from '@/common/utils/message-unique';
import { MessageUnique } from '@/common/message-unique';
import { OB11GroupCardEvent } from '@/onebot/event/notice/OB11GroupCardEvent';
import { OB11GroupUploadNoticeEvent } from '@/onebot/event/notice/OB11GroupUploadNoticeEvent';
import { OB11GroupPokeEvent } from '@/onebot/event/notice/OB11PokeEvent';
import { OB11GroupEssenceEvent } from '@/onebot/event/notice/OB11GroupEssenceEvent';
import { OB11GroupTitleEvent } from '@/onebot/event/notice/OB11GroupTitleEvent';
export class OneBotGroupApi {
obContext: NapCatOneBot11Adapter;
@@ -16,6 +28,130 @@ export class OneBotGroupApi {
this.core = core;
}
async parseGroupEvent(msg: RawMessage) {
const NTQQGroupApi = this.core.apis.GroupApi;
const NTQQUserApi = this.core.apis.UserApi;
const NTQQMsgApi = this.core.apis.MsgApi;
const logger = this.core.context.logger;
if (msg.chatType !== ChatType.KCHATTYPEGROUP) {
return;
}
//log("group msg", msg);
if (msg.senderUin && msg.senderUin !== '0') {
const member = await NTQQGroupApi.getGroupMember(msg.peerUid, msg.senderUin);
if (member && member.cardName !== 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;
}
}
for (const element of msg.elements) {
if (element.grayTipElement && 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);
if (MemberIncreaseEvent) return MemberIncreaseEvent;
} else if (groupElement.type === TipGroupElementType.ban) {
const BanEvent = await this.obContext.apis.GroupApi.parseGroupBanEvent(msg.peerUid, element.grayTipElement);
if (BanEvent) return BanEvent;
} else if (groupElement.type == TipGroupElementType.kicked) {
NTQQGroupApi.quitGroup(msg.peerUid).then();
try {
const KickEvent = await this.obContext.apis.GroupApi.parseGroupKickEvent(msg.peerUid, element.grayTipElement);
if (KickEvent) return KickEvent;
} catch (e) {
return new OB11GroupDecreaseEvent(
this.core,
parseInt(msg.peerUid),
parseInt(this.core.selfInfo.uin),
0,
'leave',
);
}
}
} else if (element.fileElement) {
return new OB11GroupUploadNoticeEvent(
this.core,
parseInt(msg.peerUid), parseInt(msg.senderUin || ''),
{
id: element.fileElement.fileUuid!,
name: element.fileElement.fileName,
size: parseInt(element.fileElement.fileSize),
busid: element.fileElement.fileBizId || 0,
},
);
}
if (element.grayTipElement) {
if (element.grayTipElement.xmlElement?.templId === '10382') {
const emojiLikeEvent = await this.obContext.apis.GroupApi.parseGroupEmojiLikeEventByGrayTip(msg.peerUid, element.grayTipElement);
if (emojiLikeEvent) return emojiLikeEvent;
}
if (element.grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_XMLMSG) {
const GroupIncreaseEvent = await this.obContext.apis.GroupApi.parseGroupIncreaseEvent(msg.peerUid, element.grayTipElement);
if (GroupIncreaseEvent) return GroupIncreaseEvent;
}
//代码歧义 GrayTipElementSubType.MEMBER_NEW_TITLE
else if (element.grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_JSON) {
const json = JSON.parse(element.grayTipElement.jsonGrayTipElement.jsonStr);
if (element.grayTipElement.jsonGrayTipElement.busiId == 1061) {
//判断业务类型
//Poke事件
const pokedetail: any[] = json.items;
//筛选item带有uid的元素
const poke_uid = pokedetail.filter(item => item.uid);
if (poke_uid.length == 2) {
return new OB11GroupPokeEvent(
this.core,
parseInt(msg.peerUid),
parseInt((await NTQQUserApi.getUinByUidV2(poke_uid[0].uid))!),
parseInt((await NTQQUserApi.getUinByUidV2(poke_uid[1].uid))!),
pokedetail,
);
}
}
if (element.grayTipElement.jsonGrayTipElement.busiId == 2401) {
const searchParams = new URL(json.items[0].jp).searchParams;
const msgSeq = searchParams.get('msgSeq')!;
const Group = searchParams.get('groupCode');
if (!Group) return;
// const businessId = searchParams.get('businessid');
const Peer = {
guildId: '',
chatType: ChatType.KCHATTYPEGROUP,
peerUid: Group,
};
const msgData = await NTQQMsgApi.getMsgsBySeqAndCount(Peer, msgSeq.toString(), 1, true, true);
let msgList = (await this.core.apis.WebApi.getGroupEssenceMsgAll(Group)).flatMap((e) => e.data.msg_list);
let realMsg = msgList.find((e) => e.msg_seq.toString() == msgSeq);
return new OB11GroupEssenceEvent(
this.core,
parseInt(msg.peerUid),
MessageUnique.getShortIdByMsgId(msgData.msgList[0].msgId)!,
parseInt(msgData.msgList[0].senderUin),
parseInt(realMsg?.add_digest_uin ?? '0'),
);
// 获取MsgSeq+Peer可获取具体消息
}
if (element.grayTipElement.jsonGrayTipElement.busiId == 2407) {
//下面得改 上面也是错的grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE
const memberUin = json.items[1].param[0];
const title = json.items[3].txt;
logger.logDebug('收到群成员新头衔消息', json);
return new OB11GroupTitleEvent(
this.core,
parseInt(msg.peerUid),
parseInt(memberUin),
title,
);
}
}
}
}
}
async parseGroupBanEvent(GroupCode: string, grayTipElement: GrayTipElement) {
const groupElement = grayTipElement?.groupElement;
const NTQQGroupApi = this.core.apis.GroupApi;
@@ -78,7 +214,7 @@ export class OneBotGroupApi {
const NTQQGroupApi = this.core.apis.GroupApi;
const groupElement = grayTipElement?.groupElement;
if (!groupElement) return undefined;
const member = await NTQQGroupApi.getGroupMemberV2(GroupCode, groupElement.memberUid);
const member = await NTQQGroupApi.getGroupMember(GroupCode, groupElement.memberUid);
const memberUin = member?.uin;
const adminMember = await NTQQGroupApi.getGroupMember(GroupCode, groupElement.adminUid);
if (memberUin) {
@@ -89,8 +225,9 @@ export class OneBotGroupApi {
parseInt(memberUin),
parseInt(operatorUin),
);
} else {
return undefined;
}
return undefined;
}
async parseGroupKickEvent(GroupCode: string, grayTipElement: GrayTipElement) {
@@ -111,42 +248,53 @@ export class OneBotGroupApi {
return undefined;
}
async parseGroupEmjioLikeEvent(GroupCode: string, grayTipElement: GrayTipElement) {
const NTQQMsgApi = this.core.apis.MsgApi;
async parseGroupEmojiLikeEventByGrayTip(
groupCode: string,
grayTipElement: GrayTipElement
) {
const emojiLikeData = new fastXmlParser.XMLParser({
ignoreAttributes: false,
attributeNamePrefix: '',
}).parse(grayTipElement.xmlElement.content);
this.core.context.logger.logDebug('收到表情回应我的消息', emojiLikeData);
try {
const senderUin = emojiLikeData.gtip.qq.jp;
const msgSeq = emojiLikeData.gtip.url.msgseq;
const emojiId = emojiLikeData.gtip.face.id;
const peer = {
chatType: ChatType.KCHATTYPEGROUP,
guildId: '',
peerUid: GroupCode,
};
const replyMsgList = (await NTQQMsgApi.getMsgExBySeq(peer, msgSeq)).msgList;
if (replyMsgList.length < 1) {
return;
}
const replyMsg = replyMsgList.filter(e => e.msgSeq == msgSeq).sort((a, b) => parseInt(a.msgTime) - parseInt(b.msgTime))[0];
//console.log("表情回应消息长度检测", msgSeq, replyMsg.elements);
if (!replyMsg) throw new Error('找不到回应消息');
return new OB11GroupMsgEmojiLikeEvent(
this.core,
parseInt(GroupCode),
parseInt(senderUin),
MessageUnique.getShortIdByMsgId(replyMsg.msgId)!,
[{
emoji_id: emojiId,
count: 1,
}],
);
} catch (e: any) {
this.core.context.logger.logError('解析表情回应消息失败', e.stack);
const senderUin = emojiLikeData.gtip.qq.jp;
const msgSeq = emojiLikeData.gtip.url.msgseq;
const emojiId = emojiLikeData.gtip.face.id;
return await this.createGroupEmojiLikeEvent(groupCode, senderUin, msgSeq, emojiId);
}
async createGroupEmojiLikeEvent(
groupCode: string,
senderUin: string,
msgSeq: string,
emojiId: string,
) {
const peer = {
chatType: ChatType.KCHATTYPEGROUP,
guildId: '',
peerUid: groupCode,
};
const replyMsgList = (await this.core.apis.MsgApi.getMsgExBySeq(peer, msgSeq)).msgList;
if (replyMsgList.length < 1) {
return;
}
return undefined;
const replyMsg = replyMsgList
.filter(e => e.msgSeq == msgSeq)
.sort((a, b) => parseInt(a.msgTime) - parseInt(b.msgTime))[0];
//console.log("表情回应消息长度检测", msgSeq, replyMsg.elements);
if (!replyMsg) {
this.core.context.logger.logError('解析表情回应消息失败: 未找到回应消息');
return undefined;
}
return new OB11GroupMsgEmojiLikeEvent(
this.core,
parseInt(groupCode),
parseInt(senderUin),
MessageUnique.getShortIdByMsgId(replyMsg.msgId)!,
[{
emoji_id: emojiId,
count: 1,
}],
);
}
}

View File

@@ -1,5 +1,5 @@
import { UUIDConverter } from '@/common/utils/helper';
import { MessageUnique } from '@/common/utils/message-unique';
import { FileNapCatOneBotUUID } from '@/common/helper';
import { MessageUnique } from '@/common/message-unique';
import {
AtType,
ChatType,
@@ -9,7 +9,7 @@ import {
FaceType,
IdMusicSignPostData,
MessageElement,
NapCatCore,
NapCatCore, NTGrayTipElementSubTypeV2,
Peer,
RawMessage,
SendMessageElement,
@@ -24,13 +24,14 @@ import {
OB11MessageFileBase,
OB11MessageForward,
} from '@/onebot';
import { OB11Entities } from '../helper';
import { OB11Entities } from '@/onebot/entities';
import { EventType } from '@/onebot/event/OB11BaseEvent';
import { encodeCQCode } from '@/onebot/helper/cqcode';
import { uri2local } from '@/common/utils/file';
import { RequestUtil } from '@/common/utils/request';
import { encodeCQCode } from '@/onebot/cqcode';
import { uri2local } from '@/common/file';
import { RequestUtil } from '@/common/request';
import fs from 'node:fs';
import fsPromise from 'node:fs/promises';
import { OB11FriendAddNoticeEvent } from '@/onebot/event/notice/OB11FriendAddNoticeEvent';
type RawToOb11Converters = {
[Key in keyof MessageElement as Key extends `${string}Element` ? Key : never]: (
@@ -97,14 +98,19 @@ export class OneBotMsgApi {
}
},
picElement: async (element, msg) => {
picElement: async (element, msg, elementWrapper) => {
try {
const peer = {
chatType: msg.chatType,
peerUid: msg.peerUid,
guildId: '',
};
return {
type: OB11MessageDataType.image,
data: {
file: element.fileName,
sub_type: element.picSubType,
file_id: UUIDConverter.encode(msg.peerUin, msg.msgId),
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId),
url: await this.core.apis.FileApi.getImageUrl(element),
file_size: element.fileSize,
},
@@ -116,6 +122,11 @@ export class OneBotMsgApi {
},
fileElement: async (element, msg, elementWrapper) => {
const peer = {
chatType: msg.chatType,
peerUid: msg.peerUid,
guildId: '',
};
await this.core.apis.FileApi.addFileCache(
{
peerUid: msg.peerUid,
@@ -136,7 +147,7 @@ export class OneBotMsgApi {
file: element.fileName,
path: element.filePath,
url: element.filePath,
file_id: UUIDConverter.encode(msg.peerUin, msg.msgId),
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId),
file_size: element.fileSize,
},
};
@@ -183,11 +194,16 @@ export class OneBotMsgApi {
'0',
'marketface',
);
const peer = {
chatType: msg.chatType,
peerUid: msg.peerUid,
guildId: '',
};
return {
type: OB11MessageDataType.image,
data: {
file: 'marketface',
file_id: UUIDConverter.encode(msg.peerUin, msg.msgId),
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId),
path: elementWrapper.elementId,
url: elementWrapper.elementId,
},
@@ -206,13 +222,21 @@ export class OneBotMsgApi {
this.core.context.logger.logError('获取不到引用的消息', element.replayMsgSeq);
return null;
}
const createReplyData = (msgId: string): OB11MessageData => ({
type: OB11MessageDataType.reply,
data: {
id: MessageUnique.createUniqueMsgId(peer, msgId).toString(),
},
});
if (records.peerUin === '284840486') {
return createReplyData(records.msgId);
}
let replyMsg: RawMessage | undefined;
// Attempt 1
replyMsg = (await NTQQMsgApi.getMsgsBySeqAndCount({
peerUid: msg.peerUid,
guildId: '',
chatType: msg.chatType,
}, element.replayMsgSeq, 1, true, true))
replyMsg = (await NTQQMsgApi.getMsgsBySeqAndCount(peer,element.replayMsgSeq, 1, true, true))
.msgList
.find(msg => msg.msgRandom === records.msgRandom);
@@ -220,7 +244,7 @@ export class OneBotMsgApi {
// Attempt 2
replyMsg = (await NTQQMsgApi.getSingleMsg(peer, element.replayMsgSeq)).msgList[0];
if ((!replyMsg || records.msgRandom !== replyMsg.msgRandom) && msg.peerUin !== '284840486') {
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
// Attempt 3
const replyMsgList = (await NTQQMsgApi.getMsgExBySeq(peer, records.msgSeq)).msgList;
if (replyMsgList.length < 1) {
@@ -232,21 +256,16 @@ export class OneBotMsgApi {
}
}
return {
type: OB11MessageDataType.reply,
data: {
id: MessageUnique.createMsg({
peerUid: msg.peerUid,
guildId: '',
chatType: msg.chatType,
}, replyMsg.msgId).toString(),
},
};
return createReplyData(replyMsg.msgId);
},
videoElement: async (element, msg, elementWrapper) => {
const NTQQFileApi = this.core.apis.FileApi;
const peer = {
chatType: msg.chatType,
peerUid: msg.peerUid,
guildId: '',
};
//读取视频链接并兜底
let videoUrlWrappers: Awaited<ReturnType<typeof NTQQFileApi.getVideoUrl>> | undefined;
@@ -301,13 +320,18 @@ export class OneBotMsgApi {
file: element.fileName,
path: videoDownUrl,
url: videoDownUrl,
file_id: UUIDConverter.encode(msg.peerUin, msg.msgId),
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId),
file_size: element.fileSize,
},
};
},
pttElement: async (element, msg, elementWrapper) => {
const peer = {
chatType: msg.chatType,
peerUid: msg.peerUid,
guildId: '',
};
await this.core.apis.FileApi.addFileCache(
{
peerUid: msg.peerUid,
@@ -327,7 +351,7 @@ export class OneBotMsgApi {
data: {
file: element.fileName,
path: element.filePath,
file_id: UUIDConverter.encode(msg.peerUin, msg.msgId),
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId),
file_size: element.fileSize,
},
};
@@ -364,7 +388,7 @@ export class OneBotMsgApi {
async multiMsgItem => {
multiMsgItem.parentMsgPeer = parentMsgPeer;
multiMsgItem.parentMsgIdList = msg.parentMsgIdList;
multiMsgItem.id = MessageUnique.createMsg(parentMsgPeer, multiMsgItem.msgId); //该ID仅用查看 无法调用
multiMsgItem.id = MessageUnique.createUniqueMsgId(parentMsgPeer, multiMsgItem.msgId); //该ID仅用查看 无法调用
return await this.parseMessage(multiMsgItem);
},
))).filter(item => item !== undefined),
@@ -656,6 +680,28 @@ export class OneBotMsgApi {
this.core = core;
}
async parsePrivateMsgEvent(msg: RawMessage) {
if (msg.chatType !== ChatType.KCHATTYPEC2C) {
return;
}
for (const element of msg.elements) {
if (element.grayTipElement) {
if (element.grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_JSON) {
if (element.grayTipElement.jsonGrayTipElement.busiId == 1061) {
const PokeEvent = await this.obContext.apis.FriendApi.parsePrivatePokeEvent(element.grayTipElement);
if (PokeEvent) return PokeEvent;
}
}
if (element.grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_XMLMSG) {
//好友添加成功事件
if (element.grayTipElement.xmlElement.templId === '10229' && msg.peerUin !== '') {
return new OB11FriendAddNoticeEvent(this.core, parseInt(msg.peerUin));
}
}
}
}
}
async parseMessage(
msg: RawMessage,
messagePostFormat: string = this.obContext.configLoader.configData.messagePostFormat,
@@ -799,16 +845,12 @@ export class OneBotMsgApi {
this.core.context.logger.logError('发送消息计算预计时间异常', e);
}
const returnMsg = await this.core.apis.MsgApi.sendMsg(peer, sendElements, waitComplete, timeout);
try {
returnMsg!.id = MessageUnique.createMsg({
chatType: peer.chatType,
guildId: '',
peerUid: peer.peerUid,
}, returnMsg!.msgId);
} catch (e: any) {
this.core.context.logger.logDebug('发送消息id获取失败', e);
returnMsg!.id = 0;
}
if (!returnMsg) throw new Error('发送消息失败');
returnMsg.id = MessageUnique.createUniqueMsgId({
chatType: peer.chatType,
guildId: '',
peerUid: peer.peerUid,
}, returnMsg.msgId);
deleteAfterSentFiles.forEach(file => {
fsPromise.unlink(file).then().catch(e => this.core.context.logger.logError('发送消息删除文件失败', e));
});

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