mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
61 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
410d6a85d7 | ||
![]() |
b693342e4f | ||
![]() |
acca361f2e | ||
![]() |
b663f47713 | ||
![]() |
d332b199b5 | ||
![]() |
78bac1dbd1 | ||
![]() |
724ff215f9 | ||
![]() |
68ea146469 | ||
![]() |
82583e616f | ||
![]() |
bfc339c58d | ||
![]() |
fe4427c076 | ||
![]() |
5745f388a9 | ||
![]() |
377e3c253f | ||
![]() |
3007a0c00e | ||
![]() |
f51ffc091d | ||
![]() |
c37c364a08 | ||
![]() |
331a106e9a | ||
![]() |
cd74687b7b | ||
![]() |
b3e145c1e6 | ||
![]() |
d8e1547736 | ||
![]() |
8617f01924 | ||
![]() |
55f9e75e6a | ||
![]() |
b93e7b7ed1 | ||
![]() |
89cc79ad60 | ||
![]() |
8dd0e60eea | ||
![]() |
df6113fdf6 | ||
![]() |
3a3095d15a | ||
![]() |
fb4d07391e | ||
![]() |
9bef9c85cf | ||
![]() |
b77b3f227f | ||
![]() |
6a065f0a34 | ||
![]() |
4e1e190797 | ||
![]() |
1ce8cd2100 | ||
![]() |
c03af6b9ad | ||
![]() |
adca850075 | ||
![]() |
e3616b484e | ||
![]() |
cfd7808169 | ||
![]() |
addcedc588 | ||
![]() |
bfea786088 | ||
![]() |
50e84c3c9e | ||
![]() |
dc92ace85e | ||
![]() |
1a543928b1 | ||
![]() |
652fe8d21e | ||
![]() |
199690f45f | ||
![]() |
37a4dd4b00 | ||
![]() |
34d4358bfc | ||
![]() |
90906b9019 | ||
![]() |
1c212ff2b4 | ||
![]() |
7d709f44a8 | ||
![]() |
ea9e88a18a | ||
![]() |
0be8a9c805 | ||
![]() |
fcf8139afe | ||
![]() |
62f969b50b | ||
![]() |
6726062500 | ||
![]() |
cf1f4bdcaf | ||
![]() |
b09a14ad4e | ||
![]() |
1dc62c9ca3 | ||
![]() |
beaa89a2dc | ||
![]() |
f39a000b49 | ||
![]() |
013a74fb14 | ||
![]() |
7c4964753b |
28
.github/workflows/release.yml
vendored
28
.github/workflows/release.yml
vendored
@@ -93,15 +93,18 @@ jobs:
|
||||
needs: [Build-LiteLoader,Build-Shell]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Clone Main Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'NapNeko/NapCatQQ'
|
||||
submodules: true
|
||||
ref: main
|
||||
token: ${{ secrets.NAPCAT_BUILD }}
|
||||
|
||||
- name: Download All Artifact
|
||||
uses: actions/download-artifact@v4
|
||||
# - name: Compress subdirectories
|
||||
# run: |
|
||||
# cd ./NapCat.Shell/
|
||||
# zip -q -r NapCat.Shell.zip *
|
||||
# cd ..
|
||||
# rm ./NapCat.Shell.zip -rf
|
||||
# mv ./NapCat.Shell/NapCat.Shell.zip ./
|
||||
|
||||
- name: Compress subdirectories
|
||||
run: |
|
||||
cd ./NapCat.Shell/
|
||||
@@ -114,6 +117,16 @@ jobs:
|
||||
rm ./NapCat.Framework.zip -rf
|
||||
mv ./NapCat.Shell/NapCat.Shell.zip ./
|
||||
mv ./NapCat.Framework/NapCat.Framework.zip ./
|
||||
|
||||
mkdir ./NapCat.Framework.Windows.Once
|
||||
unzip -q ./external/LiteLoaderWrapper.zip -d ./NapCat.Framework.Windows.Once
|
||||
cd ./NapCat.Framework.Windows.Once
|
||||
ls
|
||||
mkdir -p ./LL/plugins/NapCatQQ
|
||||
unzip -q ../NapCat.Framework.zip -d ./LL/plugins/NapCatQQ
|
||||
zip -q -r NapCat.Framework.Windows.Once.zip *
|
||||
cd ..
|
||||
mv ./NapCat.Framework.Windows.Once/NapCat.Framework.Windows.Once.zip ./
|
||||
- name: Extract version from tag
|
||||
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
||||
|
||||
@@ -129,4 +142,5 @@ jobs:
|
||||
files: |
|
||||
NapCat.Framework.zip
|
||||
NapCat.Shell.zip
|
||||
NapCat.Framework.Windows.Once.zip
|
||||
draft: true
|
||||
|
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
## 欢迎回来
|
||||
NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现。
|
||||
NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
|
||||
|
||||
## 猫猫技能
|
||||
- [x] **高性能**:1K+ 群聊数目、20 线程并行发送消息毫无压力
|
||||
@@ -20,7 +20,7 @@ NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
|
||||
|
||||
可前往 [Release](https://github.com/NapNeko/NapCatQQ/releases/) 页面下载最新版本
|
||||
|
||||
**首次使用**请务必前往[官方文档](https://napneko.github.io/)查看使用教程。
|
||||
**首次使用**请务必前往[官方文档](https://napneko.github.io/)查看使用教程
|
||||
|
||||
## 回家旅途
|
||||
[QQ Group](https://qm.qq.com/q/VfjAq5HIMS)
|
||||
@@ -40,4 +40,4 @@ NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
|
||||
> [!CAUTION]\
|
||||
> **请不要在 QQ 官方群聊和任何影响力较大的简中互联网平台(包括但不限于: 哔哩哔哩,微博,知乎,抖音等)发布和讨论*任何*与本项目存在相关性的信息**
|
||||
|
||||
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经授权二次分发或基于 NapCat 代码开发。**
|
||||
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经授权二次分发或基于 NapCat 代码开发。**
|
||||
|
BIN
external/LiteLoaderWrapper.zip
vendored
Normal file
BIN
external/LiteLoaderWrapper.zip
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"name": "qq-chat",
|
||||
"version": "9.9.15-28131",
|
||||
"version": "9.9.15-28418",
|
||||
"verHash": "206bfa62",
|
||||
"linuxVersion": "3.2.12-28327",
|
||||
"linuxVerHash": "f60e8252",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"description": "QQ",
|
||||
@@ -15,7 +18,7 @@
|
||||
"qd": "externals/devtools/cli/index.js"
|
||||
},
|
||||
"main": "./loadNapCat.js",
|
||||
"buildVersion": "28131",
|
||||
"buildVersion": "28418",
|
||||
"isPureShell": true,
|
||||
"isByteCodeShell": true,
|
||||
"platform": "win32",
|
||||
|
@@ -4,7 +4,7 @@
|
||||
"name": "NapCatQQ",
|
||||
"slug": "NapCat.Framework",
|
||||
"description": "高性能的 OneBot 11 协议实现",
|
||||
"version": "2.6.13",
|
||||
"version": "2.6.19",
|
||||
"icon": "./logo.png",
|
||||
"authors": [
|
||||
{
|
||||
|
@@ -2,7 +2,7 @@
|
||||
"name": "napcat",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "2.6.13",
|
||||
"version": "2.6.19",
|
||||
"scripts": {
|
||||
"build:framework": "vite build --mode framework",
|
||||
"build:shell": "vite build --mode shell",
|
||||
@@ -13,11 +13,10 @@
|
||||
"devDependencies": {
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"@log4js-node/log4js-api": "^1.0.2",
|
||||
"@protobuf-ts/plugin": "^2.9.4",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-typescript": "^11.1.6",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/fluent-ffmpeg": "^2.1.24",
|
||||
"@types/node": "^22.0.1",
|
||||
"@types/qrcode-terminal": "^0.12.2",
|
||||
@@ -30,11 +29,11 @@
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.2.6",
|
||||
"vite-plugin-cp": "^4.0.8",
|
||||
"vite-plugin-dts": "^3.8.2",
|
||||
"vite-tsconfig-paths": "^4.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"ajv": "^8.13.0",
|
||||
"@protobuf-ts/plugin": "^2.9.4",
|
||||
"async-mutex": "^0.5.0",
|
||||
"chalk": "^5.3.0",
|
||||
"commander": "^12.1.0",
|
||||
@@ -46,10 +45,8 @@
|
||||
"image-size": "^1.1.1",
|
||||
"json-schema-to-ts": "^3.1.0",
|
||||
"log4js": "^6.9.1",
|
||||
"protobufjs": "~7.4.0",
|
||||
"qrcode-terminal": "^0.12.0",
|
||||
"silk-wasm": "^3.6.1",
|
||||
"strtok3": "8.0.1",
|
||||
"ws": "^8.18.0"
|
||||
}
|
||||
}
|
@@ -139,8 +139,7 @@ export class LogWrapper {
|
||||
|
||||
logMessage(msg: RawMessage, selfInfo: SelfInfo) {
|
||||
const isSelfSent = msg.senderUin === selfInfo.uin;
|
||||
this.log(`${
|
||||
isSelfSent ? '发送 ->' : '接收 <-'
|
||||
this.log(`${isSelfSent ? '发送 ->' : '接收 <-'
|
||||
} ${rawMessageToText(msg)}`);
|
||||
}
|
||||
}
|
||||
@@ -180,12 +179,11 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
|
||||
const recordMsgOrNull = msg.records.find(
|
||||
record => element.replyElement!.sourceMsgIdInRecords === record.msgId,
|
||||
);
|
||||
return `[回复消息 ${
|
||||
recordMsgOrNull &&
|
||||
recordMsgOrNull.peerUin != '284840486' // 非转发消息; 否则定位不到
|
||||
?
|
||||
rawMessageToText(recordMsgOrNull, recursiveLevel + 1) :
|
||||
`未找到消息记录 (MsgId = ${element.replyElement.sourceMsgIdInRecords})`
|
||||
return `[回复消息 ${recordMsgOrNull &&
|
||||
recordMsgOrNull.peerUin != '284840486' && recordMsgOrNull.peerUin != '1094950020'// 非转发消息; 否则定位不到
|
||||
?
|
||||
rawMessageToText(recordMsgOrNull, recursiveLevel + 1) :
|
||||
`未找到消息记录 (MsgId = ${element.replyElement.sourceMsgIdInRecords})`
|
||||
}]`;
|
||||
}
|
||||
|
||||
|
@@ -1 +1 @@
|
||||
export const napCatVersion = '2.6.13';
|
||||
export const napCatVersion = '2.6.19';
|
||||
|
@@ -120,12 +120,20 @@ export class NTQQUserApi {
|
||||
return this.context.session.getProfileService().modifyDesktopMiniProfile(param);
|
||||
}
|
||||
|
||||
//需要异常处理
|
||||
async getCookies(domain: string) {
|
||||
const ClientKeyData = await this.forceFetchClientKey();
|
||||
const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + this.core.selfInfo.uin +
|
||||
'&clientkey=' + ClientKeyData.clientKey + '&u1=https%3A%2F%2F' + domain + '%2F' + this.core.selfInfo.uin + '%2Finfocenter&keyindex=19%27';
|
||||
return await RequestUtil.HttpsGetCookies(requestUrl);
|
||||
let data = await RequestUtil.HttpsGetCookies(requestUrl);
|
||||
if (!data.p_skey || data.p_skey.length == 0) {
|
||||
try {
|
||||
let pskey = (await this.getPSkey([domain])).domainPskeyMap.get(domain);
|
||||
if (pskey) data.p_skey = pskey;
|
||||
} catch {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
async getPSkey(domainList: string[]) {
|
||||
|
@@ -659,7 +659,8 @@ export interface GrayTipElement {
|
||||
export enum FaceType {
|
||||
normal = 1, // 小黄脸
|
||||
normal2 = 2, // 新小黄脸, 从faceIndex 222开始?
|
||||
dice = 3 // 骰子
|
||||
dice = 3, // 骰子
|
||||
poke = 5 // 拍一拍
|
||||
}
|
||||
|
||||
export enum FaceIndex {
|
||||
|
16
src/core/external/appid.json
vendored
16
src/core/external/appid.json
vendored
@@ -14,5 +14,21 @@
|
||||
"3.2.12-28131": {
|
||||
"appid": 537246140,
|
||||
"qua": "V1_LNX_NQ_3.2.12_28131_GW_B"
|
||||
},
|
||||
"9.9.15-28327":{
|
||||
"appid": 537249321,
|
||||
"qua": "V1_WIN_NQ_9.9.15_28327_GW_B"
|
||||
},
|
||||
"3.2.12-28327":{
|
||||
"appid": 537249393,
|
||||
"qua": "V1_LNX_NQ_3.2.12_28327_GW_B"
|
||||
},
|
||||
"9.9.15-28418":{
|
||||
"appid": 537249321,
|
||||
"qua": "V1_WIN_NQ_9.9.15_28418_GW_B"
|
||||
},
|
||||
"3.2.12-28418":{
|
||||
"appid": 537249393,
|
||||
"qua": "V1_LNX_NQ_3.2.12_28418_GW_B"
|
||||
}
|
||||
}
|
@@ -20,16 +20,15 @@ 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 path, { resolve } from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import { hostname, systemName, systemVersion } from '@/common/system';
|
||||
import { NTEventWrapper } from '@/common/event';
|
||||
import { DataSource, GroupMember, KickedOffLineInfo, SelfInfo, SelfStatusInfo } from '@/core/entities';
|
||||
import { ChatType, DataSource, GroupMember, KickedOffLineInfo, Peer, SelfInfo, SelfStatusInfo } 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';
|
||||
@@ -99,6 +98,7 @@ export class NapCatCore {
|
||||
if (!fs.existsSync(this.NapCatTempPath)) {
|
||||
fs.mkdirSync(this.NapCatTempPath, { recursive: true });
|
||||
}
|
||||
|
||||
this.initNapCatCoreListeners().then().catch(this.context.logger.logError.bind(this.context.logger));
|
||||
|
||||
this.context.logger.setFileLogEnabled(
|
||||
@@ -248,7 +248,7 @@ export class NapCatCore {
|
||||
}
|
||||
|
||||
export async function genSessionConfig(
|
||||
guid:string,
|
||||
guid: string,
|
||||
QQVersionAppid: string,
|
||||
QQVersion: string,
|
||||
selfUin: string,
|
||||
|
48
src/core/proto/Message.ts
Normal file
48
src/core/proto/Message.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { MessageType, BinaryReader, ScalarType } from '@protobuf-ts/runtime';
|
||||
|
||||
export const BodyInner = new MessageType("BodyInner", [
|
||||
{ no: 1, name: "msgType", kind: "scalar", T: ScalarType.UINT32 /* uint32 */, opt: true },
|
||||
{ no: 2, name: "subType", kind: "scalar", T: ScalarType.UINT32 /* uint32 */, opt: true }
|
||||
]);
|
||||
|
||||
export const NoifyData = new MessageType("NoifyData", [
|
||||
{ no: 1, name: "skip", kind: "scalar", T: ScalarType.BYTES /* bytes */, opt: true },
|
||||
{ no: 2, name: "innerData", kind: "scalar", T: ScalarType.BYTES /* bytes */, opt: true }
|
||||
]);
|
||||
|
||||
export const MsgHead = new MessageType("MsgHead", [
|
||||
{ no: 2, name: "bodyInner", kind: "message", T: () => BodyInner, opt: true },
|
||||
{ no: 3, name: "noifyData", kind: "message", T: () => NoifyData, opt: true }
|
||||
]);
|
||||
|
||||
export const Message = new MessageType("Message", [
|
||||
{ no: 1, name: "msgHead", kind: "message", T: () => MsgHead }
|
||||
]);
|
||||
|
||||
export const SubDetail = new MessageType("SubDetail", [
|
||||
{ no: 1, name: "msgSeq", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
|
||||
{ no: 2, name: "msgTime", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
|
||||
{ no: 6, name: "senderUid", kind: "scalar", T: ScalarType.STRING /* string */ }
|
||||
]);
|
||||
|
||||
export const RecallDetails = new MessageType("RecallDetails", [
|
||||
{ no: 1, name: "operatorUid", kind: "scalar", T: ScalarType.STRING /* string */ },
|
||||
{ no: 3, name: "subDetail", kind: "message", T: () => SubDetail }
|
||||
]);
|
||||
|
||||
export const RecallGroup = new MessageType("RecallGroup", [
|
||||
{ no: 1, name: "type", kind: "scalar", T: ScalarType.INT32 /* int32 */ },
|
||||
{ no: 4, name: "peerUid", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
|
||||
{ no: 11, name: "recallDetails", kind: "message", T: () => RecallDetails },
|
||||
{ no: 37, name: "grayTipsSeq", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ }
|
||||
]);
|
||||
|
||||
export function decodeMessage(buffer: Uint8Array): any {
|
||||
const reader = new BinaryReader(buffer);
|
||||
return Message.internalBinaryRead(reader, reader.len, { readUnknownField: true, readerFactory: () => new BinaryReader(buffer) });
|
||||
}
|
||||
|
||||
export function decodeRecallGroup(buffer: Uint8Array): any {
|
||||
const reader = new BinaryReader(buffer);
|
||||
return RecallGroup.internalBinaryRead(reader, reader.len, { readUnknownField: true, readerFactory: () => new BinaryReader(buffer) });
|
||||
}
|
@@ -1,95 +1,58 @@
|
||||
import * as pb from 'protobufjs';
|
||||
import { MessageType, BinaryReader, ScalarType, RepeatType } from '@protobuf-ts/runtime';
|
||||
|
||||
// Proto: from src/core/proto/ProfileLike.proto
|
||||
// Author: Mlikiowa
|
||||
export const LikeDetail = new MessageType("likeDetail", [
|
||||
{ no: 1, name: "txt", kind: "scalar", T: ScalarType.STRING /* string */ },
|
||||
{ no: 3, name: "uin", kind: "scalar", T: ScalarType.INT64 /* int64 */ },
|
||||
{ no: 5, name: "nickname", kind: "scalar", T: ScalarType.STRING /* string */ }
|
||||
]);
|
||||
|
||||
export interface LikeDetailType {
|
||||
txt: string;
|
||||
uin: pb.Long;
|
||||
nickname: string;
|
||||
}
|
||||
export interface LikeMsgType {
|
||||
times: number;
|
||||
time: number;
|
||||
detail: LikeDetailType;
|
||||
export const LikeMsg = new MessageType("likeMsg", [
|
||||
{ no: 1, name: "times", kind: "scalar", T: ScalarType.INT32 /* int32 */ },
|
||||
{ no: 2, name: "time", kind: "scalar", T: ScalarType.INT32 /* int32 */ },
|
||||
{ no: 3, name: "detail", kind: "message", T: () => LikeDetail }
|
||||
]);
|
||||
|
||||
export const ProfileLikeSubTip = new MessageType("profileLikeSubTip", [
|
||||
{ no: 14, name: "msg", kind: "message", T: () => LikeMsg }
|
||||
]);
|
||||
export const ProfileLikeTip = new MessageType("profileLikeTip", [
|
||||
{ no: 1, name: "msgType", kind: "scalar", T: ScalarType.INT32 /* int32 */ },
|
||||
{ no: 2, name: "subType", kind: "scalar", T: ScalarType.INT32 /* int32 */ },
|
||||
{ no: 203, name: "content", kind: "message", T: () => ProfileLikeSubTip }
|
||||
]);
|
||||
export const SysMessageHeader = new MessageType("SysMessageHeader", [
|
||||
{ no: 1, name: "PeerNumber", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
|
||||
{ no: 2, name: "PeerString", kind: "scalar", T: ScalarType.STRING /* string */ },
|
||||
{ no: 5, name: "Uin", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
|
||||
{ no: 6, name: "Uid", kind: "scalar", T: ScalarType.STRING /* string */, opt: true }
|
||||
]);
|
||||
|
||||
export const SysMessageMsgSpec = new MessageType("SysMessageMsgSpec", [
|
||||
{ no: 1, name: "msgType", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
|
||||
{ no: 2, name: "subType", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
|
||||
{ no: 3, name: "subSubType", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
|
||||
{ no: 5, name: "msgSeq", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
|
||||
{ no: 6, name: "time", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
|
||||
{ no: 12, name: "msgId", kind: "scalar", T: ScalarType.UINT64 /* uint64 */ },
|
||||
{ no: 13, name: "other", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ }
|
||||
]);
|
||||
|
||||
export const SysMessageBodyWrapper = new MessageType("SysMessageBodyWrapper", [
|
||||
{ no: 2, name: "wrappedBody", kind: "scalar", T: ScalarType.BYTES /* bytes */ }
|
||||
]);
|
||||
|
||||
export const SysMessage = new MessageType("SysMessage", [
|
||||
{ no: 1, name: "header", kind: "message", T: () => SysMessageHeader, repeat: RepeatType.UNPACKED },
|
||||
{ no: 2, name: "msgSpec", kind: "message", T: () => SysMessageMsgSpec, repeat: RepeatType.UNPACKED },
|
||||
{ no: 3, name: "bodyWrapper", kind: "message", T: () => SysMessageBodyWrapper }
|
||||
]);
|
||||
|
||||
export function decodeProfileLikeTip(buffer: Uint8Array): any {
|
||||
const reader = new BinaryReader(buffer);
|
||||
return ProfileLikeTip.internalBinaryRead(reader, reader.len, { readUnknownField: true, readerFactory: () => new BinaryReader(buffer) });
|
||||
}
|
||||
|
||||
export interface profileLikeSubTipType {
|
||||
msg: LikeMsgType;
|
||||
}
|
||||
|
||||
export interface ProfileLikeTipType {
|
||||
msgType: number;
|
||||
subType: number;
|
||||
content: profileLikeSubTipType;
|
||||
}
|
||||
export interface SysMessageHeaderType {
|
||||
id: string;
|
||||
timestamp: number;
|
||||
sender: string;
|
||||
}
|
||||
|
||||
export interface SysMessageMsgSpecType {
|
||||
msgType: number;
|
||||
subType: number;
|
||||
subSubType: number;
|
||||
msgSeq: number;
|
||||
time: number;
|
||||
msgId: pb.Long;
|
||||
other: number;
|
||||
}
|
||||
export interface SysMessageBodyWrapperType {
|
||||
wrappedBody: Uint8Array;
|
||||
}
|
||||
export interface SysMessageType {
|
||||
header: SysMessageHeaderType[];
|
||||
msgSpec: SysMessageMsgSpecType[];
|
||||
bodyWrapper: SysMessageBodyWrapperType;
|
||||
}
|
||||
|
||||
export const SysMessageHeader = new pb.Type("SysMessageHeader")
|
||||
.add(new pb.Field("PeerNumber", 1, "uint32"))
|
||||
.add(new pb.Field("PeerString", 2, "string"))
|
||||
.add(new pb.Field("Uin", 5, "uint32"))
|
||||
.add(new pb.Field("Uid", 6, "string", "optional"));
|
||||
|
||||
export const SysMessageMsgSpec = new pb.Type("SysMessageMsgSpec")
|
||||
.add(new pb.Field("msgType", 1, "uint32"))
|
||||
.add(new pb.Field("subType", 2, "uint32"))
|
||||
.add(new pb.Field("subSubType", 3, "uint32"))
|
||||
.add(new pb.Field("msgSeq", 5, "uint32"))
|
||||
.add(new pb.Field("time", 6, "uint32"))
|
||||
.add(new pb.Field("msgId", 12, "uint64"))
|
||||
.add(new pb.Field("other", 13, "uint32"));
|
||||
|
||||
export const SysMessageBodyWrapper = new pb.Type("SysMessageBodyWrapper")
|
||||
.add(new pb.Field("wrappedBody", 2, "bytes"));
|
||||
|
||||
export const SysMessage = new pb.Type("SysMessage")
|
||||
.add(SysMessageHeader)
|
||||
.add(SysMessageMsgSpec)
|
||||
.add(SysMessageBodyWrapper)
|
||||
.add(new pb.Field("header", 1, "SysMessageHeader", "repeated"))
|
||||
.add(new pb.Field("msgSpec", 2, "SysMessageMsgSpec", "repeated"))
|
||||
.add(new pb.Field("bodyWrapper", 3, "SysMessageBodyWrapper"));
|
||||
|
||||
export const likeDetail = new pb.Type("likeDetail")
|
||||
.add(new pb.Field("txt", 1, "string"))
|
||||
.add(new pb.Field("uin", 3, "int64"))
|
||||
.add(new pb.Field("nickname", 5, "string"));
|
||||
|
||||
export const likeMsg = new pb.Type("likeMsg")
|
||||
.add(likeDetail)
|
||||
.add(new pb.Field("times", 1, "int32"))
|
||||
.add(new pb.Field("time", 2, "int32"))
|
||||
.add(new pb.Field("detail", 3, "likeDetail"));
|
||||
|
||||
export const profileLikeSubTip = new pb.Type("profileLikeSubTip")
|
||||
.add(likeMsg)
|
||||
.add(new pb.Field("msg", 14, "likeMsg"))
|
||||
|
||||
export const profileLikeTip = new pb.Type("profileLikeTip")
|
||||
.add(profileLikeSubTip)
|
||||
.add(new pb.Field("msgType", 1, "int32"))
|
||||
.add(new pb.Field("subType", 2, "int32"))
|
||||
.add(new pb.Field("content", 203, "profileLikeSubTip"));
|
||||
export function decodeSysMessage(buffer: Uint8Array): any {
|
||||
const reader = new BinaryReader(buffer);
|
||||
return SysMessage.internalBinaryRead(reader, reader.len, { readUnknownField: true, readerFactory: () => new BinaryReader(buffer) });
|
||||
}
|
@@ -240,7 +240,12 @@ export interface NodeIKernelGroupService {
|
||||
|
||||
getGroupRecommendContactArkJson(groupCode: string): unknown;
|
||||
|
||||
getJoinGroupLink(groupCode: string): unknown;
|
||||
getJoinGroupLink(param: {
|
||||
groupCode: string,
|
||||
srcId: number,//73
|
||||
needShortUrl: boolean,//true
|
||||
additionalParam: string//''
|
||||
}): Promise<GeneralCallResult & { url?: string }>;
|
||||
|
||||
modifyGroupExtInfo(groupCode: string, arg: unknown): void;
|
||||
|
||||
|
BIN
src/native/external/MoeHoo.win32.node
vendored
Normal file
BIN
src/native/external/MoeHoo.win32.node
vendored
Normal file
Binary file not shown.
33
src/native/index.ts
Normal file
33
src/native/index.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { constants } from "node:os";
|
||||
import path from "path";
|
||||
import { dlopen } from "process";
|
||||
import fs from "fs";
|
||||
export class Native {
|
||||
platform: string;
|
||||
supportedPlatforms = ['win32'];
|
||||
MoeHooExport: any = { exports: {} };
|
||||
recallHookEnabled: boolean = false;
|
||||
constructor(nodePath: string, platform: string = process.platform) {
|
||||
this.platform = platform;
|
||||
if (!this.supportedPlatforms.includes(this.platform)) {
|
||||
throw new Error(`Platform ${this.platform} is not supported`);
|
||||
}
|
||||
let nativeNode = path.join(nodePath, './native/MoeHoo.win32.node');
|
||||
if (fs.existsSync(nativeNode)) {
|
||||
dlopen(this.MoeHooExport, nativeNode, constants.dlopen.RTLD_LAZY);
|
||||
}
|
||||
}
|
||||
isSetReCallEnabled(): boolean {
|
||||
return this.recallHookEnabled;
|
||||
}
|
||||
registerRecallCallback(callback: (hex: string) => any): void {
|
||||
try {
|
||||
if (this.MoeHooExport.exports?.registMsgPush) {
|
||||
this.MoeHooExport.exports.registMsgPush(callback);
|
||||
this.recallHookEnabled = true;
|
||||
}
|
||||
} catch (error) {
|
||||
this.recallHookEnabled = false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,9 +1,16 @@
|
||||
import BaseAction from '../BaseAction';
|
||||
import { OB11ForwardMessage } from '@/onebot';
|
||||
import { OB11Message, OB11MessageData, OB11MessageDataType, OB11MessageForward, OB11MessageNode as OriginalOB11MessageNode } from '@/onebot';
|
||||
import { ActionName } from '../types';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import { MessageUnique } from '@/common/message-unique';
|
||||
|
||||
type OB11MessageNode = OriginalOB11MessageNode & {
|
||||
data: {
|
||||
content?: Array<OB11MessageData>;
|
||||
message: Array<OB11MessageData>;
|
||||
};
|
||||
};
|
||||
|
||||
const SchemaData = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
@@ -18,36 +25,69 @@ export class GoCQHTTPGetForwardMsgAction extends BaseAction<Payload, any> {
|
||||
actionName = ActionName.GoCQHTTP_GetForwardMsg;
|
||||
payloadSchema = SchemaData;
|
||||
|
||||
private createTemplateNode(message: OB11Message): OB11MessageNode {
|
||||
return {
|
||||
type: OB11MessageDataType.node,
|
||||
data: {
|
||||
user_id: message.user_id,
|
||||
nickname: message.sender.nickname,
|
||||
message: [],
|
||||
content: []
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async parseForward(messages: OB11Message[]): Promise<OB11MessageNode[]> {
|
||||
const retMsg: OB11MessageNode[] = [];
|
||||
|
||||
for (const message of messages) {
|
||||
const templateNode = this.createTemplateNode(message);
|
||||
|
||||
for (const msgdata of message.message) {
|
||||
if ((msgdata as OB11MessageData).type === OB11MessageDataType.forward) {
|
||||
const newNode = this.createTemplateNode(message);
|
||||
newNode.data.message = await this.parseForward((msgdata as OB11MessageForward).data.content);
|
||||
|
||||
templateNode.data.message.push(newNode);
|
||||
} else {
|
||||
templateNode.data.message.push(msgdata as OB11MessageData);
|
||||
}
|
||||
}
|
||||
retMsg.push(templateNode);
|
||||
}
|
||||
|
||||
return retMsg;
|
||||
}
|
||||
|
||||
async _handle(payload: Payload): Promise<any> {
|
||||
const msgId = payload.message_id || payload.id;
|
||||
if (!msgId) {
|
||||
throw Error('message_id is required');
|
||||
throw new Error('message_id is required');
|
||||
}
|
||||
|
||||
const rootMsgId = MessageUnique.getShortIdByMsgId(msgId);
|
||||
const rootMsg = MessageUnique.getMsgIdAndPeerByShortId(rootMsgId || parseInt(msgId));
|
||||
if (!rootMsg) {
|
||||
throw Error('msg not found');
|
||||
throw new Error('msg not found');
|
||||
}
|
||||
const data = await this.core.apis.MsgApi.getMultiMsg(rootMsg.Peer, rootMsg.MsgId, rootMsg.MsgId);
|
||||
const data = await this.core.apis.MsgApi.getMsgsByMsgId(rootMsg.Peer, [rootMsg.MsgId]);
|
||||
|
||||
if (!data || data.result !== 0) {
|
||||
throw Error('找不到相关的聊天记录' + data?.errMsg);
|
||||
throw new Error('找不到相关的聊天记录' + data?.errMsg);
|
||||
}
|
||||
const msgList = data.msgList;
|
||||
const messages = (await Promise.all(msgList.map(async msg => {
|
||||
const resMsg = await this.obContext.apis.MsgApi
|
||||
.parseMessage(msg);
|
||||
if (!resMsg) return;
|
||||
resMsg.message_id = MessageUnique.createUniqueMsgId({
|
||||
guildId: '',
|
||||
chatType: msg.chatType,
|
||||
peerUid: msg.peerUid,
|
||||
}, msg.msgId)!;
|
||||
return resMsg;
|
||||
}))).filter(msg => !!msg);
|
||||
messages.map(msg => {
|
||||
(<OB11ForwardMessage>msg).content = msg.message;
|
||||
delete (<any>msg).message;
|
||||
});
|
||||
return { messages };
|
||||
|
||||
const singleMsg = data.msgList[0];
|
||||
const resMsg = await this.obContext.apis.MsgApi.parseMessage(singleMsg, 'array');//强制array 以便处理
|
||||
if (!resMsg) {
|
||||
throw new Error('找不到相关的聊天记录');
|
||||
}
|
||||
//if (this.obContext.configLoader.configData.messagePostFormat === 'array') {
|
||||
//提取
|
||||
let realmsg = ((await this.parseForward([resMsg]))[0].data.message as OB11MessageNode[])[0].data.message;
|
||||
//里面都是offline消息 id都是0 没得说话
|
||||
return { message: realmsg };
|
||||
//}
|
||||
|
||||
// return { message: resMsg };
|
||||
}
|
||||
}
|
||||
}
|
@@ -3,6 +3,7 @@ import BaseAction from '../BaseAction';
|
||||
import { ActionName } from '../types';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import { MessageUnique } from '@/common/message-unique';
|
||||
import { RawMessage } from '@/core';
|
||||
|
||||
|
||||
export type ReturnDataType = OB11Message
|
||||
@@ -32,13 +33,17 @@ class GetMsg extends BaseAction<Payload, OB11Message> {
|
||||
throw new Error('消息不存在');
|
||||
}
|
||||
const peer = { guildId: '', peerUid: msgIdWithPeer?.Peer.peerUid, chatType: msgIdWithPeer.Peer.chatType };
|
||||
const msg = await this.core.apis.MsgApi.getMsgsByMsgId(
|
||||
peer,
|
||||
[msgIdWithPeer?.MsgId || payload.message_id.toString()]);
|
||||
const retMsg = await this.obContext.apis.MsgApi.parseMessage(msg.msgList[0]);
|
||||
let orimsg = this.obContext.recallMsgCache.get(msgIdWithPeer.MsgId);
|
||||
let msg: RawMessage;
|
||||
if (orimsg) {
|
||||
msg = orimsg;
|
||||
} else {
|
||||
msg = (await this.core.apis.MsgApi.getMsgsByMsgId(peer, [msgIdWithPeer?.MsgId || payload.message_id.toString()])).msgList[0];
|
||||
}
|
||||
const retMsg = await this.obContext.apis.MsgApi.parseMessage(msg);
|
||||
if (!retMsg) throw Error('消息为空');
|
||||
try {
|
||||
retMsg.message_id = MessageUnique.createUniqueMsgId(peer, msg.msgList[0].msgId)!;
|
||||
retMsg.message_id = MessageUnique.createUniqueMsgId(peer, msg.msgId)!;
|
||||
retMsg.message_seq = retMsg.message_id;
|
||||
retMsg.real_id = retMsg.message_id;
|
||||
} catch (e) {
|
||||
|
@@ -15,14 +15,14 @@ export class OneBotFriendApi {
|
||||
//使用前预先判断 busiId 1061
|
||||
async parsePrivatePokeEvent(grayTipElement: GrayTipElement) {
|
||||
const json = JSON.parse(grayTipElement.jsonGrayTipElement.jsonStr);
|
||||
let pokedetail: any[] = json.items;
|
||||
let pokedetail: Array<{ uid: string }> = json.items;
|
||||
//筛选item带有uid的元素
|
||||
pokedetail = pokedetail.filter(item => item.uid);
|
||||
if (pokedetail.length == 2) {
|
||||
return new OB11FriendPokeEvent(
|
||||
this.core,
|
||||
parseInt((await this.core.apis.UserApi.getUinByUidV2(pokedetail[0].uid))!),
|
||||
parseInt((await this.core.apis.UserApi.getUinByUidV2(pokedetail[1].uid))!),
|
||||
parseInt((await this.core.apis.UserApi.getUinByUidV2(pokedetail[0].uid))),
|
||||
parseInt((await this.core.apis.UserApi.getUinByUidV2(pokedetail[1].uid))),
|
||||
pokedetail,
|
||||
);
|
||||
}
|
||||
|
@@ -96,7 +96,6 @@ export class OneBotGroupApi {
|
||||
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) {
|
||||
@@ -139,7 +138,6 @@ export class OneBotGroupApi {
|
||||
// 获取MsgSeq+Peer可获取具体消息
|
||||
}
|
||||
if (element.grayTipElement.jsonGrayTipElement.busiId == 2407) {
|
||||
//下面得改 上面也是错的grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE
|
||||
const type = json.items[json.items.length - 1]?.txt;
|
||||
switch (type) {
|
||||
case "头衔": {
|
||||
@@ -207,7 +205,6 @@ export class OneBotGroupApi {
|
||||
while ((match = regex.exec(xmlElement.content)) !== null) {
|
||||
matches.push(match[1]);
|
||||
}
|
||||
// log("新人进群匹配到的QQ号", matches)
|
||||
if (matches.length === 2) {
|
||||
const [inviter, invitee] = matches;
|
||||
return new OB11GroupIncreaseEvent(
|
||||
|
@@ -34,7 +34,7 @@ import { RequestUtil } from '@/common/request';
|
||||
import fs from 'node:fs';
|
||||
import fsPromise from 'node:fs/promises';
|
||||
import { OB11FriendAddNoticeEvent } from '@/onebot/event/notice/OB11FriendAddNoticeEvent';
|
||||
import { SysMessage, SysMessageType } from '@/core/proto/ProfileLike';
|
||||
import { decodeSysMessage } from '@/core/proto/ProfileLike';
|
||||
|
||||
type RawToOb11Converters = {
|
||||
[Key in keyof MessageElement as Key extends `${string}Element` ? Key : never]: (
|
||||
@@ -212,7 +212,7 @@ export class OneBotMsgApi {
|
||||
},
|
||||
});
|
||||
|
||||
if (records.peerUin === '284840486') {
|
||||
if (records.peerUin === '284840486' || records.peerUin === '1094950020') {
|
||||
return createReplyData(records.msgId);
|
||||
}
|
||||
const replyMsg = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV2(peer, element.replayMsgSeq, element.replyMsgTime, [element.senderUidStr]))
|
||||
@@ -234,19 +234,29 @@ export class OneBotMsgApi {
|
||||
//读取视频链接并兜底
|
||||
let videoUrlWrappers: Awaited<ReturnType<typeof this.core.apis.FileApi.getVideoUrl>> | undefined;
|
||||
|
||||
if (msg.peerUin === '284840486') {
|
||||
//TODO: 合并消息内部 应该进行特殊处理 可能需要重写peer 待测试与研究 Mlikiowa Tagged
|
||||
}
|
||||
try {
|
||||
videoUrlWrappers = await this.core.apis.FileApi.getVideoUrl({
|
||||
chatType: msg.chatType,
|
||||
peerUid: msg.peerUid,
|
||||
guildId: '0',
|
||||
}, msg.msgId, elementWrapper.elementId);
|
||||
} catch (error) {
|
||||
this.core.context.logger.logWarn('获取视频 URL 失败');
|
||||
if (msg.peerUin === '284840486' || msg.peerUin === '1094950020') {
|
||||
try {
|
||||
videoUrlWrappers = await this.core.apis.FileApi.getVideoUrl({
|
||||
chatType: msg.chatType,
|
||||
peerUid: msg.peerUid,
|
||||
guildId: '0',
|
||||
}, msg.parentMsgIdList[0] ?? msg.msgId, elementWrapper.elementId);
|
||||
} catch (error) {
|
||||
this.core.context.logger.logWarn('合并获取视频 URL 失败');
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
videoUrlWrappers = await this.core.apis.FileApi.getVideoUrl({
|
||||
chatType: msg.chatType,
|
||||
peerUid: msg.peerUid,
|
||||
guildId: '0',
|
||||
}, msg.msgId, elementWrapper.elementId);
|
||||
} catch (error) {
|
||||
this.core.context.logger.logWarn('获取视频 URL 失败');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//读取在线URL
|
||||
let videoDownUrl: string | undefined;
|
||||
|
||||
@@ -447,7 +457,6 @@ export class OneBotMsgApi {
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
[OB11MessageDataType.mface]: async ({
|
||||
data: {
|
||||
emoji_package_id, emoji_id, key, summary,
|
||||
@@ -510,13 +519,12 @@ export class OneBotMsgApi {
|
||||
faceElement: {
|
||||
faceIndex: FaceIndex.dice,
|
||||
faceType: FaceType.dice,
|
||||
'faceText': '[骰子]',
|
||||
'packId': '1',
|
||||
'stickerId': '33',
|
||||
'sourceType': 1,
|
||||
'stickerType': 2,
|
||||
// resultId: resultId.toString(),
|
||||
'surpriseId': '',
|
||||
faceText: '[骰子]',
|
||||
packId: '1',
|
||||
stickerId: '33',
|
||||
sourceType: 1,
|
||||
stickerType: 2,
|
||||
surpriseId: '',
|
||||
// "randomType": 1,
|
||||
},
|
||||
}),
|
||||
@@ -525,15 +533,14 @@ export class OneBotMsgApi {
|
||||
elementType: ElementType.FACE,
|
||||
elementId: '',
|
||||
faceElement: {
|
||||
'faceIndex': FaceIndex.RPS,
|
||||
'faceText': '[包剪锤]',
|
||||
'faceType': 3,
|
||||
'packId': '1',
|
||||
'stickerId': '34',
|
||||
'sourceType': 1,
|
||||
'stickerType': 2,
|
||||
// 'resultId': resultId.toString(),
|
||||
'surpriseId': '',
|
||||
faceIndex: FaceIndex.RPS,
|
||||
faceText: '[包剪锤]',
|
||||
faceType: 3,
|
||||
packId: '1',
|
||||
stickerId: '34',
|
||||
sourceType: 1,
|
||||
stickerType: 2,
|
||||
surpriseId: '',
|
||||
// "randomType": 1,
|
||||
},
|
||||
}),
|
||||
@@ -834,7 +841,7 @@ export class OneBotMsgApi {
|
||||
return { path, fileName: inputdata.name ?? fileName };
|
||||
}
|
||||
async parseSysMessage(msg: number[]) {
|
||||
const sysMsg = SysMessage.decode(Uint8Array.from(msg)) as unknown as SysMessageType;
|
||||
const sysMsg = decodeSysMessage(Uint8Array.from(msg));
|
||||
if (sysMsg.msgSpec.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { NapCatCore } from '@/core';
|
||||
import { profileLikeTip, ProfileLikeTipType } from '@/core/proto/ProfileLike';
|
||||
import { decodeProfileLikeTip } from '@/core/proto/ProfileLike';
|
||||
|
||||
import { NapCatOneBot11Adapter } from '@/onebot';
|
||||
import { OB11ProfileLikeEvent } from '../event/notice/OB11ProfileLikeEvent';
|
||||
@@ -13,7 +13,7 @@ export class OneBotUserApi {
|
||||
this.core = core;
|
||||
}
|
||||
async parseLikeEvent(wrappedBody: Uint8Array): Promise<OB11ProfileLikeEvent | undefined> {
|
||||
const likeTip = profileLikeTip.decode(Uint8Array.from(wrappedBody)) as unknown as ProfileLikeTipType;
|
||||
const likeTip = decodeProfileLikeTip(Uint8Array.from(wrappedBody));
|
||||
if (likeTip?.msgType !== 0 || likeTip?.subType !== 203) return;
|
||||
this.core.context.logger.logDebug("收到点赞通知消息");
|
||||
const likeMsg = likeTip.content.msg;
|
||||
|
@@ -10,6 +10,7 @@ import {
|
||||
NodeIKernelBuddyListener,
|
||||
NodeIKernelGroupListener,
|
||||
NodeIKernelMsgListener,
|
||||
Peer,
|
||||
RawMessage,
|
||||
SendStatusType,
|
||||
} from '@/core';
|
||||
@@ -43,8 +44,9 @@ import { OB11FriendRecallNoticeEvent } from '@/onebot/event/notice/OB11FriendRec
|
||||
import { OB11GroupRecallNoticeEvent } from '@/onebot/event/notice/OB11GroupRecallNoticeEvent';
|
||||
import { LRUCache } from '@/common/lru-cache';
|
||||
import { NodeIKernelRecentContactListener } from '@/core/listeners/NodeIKernelRecentContactListener';
|
||||
import { OB11ProfileLikeEvent } from './event/notice/OB11ProfileLikeEvent';
|
||||
import { profileLikeTip, ProfileLikeTipType, SysMessage, SysMessageType } from '@/core/proto/ProfileLike';
|
||||
import { Native } from '@/native';
|
||||
import { decodeMessage, decodeRecallGroup, Message, RecallGroup } from '@/core/proto/Message';
|
||||
|
||||
//OneBot实现类
|
||||
export class NapCatOneBot11Adapter {
|
||||
readonly core: NapCatCore;
|
||||
@@ -54,8 +56,9 @@ export class NapCatOneBot11Adapter {
|
||||
apis: StableOneBotApiWrapper;
|
||||
networkManager: OB11NetworkManager;
|
||||
actions: ActionMap;
|
||||
|
||||
nativeCore: Native | undefined;
|
||||
private bootTime = Date.now() / 1000;
|
||||
recallMsgCache = new LRUCache<string, RawMessage>(100);
|
||||
|
||||
constructor(core: NapCatCore, context: InstanceContext, pathWrapper: NapCatPathWrapper) {
|
||||
this.core = core;
|
||||
@@ -70,10 +73,54 @@ export class NapCatOneBot11Adapter {
|
||||
};
|
||||
this.actions = createActionMap(this, core);
|
||||
this.networkManager = new OB11NetworkManager();
|
||||
this.registerNative(core, context).then().catch();
|
||||
this.InitOneBot()
|
||||
.catch(e => this.context.logger.logError.bind(this.context.logger)('初始化OneBot失败', e));
|
||||
}
|
||||
|
||||
}
|
||||
async registerNative(core: NapCatCore, context: InstanceContext) {
|
||||
this.nativeCore = new Native(context.pathWrapper.binaryPath);
|
||||
this.nativeCore.registerRecallCallback(async (hex: string) => {
|
||||
try {
|
||||
let data = decodeMessage(Buffer.from(hex, 'hex')) as any;
|
||||
//data.MsgHead.BodyInner.MsgType SubType
|
||||
let bodyInner = data.msgHead?.bodyInner;
|
||||
//context.logger.log("[appNative] Parse MsgType:" + bodyInner.msgType + " / SubType:" + bodyInner.subType);
|
||||
if (bodyInner && bodyInner.msgType == 732 && bodyInner.subType == 17) {
|
||||
let RecallData = Buffer.from(data.msgHead.noifyData.innerData);
|
||||
//跳过 4字节 群号 + 不知道的1字节 +2字节 长度
|
||||
let uid = RecallData.readUint32BE();
|
||||
const buffer = Buffer.from(RecallData.toString('hex').slice(14), 'hex');
|
||||
let seq: number = decodeRecallGroup(buffer).recallDetails.subDetail.msgSeq;
|
||||
let peer: Peer = { chatType: ChatType.KCHATTYPEGROUP, peerUid: uid.toString() };
|
||||
context.logger.log("[Native] 群消息撤回 Peer: " + uid.toString() + " / MsgSeq:" + seq);
|
||||
let msgs = await core.apis.MsgApi.queryMsgsWithFilterExWithSeq(peer, seq.toString());
|
||||
this.recallMsgCache.put(msgs.msgList[0].msgId, msgs.msgList[0]);
|
||||
// let ob11 = await this.apis.MsgApi.parseMessage(msgs.msgList[0], 'array')
|
||||
// .catch(e => this.context.logger.logError.bind(this.context.logger)('处理消息失败', e));
|
||||
// if (ob11) {
|
||||
// const { sendElements, deleteAfterSentFiles } = await this.apis.MsgApi.createSendElements(ob11.message as OB11MessageData[], peer);
|
||||
// this.apis.MsgApi.sendMsgWithOb11UniqueId(peer, sendElements, deleteAfterSentFiles);
|
||||
// }
|
||||
|
||||
|
||||
// this.apis.MsgApi.sendMsg(peer, [{
|
||||
// elementType: 1,
|
||||
// elementId: '',
|
||||
// textElement: {
|
||||
// content: "[Native] 群消息撤回 Peer: " + uid.toString() + " / MsgSeq:" + seq,
|
||||
// atType: 0,
|
||||
// atUid: '',
|
||||
// atTinyId: '',
|
||||
// atNtUid: '',
|
||||
// },
|
||||
// }]);
|
||||
}
|
||||
} catch (error: any) {
|
||||
context.logger.logWarn("[Native] Error:", (error as Error).message, ' HEX:', hex);
|
||||
}
|
||||
});
|
||||
}
|
||||
async InitOneBot() {
|
||||
const selfInfo = this.core.selfInfo;
|
||||
const ob11Config = this.configLoader.configData;
|
||||
@@ -523,7 +570,6 @@ export class NapCatOneBot11Adapter {
|
||||
}
|
||||
}).catch(e => this.context.logger.logError.bind(this.context.logger)('constructPrivateEvent error: ', e));
|
||||
}
|
||||
|
||||
private async emitRecallMsg(msgList: RawMessage[], cache: LRUCache<string, boolean>) {
|
||||
for (const message of msgList) {
|
||||
// log("message update", message.sendStatus, message.msgId, message.msgSeq)
|
||||
@@ -555,7 +601,7 @@ export class NapCatOneBot11Adapter {
|
||||
parseInt(message.peerUin),
|
||||
parseInt(message.senderUin),
|
||||
parseInt(operatorId),
|
||||
oriMessageId,
|
||||
oriMessageId
|
||||
);
|
||||
this.networkManager.emitEvent(groupRecallEvent)
|
||||
.catch(e => this.context.logger.logError.bind(this.context.logger)('处理群消息撤回失败', e));
|
||||
|
@@ -54,7 +54,6 @@ export async function NCoreInitShell() {
|
||||
const loginService = wrapper.NodeIKernelLoginService.get();
|
||||
|
||||
const session = wrapper.NodeIQQNTWrapperSession.create();
|
||||
|
||||
// from get dataPath
|
||||
const [dataPath, dataPathGlobal] = (() => {
|
||||
if (os.platform() === 'darwin') {
|
||||
@@ -72,15 +71,15 @@ export async function NCoreInitShell() {
|
||||
})();
|
||||
let systemPlatform = PlatformType.KWINDOWS;
|
||||
switch (os.platform()) {
|
||||
case 'win32':
|
||||
systemPlatform = PlatformType.KWINDOWS;
|
||||
break;
|
||||
case 'darwin':
|
||||
systemPlatform = PlatformType.KMAC;
|
||||
break;
|
||||
case 'linux':
|
||||
systemPlatform = PlatformType.KLINUX;
|
||||
break;
|
||||
case 'win32':
|
||||
systemPlatform = PlatformType.KWINDOWS;
|
||||
break;
|
||||
case 'darwin':
|
||||
systemPlatform = PlatformType.KMAC;
|
||||
break;
|
||||
case 'linux':
|
||||
systemPlatform = PlatformType.KLINUX;
|
||||
break;
|
||||
}
|
||||
if (!basicInfoWrapper.QQVersionAppid || !basicInfoWrapper.QQVersionQua) throw new Error('QQVersionAppid or QQVersionQua is not defined');
|
||||
// from initConfig
|
||||
@@ -119,7 +118,7 @@ export async function NCoreInitShell() {
|
||||
quickLoginUin = '';
|
||||
}
|
||||
}
|
||||
let dataTimestape = new Date().getTime().toString();
|
||||
const dataTimestape = new Date().getTime().toString();
|
||||
o3Service.reportAmgomWeather('login', 'a1', [dataTimestape, '0', '0']);
|
||||
const selfInfo = await new Promise<SelfInfo>((resolve) => {
|
||||
const loginListener = new NodeIKernelLoginListener();
|
||||
@@ -234,13 +233,13 @@ export async function NCoreInitShell() {
|
||||
logger.log(`可用于快速登录的 QQ:\n${historyLoginList
|
||||
.map((u, index) => `${index + 1}. ${u.uin} ${u.nickName}`)
|
||||
.join('\n')
|
||||
}`);
|
||||
}`);
|
||||
}
|
||||
loginService.getQRCodePicture();
|
||||
}
|
||||
});
|
||||
// BEFORE LOGGING IN
|
||||
let amgomDataPiece = 'eb1fd6ac257461580dc7438eb099f23aae04ca679f4d88f53072dc56e3bb1129';
|
||||
const amgomDataPiece = 'eb1fd6ac257461580dc7438eb099f23aae04ca679f4d88f53072dc56e3bb1129';
|
||||
o3Service.setAmgomDataPiece(basicInfoWrapper.QQVersionAppid, new Uint8Array(Buffer.from(amgomDataPiece, 'hex')));
|
||||
// AFTER LOGGING IN
|
||||
//99b15bdb4c984fc69d5aa1feb9aa16xx --> 99b15bdb-4c98-4fc6-9d5a-a1feb9aa16xx
|
||||
|
@@ -30,7 +30,7 @@ async function onSettingWindowCreated(view: Element) {
|
||||
SettingItem(
|
||||
'<span id="napcat-update-title">Napcat</span>',
|
||||
undefined,
|
||||
SettingButton('V2.6.13', 'napcat-update-button', 'secondary'),
|
||||
SettingButton('V2.6.19', 'napcat-update-button', 'secondary'),
|
||||
),
|
||||
]),
|
||||
SettingList([
|
||||
|
@@ -164,7 +164,7 @@ async function onSettingWindowCreated(view) {
|
||||
SettingItem(
|
||||
'<span id="napcat-update-title">Napcat</span>',
|
||||
void 0,
|
||||
SettingButton("V2.6.13", "napcat-update-button", "secondary")
|
||||
SettingButton("V2.6.19", "napcat-update-button", "secondary")
|
||||
)
|
||||
]),
|
||||
SettingList([
|
||||
|
@@ -41,6 +41,7 @@ const FrameworkBaseConfigPlugin: PluginOption[] = [
|
||||
const ShellBaseConfigPlugin: PluginOption[] = [
|
||||
cp({
|
||||
targets: [
|
||||
{ src: './src/native/external', dest: 'dist/native', flatten: false },
|
||||
{ src: './static/', dest: 'dist/static/', flatten: false },
|
||||
{ src: './src/core/external/napcat.json', dest: 'dist/config/' },
|
||||
{ src: './src/onebot/config/onebot11.json', dest: 'dist/config/' },
|
||||
|
Reference in New Issue
Block a user