mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
111 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d10e7c37cb | ||
![]() |
d38f1853a4 | ||
![]() |
bdec16266e | ||
![]() |
49ca698ab9 | ||
![]() |
3efd8163c9 | ||
![]() |
cc2d11449c | ||
![]() |
7e9c19ca5b | ||
![]() |
3b01b6827f | ||
![]() |
8d9ef851ba | ||
![]() |
b070bc59bc | ||
![]() |
8d663946e1 | ||
![]() |
2a2328b029 | ||
![]() |
efc9064abb | ||
![]() |
dd70adf071 | ||
![]() |
0f427375cb | ||
![]() |
4001270b93 | ||
![]() |
e7f5ed3bcc | ||
![]() |
05cdc37d0a | ||
![]() |
27920e0bee | ||
![]() |
ae409b7249 | ||
![]() |
8276258348 | ||
![]() |
1bf96a97a5 | ||
![]() |
d672680c4c | ||
![]() |
b89f2805e7 | ||
![]() |
78b4aa9295 | ||
![]() |
0a06637e78 | ||
![]() |
13afa2c7ab | ||
![]() |
51d34d17cc | ||
![]() |
18a99341d5 | ||
![]() |
f01c8f0110 | ||
![]() |
d8070eee2a | ||
![]() |
8519b7f4df | ||
![]() |
591ab1b1df | ||
![]() |
393815b11e | ||
![]() |
341a397bc4 | ||
![]() |
e46d274a75 | ||
![]() |
ad6f21980c | ||
![]() |
017b8b7f15 | ||
![]() |
9b448b17e6 | ||
![]() |
f9996a9987 | ||
![]() |
000ef55273 | ||
![]() |
e1ac0f02b4 | ||
![]() |
b9297e3f1d | ||
![]() |
34d0669ca8 | ||
![]() |
25e42720cf | ||
![]() |
f7c1951191 | ||
![]() |
479b971b0c | ||
![]() |
347ba5f354 | ||
![]() |
81dbb9d980 | ||
![]() |
c4e1a3ab04 | ||
![]() |
90ec774a21 | ||
![]() |
db7a27e624 | ||
![]() |
f7d965eda2 | ||
![]() |
74ca2e2e16 | ||
![]() |
8ab550f2f5 | ||
![]() |
018aca4db2 | ||
![]() |
d4327166c1 | ||
![]() |
fa25d2e779 | ||
![]() |
3ce1c3f0ec | ||
![]() |
96dff5141e | ||
![]() |
78d85d9965 | ||
![]() |
37ec455b02 | ||
![]() |
6ab82739a6 | ||
![]() |
a36917e7c0 | ||
![]() |
21f3428b36 | ||
![]() |
f8a487db25 | ||
![]() |
73a859be04 | ||
![]() |
63bcee01a1 | ||
![]() |
85b4966ba8 | ||
![]() |
36c2c567b7 | ||
![]() |
7b1ac224f6 | ||
![]() |
34d9f04f15 | ||
![]() |
be5da7cc6f | ||
![]() |
8d32ccb5d4 | ||
![]() |
6acceb884c | ||
![]() |
4c834fd640 | ||
![]() |
301278c7a9 | ||
![]() |
42ee83c54f | ||
![]() |
e631f69621 | ||
![]() |
ce8760a39a | ||
![]() |
ff952956de | ||
![]() |
28f3ff4971 | ||
![]() |
19e728c3cb | ||
![]() |
269773ed6b | ||
![]() |
e0d32417e1 | ||
![]() |
9fa6083bed | ||
![]() |
4d2fccdfb4 | ||
![]() |
c1c4bdfe94 | ||
![]() |
8a0e9e8b61 | ||
![]() |
1190e14171 | ||
![]() |
00292b177a | ||
![]() |
88de57f984 | ||
![]() |
61ddf38892 | ||
![]() |
52b3540ec3 | ||
![]() |
5f831958c3 | ||
![]() |
c3d4698af3 | ||
![]() |
bd6e83217d | ||
![]() |
50ec49d9a2 | ||
![]() |
dc3a089070 | ||
![]() |
530e380178 | ||
![]() |
10e4387add | ||
![]() |
e925bc3aa8 | ||
![]() |
427b3a7560 | ||
![]() |
c8da950725 | ||
![]() |
743c5b8196 | ||
![]() |
5e62abea57 | ||
![]() |
6bfc545582 | ||
![]() |
411108a2d2 | ||
![]() |
308a6fa9e4 | ||
![]() |
2dc7b785d0 | ||
![]() |
0e69e9e839 |
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -127,10 +127,10 @@ jobs:
|
||||
zip -q -r NapCat.Framework.Windows.Once.zip *
|
||||
cd ..
|
||||
mv ./NapCat.Framework.Windows.Once/NapCat.Framework.Windows.Once.zip ./
|
||||
mv ./external/napcat.packet.arm64 ./
|
||||
mv ./external/napcat.packet.exe ./
|
||||
mv ./external/napcat.packet.linux ./
|
||||
mv ./external/napcat.packet.production.py ./
|
||||
mv ./external/packet/napcat.packet.arm64 ./
|
||||
mv ./external/packet/napcat.packet.exe ./
|
||||
mv ./external/packet/napcat.packet.linux ./
|
||||
mv ./external/packet/napcat.packet.production.py ./
|
||||
- name: Extract version from tag
|
||||
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
||||
|
||||
|
@@ -25,7 +25,6 @@ NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
|
||||
**首次使用**请务必查看如下文档看使用教程
|
||||
|
||||
### 文档地址
|
||||
[Github.IO](https://napneko.github.io/)
|
||||
|
||||
[Cloudflare.Worker](https://doc.napneko.icu/)
|
||||
|
||||
@@ -33,6 +32,7 @@ NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
|
||||
|
||||
[Cloudflare.Pages](https://napneko.pages.dev/)
|
||||
|
||||
[Github.IO](https://napneko.github.io/)
|
||||
## 回家旅途
|
||||
[QQ Group](https://qm.qq.com/q/VfjAq5HIMS)
|
||||
|
||||
|
BIN
external/LiteLoaderWrapper.zip
vendored
BIN
external/LiteLoaderWrapper.zip
vendored
Binary file not shown.
@@ -4,7 +4,7 @@
|
||||
"name": "NapCatQQ",
|
||||
"slug": "NapCat.Framework",
|
||||
"description": "高性能的 OneBot 11 协议实现",
|
||||
"version": "3.3.12",
|
||||
"version": "3.6.1",
|
||||
"icon": "./logo.png",
|
||||
"authors": [
|
||||
{
|
||||
|
@@ -2,7 +2,7 @@
|
||||
"name": "napcat",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "3.3.12",
|
||||
"version": "3.6.1",
|
||||
"scripts": {
|
||||
"build:framework": "vite build --mode framework",
|
||||
"build:shell": "vite build --mode shell",
|
||||
@@ -13,6 +13,7 @@
|
||||
"devDependencies": {
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"@log4js-node/log4js-api": "^1.0.2",
|
||||
"@napneko/nap-proto-core": "^0.0.2",
|
||||
"@protobuf-ts/runtime": "^2.9.4",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-typescript": "^11.1.6",
|
||||
@@ -39,7 +40,7 @@
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.2.6",
|
||||
"vite-plugin-cp": "^4.0.8",
|
||||
"vite-tsconfig-paths": "^4.3.2"
|
||||
"vite-tsconfig-paths": "^5.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^5.0.0",
|
||||
|
@@ -40,14 +40,14 @@ try {
|
||||
// 更新版本,构建安全的sed命令
|
||||
const safeScriptContent = `
|
||||
#!/bin/bash
|
||||
git config --global user.email "bot@test.wumiao.wang"
|
||||
git config --global user.name "Version"
|
||||
sed -i "s/\\\"version\\\": \\\"${currentVersion}\\\"/\\\"version\\\": \\\"${targetVersion}\\\"/g" package.json
|
||||
sed -i "s/\\\"version\\\": \\\"${manifestCurrentVersion}\\\"/\\\"version\\\": \\\"${manifestTargetVersion}\\\"/g" manifest.json
|
||||
sed -i "s/napCatVersion = '\\${currentVersion}'/napCatVersion = '\\${targetVersion}'/g" ./src/common/version.ts
|
||||
sed -i 's/SettingButton("V[0-9]\+\.[0-9]\+\.[0-9]\+", "napcat-update-button", "secondary")/SettingButton("'"${NEW_VERSION}"'", "napcat-update-button", "secondary")/g' ./static/assets/renderer.js
|
||||
git config --global user.email "nanaeonn@outlook.com"
|
||||
git config --global user.name "Mlikiowa"
|
||||
sed -i "s/\\"version\\": \\"${currentVersion}\\"/\\"version\\": \\"${targetVersion}\\"/g" package.json
|
||||
sed -i "s/\\"version\\": \\"${manifestCurrentVersion}\\"/\\"version\\": \\"${targetVersion}\\"/g" manifest.json
|
||||
sed -i "s/napCatVersion = '.*'/napCatVersion = '${targetVersion}'/g" ./src/common/version.ts
|
||||
sed -i "s/SettingButton(\\"V.*\\", \\"napcat-update-button\\", \\"secondary\\")/SettingButton(\\"V${targetVersion}\\", \\"napcat-update-button\\", \\"secondary\\")/g" ./static/assets/renderer.js
|
||||
git add .
|
||||
git commit -m "chore:version change"
|
||||
git commit -m "release: v${targetVersion}"
|
||||
git push -u origin main`;
|
||||
writeScriptToFile(safeScriptContent);
|
||||
}
|
||||
|
@@ -9,6 +9,15 @@ interface InternalMapKey {
|
||||
checker: ((...args: any[]) => boolean) | undefined;
|
||||
}
|
||||
|
||||
type EnsureFunc<T> = T extends (...args: any) => any ? T : never;
|
||||
|
||||
type FuncKeys<T> = Extract<
|
||||
{
|
||||
[K in keyof T]: EnsureFunc<T[K]> extends never ? never : K;
|
||||
}[keyof T],
|
||||
string
|
||||
>;
|
||||
|
||||
export type ListenerClassBase = Record<string, string>;
|
||||
|
||||
export class NTEventWrapper {
|
||||
@@ -43,10 +52,8 @@ export class NTEventWrapper {
|
||||
|
||||
createEventFunction<
|
||||
Service extends keyof ServiceNamingMapping,
|
||||
ServiceMethod extends Exclude<keyof ServiceNamingMapping[Service], symbol>,
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
T extends (...args: any) => any = ServiceNamingMapping[Service][ServiceMethod],
|
||||
ServiceMethod extends FuncKeys<ServiceNamingMapping[Service]>,
|
||||
T extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]>,
|
||||
>(eventName: `${Service}/${ServiceMethod}`): T | undefined {
|
||||
const eventNameArr = eventName.split('/');
|
||||
type eventType = {
|
||||
@@ -98,10 +105,8 @@ export class NTEventWrapper {
|
||||
|
||||
async callNoListenerEvent<
|
||||
Service extends keyof ServiceNamingMapping,
|
||||
ServiceMethod extends Exclude<keyof ServiceNamingMapping[Service], symbol>,
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
EventType extends (...args: any) => any = ServiceNamingMapping[Service][ServiceMethod],
|
||||
ServiceMethod extends FuncKeys<ServiceNamingMapping[Service]>,
|
||||
EventType extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]>,
|
||||
>(
|
||||
serviceAndMethod: `${Service}/${ServiceMethod}`,
|
||||
...args: Parameters<EventType>
|
||||
@@ -111,10 +116,8 @@ export class NTEventWrapper {
|
||||
|
||||
async registerListen<
|
||||
Listener extends keyof ListenerNamingMapping,
|
||||
ListenerMethod extends Exclude<keyof ListenerNamingMapping[Listener], symbol>,
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
ListenerType extends (...args: any) => any = ListenerNamingMapping[Listener][ListenerMethod],
|
||||
ListenerMethod extends FuncKeys<ListenerNamingMapping[Listener]>,
|
||||
ListenerType extends (...args: any) => any = EnsureFunc<ListenerNamingMapping[Listener][ListenerMethod]>,
|
||||
>(
|
||||
listenerAndMethod: `${Listener}/${ListenerMethod}`,
|
||||
waitTimes = 1,
|
||||
@@ -164,15 +167,11 @@ export class NTEventWrapper {
|
||||
|
||||
async callNormalEventV2<
|
||||
Service extends keyof ServiceNamingMapping,
|
||||
ServiceMethod extends Exclude<keyof ServiceNamingMapping[Service], symbol>,
|
||||
ServiceMethod extends FuncKeys<ServiceNamingMapping[Service]>,
|
||||
Listener extends keyof ListenerNamingMapping,
|
||||
ListenerMethod extends Exclude<keyof ListenerNamingMapping[Listener], symbol>,
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
EventType extends (...args: any) => any = ServiceNamingMapping[Service][ServiceMethod],
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
ListenerType extends (...args: any) => any = ListenerNamingMapping[Listener][ListenerMethod]
|
||||
ListenerMethod extends FuncKeys<ListenerNamingMapping[Listener]>,
|
||||
EventType extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]>,
|
||||
ListenerType extends (...args: any) => any = EnsureFunc<ListenerNamingMapping[Listener][ListenerMethod]>
|
||||
>(
|
||||
serviceAndMethod: `${Service}/${ServiceMethod}`,
|
||||
listenerAndMethod: `${Listener}/${ListenerMethod}`,
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { PacketMsg } from "@/core/packet/msg/message";
|
||||
import { PacketMsg } from "@/core/packet/message/message";
|
||||
import * as crypto from "node:crypto";
|
||||
|
||||
interface ForwardMsgJson {
|
||||
|
@@ -52,7 +52,7 @@ export class FileNapCatOneBotUUID {
|
||||
const [, , chatType, peerUid, modelId, fileId, fileUUID = undefined] = data;
|
||||
return {
|
||||
peer: {
|
||||
chatType: chatType as any,
|
||||
chatType: +chatType,
|
||||
peerUid: peerUid,
|
||||
},
|
||||
modelId,
|
||||
@@ -89,7 +89,7 @@ export class FileNapCatOneBotUUID {
|
||||
const [, , chatType, peerUid, msgId, elementId, fileUUID = undefined] = data;
|
||||
return {
|
||||
peer: {
|
||||
chatType: chatType as any,
|
||||
chatType: +chatType,
|
||||
peerUid: peerUid,
|
||||
},
|
||||
msgId,
|
||||
@@ -245,3 +245,36 @@ export function stringifyWithBigInt(obj: any) {
|
||||
typeof value === 'bigint' ? value.toString() : value
|
||||
);
|
||||
}
|
||||
|
||||
export function parseAppidFromMajor(nodeMajor: string): string | undefined {
|
||||
const hexSequence = "A4 09 00 00 00 35";
|
||||
const sequenceBytes = Buffer.from(hexSequence.replace(/ /g, ""), "hex");
|
||||
const filePath = path.resolve(nodeMajor);
|
||||
const fileContent = fs.readFileSync(filePath);
|
||||
|
||||
let searchPosition = 0;
|
||||
while (true) {
|
||||
const index = fileContent.indexOf(sequenceBytes, searchPosition);
|
||||
if (index === -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
const start = index + sequenceBytes.length - 1;
|
||||
const end = fileContent.indexOf(0x00, start);
|
||||
if (end === -1) {
|
||||
break;
|
||||
}
|
||||
const content = fileContent.subarray(start, end);
|
||||
if (!content.every(byte => byte === 0x00)) {
|
||||
try {
|
||||
return content.toString("utf-8");
|
||||
} catch (error) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
searchPosition = end + 1;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
export class LRUCache<K, V> {
|
||||
private capacity: number;
|
||||
private cache: Map<K, V>;
|
||||
public cache: Map<K, V>;
|
||||
|
||||
constructor(capacity: number) {
|
||||
this.capacity = capacity;
|
||||
|
@@ -23,10 +23,10 @@ export class LimitedHashTable<K, V> {
|
||||
}
|
||||
while (this.keyToValue.size > this.maxSize || this.valueToKey.size > this.maxSize) {
|
||||
const oldestKey = this.keyToValue.keys().next().value;
|
||||
// @ts-ignore
|
||||
this.valueToKey.delete(this.keyToValue.get(oldestKey)!);
|
||||
// @ts-ignore
|
||||
this.keyToValue.delete(oldestKey);
|
||||
if (oldestKey !== undefined) {
|
||||
this.valueToKey.delete(this.keyToValue.get(oldestKey) as V);
|
||||
this.keyToValue.delete(oldestKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import fs from 'node:fs';
|
||||
import { systemPlatform } from '@/common/system';
|
||||
import { getDefaultQQVersionConfigInfo, getQQPackageInfoPath, getQQVersionConfigPath } from './helper';
|
||||
import { getDefaultQQVersionConfigInfo, getQQPackageInfoPath, getQQVersionConfigPath, parseAppidFromMajor } from './helper';
|
||||
import AppidTable from '@/core/external/appid.json';
|
||||
import { LogWrapper } from './log';
|
||||
import { getMajorPath } from '@/core';
|
||||
|
||||
export class QQBasicInfoWrapper {
|
||||
QQMainPath: string | undefined;
|
||||
@@ -72,6 +73,7 @@ export class QQBasicInfoWrapper {
|
||||
}
|
||||
|
||||
getAppidV2(): { appid: string; qua: string } {
|
||||
// 通过已有表 性能好
|
||||
const appidTbale = AppidTable as unknown as QQAppidTableType;
|
||||
const fullVersion = this.getFullQQVesion();
|
||||
if (fullVersion) {
|
||||
@@ -80,10 +82,25 @@ export class QQBasicInfoWrapper {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
// else
|
||||
// 通过Major拉取 性能差
|
||||
try {
|
||||
const majorAppid = this.getAppidV2ByMajor(fullVersion);
|
||||
if (majorAppid) {
|
||||
this.context.logger.log(`[QQ版本兼容性检测] 当前版本Appid未内置 通过Major获取 为了更好的性能请尝试更新NapCat`);
|
||||
return { appid: majorAppid, qua: this.getQUAFallback() };
|
||||
}
|
||||
} catch (error) {
|
||||
this.context.logger.log(`[QQ版本兼容性检测] 通过Major 获取Appid异常 请检测NapCat/QQNT是否正常`);
|
||||
}
|
||||
// 最终兜底为老版本
|
||||
this.context.logger.log(`[QQ版本兼容性检测] 获取Appid异常 请检测NapCat/QQNT是否正常`);
|
||||
this.context.logger.log(`[QQ版本兼容性检测] ${fullVersion} 版本兼容性不佳,可能会导致一些功能无法正常使用`,);
|
||||
return { appid: this.getAppIdFallback(), qua: this.getQUAFallback() };
|
||||
}
|
||||
getAppidV2ByMajor(QQVersion: string) {
|
||||
const majorPath = getMajorPath(QQVersion);
|
||||
const appid = parseAppidFromMajor(majorPath);
|
||||
return appid;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1 +1 @@
|
||||
export const napCatVersion = '3.3.12';
|
||||
export const napCatVersion = '3.6.1';
|
||||
|
@@ -54,7 +54,9 @@ export class NTQQGroupApi {
|
||||
}, pskey);
|
||||
}
|
||||
async getGroupShutUpMemberList(groupCode: string) {
|
||||
return this.context.session.getGroupService().getGroupShutUpMemberList(groupCode);
|
||||
const data = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onShutUpMemberListChanged', 1, 1000, (group_id) => group_id === groupCode);
|
||||
this.context.session.getGroupService().getGroupShutUpMemberList(groupCode);
|
||||
return (await data)[1];
|
||||
}
|
||||
async clearGroupNotifiesUnreadCount(uk: boolean) {
|
||||
return this.context.session.getGroupService().clearGroupNotifiesUnreadCount(uk);
|
||||
@@ -320,10 +322,10 @@ export class NTQQGroupApi {
|
||||
infos: Map<string, GroupMember>;
|
||||
finish: boolean;
|
||||
hasNext: boolean | undefined;
|
||||
}>{
|
||||
}> {
|
||||
const sceneId = this.context.session.getGroupService().createMemberListScene(groupQQ, 'groupMemberList_MainWindow_1');
|
||||
const once = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onMemberListChange', 0, timeout, (params) => params.sceneId === sceneId)
|
||||
.catch(() => {});
|
||||
.catch(() => { });
|
||||
const result = await this.context.session.getGroupService().getNextMemberList(sceneId, undefined, num);
|
||||
if (result.errCode !== 0) {
|
||||
throw new Error('获取群成员列表出错,' + result.errMsg);
|
||||
@@ -337,21 +339,55 @@ export class NTQQGroupApi {
|
||||
}
|
||||
this.context.session.getGroupService().destroyMemberListScene(sceneId);
|
||||
return {
|
||||
infos: resMode2?.infos || result.result.infos,
|
||||
infos: new Map([...(resMode2?.infos ?? []), ...result.result.infos]),
|
||||
finish: result.result.finish,
|
||||
hasNext: resMode2?.hasNext,
|
||||
};
|
||||
}
|
||||
|
||||
async GetGroupMembersV3(groupQQ: string, num = 3000, timeout = 2500): Promise<{
|
||||
infos: Map<string, GroupMember>;
|
||||
finish: boolean;
|
||||
hasNext: boolean | undefined;
|
||||
listenerMode: boolean;
|
||||
}> {
|
||||
const sceneId = this.context.session.getGroupService().createMemberListScene(groupQQ, 'groupMemberList_MainWindow_1');
|
||||
const once = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onMemberListChange', 0, timeout, (params) => params.sceneId === sceneId)
|
||||
.catch(() => { });
|
||||
const result = await this.context.session.getGroupService().getNextMemberList(sceneId, undefined, num);
|
||||
if (result.errCode !== 0) {
|
||||
throw new Error('获取群成员列表出错,' + result.errMsg);
|
||||
}
|
||||
let resMode2;
|
||||
if (result.result.finish && result.result.infos.size === 0) {
|
||||
const ret = (await once)?.[0];
|
||||
if (ret) {
|
||||
resMode2 = ret;
|
||||
}
|
||||
}
|
||||
this.context.session.getGroupService().destroyMemberListScene(sceneId);
|
||||
//console.log('GetGroupMembersV3 len :', result.result.infos.size, resMode2?.infos.size, groupQQ);
|
||||
return {
|
||||
infos: new Map([...(resMode2?.infos ?? []), ...result.result.infos]),
|
||||
finish: result.result.finish,
|
||||
hasNext: resMode2?.hasNext,
|
||||
listenerMode: resMode2?.hasNext !== undefined ? true : false
|
||||
};
|
||||
}
|
||||
|
||||
async getGroupMembersV2(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
|
||||
let res = await this.tryGetGroupMembersV2(true, groupQQ);
|
||||
if (res.hasNext || !res.finish || res.infos.size === 0) {
|
||||
res = await this.tryGetGroupMembersV2(false, groupQQ, num);
|
||||
//console.log('getGroupMembers -->', groupQQ);
|
||||
let res = await this.GetGroupMembersV3(groupQQ, num);
|
||||
let ret = res.infos;
|
||||
if (res.infos.size === 0 && !res.listenerMode) {
|
||||
res = await this.GetGroupMembersV3(groupQQ, num);
|
||||
ret = res.infos;
|
||||
}
|
||||
if ((res.infos.size === 0 || res.infos.size === 30) && res.finish) {
|
||||
res = await this.tryGetGroupMembersV2(true, groupQQ, num);
|
||||
if (res.infos.size === 0) {
|
||||
ret = (await this.getGroupMemberAll(groupQQ)).result.infos;
|
||||
}
|
||||
return res.infos;
|
||||
//console.log("<---------------")
|
||||
return ret;
|
||||
}
|
||||
|
||||
async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
|
||||
|
@@ -3,6 +3,9 @@ import { InstanceContext, NapCatCore } from '@/core';
|
||||
import { GeneralCallResult } from '@/core/services/common';
|
||||
|
||||
export class NTQQMsgApi {
|
||||
getMsgByClientSeqAndTime(peer: Peer, replyMsgClientSeq: string, replyMsgTime: string) {
|
||||
return this.context.session.getMsgService().getMsgByClientSeqAndTime(peer, replyMsgClientSeq, replyMsgTime);
|
||||
}
|
||||
// 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
|
||||
@@ -22,7 +25,9 @@ export class NTQQMsgApi {
|
||||
async sendShowInputStatusReq(peer: Peer, eventType: number) {
|
||||
return this.context.session.getMsgService().sendShowInputStatusReq(peer.chatType, eventType, peer.peerUid);
|
||||
}
|
||||
|
||||
async getSourceOfReplyMsgV2(peer: Peer, clientSeq: string, time: string) {
|
||||
return this.context.session.getMsgService().getSourceOfReplyMsgV2(peer, clientSeq, time);
|
||||
}
|
||||
async getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, count: number = 20) {
|
||||
//注意此处emojiType 可选值一般为1-2 2好像是unicode表情dec值 大部分情况 Taged Mlikiowa
|
||||
return this.context.session.getMsgService().getMsgEmojiLikesList(peer, msgSeq, emojiId, emojiType, '', false, count);
|
||||
@@ -106,9 +111,9 @@ export class NTQQMsgApi {
|
||||
pageLimit: 1,
|
||||
});
|
||||
}
|
||||
//@deprecated
|
||||
async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, z: boolean) {
|
||||
return await this.context.session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, z);
|
||||
// 客户端还在用别慌
|
||||
async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, isReverseOrder: boolean) {
|
||||
return await this.context.session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, isReverseOrder);
|
||||
}
|
||||
async getMsgExBySeq(peer: Peer, msgSeq: string) {
|
||||
const DateNow = Math.floor(Date.now() / 1000);
|
||||
|
@@ -1,23 +1,30 @@
|
||||
import * as crypto from 'crypto';
|
||||
import * as os from 'os';
|
||||
import { ChatType, InstanceContext, NapCatCore } from '..';
|
||||
import offset from '@/core/external/offset.json';
|
||||
import { PacketClient, RecvPacketData } from '@/core/packet/client';
|
||||
import { PacketSession } from "@/core/packet/session";
|
||||
import { OidbPacket, PacketHexStr } from "@/core/packet/packer";
|
||||
import { NapProtoMsg } from '@/core/packet/proto/NapProto';
|
||||
import { NapProtoMsg, NapProtoEncodeStructType, NapProtoDecodeStructType } from "@napneko/nap-proto-core";
|
||||
import { OidbSvcTrpcTcp0X9067_202_Rsp_Body } from '@/core/packet/proto/oidb/Oidb.0x9067_202';
|
||||
import { OidbSvcTrpcTcpBase, OidbSvcTrpcTcpBaseRsp } from '@/core/packet/proto/oidb/OidbBase';
|
||||
import { OidbSvcTrpcTcp0XFE1_2RSP } from '@/core/packet/proto/oidb/Oidb.0XFE1_2';
|
||||
import { LogWrapper } from "@/common/log";
|
||||
import { SendLongMsgResp } from "@/core/packet/proto/message/action";
|
||||
import { PacketMsg } from "@/core/packet/msg/message";
|
||||
import { PacketMsg } from "@/core/packet/message/message";
|
||||
import { OidbSvcTrpcTcp0x6D6Response } from "@/core/packet/proto/oidb/Oidb.0x6D6";
|
||||
import {
|
||||
PacketMsgFileElement,
|
||||
PacketMsgPicElement,
|
||||
PacketMsgPttElement,
|
||||
PacketMsgVideoElement
|
||||
} from "@/core/packet/msg/element";
|
||||
} from "@/core/packet/message/element";
|
||||
import { MiniAppReqParams, MiniAppRawData } from "@/core/packet/entities/miniApp";
|
||||
import { MiniAppAdaptShareInfoResp } from "@/core/packet/proto/action/miniAppAdaptShareInfo";
|
||||
import { AIVoiceChatType, AIVoiceItemList } from "@/core/packet/entities/aiChat";
|
||||
import { OidbSvcTrpcTcp0X929B_0Resp, OidbSvcTrpcTcp0X929D_0Resp } from "@/core/packet/proto/oidb/Oidb.0x929";
|
||||
import { IndexNode, MsgInfo } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
|
||||
import { NTV2RichMediaResp } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaResp";
|
||||
import { RecvPacketData } from "@/core/packet/client/client";
|
||||
|
||||
|
||||
interface OffsetType {
|
||||
@@ -33,7 +40,6 @@ export class NTQQPacketApi {
|
||||
context: InstanceContext;
|
||||
core: NapCatCore;
|
||||
logger: LogWrapper;
|
||||
serverUrl: string | undefined;
|
||||
qqVersion: string | undefined;
|
||||
packetSession: PacketSession | undefined;
|
||||
|
||||
@@ -42,32 +48,27 @@ export class NTQQPacketApi {
|
||||
this.core = core;
|
||||
this.logger = core.context.logger;
|
||||
this.packetSession = undefined;
|
||||
const config = this.core.configLoader.configData;
|
||||
if (config && config.packetServer && config.packetServer.length > 0) {
|
||||
const serverUrl = this.core.configLoader.configData.packetServer ?? '127.0.0.1:8086';
|
||||
this.InitSendPacket(serverUrl, this.context.basicInfoWrapper.getFullQQVesion())
|
||||
.then()
|
||||
.catch(this.core.context.logger.logError.bind(this.core.context.logger));
|
||||
} else {
|
||||
this.core.context.logger.logWarn('PacketServer未配置,NapCat.Packet将不会加载!');
|
||||
}
|
||||
this.InitSendPacket(this.context.basicInfoWrapper.getFullQQVesion())
|
||||
.then()
|
||||
.catch(this.core.context.logger.logError.bind(this.core.context.logger));
|
||||
}
|
||||
|
||||
get available(): boolean {
|
||||
return this.packetSession?.client.available ?? false;
|
||||
}
|
||||
|
||||
async InitSendPacket(serverUrl: string, qqversion: string) {
|
||||
this.serverUrl = serverUrl;
|
||||
async InitSendPacket(qqversion: string) {
|
||||
this.qqVersion = qqversion;
|
||||
const offsetTable: OffsetType = offset;
|
||||
const table = offsetTable[qqversion + '-' + os.arch()];
|
||||
const table = typedOffset[qqversion + '-' + os.arch()];
|
||||
if (!table) {
|
||||
this.logger.logError('PacketServer Offset table not found for QQVersion: ', qqversion + '-' + os.arch());
|
||||
this.logger.logError('[Core] [Packet] PacketServer Offset table not found for QQVersion: ', qqversion + '-' + os.arch());
|
||||
return false;
|
||||
}
|
||||
const url = 'ws://' + this.serverUrl + '/ws';
|
||||
this.packetSession = new PacketSession(this.core.context.logger, new PacketClient(url, this.core));
|
||||
if (this.core.configLoader.configData.packetBackend === 'disable') {
|
||||
this.logger.logWarn('[Core] [Packet] 已禁用Packet后端,NapCat.Packet将不会加载!');
|
||||
return false;
|
||||
}
|
||||
this.packetSession = new PacketSession(this.core);
|
||||
const cb = () => {
|
||||
if (this.packetSession && this.packetSession.client) {
|
||||
this.packetSession.client.init(process.pid, table.recv, table.send).then().catch(this.logger.logError.bind(this.logger));
|
||||
@@ -185,4 +186,55 @@ export class NTQQPacketApi {
|
||||
}
|
||||
return `https://${resp.download.downloadDns}/ftn_handler/${Buffer.from(resp.download.downloadUrl).toString('hex')}/?fname=`;
|
||||
}
|
||||
|
||||
async sendGroupPttFileDownloadReq(groupUin: number, node: NapProtoEncodeStructType<typeof IndexNode>) {
|
||||
const data = this.packetSession?.packer.packGroupPttFileDownloadReq(groupUin, node);
|
||||
const ret = await this.sendOidbPacket(data!, true);
|
||||
const body = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(Buffer.from(ret.hex_data, 'hex')).body;
|
||||
const resp = new NapProtoMsg(NTV2RichMediaResp).decode(body);
|
||||
const info = resp.download.info;
|
||||
return `https://${info.domain}${info.urlPath}${resp.download.rKeyParam}`;
|
||||
}
|
||||
|
||||
async sendMiniAppShareInfoReq(param: MiniAppReqParams) {
|
||||
const data = this.packetSession?.packer.packMiniAppAdaptShareInfo(param);
|
||||
const ret = await this.sendPacket("LightAppSvc.mini_app_share.AdaptShareInfo", data!, true);
|
||||
const body = new NapProtoMsg(MiniAppAdaptShareInfoResp).decode(Buffer.from(ret.hex_data, 'hex'));
|
||||
return JSON.parse(body.content.jsonContent) as MiniAppRawData;
|
||||
}
|
||||
|
||||
async sendFetchAiVoiceListReq(groupUin: number, chatType: AIVoiceChatType) : Promise<AIVoiceItemList[] | null> {
|
||||
const data = this.packetSession?.packer.packFetchAiVoiceListReq(groupUin, chatType);
|
||||
const ret = await this.sendOidbPacket(data!, true);
|
||||
const body = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(Buffer.from(ret.hex_data, 'hex')).body;
|
||||
const resp = new NapProtoMsg(OidbSvcTrpcTcp0X929D_0Resp).decode(body);
|
||||
if (!resp.content) return null;
|
||||
return resp.content.map((item) => {
|
||||
return {
|
||||
category: item.category,
|
||||
voices: item.voices
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async sendAiVoiceChatReq(groupUin: number, voiceId: string, text: string, chatType: AIVoiceChatType): Promise<NapProtoDecodeStructType<typeof MsgInfo>> {
|
||||
let reqTime = 0;
|
||||
const reqMaxTime = 30;
|
||||
const sessionId = crypto.randomBytes(4).readUInt32BE(0);
|
||||
while (true) {
|
||||
if (reqTime >= reqMaxTime) {
|
||||
throw new Error(`sendAiVoiceChatReq failed after ${reqMaxTime} times`);
|
||||
}
|
||||
reqTime++;
|
||||
const data = this.packetSession?.packer.packAiVoiceChatReq(groupUin, voiceId, text, chatType, sessionId);
|
||||
const ret = await this.sendOidbPacket(data!, true);
|
||||
const body = new NapProtoMsg(OidbSvcTrpcTcpBase).decode(Buffer.from(ret.hex_data, 'hex'));
|
||||
if (body.errorCode) {
|
||||
throw new Error(`sendAiVoiceChatReq retCode: ${body.errorCode} error: ${body.errorMsg}`);
|
||||
}
|
||||
const resp = new NapProtoMsg(OidbSvcTrpcTcp0X929B_0Resp).decode(body.body);
|
||||
if (!resp.msgInfo) continue;
|
||||
return resp.msgInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -18,7 +18,7 @@ export class NTQQUserApi {
|
||||
async getStatusByUid(uid: string) {
|
||||
return this.context.session.getProfileService().getStatus(uid);
|
||||
}
|
||||
async getProfileLike(uid: string) {
|
||||
async getProfileLike(uid: string, start: number, count: number) {
|
||||
return this.context.session.getProfileLikeService().getBuddyProfileLike({
|
||||
friendUids: [uid],
|
||||
basic: 1,
|
||||
@@ -26,8 +26,8 @@ export class NTQQUserApi {
|
||||
favorite: 0,
|
||||
userProfile: 1,
|
||||
type: 2,
|
||||
start: 0,
|
||||
limit: 20,
|
||||
start: start,
|
||||
limit: count,
|
||||
});
|
||||
}
|
||||
async fetchOtherProfileLike(uid: string) {
|
||||
|
@@ -64,7 +64,7 @@ type ElementFullBase = Omit<MessageElement, 'elementType' | 'elementId' | 'extBu
|
||||
|
||||
type ElementBase<
|
||||
K extends keyof ElementFullBase,
|
||||
S extends Partial<{ [P in K]: keyof NonNullable<ElementFullBase[P]> | Array<keyof NonNullable<ElementFullBase[P]>> }> = {}
|
||||
S extends Partial<{ [P in K]: keyof NonNullable<ElementFullBase[P]> | Array<keyof NonNullable<ElementFullBase[P]>> }> = object
|
||||
> = {
|
||||
[P in K]:
|
||||
S[P] extends Array<infer U>
|
||||
@@ -822,6 +822,8 @@ export interface RawMessage {
|
||||
elements: MessageElement[];
|
||||
|
||||
sourceType: MsgSourceType;
|
||||
|
||||
isOnlineMsg: boolean;
|
||||
}
|
||||
export interface QueryMsgsParams {
|
||||
chatInfo: Peer;
|
||||
|
@@ -43,6 +43,50 @@ export enum GroupInviteType {
|
||||
BYGROUPMEMBER,
|
||||
BYDISCUSSMEMBER
|
||||
}
|
||||
export interface ShutUpGroupHonor {
|
||||
[key: string]: number;
|
||||
}
|
||||
|
||||
export interface ShutUpGroupMember {
|
||||
uid: string;
|
||||
qid: string;
|
||||
uin: string;
|
||||
nick: string;
|
||||
remark: string;
|
||||
cardType: number;
|
||||
cardName: string;
|
||||
role: number;
|
||||
avatarPath: string;
|
||||
shutUpTime: number;
|
||||
isDelete: boolean;
|
||||
isSpecialConcerned: boolean;
|
||||
isSpecialShield: boolean;
|
||||
isRobot: boolean;
|
||||
groupHonor: ShutUpGroupHonor;
|
||||
memberRealLevel: number;
|
||||
memberLevel: number;
|
||||
globalGroupLevel: number;
|
||||
globalGroupPoint: number;
|
||||
memberTitleId: number;
|
||||
memberSpecialTitle: string;
|
||||
specialTitleExpireTime: string;
|
||||
userShowFlag: number;
|
||||
userShowFlagNew: number;
|
||||
richFlag: number;
|
||||
mssVipType: number;
|
||||
bigClubLevel: number;
|
||||
bigClubFlag: number;
|
||||
autoRemark: string;
|
||||
creditLevel: number;
|
||||
joinTime: number;
|
||||
lastSpeakTime: number;
|
||||
memberFlag: number;
|
||||
memberFlagExt: number;
|
||||
memberMobileFlag: number;
|
||||
memberFlagExt2: number;
|
||||
isSpecialShielded: boolean;
|
||||
cardNameId: number;
|
||||
}
|
||||
|
||||
export interface GroupNotify {
|
||||
seq: string; // 通知序列号
|
||||
|
18
src/core/external/appid.json
vendored
18
src/core/external/appid.json
vendored
@@ -51,12 +51,28 @@
|
||||
"appid": 537249739,
|
||||
"qua": "V1_WIN_NQ_9.9.16_28788_GW_B"
|
||||
},
|
||||
"9.9.16-28971":{
|
||||
"9.9.16-28971": {
|
||||
"appid": 537249775,
|
||||
"qua": "V1_WIN_NQ_9.9.16_28971_GW_B"
|
||||
},
|
||||
"3.2.13-28971": {
|
||||
"appid": 537249848,
|
||||
"qua": "V1_LNX_NQ_3.2.13_28971_GW_B"
|
||||
},
|
||||
"6.9.58-28971": {
|
||||
"appid": 537249826,
|
||||
"qua": "V1_MAC_NQ_6.9.58_28971_GW_B"
|
||||
},
|
||||
"9.9.16-29271": {
|
||||
"appid": 537249813,
|
||||
"qua": "V1_WIN_NQ_9.9.16_29271_GW_B"
|
||||
},
|
||||
"3.2.13-29271": {
|
||||
"appid": 537249913,
|
||||
"qua": "V1_LNX_NQ_3.2.13_29271_GW_B"
|
||||
},
|
||||
"6.9.59-29271": {
|
||||
"appid": 537249863,
|
||||
"qua": "V1_MAC_NQ_6.9.59_29271_GW_B"
|
||||
}
|
||||
}
|
3
src/core/external/napcat.json
vendored
3
src/core/external/napcat.json
vendored
@@ -3,5 +3,6 @@
|
||||
"consoleLog": true,
|
||||
"fileLogLevel": "debug",
|
||||
"consoleLogLevel": "info",
|
||||
"packetBackend": "auto",
|
||||
"packetServer": ""
|
||||
}
|
||||
}
|
||||
|
22
src/core/external/offset.json
vendored
22
src/core/external/offset.json
vendored
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"6.9.56-28418-arm64": {
|
||||
"send": "4471360",
|
||||
"recv": "4473BCC"
|
||||
},
|
||||
"3.2.12-28418-x64": {
|
||||
"recv": "A0723E0",
|
||||
"send": "A06EAE0"
|
||||
@@ -35,8 +39,20 @@
|
||||
"send": "6E91318",
|
||||
"recv": "6E94B50"
|
||||
},
|
||||
"6.9.56-28418-arm64": {
|
||||
"send": "4471360",
|
||||
"recv": "4473BCC"
|
||||
"6.9.58-28971-arm64": {
|
||||
"send": "449ACA0",
|
||||
"recv": "449D50C"
|
||||
},
|
||||
"9.9.16-29271-x64": {
|
||||
"send": "3833510",
|
||||
"recv": "3837944"
|
||||
},
|
||||
"3.2.13-29271-x64": {
|
||||
"send": "A11E680",
|
||||
"recv": "A121F80"
|
||||
},
|
||||
"3.2.13-29271-arm64": {
|
||||
"send": "6ECA098",
|
||||
"recv": "6ECD8D0"
|
||||
}
|
||||
}
|
@@ -62,7 +62,26 @@ export function loadQQWrapper(QQVersion: string): WrapperNodeApi {
|
||||
process.dlopen(nativemodule, wrapperNodePath);
|
||||
return nativemodule.exports;
|
||||
}
|
||||
|
||||
export function getMajorPath(QQVersion: string): string {
|
||||
// major.node
|
||||
let appPath;
|
||||
if (os.platform() === 'darwin') {
|
||||
appPath = path.resolve(path.dirname(process.execPath), '../Resources/app');
|
||||
} else if (os.platform() === 'linux') {
|
||||
appPath = path.resolve(path.dirname(process.execPath), './resources/app');
|
||||
} else {
|
||||
appPath = path.resolve(path.dirname(process.execPath), `./versions/${QQVersion}/`);
|
||||
}
|
||||
let majorPath = path.resolve(appPath, 'major.node');
|
||||
if (!fs.existsSync(majorPath)) {
|
||||
majorPath = path.join(appPath, `./resources/app/major.node`);
|
||||
}
|
||||
//老版本兼容 未来去掉
|
||||
if (!fs.existsSync(majorPath)) {
|
||||
majorPath = path.join(path.dirname(process.execPath), `./resources/app/versions/${QQVersion}/major.node`);
|
||||
}
|
||||
return majorPath;
|
||||
}
|
||||
export class NapCatCore {
|
||||
readonly context: InstanceContext;
|
||||
readonly apis: StableNTApiWrapper;
|
||||
@@ -100,7 +119,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(
|
||||
@@ -140,7 +159,7 @@ export class NapCatCore {
|
||||
};
|
||||
//await sleep(2500);
|
||||
this.context.session.getMsgService().addKernelMsgListener(
|
||||
proxiedListenerOf(msgListener, this.context.logger) as any,
|
||||
proxiedListenerOf(msgListener, this.context.logger),
|
||||
);
|
||||
|
||||
const profileListener = new NodeIKernelProfileListener();
|
||||
@@ -236,7 +255,7 @@ export class NapCatCore {
|
||||
}
|
||||
};
|
||||
this.context.session.getGroupService().addKernelGroupListener(
|
||||
proxiedListenerOf(groupListener, this.context.logger) as any,
|
||||
proxiedListenerOf(groupListener, this.context.logger),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -276,7 +295,7 @@ export async function genSessionConfig(
|
||||
d2: '',
|
||||
d2Key: '',
|
||||
machineId: '',
|
||||
platform: systemPlatform, // 3是Windows?
|
||||
platform: systemPlatform, // 3是Windows?
|
||||
platVer: systemVersion, // 系统版本号, 应该可以固定
|
||||
appid: QQVersionAppid,
|
||||
rdeliveryConfig: {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { DataSource, Group, GroupListUpdateType, GroupMember, GroupNotify } from '@/core/entities';
|
||||
import { DataSource, Group, GroupListUpdateType, GroupMember, GroupNotify, ShutUpGroupMember } from '@/core/entities';
|
||||
|
||||
export class NodeIKernelGroupListener {
|
||||
onGroupListInited(listEmpty: boolean): void { }
|
||||
@@ -80,6 +80,6 @@ export class NodeIKernelGroupListener {
|
||||
onSearchMemberChange(...args: unknown[]) {
|
||||
}
|
||||
|
||||
onShutUpMemberListChanged(...args: unknown[]) {
|
||||
onShutUpMemberListChanged(groupCode: string, members: Array<ShutUpGroupMember>) {
|
||||
}
|
||||
}
|
@@ -1,183 +0,0 @@
|
||||
import { LogWrapper } from "@/common/log";
|
||||
import { LRUCache } from "@/common/lru-cache";
|
||||
import WebSocket, { Data } from "ws";
|
||||
import crypto, { createHash } from "crypto";
|
||||
import { NapCatCore } from "@/core";
|
||||
import { OidbPacket, PacketHexStr } from "@/core/packet/packer";
|
||||
|
||||
export interface RecvPacket {
|
||||
type: string, // 仅recv
|
||||
trace_id_md5?: string,
|
||||
data: RecvPacketData
|
||||
}
|
||||
|
||||
export interface RecvPacketData {
|
||||
seq: number
|
||||
cmd: string
|
||||
hex_data: string
|
||||
}
|
||||
|
||||
export class PacketClient {
|
||||
private websocket: WebSocket | undefined;
|
||||
private isConnected: boolean = false;
|
||||
private reconnectAttempts: number = 0;
|
||||
private readonly maxReconnectAttempts: number = 60;//现在暂时不可配置
|
||||
private readonly cb = new LRUCache<string, (json: RecvPacketData) => Promise<void>>(500); // trace_id-type callback
|
||||
private readonly clientUrl: string = '';
|
||||
readonly napCatCore: NapCatCore;
|
||||
private readonly logger: LogWrapper;
|
||||
|
||||
constructor(url: string, core: NapCatCore) {
|
||||
this.clientUrl = url;
|
||||
this.napCatCore = core;
|
||||
this.logger = core.context.logger;
|
||||
}
|
||||
|
||||
get available(): boolean {
|
||||
return this.isConnected && this.websocket !== undefined;
|
||||
}
|
||||
|
||||
private randText(len: number) {
|
||||
let text = '';
|
||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
for (let i = 0; i < len; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
connect(cb: any): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
//this.logger.log.bind(this.logger)(`[Core] [Packet Server] Attempting to connect to ${this.clientUrl}`);
|
||||
this.websocket = new WebSocket(this.clientUrl);
|
||||
this.websocket.on('error', (err) => { }/*this.logger.logError.bind(this.logger)('[Core] [Packet Server] Error:', err.message)*/);
|
||||
|
||||
this.websocket.onopen = () => {
|
||||
this.isConnected = true;
|
||||
this.reconnectAttempts = 0;
|
||||
this.logger.log.bind(this.logger)(`[Core] [Packet Server] 已连接到 ${this.clientUrl}`);
|
||||
cb();
|
||||
resolve();
|
||||
};
|
||||
|
||||
this.websocket.onerror = (error) => {
|
||||
//this.logger.logError.bind(this.logger)(`WebSocket error: ${error}`);
|
||||
reject(new Error(`${error.message}`));
|
||||
};
|
||||
|
||||
this.websocket.onmessage = (event) => {
|
||||
// const message = JSON.parse(event.data.toString());
|
||||
// console.log("Received message:", message);
|
||||
this.handleMessage(event.data).then().catch();
|
||||
};
|
||||
|
||||
this.websocket.onclose = () => {
|
||||
this.isConnected = false;
|
||||
//this.logger.logWarn.bind(this.logger)(`[Core] [Packet Server] Disconnected from ${this.clientUrl}`);
|
||||
this.attemptReconnect(cb);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private attemptReconnect(cb: any): void {
|
||||
try {
|
||||
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
||||
this.reconnectAttempts++;
|
||||
setTimeout(() => {
|
||||
this.connect(cb).catch((error) => {
|
||||
this.logger.logError.bind(this.logger)(`[Core] [Packet Server] 尝试重连失败:${error.message}`);
|
||||
});
|
||||
}, 5000 * this.reconnectAttempts);
|
||||
} else {
|
||||
this.logger.logError.bind(this.logger)(`[Core] [Packet Server] ${this.clientUrl} 已达到最大重连次数!`);
|
||||
}
|
||||
} catch (error: any) {
|
||||
this.logger.logError.bind(this.logger)(`[Core] [Packet Server] 重连时出错: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async registerCallback(trace_id: string, type: string, callback: (json: RecvPacketData) => Promise<void>): Promise<void> {
|
||||
this.cb.put(createHash('md5').update(trace_id).digest('hex') + type, callback);
|
||||
}
|
||||
|
||||
async init(pid: number, recv: string, send: string): Promise<void> {
|
||||
if (!this.isConnected || !this.websocket) {
|
||||
throw new Error("WebSocket is not connected");
|
||||
}
|
||||
const initMessage = {
|
||||
action: 'init',
|
||||
pid: pid,
|
||||
recv: recv,
|
||||
send: send
|
||||
};
|
||||
this.websocket.send(JSON.stringify(initMessage));
|
||||
}
|
||||
|
||||
private async sendCommand(cmd: string, data: string, trace_id: string, rsp: boolean = false, timeout: number = 20000, sendcb: (json: RecvPacketData) => void = () => {
|
||||
}): Promise<RecvPacketData> {
|
||||
return new Promise<RecvPacketData>((resolve, reject) => {
|
||||
if (!this.isConnected || !this.websocket) {
|
||||
throw new Error("WebSocket is not connected");
|
||||
}
|
||||
const commandMessage = {
|
||||
action: 'send',
|
||||
cmd: cmd,
|
||||
data: data,
|
||||
trace_id: trace_id
|
||||
};
|
||||
this.websocket.send(JSON.stringify(commandMessage));
|
||||
if (rsp) {
|
||||
this.registerCallback(trace_id, 'recv', async (json: RecvPacketData) => {
|
||||
clearTimeout(timeoutHandle);
|
||||
resolve(json);
|
||||
});
|
||||
}
|
||||
this.registerCallback(trace_id, 'send', async (json: RecvPacketData) => {
|
||||
sendcb(json);
|
||||
if (!rsp) {
|
||||
clearTimeout(timeoutHandle);
|
||||
resolve(json);
|
||||
}
|
||||
});
|
||||
const timeoutHandle = setTimeout(() => {
|
||||
reject(new Error(`sendCommand timed out after ${timeout} ms for ${cmd} with trace_id ${trace_id}`));
|
||||
}, timeout);
|
||||
});
|
||||
}
|
||||
|
||||
private async handleMessage(message: Data): Promise<void> {
|
||||
try {
|
||||
const json: RecvPacket = JSON.parse(message.toString());
|
||||
const trace_id_md5 = json.trace_id_md5;
|
||||
const action = json?.type ?? 'init';
|
||||
const event = this.cb.get(trace_id_md5 + action);
|
||||
if (event) {
|
||||
await event(json.data);
|
||||
}
|
||||
//console.log("Received message:", json);
|
||||
} catch (error) {
|
||||
this.logger.logError.bind(this.logger)(`Error parsing message: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
async sendPacket(cmd: string, data: PacketHexStr, rsp = false): Promise<RecvPacketData> {
|
||||
// wtfk tx
|
||||
// 校验失败和异常 可能返回undefined
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.available) {
|
||||
this.logger.logError('NapCat.Packet 未初始化!');
|
||||
return undefined;
|
||||
}
|
||||
const md5 = crypto.createHash('md5').update(data).digest('hex');
|
||||
const trace_id = (this.randText(4) + md5 + data).slice(0, data.length / 2);
|
||||
this.sendCommand(cmd, data, trace_id, rsp, 20000, async () => {
|
||||
// await sleep(10);
|
||||
await this.napCatCore.context.session.getMsgService().sendSsoCmdReqByContend(cmd, trace_id);
|
||||
}).then((res) => resolve(res)).catch((e: Error) => reject(e));
|
||||
});
|
||||
}
|
||||
|
||||
async sendOidbPacket(pkt: OidbPacket, rsp = false): Promise<RecvPacketData> {
|
||||
return this.sendPacket(pkt.cmd, pkt.data, rsp);
|
||||
}
|
||||
}
|
101
src/core/packet/client/client.ts
Normal file
101
src/core/packet/client/client.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { LRUCache } from "@/common/lru-cache";
|
||||
import { NapCatCore } from "@/core";
|
||||
import { LogWrapper } from "@/common/log";
|
||||
import crypto, { createHash } from "crypto";
|
||||
import { OidbPacket, PacketHexStr } from "@/core/packet/packer";
|
||||
import { NapCatConfig } from "@/core/helper/config";
|
||||
|
||||
export interface RecvPacket {
|
||||
type: string, // 仅recv
|
||||
trace_id_md5?: string,
|
||||
data: RecvPacketData
|
||||
}
|
||||
|
||||
export interface RecvPacketData {
|
||||
seq: number
|
||||
cmd: string
|
||||
hex_data: string
|
||||
}
|
||||
|
||||
export abstract class PacketClient {
|
||||
readonly napCatCore: NapCatCore;
|
||||
protected readonly logger: LogWrapper;
|
||||
protected readonly cb = new LRUCache<string, (json: RecvPacketData) => Promise<void>>(500); // trace_id-type callback
|
||||
protected isAvailable: boolean = false;
|
||||
protected config: NapCatConfig;
|
||||
|
||||
protected constructor(core: NapCatCore) {
|
||||
this.napCatCore = core;
|
||||
this.logger = core.context.logger;
|
||||
this.config = core.configLoader.configData;
|
||||
}
|
||||
|
||||
private randText(len: number): string {
|
||||
let text = '';
|
||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
for (let i = 0; i < len; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
get available(): boolean {
|
||||
return this.isAvailable;
|
||||
}
|
||||
|
||||
abstract check(core: NapCatCore): boolean;
|
||||
|
||||
abstract init(pid: number, recv: string, send: string): Promise<void>;
|
||||
|
||||
abstract connect(cb: () => void): Promise<void>;
|
||||
|
||||
abstract sendCommandImpl(cmd: string, data: string, trace_id: string): void;
|
||||
|
||||
private async registerCallback(trace_id: string, type: string, callback: (json: RecvPacketData) => Promise<void>): Promise<void> {
|
||||
this.cb.put(createHash('md5').update(trace_id).digest('hex') + type, callback);
|
||||
}
|
||||
|
||||
private async sendCommand(cmd: string, data: string, trace_id: string, rsp: boolean = false, timeout: number = 20000, sendcb: (json: RecvPacketData) => void = () => {
|
||||
}): Promise<RecvPacketData> {
|
||||
return new Promise<RecvPacketData>((resolve, reject) => {
|
||||
const timeoutHandle = setTimeout(() => {
|
||||
reject(new Error(`sendCommand timed out after ${timeout} ms for ${cmd} with trace_id ${trace_id}`));
|
||||
}, timeout);
|
||||
this.registerCallback(trace_id, 'send', async (json: RecvPacketData) => {
|
||||
sendcb(json);
|
||||
if (!rsp) {
|
||||
clearTimeout(timeoutHandle);
|
||||
resolve(json);
|
||||
}
|
||||
});
|
||||
if (rsp) {
|
||||
this.registerCallback(trace_id, 'recv', async (json: RecvPacketData) => {
|
||||
clearTimeout(timeoutHandle);
|
||||
resolve(json);
|
||||
});
|
||||
}
|
||||
this.sendCommandImpl(cmd, data, trace_id);
|
||||
});
|
||||
}
|
||||
|
||||
async sendPacket(cmd: string, data: PacketHexStr, rsp = false): Promise<RecvPacketData> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.available) {
|
||||
this.logger.logError('NapCat.Packet 未初始化!');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const md5 = crypto.createHash('md5').update(data).digest('hex');
|
||||
const trace_id = (this.randText(4) + md5 + data).slice(0, data.length / 2);
|
||||
|
||||
this.sendCommand(cmd, data, trace_id, rsp, 20000, async () => {
|
||||
//console.log('sendPacket:', cmd, data, trace_id);
|
||||
await this.napCatCore.context.session.getMsgService().sendSsoCmdReqByContend(cmd, trace_id);
|
||||
}).then((res) => resolve(res)).catch((e: Error) => reject(e));
|
||||
});
|
||||
}
|
||||
|
||||
async sendOidbPacket(pkt: OidbPacket, rsp = false): Promise<RecvPacketData> {
|
||||
return this.sendPacket(pkt.cmd, pkt.data, rsp);
|
||||
}
|
||||
}
|
80
src/core/packet/client/nativeClient.ts
Normal file
80
src/core/packet/client/nativeClient.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import crypto, { createHash } from "crypto";
|
||||
import { NapCatCore } from "@/core";
|
||||
import path, { dirname } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import fs from "fs";
|
||||
import { PacketClient } from "@/core/packet/client/client";
|
||||
import { constants } from "node:os";
|
||||
import { LRUCache } from "@/common/lru-cache";
|
||||
//0 send 1recv
|
||||
export interface NativePacketExportType {
|
||||
InitHook?: (recv: string, send: string, callback: (type: number, uin: string, cmd: string, seq: number, hex_data: string) => void) => boolean;
|
||||
SendPacket?: (cmd: string, data: string, trace_id: string) => void;
|
||||
}
|
||||
export class NativePacketClient extends PacketClient {
|
||||
private readonly supportedPlatforms = ['win32.x64', 'linux.x64', 'linux.arm64'];
|
||||
private MoeHooExport: { exports: NativePacketExportType } = { exports: {} };
|
||||
private sendEvent = new LRUCache<number, string>(500);//seq->trace_id
|
||||
constructor(core: NapCatCore) {
|
||||
super(core);
|
||||
}
|
||||
|
||||
get available(): boolean {
|
||||
return this.isAvailable;
|
||||
}
|
||||
|
||||
check(): boolean {
|
||||
const platform = process.platform + '.' + process.arch;
|
||||
if (!this.supportedPlatforms.includes(platform)) {
|
||||
this.logger.logWarn(`[Core] [Packet:Native] 不支持的平台: ${platform}`);
|
||||
return false;
|
||||
}
|
||||
const moehoo_path = path.join(dirname(fileURLToPath(import.meta.url)), './moehoo/MoeHoo.' + platform + '.node');
|
||||
if (!fs.existsSync(moehoo_path)) {
|
||||
this.logger.logWarn(`[Core] [Packet:Native] 缺失运行时文件: ${moehoo_path}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async init(pid: number, recv: string, send: string): Promise<void> {
|
||||
const platform = process.platform + '.' + process.arch;
|
||||
const moehoo_path = path.join(dirname(fileURLToPath(import.meta.url)), './moehoo/MoeHoo.' + platform + '.node');
|
||||
process.dlopen(this.MoeHooExport, moehoo_path, constants.dlopen.RTLD_LAZY);
|
||||
this.MoeHooExport.exports.InitHook?.(send, recv, (type: number, uin: string, cmd: string, seq: number, hex_data: string) => {
|
||||
const trace_id = createHash('md5').update(Buffer.from(hex_data, 'hex')).digest('hex');
|
||||
if (type === 0 && this.cb.get(trace_id + 'recv')) {
|
||||
//此时为send 提取seq
|
||||
this.sendEvent.put(seq, trace_id);
|
||||
}
|
||||
if (type === 1 && this.sendEvent.get(seq)) {
|
||||
//此时为recv 调用callback
|
||||
const trace_id = this.sendEvent.get(seq);
|
||||
const callback = this.cb.get(trace_id + 'recv');
|
||||
// console.log('callback:', callback, trace_id);
|
||||
callback?.({ seq, cmd, hex_data });
|
||||
}
|
||||
|
||||
// const callback = this.cb.get(createHash('md5').update(Buffer.from(hex_data, 'hex')).digest('hex') + (type === 0 ? 'send' : 'recv'));
|
||||
// if (callback) {
|
||||
// callback({ seq, cmd, hex_data });
|
||||
// } else {
|
||||
// this.logger.logError(`Callback not found for hex_data: ${hex_data}`);
|
||||
// }
|
||||
//console.log('type:', type, 'cmd:', cmd, 'trace_id:', trace_id);
|
||||
});
|
||||
this.isAvailable = true;
|
||||
}
|
||||
|
||||
sendCommandImpl(cmd: string, data: string, trace_id: string): void {
|
||||
const trace_id_md5 = createHash('md5').update(trace_id).digest('hex');
|
||||
//console.log('sendCommandImpl:', cmd, data, trace_id_md5);
|
||||
this.MoeHooExport.exports.SendPacket?.(cmd, data, trace_id_md5);
|
||||
this.cb.get(trace_id_md5 + 'send')?.({ seq: 0, cmd, hex_data: '' });
|
||||
}
|
||||
|
||||
connect(cb: () => void): Promise<void> {
|
||||
cb();
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
112
src/core/packet/client/wsClient.ts
Normal file
112
src/core/packet/client/wsClient.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { Data, WebSocket } from "ws";
|
||||
import { NapCatCore } from "@/core";
|
||||
import { PacketClient, RecvPacket } from "@/core/packet/client/client";
|
||||
|
||||
export class wsPacketClient extends PacketClient {
|
||||
private websocket: WebSocket | undefined;
|
||||
private reconnectAttempts: number = 0;
|
||||
private readonly maxReconnectAttempts: number = 60; // 现在暂时不可配置
|
||||
private readonly clientUrl: string | null = null;
|
||||
private clientUrlWrap: (url: string) => string = (url: string) => `ws://${url}/ws`;
|
||||
|
||||
constructor(core: NapCatCore) {
|
||||
super(core);
|
||||
this.clientUrl = this.config.packetServer ? this.clientUrlWrap( this.config.packetServer) : null;
|
||||
}
|
||||
|
||||
check(): boolean {
|
||||
if (!this.clientUrl) {
|
||||
this.logger.logWarn(`[Core] [Packet:Server] 未配置服务器地址`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
connect(cb: () => void): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
//this.logger.log.bind(this.logger)(`[Core] [Packet Server] Attempting to connect to ${this.clientUrl}`);
|
||||
this.websocket = new WebSocket(this.clientUrl!);
|
||||
this.websocket.on('error', (err) => { }/*this.logger.logError.bind(this.logger)('[Core] [Packet Server] Error:', err.message)*/);
|
||||
|
||||
this.websocket.onopen = () => {
|
||||
this.isAvailable = true;
|
||||
this.reconnectAttempts = 0;
|
||||
this.logger.log.bind(this.logger)(`[Core] [Packet:Server] 已连接到 ${this.clientUrl}`);
|
||||
cb();
|
||||
resolve();
|
||||
};
|
||||
|
||||
this.websocket.onerror = (error) => {
|
||||
//this.logger.logError.bind(this.logger)(`WebSocket error: ${error}`);
|
||||
reject(new Error(`${error.message}`));
|
||||
};
|
||||
|
||||
this.websocket.onmessage = (event) => {
|
||||
// const message = JSON.parse(event.data.toString());
|
||||
// console.log("Received message:", message);
|
||||
this.handleMessage(event.data).then().catch();
|
||||
};
|
||||
|
||||
this.websocket.onclose = () => {
|
||||
this.isAvailable = false;
|
||||
//this.logger.logWarn.bind(this.logger)(`[Core] [Packet Server] Disconnected from ${this.clientUrl}`);
|
||||
this.attemptReconnect(cb);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private attemptReconnect(cb: any): void {
|
||||
try {
|
||||
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
||||
this.reconnectAttempts++;
|
||||
setTimeout(() => {
|
||||
this.connect(cb).catch((error) => {
|
||||
this.logger.logError.bind(this.logger)(`[Core] [Packet:Server] 尝试重连失败:${error.message}`);
|
||||
});
|
||||
}, 5000 * this.reconnectAttempts);
|
||||
} else {
|
||||
this.logger.logError.bind(this.logger)(`[Core] [Packet:Server] ${this.clientUrl} 已达到最大重连次数!`);
|
||||
}
|
||||
} catch (error: any) {
|
||||
this.logger.logError.bind(this.logger)(`[Core] [Packet:Server] 重连时出错: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async init(pid: number, recv: string, send: string): Promise<void> {
|
||||
if (!this.isAvailable || !this.websocket) {
|
||||
throw new Error("WebSocket is not connected");
|
||||
}
|
||||
const initMessage = {
|
||||
action: 'init',
|
||||
pid: pid,
|
||||
recv: recv,
|
||||
send: send
|
||||
};
|
||||
this.websocket.send(JSON.stringify(initMessage));
|
||||
}
|
||||
|
||||
sendCommandImpl(cmd: string, data: string, trace_id: string) : void {
|
||||
const commandMessage = {
|
||||
action: 'send',
|
||||
cmd: cmd,
|
||||
data: data,
|
||||
trace_id: trace_id
|
||||
};
|
||||
this.websocket!.send(JSON.stringify(commandMessage));
|
||||
}
|
||||
|
||||
private async handleMessage(message: Data): Promise<void> {
|
||||
try {
|
||||
const json: RecvPacket = JSON.parse(message.toString());
|
||||
const trace_id_md5 = json.trace_id_md5;
|
||||
const action = json?.type ?? 'init';
|
||||
const event = this.cb.get(trace_id_md5 + action);
|
||||
if (event) {
|
||||
await event(json.data);
|
||||
}
|
||||
//console.log("Received message:", json);
|
||||
} catch (error) {
|
||||
this.logger.logError.bind(this.logger)(`Error parsing message: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
16
src/core/packet/entities/aiChat.ts
Normal file
16
src/core/packet/entities/aiChat.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export enum AIVoiceChatType {
|
||||
Unknown = 0,
|
||||
Sound = 1,
|
||||
Sing = 2
|
||||
}
|
||||
|
||||
export interface AIVoiceItem {
|
||||
voiceId: string;
|
||||
voiceDisplayName: string;
|
||||
voiceExampleUrl: string;
|
||||
}
|
||||
|
||||
export interface AIVoiceItemList {
|
||||
category: string;
|
||||
voices: AIVoiceItem[];
|
||||
}
|
79
src/core/packet/entities/miniApp.ts
Normal file
79
src/core/packet/entities/miniApp.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
export interface MiniAppReqCustomParams {
|
||||
title: string;
|
||||
desc: string;
|
||||
picUrl: string;
|
||||
jumpUrl: string;
|
||||
}
|
||||
|
||||
export interface MiniAppReqTemplateParams {
|
||||
sdkId: string;
|
||||
appId: string;
|
||||
scene: number;
|
||||
iconUrl: string;
|
||||
templateType: number;
|
||||
businessType: number;
|
||||
verType: number;
|
||||
shareType: number;
|
||||
versionId: string;
|
||||
withShareTicket: number;
|
||||
}
|
||||
|
||||
export interface MiniAppReqParams extends MiniAppReqCustomParams, MiniAppReqTemplateParams {}
|
||||
|
||||
export interface MiniAppData {
|
||||
ver: string;
|
||||
prompt: string;
|
||||
config: Config;
|
||||
app: string;
|
||||
view: string;
|
||||
meta: MetaData;
|
||||
miniappShareOrigin: number;
|
||||
miniappOpenRefer: string;
|
||||
}
|
||||
|
||||
export interface MiniAppRawData {
|
||||
appName: string;
|
||||
appView: string;
|
||||
ver: string;
|
||||
desc: string;
|
||||
prompt: string;
|
||||
metaData: MetaData;
|
||||
config: Config;
|
||||
}
|
||||
|
||||
interface Config {
|
||||
type: string;
|
||||
width: number;
|
||||
height: number;
|
||||
forward: number;
|
||||
autoSize: number;
|
||||
ctime: number;
|
||||
token: string;
|
||||
}
|
||||
|
||||
interface Host {
|
||||
uin: number;
|
||||
nick: string;
|
||||
}
|
||||
|
||||
interface Detail {
|
||||
appid: string;
|
||||
appType: number;
|
||||
title: string;
|
||||
desc: string;
|
||||
icon: string;
|
||||
preview: string;
|
||||
url: string;
|
||||
scene: number;
|
||||
host: Host;
|
||||
shareTemplateId: string;
|
||||
shareTemplateData: Record<string, unknown>;
|
||||
showLittleTail: string;
|
||||
gamePoints: string;
|
||||
gamePointsUrl: string;
|
||||
shareOrigin: number;
|
||||
}
|
||||
|
||||
interface MetaData {
|
||||
detail_1: Detail;
|
||||
}
|
94
src/core/packet/helper/miniAppHelper.ts
Normal file
94
src/core/packet/helper/miniAppHelper.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import {
|
||||
MiniAppData,
|
||||
MiniAppReqParams,
|
||||
MiniAppRawData,
|
||||
MiniAppReqCustomParams,
|
||||
MiniAppReqTemplateParams
|
||||
} from "@/core/packet/entities/miniApp";
|
||||
|
||||
type MiniAppTemplateNameList = "bili" | "weibo";
|
||||
|
||||
export abstract class MiniAppInfo {
|
||||
static sdkId: string = "V1_PC_MINISDK_99.99.99_1_APP_A";
|
||||
template: MiniAppReqTemplateParams;
|
||||
|
||||
private static appMap = new Map<MiniAppTemplateNameList, MiniAppInfo>();
|
||||
|
||||
protected constructor(template: MiniAppReqTemplateParams) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
static get(name: MiniAppTemplateNameList): MiniAppInfo | undefined {
|
||||
return this.appMap.get(name);
|
||||
}
|
||||
|
||||
static Bili = new class extends MiniAppInfo {
|
||||
constructor() {
|
||||
super({
|
||||
sdkId: MiniAppInfo.sdkId,
|
||||
appId: "1109937557",
|
||||
scene: 1,
|
||||
templateType: 1,
|
||||
businessType: 0,
|
||||
verType: 3,
|
||||
shareType: 0,
|
||||
versionId: "cfc5f7b05b44b5956502edaecf9d2240",
|
||||
withShareTicket: 0,
|
||||
iconUrl: "https://miniapp.gtimg.cn/public/appicon/51f90239b78a2e4994c11215f4c4ba15_200.jpg"
|
||||
});
|
||||
MiniAppInfo.appMap.set("bili", this);
|
||||
}
|
||||
};
|
||||
|
||||
static WeiBo = new class extends MiniAppInfo {
|
||||
constructor() {
|
||||
super({
|
||||
sdkId: MiniAppInfo.sdkId,
|
||||
appId: "1109224783",
|
||||
scene: 1,
|
||||
templateType: 1,
|
||||
businessType: 0,
|
||||
verType: 3,
|
||||
shareType: 0,
|
||||
versionId: "e482a3cc4e574d9b772e96ba6eec9ba2",
|
||||
withShareTicket: 0,
|
||||
iconUrl: "https://miniapp.gtimg.cn/public/appicon/35bbb44dc68e65194cfacfb206b8f1f7_200.jpg"
|
||||
});
|
||||
MiniAppInfo.appMap.set("weibo", this);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export class MiniAppInfoHelper {
|
||||
static generateReq(custom: MiniAppReqCustomParams, template: MiniAppReqTemplateParams): MiniAppReqParams {
|
||||
return {
|
||||
...custom,
|
||||
...template
|
||||
};
|
||||
}
|
||||
|
||||
static RawToSend(rawData: MiniAppRawData): MiniAppData {
|
||||
return {
|
||||
ver: rawData.ver,
|
||||
prompt: rawData.prompt,
|
||||
config: rawData.config,
|
||||
app: rawData.appName,
|
||||
view: rawData.appView,
|
||||
meta: rawData.metaData,
|
||||
miniappShareOrigin: 3,
|
||||
miniappOpenRefer: "10002",
|
||||
};
|
||||
}
|
||||
|
||||
static SendToRaw(data: MiniAppData): MiniAppRawData {
|
||||
return {
|
||||
appName: data.app,
|
||||
appView: data.view,
|
||||
ver: data.ver,
|
||||
desc: data.meta.detail_1.desc,
|
||||
prompt: data.prompt,
|
||||
metaData: data.meta,
|
||||
config: data.config,
|
||||
};
|
||||
}
|
||||
}
|
@@ -1,9 +1,8 @@
|
||||
import * as fs from "node:fs";
|
||||
import { ChatType, Peer } from "@/core";
|
||||
import { LogWrapper } from "@/common/log";
|
||||
import { PacketClient } from "@/core/packet/client";
|
||||
import { PacketPacker } from "@/core/packet/packer";
|
||||
import { NapProtoMsg } from "@/core/packet/proto/NapProto";
|
||||
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
||||
import { HttpConn0x6ff_501Response } from "@/core/packet/proto/action/action";
|
||||
import { PacketHighwayClient } from "@/core/packet/highway/client";
|
||||
import { NTV2RichMediaResp } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaResp";
|
||||
@@ -13,12 +12,13 @@ import {
|
||||
PacketMsgPicElement,
|
||||
PacketMsgPttElement,
|
||||
PacketMsgVideoElement
|
||||
} from "@/core/packet/msg/element";
|
||||
} from "@/core/packet/message/element";
|
||||
import { FileUploadExt, NTV2RichMediaHighwayExt } from "@/core/packet/proto/highway/highway";
|
||||
import { int32ip2str, oidbIpv4s2HighwayIpv4s } from "@/core/packet/highway/utils";
|
||||
import { calculateSha1, calculateSha1StreamBytes, computeMd5AndLengthWithLimit } from "@/core/packet/utils/crypto/hash";
|
||||
import { OidbSvcTrpcTcp0x6D6Response } from "@/core/packet/proto/oidb/Oidb.0x6D6";
|
||||
import { OidbSvcTrpcTcp0XE37_800Response, OidbSvcTrpcTcp0XE37Response } from "@/core/packet/proto/oidb/Oidb.0XE37_800";
|
||||
import { PacketClient } from "@/core/packet/client/client";
|
||||
|
||||
export const BlockSize = 1024 * 1024;
|
||||
|
||||
@@ -59,7 +59,7 @@ export class PacketHighwaySession {
|
||||
|
||||
private async checkAvailable() {
|
||||
if (!this.packetClient.available) {
|
||||
throw new Error('packetServer不可用,请参照文档 https://napneko.github.io/config/advanced 检查packetServer状态或进行配置');
|
||||
throw new Error('packetBackend不可用,请参照文档 https://napneko.github.io/config/advanced 和启动日志检查packetBackend状态或进行配置!');
|
||||
}
|
||||
if (this.sig.sigSession === null || this.sig.sessionKey === null) {
|
||||
this.logger.logWarn('[Highway] sigSession or sessionKey not available!');
|
||||
|
@@ -4,7 +4,7 @@ import * as http from "node:http";
|
||||
import * as stream from "node:stream";
|
||||
import { LogWrapper } from "@/common/log";
|
||||
import * as tea from "@/core/packet/utils/crypto/tea";
|
||||
import { NapProtoMsg } from "@/core/packet/proto/NapProto";
|
||||
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
||||
import { ReqDataHighwayHead, RespDataHighwayHead } from "@/core/packet/proto/highway/highway";
|
||||
import { BlockSize } from "@/core/packet/highway/session";
|
||||
import { PacketHighwayTrans } from "@/core/packet/highway/client";
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { NapProtoEncodeStructType } from "@/core/packet/proto/NapProto";
|
||||
import { NapProtoEncodeStructType } from "@napneko/nap-proto-core";
|
||||
import { IPv4 } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaResp";
|
||||
import { NTHighwayIPv4 } from "@/core/packet/proto/highway/highway";
|
||||
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import * as crypto from "crypto";
|
||||
import { PushMsgBody } from "@/core/packet/proto/message/message";
|
||||
import { NapProtoEncodeStructType } from "@/core/packet/proto/NapProto";
|
||||
import { NapProtoEncodeStructType } from "@napneko/nap-proto-core";
|
||||
import { LogWrapper } from "@/common/log";
|
||||
import { PacketMsg, PacketSendMsgElement } from "@/core/packet/msg/message";
|
||||
import { IPacketMsgElement, PacketMsgTextElement } from "@/core/packet/msg/element";
|
||||
import { PacketMsg, PacketSendMsgElement } from "@/core/packet/message/message";
|
||||
import { IPacketMsgElement, PacketMsgTextElement } from "@/core/packet/message/element";
|
||||
import { SendTextElement } from "@/core";
|
||||
|
||||
export class PacketMsgBuilder {
|
@@ -30,8 +30,8 @@ import {
|
||||
PacketMsgTextElement,
|
||||
PacketMsgVideoElement,
|
||||
PacketMultiMsgElement
|
||||
} from "@/core/packet/msg/element";
|
||||
import { PacketMsg, PacketSendMsgElement } from "@/core/packet/msg/message";
|
||||
} from "@/core/packet/message/element";
|
||||
import { PacketMsg, PacketSendMsgElement } from "@/core/packet/message/message";
|
||||
import { LogWrapper } from "@/common/log";
|
||||
|
||||
const SupportedElementTypes = [
|
@@ -1,5 +1,5 @@
|
||||
import * as zlib from "node:zlib";
|
||||
import { NapProtoEncodeStructType, NapProtoMsg } from "@/core/packet/proto/NapProto";
|
||||
import { NapProtoEncodeStructType, NapProtoMsg } from "@napneko/nap-proto-core";
|
||||
import {
|
||||
CustomFace,
|
||||
Elem,
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
SendVideoElement
|
||||
} from "@/core";
|
||||
import { MsgInfo } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
|
||||
import { PacketMsg, PacketSendMsgElement } from "@/core/packet/msg/message";
|
||||
import { PacketMsg, PacketSendMsgElement } from "@/core/packet/message/message";
|
||||
import { ForwardMsgBuilder } from "@/common/forward-msg-builder";
|
||||
import { FileExtra, GroupFileExtra } from "@/core/packet/proto/message/component";
|
||||
import { OidbSvcTrpcTcp0XE37_800Response } from "@/core/packet/proto/oidb/Oidb.0XE37_800";
|
||||
@@ -241,7 +241,7 @@ export class PacketMsgMarkFaceElement extends IPacketMsgElement<SendMarketFaceEl
|
||||
}
|
||||
|
||||
toPreview(): string {
|
||||
return `[${this.emojiName}]`;
|
||||
return `${this.emojiName}`;
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { IPacketMsgElement } from "@/core/packet/msg/element";
|
||||
import { IPacketMsgElement } from "@/core/packet/message/element";
|
||||
import { SendMessageElement, SendStructLongMsgElement } from "@/core";
|
||||
|
||||
export type PacketSendMsgElement = SendMessageElement | SendStructLongMsgElement
|
@@ -1,31 +1,35 @@
|
||||
import * as zlib from "node:zlib";
|
||||
import * as crypto from "node:crypto";
|
||||
import { computeMd5AndLengthWithLimit } from "@/core/packet/utils/crypto/hash";
|
||||
import { NapProtoMsg } from "@/core/packet/proto/NapProto";
|
||||
import { NapProtoEncodeStructType, NapProtoMsg } from "@napneko/nap-proto-core";
|
||||
import { OidbSvcTrpcTcpBase } from "@/core/packet/proto/oidb/OidbBase";
|
||||
import { OidbSvcTrpcTcp0X9067_202 } from "@/core/packet/proto/oidb/Oidb.0x9067_202";
|
||||
import { OidbSvcTrpcTcp0X8FC_2, OidbSvcTrpcTcp0X8FC_2_Body } from "@/core/packet/proto/oidb/Oidb.0x8FC_2";
|
||||
import { OidbSvcTrpcTcp0XFE1_2 } from "@/core/packet/proto/oidb/Oidb.0XFE1_2";
|
||||
import { OidbSvcTrpcTcp0XED3_1 } from "@/core/packet/proto/oidb/Oidb.0xED3_1";
|
||||
import { NTV2RichMediaReq } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
|
||||
import { IndexNode, NTV2RichMediaReq } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
|
||||
import { HttpConn0x6ff_501 } from "@/core/packet/proto/action/action";
|
||||
import { LongMsgResult, SendLongMsgReq } from "@/core/packet/proto/message/action";
|
||||
import { PacketMsgBuilder } from "@/core/packet/msg/builder";
|
||||
import { PacketMsgBuilder } from "@/core/packet/message/builder";
|
||||
import {
|
||||
PacketMsgFileElement,
|
||||
PacketMsgPicElement,
|
||||
PacketMsgPttElement,
|
||||
PacketMsgVideoElement
|
||||
} from "@/core/packet/msg/element";
|
||||
} from "@/core/packet/message/element";
|
||||
import { LogWrapper } from "@/common/log";
|
||||
import { PacketMsg } from "@/core/packet/msg/message";
|
||||
import { PacketMsg } from "@/core/packet/message/message";
|
||||
import { OidbSvcTrpcTcp0x6D6 } from "@/core/packet/proto/oidb/Oidb.0x6D6";
|
||||
import { OidbSvcTrpcTcp0XE37_1200 } from "@/core/packet/proto/oidb/Oidb.0xE37_1200";
|
||||
import { PacketMsgConverter } from "@/core/packet/msg/converter";
|
||||
import { PacketClient } from "@/core/packet/client";
|
||||
import { PacketMsgConverter } from "@/core/packet/message/converter";
|
||||
import { OidbSvcTrpcTcp0XE37_1700 } from "@/core/packet/proto/oidb/Oidb.0xE37_1700";
|
||||
import { OidbSvcTrpcTcp0XE37_800 } from "@/core/packet/proto/oidb/Oidb.0XE37_800";
|
||||
import { OidbSvcTrpcTcp0XEB7 } from "./proto/oidb/Oidb.0xEB7";
|
||||
import { MiniAppReqParams } from "@/core/packet/entities/miniApp";
|
||||
import { MiniAppAdaptShareInfoReq } from "@/core/packet/proto/action/miniAppAdaptShareInfo";
|
||||
import { AIVoiceChatType } from "@/core/packet/entities/aiChat";
|
||||
import { OidbSvcTrpcTcp0X929B_0, OidbSvcTrpcTcp0X929D_0 } from "@/core/packet/proto/oidb/Oidb.0x929";
|
||||
import { PacketClient } from "@/core/packet/client/client";
|
||||
|
||||
export type PacketHexStr = string & { readonly hexNya: unique symbol };
|
||||
|
||||
@@ -694,6 +698,37 @@ export class PacketPacker {
|
||||
);
|
||||
}
|
||||
|
||||
packGroupPttFileDownloadReq(groupUin: number, node: NapProtoEncodeStructType<typeof IndexNode>): OidbPacket {
|
||||
return this.packOidbPacket(0x126E, 200, new NapProtoMsg(NTV2RichMediaReq).encode({
|
||||
reqHead: {
|
||||
common: {
|
||||
requestId: 4,
|
||||
command: 200
|
||||
},
|
||||
scene: {
|
||||
requestType: 1,
|
||||
businessType: 3,
|
||||
sceneType: 2,
|
||||
group: {
|
||||
groupUin: groupUin
|
||||
}
|
||||
},
|
||||
client: {
|
||||
agentType: 2
|
||||
}
|
||||
},
|
||||
download: {
|
||||
node: node,
|
||||
download: {
|
||||
video: {
|
||||
busiType: 0,
|
||||
sceneType: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
}), true, false);
|
||||
}
|
||||
|
||||
packGroupSignReq(uin: string, groupCode: string): OidbPacket {
|
||||
return this.packOidbPacket(0XEB7, 1, new NapProtoMsg(OidbSvcTrpcTcp0XEB7).encode(
|
||||
{
|
||||
@@ -705,4 +740,64 @@ export class PacketPacker {
|
||||
}
|
||||
), false, false);
|
||||
}
|
||||
|
||||
packMiniAppAdaptShareInfo(req: MiniAppReqParams): PacketHexStr {
|
||||
return this.packetPacket(
|
||||
new NapProtoMsg(MiniAppAdaptShareInfoReq).encode(
|
||||
{
|
||||
appId: req.sdkId,
|
||||
body: {
|
||||
extInfo: {
|
||||
field2: Buffer.alloc(0)
|
||||
},
|
||||
appid: req.appId,
|
||||
title: req.title,
|
||||
desc: req.desc,
|
||||
time: BigInt(Date.now()),
|
||||
scene: req.scene,
|
||||
templateType: req.templateType,
|
||||
businessType: req.businessType,
|
||||
picUrl: req.picUrl,
|
||||
vidUrl: "",
|
||||
jumpUrl: req.jumpUrl,
|
||||
iconUrl: req.iconUrl,
|
||||
verType: req.verType,
|
||||
shareType: req.shareType,
|
||||
versionId: req.versionId,
|
||||
withShareTicket: req.withShareTicket,
|
||||
webURL: "",
|
||||
appidRich: Buffer.alloc(0),
|
||||
template: {
|
||||
templateId: "",
|
||||
templateData: ""
|
||||
},
|
||||
field20: ""
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
packFetchAiVoiceListReq(groupUin: number, chatType: AIVoiceChatType): OidbPacket {
|
||||
return this.packOidbPacket(0x929D, 0,
|
||||
new NapProtoMsg(OidbSvcTrpcTcp0X929D_0).encode({
|
||||
groupUin: groupUin,
|
||||
chatType: chatType
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
packAiVoiceChatReq(groupUin: number, voiceId: string, text: string, chatType: AIVoiceChatType, sessionId: number): OidbPacket {
|
||||
return this.packOidbPacket(0x929B, 0,
|
||||
new NapProtoMsg(OidbSvcTrpcTcp0X929B_0).encode({
|
||||
groupUin: groupUin,
|
||||
voiceId: voiceId,
|
||||
text: text,
|
||||
chatType: chatType,
|
||||
session: {
|
||||
sessionId: sessionId
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -1,139 +0,0 @@
|
||||
import { MessageType, PartialMessage, RepeatType, ScalarType } from '@protobuf-ts/runtime';
|
||||
import { PartialFieldInfo } from "@protobuf-ts/runtime/build/types/reflection-info";
|
||||
|
||||
type LowerCamelCase<S extends string> = CamelCaseHelper<S, false, true>;
|
||||
|
||||
type CamelCaseHelper<
|
||||
S extends string,
|
||||
CapNext extends boolean,
|
||||
IsFirstChar extends boolean
|
||||
> = S extends `${infer F}${infer R}`
|
||||
? F extends '_'
|
||||
? CamelCaseHelper<R, true, false>
|
||||
: F extends `${number}`
|
||||
? `${F}${CamelCaseHelper<R, true, false>}`
|
||||
: CapNext extends true
|
||||
? `${Uppercase<F>}${CamelCaseHelper<R, false, false>}`
|
||||
: IsFirstChar extends true
|
||||
? `${Lowercase<F>}${CamelCaseHelper<R, false, false>}`
|
||||
: `${F}${CamelCaseHelper<R, false, false>}`
|
||||
: '';
|
||||
|
||||
type ScalarTypeToTsType<T extends ScalarType> =
|
||||
T extends ScalarType.DOUBLE | ScalarType.FLOAT | ScalarType.INT32 | ScalarType.FIXED32 | ScalarType.UINT32 | ScalarType.SFIXED32 | ScalarType.SINT32 ? number :
|
||||
T extends ScalarType.INT64 | ScalarType.UINT64 | ScalarType.FIXED64 | ScalarType.SFIXED64 | ScalarType.SINT64 ? bigint :
|
||||
T extends ScalarType.BOOL ? boolean :
|
||||
T extends ScalarType.STRING ? string :
|
||||
T extends ScalarType.BYTES ? Uint8Array :
|
||||
never;
|
||||
|
||||
interface BaseProtoFieldType<T, O extends boolean, R extends O extends true ? false : boolean> {
|
||||
kind: 'scalar' | 'message';
|
||||
no: number;
|
||||
type: T;
|
||||
optional: O;
|
||||
repeat: R;
|
||||
}
|
||||
|
||||
interface ScalarProtoFieldType<T extends ScalarType, O extends boolean, R extends O extends true ? false : boolean> extends BaseProtoFieldType<T, O, R> {
|
||||
kind: 'scalar';
|
||||
}
|
||||
|
||||
interface MessageProtoFieldType<T extends () => ProtoMessageType, O extends boolean, R extends O extends true ? false : boolean> extends BaseProtoFieldType<T, O, R> {
|
||||
kind: 'message';
|
||||
}
|
||||
|
||||
type ProtoFieldType =
|
||||
| ScalarProtoFieldType<ScalarType, boolean, boolean>
|
||||
| MessageProtoFieldType<() => ProtoMessageType, boolean, boolean>;
|
||||
|
||||
type ProtoMessageType = {
|
||||
[key: string]: ProtoFieldType;
|
||||
};
|
||||
|
||||
export function ProtoField<T extends ScalarType, O extends boolean = false, R extends O extends true ? false : boolean = false>(no: number, type: T, optional?: O, repeat?: R): ScalarProtoFieldType<T, O, R>;
|
||||
export function ProtoField<T extends () => ProtoMessageType, O extends boolean = false, R extends O extends true ? false : boolean = false>(no: number, type: T, optional?: O, repeat?: R): MessageProtoFieldType<T, O, R>;
|
||||
export function ProtoField(no: number, type: ScalarType | (() => ProtoMessageType), optional?: boolean, repeat?: boolean): ProtoFieldType {
|
||||
if (typeof type === 'function') {
|
||||
return { kind: 'message', no: no, type: type, optional: optional ?? false, repeat: repeat ?? false };
|
||||
} else {
|
||||
return { kind: 'scalar', no: no, type: type, optional: optional ?? false, repeat: repeat ?? false };
|
||||
}
|
||||
}
|
||||
|
||||
type ProtoFieldReturnType<T extends unknown, E extends boolean> = NonNullable<T> extends ScalarProtoFieldType<infer S, infer O, infer R>
|
||||
? ScalarTypeToTsType<S>
|
||||
: T extends NonNullable<MessageProtoFieldType<infer S, infer O, infer R>>
|
||||
? NonNullable<NapProtoStructType<ReturnType<S>, E>>
|
||||
: never;
|
||||
|
||||
type RequiredFieldsBaseType<T extends unknown, E extends boolean> = {
|
||||
[K in keyof T as T[K] extends { optional: true } ? never : LowerCamelCase<K & string>]:
|
||||
T[K] extends { repeat: true }
|
||||
? ProtoFieldReturnType<T[K], E>[]
|
||||
: ProtoFieldReturnType<T[K], E>
|
||||
}
|
||||
|
||||
type OptionalFieldsBaseType<T extends unknown, E extends boolean> = {
|
||||
[K in keyof T as T[K] extends { optional: true } ? LowerCamelCase<K & string> : never]?:
|
||||
T[K] extends { repeat: true }
|
||||
? ProtoFieldReturnType<T[K], E>[]
|
||||
: ProtoFieldReturnType<T[K], E>
|
||||
}
|
||||
|
||||
type RequiredFieldsType<T extends unknown, E extends boolean> = E extends true ? Partial<RequiredFieldsBaseType<T, E>> : RequiredFieldsBaseType<T, E>;
|
||||
|
||||
type OptionalFieldsType<T extends unknown, E extends boolean> = E extends true ? Partial<OptionalFieldsBaseType<T, E>> : OptionalFieldsBaseType<T, E>;
|
||||
|
||||
type NapProtoStructType<T extends unknown, E extends boolean> = RequiredFieldsType<T, E> & OptionalFieldsType<T, E>;
|
||||
|
||||
export type NapProtoEncodeStructType<T extends unknown> = NapProtoStructType<T, true>;
|
||||
|
||||
export type NapProtoDecodeStructType<T extends unknown> = NapProtoStructType<T, false>;
|
||||
|
||||
const NapProtoMsgCache = new Map<ProtoMessageType, MessageType<NapProtoStructType<ProtoMessageType, boolean>>>();
|
||||
|
||||
export class NapProtoMsg<T extends ProtoMessageType> {
|
||||
private readonly _msg: T;
|
||||
private readonly _field: PartialFieldInfo[];
|
||||
private readonly _proto_msg: MessageType<NapProtoStructType<T, boolean>>;
|
||||
|
||||
constructor(fields: T) {
|
||||
this._msg = fields;
|
||||
this._field = Object.keys(fields).map(key => {
|
||||
const field = fields[key];
|
||||
if (field.kind === 'scalar') {
|
||||
const repeatType = field.repeat
|
||||
? [ScalarType.STRING, ScalarType.BYTES].includes(field.type)
|
||||
? RepeatType.UNPACKED
|
||||
: RepeatType.PACKED
|
||||
: RepeatType.NO;
|
||||
return {
|
||||
no: field.no,
|
||||
name: key,
|
||||
kind: 'scalar',
|
||||
T: field.type,
|
||||
opt: field.optional,
|
||||
repeat: repeatType,
|
||||
};
|
||||
} else if (field.kind === 'message') {
|
||||
return {
|
||||
no: field.no,
|
||||
name: key,
|
||||
kind: 'message',
|
||||
repeat: field.repeat ? RepeatType.PACKED : RepeatType.NO,
|
||||
T: () => new NapProtoMsg(field.type())._proto_msg,
|
||||
};
|
||||
}
|
||||
}) as PartialFieldInfo[];
|
||||
this._proto_msg = new MessageType<NapProtoStructType<T, boolean>>('nya', this._field);
|
||||
}
|
||||
|
||||
encode(data: NapProtoEncodeStructType<T>): Uint8Array {
|
||||
return this._proto_msg.toBinary(this._proto_msg.create(data as PartialMessage<NapProtoEncodeStructType<T>>));
|
||||
}
|
||||
|
||||
decode(data: Uint8Array): NapProtoDecodeStructType<T> {
|
||||
return this._proto_msg.fromBinary(data) as NapProtoDecodeStructType<T>;
|
||||
}
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
import { ContentHead, MessageBody, MessageControl, RoutingHead } from "@/core/packet/proto/message/message";
|
||||
|
||||
export const FaceRoamRequest = {
|
||||
|
49
src/core/packet/proto/action/miniAppAdaptShareInfo.ts
Normal file
49
src/core/packet/proto/action/miniAppAdaptShareInfo.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
export const MiniAppAdaptShareInfoReq = {
|
||||
appId: ProtoField(2, ScalarType.STRING),
|
||||
body: ProtoField(4, () => MiniAppAdaptShareInfoReqBody),
|
||||
};
|
||||
|
||||
export const MiniAppAdaptShareInfoReqBody = {
|
||||
extInfo: ProtoField(1, () => ExtInfo),
|
||||
appid: ProtoField(2, ScalarType.STRING),
|
||||
title: ProtoField(3, ScalarType.STRING),
|
||||
desc: ProtoField(4, ScalarType.STRING),
|
||||
time: ProtoField(5, ScalarType.UINT64),
|
||||
scene: ProtoField(6, ScalarType.UINT32),
|
||||
templateType: ProtoField(7, ScalarType.UINT32),
|
||||
businessType: ProtoField(8, ScalarType.UINT32),
|
||||
picUrl: ProtoField(9, ScalarType.STRING),
|
||||
vidUrl: ProtoField(10, ScalarType.STRING),
|
||||
jumpUrl: ProtoField(11, ScalarType.STRING),
|
||||
iconUrl: ProtoField(12, ScalarType.STRING),
|
||||
verType: ProtoField(13, ScalarType.UINT32),
|
||||
shareType: ProtoField(14, ScalarType.UINT32),
|
||||
versionId: ProtoField(15, ScalarType.STRING),
|
||||
withShareTicket: ProtoField(16, ScalarType.UINT32),
|
||||
webURL: ProtoField(17, ScalarType.STRING),
|
||||
appidRich: ProtoField(18, ScalarType.BYTES),
|
||||
template: ProtoField(19, () => Template),
|
||||
field20: ProtoField(20, ScalarType.STRING),
|
||||
};
|
||||
|
||||
export const ExtInfo = {
|
||||
field2: ProtoField(2, ScalarType.BYTES),
|
||||
};
|
||||
|
||||
export const Template = {
|
||||
templateId: ProtoField(1, ScalarType.STRING),
|
||||
templateData: ProtoField(2, ScalarType.STRING),
|
||||
};
|
||||
|
||||
export const MiniAppAdaptShareInfoResp = {
|
||||
field2: ProtoField(2, ScalarType.UINT32),
|
||||
field3: ProtoField(3, ScalarType.STRING),
|
||||
content: ProtoField(4, () => MiniAppAdaptShareInfoRespContent),
|
||||
};
|
||||
|
||||
export const MiniAppAdaptShareInfoRespContent = {
|
||||
jsonContent: ProtoField(2, ScalarType.STRING),
|
||||
};
|
@@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
import { MsgInfo, MsgInfoBody } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
|
||||
|
||||
export const DataHighwayHead = {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
import { PushMsgBody } from "@/core/packet/proto/message/message";
|
||||
|
||||
export const LongMsgResult = {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
export const C2C = {
|
||||
uin: ProtoField(1, ScalarType.UINT32, true),
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
import { Elem } from "@/core/packet/proto/message/element";
|
||||
|
||||
export const Attr = {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
export const Elem = {
|
||||
text: ProtoField(1, () => Text, true),
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
export const GroupRecallMsg = {
|
||||
type: ProtoField(1, ScalarType.UINT32),
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
import { ForwardHead, Grp, GrpTmp, ResponseForward, ResponseGrp, Trans0X211, WPATmp } from "@/core/packet/proto/message/routing";
|
||||
import { RichText } from "@/core/packet/proto/message/component";
|
||||
import { C2C } from "@/core/packet/proto/message/c2c";
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
export const FriendRecall = {
|
||||
info: ProtoField(1, () => FriendRecallInfo),
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
export const ForwardHead = {
|
||||
field1: ProtoField(1, ScalarType.UINT32, true),
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
import { OidbSvcTrpcTcp0XE37_800_1200Metadata } from "@/core/packet/proto/oidb/Oidb.0xE37_1200";
|
||||
|
||||
export const OidbSvcTrpcTcp0XE37_800 = {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
export const OidbSvcTrpcTcp0XFE1_2 = {
|
||||
uin: ProtoField(1, ScalarType.UINT32),
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
export const OidbSvcTrpcTcp0x6D6 = {
|
||||
file: ProtoField(1, () => OidbSvcTrpcTcp0x6D6Upload, true),
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
|
||||
//设置群头衔 OidbSvcTrpcTcp.0x8fc_2
|
||||
@@ -13,4 +13,4 @@ export const OidbSvcTrpcTcp0X8FC_2_Body = {
|
||||
export const OidbSvcTrpcTcp0X8FC_2 = {
|
||||
groupUin: ProtoField(1, ScalarType.UINT32),
|
||||
body: ProtoField(3, ScalarType.BYTES),
|
||||
};
|
||||
};
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
import { MultiMediaReqHead } from "./common/Ntv2.RichMediaReq";
|
||||
|
||||
//Req
|
||||
|
42
src/core/packet/proto/oidb/Oidb.0x929.ts
Normal file
42
src/core/packet/proto/oidb/Oidb.0x929.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
import { MsgInfo } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
|
||||
|
||||
export const OidbSvcTrpcTcp0X929D_0 = {
|
||||
groupUin: ProtoField(1, ScalarType.UINT32),
|
||||
chatType: ProtoField(2, ScalarType.UINT32),
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0X929D_0Resp = {
|
||||
content: ProtoField(1, () => OidbSvcTrpcTcp0X929D_0RespContent, false, true),
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0X929D_0RespContent = {
|
||||
category: ProtoField(1, ScalarType.STRING),
|
||||
voices: ProtoField(2, () => OidbSvcTrpcTcp0X929D_0RespContentVoice, false, true),
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0X929D_0RespContentVoice = {
|
||||
voiceId: ProtoField(1, ScalarType.STRING),
|
||||
voiceDisplayName: ProtoField(2, ScalarType.STRING),
|
||||
voiceExampleUrl: ProtoField(3, ScalarType.STRING),
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0X929B_0 = {
|
||||
groupUin: ProtoField(1, ScalarType.UINT32),
|
||||
voiceId: ProtoField(2, ScalarType.STRING),
|
||||
text: ProtoField(3, ScalarType.STRING),
|
||||
chatType: ProtoField(4, ScalarType.UINT32),
|
||||
session: ProtoField(5, () => OidbSvcTrpcTcp0X929B_0_Session),
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0X929B_0_Session = {
|
||||
sessionId: ProtoField(1, ScalarType.UINT32),
|
||||
};
|
||||
|
||||
export const OidbSvcTrpcTcp0X929B_0Resp = {
|
||||
statusCode: ProtoField(1, ScalarType.UINT32),
|
||||
field2: ProtoField(2, ScalarType.UINT32, true),
|
||||
field3: ProtoField(3, ScalarType.UINT32),
|
||||
msgInfo: ProtoField(4, () => MsgInfo, true),
|
||||
};
|
@@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
export const OidbSvcTrpcTcp0XE37_1200 = {
|
||||
subCommand: ProtoField(1, ScalarType.UINT32, true),
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
export const OidbSvcTrpcTcp0XE37_1700 = {
|
||||
command: ProtoField(1, ScalarType.UINT32, true),
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
export const OidbSvcTrpcTcp0XEB7_Body = {
|
||||
uin: ProtoField(1, ScalarType.STRING),
|
||||
@@ -9,4 +9,4 @@ export const OidbSvcTrpcTcp0XEB7_Body = {
|
||||
|
||||
export const OidbSvcTrpcTcp0XEB7 = {
|
||||
body: ProtoField(2, () => OidbSvcTrpcTcp0XEB7_Body),
|
||||
};
|
||||
};
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
// Send Poke
|
||||
export const OidbSvcTrpcTcp0XED3_1 = {
|
||||
|
@@ -1,13 +1,14 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
export const OidbSvcTrpcTcpBase = {
|
||||
command: ProtoField(1, ScalarType.UINT32),
|
||||
subCommand: ProtoField(2, ScalarType.UINT32),
|
||||
errorCode: ProtoField(3, ScalarType.UINT32),
|
||||
body: ProtoField(4, ScalarType.BYTES),
|
||||
errorMsg: ProtoField(5, ScalarType.STRING, true),
|
||||
isReserved: ProtoField(12, ScalarType.UINT32)
|
||||
};
|
||||
export const OidbSvcTrpcTcpBaseRsp = {
|
||||
body: ProtoField(4, ScalarType.BYTES)
|
||||
};
|
||||
};
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
export const NTV2RichMediaReq = {
|
||||
ReqHead: ProtoField(1, () => MultiMediaReqHead),
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
import { CommonHead, MsgInfo, PicUrlExtInfo, VideoExtInfo } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
|
||||
|
||||
export const NTV2RichMediaResp = {
|
||||
|
@@ -1,18 +1,73 @@
|
||||
import { PacketClient } from "@/core/packet/client";
|
||||
import { PacketHighwaySession } from "@/core/packet/highway/session";
|
||||
import { LogWrapper } from "@/common/log";
|
||||
import { PacketPacker } from "@/core/packet/packer";
|
||||
import { PacketClient } from "@/core/packet/client/client";
|
||||
import { NativePacketClient } from "@/core/packet/client/nativeClient";
|
||||
import { wsPacketClient } from "@/core/packet/client/wsClient";
|
||||
import { NapCatCore } from "@/core";
|
||||
|
||||
type clientPriority = {
|
||||
[key: number]: (core: NapCatCore) => PacketClient;
|
||||
}
|
||||
|
||||
const clientPriority: clientPriority = {
|
||||
10: (core: NapCatCore) => new NativePacketClient(core),
|
||||
1: (core: NapCatCore) => new wsPacketClient(core),
|
||||
};
|
||||
|
||||
export class PacketSession {
|
||||
readonly logger: LogWrapper;
|
||||
readonly client: PacketClient;
|
||||
readonly client: PacketClient ;
|
||||
readonly packer: PacketPacker;
|
||||
readonly highwaySession: PacketHighwaySession;
|
||||
|
||||
constructor(logger: LogWrapper, client: PacketClient) {
|
||||
this.logger = logger;
|
||||
this.client = client;
|
||||
constructor(core: NapCatCore) {
|
||||
this.logger = core.context.logger;
|
||||
this.client = this.newClient(core);
|
||||
this.packer = new PacketPacker(this.logger, this.client);
|
||||
this.highwaySession = new PacketHighwaySession(this.logger, this.client, this.packer);
|
||||
}
|
||||
|
||||
private newClient(core: NapCatCore): PacketClient {
|
||||
const prefer = core.configLoader.configData.packetBackend;
|
||||
let client: PacketClient | null;
|
||||
switch (prefer) {
|
||||
case "native":
|
||||
this.logger.log("[Core] [Packet] 使用指定的 NativePacketClient 作为后端");
|
||||
client = new NativePacketClient(core);
|
||||
break;
|
||||
case "frida":
|
||||
this.logger.log("[Core] [Packet] 使用指定的 FridaPacketClient 作为后端");
|
||||
client = new wsPacketClient(core);
|
||||
break;
|
||||
case "auto":
|
||||
case undefined:
|
||||
client = this.judgeClient(core);
|
||||
break;
|
||||
default:
|
||||
this.logger.logError(`[Core] [Packet] 未知的Packet后端类型 ${prefer},请检查配置文件!`);
|
||||
client = null;
|
||||
}
|
||||
if (!(client && client.check(core))) {
|
||||
throw new Error("[Core] [Packet] 无可用的后端,NapCat.Packet将不会加载!");
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
private judgeClient(core: NapCatCore): PacketClient {
|
||||
const sortedClients = Object.entries(clientPriority)
|
||||
.map(([priority, clientFactory]) => {
|
||||
const client = clientFactory(core);
|
||||
const score = +priority * +client.check(core);
|
||||
return { client, score };
|
||||
})
|
||||
.filter(({ score }) => score > 0)
|
||||
.sort((a, b) => b.score - a.score);
|
||||
const selectedClient = sortedClients[0]?.client;
|
||||
if (!selectedClient) {
|
||||
throw new Error("[Core] [Packet] 无可用的后端,NapCat.Packet将不会加载!");
|
||||
}
|
||||
this.logger.log(`[Core] [Packet] 自动选择 ${selectedClient.constructor.name} 作为后端`);
|
||||
return selectedClient;
|
||||
}
|
||||
}
|
||||
|
@@ -12,13 +12,13 @@ import {
|
||||
import { GeneralCallResult } from '@/core/services/common';
|
||||
|
||||
export interface NodeIKernelGroupService {
|
||||
// --->
|
||||
// --->
|
||||
// 待启用 For Next Version 3.2.0
|
||||
// isTroopMember ? 0 : 111
|
||||
getGroupMemberMaxNum(groupCode: string, serviceType: number): Promise<unknown>;
|
||||
|
||||
getAllGroupPrivilegeFlag(troopUinList: string[], serviceType: number): Promise<unknown>;
|
||||
// <---
|
||||
// <---
|
||||
getGroupExt0xEF0Info(enableGroupCodes: string[], bannedGroupCodes: string[], filter: GroupExt0xEF0InfoFilter, forceFetch: boolean):
|
||||
Promise<GeneralCallResult & { result: { groupExtInfos: Map<string, any> } }>;
|
||||
|
||||
@@ -105,13 +105,13 @@ export interface NodeIKernelGroupService {
|
||||
uid: string,
|
||||
index: number//0
|
||||
}>,
|
||||
infos: unknown,
|
||||
infos: Map<string, GroupMember>,
|
||||
finish: true,
|
||||
hasRobot: false
|
||||
}
|
||||
}>;
|
||||
|
||||
setHeader(uid: string, path: string): unknown;
|
||||
setHeader(uid: string, path: string): Promise<GeneralCallResult>;
|
||||
|
||||
addKernelGroupListener(listener: NodeIKernelGroupListener): number;
|
||||
|
||||
|
@@ -172,7 +172,7 @@ export interface NodeIKernelMsgService {
|
||||
msgList: RawMessage[]
|
||||
}>;
|
||||
//@deprecated
|
||||
getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, unknownArg: boolean): Promise<GeneralCallResult & {
|
||||
getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, isReverseOrder: boolean): Promise<GeneralCallResult & {
|
||||
msgList: RawMessage[]
|
||||
}>;
|
||||
|
||||
@@ -186,27 +186,29 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
getSingleMsg(Peer: Peer, msgSeq: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
getSourceOfReplyMsg(peer: Peer, MsgId: string, SourceSeq: string): unknown;
|
||||
// 下面的msgid全部不真实
|
||||
getSourceOfReplyMsg(peer: Peer, msgId: string, sourceSeq: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
getSourceOfReplyMsgV2(peer: Peer, RootMsgId: string, ReplyMsgId: string): unknown;
|
||||
//用法和聊天记录一样
|
||||
getSourceOfReplyMsgV2(peer: Peer, rootMsgId: string, replyMsgId: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
getMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): unknown;
|
||||
getMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
getSourceOfReplyMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): unknown;
|
||||
getSourceOfReplyMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string, replyMsgId: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
getMsgsByTypeFilter(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilter: {
|
||||
type: number,
|
||||
subtype: Array<number>
|
||||
}): unknown;
|
||||
}): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
getMsgsByTypeFilters(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilters: Array<{
|
||||
type: number,
|
||||
subtype: Array<number>
|
||||
}>): unknown;
|
||||
}>): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
getMsgWithAbstractByFilterParam(...args: unknown[]): unknown;
|
||||
getMsgWithAbstractByFilterParam(...args: unknown[]): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
queryMsgsWithFilter(...args: unknown[]): unknown;
|
||||
queryMsgsWithFilter(...args: unknown[]): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
//queryMsgsWithFilterVer2(MsgId: string, MsgTime: string, param: QueryMsgsParams): Promise<unknown>;
|
||||
|
||||
|
@@ -41,7 +41,7 @@ export async function NCoreInitFramework(
|
||||
online: true,
|
||||
});
|
||||
};
|
||||
loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger) as any);
|
||||
loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger));
|
||||
});
|
||||
// 过早进入会导致addKernelMsgListener等Listener添加失败
|
||||
// await sleep(2500);
|
||||
|
BIN
src/native/packet/MoeHoo.linux.arm64.node
Normal file
BIN
src/native/packet/MoeHoo.linux.arm64.node
Normal file
Binary file not shown.
BIN
src/native/packet/MoeHoo.linux.x64.node
Normal file
BIN
src/native/packet/MoeHoo.linux.x64.node
Normal file
Binary file not shown.
BIN
src/native/packet/MoeHoo.win32.x64.node
Normal file
BIN
src/native/packet/MoeHoo.win32.x64.node
Normal file
Binary file not shown.
41
src/onebot/action/extends/GetAiCharacters.ts
Normal file
41
src/onebot/action/extends/GetAiCharacters.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { ActionName } from '../types';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
|
||||
import { AIVoiceChatType } from "@/core/packet/entities/aiChat";
|
||||
|
||||
const SchemaData = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
group_id: { type: ['number', 'string'] },
|
||||
chat_type: { type: ['number', 'string'] },
|
||||
},
|
||||
required: ['group_id'],
|
||||
} as const satisfies JSONSchema;
|
||||
|
||||
type Payload = FromSchema<typeof SchemaData>;
|
||||
|
||||
interface GetAiCharactersResponse {
|
||||
type: string;
|
||||
characters: {
|
||||
character_id: string;
|
||||
character_name: string;
|
||||
preview_url: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export class GetAiCharacters extends GetPacketStatusDepends<Payload, GetAiCharactersResponse[]> {
|
||||
actionName = ActionName.GetAiCharacters;
|
||||
payloadSchema = SchemaData;
|
||||
|
||||
async _handle(payload: Payload) {
|
||||
const rawList = await this.core.apis.PacketApi.sendFetchAiVoiceListReq(+payload.group_id, +(payload.chat_type ?? 1) as AIVoiceChatType);
|
||||
return rawList?.map((item) => ({
|
||||
type: item.category,
|
||||
characters: item.voices.map((voice) => ({
|
||||
character_id: voice.voiceId,
|
||||
character_name: voice.voiceDisplayName,
|
||||
preview_url: voice.voiceExampleUrl,
|
||||
})),
|
||||
})) ?? [];
|
||||
}
|
||||
}
|
85
src/onebot/action/extends/GetMiniAppArk.ts
Normal file
85
src/onebot/action/extends/GetMiniAppArk.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { ActionName } from '../types';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
|
||||
import { MiniAppData, MiniAppRawData, MiniAppReqCustomParams, MiniAppReqParams } from "@/core/packet/entities/miniApp";
|
||||
import { MiniAppInfo, MiniAppInfoHelper } from "@/core/packet/helper/miniAppHelper";
|
||||
|
||||
const SchemaData = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: ['bili', 'weibo']
|
||||
},
|
||||
title: { type: 'string' },
|
||||
desc: { type: 'string' },
|
||||
picUrl: { type: 'string' },
|
||||
jumpUrl: { type: 'string' },
|
||||
iconUrl: { type: 'string' },
|
||||
sdkId: { type: 'string' },
|
||||
appId: { type: 'string' },
|
||||
scene: { type: ['number', 'string'] },
|
||||
templateType: { type: ['number', 'string'] },
|
||||
businessType: { type: ['number', 'string'] },
|
||||
verType: { type: ['number', 'string'] },
|
||||
shareType: { type: ['number', 'string'] },
|
||||
versionId: { type: 'string' },
|
||||
withShareTicket: { type: ['number', 'string'] },
|
||||
rawArkData: { type: ['boolean', 'string'] }
|
||||
},
|
||||
oneOf: [
|
||||
{
|
||||
required: ['type', 'title', 'desc', 'picUrl', 'jumpUrl']
|
||||
},
|
||||
{
|
||||
required: [
|
||||
'title', 'desc', 'picUrl', 'jumpUrl',
|
||||
'iconUrl', 'appId', 'scene', 'templateType', 'businessType',
|
||||
'verType', 'shareType', 'versionId', 'withShareTicket'
|
||||
]
|
||||
}
|
||||
]
|
||||
} as const satisfies JSONSchema;
|
||||
|
||||
type Payload = FromSchema<typeof SchemaData>;
|
||||
|
||||
export class GetMiniAppArk extends GetPacketStatusDepends<Payload, {
|
||||
data: MiniAppData | MiniAppRawData
|
||||
}> {
|
||||
actionName = ActionName.GetMiniAppArk;
|
||||
payloadSchema = SchemaData;
|
||||
|
||||
async _handle(payload: Payload) {
|
||||
let reqParam: MiniAppReqParams;
|
||||
const customParams = {
|
||||
title: payload.title,
|
||||
desc: payload.desc,
|
||||
picUrl: payload.picUrl,
|
||||
jumpUrl: payload.jumpUrl
|
||||
} as MiniAppReqCustomParams;
|
||||
if (payload.type) {
|
||||
reqParam = MiniAppInfoHelper.generateReq(customParams, MiniAppInfo.get(payload.type)!.template);
|
||||
} else {
|
||||
const { appId, scene, iconUrl, templateType, businessType, verType, shareType, versionId, withShareTicket } = payload as Required<Payload>;
|
||||
reqParam = MiniAppInfoHelper.generateReq(
|
||||
customParams,
|
||||
{
|
||||
sdkId: payload.sdkId ?? MiniAppInfo.sdkId,
|
||||
appId: appId,
|
||||
scene: +scene,
|
||||
iconUrl: iconUrl,
|
||||
templateType: +templateType,
|
||||
businessType: +businessType,
|
||||
verType: +verType,
|
||||
shareType: +shareType,
|
||||
versionId: versionId,
|
||||
withShareTicket: +withShareTicket
|
||||
}
|
||||
);
|
||||
}
|
||||
const arkData = await this.core.apis.PacketApi.sendMiniAppShareInfoReq(reqParam);
|
||||
return {
|
||||
data: payload.rawArkData ? arkData : MiniAppInfoHelper.RawToSend(arkData)
|
||||
};
|
||||
}
|
||||
}
|
@@ -1,15 +1,22 @@
|
||||
import BaseAction from '../BaseAction';
|
||||
import { ActionName } from '../types';
|
||||
import { ActionName, BaseCheckResult } from '../types';
|
||||
|
||||
export class GetProfileLike extends BaseAction<void, any> {
|
||||
interface Payload {
|
||||
start: number,
|
||||
count: number
|
||||
}
|
||||
|
||||
export class GetProfileLike extends BaseAction<Payload, any> {
|
||||
actionName = ActionName.GetProfileLike;
|
||||
|
||||
async _handle(payload: void) {
|
||||
const ret = await this.core.apis.UserApi.getProfileLike(this.core.selfInfo.uid);
|
||||
const listdata: any[] = ret.info.userLikeInfos[0].favoriteInfo.userInfos;
|
||||
async _handle(payload: Payload) {
|
||||
const start = payload.start ? Number(payload.start) : 0;
|
||||
const count = payload.count ? Number(payload.count) : 10;
|
||||
const ret = await this.core.apis.UserApi.getProfileLike(this.core.selfInfo.uid, start, count);
|
||||
const listdata: any[] = ret.info.userLikeInfos[0].voteInfo.userInfos;
|
||||
for (let i = 0; i < listdata.length; i++) {
|
||||
listdata[i].uin = parseInt((await this.core.apis.UserApi.getUinByUidV2(listdata[i].uid)) || '');
|
||||
}
|
||||
return listdata;
|
||||
return ret.info.userLikeInfos[0].voteInfo;
|
||||
}
|
||||
}
|
||||
|
@@ -1,16 +1,15 @@
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import BaseAction from '../BaseAction';
|
||||
import { ActionName } from '../types';
|
||||
import { ActionName, BaseCheckResult } from '../types';
|
||||
import { ChatType, Peer } from '@/core';
|
||||
|
||||
const SchemaData = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
eventType: { type: 'string' },
|
||||
group_id: { type: 'string' },
|
||||
user_id: { type: 'string' },
|
||||
event_type: { type: 'number' },
|
||||
user_id: { type: ['number', 'string'] },
|
||||
},
|
||||
required: ['eventType'],
|
||||
required: ['event_type','user_id'],
|
||||
} as const satisfies JSONSchema;
|
||||
|
||||
type Payload = FromSchema<typeof SchemaData>;
|
||||
@@ -19,23 +18,12 @@ export class SetInputStatus extends BaseAction<Payload, any> {
|
||||
actionName = ActionName.SetInputStatus;
|
||||
|
||||
async _handle(payload: Payload) {
|
||||
let peer: Peer;
|
||||
if (payload.group_id) {
|
||||
peer = {
|
||||
chatType: ChatType.KCHATTYPEGROUP,
|
||||
peerUid: payload.group_id,
|
||||
};
|
||||
} else if (payload.user_id) {
|
||||
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id);
|
||||
if (!uid) throw new Error('uid is empty');
|
||||
peer = {
|
||||
chatType: ChatType.KCHATTYPEC2C,
|
||||
peerUid: uid,
|
||||
};
|
||||
} else {
|
||||
throw new Error('请指定 group_id 或 user_id');
|
||||
}
|
||||
|
||||
return await this.core.apis.MsgApi.sendShowInputStatusReq(peer, parseInt(payload.eventType));
|
||||
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
||||
if (!uid) throw new Error('uid is empty');
|
||||
const peer = {
|
||||
chatType: ChatType.KCHATTYPEC2C,
|
||||
peerUid: uid,
|
||||
};
|
||||
return await this.core.apis.MsgApi.sendShowInputStatusReq(peer, payload.event_type);
|
||||
}
|
||||
}
|
||||
|
@@ -38,11 +38,10 @@ export default class SetAvatar extends BaseAction<Payload, null> {
|
||||
throw `头像${payload.file}设置失败,api无返回`;
|
||||
}
|
||||
// log(`头像设置返回:${JSON.stringify(ret)}`)
|
||||
// @ts-ignore
|
||||
if (ret['result'] == 1004022) {
|
||||
if (ret.result as number == 1004022) {
|
||||
throw `头像${payload.file}设置失败,文件可能不是图片格式`;
|
||||
} else if (ret['result'] != 0) {
|
||||
throw `头像${payload.file}设置失败,未知的错误,${ret['result']}:${ret['errMsg']}`;
|
||||
} else if (ret.result != 0) {
|
||||
throw `头像${payload.file}设置失败,未知的错误,${ret.result}:${ret.errMsg}`;
|
||||
}
|
||||
} else {
|
||||
fs.unlink(path, () => { });
|
||||
|
@@ -5,9 +5,11 @@ import { promises as fs } from 'fs';
|
||||
import { decode } from 'silk-wasm';
|
||||
const FFMPEG_PATH = process.env.FFMPEG_PATH || 'ffmpeg';
|
||||
|
||||
interface Payload extends GetFilePayload {
|
||||
out_format: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac';
|
||||
}
|
||||
const out_format = ['mp3' , 'amr' , 'wma' , 'm4a' , 'spx' , 'ogg' , 'wav' , 'flac'];
|
||||
|
||||
type Payload = {
|
||||
out_format : string
|
||||
} & GetFilePayload
|
||||
|
||||
export default class GetRecord extends GetFileBase {
|
||||
actionName = ActionName.GetRecord;
|
||||
@@ -17,12 +19,19 @@ export default class GetRecord extends GetFileBase {
|
||||
if (payload.out_format && typeof payload.out_format === 'string') {
|
||||
const inputFile = res.file;
|
||||
if (!inputFile) throw new Error('file not found');
|
||||
if (!out_format.includes(payload.out_format)) {
|
||||
throw new Error('转换失败 out_format 字段可能格式不正确');
|
||||
}
|
||||
const pcmFile = `${inputFile}.pcm`;
|
||||
const outputFile = `${inputFile}.${payload.out_format}`;
|
||||
try {
|
||||
await fs.access(inputFile);
|
||||
await this.decodeFile(inputFile, pcmFile);
|
||||
await this.convertFile(pcmFile, outputFile, payload.out_format);
|
||||
try {
|
||||
await fs.access(outputFile);
|
||||
} catch (error) {
|
||||
await this.decodeFile(inputFile, pcmFile);
|
||||
await this.convertFile(pcmFile, outputFile, payload.out_format);
|
||||
}
|
||||
const base64Data = await fs.readFile(outputFile, { encoding: 'base64' });
|
||||
res.file = outputFile;
|
||||
res.url = outputFile;
|
||||
@@ -48,7 +57,8 @@ export default class GetRecord extends GetFileBase {
|
||||
|
||||
private convertFile(inputFile: string, outputFile: string, format: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const ffmpeg = spawn(FFMPEG_PATH, ['-f', 's16le', '-ar', '24000', '-ac', '1', '-i', inputFile, outputFile]);
|
||||
const params = format === 'amr' ? ['-f', 's16le', '-ar', '24000', '-ac', '1', '-i', inputFile, '-ar', '8000', '-b:a', '12.2k', outputFile] : ['-f', 's16le', '-ar', '24000', '-ac', '1', '-i', inputFile, outputFile];
|
||||
const ffmpeg = spawn(FFMPEG_PATH, params);
|
||||
|
||||
ffmpeg.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
@@ -63,4 +73,4 @@ export default class GetRecord extends GetFileBase {
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -73,16 +73,14 @@ export class GoCQHTTPGetForwardMsgAction extends BaseAction<Payload, any> {
|
||||
|
||||
const singleMsg = data.msgList[0];
|
||||
const resMsg = await this.obContext.apis.MsgApi.parseMessage(singleMsg, 'array');//强制array 以便处理
|
||||
if (!resMsg) {
|
||||
if (!(resMsg?.message?.[0] as OB11MessageForward)?.data?.content) {
|
||||
throw new Error('找不到相关的聊天记录');
|
||||
}
|
||||
//if (this.obContext.configLoader.configData.messagePostFormat === 'array') {
|
||||
//提取
|
||||
const realmsg = ((await this.parseForward([resMsg]))[0].data.message as OB11MessageNode[])[0].data.message;
|
||||
//里面都是offline消息 id都是0 没得说话
|
||||
return { message: realmsg };
|
||||
return {
|
||||
messages: (resMsg?.message?.[0] as OB11MessageForward)?.data?.content
|
||||
};
|
||||
//}
|
||||
|
||||
// return { message: resMsg };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -25,6 +25,11 @@ export default class GoCQHTTPGetStrangerInfo extends BaseAction<Payload, OB11Use
|
||||
if (!uid) uid = extendData.detail.uid;
|
||||
const info = (await this.core.apis.UserApi.getUserDetailInfo(uid));
|
||||
return {
|
||||
...extendData.detail.simpleInfo.coreInfo,
|
||||
...extendData.detail.commonExt ?? {},
|
||||
...extendData.detail.simpleInfo.baseInfo,
|
||||
...extendData.detail.simpleInfo.relationFlags ?? {},
|
||||
...extendData.detail.simpleInfo.status ?? {},
|
||||
user_id: parseInt(extendData.detail.uin) ?? 0,
|
||||
uid: info.uid ?? uid,
|
||||
nickname: extendData.detail.simpleInfo.coreInfo.nick,
|
||||
@@ -33,7 +38,7 @@ export default class GoCQHTTPGetStrangerInfo extends BaseAction<Payload, OB11Use
|
||||
qqLevel: calcQQLevel(extendData.detail.commonExt?.qqLevel ?? info.qqLevel),
|
||||
sex: OB11Entities.sex(extendData.detail.simpleInfo.baseInfo.sex) ?? OB11UserSex.unknown,
|
||||
long_nick: extendData.detail.simpleInfo.baseInfo.longNick ?? info.longNick,
|
||||
reg_time: extendData.detail.commonExt.regTime ?? info.regTime,
|
||||
reg_time: extendData.detail.commonExt?.regTime ?? info.regTime,
|
||||
is_vip: extendData.detail.simpleInfo.vasInfo?.svipFlag,
|
||||
is_years_vip: extendData.detail.simpleInfo.vasInfo?.yearVipFlag,
|
||||
vip_level: extendData.detail.simpleInfo.vasInfo?.vipLevel,
|
||||
|
@@ -6,10 +6,15 @@ const SchemaData = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
friend_id: { type: ['string', 'number'] },
|
||||
user_id: { type: ['string', 'number'] },
|
||||
temp_block: { type: 'boolean' },
|
||||
temp_both_del: { type: 'boolean' },
|
||||
},
|
||||
required: ['friend_id'],
|
||||
oneOf: [
|
||||
{ required: ['friend_id'] },
|
||||
{ required: ['user_id'] },
|
||||
],
|
||||
|
||||
} as const satisfies JSONSchema;
|
||||
type Payload = FromSchema<typeof SchemaData>;
|
||||
|
||||
@@ -18,7 +23,8 @@ export class GoCQHTTPDeleteFriend extends BaseAction<Payload, any> {
|
||||
payloadSchema = SchemaData;
|
||||
|
||||
async _handle(payload: Payload) {
|
||||
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.friend_id.toString());
|
||||
const uin = payload.friend_id ?? payload.user_id ?? '';
|
||||
const uid = await this.core.apis.UserApi.getUidByUinV2(uin.toString());
|
||||
|
||||
if (!uid) {
|
||||
return {
|
||||
|
@@ -31,15 +31,15 @@ export default class SetGroupPortrait extends BaseAction<Payload, any> {
|
||||
}
|
||||
if (path) {
|
||||
await checkFileReceived(path, 5000); // 文件不存在QQ会崩溃,需要提前判断
|
||||
const ret = await this.core.apis.GroupApi.setGroupAvatar(payload.group_id.toString(), path) as any;
|
||||
const ret = await this.core.apis.GroupApi.setGroupAvatar(payload.group_id.toString(), path);
|
||||
fs.unlink(path, () => { });
|
||||
if (!ret) {
|
||||
throw `头像${payload.file}设置失败,api无返回`;
|
||||
}
|
||||
if (ret['result'] == 1004022) {
|
||||
if (ret.result as number == 1004022) {
|
||||
throw `头像${payload.file}设置失败,文件可能不是图片格式或权限不足`;
|
||||
} else if (ret['result'] != 0) {
|
||||
throw `头像${payload.file}设置失败,未知的错误,${ret['result']}:${ret['errMsg']}`;
|
||||
} else if (ret.result != 0) {
|
||||
throw `头像${payload.file}设置失败,未知的错误,${ret.result}:${ret.errMsg}`;
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
|
28
src/onebot/action/group/GetAiRecord.ts
Normal file
28
src/onebot/action/group/GetAiRecord.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { ActionName } from '../types';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
|
||||
import { AIVoiceChatType } from "@/core/packet/entities/aiChat";
|
||||
import { NapProtoEncodeStructType } from "@/core/packet/proto/NapProto";
|
||||
import { IndexNode } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
|
||||
|
||||
const SchemaData = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
character: { type: ['string'] },
|
||||
group_id: { type: ['number', 'string'] },
|
||||
text: { type: 'string' },
|
||||
},
|
||||
required: ['character', 'group_id', 'text'],
|
||||
} as const satisfies JSONSchema;
|
||||
|
||||
type Payload = FromSchema<typeof SchemaData>;
|
||||
|
||||
export class GetAiRecord extends GetPacketStatusDepends<Payload, string> {
|
||||
actionName = ActionName.GetAiRecord;
|
||||
payloadSchema = SchemaData;
|
||||
|
||||
async _handle(payload: Payload) {
|
||||
const rawRsp = await this.core.apis.PacketApi.sendAiVoiceChatReq(+payload.group_id, payload.character, payload.text, AIVoiceChatType.Sound);
|
||||
return await this.core.apis.PacketApi.sendGroupPttFileDownloadReq(+payload.group_id, rawRsp.msgInfoBody[0].index);
|
||||
}
|
||||
}
|
@@ -13,7 +13,7 @@ const SchemaData = {
|
||||
|
||||
type Payload = FromSchema<typeof SchemaData>;
|
||||
|
||||
export class GetGroupShutList extends BaseAction<Payload, OB11Group> {
|
||||
export class GetGroupShutList extends BaseAction<Payload, any> {
|
||||
actionName = ActionName.GetGroupShutList;
|
||||
payloadSchema = SchemaData;
|
||||
|
||||
|
38
src/onebot/action/group/SendGroupAiRecord.ts
Normal file
38
src/onebot/action/group/SendGroupAiRecord.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { ActionName } from '../types';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
|
||||
import { AIVoiceChatType } from "@/core/packet/entities/aiChat";
|
||||
import { uri2local } from "@/common/file";
|
||||
import { ChatType, Peer } from "@/core";
|
||||
|
||||
const SchemaData = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
character: { type: ['string'] },
|
||||
group_id: { type: ['number', 'string'] },
|
||||
text: { type: 'string' },
|
||||
},
|
||||
required: ['character', 'group_id', 'text'],
|
||||
} as const satisfies JSONSchema;
|
||||
|
||||
type Payload = FromSchema<typeof SchemaData>;
|
||||
|
||||
export class SendGroupAiRecord extends GetPacketStatusDepends<Payload, {
|
||||
message_id: number
|
||||
}> {
|
||||
actionName = ActionName.SendGroupAiRecord;
|
||||
payloadSchema = SchemaData;
|
||||
|
||||
async _handle(payload: Payload) {
|
||||
const rawRsp = await this.core.apis.PacketApi.sendAiVoiceChatReq(+payload.group_id, payload.character, payload.text, AIVoiceChatType.Sound);
|
||||
const url = await this.core.apis.PacketApi.sendGroupPttFileDownloadReq(+payload.group_id, rawRsp.msgInfoBody[0].index);
|
||||
const { path, fileName, errMsg, success } = (await uri2local(this.core.NapCatTempPath, url));
|
||||
if (!success) {
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
const peer = { chatType: ChatType.KCHATTYPEGROUP, peerUid: payload.group_id.toString() } as Peer;
|
||||
const element = await this.core.apis.FileApi.createValidSendPttElement(path);
|
||||
const sendRes = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(peer, [element], [path]);
|
||||
return { message_id: sendRes.id ?? -1 };
|
||||
}
|
||||
}
|
@@ -98,6 +98,10 @@ import { GoCQHTTPCheckUrlSafely } from './go-cqhttp/GoCQHTTPCheckUrlSafely';
|
||||
import { GoCQHTTPGetModelShow } from './go-cqhttp/GoCQHTTPGetModelShow';
|
||||
import { GoCQHTTPSetModelShow } from './go-cqhttp/GoCQHTTPSetModelShow';
|
||||
import { GoCQHTTPDeleteFriend } from './go-cqhttp/GoCQHTTPDeleteFriend';
|
||||
import { GetMiniAppArk } from "@/onebot/action/extends/GetMiniAppArk";
|
||||
import { GetAiRecord } from "@/onebot/action/group/GetAiRecord";
|
||||
import { SendGroupAiRecord } from "@/onebot/action/group/SendGroupAiRecord";
|
||||
import { GetAiCharacters } from "@/onebot/action/extends/GetAiCharacters";
|
||||
|
||||
|
||||
export type ActionMap = Map<string, BaseAction<any, any>>;
|
||||
@@ -210,6 +214,10 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
|
||||
// new UploadForwardMsg(obContext, core),
|
||||
new GetGroupShutList(obContext, core),
|
||||
new GetGroupFileUrl(obContext, core),
|
||||
new GetMiniAppArk(obContext, core),
|
||||
new GetAiRecord(obContext, core),
|
||||
new SendGroupAiRecord(obContext, core),
|
||||
new GetAiCharacters(obContext, core),
|
||||
];
|
||||
const actionMap = new Map();
|
||||
for (const action of actionHandlers) {
|
||||
|
@@ -11,8 +11,8 @@ import { decodeCQCode } from '@/onebot/cqcode';
|
||||
import { MessageUnique } from '@/common/message-unique';
|
||||
import { ChatType, ElementType, NapCatCore, Peer, RawMessage, SendArkElement, SendMessageElement } from '@/core';
|
||||
import BaseAction from '../BaseAction';
|
||||
import { rawMsgWithSendMsg } from "@/core/packet/msg/converter";
|
||||
import { PacketMsg } from "@/core/packet/msg/message";
|
||||
import { rawMsgWithSendMsg } from "@/core/packet/message/converter";
|
||||
import { PacketMsg } from "@/core/packet/message/message";
|
||||
import { ForwardMsgBuilder } from "@/common/forward-msg-builder";
|
||||
import { stringifyWithBigInt } from "@/common/helper";
|
||||
|
||||
@@ -236,12 +236,11 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
message: RawMessage | null,
|
||||
res_id?: string
|
||||
}> {
|
||||
let returnMsg: RawMessage | undefined, res_id: string | undefined;
|
||||
const uploadReturnData = await this.uploadForwardedNodesPacket(msgPeer, messageNodes, source, news, summary, prompt);
|
||||
res_id = uploadReturnData?.res_id;
|
||||
const res_id = uploadReturnData?.res_id;
|
||||
const finallySendElements = uploadReturnData?.finallySendElements;
|
||||
if (!finallySendElements) throw Error('转发消息失败,生成节点为空');
|
||||
returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(msgPeer, [finallySendElements], [], true).catch(_ => undefined);
|
||||
const returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(msgPeer, [finallySendElements], [], true).catch(_ => undefined);
|
||||
return { message: returnMsg ?? null, res_id };
|
||||
}
|
||||
|
||||
@@ -276,7 +275,6 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
logger.logError.bind(this.core.context.logger)('子消息中包含非node消息 跳过不合法部分');
|
||||
continue;
|
||||
}
|
||||
// @ts-ignore
|
||||
const nodeMsg = await this.handleForwardedNodes(selfPeer, OB11Data.filter(e => e.type === OB11MessageDataType.node));
|
||||
if (nodeMsg) {
|
||||
nodeMsgIds.push(nodeMsg.message!.msgId);
|
||||
|
@@ -9,7 +9,7 @@ export abstract class GetPacketStatusDepends<PT, RT> extends BaseAction<PT, RT>
|
||||
if (!this.core.apis.PacketApi.available) {
|
||||
return {
|
||||
valid: false,
|
||||
message: "packetServer不可用,请参照文档 https://napneko.github.io/config/advanced 检查packetServer状态或进行配置!",
|
||||
message: "packetBackend不可用,请参照文档 https://napneko.github.io/config/advanced 和启动日志检查packetBackend状态或进行配置!",
|
||||
};
|
||||
}
|
||||
return await super.check(payload);
|
||||
|
@@ -134,7 +134,11 @@ export enum ActionName {
|
||||
GetGuildProfile = 'get_guild_service_profile',
|
||||
|
||||
GetGroupIgnoredNotifies = 'get_group_ignored_notifies',
|
||||
|
||||
|
||||
SetGroupSign = "set_group_sign",
|
||||
GetMiniAppArk = "get_mini_app_ark",
|
||||
// UploadForwardMsg = "upload_forward_msg",
|
||||
GetAiRecord = "get_ai_record",
|
||||
GetAiCharacters = "get_ai_characters",
|
||||
SendGroupAiRecord = "send_group_ai_record",
|
||||
}
|
||||
|
@@ -24,7 +24,6 @@ import {
|
||||
OB11MessageData,
|
||||
OB11MessageDataType,
|
||||
OB11MessageFileBase,
|
||||
OB11MessageForward,
|
||||
} from '@/onebot';
|
||||
import { OB11Entities } from '@/onebot/entities';
|
||||
import { EventType } from '@/onebot/event/OB11BaseEvent';
|
||||
@@ -218,20 +217,34 @@ export class OneBotMsgApi {
|
||||
if (records.peerUin === '284840486' || records.peerUin === '1094950020') {
|
||||
return createReplyData(records.msgId);
|
||||
}
|
||||
let replyMsgList = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV2(peer, element.replayMsgSeq, element.replyMsgTime, [element.senderUidStr])).msgList;
|
||||
let replyMsgList = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV2(peer, element.replayMsgSeq, records.msgTime, [element.senderUidStr])).msgList;
|
||||
let replyMsg = replyMsgList.find(msg => msg.msgRandom === records.msgRandom);
|
||||
|
||||
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
|
||||
// 我猜测可能是时间参数未对上 导致找不到引用消息 或者msgList 存在问题
|
||||
this.core.context.logger.logWarn.bind(this.core.context.logger)(
|
||||
'初次筛选消息失败,获取不到引用的消息 Seq:',
|
||||
this.core.context.logger.logError.bind(this.core.context.logger)(
|
||||
'筛选结果,筛选消息失败,将使用Fallback-1 Seq: ',
|
||||
element.replayMsgSeq,
|
||||
',消息长度:',
|
||||
replyMsgList.length
|
||||
);
|
||||
replyMsgList = (await this.core.apis.MsgApi.getMsgsBySeqAndCount(peer, element.replayMsgSeq, 1, true, true)).msgList;
|
||||
replyMsg = replyMsgList.find(msg => msg.msgRandom === records.msgRandom);
|
||||
}
|
||||
|
||||
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
|
||||
this.core.context.logger.logWarn.bind(this.core.context.logger)(
|
||||
'筛选消息失败,将使用Fallback-2 Seq:',
|
||||
element.replayMsgSeq,
|
||||
',消息长度:',
|
||||
replyMsgList.length
|
||||
);
|
||||
// 再次筛选
|
||||
replyMsgList = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV3(peer, element.replayMsgSeq, [element.senderUidStr])).msgList;
|
||||
replyMsg = replyMsgList.find(msg => msg.msgRandom === records.msgRandom);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 丢弃该消息段
|
||||
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
|
||||
this.core.context.logger.logError.bind(this.core.context.logger)(
|
||||
'最终筛选结果,筛选消息失败,获取不到引用的消息 Seq: ',
|
||||
@@ -327,11 +340,11 @@ export class OneBotMsgApi {
|
||||
},
|
||||
|
||||
multiForwardMsgElement: async (_, msg) => {
|
||||
const message_data: OB11MessageForward = {
|
||||
data: {} as any,
|
||||
type: OB11MessageDataType.forward,
|
||||
};
|
||||
message_data.data.id = msg.msgId;
|
||||
// const message_data: OB11MessageForward = {
|
||||
// data: {} as any,
|
||||
// type: OB11MessageDataType.forward,
|
||||
// };
|
||||
// message_data.data.id = msg.msgId;
|
||||
const parentMsgPeer = msg.parentMsgPeer ?? {
|
||||
chatType: msg.chatType,
|
||||
guildId: '',
|
||||
@@ -743,9 +756,12 @@ export class OneBotMsgApi {
|
||||
async (element) => {
|
||||
for (const key in element) {
|
||||
if (keyCanBeParsed(key, this.rawToOb11Converters) && element[key]) {
|
||||
const parsedElement = await this.rawToOb11Converters[key]?.(
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
const converters = this.rawToOb11Converters[key] as (
|
||||
element: Exclude<MessageElement[keyof RawToOb11Converters], null | undefined>,
|
||||
msg: RawMessage,
|
||||
elementWrapper: MessageElement,
|
||||
) => PromiseLike<OB11MessageData | null>;
|
||||
const parsedElement = await converters?.(
|
||||
element[key],
|
||||
msg,
|
||||
element,
|
||||
@@ -794,9 +810,11 @@ export class OneBotMsgApi {
|
||||
if (ignoreTypes.includes(sendMsg.type)) {
|
||||
continue;
|
||||
}
|
||||
const callResult = this.ob11ToRawConverters[sendMsg.type](
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
const converter = this.ob11ToRawConverters[sendMsg.type] as (
|
||||
sendMsg: Extract<OB11MessageData, { type: OB11MessageData['type'] }>,
|
||||
context: MessageContext,
|
||||
) => Promise<SendMessageElement | undefined>;
|
||||
const callResult = converter(
|
||||
sendMsg,
|
||||
{ peer, deleteAfterSentFiles },
|
||||
)?.catch(undefined);
|
||||
|
@@ -68,13 +68,13 @@ export function encodeCQCode(data: OB11MessageData) {
|
||||
|
||||
let result = '[CQ:' + data.type;
|
||||
for (const name in data.data) {
|
||||
const value = (data.data as any)[name];
|
||||
const value = (data.data as Record<string, unknown>)[name];
|
||||
if (value === undefined) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const text = value.toString();
|
||||
result += `,${name}=${CQCodeEscape(text)}`;
|
||||
const text = value?.toString();
|
||||
if (text) result += `,${name}=${CQCodeEscape(text)}`;
|
||||
} catch (error) {
|
||||
// If it can't be converted, skip this name-value pair
|
||||
}
|
||||
|
@@ -311,15 +311,19 @@ export class NapCatOneBot11Adapter {
|
||||
}
|
||||
};
|
||||
|
||||
const msgIdSend = new LRUCache<string, boolean>(100);
|
||||
const msgIdSend = new LRUCache<string, number>(100);
|
||||
const recallMsgs = new LRUCache<string, boolean>(100);
|
||||
msgListener.onAddSendMsg = async msg => {
|
||||
if (msg.sendStatus == SendStatusType.KSEND_STATUS_SENDING) {
|
||||
msgIdSend.put(msg.msgId, 0);
|
||||
}
|
||||
};
|
||||
msgListener.onMsgInfoListUpdate = async msgList => {
|
||||
this.emitRecallMsg(msgList, recallMsgs)
|
||||
.catch(e => this.context.logger.logError.bind(this.context.logger)('处理消息失败', e));
|
||||
|
||||
for (const msg of msgList.filter(e => e.senderUin == this.core.selfInfo.uin)) {
|
||||
if (msg.sendStatus == SendStatusType.KSEND_STATUS_SUCCESS && !msgIdSend.get(msg.msgId)) {
|
||||
msgIdSend.put(msg.msgId, true);
|
||||
if (msg.sendStatus == SendStatusType.KSEND_STATUS_SUCCESS && msgIdSend.get(msg.msgId) == 0) {
|
||||
msgIdSend.put(msg.msgId, 1);
|
||||
// 完成后再post
|
||||
this.apis.MsgApi.parseMessage(msg)
|
||||
.then((ob11Msg) => {
|
||||
@@ -341,7 +345,7 @@ export class NapCatOneBot11Adapter {
|
||||
};
|
||||
|
||||
this.context.session.getMsgService().addKernelMsgListener(
|
||||
proxiedListenerOf(msgListener, this.context.logger) as any,
|
||||
proxiedListenerOf(msgListener, this.context.logger),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -370,7 +374,7 @@ export class NapCatOneBot11Adapter {
|
||||
};
|
||||
|
||||
this.context.session.getBuddyService().addKernelBuddyListener(
|
||||
proxiedListenerOf(buddyListener, this.context.logger) as any,
|
||||
proxiedListenerOf(buddyListener, this.context.logger),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -523,7 +527,7 @@ export class NapCatOneBot11Adapter {
|
||||
);
|
||||
}
|
||||
|
||||
private async emitMsg(message: RawMessage) {
|
||||
private async emitMsg(message: RawMessage, parseEvent: boolean = true) {
|
||||
const { debug, reportSelfMessage, messagePostFormat } = this.configLoader.configData;
|
||||
this.context.logger.logDebug('收到新消息 RawMessage', message);
|
||||
this.apis.MsgApi.parseMessage(message, messagePostFormat).then((ob11Msg) => {
|
||||
|
@@ -64,9 +64,9 @@ export class OB11PassiveHttpAdapter implements IOB11NetworkAdapter {
|
||||
});
|
||||
|
||||
this.app.use((req, res, next) => this.authorize(this.token, req, res, next));
|
||||
// @ts-ignore
|
||||
this.app.use((req, res) => this.handleRequest(req, res));
|
||||
|
||||
this.app.use(async (req, res, _) => {
|
||||
await this.handleRequest(req, res);
|
||||
});
|
||||
this.server.listen(this.port, () => {
|
||||
this.core.context.logger.log(`[OneBot] [HTTP Server Adapter] Start On Port ${this.port}`);
|
||||
});
|
||||
|
@@ -143,7 +143,7 @@ export class OB11PassiveWebSocketAdapter implements IOB11NetworkAdapter {
|
||||
private registerHeartBeat() {
|
||||
this.heartbeatIntervalId = setInterval(() => {
|
||||
this.wsClientsMutex.runExclusive(async () => {
|
||||
this.wsClients.forEach((wsClient) => {
|
||||
this.wsClientWithEvent.forEach((wsClient) => {
|
||||
if (wsClient.readyState === WebSocket.OPEN) {
|
||||
wsClient.send(JSON.stringify(new OB11HeartbeatEvent(this.core, this.heartbeatInterval, this.core.selfInfo.online, true)));
|
||||
}
|
||||
|
@@ -165,7 +165,7 @@ export async function NCoreInitShell() {
|
||||
logger.logError.bind(logger)('[Core] [Login] Login Error , ErrInfo: ', args);
|
||||
};
|
||||
|
||||
loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger) as any);
|
||||
loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger));
|
||||
const isConnect = loginService.connect();
|
||||
if (!isConnect) {
|
||||
logger.logError.bind(logger)('核心登录服务连接失败!');
|
||||
|
@@ -164,7 +164,7 @@ async function onSettingWindowCreated(view) {
|
||||
SettingItem(
|
||||
'<span id="napcat-update-title">Napcat</span>',
|
||||
void 0,
|
||||
SettingButton("V3.3.12", "napcat-update-button", "secondary")
|
||||
SettingButton("V3.6.1", "napcat-update-button", "secondary")
|
||||
)
|
||||
]),
|
||||
SettingList([
|
||||
|
@@ -26,6 +26,7 @@ const FrameworkBaseConfigPlugin: PluginOption[] = [
|
||||
targets: [
|
||||
{ src: './manifest.json', dest: 'dist' },
|
||||
{ src: './src/core/external/napcat.json', dest: 'dist/config/' },
|
||||
{ src: './src/native/packet', dest: 'dist/moehoo', flatten: false },
|
||||
{ src: './static/', dest: 'dist/static/', flatten: false },
|
||||
{ src: './src/onebot/config/onebot11.json', dest: 'dist/config/' },
|
||||
{ src: './src/framework/liteloader.cjs', dest: 'dist' },
|
||||
@@ -42,6 +43,7 @@ const ShellBaseConfigPlugin: PluginOption[] = [
|
||||
cp({
|
||||
targets: [
|
||||
{ src: './src/native/external', dest: 'dist/native', flatten: false },
|
||||
{ src: './src/native/packet', dest: 'dist/moehoo', flatten: false },
|
||||
{ src: './static/', dest: 'dist/static/', flatten: false },
|
||||
{ src: './src/core/external/napcat.json', dest: 'dist/config/' },
|
||||
{ src: './src/onebot/config/onebot11.json', dest: 'dist/config/' },
|
||||
|
Reference in New Issue
Block a user