mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
165 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
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 | ||
![]() |
b83229b5da | ||
![]() |
6f053f5f7d | ||
![]() |
c3dc53eaaf | ||
![]() |
ffdc34cfe2 | ||
![]() |
4825a0e341 | ||
![]() |
95a00d7f35 | ||
![]() |
d885bab426 | ||
![]() |
e2a6a0bc02 | ||
![]() |
ff7d8609ce | ||
![]() |
7507b90e03 | ||
![]() |
2b226a4b27 | ||
![]() |
8b0232c4fe | ||
![]() |
0728ee9ad6 | ||
![]() |
8c6f04d0bc | ||
![]() |
c67fad789e | ||
![]() |
4072339d70 | ||
![]() |
3a244f5804 | ||
![]() |
f12cf59137 | ||
![]() |
c76f556a11 | ||
![]() |
e0f3d07b98 | ||
![]() |
378d85dc67 | ||
![]() |
875e91fc0e | ||
![]() |
15f7cd9814 | ||
![]() |
1eb5cd6237 | ||
![]() |
ad2f843c8f | ||
![]() |
8e550e216e | ||
![]() |
9f07b07c82 | ||
![]() |
0be6effc32 | ||
![]() |
7ab6a10fc9 | ||
![]() |
fb09af0e64 | ||
![]() |
0d99d30b2d | ||
![]() |
0000ec8b5b | ||
![]() |
0085bd8a1f | ||
![]() |
617139dfa4 | ||
![]() |
4eb4a612d0 | ||
![]() |
cda5e784f6 | ||
![]() |
d93a280ab3 | ||
![]() |
f7e2b3a4a7 | ||
![]() |
39d9c8fa74 | ||
![]() |
8823895a03 | ||
![]() |
b44a9e696c | ||
![]() |
cf28a3dc17 | ||
![]() |
7416e6caf6 | ||
![]() |
90f6896f3c | ||
![]() |
eebcd0700d | ||
![]() |
133eee0c66 | ||
![]() |
640fb75f74 | ||
![]() |
51dcc1add6 | ||
![]() |
730c928f91 | ||
![]() |
c3b7e111b9 | ||
![]() |
1874e48925 | ||
![]() |
e7a082c91c | ||
![]() |
5d4f45407e | ||
![]() |
17c37ec32f | ||
![]() |
b5f8140c79 | ||
![]() |
63f746c237 | ||
![]() |
dac6709f27 | ||
![]() |
470c8d0b29 | ||
![]() |
b0d35e803b | ||
![]() |
a71475be8b | ||
![]() |
b9f2cc5142 | ||
![]() |
2d46e55b9b | ||
![]() |
684e254996 | ||
![]() |
a2f7903960 | ||
![]() |
c0c757d6bd | ||
![]() |
da0fad743d | ||
![]() |
80b10d6025 | ||
![]() |
a27c2a69c4 | ||
![]() |
9ed2a2fd19 | ||
![]() |
aa9d96718c | ||
![]() |
aa67a2b71c | ||
![]() |
d3405edd42 | ||
![]() |
3612098d62 | ||
![]() |
2f08b72d69 | ||
![]() |
ab66904c1a | ||
![]() |
55542a3dbe | ||
![]() |
8569a45114 | ||
![]() |
c790311fc3 | ||
![]() |
3c45c8bd80 | ||
![]() |
d5b7b3ae31 | ||
![]() |
43e73a5f24 | ||
![]() |
698947ed97 | ||
![]() |
f3d967ae07 | ||
![]() |
dbe72fa07e | ||
![]() |
801a97d85b | ||
![]() |
9f8f938c47 | ||
![]() |
8fe37d1c1e | ||
![]() |
5cca8457e7 | ||
![]() |
e9332e7646 | ||
![]() |
31365505d8 | ||
![]() |
b3fbe9e34a | ||
![]() |
4082b651c5 | ||
![]() |
0081000ef0 | ||
![]() |
ad4d6a1070 | ||
![]() |
5190b26399 | ||
![]() |
29a8db96f4 | ||
![]() |
1a4c2cabfd | ||
![]() |
ef9189055c | ||
![]() |
5cc3719125 | ||
![]() |
5d46f41348 | ||
![]() |
3c2c1963f4 | ||
![]() |
4896ca9279 | ||
![]() |
f0afba6cd9 | ||
![]() |
bd717c298a | ||
![]() |
baaa8a70dc | ||
![]() |
6d561c6e6f | ||
![]() |
e6b6947d49 | ||
![]() |
52e99a2175 | ||
![]() |
052d17a46f | ||
![]() |
1aa1f4c212 | ||
![]() |
c3a48e3344 | ||
![]() |
1d5483dc28 | ||
![]() |
54277fa0df | ||
![]() |
ab04bd262f | ||
![]() |
fb23087b65 | ||
![]() |
846fee7ac8 | ||
![]() |
977eacc679 | ||
![]() |
dacfefe644 | ||
![]() |
345e941e11 | ||
![]() |
6cb7d45464 | ||
![]() |
e7222653fa | ||
![]() |
014f0758f5 | ||
![]() |
0e8b416f6d | ||
![]() |
09a60a2204 | ||
![]() |
b0eae307c2 | ||
![]() |
f5d2b54cca | ||
![]() |
3eefec3899 | ||
![]() |
b6a8094554 | ||
![]() |
4083b35436 | ||
![]() |
bb72d70baf | ||
![]() |
95d1a77f52 | ||
![]() |
051729886e | ||
![]() |
0f00123dc7 |
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -127,6 +127,10 @@ jobs:
|
|||||||
zip -q -r NapCat.Framework.Windows.Once.zip *
|
zip -q -r NapCat.Framework.Windows.Once.zip *
|
||||||
cd ..
|
cd ..
|
||||||
mv ./NapCat.Framework.Windows.Once/NapCat.Framework.Windows.Once.zip ./
|
mv ./NapCat.Framework.Windows.Once/NapCat.Framework.Windows.Once.zip ./
|
||||||
|
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
|
- name: Extract version from tag
|
||||||
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
||||||
|
|
||||||
@@ -143,4 +147,8 @@ jobs:
|
|||||||
NapCat.Framework.zip
|
NapCat.Framework.zip
|
||||||
NapCat.Shell.zip
|
NapCat.Shell.zip
|
||||||
NapCat.Framework.Windows.Once.zip
|
NapCat.Framework.Windows.Once.zip
|
||||||
|
napcat.packet.arm64
|
||||||
|
napcat.packet.exe
|
||||||
|
napcat.packet.linux
|
||||||
|
napcat.packet.production.py
|
||||||
draft: true
|
draft: true
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="https://socialify.git.ci/NapNeko/NapCatQQ/image?font=Jost&logo=https%3A%2F%2Fnapneko.github.io%2Fassets%2Flogo.png&name=1&owner=1&pattern=Diagonal%20Stripes&stargazers=1&theme=Auto" alt="NapCatQQ" width="640" height="320" />
|
|
||||||
|

|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -23,7 +25,6 @@ NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
|
|||||||
**首次使用**请务必查看如下文档看使用教程
|
**首次使用**请务必查看如下文档看使用教程
|
||||||
|
|
||||||
### 文档地址
|
### 文档地址
|
||||||
[Github.IO](https://napneko.github.io/)
|
|
||||||
|
|
||||||
[Cloudflare.Worker](https://doc.napneko.icu/)
|
[Cloudflare.Worker](https://doc.napneko.icu/)
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
|
|||||||
|
|
||||||
[Cloudflare.Pages](https://napneko.pages.dev/)
|
[Cloudflare.Pages](https://napneko.pages.dev/)
|
||||||
|
|
||||||
|
[Github.IO](https://napneko.github.io/)
|
||||||
## 回家旅途
|
## 回家旅途
|
||||||
[QQ Group](https://qm.qq.com/q/VfjAq5HIMS)
|
[QQ Group](https://qm.qq.com/q/VfjAq5HIMS)
|
||||||
|
|
||||||
|
BIN
external/packet/napcat.packet.arm64
vendored
Normal file
BIN
external/packet/napcat.packet.arm64
vendored
Normal file
Binary file not shown.
BIN
external/packet/napcat.packet.exe
vendored
Normal file
BIN
external/packet/napcat.packet.exe
vendored
Normal file
Binary file not shown.
BIN
external/packet/napcat.packet.linux
vendored
Normal file
BIN
external/packet/napcat.packet.linux
vendored
Normal file
Binary file not shown.
102
external/packet/napcat.packet.production.py
vendored
Normal file
102
external/packet/napcat.packet.production.py
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
logo.png
BIN
logo.png
Binary file not shown.
Before Width: | Height: | Size: 208 KiB After Width: | Height: | Size: 335 KiB |
@@ -4,7 +4,7 @@
|
|||||||
"name": "NapCatQQ",
|
"name": "NapCatQQ",
|
||||||
"slug": "NapCat.Framework",
|
"slug": "NapCat.Framework",
|
||||||
"description": "高性能的 OneBot 11 协议实现",
|
"description": "高性能的 OneBot 11 协议实现",
|
||||||
"version": "3.0.1",
|
"version": "3.3.27",
|
||||||
"icon": "./logo.png",
|
"icon": "./logo.png",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
|
28
package.json
28
package.json
@@ -2,7 +2,7 @@
|
|||||||
"name": "napcat",
|
"name": "napcat",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "3.0.1",
|
"version": "3.3.27",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:framework": "vite build --mode framework",
|
"build:framework": "vite build --mode framework",
|
||||||
"build:shell": "vite build --mode shell",
|
"build:shell": "vite build --mode shell",
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/preset-typescript": "^7.24.7",
|
"@babel/preset-typescript": "^7.24.7",
|
||||||
"@log4js-node/log4js-api": "^1.0.2",
|
"@log4js-node/log4js-api": "^1.0.2",
|
||||||
|
"@protobuf-ts/runtime": "^2.9.4",
|
||||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
"@rollup/plugin-typescript": "^11.1.6",
|
"@rollup/plugin-typescript": "^11.1.6",
|
||||||
"@types/cors": "^2.8.17",
|
"@types/cors": "^2.8.17",
|
||||||
@@ -23,29 +24,28 @@
|
|||||||
"@types/ws": "^8.5.12",
|
"@types/ws": "^8.5.12",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.3.0",
|
"@typescript-eslint/eslint-plugin": "^8.3.0",
|
||||||
"@typescript-eslint/parser": "^8.3.0",
|
"@typescript-eslint/parser": "^8.3.0",
|
||||||
|
"ajv": "^8.13.0",
|
||||||
|
"async-mutex": "^0.5.0",
|
||||||
|
"chalk": "^5.3.0",
|
||||||
|
"commander": "^12.1.0",
|
||||||
|
"cors": "^2.8.5",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-import-resolver-typescript": "^3.6.1",
|
"eslint-import-resolver-typescript": "^3.6.1",
|
||||||
"eslint-plugin-import": "^2.29.1",
|
"eslint-plugin-import": "^2.29.1",
|
||||||
|
"fast-xml-parser": "^4.3.6",
|
||||||
|
"file-type": "^19.0.0",
|
||||||
|
"image-size": "^1.1.1",
|
||||||
|
"json-schema-to-ts": "^3.1.1",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"vite": "^5.2.6",
|
"vite": "^5.2.6",
|
||||||
"vite-plugin-cp": "^4.0.8",
|
"vite-plugin-cp": "^4.0.8",
|
||||||
"vite-tsconfig-paths": "^4.3.2",
|
"vite-tsconfig-paths": "^4.3.2"
|
||||||
"@protobuf-ts/runtime": "^2.9.4",
|
|
||||||
"ajv": "^8.13.0",
|
|
||||||
"fast-xml-parser": "^4.3.6",
|
|
||||||
"chalk": "^5.3.0",
|
|
||||||
"commander": "^12.1.0",
|
|
||||||
"async-mutex": "^0.5.0",
|
|
||||||
"file-type": "^19.0.0",
|
|
||||||
"json-schema-to-ts": "^3.1.0",
|
|
||||||
"image-size": "^1.1.1",
|
|
||||||
"cors": "^2.8.5"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"qrcode-terminal": "^0.12.0",
|
"express": "^5.0.0",
|
||||||
"fluent-ffmpeg": "^2.1.2",
|
"fluent-ffmpeg": "^2.1.2",
|
||||||
"express": "^5.0.0-beta.2",
|
|
||||||
"log4js": "^6.9.1",
|
"log4js": "^6.9.1",
|
||||||
|
"qrcode-terminal": "^0.12.0",
|
||||||
"silk-wasm": "^3.6.1",
|
"silk-wasm": "^3.6.1",
|
||||||
"ws": "^8.18.0"
|
"ws": "^8.18.0"
|
||||||
}
|
}
|
||||||
|
@@ -4,16 +4,27 @@ const process = require("process");
|
|||||||
console.log("[NapCat] [CheckVersion] 开始检测当前仓库版本...");
|
console.log("[NapCat] [CheckVersion] 开始检测当前仓库版本...");
|
||||||
try {
|
try {
|
||||||
const packageJson = require("../package.json");
|
const packageJson = require("../package.json");
|
||||||
|
const manifsetJson = require("../manifest.json");
|
||||||
|
|
||||||
const currentVersion = packageJson.version;
|
const currentVersion = packageJson.version;
|
||||||
const targetVersion = process.env.VERSION;
|
const targetVersion = process.env.VERSION;
|
||||||
|
|
||||||
|
const manifestCurrentVersion = manifsetJson.version;
|
||||||
|
const manifestTargetVersion = process.env.VERSION;
|
||||||
|
|
||||||
console.log("[NapCat] [CheckVersion] currentVersion:", currentVersion, "targetVersion:", targetVersion);
|
console.log("[NapCat] [CheckVersion] currentVersion:", currentVersion, "targetVersion:", targetVersion);
|
||||||
|
console.log("[NapCat] [CheckVersion] manifestCurrentVersion:", manifestCurrentVersion, "manifestTargetVersion:", manifestTargetVersion);
|
||||||
|
|
||||||
// 验证 targetVersion 格式
|
// 验证 targetVersion 格式
|
||||||
if (!targetVersion || typeof targetVersion !== 'string') {
|
if (!targetVersion || typeof targetVersion !== 'string') {
|
||||||
console.log("[NapCat] [CheckVersion] 目标版本格式不正确或未设置!");
|
console.log("[NapCat] [CheckVersion] 目标版本格式不正确或未设置!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// 验证 manifestTargetVersion 格式
|
||||||
|
if (!manifestTargetVersion || typeof manifestTargetVersion !== 'string') {
|
||||||
|
console.log("[NapCat] [CheckVersion] manifest目标版本格式不正确或未设置!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 写入脚本文件的统一函数
|
// 写入脚本文件的统一函数
|
||||||
const writeScriptToFile = (content) => {
|
const writeScriptToFile = (content) => {
|
||||||
@@ -21,7 +32,7 @@ try {
|
|||||||
console.log("[NapCat] [CheckVersion] checkVersion.sh 文件已更新。");
|
console.log("[NapCat] [CheckVersion] checkVersion.sh 文件已更新。");
|
||||||
};
|
};
|
||||||
|
|
||||||
if (currentVersion === targetVersion) {
|
if (currentVersion === targetVersion && manifestCurrentVersion === manifestTargetVersion) {
|
||||||
// 不需要更新版本,写入一个简单的脚本
|
// 不需要更新版本,写入一个简单的脚本
|
||||||
const simpleScript = "#!/bin/bash\necho \"CheckVersion Is Done\"";
|
const simpleScript = "#!/bin/bash\necho \"CheckVersion Is Done\"";
|
||||||
writeScriptToFile(simpleScript);
|
writeScriptToFile(simpleScript);
|
||||||
@@ -29,11 +40,14 @@ try {
|
|||||||
// 更新版本,构建安全的sed命令
|
// 更新版本,构建安全的sed命令
|
||||||
const safeScriptContent = `
|
const safeScriptContent = `
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
git config --global user.email "bot@test.wumiao.wang"
|
git config --global user.email "nanaeonn@outlook.com"
|
||||||
git config --global user.name "Version"
|
git config --global user.name "Mlikiowa"
|
||||||
sed -i "s/\\\"version\\\": \\\"${currentVersion}\\\"/\\\"version\\\": \\\"${targetVersion}\\\"/g" package.json
|
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 add .
|
||||||
git commit -m "chore:version change"
|
git commit -m "release: v${targetVersion}"
|
||||||
git push -u origin main`;
|
git push -u origin main`;
|
||||||
writeScriptToFile(safeScriptContent);
|
writeScriptToFile(safeScriptContent);
|
||||||
}
|
}
|
||||||
|
@@ -9,6 +9,15 @@ interface InternalMapKey {
|
|||||||
checker: ((...args: any[]) => boolean) | undefined;
|
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 type ListenerClassBase = Record<string, string>;
|
||||||
|
|
||||||
export class NTEventWrapper {
|
export class NTEventWrapper {
|
||||||
@@ -43,10 +52,8 @@ export class NTEventWrapper {
|
|||||||
|
|
||||||
createEventFunction<
|
createEventFunction<
|
||||||
Service extends keyof ServiceNamingMapping,
|
Service extends keyof ServiceNamingMapping,
|
||||||
ServiceMethod extends Exclude<keyof ServiceNamingMapping[Service], symbol>,
|
ServiceMethod extends FuncKeys<ServiceNamingMapping[Service]>,
|
||||||
// eslint-disable-next-line
|
T extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]>,
|
||||||
// @ts-ignore
|
|
||||||
T extends (...args: any) => any = ServiceNamingMapping[Service][ServiceMethod],
|
|
||||||
>(eventName: `${Service}/${ServiceMethod}`): T | undefined {
|
>(eventName: `${Service}/${ServiceMethod}`): T | undefined {
|
||||||
const eventNameArr = eventName.split('/');
|
const eventNameArr = eventName.split('/');
|
||||||
type eventType = {
|
type eventType = {
|
||||||
@@ -98,10 +105,8 @@ export class NTEventWrapper {
|
|||||||
|
|
||||||
async callNoListenerEvent<
|
async callNoListenerEvent<
|
||||||
Service extends keyof ServiceNamingMapping,
|
Service extends keyof ServiceNamingMapping,
|
||||||
ServiceMethod extends Exclude<keyof ServiceNamingMapping[Service], symbol>,
|
ServiceMethod extends FuncKeys<ServiceNamingMapping[Service]>,
|
||||||
// eslint-disable-next-line
|
EventType extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]>,
|
||||||
// @ts-ignore
|
|
||||||
EventType extends (...args: any) => any = ServiceNamingMapping[Service][ServiceMethod],
|
|
||||||
>(
|
>(
|
||||||
serviceAndMethod: `${Service}/${ServiceMethod}`,
|
serviceAndMethod: `${Service}/${ServiceMethod}`,
|
||||||
...args: Parameters<EventType>
|
...args: Parameters<EventType>
|
||||||
@@ -111,10 +116,8 @@ export class NTEventWrapper {
|
|||||||
|
|
||||||
async registerListen<
|
async registerListen<
|
||||||
Listener extends keyof ListenerNamingMapping,
|
Listener extends keyof ListenerNamingMapping,
|
||||||
ListenerMethod extends Exclude<keyof ListenerNamingMapping[Listener], symbol>,
|
ListenerMethod extends FuncKeys<ListenerNamingMapping[Listener]>,
|
||||||
// eslint-disable-next-line
|
ListenerType extends (...args: any) => any = EnsureFunc<ListenerNamingMapping[Listener][ListenerMethod]>,
|
||||||
// @ts-ignore
|
|
||||||
ListenerType extends (...args: any) => any = ListenerNamingMapping[Listener][ListenerMethod],
|
|
||||||
>(
|
>(
|
||||||
listenerAndMethod: `${Listener}/${ListenerMethod}`,
|
listenerAndMethod: `${Listener}/${ListenerMethod}`,
|
||||||
waitTimes = 1,
|
waitTimes = 1,
|
||||||
@@ -164,15 +167,11 @@ export class NTEventWrapper {
|
|||||||
|
|
||||||
async callNormalEventV2<
|
async callNormalEventV2<
|
||||||
Service extends keyof ServiceNamingMapping,
|
Service extends keyof ServiceNamingMapping,
|
||||||
ServiceMethod extends Exclude<keyof ServiceNamingMapping[Service], symbol>,
|
ServiceMethod extends FuncKeys<ServiceNamingMapping[Service]>,
|
||||||
Listener extends keyof ListenerNamingMapping,
|
Listener extends keyof ListenerNamingMapping,
|
||||||
ListenerMethod extends Exclude<keyof ListenerNamingMapping[Listener], symbol>,
|
ListenerMethod extends FuncKeys<ListenerNamingMapping[Listener]>,
|
||||||
// eslint-disable-next-line
|
EventType extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]>,
|
||||||
// @ts-ignore
|
ListenerType extends (...args: any) => any = EnsureFunc<ListenerNamingMapping[Listener][ListenerMethod]>
|
||||||
EventType extends (...args: any) => any = ServiceNamingMapping[Service][ServiceMethod],
|
|
||||||
// eslint-disable-next-line
|
|
||||||
// @ts-ignore
|
|
||||||
ListenerType extends (...args: any) => any = ListenerNamingMapping[Listener][ListenerMethod]
|
|
||||||
>(
|
>(
|
||||||
serviceAndMethod: `${Service}/${ServiceMethod}`,
|
serviceAndMethod: `${Service}/${ServiceMethod}`,
|
||||||
listenerAndMethod: `${Listener}/${ListenerMethod}`,
|
listenerAndMethod: `${Listener}/${ListenerMethod}`,
|
||||||
|
118
src/common/forward-msg-builder.ts
Normal file
118
src/common/forward-msg-builder.ts
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import { PacketMsg } from "@/core/packet/message/message";
|
||||||
|
import * as crypto from "node:crypto";
|
||||||
|
|
||||||
|
interface ForwardMsgJson {
|
||||||
|
app: string
|
||||||
|
config: ForwardMsgJsonConfig,
|
||||||
|
desc: string,
|
||||||
|
extra: ForwardMsgJsonExtra,
|
||||||
|
meta: ForwardMsgJsonMeta,
|
||||||
|
prompt: string,
|
||||||
|
ver: string,
|
||||||
|
view: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ForwardMsgJsonConfig {
|
||||||
|
autosize: number,
|
||||||
|
forward: number,
|
||||||
|
round: number,
|
||||||
|
type: string,
|
||||||
|
width: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ForwardMsgJsonExtra {
|
||||||
|
filename: string,
|
||||||
|
tsum: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ForwardMsgJsonMeta {
|
||||||
|
detail: ForwardMsgJsonMetaDetail
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ForwardMsgJsonMetaDetail {
|
||||||
|
news: {
|
||||||
|
text: string
|
||||||
|
}[],
|
||||||
|
resid: string,
|
||||||
|
source: string,
|
||||||
|
summary: string,
|
||||||
|
uniseq: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ForwardAdaptMsg {
|
||||||
|
senderName?: string;
|
||||||
|
isGroupMsg?: boolean;
|
||||||
|
msg?: ForwardAdaptMsgElement[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ForwardAdaptMsgElement {
|
||||||
|
preview?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ForwardMsgBuilder {
|
||||||
|
private static build(resId: string, msg: ForwardAdaptMsg[], source?: string, news?: ForwardMsgJsonMetaDetail["news"], summary?: string, prompt?: string): ForwardMsgJson {
|
||||||
|
const id = crypto.randomUUID();
|
||||||
|
const isGroupMsg = msg.some(m => m.isGroupMsg);
|
||||||
|
if (!source) {
|
||||||
|
source = isGroupMsg ? "群聊的聊天记录" :
|
||||||
|
msg.length
|
||||||
|
? Array.from(new Set(msg.slice(0, 4).map(m => m.senderName)))
|
||||||
|
.join('和') + '的聊天记录'
|
||||||
|
: '聊天记录';
|
||||||
|
}
|
||||||
|
if (!news) {
|
||||||
|
news = msg.length === 0 ? [{
|
||||||
|
text: "Nya~ This message is send from NapCat.Packet!",
|
||||||
|
}] : msg.map(m => ({
|
||||||
|
text: `${m.senderName}: ${m.msg?.map(msg => msg.preview).join('')}`,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (!summary) {
|
||||||
|
summary = `查看${msg.length}条转发消息`;
|
||||||
|
}
|
||||||
|
if (!prompt) {
|
||||||
|
prompt = "[聊天记录]";
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
app: "com.tencent.multimsg",
|
||||||
|
config: {
|
||||||
|
autosize: 1,
|
||||||
|
forward: 1,
|
||||||
|
round: 1,
|
||||||
|
type: "normal",
|
||||||
|
width: 300
|
||||||
|
},
|
||||||
|
desc: prompt,
|
||||||
|
extra: {
|
||||||
|
filename: id,
|
||||||
|
tsum: msg.length,
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
detail: {
|
||||||
|
news,
|
||||||
|
resid: resId,
|
||||||
|
source,
|
||||||
|
summary,
|
||||||
|
uniseq: id,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
prompt,
|
||||||
|
ver: "0.0.0.5",
|
||||||
|
view: "contact",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromResId(resId: string): ForwardMsgJson {
|
||||||
|
return this.build(resId, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromPacketMsg(resId: string, packetMsg: PacketMsg[], source?: string, news?: ForwardMsgJsonMetaDetail["news"], summary?: string, prompt?: string): ForwardMsgJson {
|
||||||
|
return this.build(resId, packetMsg.map(msg => ({
|
||||||
|
senderName: msg.senderName,
|
||||||
|
isGroupMsg: msg.groupId !== undefined,
|
||||||
|
msg: msg.msg.map(m => ({
|
||||||
|
preview: m.valid? m.toPreview() : "[该消息类型暂不支持查看]",
|
||||||
|
}))
|
||||||
|
})), source, news, summary, prompt);
|
||||||
|
}
|
||||||
|
}
|
@@ -52,7 +52,7 @@ export class FileNapCatOneBotUUID {
|
|||||||
const [, , chatType, peerUid, modelId, fileId, fileUUID = undefined] = data;
|
const [, , chatType, peerUid, modelId, fileId, fileUUID = undefined] = data;
|
||||||
return {
|
return {
|
||||||
peer: {
|
peer: {
|
||||||
chatType: chatType as any,
|
chatType: +chatType,
|
||||||
peerUid: peerUid,
|
peerUid: peerUid,
|
||||||
},
|
},
|
||||||
modelId,
|
modelId,
|
||||||
@@ -89,7 +89,7 @@ export class FileNapCatOneBotUUID {
|
|||||||
const [, , chatType, peerUid, msgId, elementId, fileUUID = undefined] = data;
|
const [, , chatType, peerUid, msgId, elementId, fileUUID = undefined] = data;
|
||||||
return {
|
return {
|
||||||
peer: {
|
peer: {
|
||||||
chatType: chatType as any,
|
chatType: +chatType,
|
||||||
peerUid: peerUid,
|
peerUid: peerUid,
|
||||||
},
|
},
|
||||||
msgId,
|
msgId,
|
||||||
@@ -239,3 +239,42 @@ export function calcQQLevel(level?: QQLevel) {
|
|||||||
const { crownNum, sunNum, moonNum, starNum } = level;
|
const { crownNum, sunNum, moonNum, starNum } = level;
|
||||||
return crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum;
|
return crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function stringifyWithBigInt(obj: any) {
|
||||||
|
return JSON.stringify(obj, (key, value) =>
|
||||||
|
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;
|
||||||
|
}
|
@@ -23,12 +23,12 @@ export class LimitedHashTable<K, V> {
|
|||||||
}
|
}
|
||||||
while (this.keyToValue.size > this.maxSize || this.valueToKey.size > this.maxSize) {
|
while (this.keyToValue.size > this.maxSize || this.valueToKey.size > this.maxSize) {
|
||||||
const oldestKey = this.keyToValue.keys().next().value;
|
const oldestKey = this.keyToValue.keys().next().value;
|
||||||
// @ts-ignore
|
if (oldestKey !== undefined) {
|
||||||
this.valueToKey.delete(this.keyToValue.get(oldestKey)!);
|
this.valueToKey.delete(this.keyToValue.get(oldestKey) as V);
|
||||||
// @ts-ignore
|
|
||||||
this.keyToValue.delete(oldestKey);
|
this.keyToValue.delete(oldestKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getValue(key: K): V | undefined {
|
getValue(key: K): V | undefined {
|
||||||
return this.keyToValue.get(key);
|
return this.keyToValue.get(key);
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import { systemPlatform } from '@/common/system';
|
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 AppidTable from '@/core/external/appid.json';
|
||||||
import { LogWrapper } from './log';
|
import { LogWrapper } from './log';
|
||||||
|
import { getMajorPath } from '@/core';
|
||||||
|
|
||||||
export class QQBasicInfoWrapper {
|
export class QQBasicInfoWrapper {
|
||||||
QQMainPath: string | undefined;
|
QQMainPath: string | undefined;
|
||||||
@@ -72,6 +73,7 @@ export class QQBasicInfoWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getAppidV2(): { appid: string; qua: string } {
|
getAppidV2(): { appid: string; qua: string } {
|
||||||
|
// 通过已有表 性能好
|
||||||
const appidTbale = AppidTable as unknown as QQAppidTableType;
|
const appidTbale = AppidTable as unknown as QQAppidTableType;
|
||||||
const fullVersion = this.getFullQQVesion();
|
const fullVersion = this.getFullQQVesion();
|
||||||
if (fullVersion) {
|
if (fullVersion) {
|
||||||
@@ -80,10 +82,22 @@ export class QQBasicInfoWrapper {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 通过Major拉取 性能差
|
||||||
// else
|
try {
|
||||||
|
let majorAppid = this.getAppidV2ByMajor(fullVersion);
|
||||||
|
if (majorAppid) { 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版本兼容性检测] 获取Appid异常 请检测NapCat/QQNT是否正常`);
|
||||||
this.context.logger.log(`[QQ版本兼容性检测] ${fullVersion} 版本兼容性不佳,可能会导致一些功能无法正常使用`,);
|
this.context.logger.log(`[QQ版本兼容性检测] ${fullVersion} 版本兼容性不佳,可能会导致一些功能无法正常使用`,);
|
||||||
return { appid: this.getAppIdFallback(), qua: this.getQUAFallback() };
|
return { appid: this.getAppIdFallback(), qua: this.getQUAFallback() };
|
||||||
}
|
}
|
||||||
|
getAppidV2ByMajor(QQVersion: string) {
|
||||||
|
let majorPath = getMajorPath(QQVersion);
|
||||||
|
let appid = parseAppidFromMajor(majorPath);
|
||||||
|
return appid;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1 +1 @@
|
|||||||
export const napCatVersion = '3.0.1';
|
export const napCatVersion = '3.3.27';
|
||||||
|
@@ -6,6 +6,7 @@ import {
|
|||||||
Peer,
|
Peer,
|
||||||
PicElement,
|
PicElement,
|
||||||
PicType,
|
PicType,
|
||||||
|
RawMessage,
|
||||||
SendFileElement,
|
SendFileElement,
|
||||||
SendPicElement,
|
SendPicElement,
|
||||||
SendPttElement,
|
SendPttElement,
|
||||||
@@ -30,7 +31,7 @@ export class NTQQFileApi {
|
|||||||
context: InstanceContext;
|
context: InstanceContext;
|
||||||
core: NapCatCore;
|
core: NapCatCore;
|
||||||
rkeyManager: RkeyManager;
|
rkeyManager: RkeyManager;
|
||||||
packetRkey: Array<{ rkey: string; time: number; type: number; }> | undefined;
|
packetRkey: Array<{ rkey: string; time: number; type: number; ttl: bigint }> | undefined;
|
||||||
|
|
||||||
constructor(context: InstanceContext, core: NapCatCore) {
|
constructor(context: InstanceContext, core: NapCatCore) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
@@ -238,7 +239,7 @@ export class NTQQFileApi {
|
|||||||
fileName: fileName,
|
fileName: fileName,
|
||||||
filePath: path,
|
filePath: path,
|
||||||
md5HexStr: md5,
|
md5HexStr: md5,
|
||||||
fileSize: fileSize,
|
fileSize: fileSize.toString(),
|
||||||
duration: duration ?? 1,
|
duration: duration ?? 1,
|
||||||
formatType: 1,
|
formatType: 1,
|
||||||
voiceType: 1,
|
voiceType: 1,
|
||||||
@@ -267,6 +268,53 @@ export class NTQQFileApi {
|
|||||||
return fileTransNotifyInfo.filePath;
|
return fileTransNotifyInfo.filePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async downloadRawMsgMedia(msg: RawMessage[]) {
|
||||||
|
const res = await Promise.all(
|
||||||
|
msg.map(m =>
|
||||||
|
Promise.all(
|
||||||
|
m.elements
|
||||||
|
.filter(element =>
|
||||||
|
element.elementType === ElementType.PIC ||
|
||||||
|
element.elementType === ElementType.VIDEO ||
|
||||||
|
element.elementType === ElementType.PTT ||
|
||||||
|
element.elementType === ElementType.FILE
|
||||||
|
)
|
||||||
|
.map(element =>
|
||||||
|
this.downloadMedia(m.msgId, m.chatType, m.peerUid, element.elementId, '', '', 1000 * 60 * 2, true)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
msg.forEach((m, msgIndex) => {
|
||||||
|
const elementResults = res[msgIndex];
|
||||||
|
let elementIndex = 0;
|
||||||
|
m.elements.forEach(element => {
|
||||||
|
if (
|
||||||
|
element.elementType === ElementType.PIC ||
|
||||||
|
element.elementType === ElementType.VIDEO ||
|
||||||
|
element.elementType === ElementType.PTT ||
|
||||||
|
element.elementType === ElementType.FILE
|
||||||
|
) {
|
||||||
|
switch (element.elementType) {
|
||||||
|
case ElementType.PIC:
|
||||||
|
element.picElement!.sourcePath = elementResults[elementIndex];
|
||||||
|
break;
|
||||||
|
case ElementType.VIDEO:
|
||||||
|
element.videoElement!.filePath = elementResults[elementIndex];
|
||||||
|
break;
|
||||||
|
case ElementType.PTT:
|
||||||
|
element.pttElement!.filePath = elementResults[elementIndex];
|
||||||
|
break;
|
||||||
|
case ElementType.FILE:
|
||||||
|
element.fileElement!.filePath = elementResults[elementIndex];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
elementIndex++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, timeout = 1000 * 60 * 2, force: boolean = false) {
|
async downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, timeout = 1000 * 60 * 2, force: boolean = false) {
|
||||||
// 用于下载收到的消息中的图片等
|
// 用于下载收到的消息中的图片等
|
||||||
if (sourcePath && fs.existsSync(sourcePath)) {
|
if (sourcePath && fs.existsSync(sourcePath)) {
|
||||||
@@ -296,7 +344,7 @@ export class NTQQFileApi {
|
|||||||
filePath: thumbPath,
|
filePath: thumbPath,
|
||||||
}],
|
}],
|
||||||
() => true,
|
() => true,
|
||||||
(arg) => arg.msgId === msgId,
|
(arg) => arg.msgElementId === elementId && arg.msgId === msgId,
|
||||||
1,
|
1,
|
||||||
timeout,
|
timeout,
|
||||||
);
|
);
|
||||||
@@ -378,10 +426,12 @@ export class NTQQFileApi {
|
|||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
if (this.core.apis.PacketApi.available) {
|
if (this.core.apis.PacketApi.available) {
|
||||||
if ((!this.packetRkey || this.packetRkey[0].time > Date.now() / 1000)) {
|
const rkey_expired_private = !this.packetRkey || this.packetRkey[0].time + Number(this.packetRkey[0].ttl) < Date.now() / 1000;
|
||||||
|
const rkey_expired_group = !this.packetRkey || this.packetRkey[0].time + Number(this.packetRkey[0].ttl) < Date.now() / 1000;
|
||||||
|
if (rkey_expired_private || rkey_expired_group) {
|
||||||
this.packetRkey = await this.core.apis.PacketApi.sendRkeyPacket();
|
this.packetRkey = await this.core.apis.PacketApi.sendRkeyPacket();
|
||||||
}
|
}
|
||||||
if (this.packetRkey.length > 0) {
|
if (this.packetRkey && this.packetRkey.length > 0) {
|
||||||
rkeyData.group_rkey = this.packetRkey[1].rkey.slice(6);
|
rkeyData.group_rkey = this.packetRkey[1].rkey.slice(6);
|
||||||
rkeyData.private_rkey = this.packetRkey[0].rkey.slice(6);
|
rkeyData.private_rkey = this.packetRkey[0].rkey.slice(6);
|
||||||
rkeyData.online_rkey = true;
|
rkeyData.online_rkey = true;
|
||||||
|
@@ -11,7 +11,7 @@ export class NTQQFriendApi {
|
|||||||
this.core = core;
|
this.core = core;
|
||||||
}
|
}
|
||||||
async setBuddyRemark(uid: string, remark: string) {
|
async setBuddyRemark(uid: string, remark: string) {
|
||||||
return this.context.session.getBuddyService().setBuddyRemark(uid, remark);
|
return this.context.session.getBuddyService().setBuddyRemark({ uid, remark });
|
||||||
}
|
}
|
||||||
async getBuddyV2SimpleInfoMap(refresh = false) {
|
async getBuddyV2SimpleInfoMap(refresh = false) {
|
||||||
const buddyService = this.context.session.getBuddyService();
|
const buddyService = this.context.session.getBuddyService();
|
||||||
@@ -34,7 +34,13 @@ export class NTQQFriendApi {
|
|||||||
data.forEach((value) => retMap.set(value.uin!, value.uid!));
|
data.forEach((value) => retMap.set(value.uin!, value.uid!));
|
||||||
return retMap;
|
return retMap;
|
||||||
}
|
}
|
||||||
|
async delBuudy(uid: string, tempBlock = false, tempBothDel = false) {
|
||||||
|
return this.context.session.getBuddyService().delBuddy({
|
||||||
|
friendUid: uid,
|
||||||
|
tempBlock: tempBlock,
|
||||||
|
tempBothDel: tempBothDel
|
||||||
|
});
|
||||||
|
}
|
||||||
async getBuddyV2ExWithCate(refresh = false) {
|
async getBuddyV2ExWithCate(refresh = false) {
|
||||||
const categoryMap: Map<string, any> = new Map();
|
const categoryMap: Map<string, any> = new Map();
|
||||||
const buddyService = this.context.session.getBuddyService();
|
const buddyService = this.context.session.getBuddyService();
|
||||||
|
@@ -316,18 +316,76 @@ export class NTQQGroupApi {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getGroupMembersV2(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
|
async tryGetGroupMembersV2(modeListener = false, groupQQ: string, num = 30, timeout = 100): Promise<{
|
||||||
const sceneId = this.context.session.getGroupService().createMemberListScene(groupQQ, 'groupMemberList_MainWindow');
|
infos: Map<string, GroupMember>;
|
||||||
let once = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onMemberListChange', 1, 2000, (params) => params.sceneId === sceneId)
|
finish: boolean;
|
||||||
.catch();
|
hasNext: boolean | undefined;
|
||||||
const result = await this.context.session.getGroupService().getNextMemberList(sceneId!, undefined, num);
|
}> {
|
||||||
|
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) {
|
if (result.errCode !== 0) {
|
||||||
throw new Error('获取群成员列表出错,' + result.errMsg);
|
throw new Error('获取群成员列表出错,' + result.errMsg);
|
||||||
}
|
}
|
||||||
if (result.result.infos.size === 0) {
|
let resMode2;
|
||||||
return (await once)[0].infos;
|
if (modeListener) {
|
||||||
|
const ret = (await once)?.[0];
|
||||||
|
if (ret) {
|
||||||
|
resMode2 = ret;
|
||||||
}
|
}
|
||||||
return result.result.infos;
|
}
|
||||||
|
this.context.session.getGroupService().destroyMemberListScene(sceneId);
|
||||||
|
return {
|
||||||
|
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>> {
|
||||||
|
//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) {
|
||||||
|
ret = (await this.getGroupMemberAll(groupQQ)).result.infos;
|
||||||
|
}
|
||||||
|
//console.log("<---------------")
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
|
async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
|
||||||
@@ -425,7 +483,7 @@ export class NTQQGroupApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getGroupRemainAtTimes(GroupCode: string) {
|
async getGroupRemainAtTimes(GroupCode: string) {
|
||||||
this.context.session.getGroupService().getGroupRemainAtTimes(GroupCode);
|
return this.context.session.getGroupService().getGroupRemainAtTimes(GroupCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMemberExtInfo(groupCode: string, uin: string) {
|
async getMemberExtInfo(groupCode: string, uin: string) {
|
||||||
|
@@ -3,6 +3,9 @@ import { InstanceContext, NapCatCore } from '@/core';
|
|||||||
import { GeneralCallResult } from '@/core/services/common';
|
import { GeneralCallResult } from '@/core/services/common';
|
||||||
|
|
||||||
export class NTQQMsgApi {
|
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//sysface_res/apng/ 下可以看到所有QQ表情预览
|
||||||
// nt_qq\global\nt_data\Emoji\emoji-resource\face_config.json 里面有所有表情的id, 自带表情id是QSid, 标准emoji表情id是QCid
|
// 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
|
// 其实以官方文档为准是最好的,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) {
|
async sendShowInputStatusReq(peer: Peer, eventType: number) {
|
||||||
return this.context.session.getMsgService().sendShowInputStatusReq(peer.chatType, eventType, peer.peerUid);
|
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) {
|
async getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, count: number = 20) {
|
||||||
//注意此处emojiType 可选值一般为1-2 2好像是unicode表情dec值 大部分情况 Taged Mlikiowa
|
//注意此处emojiType 可选值一般为1-2 2好像是unicode表情dec值 大部分情况 Taged Mlikiowa
|
||||||
return this.context.session.getMsgService().getMsgEmojiLikesList(peer, msgSeq, emojiId, emojiType, '', false, count);
|
return this.context.session.getMsgService().getMsgEmojiLikesList(peer, msgSeq, emojiId, emojiType, '', false, count);
|
||||||
@@ -82,6 +87,18 @@ export class NTQQMsgApi {
|
|||||||
pageLimit: 1,
|
pageLimit: 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
async queryMsgsWithFilterExWithSeqV3(peer: Peer, msgSeq: string, SendersUid: string[]) {
|
||||||
|
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
|
||||||
|
chatInfo: peer,
|
||||||
|
filterMsgType: [],
|
||||||
|
filterSendersUid: SendersUid,
|
||||||
|
filterMsgToTime: '0',
|
||||||
|
filterMsgFromTime: '0',
|
||||||
|
isReverseOrder: false,
|
||||||
|
isIncludeCurrent: true,
|
||||||
|
pageLimit: 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
async queryFirstMsgBySeq(peer: Peer, msgSeq: string) {
|
async queryFirstMsgBySeq(peer: Peer, msgSeq: string) {
|
||||||
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
|
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
|
||||||
chatInfo: peer,
|
chatInfo: peer,
|
||||||
@@ -94,9 +111,9 @@ export class NTQQMsgApi {
|
|||||||
pageLimit: 1,
|
pageLimit: 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
//@deprecated
|
// 客户端还在用别慌
|
||||||
async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, z: boolean) {
|
async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, isReverseOrder: boolean) {
|
||||||
return await this.context.session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, z);
|
return await this.context.session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, isReverseOrder);
|
||||||
}
|
}
|
||||||
async getMsgExBySeq(peer: Peer, msgSeq: string) {
|
async getMsgExBySeq(peer: Peer, msgSeq: string) {
|
||||||
const DateNow = Math.floor(Date.now() / 1000);
|
const DateNow = Math.floor(Date.now() / 1000);
|
||||||
|
@@ -3,16 +3,24 @@ import {ChatType, InstanceContext, NapCatCore} from '..';
|
|||||||
import offset from '@/core/external/offset.json';
|
import offset from '@/core/external/offset.json';
|
||||||
import { PacketClient, RecvPacketData } from '@/core/packet/client';
|
import { PacketClient, RecvPacketData } from '@/core/packet/client';
|
||||||
import { PacketSession } from "@/core/packet/session";
|
import { PacketSession } from "@/core/packet/session";
|
||||||
import {PacketHexStr} from "@/core/packet/packer";
|
import { OidbPacket, PacketHexStr } from "@/core/packet/packer";
|
||||||
import { NapProtoMsg } from '@/core/packet/proto/NapProto';
|
import { NapProtoMsg } from '@/core/packet/proto/NapProto';
|
||||||
import { OidbSvcTrpcTcp0X9067_202_Rsp_Body } from '@/core/packet/proto/oidb/Oidb.0x9067_202';
|
import { OidbSvcTrpcTcp0X9067_202_Rsp_Body } from '@/core/packet/proto/oidb/Oidb.0x9067_202';
|
||||||
import { OidbSvcTrpcTcpBase, OidbSvcTrpcTcpBaseRsp } from '@/core/packet/proto/oidb/OidbBase';
|
import { OidbSvcTrpcTcpBase, OidbSvcTrpcTcpBaseRsp } from '@/core/packet/proto/oidb/OidbBase';
|
||||||
import { OidbSvcTrpcTcp0XFE1_2RSP } from '@/core/packet/proto/oidb/Oidb.0XFE1_2';
|
import { OidbSvcTrpcTcp0XFE1_2RSP } from '@/core/packet/proto/oidb/Oidb.0XFE1_2';
|
||||||
import { LogWrapper } from "@/common/log";
|
import { LogWrapper } from "@/common/log";
|
||||||
import { SendLongMsgResp } from "@/core/packet/proto/message/action";
|
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 { OidbSvcTrpcTcp0x6D6Response } from "@/core/packet/proto/oidb/Oidb.0x6D6";
|
||||||
import {PacketMsgPicElement} from "@/core/packet/msg/element";
|
import {
|
||||||
|
PacketMsgFileElement,
|
||||||
|
PacketMsgPicElement,
|
||||||
|
PacketMsgPttElement,
|
||||||
|
PacketMsgVideoElement
|
||||||
|
} from "@/core/packet/message/element";
|
||||||
|
import { MiniAppReqParams, MiniAppRawData } from "@/core/packet/entities/miniApp";
|
||||||
|
import { MiniAppAdaptShareInfoResp } from "@/core/packet/proto/action/miniAppAdaptShareInfo";
|
||||||
|
|
||||||
|
|
||||||
interface OffsetType {
|
interface OffsetType {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
@@ -26,7 +34,7 @@ const typedOffset: OffsetType = offset;
|
|||||||
export class NTQQPacketApi {
|
export class NTQQPacketApi {
|
||||||
context: InstanceContext;
|
context: InstanceContext;
|
||||||
core: NapCatCore;
|
core: NapCatCore;
|
||||||
logger: LogWrapper
|
logger: LogWrapper;
|
||||||
serverUrl: string | undefined;
|
serverUrl: string | undefined;
|
||||||
qqVersion: string | undefined;
|
qqVersion: string | undefined;
|
||||||
packetSession: PacketSession | undefined;
|
packetSession: PacketSession | undefined;
|
||||||
@@ -43,7 +51,7 @@ export class NTQQPacketApi {
|
|||||||
.then()
|
.then()
|
||||||
.catch(this.core.context.logger.logError.bind(this.core.context.logger));
|
.catch(this.core.context.logger.logError.bind(this.core.context.logger));
|
||||||
} else {
|
} else {
|
||||||
this.core.context.logger.logWarn('PacketServer is not set, will not init NapCat.Packet!');
|
this.core.context.logger.logWarn('PacketServer未配置,NapCat.Packet将不会加载!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,11 +64,18 @@ export class NTQQPacketApi {
|
|||||||
this.qqVersion = qqversion;
|
this.qqVersion = qqversion;
|
||||||
const offsetTable: OffsetType = offset;
|
const offsetTable: OffsetType = offset;
|
||||||
const table = offsetTable[qqversion + '-' + os.arch()];
|
const table = offsetTable[qqversion + '-' + os.arch()];
|
||||||
if (!table) return false;
|
if (!table) {
|
||||||
|
this.logger.logError('PacketServer Offset table not found for QQVersion: ', qqversion + '-' + os.arch());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const url = 'ws://' + this.serverUrl + '/ws';
|
const url = 'ws://' + this.serverUrl + '/ws';
|
||||||
this.packetSession = new PacketSession(this.core.context.logger, new PacketClient(url, this.core));
|
this.packetSession = new PacketSession(this.core.context.logger, new PacketClient(url, this.core));
|
||||||
await this.packetSession.client.connect();
|
const cb = () => {
|
||||||
await this.packetSession.client.init(process.pid, table.recv, table.send);
|
if (this.packetSession && this.packetSession.client) {
|
||||||
|
this.packetSession.client.init(process.pid, table.recv, table.send).then().catch(this.logger.logError.bind(this.logger));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
await this.packetSession.client.connect(cb);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,25 +83,32 @@ export class NTQQPacketApi {
|
|||||||
return this.packetSession!.client.sendPacket(cmd, data, rsp);
|
return this.packetSession!.client.sendPacket(cmd, data, rsp);
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendPokePacket(group: number, peer: number) {
|
async sendOidbPacket(pkt: OidbPacket, rsp = false): Promise<RecvPacketData> {
|
||||||
const data = this.packetSession?.packer.packPokePacket(group, peer);
|
return this.sendPacket(pkt.cmd, pkt.data, rsp);
|
||||||
await this.sendPacket('OidbSvcTrpcTcp.0xed3_1', data!, false);
|
}
|
||||||
|
|
||||||
|
async sendPokePacket(peer: number, group?: number) {
|
||||||
|
const data = this.packetSession?.packer.packPokePacket(peer, group);
|
||||||
|
await this.sendOidbPacket(data!, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendRkeyPacket() {
|
async sendRkeyPacket() {
|
||||||
const packet = this.packetSession?.packer.packRkeyPacket();
|
const packet = this.packetSession?.packer.packRkeyPacket();
|
||||||
const ret = await this.sendPacket('OidbSvcTrpcTcp.0x9067_202', packet!, true);
|
const ret = await this.sendOidbPacket(packet!, true);
|
||||||
if (!ret?.hex_data) return [];
|
if (!ret?.hex_data) return [];
|
||||||
const body = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(Buffer.from(ret.hex_data, 'hex')).body;
|
const body = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(Buffer.from(ret.hex_data, 'hex')).body;
|
||||||
const retData = new NapProtoMsg(OidbSvcTrpcTcp0X9067_202_Rsp_Body).decode(body);
|
const retData = new NapProtoMsg(OidbSvcTrpcTcp0X9067_202_Rsp_Body).decode(body);
|
||||||
return retData.data.rkeyList;
|
return retData.data.rkeyList;
|
||||||
}
|
}
|
||||||
|
async sendGroupSignPacket(groupCode: string) {
|
||||||
|
const packet = this.packetSession?.packer.packGroupSignReq(this.core.selfInfo.uin, groupCode);
|
||||||
|
await this.sendOidbPacket(packet!, true);
|
||||||
|
}
|
||||||
async sendStatusPacket(uin: number): Promise<{ status: number; ext_status: number; } | undefined> {
|
async sendStatusPacket(uin: number): Promise<{ status: number; ext_status: number; } | undefined> {
|
||||||
let status = 0;
|
let status = 0;
|
||||||
try {
|
try {
|
||||||
const packet = this.packetSession?.packer.packStatusPacket(uin);
|
const packet = this.packetSession?.packer.packStatusPacket(uin);
|
||||||
const ret = await this.sendPacket('OidbSvcTrpcTcp.0xfe1_2', packet!, true);
|
const ret = await this.sendOidbPacket(packet!, true);
|
||||||
const data = Buffer.from(ret.hex_data, 'hex');
|
const data = Buffer.from(ret.hex_data, 'hex');
|
||||||
const ext = new NapProtoMsg(OidbSvcTrpcTcp0XFE1_2RSP).decode(new NapProtoMsg(OidbSvcTrpcTcpBase).decode(data).body).data.status.value;
|
const ext = new NapProtoMsg(OidbSvcTrpcTcp0XFE1_2RSP).decode(new NapProtoMsg(OidbSvcTrpcTcpBase).decode(data).body).data.status.value;
|
||||||
// ext & 0xff00 + ext >> 16 & 0xff
|
// ext & 0xff00 + ext >> 16 & 0xff
|
||||||
@@ -103,22 +125,47 @@ export class NTQQPacketApi {
|
|||||||
|
|
||||||
async sendSetSpecialTittlePacket(groupCode: string, uid: string, tittle: string) {
|
async sendSetSpecialTittlePacket(groupCode: string, uid: string, tittle: string) {
|
||||||
const data = this.packetSession?.packer.packSetSpecialTittlePacket(groupCode, uid, tittle);
|
const data = this.packetSession?.packer.packSetSpecialTittlePacket(groupCode, uid, tittle);
|
||||||
await this.sendPacket('OidbSvcTrpcTcp.0x8fc_2', data!, true);
|
await this.sendOidbPacket(data!, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async uploadResources(msg: PacketMsg[], groupUin: number = 0){
|
// TODO: can simplify this
|
||||||
const reqList = []
|
async uploadResources(msg: PacketMsg[], groupUin: number = 0) {
|
||||||
|
const reqList = [];
|
||||||
for (const m of msg) {
|
for (const m of msg) {
|
||||||
for (const e of m.msg) {
|
for (const e of m.msg) {
|
||||||
if (e instanceof PacketMsgPicElement) {
|
if (e instanceof PacketMsgPicElement) {
|
||||||
reqList.push(this.packetSession?.highwaySession.uploadImage({
|
reqList.push(this.packetSession?.highwaySession.uploadImage({
|
||||||
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
|
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
|
||||||
peerUid: String(groupUin) ? String(groupUin) : this.core.selfInfo.uid
|
peerUid: groupUin ? String(groupUin) : this.core.selfInfo.uid
|
||||||
|
}, e));
|
||||||
|
}
|
||||||
|
if (e instanceof PacketMsgVideoElement) {
|
||||||
|
reqList.push(this.packetSession?.highwaySession.uploadVideo({
|
||||||
|
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
|
||||||
|
peerUid: groupUin ? String(groupUin) : this.core.selfInfo.uid
|
||||||
|
}, e));
|
||||||
|
}
|
||||||
|
if (e instanceof PacketMsgPttElement) {
|
||||||
|
reqList.push(this.packetSession?.highwaySession.uploadPtt({
|
||||||
|
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
|
||||||
|
peerUid: groupUin ? String(groupUin) : this.core.selfInfo.uid
|
||||||
|
}, e));
|
||||||
|
}
|
||||||
|
if (e instanceof PacketMsgFileElement) {
|
||||||
|
reqList.push(this.packetSession?.highwaySession.uploadFile({
|
||||||
|
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
|
||||||
|
peerUid: groupUin ? String(groupUin) : this.core.selfInfo.uid
|
||||||
}, e));
|
}, e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Promise.all(reqList);
|
const res = await Promise.allSettled(reqList);
|
||||||
|
this.logger.log(`上传资源${res.length}个,失败${res.filter(r => r.status === 'rejected').length}个`);
|
||||||
|
res.forEach((result, index) => {
|
||||||
|
if (result.status === 'rejected') {
|
||||||
|
this.logger.logError(`上传第${index + 1}个资源失败:${result.reason}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendUploadForwardMsg(msg: PacketMsg[], groupUin: number = 0) {
|
async sendUploadForwardMsg(msg: PacketMsg[], groupUin: number = 0) {
|
||||||
@@ -132,12 +179,19 @@ export class NTQQPacketApi {
|
|||||||
|
|
||||||
async sendGroupFileDownloadReq(groupUin: number, fileUUID: string) {
|
async sendGroupFileDownloadReq(groupUin: number, fileUUID: string) {
|
||||||
const data = this.packetSession?.packer.packGroupFileDownloadReq(groupUin, fileUUID);
|
const data = this.packetSession?.packer.packGroupFileDownloadReq(groupUin, fileUUID);
|
||||||
const ret = await this.sendPacket('OidbSvcTrpcTcp.0x6d6_2', data!, true);
|
const ret = await this.sendOidbPacket(data!, true);
|
||||||
const body = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(Buffer.from(ret.hex_data, 'hex')).body;
|
const body = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(Buffer.from(ret.hex_data, 'hex')).body;
|
||||||
const resp = new NapProtoMsg(OidbSvcTrpcTcp0x6D6Response).decode(body);
|
const resp = new NapProtoMsg(OidbSvcTrpcTcp0x6D6Response).decode(body);
|
||||||
if (resp.download.retCode !== 0) {
|
if (resp.download.retCode !== 0) {
|
||||||
throw new Error(`sendGroupFileDownloadReq error: ${resp.download.clientWording}`);
|
throw new Error(`sendGroupFileDownloadReq error: ${resp.download.clientWording}`);
|
||||||
}
|
}
|
||||||
return `https://${resp.download.downloadDns}/ftn_handler/${Buffer.from(resp.download.downloadUrl).toString('hex')}/?fname=`
|
return `https://${resp.download.downloadDns}/ftn_handler/${Buffer.from(resp.download.downloadUrl).toString('hex')}/?fname=`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,7 +18,7 @@ export class NTQQUserApi {
|
|||||||
async getStatusByUid(uid: string) {
|
async getStatusByUid(uid: string) {
|
||||||
return this.context.session.getProfileService().getStatus(uid);
|
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({
|
return this.context.session.getProfileLikeService().getBuddyProfileLike({
|
||||||
friendUids: [uid],
|
friendUids: [uid],
|
||||||
basic: 1,
|
basic: 1,
|
||||||
@@ -26,8 +26,8 @@ export class NTQQUserApi {
|
|||||||
favorite: 0,
|
favorite: 0,
|
||||||
userProfile: 1,
|
userProfile: 1,
|
||||||
type: 2,
|
type: 2,
|
||||||
start: 0,
|
start: start,
|
||||||
limit: 20,
|
limit: count,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async fetchOtherProfileLike(uid: string) {
|
async fetchOtherProfileLike(uid: string) {
|
||||||
|
@@ -338,4 +338,12 @@ export class NTQQWebApi {
|
|||||||
}
|
}
|
||||||
return (hash & 0x7FFFFFFF).toString();
|
return (hash & 0x7FFFFFFF).toString();
|
||||||
}
|
}
|
||||||
|
public getBknFromSKey(sKey: string) {
|
||||||
|
let hash = 5381;
|
||||||
|
for (let i = 0; i < sKey.length; i++) {
|
||||||
|
const code = sKey.charCodeAt(i);
|
||||||
|
hash = hash + (hash << 5) + code;
|
||||||
|
}
|
||||||
|
return (hash & 0x7FFFFFFF).toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -27,94 +27,70 @@ export interface GetFileListParam {
|
|||||||
|
|
||||||
export enum ElementType {
|
export enum ElementType {
|
||||||
UNKNOWN = 0,
|
UNKNOWN = 0,
|
||||||
|
|
||||||
TEXT = 1,
|
TEXT = 1,
|
||||||
|
|
||||||
PIC = 2,
|
PIC = 2,
|
||||||
|
|
||||||
FILE = 3,
|
FILE = 3,
|
||||||
|
|
||||||
PTT = 4,
|
PTT = 4,
|
||||||
|
|
||||||
VIDEO = 5,
|
VIDEO = 5,
|
||||||
|
|
||||||
FACE = 6,
|
FACE = 6,
|
||||||
|
|
||||||
REPLY = 7,
|
REPLY = 7,
|
||||||
|
GreyTip = 8, // “小灰条”,包括拍一拍 (Poke)、撤回提示等
|
||||||
WALLET = 9,
|
WALLET = 9,
|
||||||
|
|
||||||
/**
|
|
||||||
* “小灰条”,包括拍一拍 (Poke)、撤回提示等
|
|
||||||
*/
|
|
||||||
GreyTip = 8,
|
|
||||||
|
|
||||||
ARK = 10,
|
ARK = 10,
|
||||||
|
|
||||||
MFACE = 11,
|
MFACE = 11,
|
||||||
|
|
||||||
LIVEGIFT = 12,
|
LIVEGIFT = 12,
|
||||||
|
|
||||||
STRUCTLONGMSG = 13,
|
STRUCTLONGMSG = 13,
|
||||||
|
|
||||||
MARKDOWN = 14,
|
MARKDOWN = 14,
|
||||||
|
|
||||||
GIPHY = 15,
|
GIPHY = 15,
|
||||||
|
|
||||||
MULTIFORWARD = 16,
|
MULTIFORWARD = 16,
|
||||||
|
|
||||||
INLINEKEYBOARD = 17,
|
INLINEKEYBOARD = 17,
|
||||||
|
|
||||||
INTEXTGIFT = 18,
|
INTEXTGIFT = 18,
|
||||||
|
|
||||||
CALENDAR = 19,
|
CALENDAR = 19,
|
||||||
|
|
||||||
YOLOGAMERESULT = 20,
|
YOLOGAMERESULT = 20,
|
||||||
|
|
||||||
AVRECORD = 21,
|
AVRECORD = 21,
|
||||||
|
|
||||||
FEED = 22,
|
FEED = 22,
|
||||||
|
|
||||||
TOFURECORD = 23,
|
TOFURECORD = 23,
|
||||||
|
|
||||||
ACEBUBBLE = 24,
|
ACEBUBBLE = 24,
|
||||||
|
|
||||||
ACTIVITY = 25,
|
ACTIVITY = 25,
|
||||||
|
|
||||||
TOFU = 26,
|
TOFU = 26,
|
||||||
|
|
||||||
FACEBUBBLE = 27,
|
FACEBUBBLE = 27,
|
||||||
|
|
||||||
SHARELOCATION = 28,
|
SHARELOCATION = 28,
|
||||||
|
|
||||||
TASKTOPMSG = 29,
|
TASKTOPMSG = 29,
|
||||||
|
|
||||||
RECOMMENDEDMSG = 43,
|
RECOMMENDEDMSG = 43,
|
||||||
|
|
||||||
ACTIONBAR = 44
|
ACTIONBAR = 44
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ElementFullBase = Omit<MessageElement, 'elementType' | 'elementId' | 'extBufForUI'>;
|
||||||
|
|
||||||
|
type ElementBase<
|
||||||
|
K extends keyof ElementFullBase,
|
||||||
|
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>
|
||||||
|
? Pick<NonNullable<ElementFullBase[P]>, U & keyof NonNullable<ElementFullBase[P]>>
|
||||||
|
: S[P] extends keyof NonNullable<ElementFullBase[P]>
|
||||||
|
? Pick<NonNullable<ElementFullBase[P]>, S[P]>
|
||||||
|
: NonNullable<ElementFullBase[P]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface SendElementBase<ET extends ElementType> {
|
||||||
|
elementType: ET;
|
||||||
|
elementId: string;
|
||||||
|
extBufForUI?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ActionBarElement {
|
export interface ActionBarElement {
|
||||||
rows: InlineKeyboardRow[];
|
rows: InlineKeyboardRow[];
|
||||||
botAppid: string;
|
botAppid: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SendActionBarElement {
|
|
||||||
elementType: ElementType.ACTIONBAR;
|
|
||||||
elementId: string;
|
|
||||||
actionBarElement: ActionBarElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RecommendedMsgElement {
|
export interface RecommendedMsgElement {
|
||||||
rows: InlineKeyboardRow[];
|
rows: InlineKeyboardRow[];
|
||||||
botAppid: string;
|
botAppid: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SendRecommendedMsgElement {
|
export type SendRecommendedMsgElement = SendElementBase<ElementType.RECOMMENDEDMSG> & ElementBase<'recommendedMsgElement'>;
|
||||||
elementType: ElementType.RECOMMENDEDMSG;
|
|
||||||
elementId: string;
|
|
||||||
recommendedMsgElement: RecommendedMsgElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface InlineKeyboardButton {
|
export interface InlineKeyboardButton {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -171,11 +147,7 @@ export enum NTMsgType {
|
|||||||
KMSGTYPEWALLET = 10
|
KMSGTYPEWALLET = 10
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SendTaskTopMsgElement {
|
export type SendTaskTopMsgElement = SendElementBase<ElementType.TASKTOPMSG> & ElementBase<'taskTopMsgElement'>;
|
||||||
elementType: ElementType.TASKTOPMSG;
|
|
||||||
elementId: string;
|
|
||||||
taskTopMsgElement: TaskTopMsgElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TofuRecordElement {
|
export interface TofuRecordElement {
|
||||||
type: number;
|
type: number;
|
||||||
@@ -194,11 +166,7 @@ export interface TofuRecordElement {
|
|||||||
onscreennotify: boolean;
|
onscreennotify: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SendTofuRecordElement {
|
export type SendTofuRecordElement = SendElementBase<ElementType.TOFURECORD> & ElementBase<'tofuRecordElement'>;
|
||||||
elementType: ElementType.TOFURECORD;
|
|
||||||
elementId: string;
|
|
||||||
tofuRecordElement: TofuRecordElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FaceBubbleElement {
|
export interface FaceBubbleElement {
|
||||||
faceCount: number;
|
faceCount: number;
|
||||||
@@ -216,12 +184,7 @@ export interface FaceBubbleElement {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SendFaceBubbleElement {
|
export type SendFaceBubbleElement = SendElementBase<ElementType.FACEBUBBLE> & ElementBase<'faceBubbleElement'>;
|
||||||
elementType: ElementType.FACEBUBBLE;
|
|
||||||
elementId: string;
|
|
||||||
faceBubbleElement: FaceBubbleElement;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AvRecordElement {
|
export interface AvRecordElement {
|
||||||
type: number;
|
type: number;
|
||||||
@@ -232,11 +195,7 @@ export interface AvRecordElement {
|
|||||||
extraType: number;
|
extraType: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SendavRecordElement {
|
export type SendAvRecordElement = SendElementBase<ElementType.AVRECORD> & ElementBase<'avRecordElement'>;
|
||||||
elementType: ElementType.AVRECORD;
|
|
||||||
elementId: string;
|
|
||||||
avRecordElement: AvRecordElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface YoloUserInfo {
|
export interface YoloUserInfo {
|
||||||
uid: string;
|
uid: string;
|
||||||
@@ -245,24 +204,13 @@ export interface YoloUserInfo {
|
|||||||
bizId: string;
|
bizId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SendInlineKeyboardElement {
|
export type SendInlineKeyboardElement = SendElementBase<ElementType.INLINEKEYBOARD> & ElementBase<'inlineKeyboardElement'>;
|
||||||
elementType: ElementType.INLINEKEYBOARD;
|
|
||||||
elementId: string;
|
|
||||||
inlineKeyboardElement: {
|
|
||||||
rows: number;
|
|
||||||
botAppid: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface YoloGameResultElement {
|
export interface YoloGameResultElement {
|
||||||
UserInfo: YoloUserInfo[];
|
UserInfo: YoloUserInfo[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SendYoloGameResultElement {
|
export type SendYoloGameResultElement = SendElementBase<ElementType.YOLOGAMERESULT> & ElementBase<'yoloGameResultElement'>;
|
||||||
elementType: ElementType.YOLOGAMERESULT;
|
|
||||||
yoloGameResultElement: YoloGameResultElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GiphyElement {
|
export interface GiphyElement {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -271,17 +219,9 @@ export interface GiphyElement {
|
|||||||
height: number;
|
height: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SendGiphyElement {
|
export type SendGiphyElement = SendElementBase<ElementType.GIPHY> & ElementBase<'giphyElement'>;
|
||||||
elementType: ElementType.GIPHY;
|
|
||||||
elementId: string;
|
|
||||||
giphyElement: GiphyElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SendWalletElement {
|
export type SendWalletElement = SendElementBase<ElementType.UNKNOWN> & ElementBase<'walletElement'>;
|
||||||
elementType: ElementType.UNKNOWN;//不做 设置位置
|
|
||||||
elementId: string;
|
|
||||||
walletElement: Record<string, never>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CalendarElement {
|
export interface CalendarElement {
|
||||||
summary: string;
|
summary: string;
|
||||||
@@ -291,49 +231,16 @@ export interface CalendarElement {
|
|||||||
schema: string;
|
schema: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SendCalendarElement {
|
export type SendCalendarElement = SendElementBase<ElementType.CALENDAR> & ElementBase<'calendarElement'>;
|
||||||
elementType: ElementType.CALENDAR;
|
|
||||||
elementId: string;
|
|
||||||
calendarElement: CalendarElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SendliveGiftElement {
|
export type SendLiveGiftElement = SendElementBase<ElementType.LIVEGIFT> & ElementBase<'liveGiftElement'>;
|
||||||
elementType: ElementType.LIVEGIFT;
|
|
||||||
elementId: string;
|
|
||||||
liveGiftElement: Record<string, never>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SendTextElement {
|
export type SendTextElement = SendElementBase<ElementType.TEXT> & ElementBase<'textElement'>;
|
||||||
elementType: ElementType.TEXT;
|
|
||||||
elementId: string;
|
|
||||||
textElement: {
|
|
||||||
content: string;
|
|
||||||
atType: number;
|
|
||||||
atUid: string;
|
|
||||||
atTinyId: string;
|
|
||||||
atNtUid: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SendPttElement {
|
export type SendPttElement = SendElementBase<ElementType.PTT> & ElementBase<'pttElement', {
|
||||||
elementType: ElementType.PTT;
|
pttElement: ['fileName', 'filePath', 'md5HexStr', 'fileSize', 'duration', 'formatType', 'voiceType',
|
||||||
elementId: string;
|
'voiceChangeType', 'canConvert2Text', 'waveAmplitudes', 'fileSubId', 'playState', 'autoConvertText']
|
||||||
pttElement: {
|
}>;
|
||||||
fileName: string;
|
|
||||||
filePath: string;
|
|
||||||
md5HexStr: string;
|
|
||||||
fileSize: number;
|
|
||||||
duration: number; // 单位是秒
|
|
||||||
formatType: number;
|
|
||||||
voiceType: number;
|
|
||||||
voiceChangeType: number;
|
|
||||||
canConvert2Text: boolean;
|
|
||||||
waveAmplitudes: number[];
|
|
||||||
fileSubId: string;
|
|
||||||
playState: number;
|
|
||||||
autoConvertText: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum PicType {
|
export enum PicType {
|
||||||
gif = 2000,
|
gif = 2000,
|
||||||
@@ -359,11 +266,7 @@ export enum NTMsgAtType {
|
|||||||
ATTYPEUNKNOWN = 0
|
ATTYPEUNKNOWN = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SendPicElement {
|
export type SendPicElement = SendElementBase<ElementType.PIC> & ElementBase<'picElement'>;
|
||||||
elementType: ElementType.PIC;
|
|
||||||
elementId: string;
|
|
||||||
picElement: PicElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ReplyElement {
|
export interface ReplyElement {
|
||||||
sourceMsgIdInRecords?: string;
|
sourceMsgIdInRecords?: string;
|
||||||
@@ -375,53 +278,27 @@ export interface ReplyElement {
|
|||||||
replyMsgClientSeq?: string;
|
replyMsgClientSeq?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SendReplyElement {
|
export type SendReplyElement = SendElementBase<ElementType.REPLY> & ElementBase<'replyElement'>;
|
||||||
elementType: ElementType.REPLY;
|
|
||||||
elementId: string;
|
|
||||||
replyElement: ReplyElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SendFaceElement {
|
export type SendFaceElement = SendElementBase<ElementType.FACE> & ElementBase<'faceElement'>;
|
||||||
elementType: ElementType.FACE;
|
|
||||||
elementId: string;
|
|
||||||
faceElement: FaceElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SendMarketFaceElement {
|
export type SendMarketFaceElement = SendElementBase<ElementType.MFACE> & ElementBase<'marketFaceElement'>;
|
||||||
elementType: ElementType.MFACE;
|
|
||||||
marketFaceElement: MarketFaceElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SendStructLongMsgElement {
|
export type SendStructLongMsgElement = SendElementBase<ElementType.STRUCTLONGMSG> & ElementBase<'structLongMsgElement'>;
|
||||||
elementType: ElementType.STRUCTLONGMSG;
|
|
||||||
elementId: string;
|
|
||||||
structLongMsgElement: StructLongMsgElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StructLongMsgElement {
|
export interface StructLongMsgElement {
|
||||||
xmlContent: string;
|
xmlContent: string;
|
||||||
resId: string;
|
resId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SendactionBarElement {
|
export type SendActionBarElement = SendElementBase<ElementType.ACTIONBAR> & ElementBase<'actionBarElement'>;
|
||||||
elementType: ElementType.ACTIONBAR;
|
|
||||||
elementId: string;
|
|
||||||
actionBarElement: {
|
|
||||||
rows: number;
|
|
||||||
botAppid: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ShareLocationElement {
|
export interface ShareLocationElement {
|
||||||
text: string;
|
text: string;
|
||||||
ext: string;
|
ext: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SendShareLocationElement {
|
export type SendShareLocationElement = SendElementBase<ElementType.SHARELOCATION> & ElementBase<'shareLocationElement'>;
|
||||||
elementType: ElementType.SHARELOCATION;
|
|
||||||
elementId: string;
|
|
||||||
shareLocationElement?: ShareLocationElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FileElement {
|
export interface FileElement {
|
||||||
fileMd5?: string;
|
fileMd5?: string;
|
||||||
@@ -441,29 +318,13 @@ export interface FileElement {
|
|||||||
fileBizId?: number;
|
fileBizId?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SendFileElement {
|
export type SendFileElement = SendElementBase<ElementType.FILE> & ElementBase<'fileElement'>;
|
||||||
elementType: ElementType.FILE;
|
|
||||||
elementId: string;
|
|
||||||
fileElement: FileElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SendVideoElement {
|
export type SendVideoElement = SendElementBase<ElementType.VIDEO> & ElementBase<'videoElement'>;
|
||||||
elementType: ElementType.VIDEO;
|
|
||||||
elementId: string;
|
|
||||||
videoElement: VideoElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SendArkElement {
|
export type SendArkElement = SendElementBase<ElementType.ARK> & ElementBase<'arkElement'>;
|
||||||
elementType: ElementType.ARK;
|
|
||||||
elementId: string;
|
|
||||||
arkElement: ArkElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SendMarkdownElement {
|
export type SendMarkdownElement = SendElementBase<ElementType.MARKDOWN> & ElementBase<'markdownElement'>;
|
||||||
elementType: ElementType.MARKDOWN;
|
|
||||||
elementId: string;
|
|
||||||
markdownElement: MarkdownElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SendMessageElement = SendTextElement | SendPttElement |
|
export type SendMessageElement = SendTextElement | SendPttElement |
|
||||||
SendPicElement | SendReplyElement | SendFaceElement | SendMarketFaceElement | SendFileElement |
|
SendPicElement | SendReplyElement | SendFaceElement | SendMarketFaceElement | SendFileElement |
|
||||||
@@ -480,7 +341,7 @@ export interface TextElement {
|
|||||||
export interface MessageElement {
|
export interface MessageElement {
|
||||||
elementType: ElementType,
|
elementType: ElementType,
|
||||||
elementId: string,
|
elementId: string,
|
||||||
extBufForUI: string,//"0x",
|
extBufForUI?: string, //"0x",
|
||||||
textElement?: TextElement;
|
textElement?: TextElement;
|
||||||
faceElement?: FaceElement,
|
faceElement?: FaceElement,
|
||||||
marketFaceElement?: MarketFaceElement,
|
marketFaceElement?: MarketFaceElement,
|
||||||
@@ -509,7 +370,6 @@ export interface MessageElement {
|
|||||||
taskTopMsgElement?: TaskTopMsgElement,
|
taskTopMsgElement?: TaskTopMsgElement,
|
||||||
recommendedMsgElement?: RecommendedMsgElement,
|
recommendedMsgElement?: RecommendedMsgElement,
|
||||||
actionBarElement?: ActionBarElement
|
actionBarElement?: ActionBarElement
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AtType {
|
export enum AtType {
|
||||||
@@ -517,6 +377,12 @@ export enum AtType {
|
|||||||
atAll = 1,
|
atAll = 1,
|
||||||
atUser = 2
|
atUser = 2
|
||||||
}
|
}
|
||||||
|
export enum MsgSourceType {
|
||||||
|
K_DOWN_SOURCETYPE_AIOINNER = 1,
|
||||||
|
K_DOWN_SOURCETYPE_BIGSCREEN = 2,
|
||||||
|
K_DOWN_SOURCETYPE_HISTORY = 3,
|
||||||
|
K_DOWN_SOURCETYPE_UNKNOWN = 0
|
||||||
|
}
|
||||||
|
|
||||||
// 来自Android分析
|
// 来自Android分析
|
||||||
export enum ChatType {
|
export enum ChatType {
|
||||||
@@ -572,7 +438,7 @@ export interface PttElement {
|
|||||||
fileSize: string; // "4261"
|
fileSize: string; // "4261"
|
||||||
fileSubId: string; // "0"
|
fileSubId: string; // "0"
|
||||||
fileUuid: string; // "90j3z7rmRphDPrdVgP9udFBaYar#oK0TWZIV"
|
fileUuid: string; // "90j3z7rmRphDPrdVgP9udFBaYar#oK0TWZIV"
|
||||||
formatType: string; // 1
|
formatType: number; // 1
|
||||||
invalidState: number; // 0
|
invalidState: number; // 0
|
||||||
md5HexStr: string; // "e4d09c784d5a2abcb2f9980bdc7acfe6"
|
md5HexStr: string; // "e4d09c784d5a2abcb2f9980bdc7acfe6"
|
||||||
playState: number; // 0
|
playState: number; // 0
|
||||||
@@ -583,6 +449,7 @@ export interface PttElement {
|
|||||||
voiceChangeType: number; // 0
|
voiceChangeType: number; // 0
|
||||||
voiceType: number; // 0
|
voiceType: number; // 0
|
||||||
waveAmplitudes: number[];
|
waveAmplitudes: number[];
|
||||||
|
autoConvertText: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ArkElement {
|
export interface ArkElement {
|
||||||
@@ -788,7 +655,8 @@ export interface InlineKeyboardElementRowButton {
|
|||||||
export interface InlineKeyboardElement {
|
export interface InlineKeyboardElement {
|
||||||
rows: [{
|
rows: [{
|
||||||
buttons: InlineKeyboardElementRowButton[]
|
buttons: InlineKeyboardElementRowButton[]
|
||||||
}];
|
}],
|
||||||
|
botAppid: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TipAioOpGrayTipElement { // 这是什么提示来着?
|
export interface TipAioOpGrayTipElement { // 这是什么提示来着?
|
||||||
@@ -874,6 +742,8 @@ export interface RawMessage {
|
|||||||
/**
|
/**
|
||||||
* 扩展字段,与 Ob11 msg ID 有关
|
* 扩展字段,与 Ob11 msg ID 有关
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
id?: number;
|
id?: number;
|
||||||
|
|
||||||
guildId: string;
|
guildId: string;
|
||||||
@@ -950,6 +820,8 @@ export interface RawMessage {
|
|||||||
records: RawMessage[];
|
records: RawMessage[];
|
||||||
|
|
||||||
elements: MessageElement[];
|
elements: MessageElement[];
|
||||||
|
|
||||||
|
sourceType: MsgSourceType;
|
||||||
}
|
}
|
||||||
export interface QueryMsgsParams {
|
export interface QueryMsgsParams {
|
||||||
chatInfo: Peer;
|
chatInfo: Peer;
|
||||||
|
12
src/core/external/appid.json
vendored
12
src/core/external/appid.json
vendored
@@ -50,5 +50,17 @@
|
|||||||
"9.9.16-28788": {
|
"9.9.16-28788": {
|
||||||
"appid": 537249739,
|
"appid": 537249739,
|
||||||
"qua": "V1_WIN_NQ_9.9.16_28788_GW_B"
|
"qua": "V1_WIN_NQ_9.9.16_28788_GW_B"
|
||||||
|
},
|
||||||
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
20
src/core/external/offset.json
vendored
20
src/core/external/offset.json
vendored
@@ -18,5 +18,25 @@
|
|||||||
"3.2.13-28788-x64": {
|
"3.2.13-28788-x64": {
|
||||||
"send": "A0CEC20",
|
"send": "A0CEC20",
|
||||||
"recv": "A0D2520"
|
"recv": "A0D2520"
|
||||||
|
},
|
||||||
|
"3.2.13-28788-arm64": {
|
||||||
|
"send": "6E91018",
|
||||||
|
"recv": "6E94850"
|
||||||
|
},
|
||||||
|
"9.9.16-28971-x64": {
|
||||||
|
"send": "38079F0",
|
||||||
|
"recv": "380BE24"
|
||||||
|
},
|
||||||
|
"3.2.13-28971-x64": {
|
||||||
|
"send": "A0CEF60",
|
||||||
|
"recv": "A0D2860"
|
||||||
|
},
|
||||||
|
"3.2.12-28971-arm64": {
|
||||||
|
"send": "6E91318",
|
||||||
|
"recv": "6E94B50"
|
||||||
|
},
|
||||||
|
"6.9.56-28418-arm64": {
|
||||||
|
"send": "4471360",
|
||||||
|
"recv": "4473BCC"
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -62,7 +62,26 @@ export function loadQQWrapper(QQVersion: string): WrapperNodeApi {
|
|||||||
process.dlopen(nativemodule, wrapperNodePath);
|
process.dlopen(nativemodule, wrapperNodePath);
|
||||||
return nativemodule.exports;
|
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 {
|
export class NapCatCore {
|
||||||
readonly context: InstanceContext;
|
readonly context: InstanceContext;
|
||||||
readonly apis: StableNTApiWrapper;
|
readonly apis: StableNTApiWrapper;
|
||||||
@@ -140,7 +159,7 @@ export class NapCatCore {
|
|||||||
};
|
};
|
||||||
//await sleep(2500);
|
//await sleep(2500);
|
||||||
this.context.session.getMsgService().addKernelMsgListener(
|
this.context.session.getMsgService().addKernelMsgListener(
|
||||||
proxiedListenerOf(msgListener, this.context.logger) as any,
|
proxiedListenerOf(msgListener, this.context.logger),
|
||||||
);
|
);
|
||||||
|
|
||||||
const profileListener = new NodeIKernelProfileListener();
|
const profileListener = new NodeIKernelProfileListener();
|
||||||
@@ -236,7 +255,7 @@ export class NapCatCore {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.context.session.getGroupService().addKernelGroupListener(
|
this.context.session.getGroupService().addKernelGroupListener(
|
||||||
proxiedListenerOf(groupListener, this.context.logger) as any,
|
proxiedListenerOf(groupListener, this.context.logger),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -71,7 +71,8 @@ export class NodeIKernelGroupListener {
|
|||||||
sceneId: string,
|
sceneId: string,
|
||||||
ids: string[],
|
ids: string[],
|
||||||
infos: Map<string, GroupMember>, // uid -> GroupMember
|
infos: Map<string, GroupMember>, // uid -> GroupMember
|
||||||
finish: boolean,
|
hasPrev: boolean,
|
||||||
|
hasNext: boolean,
|
||||||
hasRobot: boolean
|
hasRobot: boolean
|
||||||
}) {
|
}) {
|
||||||
}
|
}
|
||||||
|
@@ -3,8 +3,7 @@ import { LRUCache } from "@/common/lru-cache";
|
|||||||
import WebSocket, { Data } from "ws";
|
import WebSocket, { Data } from "ws";
|
||||||
import crypto, { createHash } from "crypto";
|
import crypto, { createHash } from "crypto";
|
||||||
import { NapCatCore } from "@/core";
|
import { NapCatCore } from "@/core";
|
||||||
import { PacketHexStr } from "@/core/packet/packer";
|
import { OidbPacket, PacketHexStr } from "@/core/packet/packer";
|
||||||
import { sleep } from "@/common/helper";
|
|
||||||
|
|
||||||
export interface RecvPacket {
|
export interface RecvPacket {
|
||||||
type: string, // 仅recv
|
type: string, // 仅recv
|
||||||
@@ -22,7 +21,7 @@ export class PacketClient {
|
|||||||
private websocket: WebSocket | undefined;
|
private websocket: WebSocket | undefined;
|
||||||
private isConnected: boolean = false;
|
private isConnected: boolean = false;
|
||||||
private reconnectAttempts: number = 0;
|
private reconnectAttempts: number = 0;
|
||||||
private readonly maxReconnectAttempts: number = 5;//现在暂时不可配置
|
private readonly maxReconnectAttempts: number = 60;//现在暂时不可配置
|
||||||
private readonly cb = new LRUCache<string, (json: RecvPacketData) => Promise<void>>(500); // trace_id-type callback
|
private readonly cb = new LRUCache<string, (json: RecvPacketData) => Promise<void>>(500); // trace_id-type callback
|
||||||
private readonly clientUrl: string = '';
|
private readonly clientUrl: string = '';
|
||||||
readonly napCatCore: NapCatCore;
|
readonly napCatCore: NapCatCore;
|
||||||
@@ -47,7 +46,7 @@ export class PacketClient {
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(): Promise<void> {
|
connect(cb: any): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
//this.logger.log.bind(this.logger)(`[Core] [Packet Server] Attempting to connect to ${this.clientUrl}`);
|
//this.logger.log.bind(this.logger)(`[Core] [Packet Server] Attempting to connect to ${this.clientUrl}`);
|
||||||
this.websocket = new WebSocket(this.clientUrl);
|
this.websocket = new WebSocket(this.clientUrl);
|
||||||
@@ -56,7 +55,8 @@ export class PacketClient {
|
|||||||
this.websocket.onopen = () => {
|
this.websocket.onopen = () => {
|
||||||
this.isConnected = true;
|
this.isConnected = true;
|
||||||
this.reconnectAttempts = 0;
|
this.reconnectAttempts = 0;
|
||||||
this.logger.log.bind(this.logger)(`[Core] [Packet Server] Connected to ${this.clientUrl}`);
|
this.logger.log.bind(this.logger)(`[Core] [Packet Server] 已连接到 ${this.clientUrl}`);
|
||||||
|
cb();
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -74,25 +74,25 @@ export class PacketClient {
|
|||||||
this.websocket.onclose = () => {
|
this.websocket.onclose = () => {
|
||||||
this.isConnected = false;
|
this.isConnected = false;
|
||||||
//this.logger.logWarn.bind(this.logger)(`[Core] [Packet Server] Disconnected from ${this.clientUrl}`);
|
//this.logger.logWarn.bind(this.logger)(`[Core] [Packet Server] Disconnected from ${this.clientUrl}`);
|
||||||
this.attemptReconnect();
|
this.attemptReconnect(cb);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private attemptReconnect(): void {
|
private attemptReconnect(cb: any): void {
|
||||||
try {
|
try {
|
||||||
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
||||||
this.reconnectAttempts++;
|
this.reconnectAttempts++;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.connect().catch((error) => {
|
this.connect(cb).catch((error) => {
|
||||||
this.logger.logError.bind(this.logger)(`[Core] [Packet Server] Reconnecting attempt failed,${error.message}`);
|
this.logger.logError.bind(this.logger)(`[Core] [Packet Server] 尝试重连失败:${error.message}`);
|
||||||
});
|
});
|
||||||
}, 5000 * this.reconnectAttempts);
|
}, 5000 * this.reconnectAttempts);
|
||||||
} else {
|
} else {
|
||||||
this.logger.logError.bind(this.logger)(`[Core] [Packet Server] Max reconnect attempts reached. ${this.clientUrl}`);
|
this.logger.logError.bind(this.logger)(`[Core] [Packet Server] ${this.clientUrl} 已达到最大重连次数!`);
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
this.logger.logError.bind(this.logger)(`Error attempting to reconnect: ${error.message}`);
|
this.logger.logError.bind(this.logger)(`[Core] [Packet Server] 重连时出错: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,7 +165,7 @@ export class PacketClient {
|
|||||||
// 校验失败和异常 可能返回undefined
|
// 校验失败和异常 可能返回undefined
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!this.available) {
|
if (!this.available) {
|
||||||
this.logger.logError('NapCat.Packet is not init');
|
this.logger.logError('NapCat.Packet 未初始化!');
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const md5 = crypto.createHash('md5').update(data).digest('hex');
|
const md5 = crypto.createHash('md5').update(data).digest('hex');
|
||||||
@@ -176,4 +176,8 @@ export class PacketClient {
|
|||||||
}).then((res) => resolve(res)).catch((e: Error) => reject(e));
|
}).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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@@ -36,7 +36,7 @@ export class PacketHighwayClient {
|
|||||||
this.port = port;
|
this.port = port;
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildDataUpTrans(cmd: number, data: ReadStream, fileSize: number, md5: Uint8Array, extendInfo: Uint8Array, timeout: number = 3600): PacketHighwayTrans {
|
private buildDataUpTrans(cmd: number, data: ReadStream, fileSize: number, md5: Uint8Array, extendInfo: Uint8Array, timeout: number = 1200): PacketHighwayTrans {
|
||||||
return {
|
return {
|
||||||
uin: this.sig.uin,
|
uin: this.sig.uin,
|
||||||
cmd: cmd,
|
cmd: cmd,
|
||||||
|
@@ -8,9 +8,17 @@ import {HttpConn0x6ff_501Response} from "@/core/packet/proto/action/action";
|
|||||||
import { PacketHighwayClient } from "@/core/packet/highway/client";
|
import { PacketHighwayClient } from "@/core/packet/highway/client";
|
||||||
import { NTV2RichMediaResp } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaResp";
|
import { NTV2RichMediaResp } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaResp";
|
||||||
import { OidbSvcTrpcTcpBaseRsp } from "@/core/packet/proto/oidb/OidbBase";
|
import { OidbSvcTrpcTcpBaseRsp } from "@/core/packet/proto/oidb/OidbBase";
|
||||||
import {PacketMsgPicElement} from "@/core/packet/msg/element";
|
import {
|
||||||
import {NTV2RichMediaHighwayExt} from "@/core/packet/proto/highway/highway";
|
PacketMsgFileElement,
|
||||||
|
PacketMsgPicElement,
|
||||||
|
PacketMsgPttElement,
|
||||||
|
PacketMsgVideoElement
|
||||||
|
} from "@/core/packet/message/element";
|
||||||
|
import { FileUploadExt, NTV2RichMediaHighwayExt } from "@/core/packet/proto/highway/highway";
|
||||||
import { int32ip2str, oidbIpv4s2HighwayIpv4s } from "@/core/packet/highway/utils";
|
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";
|
||||||
|
|
||||||
export const BlockSize = 1024 * 1024;
|
export const BlockSize = 1024 * 1024;
|
||||||
|
|
||||||
@@ -21,6 +29,7 @@ interface HighwayServerAddr {
|
|||||||
|
|
||||||
export interface PacketHighwaySig {
|
export interface PacketHighwaySig {
|
||||||
uin: string;
|
uin: string;
|
||||||
|
uid: string;
|
||||||
sigSession: Uint8Array | null
|
sigSession: Uint8Array | null
|
||||||
sessionKey: Uint8Array | null
|
sessionKey: Uint8Array | null
|
||||||
serverAddr: HighwayServerAddr[]
|
serverAddr: HighwayServerAddr[]
|
||||||
@@ -39,17 +48,17 @@ export class PacketHighwaySession {
|
|||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.sig = {
|
this.sig = {
|
||||||
uin: this.packetClient.napCatCore.selfInfo.uin,
|
uin: this.packetClient.napCatCore.selfInfo.uin,
|
||||||
|
uid: this.packetClient.napCatCore.selfInfo.uid,
|
||||||
sigSession: null,
|
sigSession: null,
|
||||||
sessionKey: null,
|
sessionKey: null,
|
||||||
serverAddr: [],
|
serverAddr: [],
|
||||||
}
|
};
|
||||||
this.packer = packer;
|
this.packer = packer;
|
||||||
this.packetHighwayClient = new PacketHighwayClient(this.sig, this.logger);
|
this.packetHighwayClient = new PacketHighwayClient(this.sig, this.logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async checkAvailable() {
|
private async checkAvailable() {
|
||||||
if (!this.packetClient.available) {
|
if (!this.packetClient.available) {
|
||||||
this.logger.logError('[Highway] packetServer not available!');
|
|
||||||
throw new Error('packetServer不可用,请参照文档 https://napneko.github.io/config/advanced 检查packetServer状态或进行配置');
|
throw new Error('packetServer不可用,请参照文档 https://napneko.github.io/config/advanced 检查packetServer状态或进行配置');
|
||||||
}
|
}
|
||||||
if (this.sig.sigSession === null || this.sig.sessionKey === null) {
|
if (this.sig.sigSession === null || this.sig.sessionKey === null) {
|
||||||
@@ -69,8 +78,8 @@ export class PacketHighwaySession {
|
|||||||
const rsp = new NapProtoMsg(HttpConn0x6ff_501Response).decode(
|
const rsp = new NapProtoMsg(HttpConn0x6ff_501Response).decode(
|
||||||
Buffer.from(req.hex_data, 'hex')
|
Buffer.from(req.hex_data, 'hex')
|
||||||
);
|
);
|
||||||
this.sig.sigSession = rsp.httpConn.sigSession
|
this.sig.sigSession = rsp.httpConn.sigSession;
|
||||||
this.sig.sessionKey = rsp.httpConn.sessionKey
|
this.sig.sessionKey = rsp.httpConn.sessionKey;
|
||||||
for (const info of rsp.httpConn.serverInfos) {
|
for (const info of rsp.httpConn.serverInfos) {
|
||||||
if (info.serviceType !== 1) continue;
|
if (info.serviceType !== 1) continue;
|
||||||
for (const addr of info.serverAddrs) {
|
for (const addr of info.serverAddrs) {
|
||||||
@@ -78,7 +87,7 @@ export class PacketHighwaySession {
|
|||||||
this.sig.serverAddr.push({
|
this.sig.serverAddr.push({
|
||||||
ip: int32ip2str(addr.ip),
|
ip: int32ip2str(addr.ip),
|
||||||
port: addr.port
|
port: addr.port
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,7 +95,7 @@ export class PacketHighwaySession {
|
|||||||
async uploadImage(peer: Peer, img: PacketMsgPicElement): Promise<void> {
|
async uploadImage(peer: Peer, img: PacketMsgPicElement): Promise<void> {
|
||||||
await this.checkAvailable();
|
await this.checkAvailable();
|
||||||
if (peer.chatType === ChatType.KCHATTYPEGROUP) {
|
if (peer.chatType === ChatType.KCHATTYPEGROUP) {
|
||||||
await this.uploadGroupImageReq(Number(peer.peerUid), img);
|
await this.uploadGroupImageReq(+peer.peerUid, img);
|
||||||
} else if (peer.chatType === ChatType.KCHATTYPEC2C) {
|
} else if (peer.chatType === ChatType.KCHATTYPEC2C) {
|
||||||
await this.uploadC2CImageReq(peer.peerUid, img);
|
await this.uploadC2CImageReq(peer.peerUid, img);
|
||||||
} else {
|
} else {
|
||||||
@@ -94,16 +103,53 @@ export class PacketHighwaySession {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async uploadVideo(peer: Peer, video: PacketMsgVideoElement): Promise<void> {
|
||||||
|
await this.checkAvailable();
|
||||||
|
if (+(video.fileSize ?? 0) > 1024 * 1024 * 100) {
|
||||||
|
throw new Error(`[Highway] 视频文件过大: ${(+(video.fileSize ?? 0) / (1024 * 1024)).toFixed(2)} MB > 100 MB,请使用文件上传!`);
|
||||||
|
}
|
||||||
|
if (peer.chatType === ChatType.KCHATTYPEGROUP) {
|
||||||
|
await this.uploadGroupVideoReq(+peer.peerUid, video);
|
||||||
|
} else if (peer.chatType === ChatType.KCHATTYPEC2C) {
|
||||||
|
await this.uploadC2CVideoReq(peer.peerUid, video);
|
||||||
|
} else {
|
||||||
|
throw new Error(`[Highway] unsupported chatType: ${peer.chatType}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async uploadPtt(peer: Peer, ptt: PacketMsgPttElement): Promise<void> {
|
||||||
|
await this.checkAvailable();
|
||||||
|
if (peer.chatType === ChatType.KCHATTYPEGROUP) {
|
||||||
|
await this.uploadGroupPttReq(+peer.peerUid, ptt);
|
||||||
|
} else if (peer.chatType === ChatType.KCHATTYPEC2C) {
|
||||||
|
await this.uploadC2CPttReq(peer.peerUid, ptt);
|
||||||
|
} else {
|
||||||
|
throw new Error(`[Highway] unsupported chatType: ${peer.chatType}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async uploadFile(peer: Peer, file: PacketMsgFileElement): Promise<void> {
|
||||||
|
await this.checkAvailable();
|
||||||
|
if (peer.chatType === ChatType.KCHATTYPEGROUP) {
|
||||||
|
await this.uploadGroupFileReq(+peer.peerUid, file);
|
||||||
|
} else if (peer.chatType === ChatType.KCHATTYPEC2C) {
|
||||||
|
await this.uploadC2CFileReq(peer.peerUid, file);
|
||||||
|
} else {
|
||||||
|
throw new Error(`[Highway] unsupported chatType: ${peer.chatType}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async uploadGroupImageReq(groupUin: number, img: PacketMsgPicElement): Promise<void> {
|
private async uploadGroupImageReq(groupUin: number, img: PacketMsgPicElement): Promise<void> {
|
||||||
|
img.sha1 = Buffer.from(await calculateSha1(img.path)).toString('hex');
|
||||||
const preReq = await this.packer.packUploadGroupImgReq(groupUin, img);
|
const preReq = await this.packer.packUploadGroupImgReq(groupUin, img);
|
||||||
const preRespRaw = await this.packetClient.sendPacket('OidbSvcTrpcTcp.0x11c4_100', preReq, true);
|
const preRespRaw = await this.packetClient.sendOidbPacket(preReq, true);
|
||||||
const preResp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(
|
const preResp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(
|
||||||
Buffer.from(preRespRaw.hex_data, 'hex')
|
Buffer.from(preRespRaw.hex_data, 'hex')
|
||||||
);
|
);
|
||||||
const preRespData = new NapProtoMsg(NTV2RichMediaResp).decode(preResp.body);
|
const preRespData = new NapProtoMsg(NTV2RichMediaResp).decode(preResp.body);
|
||||||
const ukey = preRespData.upload.uKey;
|
const ukey = preRespData.upload.uKey;
|
||||||
if (ukey && ukey != "") {
|
if (ukey && ukey != "") {
|
||||||
this.logger.logDebug(`[Highway] get upload ukey: ${ukey}, need upload!`);
|
this.logger.logDebug(`[Highway] uploadGroupImageReq get upload ukey: ${ukey}, need upload!`);
|
||||||
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
||||||
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
||||||
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||||
@@ -118,7 +164,7 @@ export class PacketHighwaySession {
|
|||||||
hash: {
|
hash: {
|
||||||
fileSha1: [sha1]
|
fileSha1: [sha1]
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
await this.packetHighwayClient.upload(
|
await this.packetHighwayClient.upload(
|
||||||
1004,
|
1004,
|
||||||
fs.createReadStream(img.path, { highWaterMark: BlockSize }),
|
fs.createReadStream(img.path, { highWaterMark: BlockSize }),
|
||||||
@@ -127,22 +173,23 @@ export class PacketHighwaySession {
|
|||||||
extend
|
extend
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.logger.logDebug(`[Highway] get upload invalid ukey ${ukey}, don't need upload!`);
|
this.logger.logDebug(`[Highway] uploadGroupImageReq get upload invalid ukey ${ukey}, don't need upload!`);
|
||||||
}
|
}
|
||||||
img.msgInfo = preRespData.upload.msgInfo;
|
img.msgInfo = preRespData.upload.msgInfo;
|
||||||
// img.groupPicExt = new NapProtoMsg(CustomFace).decode(preRespData.tcpUpload.compatQMsg)
|
// img.groupPicExt = new NapProtoMsg(CustomFace).decode(preRespData.tcpUpload.compatQMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
private async uploadC2CImageReq(peerUid: string, img: PacketMsgPicElement): Promise<void> {
|
private async uploadC2CImageReq(peerUid: string, img: PacketMsgPicElement): Promise<void> {
|
||||||
|
img.sha1 = Buffer.from(await calculateSha1(img.path)).toString('hex');
|
||||||
const preReq = await this.packer.packUploadC2CImgReq(peerUid, img);
|
const preReq = await this.packer.packUploadC2CImgReq(peerUid, img);
|
||||||
const preRespRaw = await this.packetClient.sendPacket('OidbSvcTrpcTcp.0x11c5_100', preReq, true);
|
const preRespRaw = await this.packetClient.sendOidbPacket(preReq, true);
|
||||||
const preResp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(
|
const preResp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(
|
||||||
Buffer.from(preRespRaw.hex_data, 'hex')
|
Buffer.from(preRespRaw.hex_data, 'hex')
|
||||||
);
|
);
|
||||||
const preRespData = new NapProtoMsg(NTV2RichMediaResp).decode(preResp.body);
|
const preRespData = new NapProtoMsg(NTV2RichMediaResp).decode(preResp.body);
|
||||||
const ukey = preRespData.upload.uKey;
|
const ukey = preRespData.upload.uKey;
|
||||||
if (ukey && ukey != "") {
|
if (ukey && ukey != "") {
|
||||||
this.logger.logDebug(`[Highway] get upload ukey: ${ukey}, need upload!`);
|
this.logger.logDebug(`[Highway] uploadC2CImageReq get upload ukey: ${ukey}, need upload!`);
|
||||||
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
||||||
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
||||||
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||||
@@ -157,7 +204,7 @@ export class PacketHighwaySession {
|
|||||||
hash: {
|
hash: {
|
||||||
fileSha1: [sha1]
|
fileSha1: [sha1]
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
await this.packetHighwayClient.upload(
|
await this.packetHighwayClient.upload(
|
||||||
1003,
|
1003,
|
||||||
fs.createReadStream(img.path, { highWaterMark: BlockSize }),
|
fs.createReadStream(img.path, { highWaterMark: BlockSize }),
|
||||||
@@ -165,7 +212,359 @@ export class PacketHighwaySession {
|
|||||||
md5,
|
md5,
|
||||||
extend
|
extend
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
this.logger.logDebug(`[Highway] uploadC2CImageReq get upload invalid ukey ${ukey}, don't need upload!`);
|
||||||
}
|
}
|
||||||
img.msgInfo = preRespData.upload.msgInfo;
|
img.msgInfo = preRespData.upload.msgInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async uploadGroupVideoReq(groupUin: number, video: PacketMsgVideoElement): Promise<void> {
|
||||||
|
if (!video.filePath || !video.thumbPath) throw new Error("video.filePath or video.thumbPath is empty");
|
||||||
|
video.fileSha1 = Buffer.from(await calculateSha1(video.filePath)).toString('hex');
|
||||||
|
video.thumbSha1 = Buffer.from(await calculateSha1(video.thumbPath)).toString('hex');
|
||||||
|
const preReq = await this.packer.packUploadGroupVideoReq(groupUin, video);
|
||||||
|
const preRespRaw = await this.packetClient.sendOidbPacket(preReq, true);
|
||||||
|
const preResp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(
|
||||||
|
Buffer.from(preRespRaw.hex_data, 'hex')
|
||||||
|
);
|
||||||
|
const preRespData = new NapProtoMsg(NTV2RichMediaResp).decode(preResp.body);
|
||||||
|
const ukey = preRespData.upload.uKey;
|
||||||
|
if (ukey && ukey != "") {
|
||||||
|
this.logger.logDebug(`[Highway] uploadGroupVideoReq get upload video ukey: ${ukey}, need upload!`);
|
||||||
|
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
||||||
|
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||||
|
const extend = new NapProtoMsg(NTV2RichMediaHighwayExt).encode({
|
||||||
|
fileUuid: index.fileUuid,
|
||||||
|
uKey: ukey,
|
||||||
|
network: {
|
||||||
|
ipv4S: oidbIpv4s2HighwayIpv4s(preRespData.upload.ipv4S)
|
||||||
|
},
|
||||||
|
msgInfoBody: preRespData.upload.msgInfo.msgInfoBody,
|
||||||
|
blockSize: BlockSize,
|
||||||
|
hash: {
|
||||||
|
fileSha1: await calculateSha1StreamBytes(video.filePath!)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await this.packetHighwayClient.upload(
|
||||||
|
1005,
|
||||||
|
fs.createReadStream(video.filePath!, { highWaterMark: BlockSize }),
|
||||||
|
+video.fileSize!,
|
||||||
|
md5,
|
||||||
|
extend
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.logger.logDebug(`[Highway] uploadGroupVideoReq get upload invalid ukey ${ukey}, don't need upload!`);
|
||||||
|
}
|
||||||
|
const subFile = preRespData.upload.subFileInfos[0];
|
||||||
|
if (subFile.uKey && subFile.uKey != "") {
|
||||||
|
this.logger.logDebug(`[Highway] uploadGroupVideoReq get upload video thumb ukey: ${subFile.uKey}, need upload!`);
|
||||||
|
const index = preRespData.upload.msgInfo.msgInfoBody[1].index;
|
||||||
|
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||||
|
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
||||||
|
const extend = new NapProtoMsg(NTV2RichMediaHighwayExt).encode({
|
||||||
|
fileUuid: index.fileUuid,
|
||||||
|
uKey: subFile.uKey,
|
||||||
|
network: {
|
||||||
|
ipv4S: oidbIpv4s2HighwayIpv4s(subFile.ipv4S)
|
||||||
|
},
|
||||||
|
msgInfoBody: preRespData.upload.msgInfo.msgInfoBody,
|
||||||
|
blockSize: BlockSize,
|
||||||
|
hash: {
|
||||||
|
fileSha1: [sha1]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await this.packetHighwayClient.upload(
|
||||||
|
1006,
|
||||||
|
fs.createReadStream(video.thumbPath!, { highWaterMark: BlockSize }),
|
||||||
|
+video.thumbSize!,
|
||||||
|
md5,
|
||||||
|
extend
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.logger.logDebug(`[Highway] uploadGroupVideoReq get upload invalid thumb ukey ${subFile.uKey}, don't need upload!`);
|
||||||
|
}
|
||||||
|
video.msgInfo = preRespData.upload.msgInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async uploadC2CVideoReq(peerUid: string, video: PacketMsgVideoElement): Promise<void> {
|
||||||
|
if (!video.filePath || !video.thumbPath) throw new Error("video.filePath or video.thumbPath is empty");
|
||||||
|
video.fileSha1 = Buffer.from(await calculateSha1(video.filePath)).toString('hex');
|
||||||
|
video.thumbSha1 = Buffer.from(await calculateSha1(video.thumbPath)).toString('hex');
|
||||||
|
const preReq = await this.packer.packUploadC2CVideoReq(peerUid, video);
|
||||||
|
const preRespRaw = await this.packetClient.sendOidbPacket(preReq, true);
|
||||||
|
const preResp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(
|
||||||
|
Buffer.from(preRespRaw.hex_data, 'hex')
|
||||||
|
);
|
||||||
|
const preRespData = new NapProtoMsg(NTV2RichMediaResp).decode(preResp.body);
|
||||||
|
const ukey = preRespData.upload.uKey;
|
||||||
|
if (ukey && ukey != "") {
|
||||||
|
this.logger.logDebug(`[Highway] uploadC2CVideoReq get upload video ukey: ${ukey}, need upload!`);
|
||||||
|
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
||||||
|
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||||
|
const extend = new NapProtoMsg(NTV2RichMediaHighwayExt).encode({
|
||||||
|
fileUuid: index.fileUuid,
|
||||||
|
uKey: ukey,
|
||||||
|
network: {
|
||||||
|
ipv4S: oidbIpv4s2HighwayIpv4s(preRespData.upload.ipv4S)
|
||||||
|
},
|
||||||
|
msgInfoBody: preRespData.upload.msgInfo.msgInfoBody,
|
||||||
|
blockSize: BlockSize,
|
||||||
|
hash: {
|
||||||
|
fileSha1: await calculateSha1StreamBytes(video.filePath!)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await this.packetHighwayClient.upload(
|
||||||
|
1001,
|
||||||
|
fs.createReadStream(video.filePath!, { highWaterMark: BlockSize }),
|
||||||
|
+video.fileSize!,
|
||||||
|
md5,
|
||||||
|
extend
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.logger.logDebug(`[Highway] uploadC2CVideoReq get upload invalid ukey ${ukey}, don't need upload!`);
|
||||||
|
}
|
||||||
|
const subFile = preRespData.upload.subFileInfos[0];
|
||||||
|
if (subFile.uKey && subFile.uKey != "") {
|
||||||
|
this.logger.logDebug(`[Highway] uploadC2CVideoReq get upload video thumb ukey: ${subFile.uKey}, need upload!`);
|
||||||
|
const index = preRespData.upload.msgInfo.msgInfoBody[1].index;
|
||||||
|
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||||
|
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
||||||
|
const extend = new NapProtoMsg(NTV2RichMediaHighwayExt).encode({
|
||||||
|
fileUuid: index.fileUuid,
|
||||||
|
uKey: subFile.uKey,
|
||||||
|
network: {
|
||||||
|
ipv4S: oidbIpv4s2HighwayIpv4s(subFile.ipv4S)
|
||||||
|
},
|
||||||
|
msgInfoBody: preRespData.upload.msgInfo.msgInfoBody,
|
||||||
|
blockSize: BlockSize,
|
||||||
|
hash: {
|
||||||
|
fileSha1: [sha1]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await this.packetHighwayClient.upload(
|
||||||
|
1002,
|
||||||
|
fs.createReadStream(video.thumbPath!, { highWaterMark: BlockSize }),
|
||||||
|
+video.thumbSize!,
|
||||||
|
md5,
|
||||||
|
extend
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.logger.logDebug(`[Highway] uploadC2CVideoReq get upload invalid thumb ukey ${subFile.uKey}, don't need upload!`);
|
||||||
|
}
|
||||||
|
video.msgInfo = preRespData.upload.msgInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async uploadGroupPttReq(groupUin: number, ptt: PacketMsgPttElement): Promise<void> {
|
||||||
|
ptt.fileSha1 = Buffer.from(await calculateSha1(ptt.filePath)).toString('hex');
|
||||||
|
const preReq = await this.packer.packUploadGroupPttReq(groupUin, ptt);
|
||||||
|
const preRespRaw = await this.packetClient.sendOidbPacket(preReq, true);
|
||||||
|
const preResp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(
|
||||||
|
Buffer.from(preRespRaw.hex_data, 'hex')
|
||||||
|
);
|
||||||
|
const preRespData = new NapProtoMsg(NTV2RichMediaResp).decode(preResp.body);
|
||||||
|
const ukey = preRespData.upload.uKey;
|
||||||
|
if (ukey && ukey != "") {
|
||||||
|
this.logger.logDebug(`[Highway] uploadGroupPttReq get upload ptt ukey: ${ukey}, need upload!`);
|
||||||
|
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
||||||
|
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||||
|
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
||||||
|
const extend = new NapProtoMsg(NTV2RichMediaHighwayExt).encode({
|
||||||
|
fileUuid: index.fileUuid,
|
||||||
|
uKey: ukey,
|
||||||
|
network: {
|
||||||
|
ipv4S: oidbIpv4s2HighwayIpv4s(preRespData.upload.ipv4S)
|
||||||
|
},
|
||||||
|
msgInfoBody: preRespData.upload.msgInfo.msgInfoBody,
|
||||||
|
blockSize: BlockSize,
|
||||||
|
hash: {
|
||||||
|
fileSha1: [sha1]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await this.packetHighwayClient.upload(
|
||||||
|
1008,
|
||||||
|
fs.createReadStream(ptt.filePath, { highWaterMark: BlockSize }),
|
||||||
|
ptt.fileSize,
|
||||||
|
md5,
|
||||||
|
extend
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.logger.logDebug(`[Highway] uploadGroupPttReq get upload invalid ukey ${ukey}, don't need upload!`);
|
||||||
|
}
|
||||||
|
ptt.msgInfo = preRespData.upload.msgInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async uploadC2CPttReq(peerUid: string, ptt: PacketMsgPttElement): Promise<void> {
|
||||||
|
ptt.fileSha1 = Buffer.from(await calculateSha1(ptt.filePath)).toString('hex');
|
||||||
|
const preReq = await this.packer.packUploadC2CPttReq(peerUid, ptt);
|
||||||
|
const preRespRaw = await this.packetClient.sendOidbPacket(preReq, true);
|
||||||
|
const preResp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(
|
||||||
|
Buffer.from(preRespRaw.hex_data, 'hex')
|
||||||
|
);
|
||||||
|
const preRespData = new NapProtoMsg(NTV2RichMediaResp).decode(preResp.body);
|
||||||
|
const ukey = preRespData.upload.uKey;
|
||||||
|
if (ukey && ukey != "") {
|
||||||
|
this.logger.logDebug(`[Highway] uploadC2CPttReq get upload ptt ukey: ${ukey}, need upload!`);
|
||||||
|
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
||||||
|
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||||
|
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
||||||
|
const extend = new NapProtoMsg(NTV2RichMediaHighwayExt).encode({
|
||||||
|
fileUuid: index.fileUuid,
|
||||||
|
uKey: ukey,
|
||||||
|
network: {
|
||||||
|
ipv4S: oidbIpv4s2HighwayIpv4s(preRespData.upload.ipv4S)
|
||||||
|
},
|
||||||
|
msgInfoBody: preRespData.upload.msgInfo.msgInfoBody,
|
||||||
|
blockSize: BlockSize,
|
||||||
|
hash: {
|
||||||
|
fileSha1: [sha1]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await this.packetHighwayClient.upload(
|
||||||
|
1007,
|
||||||
|
fs.createReadStream(ptt.filePath, { highWaterMark: BlockSize }),
|
||||||
|
ptt.fileSize,
|
||||||
|
md5,
|
||||||
|
extend
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.logger.logDebug(`[Highway] uploadC2CPttReq get upload invalid ukey ${ukey}, don't need upload!`);
|
||||||
|
}
|
||||||
|
ptt.msgInfo = preRespData.upload.msgInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async uploadGroupFileReq(groupUin: number, file: PacketMsgFileElement): Promise<void> {
|
||||||
|
file.isGroupFile = true;
|
||||||
|
file.fileMd5 = await computeMd5AndLengthWithLimit(file.filePath);
|
||||||
|
file.fileSha1 = await calculateSha1(file.filePath);
|
||||||
|
const preReq = await this.packer.packUploadGroupFileReq(groupUin, file);
|
||||||
|
const preRespRaw = await this.packetClient.sendOidbPacket(preReq, true);
|
||||||
|
const preResp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(
|
||||||
|
Buffer.from(preRespRaw.hex_data, 'hex')
|
||||||
|
);
|
||||||
|
const preRespData = new NapProtoMsg(OidbSvcTrpcTcp0x6D6Response).decode(preResp.body);
|
||||||
|
if (!preRespData?.upload?.boolFileExist) {
|
||||||
|
this.logger.logDebug(`[Highway] uploadGroupFileReq file not exist, need upload!`);
|
||||||
|
const ext = new NapProtoMsg(FileUploadExt).encode({
|
||||||
|
unknown1: 100,
|
||||||
|
unknown2: 1,
|
||||||
|
entry: {
|
||||||
|
busiBuff: {
|
||||||
|
senderUin: BigInt(this.sig.uin),
|
||||||
|
receiverUin: BigInt(groupUin),
|
||||||
|
groupCode: BigInt(groupUin),
|
||||||
|
},
|
||||||
|
fileEntry: {
|
||||||
|
fileSize: BigInt(file.fileSize),
|
||||||
|
md5: file.fileMd5,
|
||||||
|
md5S2: file.fileMd5,
|
||||||
|
checkKey: preRespData.upload.checkKey,
|
||||||
|
fileId: preRespData.upload.fileId,
|
||||||
|
uploadKey: preRespData.upload.fileKey,
|
||||||
|
},
|
||||||
|
clientInfo: {
|
||||||
|
clientType: 3,
|
||||||
|
appId: "100",
|
||||||
|
terminalType: 3,
|
||||||
|
clientVer: "1.1.1",
|
||||||
|
unknown: 4
|
||||||
|
},
|
||||||
|
fileNameInfo: {
|
||||||
|
fileName: file.fileName
|
||||||
|
},
|
||||||
|
host: {
|
||||||
|
hosts: [
|
||||||
|
{
|
||||||
|
url: {
|
||||||
|
host: preRespData.upload.uploadIp,
|
||||||
|
unknown: 1,
|
||||||
|
},
|
||||||
|
port: preRespData.upload.uploadPort,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
unknown200: 0,
|
||||||
|
});
|
||||||
|
await this.packetHighwayClient.upload(
|
||||||
|
71,
|
||||||
|
fs.createReadStream(file.filePath, { highWaterMark: BlockSize }),
|
||||||
|
file.fileSize,
|
||||||
|
file.fileMd5,
|
||||||
|
ext
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.logger.logDebug(`[Highway] uploadGroupFileReq file exist, don't need upload!`);
|
||||||
|
}
|
||||||
|
file.fileUuid = preRespData.upload.fileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async uploadC2CFileReq(peerUid: string, file: PacketMsgFileElement): Promise<void> {
|
||||||
|
file.isGroupFile = false;
|
||||||
|
file.fileMd5 = await computeMd5AndLengthWithLimit(file.filePath);
|
||||||
|
file.fileSha1 = await calculateSha1(file.filePath);
|
||||||
|
const preReq = await this.packer.packUploadC2CFileReq(this.sig.uid, peerUid, file);
|
||||||
|
const preRespRaw = await this.packetClient.sendOidbPacket( preReq, true);
|
||||||
|
const preResp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(
|
||||||
|
Buffer.from(preRespRaw.hex_data, 'hex')
|
||||||
|
);
|
||||||
|
const preRespData = new NapProtoMsg(OidbSvcTrpcTcp0XE37Response).decode(preResp.body);
|
||||||
|
if (!preRespData.upload?.boolFileExist) {
|
||||||
|
this.logger.logDebug(`[Highway] uploadC2CFileReq file not exist, need upload!`);
|
||||||
|
const ext = new NapProtoMsg(FileUploadExt).encode({
|
||||||
|
unknown1: 100,
|
||||||
|
unknown2: 1,
|
||||||
|
entry: {
|
||||||
|
busiBuff: {
|
||||||
|
senderUin: BigInt(this.sig.uin),
|
||||||
|
},
|
||||||
|
fileEntry: {
|
||||||
|
fileSize: BigInt(file.fileSize),
|
||||||
|
md5: file.fileMd5,
|
||||||
|
md5S2: file.fileMd5,
|
||||||
|
checkKey: file.fileSha1,
|
||||||
|
fileId: preRespData.upload?.uuid,
|
||||||
|
uploadKey: preRespData.upload?.mediaPlatformUploadKey,
|
||||||
|
},
|
||||||
|
clientInfo: {
|
||||||
|
clientType: 3,
|
||||||
|
appId: "100",
|
||||||
|
terminalType: 3,
|
||||||
|
clientVer: "1.1.1",
|
||||||
|
unknown: 4
|
||||||
|
},
|
||||||
|
fileNameInfo: {
|
||||||
|
fileName: file.fileName
|
||||||
|
},
|
||||||
|
host: {
|
||||||
|
hosts: [
|
||||||
|
{
|
||||||
|
url: {
|
||||||
|
host: preRespData.upload?.uploadIp,
|
||||||
|
unknown: 1,
|
||||||
|
},
|
||||||
|
port: preRespData.upload?.uploadPort,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
unknown200: 1,
|
||||||
|
unknown3: 0
|
||||||
|
});
|
||||||
|
await this.packetHighwayClient.upload(
|
||||||
|
95,
|
||||||
|
fs.createReadStream(file.filePath, { highWaterMark: BlockSize }),
|
||||||
|
file.fileSize,
|
||||||
|
file.fileMd5,
|
||||||
|
ext
|
||||||
|
);
|
||||||
|
}
|
||||||
|
file.fileUuid = preRespData.upload?.uuid;
|
||||||
|
file.fileHash = preRespData.upload?.fileAddon;
|
||||||
|
const FetchExistFileReq = this.packer.packOfflineFileDownloadReq(file.fileUuid!, file.fileHash!, this.sig.uid, peerUid);
|
||||||
|
const resp = await this.packetClient.sendOidbPacket(FetchExistFileReq, true);
|
||||||
|
const oidb_resp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(Buffer.from(resp.hex_data, 'hex'));
|
||||||
|
file._e37_800_rsp = new NapProtoMsg(OidbSvcTrpcTcp0XE37_800Response).decode(oidb_resp.body);
|
||||||
|
file._private_send_uid = this.sig.uid;
|
||||||
|
file._private_recv_uid = peerUid;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,11 +19,20 @@ abstract class HighwayUploader {
|
|||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptTransExt(key: Uint8Array) {
|
private encryptTransExt(key: Uint8Array) {
|
||||||
if (!this.trans.encrypt) return;
|
if (!this.trans.encrypt) return;
|
||||||
this.trans.ext = tea.encrypt(Buffer.from(this.trans.ext), Buffer.from(key));
|
this.trans.ext = tea.encrypt(Buffer.from(this.trans.ext), Buffer.from(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected timeout(): Promise<void> {
|
||||||
|
return new Promise<void>((_, reject) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
reject(new Error(`[Highway] timeout after ${this.trans.timeout}s`));
|
||||||
|
}, (this.trans.timeout ?? Infinity) * 1000
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
buildPicUpHead(offset: number, bodyLength: number, bodyMd5: Uint8Array): Uint8Array {
|
buildPicUpHead(offset: number, bodyLength: number, bodyMd5: Uint8Array): Uint8Array {
|
||||||
return new NapProtoMsg(ReqDataHighwayHead).encode({
|
return new NapProtoMsg(ReqDataHighwayHead).encode({
|
||||||
msgBaseHead: {
|
msgBaseHead: {
|
||||||
@@ -53,7 +62,7 @@ abstract class HighwayUploader {
|
|||||||
uint32LoginSigType: 8,
|
uint32LoginSigType: 8,
|
||||||
appId: 1600001604,
|
appId: 1600001604,
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract upload(): Promise<void>;
|
abstract upload(): Promise<void>;
|
||||||
@@ -86,15 +95,18 @@ class HighwayTcpUploaderTransform extends stream.Transform {
|
|||||||
|
|
||||||
export class HighwayTcpUploader extends HighwayUploader {
|
export class HighwayTcpUploader extends HighwayUploader {
|
||||||
async upload(): Promise<void> {
|
async upload(): Promise<void> {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const { signal } = controller;
|
||||||
|
const upload = new Promise<void>((resolve, reject) => {
|
||||||
const highwayTransForm = new HighwayTcpUploaderTransform(this);
|
const highwayTransForm = new HighwayTcpUploaderTransform(this);
|
||||||
const upload = new Promise<void>((resolve, _) => {
|
|
||||||
const socket = net.connect(this.trans.port, this.trans.server, () => {
|
const socket = net.connect(this.trans.port, this.trans.server, () => {
|
||||||
this.trans.data.pipe(highwayTransForm).pipe(socket, { end: false });
|
this.trans.data.pipe(highwayTransForm).pipe(socket, { end: false });
|
||||||
})
|
});
|
||||||
const handleRspHeader = (header: Buffer) => {
|
const handleRspHeader = (header: Buffer) => {
|
||||||
const rsp = new NapProtoMsg(RespDataHighwayHead).decode(header);
|
const rsp = new NapProtoMsg(RespDataHighwayHead).decode(header);
|
||||||
if (rsp.errorCode !== 0) {
|
if (rsp.errorCode !== 0) {
|
||||||
this.logger.logWarn(`[Highway] tcpUpload failed (code: ${rsp.errorCode})`);
|
socket.end();
|
||||||
|
reject(new Error(`[Highway] tcpUpload failed (code=${rsp.errorCode})`));
|
||||||
}
|
}
|
||||||
const percent = ((Number(rsp.msgSegHead?.dataOffset) + Number(rsp.msgSegHead?.dataLength)) / Number(rsp.msgSegHead?.filesize)).toFixed(2);
|
const percent = ((Number(rsp.msgSegHead?.dataOffset) + Number(rsp.msgSegHead?.dataLength)) / Number(rsp.msgSegHead?.filesize)).toFixed(2);
|
||||||
this.logger.logDebug(`[Highway] tcpUpload ${rsp.errorCode} | ${percent} | ${Buffer.from(header).toString('hex')}`);
|
this.logger.logDebug(`[Highway] tcpUpload ${rsp.errorCode} | ${percent} | ${Buffer.from(header).toString('hex')}`);
|
||||||
@@ -105,62 +117,69 @@ export class HighwayTcpUploader extends HighwayUploader {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
socket.on('data', (chunk: Buffer) => {
|
socket.on('data', (chunk: Buffer) => {
|
||||||
try {
|
if (signal.aborted) {
|
||||||
|
socket.end();
|
||||||
|
reject(new Error('Upload aborted due to timeout'));
|
||||||
|
}
|
||||||
const [head, _] = Frame.unpack(chunk);
|
const [head, _] = Frame.unpack(chunk);
|
||||||
handleRspHeader(head);
|
handleRspHeader(head);
|
||||||
} catch (e) {
|
});
|
||||||
this.logger.logError(`[Highway] tcpUpload parse response error: ${e}`);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
socket.on('close', () => {
|
socket.on('close', () => {
|
||||||
this.logger.logDebug('[Highway] tcpUpload socket closed.');
|
this.logger.logDebug('[Highway] tcpUpload socket closed.');
|
||||||
resolve();
|
resolve();
|
||||||
})
|
});
|
||||||
socket.on('error', (err) => {
|
socket.on('error', (err) => {
|
||||||
this.logger.logError('[Highway] tcpUpload socket.on error:', err);
|
|
||||||
})
|
|
||||||
this.trans.data.on('error', (err) => {
|
|
||||||
this.logger.logError('[Highway] tcpUpload readable error:', err);
|
|
||||||
socket.end();
|
socket.end();
|
||||||
})
|
reject(new Error(`[Highway] tcpUpload socket.on error: ${err}`));
|
||||||
})
|
});
|
||||||
const timeout = new Promise<void>((_, reject) => {
|
this.trans.data.on('error', (err) => {
|
||||||
setTimeout(() => {
|
socket.end();
|
||||||
reject(new Error(`[Highway] tcpUpload timeout after ${this.trans.timeout}s`))
|
reject(new Error(`[Highway] tcpUpload readable error: ${err}`));
|
||||||
}, (this.trans.timeout ?? Infinity) * 1000
|
});
|
||||||
)
|
});
|
||||||
})
|
const timeout = this.timeout().catch((err) => {
|
||||||
|
controller.abort();
|
||||||
|
throw new Error(err.message);
|
||||||
|
});
|
||||||
await Promise.race([upload, timeout]);
|
await Promise.race([upload, timeout]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: timeout impl
|
|
||||||
export class HighwayHttpUploader extends HighwayUploader {
|
export class HighwayHttpUploader extends HighwayUploader {
|
||||||
async upload(): Promise<void> {
|
async upload(): Promise<void> {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const { signal } = controller;
|
||||||
|
const upload = (async () => {
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
for await (const chunk of this.trans.data) {
|
for await (const chunk of this.trans.data) {
|
||||||
let block = chunk as Buffer;
|
if (signal.aborted) {
|
||||||
|
throw new Error('Upload aborted due to timeout');
|
||||||
|
}
|
||||||
|
const block = chunk as Buffer;
|
||||||
try {
|
try {
|
||||||
await this.uploadBlock(block, offset);
|
await this.uploadBlock(block, offset);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.logger.logError(`[Highway] httpUpload Error uploading block at offset ${offset}: ${err}`);
|
throw new Error(`[Highway] httpUpload Error uploading block at offset ${offset}: ${err}`);
|
||||||
throw err;
|
|
||||||
}
|
}
|
||||||
offset += block.length;
|
offset += block.length;
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
|
const timeout = this.timeout().catch((err) => {
|
||||||
|
controller.abort();
|
||||||
|
throw new Error(err.message);
|
||||||
|
});
|
||||||
|
await Promise.race([upload, timeout]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async uploadBlock(block: Buffer, offset: number): Promise<void> {
|
private async uploadBlock(block: Buffer, offset: number): Promise<void> {
|
||||||
const chunkMD5 = crypto.createHash('md5').update(block).digest();
|
const chunkMD5 = crypto.createHash('md5').update(block).digest();
|
||||||
const payload = this.buildPicUpHead(offset, block.length, chunkMD5);
|
const payload = this.buildPicUpHead(offset, block.length, chunkMD5);
|
||||||
const frame = Frame.pack(Buffer.from(payload), block)
|
const frame = Frame.pack(Buffer.from(payload), block);
|
||||||
const resp = await this.httpPostHighwayContent(frame, `http://${this.trans.server}:${this.trans.port}/cgi-bin/httpconn?htcmd=0x6FF0087&uin=${this.trans.uin}`);
|
const resp = await this.httpPostHighwayContent(frame, `http://${this.trans.server}:${this.trans.port}/cgi-bin/httpconn?htcmd=0x6FF0087&uin=${this.trans.uin}`);
|
||||||
const [head, body] = Frame.unpack(resp);
|
const [head, body] = Frame.unpack(resp);
|
||||||
const headData = new NapProtoMsg(RespDataHighwayHead).decode(head);
|
const headData = new NapProtoMsg(RespDataHighwayHead).decode(head);
|
||||||
this.logger.logDebug(`[Highway] httpUploadBlock: ${headData.errorCode} | ${headData.msgSegHead?.retCode} | ${headData.bytesRspExtendInfo} | ${head.toString('hex')} | ${body.toString('hex')}`);
|
this.logger.logDebug(`[Highway] httpUploadBlock: ${headData.errorCode} | ${headData.msgSegHead?.retCode} | ${headData.bytesRspExtendInfo} | ${head.toString('hex')} | ${body.toString('hex')}`);
|
||||||
if (headData.errorCode !== 0) {
|
if (headData.errorCode !== 0) throw new Error(`[Highway] httpUploadBlock failed (code=${headData.errorCode})`);
|
||||||
this.logger.logError(`[Highway] httpUploadBlock failed (code=${headData.errorCode})`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async httpPostHighwayContent(frame: Buffer, serverURL: string): Promise<Buffer> {
|
private async httpPostHighwayContent(frame: Buffer, serverURL: string): Promise<Buffer> {
|
||||||
@@ -176,12 +195,12 @@ export class HighwayHttpUploader extends HighwayUploader {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
const req = http.request(serverURL, options, (res) => {
|
const req = http.request(serverURL, options, (res) => {
|
||||||
let data = Buffer.alloc(0);
|
const data: Buffer[] = [];
|
||||||
res.on('data', (chunk) => {
|
res.on('data', (chunk) => {
|
||||||
data = Buffer.concat([data, chunk]);
|
data.push(chunk);
|
||||||
});
|
});
|
||||||
res.on('end', () => {
|
res.on('end', () => {
|
||||||
resolve(data);
|
resolve(Buffer.concat(data));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
req.write(frame);
|
req.write(frame);
|
||||||
|
@@ -5,7 +5,7 @@ import {NTHighwayIPv4} from "@/core/packet/proto/highway/highway";
|
|||||||
export const int32ip2str = (ip: number) => {
|
export const int32ip2str = (ip: number) => {
|
||||||
ip = ip & 0xffffffff;
|
ip = ip & 0xffffffff;
|
||||||
return [ip & 0xff, (ip & 0xff00) >> 8, (ip & 0xff0000) >> 16, ((ip & 0xff000000) >> 24) & 0xff].join('.');
|
return [ip & 0xff, (ip & 0xff00) >> 8, (ip & 0xff0000) >> 16, ((ip & 0xff000000) >> 24) & 0xff].join('.');
|
||||||
}
|
};
|
||||||
|
|
||||||
export const oidbIpv4s2HighwayIpv4s = (ipv4s: NapProtoEncodeStructType<typeof IPv4>[]): NapProtoEncodeStructType<typeof NTHighwayIPv4>[] =>{
|
export const oidbIpv4s2HighwayIpv4s = (ipv4s: NapProtoEncodeStructType<typeof IPv4>[]): NapProtoEncodeStructType<typeof NTHighwayIPv4>[] =>{
|
||||||
return ipv4s.map((ip) => {
|
return ipv4s.map((ip) => {
|
||||||
@@ -15,6 +15,6 @@ export const oidbIpv4s2HighwayIpv4s = (ipv4s: NapProtoEncodeStructType<typeof IP
|
|||||||
ip: int32ip2str(ip.outIP!),
|
ip: int32ip2str(ip.outIP!),
|
||||||
},
|
},
|
||||||
port: ip.outPort!
|
port: ip.outPort!
|
||||||
} as NapProtoEncodeStructType<typeof NTHighwayIPv4>
|
} as NapProtoEncodeStructType<typeof NTHighwayIPv4>;
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
@@ -2,7 +2,9 @@ import * as crypto from "crypto";
|
|||||||
import { PushMsgBody } from "@/core/packet/proto/message/message";
|
import { PushMsgBody } from "@/core/packet/proto/message/message";
|
||||||
import { NapProtoEncodeStructType } from "@/core/packet/proto/NapProto";
|
import { NapProtoEncodeStructType } from "@/core/packet/proto/NapProto";
|
||||||
import { LogWrapper } from "@/common/log";
|
import { LogWrapper } from "@/common/log";
|
||||||
import {PacketMsg} from "@/core/packet/msg/message";
|
import { PacketMsg, PacketSendMsgElement } from "@/core/packet/message/message";
|
||||||
|
import { IPacketMsgElement, PacketMsgTextElement } from "@/core/packet/message/element";
|
||||||
|
import { SendTextElement } from "@/core";
|
||||||
|
|
||||||
export class PacketMsgBuilder {
|
export class PacketMsgBuilder {
|
||||||
private logger: LogWrapper;
|
private logger: LogWrapper;
|
||||||
@@ -11,10 +13,23 @@ export class PacketMsgBuilder {
|
|||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static failBackText = new PacketMsgTextElement(
|
||||||
|
{
|
||||||
|
textElement: { content: "[该消息类型暂不支持查看]" }!
|
||||||
|
} as SendTextElement
|
||||||
|
);
|
||||||
|
|
||||||
buildFakeMsg(selfUid: string, element: PacketMsg[]): NapProtoEncodeStructType<typeof PushMsgBody>[] {
|
buildFakeMsg(selfUid: string, element: PacketMsg[]): NapProtoEncodeStructType<typeof PushMsgBody>[] {
|
||||||
return element.map((node): NapProtoEncodeStructType<typeof PushMsgBody> => {
|
return element.map((node): NapProtoEncodeStructType<typeof PushMsgBody> => {
|
||||||
const avatar = `https://q.qlogo.cn/headimg_dl?dst_uin=${node.senderUin}&spec=640&img_type=jpg`;
|
const avatar = `https://q.qlogo.cn/headimg_dl?dst_uin=${node.senderUin}&spec=640&img_type=jpg`;
|
||||||
|
const msgContent = node.msg.reduceRight((acc: undefined | Uint8Array, msg: IPacketMsgElement<PacketSendMsgElement>) => {
|
||||||
|
return acc !== undefined ? acc : msg.buildContent();
|
||||||
|
}, undefined);
|
||||||
const msgElement = node.msg.flatMap(msg => msg.buildElement() ?? []);
|
const msgElement = node.msg.flatMap(msg => msg.buildElement() ?? []);
|
||||||
|
if (!msgContent && !msgElement.length) {
|
||||||
|
this.logger.logWarn(`[PacketMsgBuilder] buildFakeMsg: 空的msgContent和msgElement!`);
|
||||||
|
msgElement.push(PacketMsgBuilder.failBackText.buildElement());
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
responseHead: {
|
responseHead: {
|
||||||
fromUid: "",
|
fromUid: "",
|
||||||
@@ -35,7 +50,7 @@ export class PacketMsgBuilder {
|
|||||||
divSeq: node.groupId ? undefined : 4,
|
divSeq: node.groupId ? undefined : 4,
|
||||||
msgId: crypto.randomBytes(4).readUInt32LE(0),
|
msgId: crypto.randomBytes(4).readUInt32LE(0),
|
||||||
sequence: crypto.randomBytes(4).readUInt32LE(0),
|
sequence: crypto.randomBytes(4).readUInt32LE(0),
|
||||||
timeStamp: Math.floor(Date.now() / 1000),
|
timeStamp: +node.time.toString().substring(0, 10),
|
||||||
field7: BigInt(1),
|
field7: BigInt(1),
|
||||||
field8: 0,
|
field8: 0,
|
||||||
field9: 0,
|
field9: 0,
|
||||||
@@ -50,7 +65,8 @@ export class PacketMsgBuilder {
|
|||||||
body: {
|
body: {
|
||||||
richText: {
|
richText: {
|
||||||
elems: msgElement
|
elems: msgElement
|
||||||
}
|
},
|
||||||
|
msgContent: msgContent,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
163
src/core/packet/message/converter.ts
Normal file
163
src/core/packet/message/converter.ts
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
import {
|
||||||
|
Peer,
|
||||||
|
ChatType,
|
||||||
|
ElementType,
|
||||||
|
MessageElement,
|
||||||
|
RawMessage,
|
||||||
|
SendArkElement,
|
||||||
|
SendFaceElement,
|
||||||
|
SendFileElement,
|
||||||
|
SendMarkdownElement,
|
||||||
|
SendMarketFaceElement,
|
||||||
|
SendPicElement,
|
||||||
|
SendPttElement,
|
||||||
|
SendReplyElement,
|
||||||
|
SendStructLongMsgElement,
|
||||||
|
SendTextElement,
|
||||||
|
SendVideoElement
|
||||||
|
} from "@/core";
|
||||||
|
import {
|
||||||
|
IPacketMsgElement,
|
||||||
|
PacketMsgAtElement,
|
||||||
|
PacketMsgFaceElement,
|
||||||
|
PacketMsgFileElement,
|
||||||
|
PacketMsgLightAppElement,
|
||||||
|
PacketMsgMarkDownElement,
|
||||||
|
PacketMsgMarkFaceElement,
|
||||||
|
PacketMsgPicElement,
|
||||||
|
PacketMsgPttElement,
|
||||||
|
PacketMsgReplyElement,
|
||||||
|
PacketMsgTextElement,
|
||||||
|
PacketMsgVideoElement,
|
||||||
|
PacketMultiMsgElement
|
||||||
|
} from "@/core/packet/message/element";
|
||||||
|
import { PacketMsg, PacketSendMsgElement } from "@/core/packet/message/message";
|
||||||
|
import { LogWrapper } from "@/common/log";
|
||||||
|
|
||||||
|
const SupportedElementTypes = [
|
||||||
|
ElementType.TEXT,
|
||||||
|
ElementType.PIC,
|
||||||
|
ElementType.REPLY,
|
||||||
|
ElementType.FACE,
|
||||||
|
ElementType.MFACE,
|
||||||
|
ElementType.VIDEO,
|
||||||
|
ElementType.FILE,
|
||||||
|
ElementType.PTT,
|
||||||
|
ElementType.ARK,
|
||||||
|
ElementType.MARKDOWN,
|
||||||
|
ElementType.STRUCTLONGMSG
|
||||||
|
];
|
||||||
|
|
||||||
|
type SendMessageTypeElementMap = {
|
||||||
|
[ElementType.TEXT]: SendTextElement,
|
||||||
|
[ElementType.PIC]: SendPicElement,
|
||||||
|
[ElementType.FILE]: SendFileElement,
|
||||||
|
[ElementType.PTT]: SendPttElement,
|
||||||
|
[ElementType.VIDEO]: SendVideoElement,
|
||||||
|
[ElementType.FACE]: SendFaceElement,
|
||||||
|
[ElementType.REPLY]: SendReplyElement,
|
||||||
|
[ElementType.ARK]: SendArkElement,
|
||||||
|
[ElementType.MFACE]: SendMarketFaceElement,
|
||||||
|
[ElementType.STRUCTLONGMSG]: SendStructLongMsgElement,
|
||||||
|
[ElementType.MARKDOWN]: SendMarkdownElement,
|
||||||
|
};
|
||||||
|
|
||||||
|
type ElementToPacketMsgConverters = {
|
||||||
|
[K in keyof SendMessageTypeElementMap]: (
|
||||||
|
sendElement: MessageElement
|
||||||
|
) => IPacketMsgElement<SendMessageTypeElementMap[K]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type rawMsgWithSendMsg = {
|
||||||
|
senderUin: number;
|
||||||
|
senderUid?: string;
|
||||||
|
senderName: string;
|
||||||
|
groupId?: number;
|
||||||
|
time: number;
|
||||||
|
msg: PacketSendMsgElement[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PacketMsgConverter {
|
||||||
|
private logger: LogWrapper;
|
||||||
|
|
||||||
|
constructor(logger: LogWrapper) {
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private isValidElementType(type: ElementType): type is keyof ElementToPacketMsgConverters {
|
||||||
|
return SupportedElementTypes.includes(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
rawMsgWithSendMsgToPacketMsg(msg: rawMsgWithSendMsg): PacketMsg {
|
||||||
|
return {
|
||||||
|
senderUid: msg.senderUid ?? '',
|
||||||
|
senderUin: msg.senderUin,
|
||||||
|
senderName: msg.senderName,
|
||||||
|
groupId: msg.groupId,
|
||||||
|
time: msg.time,
|
||||||
|
msg: msg.msg.map((element) => {
|
||||||
|
if (!this.isValidElementType(element.elementType)) return null;
|
||||||
|
return this.rawToPacketMsgConverters[element.elementType](element as MessageElement);
|
||||||
|
}).filter((e) => e !== null)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
rawMsgToPacketMsg(msg: RawMessage, ctxPeer: Peer): PacketMsg {
|
||||||
|
return {
|
||||||
|
seq: +msg.msgSeq,
|
||||||
|
groupId: ctxPeer.chatType === ChatType.KCHATTYPEGROUP ? +msg.peerUid : undefined,
|
||||||
|
senderUid: msg.senderUid,
|
||||||
|
senderUin: +msg.senderUin,
|
||||||
|
senderName: msg.sendMemberName && msg.sendMemberName !== ''
|
||||||
|
? msg.sendMemberName
|
||||||
|
: msg.sendNickName && msg.sendNickName !== ''
|
||||||
|
? msg.sendNickName
|
||||||
|
: "QQ用户",
|
||||||
|
time: +msg.msgTime,
|
||||||
|
msg: msg.elements.map((element) => {
|
||||||
|
if (!this.isValidElementType(element.elementType)) return null;
|
||||||
|
return this.rawToPacketMsgConverters[element.elementType](element);
|
||||||
|
}).filter((e) => e !== null)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private rawToPacketMsgConverters: ElementToPacketMsgConverters = {
|
||||||
|
[ElementType.TEXT]: (element) => {
|
||||||
|
if (element.textElement?.atType) {
|
||||||
|
return new PacketMsgAtElement(element as SendTextElement);
|
||||||
|
}
|
||||||
|
return new PacketMsgTextElement(element as SendTextElement);
|
||||||
|
},
|
||||||
|
[ElementType.PIC]: (element) => {
|
||||||
|
return new PacketMsgPicElement(element as SendPicElement);
|
||||||
|
},
|
||||||
|
[ElementType.REPLY]: (element) => {
|
||||||
|
return new PacketMsgReplyElement(element as SendReplyElement);
|
||||||
|
},
|
||||||
|
[ElementType.FACE]: (element) => {
|
||||||
|
return new PacketMsgFaceElement(element as SendFaceElement);
|
||||||
|
},
|
||||||
|
[ElementType.MFACE]: (element) => {
|
||||||
|
return new PacketMsgMarkFaceElement(element as SendMarketFaceElement);
|
||||||
|
},
|
||||||
|
[ElementType.VIDEO]: (element) => {
|
||||||
|
return new PacketMsgVideoElement(element as SendVideoElement);
|
||||||
|
},
|
||||||
|
[ElementType.FILE]: (element) => {
|
||||||
|
return new PacketMsgFileElement(element as SendFileElement);
|
||||||
|
},
|
||||||
|
[ElementType.PTT]: (element) => {
|
||||||
|
return new PacketMsgPttElement(element as SendPttElement);
|
||||||
|
},
|
||||||
|
[ElementType.ARK]: (element) => {
|
||||||
|
return new PacketMsgLightAppElement(element as SendArkElement);
|
||||||
|
},
|
||||||
|
[ElementType.MARKDOWN]: (element) => {
|
||||||
|
return new PacketMsgMarkDownElement(element as SendMarkdownElement);
|
||||||
|
},
|
||||||
|
// TODO: check this logic, move it in arkElement?
|
||||||
|
[ElementType.STRUCTLONGMSG]: (element) => {
|
||||||
|
return new PacketMultiMsgElement(element as SendStructLongMsgElement);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@@ -1,6 +1,4 @@
|
|||||||
import assert from "node:assert";
|
|
||||||
import * as zlib from "node:zlib";
|
import * as zlib from "node:zlib";
|
||||||
import * as crypto from "node:crypto";
|
|
||||||
import { NapProtoEncodeStructType, NapProtoMsg } from "@/core/packet/proto/NapProto";
|
import { NapProtoEncodeStructType, NapProtoMsg } from "@/core/packet/proto/NapProto";
|
||||||
import {
|
import {
|
||||||
CustomFace,
|
CustomFace,
|
||||||
@@ -27,26 +25,31 @@ import {
|
|||||||
SendVideoElement
|
SendVideoElement
|
||||||
} from "@/core";
|
} from "@/core";
|
||||||
import { MsgInfo } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
|
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";
|
||||||
|
|
||||||
// raw <-> packet
|
// raw <-> packet
|
||||||
// TODO: check ob11 -> raw impl!
|
|
||||||
// TODO: parse to raw element
|
|
||||||
// TODO: SendStructLongMsgElement
|
// TODO: SendStructLongMsgElement
|
||||||
export abstract class IPacketMsgElement<T extends PacketSendMsgElement> {
|
export abstract class IPacketMsgElement<T extends PacketSendMsgElement> {
|
||||||
protected constructor(rawElement: T) {
|
protected constructor(rawElement: T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get valid(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
buildContent(): Uint8Array | undefined {
|
buildContent(): Uint8Array | undefined {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
buildElement(): NapProtoEncodeStructType<typeof Elem>[] | undefined {
|
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
|
||||||
return undefined;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
toPreview(): string {
|
toPreview(): string {
|
||||||
return '[nya~]';
|
return '[暂不支持该消息类型喵~]';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,50 +98,6 @@ export class PacketMsgAtElement extends PacketMsgTextElement {
|
|||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
toPreview(): string {
|
|
||||||
return `@${this.targetUid} ${this.text}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PacketMsgPicElement extends IPacketMsgElement<SendPicElement> {
|
|
||||||
path: string;
|
|
||||||
name: string
|
|
||||||
size: number;
|
|
||||||
md5: string;
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
picType: PicType;
|
|
||||||
sha1: string | null = null;
|
|
||||||
msgInfo: NapProtoEncodeStructType<typeof MsgInfo> | null = null;
|
|
||||||
groupPicExt: NapProtoEncodeStructType<typeof CustomFace> | null = null;
|
|
||||||
c2cPicExt: NapProtoEncodeStructType<typeof NotOnlineImage> | null = null;
|
|
||||||
|
|
||||||
constructor(element: SendPicElement) {
|
|
||||||
super(element);
|
|
||||||
this.path = element.picElement.sourcePath;
|
|
||||||
this.name = element.picElement.fileName;
|
|
||||||
this.size = Number(element.picElement.fileSize);
|
|
||||||
this.md5 = element.picElement.md5HexStr ?? '';
|
|
||||||
this.width = element.picElement.picWidth;
|
|
||||||
this.height = element.picElement.picHeight;
|
|
||||||
this.picType = element.picElement.picType;
|
|
||||||
}
|
|
||||||
|
|
||||||
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
|
|
||||||
assert(this.msgInfo !== null, 'msgInfo is null, expected not null');
|
|
||||||
return [{
|
|
||||||
commonElem: {
|
|
||||||
serviceType: 48,
|
|
||||||
pbElem: new NapProtoMsg(MsgInfo).encode(this.msgInfo),
|
|
||||||
businessType: 10,
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
|
|
||||||
toPreview(): string {
|
|
||||||
return "[图片]";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PacketMsgReplyElement extends IPacketMsgElement<SendReplyElement> {
|
export class PacketMsgReplyElement extends IPacketMsgElement<SendReplyElement> {
|
||||||
@@ -153,11 +112,11 @@ export class PacketMsgReplyElement extends IPacketMsgElement<SendReplyElement> {
|
|||||||
constructor(element: SendReplyElement) {
|
constructor(element: SendReplyElement) {
|
||||||
super(element);
|
super(element);
|
||||||
this.messageId = BigInt(element.replyElement.replayMsgId ?? 0);
|
this.messageId = BigInt(element.replyElement.replayMsgId ?? 0);
|
||||||
this.messageSeq = Number(element.replyElement.replayMsgSeq ?? 0);
|
this.messageSeq = +(element.replyElement.replayMsgSeq ?? 0);
|
||||||
this.messageClientSeq = Number(element.replyElement.replyMsgClientSeq ?? 0);
|
this.messageClientSeq = +(element.replyElement.replyMsgClientSeq ?? 0);
|
||||||
this.targetUin = Number(element.replyElement.senderUin ?? 0);
|
this.targetUin = +(element.replyElement.senderUin ?? 0);
|
||||||
this.targetUid = element.replyElement.senderUidStr ?? '';
|
this.targetUid = element.replyElement.senderUidStr ?? '';
|
||||||
this.time = Number(element.replyElement.replyMsgTime ?? 0);
|
this.time = +(element.replyElement.replyMsgTime ?? 0);
|
||||||
this.elems = []; // TODO: in replyElement.sourceMsgTextElems
|
this.elems = []; // TODO: in replyElement.sourceMsgTextElems
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,11 +146,11 @@ export class PacketMsgReplyElement extends IPacketMsgElement<SendReplyElement> {
|
|||||||
uid: String(this.targetUid),
|
uid: String(this.targetUid),
|
||||||
}),
|
}),
|
||||||
} : undefined,
|
} : undefined,
|
||||||
}]
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
toPreview(): string {
|
toPreview(): string {
|
||||||
return "[回复]";
|
return "[回复消息]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,7 +180,7 @@ export class PacketMsgFaceElement extends IPacketMsgElement<SendFaceElement> {
|
|||||||
}),
|
}),
|
||||||
businessType: 1
|
businessType: 1
|
||||||
}
|
}
|
||||||
}]
|
}];
|
||||||
} else if (this.faceId < 260) {
|
} else if (this.faceId < 260) {
|
||||||
return [{
|
return [{
|
||||||
face: {
|
face: {
|
||||||
@@ -239,7 +198,7 @@ export class PacketMsgFaceElement extends IPacketMsgElement<SendFaceElement> {
|
|||||||
}),
|
}),
|
||||||
businessType: 1
|
businessType: 1
|
||||||
}
|
}
|
||||||
}]
|
}];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,29 +237,224 @@ export class PacketMsgMarkFaceElement extends IPacketMsgElement<SendMarketFaceEl
|
|||||||
field8: 1
|
field8: 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}]
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
toPreview(): string {
|
toPreview(): string {
|
||||||
return this.emojiName;
|
return `[${this.emojiName}]`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PacketMsgPicElement extends IPacketMsgElement<SendPicElement> {
|
||||||
|
path: string;
|
||||||
|
name: string;
|
||||||
|
size: number;
|
||||||
|
md5: string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
picType: PicType;
|
||||||
|
sha1: string | null = null;
|
||||||
|
msgInfo: NapProtoEncodeStructType<typeof MsgInfo> | null = null;
|
||||||
|
groupPicExt: NapProtoEncodeStructType<typeof CustomFace> | null = null;
|
||||||
|
c2cPicExt: NapProtoEncodeStructType<typeof NotOnlineImage> | null = null;
|
||||||
|
|
||||||
|
constructor(element: SendPicElement) {
|
||||||
|
super(element);
|
||||||
|
this.path = element.picElement.sourcePath;
|
||||||
|
this.name = element.picElement.fileName;
|
||||||
|
this.size = +element.picElement.fileSize;
|
||||||
|
this.md5 = element.picElement.md5HexStr ?? '';
|
||||||
|
this.width = element.picElement.picWidth;
|
||||||
|
this.height = element.picElement.picHeight;
|
||||||
|
this.picType = element.picElement.picType;
|
||||||
|
}
|
||||||
|
|
||||||
|
get valid(): boolean {
|
||||||
|
return !!this.msgInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
|
||||||
|
if (!this.msgInfo) return [];
|
||||||
|
return [{
|
||||||
|
commonElem: {
|
||||||
|
serviceType: 48,
|
||||||
|
pbElem: new NapProtoMsg(MsgInfo).encode(this.msgInfo),
|
||||||
|
businessType: 10,
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
toPreview(): string {
|
||||||
|
return "[图片]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PacketMsgVideoElement extends IPacketMsgElement<SendVideoElement> {
|
export class PacketMsgVideoElement extends IPacketMsgElement<SendVideoElement> {
|
||||||
|
fileSize?: string;
|
||||||
|
filePath?: string;
|
||||||
|
thumbSize?: number;
|
||||||
|
thumbPath?: string;
|
||||||
|
fileMd5?: string;
|
||||||
|
fileSha1?: string;
|
||||||
|
thumbMd5?: string;
|
||||||
|
thumbSha1?: string;
|
||||||
|
thumbWidth?: number;
|
||||||
|
thumbHeight?: number;
|
||||||
|
msgInfo: NapProtoEncodeStructType<typeof MsgInfo> | null = null;
|
||||||
|
|
||||||
constructor(element: SendVideoElement) {
|
constructor(element: SendVideoElement) {
|
||||||
super(element);
|
super(element);
|
||||||
}
|
this.fileSize = element.videoElement.fileSize;
|
||||||
|
this.filePath = element.videoElement.filePath;
|
||||||
|
this.thumbSize = element.videoElement.thumbSize;
|
||||||
|
this.thumbPath = element.videoElement.thumbPath?.get(0);
|
||||||
|
this.fileMd5 = element.videoElement.videoMd5;
|
||||||
|
this.thumbMd5 = element.videoElement.thumbMd5;
|
||||||
|
this.thumbWidth = element.videoElement.thumbWidth;
|
||||||
|
this.thumbHeight = element.videoElement.thumbHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PacketMsgFileElement extends IPacketMsgElement<SendFileElement> {
|
get valid(): boolean {
|
||||||
constructor(element: SendFileElement) {
|
return !!this.msgInfo;
|
||||||
super(element);
|
}
|
||||||
|
|
||||||
|
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
|
||||||
|
if (!this.msgInfo) return [];
|
||||||
|
return [{
|
||||||
|
commonElem: {
|
||||||
|
serviceType: 48,
|
||||||
|
pbElem: new NapProtoMsg(MsgInfo).encode(this.msgInfo),
|
||||||
|
businessType: 21,
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
toPreview(): string {
|
||||||
|
return "[视频]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PacketMsgPttElement extends IPacketMsgElement<SendPttElement> {
|
export class PacketMsgPttElement extends IPacketMsgElement<SendPttElement> {
|
||||||
|
filePath: string;
|
||||||
|
fileSize: number;
|
||||||
|
fileMd5: string;
|
||||||
|
fileSha1?: string;
|
||||||
|
fileDuration: number;
|
||||||
|
msgInfo: NapProtoEncodeStructType<typeof MsgInfo> | null = null;
|
||||||
|
|
||||||
constructor(element: SendPttElement) {
|
constructor(element: SendPttElement) {
|
||||||
super(element);
|
super(element);
|
||||||
|
this.filePath = element.pttElement.filePath;
|
||||||
|
this.fileSize = +element.pttElement.fileSize; // TODO: cc
|
||||||
|
this.fileMd5 = element.pttElement.md5HexStr;
|
||||||
|
this.fileDuration = Math.round(element.pttElement.duration); // TODO: cc
|
||||||
|
}
|
||||||
|
|
||||||
|
get valid(): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
|
||||||
|
return [];
|
||||||
|
// if (!this.msgInfo) return [];
|
||||||
|
// return [{
|
||||||
|
// commonElem: {
|
||||||
|
// serviceType: 48,
|
||||||
|
// pbElem: new NapProtoMsg(MsgInfo).encode(this.msgInfo),
|
||||||
|
// businessType: 22,
|
||||||
|
// }
|
||||||
|
// }];
|
||||||
|
}
|
||||||
|
|
||||||
|
toPreview(): string {
|
||||||
|
return "[语音]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PacketMsgFileElement extends IPacketMsgElement<SendFileElement> {
|
||||||
|
fileName: string;
|
||||||
|
filePath: string;
|
||||||
|
fileSize: number;
|
||||||
|
fileSha1?: Uint8Array;
|
||||||
|
fileMd5?: Uint8Array;
|
||||||
|
fileUuid?: string;
|
||||||
|
fileHash?: string;
|
||||||
|
isGroupFile?: boolean;
|
||||||
|
_private_send_uid?: string;
|
||||||
|
_private_recv_uid?: string;
|
||||||
|
_e37_800_rsp?: NapProtoEncodeStructType<typeof OidbSvcTrpcTcp0XE37_800Response>;
|
||||||
|
|
||||||
|
constructor(element: SendFileElement) {
|
||||||
|
super(element);
|
||||||
|
this.fileName = element.fileElement.fileName;
|
||||||
|
this.filePath = element.fileElement.filePath;
|
||||||
|
this.fileSize = +element.fileElement.fileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
get valid(): boolean {
|
||||||
|
return this.isGroupFile || Boolean(this._e37_800_rsp);
|
||||||
|
}
|
||||||
|
|
||||||
|
buildContent(): Uint8Array | undefined {
|
||||||
|
if (this.isGroupFile || !this._e37_800_rsp) return undefined;
|
||||||
|
return new NapProtoMsg(FileExtra).encode({
|
||||||
|
file: {
|
||||||
|
fileType: 0,
|
||||||
|
fileUuid: this.fileUuid,
|
||||||
|
fileMd5: this.fileMd5,
|
||||||
|
fileName: this.fileName,
|
||||||
|
fileSize: BigInt(this.fileSize),
|
||||||
|
subcmd: 1,
|
||||||
|
dangerEvel: 0,
|
||||||
|
expireTime: Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60,
|
||||||
|
fileHash: this.fileHash,
|
||||||
|
},
|
||||||
|
field6: {
|
||||||
|
field2: {
|
||||||
|
field1: this._e37_800_rsp?.body?.field30?.field110,
|
||||||
|
fileUuid: this.fileUuid,
|
||||||
|
fileName: this.fileName,
|
||||||
|
field6: this._e37_800_rsp?.body?.field30?.field3,
|
||||||
|
field7: this._e37_800_rsp?.body?.field30?.field101,
|
||||||
|
field8: this._e37_800_rsp?.body?.field30?.field100,
|
||||||
|
timestamp1: this._e37_800_rsp?.body?.field30?.timestamp1,
|
||||||
|
fileHash: this.fileHash,
|
||||||
|
selfUid: this._private_send_uid,
|
||||||
|
destUid: this._private_recv_uid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
|
||||||
|
if (!this.isGroupFile) return [];
|
||||||
|
const lb = Buffer.alloc(2);
|
||||||
|
const transElemVal = new NapProtoMsg(GroupFileExtra).encode({
|
||||||
|
field1: 6,
|
||||||
|
fileName: this.fileName,
|
||||||
|
inner: {
|
||||||
|
info: {
|
||||||
|
busId: 102,
|
||||||
|
fileId: this.fileUuid,
|
||||||
|
fileSize: BigInt(this.fileSize),
|
||||||
|
fileName: this.fileName,
|
||||||
|
fileSha: this.fileSha1,
|
||||||
|
extInfoString: "",
|
||||||
|
fileMd5: this.fileMd5,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
lb.writeUInt16BE(transElemVal.length);
|
||||||
|
return [{
|
||||||
|
transElem: {
|
||||||
|
elemType: 24,
|
||||||
|
elemValue: Buffer.concat([Buffer.from([0x01]), lb, transElemVal]) // TLV
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
toPreview(): string {
|
||||||
|
return `[文件]${this.fileName}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,11 +474,11 @@ export class PacketMsgLightAppElement extends IPacketMsgElement<SendArkElement>
|
|||||||
zlib.deflateSync(Buffer.from(this.payload, 'utf-8'))
|
zlib.deflateSync(Buffer.from(this.payload, 'utf-8'))
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}]
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
toPreview(): string {
|
toPreview(): string {
|
||||||
return "[小程序]";
|
return "[卡片消息]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,11 +499,11 @@ export class PacketMsgMarkDownElement extends IPacketMsgElement<SendMarkdownElem
|
|||||||
}),
|
}),
|
||||||
businessType: 1
|
businessType: 1
|
||||||
}
|
}
|
||||||
}]
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
toPreview(): string {
|
toPreview(): string {
|
||||||
return this.content;
|
return `[Markdown消息 ${this.content}]`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,58 +517,15 @@ export class PacketMultiMsgElement extends IPacketMsgElement<SendStructLongMsgEl
|
|||||||
this.message = message ?? [];
|
this.message = message ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
get isGroupMsg(): boolean {
|
|
||||||
return this.message.some(msg => msg.groupId !== undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
get JSON() {
|
|
||||||
const id = crypto.randomUUID();
|
|
||||||
return {
|
|
||||||
app: "com.tencent.multimsg",
|
|
||||||
config: {
|
|
||||||
autosize: 1,
|
|
||||||
forward: 1,
|
|
||||||
round: 1,
|
|
||||||
type: "normal",
|
|
||||||
width: 300
|
|
||||||
},
|
|
||||||
desc: "[聊天记录]",
|
|
||||||
extra: {
|
|
||||||
filename: id,
|
|
||||||
tsum: this.message.length,
|
|
||||||
},
|
|
||||||
meta: {
|
|
||||||
detail: {
|
|
||||||
news: this.message.length === 0 ? [{
|
|
||||||
text: "[Nya~ This message is send from NapCat.Packet!]",
|
|
||||||
}] : this.message.map(packetMsg => ({
|
|
||||||
text: `${packetMsg.senderName}: ${packetMsg.msg.map(msg => msg.toPreview()).join('')}`,
|
|
||||||
})),
|
|
||||||
resid: this.resid,
|
|
||||||
source: this.isGroupMsg ? "群聊的聊天记录" :
|
|
||||||
this.message.length
|
|
||||||
? Array.from(new Set(this.message.map(msg => msg.senderName)))
|
|
||||||
.join('和') + '的聊天记录'
|
|
||||||
: '聊天记录',
|
|
||||||
summary: `查看${this.message.length}条转发消息`,
|
|
||||||
uniseq: id,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
prompt: "[聊天记录]",
|
|
||||||
ver: "0.0.0.5",
|
|
||||||
view: "contact",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
|
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
|
||||||
return [{
|
return [{
|
||||||
lightAppElem: {
|
lightAppElem: {
|
||||||
data: Buffer.concat([
|
data: Buffer.concat([
|
||||||
Buffer.from([0x01]),
|
Buffer.from([0x01]),
|
||||||
zlib.deflateSync(Buffer.from(JSON.stringify(this.JSON), 'utf-8'))
|
zlib.deflateSync(Buffer.from(JSON.stringify(ForwardMsgBuilder.fromPacketMsg(this.resid, this.message)), 'utf-8'))
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}]
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
toPreview(): string {
|
toPreview(): string {
|
@@ -1,4 +1,4 @@
|
|||||||
import {IPacketMsgElement} from "@/core/packet/msg/element";
|
import { IPacketMsgElement } from "@/core/packet/message/element";
|
||||||
import { SendMessageElement, SendStructLongMsgElement } from "@/core";
|
import { SendMessageElement, SendStructLongMsgElement } from "@/core";
|
||||||
|
|
||||||
export type PacketSendMsgElement = SendMessageElement | SendStructLongMsgElement
|
export type PacketSendMsgElement = SendMessageElement | SendStructLongMsgElement
|
@@ -1,131 +0,0 @@
|
|||||||
import {
|
|
||||||
MessageElement,
|
|
||||||
RawMessage,
|
|
||||||
SendArkElement,
|
|
||||||
SendFaceElement,
|
|
||||||
SendFileElement,
|
|
||||||
SendMarkdownElement,
|
|
||||||
SendMarketFaceElement,
|
|
||||||
SendPicElement,
|
|
||||||
SendPttElement,
|
|
||||||
SendReplyElement,
|
|
||||||
SendStructLongMsgElement,
|
|
||||||
SendTextElement,
|
|
||||||
SendVideoElement
|
|
||||||
} from "@/core";
|
|
||||||
import {
|
|
||||||
IPacketMsgElement,
|
|
||||||
PacketMsgAtElement,
|
|
||||||
PacketMsgFaceElement,
|
|
||||||
PacketMsgFileElement,
|
|
||||||
PacketMsgLightAppElement,
|
|
||||||
PacketMsgMarkDownElement,
|
|
||||||
PacketMsgMarkFaceElement,
|
|
||||||
PacketMsgPicElement,
|
|
||||||
PacketMsgPttElement,
|
|
||||||
PacketMsgReplyElement,
|
|
||||||
PacketMsgTextElement,
|
|
||||||
PacketMsgVideoElement,
|
|
||||||
PacketMultiMsgElement
|
|
||||||
} from "@/core/packet/msg/element";
|
|
||||||
import {PacketMsg, PacketSendMsgElement} from "@/core/packet/msg/message";
|
|
||||||
import {LogWrapper} from "@/common/log";
|
|
||||||
|
|
||||||
type SendMessageElementMap = {
|
|
||||||
textElement: SendTextElement,
|
|
||||||
picElement: SendPicElement,
|
|
||||||
replyElement: SendReplyElement,
|
|
||||||
faceElement: SendFaceElement,
|
|
||||||
marketFaceElement: SendMarketFaceElement,
|
|
||||||
videoElement: SendVideoElement,
|
|
||||||
fileElement: SendFileElement,
|
|
||||||
pttElement: SendPttElement,
|
|
||||||
arkElement: SendArkElement,
|
|
||||||
markdownElement: SendMarkdownElement,
|
|
||||||
structLongMsgElement: SendStructLongMsgElement
|
|
||||||
};
|
|
||||||
|
|
||||||
type RawToPacketMsgConverters = {
|
|
||||||
[K in keyof SendMessageElementMap]: (
|
|
||||||
element: SendMessageElementMap[K],
|
|
||||||
msg?: RawMessage,
|
|
||||||
elementWrapper?: MessageElement,
|
|
||||||
) => IPacketMsgElement<SendMessageElementMap[K]> | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type rawMsgWithSendMsg = {
|
|
||||||
senderUin: number;
|
|
||||||
senderUid?: string;
|
|
||||||
senderName: string;
|
|
||||||
groupId?: number;
|
|
||||||
time: number;
|
|
||||||
msg: PacketSendMsgElement[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PacketMsgConverter {
|
|
||||||
private logger: LogWrapper;
|
|
||||||
|
|
||||||
constructor(logger: LogWrapper) {
|
|
||||||
this.logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
rawMsgWithSendMsgToPacketMsg(msg: rawMsgWithSendMsg): PacketMsg {
|
|
||||||
return {
|
|
||||||
senderUid: msg.senderUid ?? '',
|
|
||||||
senderUin: msg.senderUin,
|
|
||||||
senderName: msg.senderName,
|
|
||||||
groupId: msg.groupId,
|
|
||||||
time: msg.time,
|
|
||||||
msg: msg.msg.map((element) => {
|
|
||||||
const key = (Object.keys(this.rawToPacketMsgConverters) as Array<keyof SendMessageElementMap>).find(
|
|
||||||
(k) => (element as any)[k] !== undefined // TODO:
|
|
||||||
);
|
|
||||||
if (key) {
|
|
||||||
const elementData = (element as any)[key]; // TODO:
|
|
||||||
if (elementData) return this.rawToPacketMsgConverters[key](element as any)
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}).filter((e) => e !== null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private rawToPacketMsgConverters: RawToPacketMsgConverters = {
|
|
||||||
textElement: (element: SendTextElement) => {
|
|
||||||
if (element.textElement.atType) {
|
|
||||||
return new PacketMsgAtElement(element)
|
|
||||||
}
|
|
||||||
return new PacketMsgTextElement(element)
|
|
||||||
},
|
|
||||||
picElement: (element: SendPicElement) => {
|
|
||||||
return new PacketMsgPicElement(element)
|
|
||||||
},
|
|
||||||
replyElement: (element: SendReplyElement) => {
|
|
||||||
return new PacketMsgReplyElement(element)
|
|
||||||
},
|
|
||||||
faceElement: (element: SendFaceElement) => {
|
|
||||||
return new PacketMsgFaceElement(element)
|
|
||||||
},
|
|
||||||
marketFaceElement: (element: SendMarketFaceElement) => {
|
|
||||||
return new PacketMsgMarkFaceElement(element)
|
|
||||||
},
|
|
||||||
videoElement: (element: SendVideoElement) => {
|
|
||||||
return new PacketMsgVideoElement(element)
|
|
||||||
},
|
|
||||||
fileElement: (element: SendFileElement) => {
|
|
||||||
return new PacketMsgFileElement(element)
|
|
||||||
},
|
|
||||||
pttElement: (element: SendPttElement) => {
|
|
||||||
return new PacketMsgPttElement(element)
|
|
||||||
},
|
|
||||||
arkElement: (element: SendArkElement) => {
|
|
||||||
return new PacketMsgLightAppElement(element)
|
|
||||||
},
|
|
||||||
markdownElement: (element: SendMarkdownElement) => {
|
|
||||||
return new PacketMsgMarkDownElement(element)
|
|
||||||
},
|
|
||||||
// TODO: check this logic, move it in arkElement?
|
|
||||||
structLongMsgElement: (element: SendStructLongMsgElement) => {
|
|
||||||
return new PacketMultiMsgElement(element)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,6 +1,6 @@
|
|||||||
import * as zlib from "node:zlib";
|
import * as zlib from "node:zlib";
|
||||||
import * as crypto from "node:crypto";
|
import * as crypto from "node:crypto";
|
||||||
import {calculateSha1} from "@/core/packet/utils/crypto/hash"
|
import { computeMd5AndLengthWithLimit } from "@/core/packet/utils/crypto/hash";
|
||||||
import { NapProtoMsg } from "@/core/packet/proto/NapProto";
|
import { NapProtoMsg } from "@/core/packet/proto/NapProto";
|
||||||
import { OidbSvcTrpcTcpBase } from "@/core/packet/proto/oidb/OidbBase";
|
import { OidbSvcTrpcTcpBase } from "@/core/packet/proto/oidb/OidbBase";
|
||||||
import { OidbSvcTrpcTcp0X9067_202 } from "@/core/packet/proto/oidb/Oidb.0x9067_202";
|
import { OidbSvcTrpcTcp0X9067_202 } from "@/core/packet/proto/oidb/Oidb.0x9067_202";
|
||||||
@@ -10,17 +10,32 @@ import {OidbSvcTrpcTcp0XED3_1} from "@/core/packet/proto/oidb/Oidb.0xED3_1";
|
|||||||
import { NTV2RichMediaReq } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
|
import { NTV2RichMediaReq } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
|
||||||
import { HttpConn0x6ff_501 } from "@/core/packet/proto/action/action";
|
import { HttpConn0x6ff_501 } from "@/core/packet/proto/action/action";
|
||||||
import { LongMsgResult, SendLongMsgReq } from "@/core/packet/proto/message/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 {PacketMsgPicElement} from "@/core/packet/msg/element";
|
import {
|
||||||
|
PacketMsgFileElement,
|
||||||
|
PacketMsgPicElement,
|
||||||
|
PacketMsgPttElement,
|
||||||
|
PacketMsgVideoElement
|
||||||
|
} from "@/core/packet/message/element";
|
||||||
import { LogWrapper } from "@/common/log";
|
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 { OidbSvcTrpcTcp0x6D6 } from "@/core/packet/proto/oidb/Oidb.0x6D6";
|
||||||
import { OidbSvcTrpcTcp0XE37_1200 } from "@/core/packet/proto/oidb/Oidb.0xE37_1200";
|
import { OidbSvcTrpcTcp0XE37_1200 } from "@/core/packet/proto/oidb/Oidb.0xE37_1200";
|
||||||
import {PacketMsgConverter} from "@/core/packet/msg/converter";
|
import { PacketMsgConverter } from "@/core/packet/message/converter";
|
||||||
import { PacketClient } from "@/core/packet/client";
|
import { PacketClient } from "@/core/packet/client";
|
||||||
|
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";
|
||||||
|
|
||||||
export type PacketHexStr = string & { readonly hexNya: unique symbol };
|
export type PacketHexStr = string & { readonly hexNya: unique symbol };
|
||||||
|
|
||||||
|
export interface OidbPacket {
|
||||||
|
cmd: string;
|
||||||
|
data: PacketHexStr
|
||||||
|
}
|
||||||
|
|
||||||
export class PacketPacker {
|
export class PacketPacker {
|
||||||
readonly logger: LogWrapper;
|
readonly logger: LogWrapper;
|
||||||
readonly client: PacketClient;
|
readonly client: PacketClient;
|
||||||
@@ -34,30 +49,34 @@ export class PacketPacker {
|
|||||||
this.packetConverter = new PacketMsgConverter(logger);
|
this.packetConverter = new PacketMsgConverter(logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
private toHexStr(byteArray: Uint8Array): PacketHexStr {
|
private packetPacket(byteArray: Uint8Array): PacketHexStr {
|
||||||
return Buffer.from(byteArray).toString('hex') as PacketHexStr;
|
return Buffer.from(byteArray).toString('hex') as PacketHexStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
packOidbPacket(cmd: number, subCmd: number, body: Uint8Array, isUid: boolean = true, isLafter: boolean = false): Uint8Array {
|
packOidbPacket(cmd: number, subCmd: number, body: Uint8Array, isUid: boolean = true, isLafter: boolean = false): OidbPacket {
|
||||||
return new NapProtoMsg(OidbSvcTrpcTcpBase).encode({
|
const data = new NapProtoMsg(OidbSvcTrpcTcpBase).encode({
|
||||||
command: cmd,
|
command: cmd,
|
||||||
subCommand: subCmd,
|
subCommand: subCmd,
|
||||||
body: body,
|
body: body,
|
||||||
isReserved: isUid ? 1 : 0
|
isReserved: isUid ? 1 : 0
|
||||||
});
|
});
|
||||||
|
return {
|
||||||
|
cmd: `OidbSvcTrpcTcp.0x${cmd.toString(16).toUpperCase()}_${subCmd}`,
|
||||||
|
data: this.packetPacket(data)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
packPokePacket(group: number, peer: number): PacketHexStr {
|
packPokePacket(peer: number, group?: number): OidbPacket {
|
||||||
const oidb_0xed3 = new NapProtoMsg(OidbSvcTrpcTcp0XED3_1).encode({
|
const oidb_0xed3 = new NapProtoMsg(OidbSvcTrpcTcp0XED3_1).encode({
|
||||||
uin: peer,
|
uin: peer,
|
||||||
groupUin: group,
|
groupUin: group,
|
||||||
friendUin: group,
|
friendUin: group ?? peer,
|
||||||
ext: 0
|
ext: 0
|
||||||
});
|
});
|
||||||
return this.toHexStr(this.packOidbPacket(0xed3, 1, oidb_0xed3));
|
return this.packOidbPacket(0xed3, 1, oidb_0xed3);
|
||||||
}
|
}
|
||||||
|
|
||||||
packRkeyPacket(): PacketHexStr {
|
packRkeyPacket(): OidbPacket {
|
||||||
const oidb_0x9067_202 = new NapProtoMsg(OidbSvcTrpcTcp0X9067_202).encode({
|
const oidb_0x9067_202 = new NapProtoMsg(OidbSvcTrpcTcp0X9067_202).encode({
|
||||||
reqHead: {
|
reqHead: {
|
||||||
common: {
|
common: {
|
||||||
@@ -77,10 +96,10 @@ export class PacketPacker {
|
|||||||
key: [10, 20, 2]
|
key: [10, 20, 2]
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return this.toHexStr(this.packOidbPacket(0x9067, 202, oidb_0x9067_202));
|
return this.packOidbPacket(0x9067, 202, oidb_0x9067_202);
|
||||||
}
|
}
|
||||||
|
|
||||||
packSetSpecialTittlePacket(groupCode: string, uid: string, tittle: string): PacketHexStr {
|
packSetSpecialTittlePacket(groupCode: string, uid: string, tittle: string): OidbPacket {
|
||||||
const oidb_0x8FC_2_body = new NapProtoMsg(OidbSvcTrpcTcp0X8FC_2_Body).encode({
|
const oidb_0x8FC_2_body = new NapProtoMsg(OidbSvcTrpcTcp0X8FC_2_Body).encode({
|
||||||
targetUid: uid,
|
targetUid: uid,
|
||||||
specialTitle: tittle,
|
specialTitle: tittle,
|
||||||
@@ -91,15 +110,15 @@ export class PacketPacker {
|
|||||||
groupUin: +groupCode,
|
groupUin: +groupCode,
|
||||||
body: oidb_0x8FC_2_body
|
body: oidb_0x8FC_2_body
|
||||||
});
|
});
|
||||||
return this.toHexStr(this.packOidbPacket(0x8FC, 2, oidb_0x8FC_2, false, false));
|
return this.packOidbPacket(0x8FC, 2, oidb_0x8FC_2, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
packStatusPacket(uin: number): PacketHexStr {
|
packStatusPacket(uin: number): OidbPacket {
|
||||||
const oidb_0xfe1_2 = new NapProtoMsg(OidbSvcTrpcTcp0XFE1_2).encode({
|
const oidb_0xfe1_2 = new NapProtoMsg(OidbSvcTrpcTcp0XFE1_2).encode({
|
||||||
uin: uin,
|
uin: uin,
|
||||||
key: [{ key: 27372 }]
|
key: [{ key: 27372 }]
|
||||||
});
|
});
|
||||||
return this.toHexStr(this.packOidbPacket(0xfe1, 2, oidb_0xfe1_2));
|
return this.packOidbPacket(0xfe1, 2, oidb_0xfe1_2);
|
||||||
}
|
}
|
||||||
|
|
||||||
async packUploadForwardMsg(selfUid: string, msg: PacketMsg[], groupUin: number = 0): Promise<PacketHexStr> {
|
async packUploadForwardMsg(selfUid: string, msg: PacketMsg[], groupUin: number = 0): Promise<PacketHexStr> {
|
||||||
@@ -131,12 +150,12 @@ export class PacketPacker {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
// this.logger.logDebug("packUploadForwardMsg REQ!!!", req);
|
// this.logger.logDebug("packUploadForwardMsg REQ!!!", req);
|
||||||
return this.toHexStr(req);
|
return this.packetPacket(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
// highway part
|
// highway part
|
||||||
packHttp0x6ff_501(): PacketHexStr {
|
packHttp0x6ff_501(): PacketHexStr {
|
||||||
return this.toHexStr(new NapProtoMsg(HttpConn0x6ff_501).encode({
|
return this.packetPacket(new NapProtoMsg(HttpConn0x6ff_501).encode({
|
||||||
httpConn: {
|
httpConn: {
|
||||||
field1: 0,
|
field1: 0,
|
||||||
field2: 0,
|
field2: 0,
|
||||||
@@ -153,7 +172,7 @@ export class PacketPacker {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async packUploadGroupImgReq(groupUin: number, img: PacketMsgPicElement): Promise<PacketHexStr> {
|
async packUploadGroupImgReq(groupUin: number, img: PacketMsgPicElement): Promise<OidbPacket> {
|
||||||
const req = new NapProtoMsg(NTV2RichMediaReq).encode(
|
const req = new NapProtoMsg(NTV2RichMediaReq).encode(
|
||||||
{
|
{
|
||||||
reqHead: {
|
reqHead: {
|
||||||
@@ -177,9 +196,9 @@ export class PacketPacker {
|
|||||||
uploadInfo: [
|
uploadInfo: [
|
||||||
{
|
{
|
||||||
fileInfo: {
|
fileInfo: {
|
||||||
fileSize: Number(img.size),
|
fileSize: +img.size,
|
||||||
fileHash: img.md5,
|
fileHash: img.md5,
|
||||||
fileSha1: this.toHexStr(await calculateSha1(img.path)),
|
fileSha1: img.sha1!,
|
||||||
fileName: img.name,
|
fileName: img.name,
|
||||||
type: {
|
type: {
|
||||||
type: 1,
|
type: 1,
|
||||||
@@ -217,11 +236,11 @@ export class PacketPacker {
|
|||||||
noNeedCompatMsg: false,
|
noNeedCompatMsg: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
return this.toHexStr(this.packOidbPacket(0x11c4, 100, req, true, false));
|
return this.packOidbPacket(0x11c4, 100, req, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async packUploadC2CImgReq(peerUin: string, img: PacketMsgPicElement): Promise<PacketHexStr> {
|
async packUploadC2CImgReq(peerUin: string, img: PacketMsgPicElement): Promise<OidbPacket> {
|
||||||
const req = new NapProtoMsg(NTV2RichMediaReq).encode({
|
const req = new NapProtoMsg(NTV2RichMediaReq).encode({
|
||||||
reqHead: {
|
reqHead: {
|
||||||
common: {
|
common: {
|
||||||
@@ -245,9 +264,9 @@ export class PacketPacker {
|
|||||||
uploadInfo: [
|
uploadInfo: [
|
||||||
{
|
{
|
||||||
fileInfo: {
|
fileInfo: {
|
||||||
fileSize: Number(img.size),
|
fileSize: +img.size,
|
||||||
fileHash: img.md5,
|
fileHash: img.md5,
|
||||||
fileSha1: this.toHexStr(await calculateSha1(img.path)),
|
fileSha1: img.sha1!,
|
||||||
fileName: img.name,
|
fileName: img.name,
|
||||||
type: {
|
type: {
|
||||||
type: 1,
|
type: 1,
|
||||||
@@ -285,25 +304,380 @@ export class PacketPacker {
|
|||||||
noNeedCompatMsg: false,
|
noNeedCompatMsg: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
return this.toHexStr(this.packOidbPacket(0x11c5, 100, req, true, false));
|
return this.packOidbPacket(0x11c5, 100, req, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
packGroupFileDownloadReq(groupUin: number, fileUUID: string): PacketHexStr {
|
async packUploadGroupVideoReq(groupUin: number, video: PacketMsgVideoElement): Promise<OidbPacket> {
|
||||||
return this.toHexStr(
|
if (!video.fileSize || !video.thumbSize) throw new Error("video.fileSize or video.thumbSize is empty");
|
||||||
this.packOidbPacket(0x6D6, 2, new NapProtoMsg(OidbSvcTrpcTcp0x6D6).encode({
|
const req = new NapProtoMsg(NTV2RichMediaReq).encode({
|
||||||
|
reqHead: {
|
||||||
|
common: {
|
||||||
|
requestId: 3,
|
||||||
|
command: 100
|
||||||
|
},
|
||||||
|
scene: {
|
||||||
|
requestType: 2,
|
||||||
|
businessType: 2,
|
||||||
|
sceneType: 2,
|
||||||
|
group: {
|
||||||
|
groupUin: groupUin
|
||||||
|
},
|
||||||
|
},
|
||||||
|
client: {
|
||||||
|
agentType: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
upload: {
|
||||||
|
uploadInfo: [
|
||||||
|
{
|
||||||
|
fileInfo: {
|
||||||
|
fileSize: +video.fileSize,
|
||||||
|
fileHash: video.fileMd5,
|
||||||
|
fileSha1: video.fileSha1,
|
||||||
|
fileName: "nya.mp4",
|
||||||
|
type: {
|
||||||
|
type: 2,
|
||||||
|
picFormat: 0,
|
||||||
|
videoFormat: 0,
|
||||||
|
voiceFormat: 0
|
||||||
|
},
|
||||||
|
height: 0,
|
||||||
|
width: 0,
|
||||||
|
time: 0,
|
||||||
|
original: 0
|
||||||
|
},
|
||||||
|
subFileType: 0
|
||||||
|
}, {
|
||||||
|
fileInfo: {
|
||||||
|
fileSize: +video.thumbSize,
|
||||||
|
fileHash: video.thumbMd5,
|
||||||
|
fileSha1: video.thumbSha1,
|
||||||
|
fileName: "nya.jpg",
|
||||||
|
type: {
|
||||||
|
type: 1,
|
||||||
|
picFormat: 0,
|
||||||
|
videoFormat: 0,
|
||||||
|
voiceFormat: 0
|
||||||
|
},
|
||||||
|
height: video.thumbHeight,
|
||||||
|
width: video.thumbWidth,
|
||||||
|
time: 0,
|
||||||
|
original: 0
|
||||||
|
},
|
||||||
|
subFileType: 100
|
||||||
|
}
|
||||||
|
],
|
||||||
|
tryFastUploadCompleted: true,
|
||||||
|
srvSendMsg: false,
|
||||||
|
clientRandomId: crypto.randomBytes(8).readBigUInt64BE() & BigInt('0x7FFFFFFFFFFFFFFF'),
|
||||||
|
compatQMsgSceneType: 2,
|
||||||
|
extBizInfo: {
|
||||||
|
pic: {
|
||||||
|
bizType: 0,
|
||||||
|
textSummary: "Nya~",
|
||||||
|
},
|
||||||
|
video: {
|
||||||
|
bytesPbReserve: Buffer.from([0x80, 0x01, 0x00]),
|
||||||
|
},
|
||||||
|
ptt: {
|
||||||
|
bytesPbReserve: Buffer.alloc(0),
|
||||||
|
bytesReserve: Buffer.alloc(0),
|
||||||
|
bytesGeneralFlags: Buffer.alloc(0),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clientSeq: 0,
|
||||||
|
noNeedCompatMsg: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return this.packOidbPacket(0x11EA, 100, req, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async packUploadC2CVideoReq(peerUin: string, video: PacketMsgVideoElement): Promise<OidbPacket> {
|
||||||
|
if (!video.fileSize || !video.thumbSize) throw new Error("video.fileSize or video.thumbSize is empty");
|
||||||
|
const req = new NapProtoMsg(NTV2RichMediaReq).encode({
|
||||||
|
reqHead: {
|
||||||
|
common: {
|
||||||
|
requestId: 3,
|
||||||
|
command: 100
|
||||||
|
},
|
||||||
|
scene: {
|
||||||
|
requestType: 2,
|
||||||
|
businessType: 2,
|
||||||
|
sceneType: 1,
|
||||||
|
c2C: {
|
||||||
|
accountType: 2,
|
||||||
|
targetUid: peerUin
|
||||||
|
}
|
||||||
|
},
|
||||||
|
client: {
|
||||||
|
agentType: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
upload: {
|
||||||
|
uploadInfo: [
|
||||||
|
{
|
||||||
|
fileInfo: {
|
||||||
|
fileSize: +video.fileSize,
|
||||||
|
fileHash: video.fileMd5,
|
||||||
|
fileSha1: video.fileSha1,
|
||||||
|
fileName: "nya.mp4",
|
||||||
|
type: {
|
||||||
|
type: 2,
|
||||||
|
picFormat: 0,
|
||||||
|
videoFormat: 0,
|
||||||
|
voiceFormat: 0
|
||||||
|
},
|
||||||
|
height: 0,
|
||||||
|
width: 0,
|
||||||
|
time: 0,
|
||||||
|
original: 0
|
||||||
|
},
|
||||||
|
subFileType: 0
|
||||||
|
}, {
|
||||||
|
fileInfo: {
|
||||||
|
fileSize: +video.thumbSize,
|
||||||
|
fileHash: video.thumbMd5,
|
||||||
|
fileSha1: video.thumbSha1,
|
||||||
|
fileName: "nya.jpg",
|
||||||
|
type: {
|
||||||
|
type: 1,
|
||||||
|
picFormat: 0,
|
||||||
|
videoFormat: 0,
|
||||||
|
voiceFormat: 0
|
||||||
|
},
|
||||||
|
height: video.thumbHeight,
|
||||||
|
width: video.thumbWidth,
|
||||||
|
time: 0,
|
||||||
|
original: 0
|
||||||
|
},
|
||||||
|
subFileType: 100
|
||||||
|
}
|
||||||
|
],
|
||||||
|
tryFastUploadCompleted: true,
|
||||||
|
srvSendMsg: false,
|
||||||
|
clientRandomId: crypto.randomBytes(8).readBigUInt64BE() & BigInt('0x7FFFFFFFFFFFFFFF'),
|
||||||
|
compatQMsgSceneType: 2,
|
||||||
|
extBizInfo: {
|
||||||
|
pic: {
|
||||||
|
bizType: 0,
|
||||||
|
textSummary: "Nya~",
|
||||||
|
},
|
||||||
|
video: {
|
||||||
|
bytesPbReserve: Buffer.from([0x80, 0x01, 0x00]),
|
||||||
|
},
|
||||||
|
ptt: {
|
||||||
|
bytesPbReserve: Buffer.alloc(0),
|
||||||
|
bytesReserve: Buffer.alloc(0),
|
||||||
|
bytesGeneralFlags: Buffer.alloc(0),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clientSeq: 0,
|
||||||
|
noNeedCompatMsg: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return this.packOidbPacket(0x11E9, 100, req, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async packUploadGroupPttReq(groupUin: number, ptt: PacketMsgPttElement): Promise<OidbPacket> {
|
||||||
|
const req = new NapProtoMsg(NTV2RichMediaReq).encode({
|
||||||
|
reqHead: {
|
||||||
|
common: {
|
||||||
|
requestId: 1,
|
||||||
|
command: 100
|
||||||
|
},
|
||||||
|
scene: {
|
||||||
|
requestType: 2,
|
||||||
|
businessType: 3,
|
||||||
|
sceneType: 2,
|
||||||
|
group: {
|
||||||
|
groupUin: groupUin
|
||||||
|
}
|
||||||
|
},
|
||||||
|
client: {
|
||||||
|
agentType: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
upload: {
|
||||||
|
uploadInfo: [
|
||||||
|
{
|
||||||
|
fileInfo: {
|
||||||
|
fileSize: ptt.fileSize,
|
||||||
|
fileHash: ptt.fileMd5,
|
||||||
|
fileSha1: ptt.fileSha1,
|
||||||
|
fileName: `${ptt.fileMd5}.amr`,
|
||||||
|
type: {
|
||||||
|
type: 3,
|
||||||
|
picFormat: 0,
|
||||||
|
videoFormat: 0,
|
||||||
|
voiceFormat: 1
|
||||||
|
},
|
||||||
|
height: 0,
|
||||||
|
width: 0,
|
||||||
|
time: ptt.fileDuration,
|
||||||
|
original: 0
|
||||||
|
},
|
||||||
|
subFileType: 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
tryFastUploadCompleted: true,
|
||||||
|
srvSendMsg: false,
|
||||||
|
clientRandomId: crypto.randomBytes(8).readBigUInt64BE() & BigInt('0x7FFFFFFFFFFFFFFF'),
|
||||||
|
compatQMsgSceneType: 2,
|
||||||
|
extBizInfo: {
|
||||||
|
pic: {
|
||||||
|
textSummary: "Nya~",
|
||||||
|
},
|
||||||
|
video: {
|
||||||
|
bytesPbReserve: Buffer.alloc(0),
|
||||||
|
},
|
||||||
|
ptt: {
|
||||||
|
bytesPbReserve: Buffer.alloc(0),
|
||||||
|
bytesReserve: Buffer.from([0x08, 0x00, 0x38, 0x00]),
|
||||||
|
bytesGeneralFlags: Buffer.from([0x9a, 0x01, 0x07, 0xaa, 0x03, 0x04, 0x08, 0x08, 0x12, 0x00]),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clientSeq: 0,
|
||||||
|
noNeedCompatMsg: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return this.packOidbPacket(0x126E, 100, req, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async packUploadC2CPttReq(peerUin: string, ptt: PacketMsgPttElement): Promise<OidbPacket> {
|
||||||
|
const req = new NapProtoMsg(NTV2RichMediaReq).encode({
|
||||||
|
reqHead: {
|
||||||
|
common: {
|
||||||
|
requestId: 4,
|
||||||
|
command: 100
|
||||||
|
},
|
||||||
|
scene: {
|
||||||
|
requestType: 2,
|
||||||
|
businessType: 3,
|
||||||
|
sceneType: 1,
|
||||||
|
c2C: {
|
||||||
|
accountType: 2,
|
||||||
|
targetUid: peerUin
|
||||||
|
}
|
||||||
|
},
|
||||||
|
client: {
|
||||||
|
agentType: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
upload: {
|
||||||
|
uploadInfo: [
|
||||||
|
{
|
||||||
|
fileInfo: {
|
||||||
|
fileSize: ptt.fileSize,
|
||||||
|
fileHash: ptt.fileMd5,
|
||||||
|
fileSha1: ptt.fileSha1,
|
||||||
|
fileName: `${ptt.fileMd5}.amr`,
|
||||||
|
type: {
|
||||||
|
type: 3,
|
||||||
|
picFormat: 0,
|
||||||
|
videoFormat: 0,
|
||||||
|
voiceFormat: 1
|
||||||
|
},
|
||||||
|
height: 0,
|
||||||
|
width: 0,
|
||||||
|
time: ptt.fileDuration,
|
||||||
|
original: 0
|
||||||
|
},
|
||||||
|
subFileType: 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
tryFastUploadCompleted: true,
|
||||||
|
srvSendMsg: false,
|
||||||
|
clientRandomId: crypto.randomBytes(8).readBigUInt64BE() & BigInt('0x7FFFFFFFFFFFFFFF'),
|
||||||
|
compatQMsgSceneType: 1,
|
||||||
|
extBizInfo: {
|
||||||
|
pic: {
|
||||||
|
textSummary: "Nya~",
|
||||||
|
},
|
||||||
|
ptt: {
|
||||||
|
bytesReserve: Buffer.from([0x08, 0x00, 0x38, 0x00]),
|
||||||
|
bytesGeneralFlags: Buffer.from([0x9a, 0x01, 0x0b, 0xaa, 0x03, 0x08, 0x08, 0x04, 0x12, 0x04, 0x00, 0x00, 0x00, 0x00]),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clientSeq: 0,
|
||||||
|
noNeedCompatMsg: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return this.packOidbPacket(0x126D, 100, req, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async packUploadGroupFileReq(groupUin: number, file: PacketMsgFileElement): Promise<OidbPacket> {
|
||||||
|
const body = new NapProtoMsg(OidbSvcTrpcTcp0x6D6).encode({
|
||||||
|
file: {
|
||||||
|
groupUin: groupUin,
|
||||||
|
appId: 4,
|
||||||
|
busId: 102,
|
||||||
|
entrance: 6,
|
||||||
|
targetDirectory: '/', // TODO:
|
||||||
|
fileName: file.fileName,
|
||||||
|
localDirectory: `/${file.fileName}`,
|
||||||
|
fileSize: BigInt(file.fileSize),
|
||||||
|
fileMd5: file.fileMd5,
|
||||||
|
fileSha1: file.fileSha1,
|
||||||
|
fileSha3: Buffer.alloc(0),
|
||||||
|
field15: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return this.packOidbPacket(0x6D6, 0, body, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async packUploadC2CFileReq(selfUid: string, peerUid: string, file: PacketMsgFileElement): Promise<OidbPacket> {
|
||||||
|
const body = new NapProtoMsg(OidbSvcTrpcTcp0XE37_1700).encode({
|
||||||
|
command: 1700,
|
||||||
|
seq: 0,
|
||||||
|
upload: {
|
||||||
|
senderUid: selfUid,
|
||||||
|
receiverUid: peerUid,
|
||||||
|
fileSize: file.fileSize,
|
||||||
|
fileName: file.fileName,
|
||||||
|
md510MCheckSum: await computeMd5AndLengthWithLimit(file.filePath, 10 * 1024 * 1024),
|
||||||
|
sha1CheckSum: file.fileSha1,
|
||||||
|
localPath: "/",
|
||||||
|
md5CheckSum: file.fileMd5,
|
||||||
|
sha3CheckSum: Buffer.alloc(0)
|
||||||
|
},
|
||||||
|
businessId: 3,
|
||||||
|
clientType: 1,
|
||||||
|
flagSupportMediaPlatform: 1
|
||||||
|
});
|
||||||
|
return this.packOidbPacket(0xE37, 1700, body, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
packOfflineFileDownloadReq(fileUUID: string, fileHash: string, senderUid: string, receiverUid: string): OidbPacket {
|
||||||
|
return this.packOidbPacket(0xE37, 800, new NapProtoMsg(OidbSvcTrpcTcp0XE37_800).encode({
|
||||||
|
subCommand: 800,
|
||||||
|
field2: 0,
|
||||||
|
body: {
|
||||||
|
senderUid: senderUid,
|
||||||
|
receiverUid: receiverUid,
|
||||||
|
fileUuid: fileUUID,
|
||||||
|
fileHash: fileHash,
|
||||||
|
},
|
||||||
|
field101: 3,
|
||||||
|
field102: 1,
|
||||||
|
field200: 1,
|
||||||
|
}), false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
packGroupFileDownloadReq(groupUin: number, fileUUID: string): OidbPacket {
|
||||||
|
return this.packOidbPacket(0x6D6, 2, new NapProtoMsg(OidbSvcTrpcTcp0x6D6).encode({
|
||||||
download: {
|
download: {
|
||||||
groupUin: groupUin,
|
groupUin: groupUin,
|
||||||
appId: 7,
|
appId: 7,
|
||||||
busId: 102,
|
busId: 102,
|
||||||
fileId: fileUUID
|
fileId: fileUUID
|
||||||
}
|
}
|
||||||
}), true, false)
|
}), true, false
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
packC2CFileDownloadReq(selfUid: string, fileUUID: string, fileHash: string): PacketHexStr {
|
packC2CFileDownloadReq(selfUid: string, fileUUID: string, fileHash: string): PacketHexStr {
|
||||||
return this.toHexStr(
|
return this.packetPacket(
|
||||||
new NapProtoMsg(OidbSvcTrpcTcp0XE37_1200).encode({
|
new NapProtoMsg(OidbSvcTrpcTcp0XE37_1200).encode({
|
||||||
subCommand: 1200,
|
subCommand: 1200,
|
||||||
field2: 1,
|
field2: 1,
|
||||||
@@ -319,6 +693,55 @@ export class PacketPacker {
|
|||||||
field200: 1,
|
field200: 1,
|
||||||
field99999: Buffer.from([0xc0, 0x85, 0x2c, 0x01])
|
field99999: Buffer.from([0xc0, 0x85, 0x2c, 0x01])
|
||||||
})
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
packGroupSignReq(uin: string, groupCode: string): OidbPacket {
|
||||||
|
return this.packOidbPacket(0XEB7, 1, new NapProtoMsg(OidbSvcTrpcTcp0XEB7).encode(
|
||||||
|
{
|
||||||
|
body: {
|
||||||
|
uin: uin,
|
||||||
|
groupUin: groupCode,
|
||||||
|
version: "9.0.90"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
), 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: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -61,35 +61,35 @@ export function ProtoField(no: number, type: ScalarType | (() => ProtoMessageTyp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProtoFieldReturnType<T extends unknown, E extends boolean> = NonNullable<T> extends ScalarProtoFieldType<infer S, infer O, infer R>
|
type ProtoFieldReturnType<T, E extends boolean> = NonNullable<T> extends ScalarProtoFieldType<infer S, infer O, infer R>
|
||||||
? ScalarTypeToTsType<S>
|
? ScalarTypeToTsType<S>
|
||||||
: T extends NonNullable<MessageProtoFieldType<infer S, infer O, infer R>>
|
: T extends NonNullable<MessageProtoFieldType<infer S, infer O, infer R>>
|
||||||
? NonNullable<NapProtoStructType<ReturnType<S>, E>>
|
? NonNullable<NapProtoStructType<ReturnType<S>, E>>
|
||||||
: never;
|
: never;
|
||||||
|
|
||||||
type RequiredFieldsBaseType<T extends unknown, E extends boolean> = {
|
type RequiredFieldsBaseType<T, E extends boolean> = {
|
||||||
[K in keyof T as T[K] extends { optional: true } ? never : LowerCamelCase<K & string>]:
|
[K in keyof T as T[K] extends { optional: true } ? never : LowerCamelCase<K & string>]:
|
||||||
T[K] extends { repeat: true }
|
T[K] extends { repeat: true }
|
||||||
? ProtoFieldReturnType<T[K], E>[]
|
? ProtoFieldReturnType<T[K], E>[]
|
||||||
: ProtoFieldReturnType<T[K], E>
|
: ProtoFieldReturnType<T[K], E>
|
||||||
}
|
}
|
||||||
|
|
||||||
type OptionalFieldsBaseType<T extends unknown, E extends boolean> = {
|
type OptionalFieldsBaseType<T, E extends boolean> = {
|
||||||
[K in keyof T as T[K] extends { optional: true } ? LowerCamelCase<K & string> : never]?:
|
[K in keyof T as T[K] extends { optional: true } ? LowerCamelCase<K & string> : never]?:
|
||||||
T[K] extends { repeat: true }
|
T[K] extends { repeat: true }
|
||||||
? ProtoFieldReturnType<T[K], E>[]
|
? ProtoFieldReturnType<T[K], E>[]
|
||||||
: 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 RequiredFieldsType<T, 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 OptionalFieldsType<T, 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>;
|
type NapProtoStructType<T, E extends boolean> = RequiredFieldsType<T, E> & OptionalFieldsType<T, E>;
|
||||||
|
|
||||||
export type NapProtoEncodeStructType<T extends unknown> = NapProtoStructType<T, true>;
|
export type NapProtoEncodeStructType<T> = NapProtoStructType<T, true>;
|
||||||
|
|
||||||
export type NapProtoDecodeStructType<T extends unknown> = NapProtoStructType<T, false>;
|
export type NapProtoDecodeStructType<T> = NapProtoStructType<T, false>;
|
||||||
|
|
||||||
const NapProtoMsgCache = new Map<ProtoMessageType, MessageType<NapProtoStructType<ProtoMessageType, boolean>>>();
|
const NapProtoMsgCache = new Map<ProtoMessageType, MessageType<NapProtoStructType<ProtoMessageType, boolean>>>();
|
||||||
|
|
||||||
|
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 "../NapProto";
|
||||||
|
|
||||||
|
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),
|
||||||
|
};
|
@@ -12,7 +12,7 @@ export const DataHighwayHead = {
|
|||||||
dataFlag: ProtoField(7, ScalarType.UINT32),
|
dataFlag: ProtoField(7, ScalarType.UINT32),
|
||||||
commandId: ProtoField(8, ScalarType.UINT32),
|
commandId: ProtoField(8, ScalarType.UINT32),
|
||||||
buildVer: ProtoField(9, ScalarType.BYTES, true),
|
buildVer: ProtoField(9, ScalarType.BYTES, true),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const FileUploadExt = {
|
export const FileUploadExt = {
|
||||||
unknown1: ProtoField(1, ScalarType.INT32),
|
unknown1: ProtoField(1, ScalarType.INT32),
|
||||||
@@ -20,7 +20,7 @@ export const FileUploadExt = {
|
|||||||
unknown3: ProtoField(3, ScalarType.INT32),
|
unknown3: ProtoField(3, ScalarType.INT32),
|
||||||
entry: ProtoField(100, () => FileUploadEntry),
|
entry: ProtoField(100, () => FileUploadEntry),
|
||||||
unknown200: ProtoField(200, ScalarType.INT32),
|
unknown200: ProtoField(200, ScalarType.INT32),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const FileUploadEntry = {
|
export const FileUploadEntry = {
|
||||||
busiBuff: ProtoField(100, () => ExcitingBusiInfo),
|
busiBuff: ProtoField(100, () => ExcitingBusiInfo),
|
||||||
@@ -28,14 +28,14 @@ export const FileUploadEntry = {
|
|||||||
clientInfo: ProtoField(300, () => ExcitingClientInfo),
|
clientInfo: ProtoField(300, () => ExcitingClientInfo),
|
||||||
fileNameInfo: ProtoField(400, () => ExcitingFileNameInfo),
|
fileNameInfo: ProtoField(400, () => ExcitingFileNameInfo),
|
||||||
host: ProtoField(500, () => ExcitingHostConfig),
|
host: ProtoField(500, () => ExcitingHostConfig),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const ExcitingBusiInfo = {
|
export const ExcitingBusiInfo = {
|
||||||
busId: ProtoField(1, ScalarType.INT32),
|
busId: ProtoField(1, ScalarType.INT32),
|
||||||
senderUin: ProtoField(100, ScalarType.UINT64),
|
senderUin: ProtoField(100, ScalarType.UINT64),
|
||||||
receiverUin: ProtoField(200, ScalarType.UINT64),
|
receiverUin: ProtoField(200, ScalarType.UINT64),
|
||||||
groupCode: ProtoField(400, ScalarType.UINT64),
|
groupCode: ProtoField(400, ScalarType.UINT64),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const ExcitingFileEntry = {
|
export const ExcitingFileEntry = {
|
||||||
fileSize: ProtoField(100, ScalarType.UINT64),
|
fileSize: ProtoField(100, ScalarType.UINT64),
|
||||||
@@ -44,7 +44,7 @@ export const ExcitingFileEntry = {
|
|||||||
md5S2: ProtoField(400, ScalarType.BYTES),
|
md5S2: ProtoField(400, ScalarType.BYTES),
|
||||||
fileId: ProtoField(600, ScalarType.STRING),
|
fileId: ProtoField(600, ScalarType.STRING),
|
||||||
uploadKey: ProtoField(700, ScalarType.BYTES),
|
uploadKey: ProtoField(700, ScalarType.BYTES),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const ExcitingClientInfo = {
|
export const ExcitingClientInfo = {
|
||||||
clientType: ProtoField(100, ScalarType.INT32),
|
clientType: ProtoField(100, ScalarType.INT32),
|
||||||
@@ -52,31 +52,31 @@ export const ExcitingClientInfo = {
|
|||||||
terminalType: ProtoField(300, ScalarType.INT32),
|
terminalType: ProtoField(300, ScalarType.INT32),
|
||||||
clientVer: ProtoField(400, ScalarType.STRING),
|
clientVer: ProtoField(400, ScalarType.STRING),
|
||||||
unknown: ProtoField(600, ScalarType.INT32),
|
unknown: ProtoField(600, ScalarType.INT32),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const ExcitingFileNameInfo = {
|
export const ExcitingFileNameInfo = {
|
||||||
fileName: ProtoField(100, ScalarType.STRING),
|
fileName: ProtoField(100, ScalarType.STRING),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const ExcitingHostConfig = {
|
export const ExcitingHostConfig = {
|
||||||
hosts: ProtoField(200, () => ExcitingHostInfo, false, true),
|
hosts: ProtoField(200, () => ExcitingHostInfo, false, true),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const ExcitingHostInfo = {
|
export const ExcitingHostInfo = {
|
||||||
url: ProtoField(1, () => ExcitingUrlInfo),
|
url: ProtoField(1, () => ExcitingUrlInfo),
|
||||||
port: ProtoField(2, ScalarType.UINT32),
|
port: ProtoField(2, ScalarType.UINT32),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const ExcitingUrlInfo = {
|
export const ExcitingUrlInfo = {
|
||||||
unknown: ProtoField(1, ScalarType.INT32),
|
unknown: ProtoField(1, ScalarType.INT32),
|
||||||
host: ProtoField(2, ScalarType.STRING),
|
host: ProtoField(2, ScalarType.STRING),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const LoginSigHead = {
|
export const LoginSigHead = {
|
||||||
uint32LoginSigType: ProtoField(1, ScalarType.UINT32),
|
uint32LoginSigType: ProtoField(1, ScalarType.UINT32),
|
||||||
bytesLoginSig: ProtoField(2, ScalarType.BYTES),
|
bytesLoginSig: ProtoField(2, ScalarType.BYTES),
|
||||||
appId: ProtoField(3, ScalarType.UINT32),
|
appId: ProtoField(3, ScalarType.UINT32),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const NTV2RichMediaHighwayExt = {
|
export const NTV2RichMediaHighwayExt = {
|
||||||
fileUuid: ProtoField(1, ScalarType.STRING),
|
fileUuid: ProtoField(1, ScalarType.STRING),
|
||||||
@@ -85,25 +85,25 @@ export const NTV2RichMediaHighwayExt = {
|
|||||||
msgInfoBody: ProtoField(6, () => MsgInfoBody, false, true),
|
msgInfoBody: ProtoField(6, () => MsgInfoBody, false, true),
|
||||||
blockSize: ProtoField(10, ScalarType.UINT32),
|
blockSize: ProtoField(10, ScalarType.UINT32),
|
||||||
hash: ProtoField(11, () => NTHighwayHash),
|
hash: ProtoField(11, () => NTHighwayHash),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const NTHighwayHash = {
|
export const NTHighwayHash = {
|
||||||
fileSha1: ProtoField(1, ScalarType.BYTES, false, true),
|
fileSha1: ProtoField(1, ScalarType.BYTES, false, true),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const NTHighwayNetwork = {
|
export const NTHighwayNetwork = {
|
||||||
ipv4s: ProtoField(1, () => NTHighwayIPv4, false, true),
|
ipv4s: ProtoField(1, () => NTHighwayIPv4, false, true),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const NTHighwayIPv4 = {
|
export const NTHighwayIPv4 = {
|
||||||
domain: ProtoField(1, () => NTHighwayDomain),
|
domain: ProtoField(1, () => NTHighwayDomain),
|
||||||
port: ProtoField(2, ScalarType.UINT32),
|
port: ProtoField(2, ScalarType.UINT32),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const NTHighwayDomain = {
|
export const NTHighwayDomain = {
|
||||||
isEnable: ProtoField(1, ScalarType.BOOL),
|
isEnable: ProtoField(1, ScalarType.BOOL),
|
||||||
ip: ProtoField(2, ScalarType.STRING),
|
ip: ProtoField(2, ScalarType.STRING),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const ReqDataHighwayHead = {
|
export const ReqDataHighwayHead = {
|
||||||
msgBaseHead: ProtoField(1, () => DataHighwayHead, true),
|
msgBaseHead: ProtoField(1, () => DataHighwayHead, true),
|
||||||
@@ -111,7 +111,7 @@ export const ReqDataHighwayHead = {
|
|||||||
bytesReqExtendInfo: ProtoField(3, ScalarType.BYTES, true),
|
bytesReqExtendInfo: ProtoField(3, ScalarType.BYTES, true),
|
||||||
timestamp: ProtoField(4, ScalarType.UINT64),
|
timestamp: ProtoField(4, ScalarType.UINT64),
|
||||||
msgLoginSigHead: ProtoField(5, () => LoginSigHead, true),
|
msgLoginSigHead: ProtoField(5, () => LoginSigHead, true),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const RespDataHighwayHead = {
|
export const RespDataHighwayHead = {
|
||||||
msgBaseHead: ProtoField(1, () => DataHighwayHead, true),
|
msgBaseHead: ProtoField(1, () => DataHighwayHead, true),
|
||||||
@@ -124,7 +124,7 @@ export const RespDataHighwayHead = {
|
|||||||
timestamp: ProtoField(8, ScalarType.UINT64),
|
timestamp: ProtoField(8, ScalarType.UINT64),
|
||||||
range: ProtoField(9, ScalarType.UINT64),
|
range: ProtoField(9, ScalarType.UINT64),
|
||||||
isReset: ProtoField(10, ScalarType.UINT32),
|
isReset: ProtoField(10, ScalarType.UINT32),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const SegHead = {
|
export const SegHead = {
|
||||||
serviceId: ProtoField(1, ScalarType.UINT32, true),
|
serviceId: ProtoField(1, ScalarType.UINT32, true),
|
||||||
@@ -140,7 +140,7 @@ export const SegHead = {
|
|||||||
queryTimes: ProtoField(11, ScalarType.UINT32),
|
queryTimes: ProtoField(11, ScalarType.UINT32),
|
||||||
updateCacheIp: ProtoField(12, ScalarType.UINT32),
|
updateCacheIp: ProtoField(12, ScalarType.UINT32),
|
||||||
cachePort: ProtoField(13, ScalarType.UINT32, true),
|
cachePort: ProtoField(13, ScalarType.UINT32, true),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const GroupAvatarExtra = {
|
export const GroupAvatarExtra = {
|
||||||
type: ProtoField(1, ScalarType.UINT32),
|
type: ProtoField(1, ScalarType.UINT32),
|
||||||
@@ -148,8 +148,8 @@ export const GroupAvatarExtra = {
|
|||||||
field3: ProtoField(3, () => GroupAvatarExtraField3),
|
field3: ProtoField(3, () => GroupAvatarExtraField3),
|
||||||
field5: ProtoField(5, ScalarType.UINT32),
|
field5: ProtoField(5, ScalarType.UINT32),
|
||||||
field6: ProtoField(6, ScalarType.UINT32),
|
field6: ProtoField(6, ScalarType.UINT32),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const GroupAvatarExtraField3 = {
|
export const GroupAvatarExtraField3 = {
|
||||||
field1: ProtoField(1, ScalarType.UINT32),
|
field1: ProtoField(1, ScalarType.UINT32),
|
||||||
}
|
};
|
||||||
|
@@ -113,6 +113,24 @@ export const Permission = {
|
|||||||
|
|
||||||
export const FileExtra = {
|
export const FileExtra = {
|
||||||
file: ProtoField(1, () => NotOnlineFile),
|
file: ProtoField(1, () => NotOnlineFile),
|
||||||
|
field6: ProtoField(6, () => PrivateFileExtra),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PrivateFileExtra = {
|
||||||
|
field2: ProtoField(2, () => PrivateFileExtraField2),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PrivateFileExtraField2 = {
|
||||||
|
field1: ProtoField(1, ScalarType.UINT32),
|
||||||
|
fileUuid: ProtoField(4, ScalarType.STRING),
|
||||||
|
fileName: ProtoField(5, ScalarType.STRING),
|
||||||
|
field6: ProtoField(6, ScalarType.UINT32),
|
||||||
|
field7: ProtoField(7, ScalarType.BYTES),
|
||||||
|
field8: ProtoField(8, ScalarType.BYTES),
|
||||||
|
timestamp1: ProtoField(9, ScalarType.UINT32),
|
||||||
|
fileHash: ProtoField(14, ScalarType.STRING),
|
||||||
|
selfUid: ProtoField(15, ScalarType.STRING),
|
||||||
|
destUid: ProtoField(16, ScalarType.STRING),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GroupFileExtra = {
|
export const GroupFileExtra = {
|
||||||
@@ -132,8 +150,9 @@ export const GroupFileExtraInfo = {
|
|||||||
fileSize: ProtoField(3, ScalarType.UINT64),
|
fileSize: ProtoField(3, ScalarType.UINT64),
|
||||||
fileName: ProtoField(4, ScalarType.STRING),
|
fileName: ProtoField(4, ScalarType.STRING),
|
||||||
field5: ProtoField(5, ScalarType.UINT32),
|
field5: ProtoField(5, ScalarType.UINT32),
|
||||||
field7: ProtoField(7, ScalarType.STRING),
|
fileSha: ProtoField(6, ScalarType.BYTES),
|
||||||
fileMd5: ProtoField(8, ScalarType.STRING),
|
extInfoString: ProtoField(7, ScalarType.STRING),
|
||||||
|
fileMd5: ProtoField(8, ScalarType.BYTES),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ImageExtraUrl = {
|
export const ImageExtraUrl = {
|
||||||
|
@@ -118,7 +118,7 @@ export const MarketFace = {
|
|||||||
|
|
||||||
export const MarketFacePbRes = {
|
export const MarketFacePbRes = {
|
||||||
field8: ProtoField(8, ScalarType.INT32)
|
field8: ProtoField(8, ScalarType.INT32)
|
||||||
}
|
};
|
||||||
|
|
||||||
export const CustomFace = {
|
export const CustomFace = {
|
||||||
guid: ProtoField(1, ScalarType.BYTES),
|
guid: ProtoField(1, ScalarType.BYTES),
|
||||||
@@ -315,7 +315,7 @@ export const SrcMsgPbRes = {
|
|||||||
senderUid: ProtoField(6, ScalarType.STRING, true),
|
senderUid: ProtoField(6, ScalarType.STRING, true),
|
||||||
receiverUid: ProtoField(7, ScalarType.STRING, true),
|
receiverUid: ProtoField(7, ScalarType.STRING, true),
|
||||||
friendSeq: ProtoField(8, ScalarType.UINT32, true),
|
friendSeq: ProtoField(8, ScalarType.UINT32, true),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const LightAppElem = {
|
export const LightAppElem = {
|
||||||
data: ProtoField(1, ScalarType.BYTES),
|
data: ProtoField(1, ScalarType.BYTES),
|
||||||
@@ -358,4 +358,4 @@ export const QSmallFaceExtra = {
|
|||||||
|
|
||||||
export const MarkdownData = {
|
export const MarkdownData = {
|
||||||
content: ProtoField(1, ScalarType.STRING)
|
content: ProtoField(1, ScalarType.STRING)
|
||||||
}
|
};
|
||||||
|
62
src/core/packet/proto/oidb/Oidb.0XE37_800.ts
Normal file
62
src/core/packet/proto/oidb/Oidb.0XE37_800.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { ScalarType } from "@protobuf-ts/runtime";
|
||||||
|
import { ProtoField } from "../NapProto";
|
||||||
|
import { OidbSvcTrpcTcp0XE37_800_1200Metadata } from "@/core/packet/proto/oidb/Oidb.0xE37_1200";
|
||||||
|
|
||||||
|
export const OidbSvcTrpcTcp0XE37_800 = {
|
||||||
|
subCommand: ProtoField(1, ScalarType.UINT32),
|
||||||
|
field2: ProtoField(2, ScalarType.INT32),
|
||||||
|
body: ProtoField(10, () => OidbSvcTrpcTcp0XE37_800Body, true),
|
||||||
|
field101: ProtoField(101, ScalarType.INT32),
|
||||||
|
field102: ProtoField(102, ScalarType.INT32),
|
||||||
|
field200: ProtoField(200, ScalarType.INT32)
|
||||||
|
};
|
||||||
|
|
||||||
|
export const OidbSvcTrpcTcp0XE37_800Body = {
|
||||||
|
senderUid: ProtoField(10, ScalarType.STRING, true),
|
||||||
|
receiverUid: ProtoField(20, ScalarType.STRING, true),
|
||||||
|
fileUuid: ProtoField(30, ScalarType.STRING, true),
|
||||||
|
fileHash: ProtoField(40, ScalarType.STRING, true)
|
||||||
|
};
|
||||||
|
|
||||||
|
export const OidbSvcTrpcTcp0XE37Response = {
|
||||||
|
command: ProtoField(1, ScalarType.UINT32),
|
||||||
|
seq: ProtoField(2, ScalarType.INT32),
|
||||||
|
upload: ProtoField(19, () => ApplyUploadRespV3, true),
|
||||||
|
businessId: ProtoField(101, ScalarType.INT32),
|
||||||
|
clientType: ProtoField(102, ScalarType.INT32),
|
||||||
|
flagSupportMediaPlatform: ProtoField(200, ScalarType.INT32)
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ApplyUploadRespV3 = {
|
||||||
|
retCode: ProtoField(10, ScalarType.INT32),
|
||||||
|
retMsg: ProtoField(20, ScalarType.STRING, true),
|
||||||
|
totalSpace: ProtoField(30, ScalarType.INT64),
|
||||||
|
usedSpace: ProtoField(40, ScalarType.INT64),
|
||||||
|
uploadedSize: ProtoField(50, ScalarType.INT64),
|
||||||
|
uploadIp: ProtoField(60, ScalarType.STRING, true),
|
||||||
|
uploadDomain: ProtoField(70, ScalarType.STRING, true),
|
||||||
|
uploadPort: ProtoField(80, ScalarType.UINT32),
|
||||||
|
uuid: ProtoField(90, ScalarType.STRING, true),
|
||||||
|
uploadKey: ProtoField(100, ScalarType.BYTES, true),
|
||||||
|
boolFileExist: ProtoField(110, ScalarType.BOOL),
|
||||||
|
packSize: ProtoField(120, ScalarType.INT32),
|
||||||
|
uploadIpList: ProtoField(130, ScalarType.STRING, false, true), // repeated
|
||||||
|
uploadHttpsPort: ProtoField(140, ScalarType.INT32),
|
||||||
|
uploadHttpsDomain: ProtoField(150, ScalarType.STRING, true),
|
||||||
|
uploadDns: ProtoField(160, ScalarType.STRING, true),
|
||||||
|
uploadLanip: ProtoField(170, ScalarType.STRING, true),
|
||||||
|
fileAddon: ProtoField(200, ScalarType.STRING, true),
|
||||||
|
mediaPlatformUploadKey: ProtoField(220, ScalarType.BYTES, true)
|
||||||
|
};
|
||||||
|
|
||||||
|
export const OidbSvcTrpcTcp0XE37_800Response = {
|
||||||
|
command: ProtoField(1, ScalarType.UINT32, true),
|
||||||
|
subCommand: ProtoField(2, ScalarType.UINT32, true),
|
||||||
|
body: ProtoField(10, () => OidbSvcTrpcTcp0XE37_800ResponseBody, true),
|
||||||
|
field50: ProtoField(50, ScalarType.UINT32, true),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const OidbSvcTrpcTcp0XE37_800ResponseBody = {
|
||||||
|
field10: ProtoField(10, ScalarType.UINT32, true),
|
||||||
|
field30: ProtoField(30, () => OidbSvcTrpcTcp0XE37_800_1200Metadata, true),
|
||||||
|
};
|
@@ -14,6 +14,7 @@ export const OidbSvcTrpcTcp0X9067_202Key = {
|
|||||||
//Rsp
|
//Rsp
|
||||||
export const OidbSvcTrpcTcp0X9067_202_RkeyList = {
|
export const OidbSvcTrpcTcp0X9067_202_RkeyList = {
|
||||||
rkey: ProtoField(1, ScalarType.STRING),
|
rkey: ProtoField(1, ScalarType.STRING),
|
||||||
|
ttl: ProtoField(2, ScalarType.UINT64),
|
||||||
time: ProtoField(4, ScalarType.UINT32),
|
time: ProtoField(4, ScalarType.UINT32),
|
||||||
type: ProtoField(5, ScalarType.UINT32),
|
type: ProtoField(5, ScalarType.UINT32),
|
||||||
|
|
||||||
|
@@ -30,7 +30,7 @@ export const OidbSvcTrpcTcp0XE37_1200ResponseBody = {
|
|||||||
field10: ProtoField(10, ScalarType.UINT32, true),
|
field10: ProtoField(10, ScalarType.UINT32, true),
|
||||||
state: ProtoField(20, ScalarType.STRING, true),
|
state: ProtoField(20, ScalarType.STRING, true),
|
||||||
result: ProtoField(30, () => OidbSvcTrpcTcp0XE37_1200Result, true),
|
result: ProtoField(30, () => OidbSvcTrpcTcp0XE37_1200Result, true),
|
||||||
metadata: ProtoField(40, () => OidbSvcTrpcTcp0XE37_1200Metadata, true),
|
metadata: ProtoField(40, () => OidbSvcTrpcTcp0XE37_800_1200Metadata, true),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const OidbSvcTrpcTcp0XE37_1200Result = {
|
export const OidbSvcTrpcTcp0XE37_1200Result = {
|
||||||
@@ -43,7 +43,7 @@ export const OidbSvcTrpcTcp0XE37_1200Result = {
|
|||||||
extra: ProtoField(120, ScalarType.BYTES, true),
|
extra: ProtoField(120, ScalarType.BYTES, true),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const OidbSvcTrpcTcp0XE37_1200Metadata = {
|
export const OidbSvcTrpcTcp0XE37_800_1200Metadata = {
|
||||||
uin: ProtoField(1, ScalarType.UINT32, true),
|
uin: ProtoField(1, ScalarType.UINT32, true),
|
||||||
field2: ProtoField(2, ScalarType.UINT32, true),
|
field2: ProtoField(2, ScalarType.UINT32, true),
|
||||||
field3: ProtoField(3, ScalarType.UINT32, true),
|
field3: ProtoField(3, ScalarType.UINT32, true),
|
||||||
|
23
src/core/packet/proto/oidb/Oidb.0xE37_1700.ts
Normal file
23
src/core/packet/proto/oidb/Oidb.0xE37_1700.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { ScalarType } from "@protobuf-ts/runtime";
|
||||||
|
import { ProtoField } from "../NapProto";
|
||||||
|
|
||||||
|
export const OidbSvcTrpcTcp0XE37_1700 = {
|
||||||
|
command: ProtoField(1, ScalarType.UINT32, true),
|
||||||
|
seq: ProtoField(2, ScalarType.INT32, true),
|
||||||
|
upload: ProtoField(19, () => ApplyUploadReqV3, true),
|
||||||
|
businessId: ProtoField(101, ScalarType.INT32, true),
|
||||||
|
clientType: ProtoField(102, ScalarType.INT32, true),
|
||||||
|
flagSupportMediaPlatform: ProtoField(200, ScalarType.INT32, true),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ApplyUploadReqV3 = {
|
||||||
|
senderUid: ProtoField(10, ScalarType.STRING, true),
|
||||||
|
receiverUid: ProtoField(20, ScalarType.STRING, true),
|
||||||
|
fileSize: ProtoField(30, ScalarType.UINT32, true),
|
||||||
|
fileName: ProtoField(40, ScalarType.STRING, true),
|
||||||
|
md510MCheckSum: ProtoField(50, ScalarType.BYTES, true),
|
||||||
|
sha1CheckSum: ProtoField(60, ScalarType.BYTES, true),
|
||||||
|
localPath: ProtoField(70, ScalarType.STRING, true),
|
||||||
|
md5CheckSum: ProtoField(110, ScalarType.BYTES, true),
|
||||||
|
sha3CheckSum: ProtoField(120, ScalarType.BYTES, true),
|
||||||
|
};
|
12
src/core/packet/proto/oidb/Oidb.0xEB7.ts
Normal file
12
src/core/packet/proto/oidb/Oidb.0xEB7.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { ScalarType } from "@protobuf-ts/runtime";
|
||||||
|
import { ProtoField } from "../NapProto";
|
||||||
|
|
||||||
|
export const OidbSvcTrpcTcp0XEB7_Body = {
|
||||||
|
uin: ProtoField(1, ScalarType.STRING),
|
||||||
|
groupUin: ProtoField(2, ScalarType.STRING),
|
||||||
|
version: ProtoField(3, ScalarType.STRING),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const OidbSvcTrpcTcp0XEB7 = {
|
||||||
|
body: ProtoField(2, () => OidbSvcTrpcTcp0XEB7_Body),
|
||||||
|
};
|
@@ -160,7 +160,7 @@ export const PicUrlExtInfo = {
|
|||||||
|
|
||||||
export const VideoExtInfo = {
|
export const VideoExtInfo = {
|
||||||
VideoCodecFormat: ProtoField(1, ScalarType.UINT32),
|
VideoCodecFormat: ProtoField(1, ScalarType.UINT32),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const ExtBizInfo = {
|
export const ExtBizInfo = {
|
||||||
Pic: ProtoField(1, () => PicExtBizInfo),
|
Pic: ProtoField(1, () => PicExtBizInfo),
|
||||||
|
@@ -13,20 +13,20 @@ export const NTV2RichMediaResp = {
|
|||||||
uploadKeyRenewal: ProtoField(8, () => UploadKeyRenewalResp),
|
uploadKeyRenewal: ProtoField(8, () => UploadKeyRenewalResp),
|
||||||
downloadSafe: ProtoField(9, () => DownloadSafeResp),
|
downloadSafe: ProtoField(9, () => DownloadSafeResp),
|
||||||
extension: ProtoField(99, ScalarType.BYTES, true),
|
extension: ProtoField(99, ScalarType.BYTES, true),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const MultiMediaRespHead = {
|
export const MultiMediaRespHead = {
|
||||||
common: ProtoField(1, () => CommonHead),
|
common: ProtoField(1, () => CommonHead),
|
||||||
retCode: ProtoField(2, ScalarType.UINT32),
|
retCode: ProtoField(2, ScalarType.UINT32),
|
||||||
message: ProtoField(3, ScalarType.STRING),
|
message: ProtoField(3, ScalarType.STRING),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const DownloadResp = {
|
export const DownloadResp = {
|
||||||
rKeyParam: ProtoField(1, ScalarType.STRING),
|
rKeyParam: ProtoField(1, ScalarType.STRING),
|
||||||
rKeyTtlSecond: ProtoField(2, ScalarType.UINT32),
|
rKeyTtlSecond: ProtoField(2, ScalarType.UINT32),
|
||||||
info: ProtoField(3, () => DownloadInfo),
|
info: ProtoField(3, () => DownloadInfo),
|
||||||
rKeyCreateTime: ProtoField(4, ScalarType.UINT32),
|
rKeyCreateTime: ProtoField(4, ScalarType.UINT32),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const DownloadInfo = {
|
export const DownloadInfo = {
|
||||||
domain: ProtoField(1, ScalarType.STRING),
|
domain: ProtoField(1, ScalarType.STRING),
|
||||||
@@ -36,7 +36,7 @@ export const DownloadInfo = {
|
|||||||
ipv6s: ProtoField(5, () => IPv6, false, true),
|
ipv6s: ProtoField(5, () => IPv6, false, true),
|
||||||
picUrlExtInfo: ProtoField(6, () => PicUrlExtInfo),
|
picUrlExtInfo: ProtoField(6, () => PicUrlExtInfo),
|
||||||
videoExtInfo: ProtoField(7, () => VideoExtInfo),
|
videoExtInfo: ProtoField(7, () => VideoExtInfo),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const IPv4 = {
|
export const IPv4 = {
|
||||||
outIP: ProtoField(1, ScalarType.UINT32),
|
outIP: ProtoField(1, ScalarType.UINT32),
|
||||||
@@ -44,7 +44,7 @@ export const IPv4 = {
|
|||||||
inIP: ProtoField(3, ScalarType.UINT32),
|
inIP: ProtoField(3, ScalarType.UINT32),
|
||||||
inPort: ProtoField(4, ScalarType.UINT32),
|
inPort: ProtoField(4, ScalarType.UINT32),
|
||||||
ipType: ProtoField(5, ScalarType.UINT32),
|
ipType: ProtoField(5, ScalarType.UINT32),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const IPv6 = {
|
export const IPv6 = {
|
||||||
outIP: ProtoField(1, ScalarType.BYTES),
|
outIP: ProtoField(1, ScalarType.BYTES),
|
||||||
@@ -52,7 +52,7 @@ export const IPv6 = {
|
|||||||
inIP: ProtoField(3, ScalarType.BYTES),
|
inIP: ProtoField(3, ScalarType.BYTES),
|
||||||
inPort: ProtoField(4, ScalarType.UINT32),
|
inPort: ProtoField(4, ScalarType.UINT32),
|
||||||
ipType: ProtoField(5, ScalarType.UINT32),
|
ipType: ProtoField(5, ScalarType.UINT32),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const UploadResp = {
|
export const UploadResp = {
|
||||||
uKey: ProtoField(1, ScalarType.STRING, true),
|
uKey: ProtoField(1, ScalarType.STRING, true),
|
||||||
@@ -64,13 +64,13 @@ export const UploadResp = {
|
|||||||
ext: ProtoField(7, () => RichMediaStorageTransInfo, false, true),
|
ext: ProtoField(7, () => RichMediaStorageTransInfo, false, true),
|
||||||
compatQMsg: ProtoField(8, ScalarType.BYTES),
|
compatQMsg: ProtoField(8, ScalarType.BYTES),
|
||||||
subFileInfos: ProtoField(10, () => SubFileInfo, false, true),
|
subFileInfos: ProtoField(10, () => SubFileInfo, false, true),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const RichMediaStorageTransInfo = {
|
export const RichMediaStorageTransInfo = {
|
||||||
subType: ProtoField(1, ScalarType.UINT32),
|
subType: ProtoField(1, ScalarType.UINT32),
|
||||||
extType: ProtoField(2, ScalarType.UINT32),
|
extType: ProtoField(2, ScalarType.UINT32),
|
||||||
extValue: ProtoField(3, ScalarType.BYTES),
|
extValue: ProtoField(3, ScalarType.BYTES),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const SubFileInfo = {
|
export const SubFileInfo = {
|
||||||
subType: ProtoField(1, ScalarType.UINT32),
|
subType: ProtoField(1, ScalarType.UINT32),
|
||||||
@@ -78,32 +78,32 @@ export const SubFileInfo = {
|
|||||||
uKeyTtlSecond: ProtoField(3, ScalarType.UINT32),
|
uKeyTtlSecond: ProtoField(3, ScalarType.UINT32),
|
||||||
ipv4s: ProtoField(4, () => IPv4, false, true),
|
ipv4s: ProtoField(4, () => IPv4, false, true),
|
||||||
ipv6s: ProtoField(5, () => IPv6, false, true),
|
ipv6s: ProtoField(5, () => IPv6, false, true),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const DownloadSafeResp = {
|
export const DownloadSafeResp = {
|
||||||
}
|
};
|
||||||
|
|
||||||
export const UploadKeyRenewalResp = {
|
export const UploadKeyRenewalResp = {
|
||||||
ukey: ProtoField(1, ScalarType.STRING),
|
ukey: ProtoField(1, ScalarType.STRING),
|
||||||
ukeyTtlSec: ProtoField(2, ScalarType.UINT64),
|
ukeyTtlSec: ProtoField(2, ScalarType.UINT64),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const MsgInfoAuthResp = {
|
export const MsgInfoAuthResp = {
|
||||||
authCode: ProtoField(1, ScalarType.UINT32),
|
authCode: ProtoField(1, ScalarType.UINT32),
|
||||||
msg: ProtoField(2, ScalarType.BYTES),
|
msg: ProtoField(2, ScalarType.BYTES),
|
||||||
resultTime: ProtoField(3, ScalarType.UINT64),
|
resultTime: ProtoField(3, ScalarType.UINT64),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const UploadCompletedResp = {
|
export const UploadCompletedResp = {
|
||||||
msgSeq: ProtoField(1, ScalarType.UINT64),
|
msgSeq: ProtoField(1, ScalarType.UINT64),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const DeleteResp = {
|
export const DeleteResp = {
|
||||||
}
|
};
|
||||||
|
|
||||||
export const DownloadRKeyResp = {
|
export const DownloadRKeyResp = {
|
||||||
rKeys: ProtoField(1, () => RKeyInfo, false, true),
|
rKeys: ProtoField(1, () => RKeyInfo, false, true),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const RKeyInfo = {
|
export const RKeyInfo = {
|
||||||
rkey: ProtoField(1, ScalarType.STRING),
|
rkey: ProtoField(1, ScalarType.STRING),
|
||||||
@@ -111,4 +111,4 @@ export const RKeyInfo = {
|
|||||||
storeId: ProtoField(3, ScalarType.UINT32),
|
storeId: ProtoField(3, ScalarType.UINT32),
|
||||||
rkeyCreateTime: ProtoField(4, ScalarType.UINT32, true),
|
rkeyCreateTime: ProtoField(4, ScalarType.UINT32, true),
|
||||||
type: ProtoField(5, ScalarType.UINT32, true),
|
type: ProtoField(5, ScalarType.UINT32, true),
|
||||||
}
|
};
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
import * as crypto from 'crypto';
|
import * as crypto from 'crypto';
|
||||||
import * as stream from 'stream';
|
import * as stream from 'stream';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
|
import { CalculateStreamBytesTransform } from "@/core/packet/utils/crypto/sha1StreamBytesTransform";
|
||||||
|
|
||||||
function sha1Stream(readable: stream.Readable) {
|
function sha1Stream(readable: stream.Readable) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -10,7 +11,37 @@ function sha1Stream(readable: stream.Readable) {
|
|||||||
}) as Promise<Buffer>;
|
}) as Promise<Buffer>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function md5Stream(readable: stream.Readable) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
readable.on('error', reject);
|
||||||
|
readable.pipe(crypto.createHash('md5').on('error', reject).on('data', resolve));
|
||||||
|
}) as Promise<Buffer>;
|
||||||
|
}
|
||||||
|
|
||||||
export function calculateSha1(filePath: string): Promise<Buffer> {
|
export function calculateSha1(filePath: string): Promise<Buffer> {
|
||||||
const readable = fs.createReadStream(filePath);
|
const readable = fs.createReadStream(filePath);
|
||||||
return sha1Stream(readable);
|
return sha1Stream(readable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function computeMd5AndLengthWithLimit(filePath: string, limit?: number): Promise<Buffer> {
|
||||||
|
const readStream = fs.createReadStream(filePath, limit ? { start: 0, end: limit - 1 } : {});
|
||||||
|
return md5Stream(readStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function calculateSha1StreamBytes(filePath: string): Promise<Buffer[]> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const readable = fs.createReadStream(filePath);
|
||||||
|
const calculateStreamBytes = new CalculateStreamBytesTransform();
|
||||||
|
const byteArrayList: Buffer[] = [];
|
||||||
|
calculateStreamBytes.on('data', (chunk: Buffer) => {
|
||||||
|
byteArrayList.push(chunk);
|
||||||
|
});
|
||||||
|
calculateStreamBytes.on('end', () => {
|
||||||
|
resolve(byteArrayList);
|
||||||
|
});
|
||||||
|
calculateStreamBytes.on('error', (err) => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
readable.pipe(calculateStreamBytes);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
19
src/core/packet/utils/crypto/sha1Stream.test.ts
Normal file
19
src/core/packet/utils/crypto/sha1Stream.test.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import crypto from 'crypto';
|
||||||
|
import assert from 'assert';
|
||||||
|
import { Sha1Stream } from './sha1Stream';
|
||||||
|
|
||||||
|
function testSha1Stream() {
|
||||||
|
for (let i = 0; i < 100000; i++) {
|
||||||
|
const randomLength = Math.floor(Math.random() * 1024);
|
||||||
|
const randomData = crypto.randomBytes(randomLength);
|
||||||
|
const sha1Stream = new Sha1Stream();
|
||||||
|
sha1Stream.update(randomData);
|
||||||
|
const hash = sha1Stream.final();
|
||||||
|
const expectedDigest = crypto.createHash('sha1').update(randomData).digest();
|
||||||
|
assert.strictEqual(hash.toString('hex'), expectedDigest.toString('hex'));
|
||||||
|
console.log(`Test ${i + 1}: Passed`);
|
||||||
|
}
|
||||||
|
console.log('All tests passed successfully.');
|
||||||
|
}
|
||||||
|
|
||||||
|
testSha1Stream();
|
118
src/core/packet/utils/crypto/sha1Stream.ts
Normal file
118
src/core/packet/utils/crypto/sha1Stream.ts
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
export class Sha1Stream {
|
||||||
|
readonly Sha1BlockSize = 64;
|
||||||
|
readonly Sha1DigestSize = 20;
|
||||||
|
private readonly _padding = Buffer.concat([Buffer.from([0x80]), Buffer.alloc(63)]);
|
||||||
|
private readonly _state = new Uint32Array(5);
|
||||||
|
private readonly _count = new Uint32Array(2);
|
||||||
|
private readonly _buffer = Buffer.allocUnsafe(this.Sha1BlockSize);
|
||||||
|
private readonly _w = new Uint32Array(80);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private reset(): void {
|
||||||
|
this._state[0] = 0x67452301;
|
||||||
|
this._state[1] = 0xEFCDAB89;
|
||||||
|
this._state[2] = 0x98BADCFE;
|
||||||
|
this._state[3] = 0x10325476;
|
||||||
|
this._state[4] = 0xC3D2E1F0;
|
||||||
|
this._count[0] = 0;
|
||||||
|
this._count[1] = 0;
|
||||||
|
this._buffer.fill(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private rotateLeft(v: number, o: number): number {
|
||||||
|
return ((v << o) | (v >>> (32 - o))) >>> 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private transform(chunk: Buffer, offset: number): void {
|
||||||
|
const w = this._w;
|
||||||
|
const view = new DataView(chunk.buffer, chunk.byteOffset + offset, 64);
|
||||||
|
|
||||||
|
for (let i = 0; i < 16; i++) {
|
||||||
|
w[i] = view.getUint32(i * 4, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 16; i < 80; i++) {
|
||||||
|
w[i] = this.rotateLeft(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1) >>> 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let a = this._state[0];
|
||||||
|
let b = this._state[1];
|
||||||
|
let c = this._state[2];
|
||||||
|
let d = this._state[3];
|
||||||
|
let e = this._state[4];
|
||||||
|
|
||||||
|
for (let i = 0; i < 80; i++) {
|
||||||
|
const [f, k] = (i < 20) ? [(b & c) | ((~b) & d), 0x5A827999] :
|
||||||
|
(i < 40) ? [b ^ c ^ d, 0x6ED9EBA1] :
|
||||||
|
(i < 60) ? [(b & c) | (b & d) | (c & d), 0x8F1BBCDC] :
|
||||||
|
[b ^ c ^ d, 0xCA62C1D6];
|
||||||
|
const temp = (this.rotateLeft(a, 5) + f + k + e + w[i]) >>> 0;
|
||||||
|
e = d;
|
||||||
|
d = c;
|
||||||
|
c = this.rotateLeft(b, 30) >>> 0;
|
||||||
|
b = a;
|
||||||
|
a = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._state[0] = (this._state[0] + a) >>> 0;
|
||||||
|
this._state[1] = (this._state[1] + b) >>> 0;
|
||||||
|
this._state[2] = (this._state[2] + c) >>> 0;
|
||||||
|
this._state[3] = (this._state[3] + d) >>> 0;
|
||||||
|
this._state[4] = (this._state[4] + e) >>> 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public update(data: Buffer, len?: number): void {
|
||||||
|
let index = ((this._count[0] >>> 3) & 0x3F) >>> 0;
|
||||||
|
const dataLen = len ?? data.length;
|
||||||
|
this._count[0] = (this._count[0] + (dataLen << 3)) >>> 0;
|
||||||
|
|
||||||
|
if (this._count[0] < (dataLen << 3)) this._count[1] = (this._count[1] + 1) >>> 0;
|
||||||
|
|
||||||
|
this._count[1] = (this._count[1] + (dataLen >>> 29)) >>> 0;
|
||||||
|
|
||||||
|
const partLen = (this.Sha1BlockSize - index) >>> 0;
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
if (dataLen >= partLen) {
|
||||||
|
data.copy(this._buffer, index, 0, partLen);
|
||||||
|
this.transform(this._buffer, 0);
|
||||||
|
for (i = partLen; (i + this.Sha1BlockSize) <= dataLen; i = (i + this.Sha1BlockSize) >>> 0) {
|
||||||
|
this.transform(data, i);
|
||||||
|
}
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.copy(this._buffer, index, i, dataLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
public hash(bigEndian: boolean = true): Buffer {
|
||||||
|
const digest = Buffer.allocUnsafe(this.Sha1DigestSize);
|
||||||
|
if (bigEndian) {
|
||||||
|
for (let i = 0; i < 5; i++) digest.writeUInt32BE(this._state[i], i * 4);
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < 5; i++) digest.writeUInt32LE(this._state[i], i * 4);
|
||||||
|
}
|
||||||
|
return digest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final(): Buffer {
|
||||||
|
const digest = Buffer.allocUnsafe(this.Sha1DigestSize);
|
||||||
|
const bits = Buffer.allocUnsafe(8);
|
||||||
|
bits.writeUInt32BE(this._count[1], 0);
|
||||||
|
bits.writeUInt32BE(this._count[0], 4);
|
||||||
|
|
||||||
|
const index = ((this._count[0] >>> 3) & 0x3F) >>> 0;
|
||||||
|
const padLen = ((index < 56) ? (56 - index) : (120 - index)) >>> 0;
|
||||||
|
this.update(this._padding, padLen);
|
||||||
|
this.update(bits);
|
||||||
|
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
digest.writeUInt32BE(this._state[i], i * 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
return digest;
|
||||||
|
}
|
||||||
|
}
|
53
src/core/packet/utils/crypto/sha1StreamBytesTransform.ts
Normal file
53
src/core/packet/utils/crypto/sha1StreamBytesTransform.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import * as stream from "node:stream";
|
||||||
|
import { Sha1Stream } from "@/core/packet/utils/crypto/sha1Stream";
|
||||||
|
|
||||||
|
export class CalculateStreamBytesTransform extends stream.Transform {
|
||||||
|
private readonly blockSize = 1024 * 1024;
|
||||||
|
private sha1: Sha1Stream;
|
||||||
|
private buffer: Buffer;
|
||||||
|
private bytesRead: number;
|
||||||
|
private readonly byteArrayList: Buffer[];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.sha1 = new Sha1Stream();
|
||||||
|
this.buffer = Buffer.alloc(0);
|
||||||
|
this.bytesRead = 0;
|
||||||
|
this.byteArrayList = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
_transform(chunk: Buffer, _: BufferEncoding, callback: stream.TransformCallback): void {
|
||||||
|
try {
|
||||||
|
this.buffer = Buffer.concat([this.buffer, chunk]);
|
||||||
|
let offset = 0;
|
||||||
|
while (this.buffer.length - offset >= this.sha1.Sha1BlockSize) {
|
||||||
|
const block = this.buffer.subarray(offset, offset + this.sha1.Sha1BlockSize);
|
||||||
|
this.sha1.update(block);
|
||||||
|
offset += this.sha1.Sha1BlockSize;
|
||||||
|
this.bytesRead += this.sha1.Sha1BlockSize;
|
||||||
|
if (this.bytesRead % this.blockSize === 0) {
|
||||||
|
const digest = this.sha1.hash(false);
|
||||||
|
this.byteArrayList.push(Buffer.from(digest));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.buffer = this.buffer.subarray(offset);
|
||||||
|
callback(null);
|
||||||
|
} catch (err) {
|
||||||
|
callback(err as Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_flush(callback: stream.TransformCallback): void {
|
||||||
|
try {
|
||||||
|
if (this.buffer.length > 0) this.sha1.update(this.buffer);
|
||||||
|
const finalDigest = this.sha1.final();
|
||||||
|
this.byteArrayList.push(Buffer.from(finalDigest));
|
||||||
|
for (const digest of this.byteArrayList) {
|
||||||
|
this.push(digest);
|
||||||
|
}
|
||||||
|
callback(null);
|
||||||
|
} catch (err) {
|
||||||
|
callback(err as Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -76,7 +76,7 @@ export function decrypt(encrypted: Buffer, key: Buffer) {
|
|||||||
encrypted.writeInt32BE(r1, i);
|
encrypted.writeInt32BE(r1, i);
|
||||||
encrypted.writeInt32BE(r2, i + 4);
|
encrypted.writeInt32BE(r2, i + 4);
|
||||||
}
|
}
|
||||||
if (Buffer.compare(encrypted.subarray(encrypted.length - 7), BUF7) !== 0) throw ERROR_ENCRYPTED_ILLEGAL
|
if (Buffer.compare(encrypted.subarray(encrypted.length - 7), BUF7) !== 0) throw ERROR_ENCRYPTED_ILLEGAL;
|
||||||
// if (Buffer.compare(encrypted.slice(encrypted.length - 7), BUF7) !== 0) throw ERROR_ENCRYPTED_ILLEGAL;
|
// if (Buffer.compare(encrypted.slice(encrypted.length - 7), BUF7) !== 0) throw ERROR_ENCRYPTED_ILLEGAL;
|
||||||
return encrypted.subarray((encrypted[0] & 0x07) + 3, encrypted.length - 7);
|
return encrypted.subarray((encrypted[0] & 0x07) + 3, encrypted.length - 7);
|
||||||
// return encrypted.slice((encrypted[0] & 0x07) + 3, encrypted.length - 7);
|
// return encrypted.slice((encrypted[0] & 0x07) + 3, encrypted.length - 7);
|
||||||
|
@@ -36,7 +36,7 @@ export interface NodeIKernelBuddyService {
|
|||||||
|
|
||||||
getBuddyRemark(uid: number): string;
|
getBuddyRemark(uid: number): string;
|
||||||
|
|
||||||
setBuddyRemark(uid: string, remark: string): void;
|
setBuddyRemark(param: { uid: string, remark: string, signInfo?: unknown }): void;
|
||||||
|
|
||||||
getAvatarUrl(uid: number): string;
|
getAvatarUrl(uid: number): string;
|
||||||
|
|
||||||
@@ -66,7 +66,11 @@ export interface NodeIKernelBuddyService {
|
|||||||
accept: boolean;
|
accept: boolean;
|
||||||
}): Promise<void>;
|
}): Promise<void>;
|
||||||
|
|
||||||
delBuddy(uid: number): void;
|
delBuddy(param: {
|
||||||
|
friendUid: string;
|
||||||
|
tempBlock: boolean;
|
||||||
|
tempBothDel: boolean;
|
||||||
|
}): Promise<unknown>;
|
||||||
|
|
||||||
delBatchBuddy(uids: number[]): void;
|
delBatchBuddy(uids: number[]): void;
|
||||||
|
|
||||||
|
@@ -12,6 +12,13 @@ import {
|
|||||||
import { GeneralCallResult } from '@/core/services/common';
|
import { GeneralCallResult } from '@/core/services/common';
|
||||||
|
|
||||||
export interface NodeIKernelGroupService {
|
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):
|
getGroupExt0xEF0Info(enableGroupCodes: string[], bannedGroupCodes: string[], filter: GroupExt0xEF0InfoFilter, forceFetch: boolean):
|
||||||
Promise<GeneralCallResult & { result: { groupExtInfos: Map<string, any> } }>;
|
Promise<GeneralCallResult & { result: { groupExtInfos: Map<string, any> } }>;
|
||||||
|
|
||||||
@@ -98,13 +105,13 @@ export interface NodeIKernelGroupService {
|
|||||||
uid: string,
|
uid: string,
|
||||||
index: number//0
|
index: number//0
|
||||||
}>,
|
}>,
|
||||||
infos: unknown,
|
infos: Map<string, GroupMember>,
|
||||||
finish: true,
|
finish: true,
|
||||||
hasRobot: false
|
hasRobot: false
|
||||||
}
|
}
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
setHeader(uid: string, path: string): unknown;
|
setHeader(uid: string, path: string): Promise<GeneralCallResult>;
|
||||||
|
|
||||||
addKernelGroupListener(listener: NodeIKernelGroupListener): number;
|
addKernelGroupListener(listener: NodeIKernelGroupListener): number;
|
||||||
|
|
||||||
@@ -114,7 +121,7 @@ export interface NodeIKernelGroupService {
|
|||||||
|
|
||||||
destroyMemberListScene(SceneId: string): void;
|
destroyMemberListScene(SceneId: string): void;
|
||||||
|
|
||||||
getNextMemberList(sceneId: string, a: undefined, num: number): Promise<{
|
getNextMemberList(sceneId: string, groupMemberInfoListId: { index: number, uid: string } | undefined, num: number): Promise<{
|
||||||
errCode: number,
|
errCode: number,
|
||||||
errMsg: string,
|
errMsg: string,
|
||||||
result: { ids: string[], infos: Map<string, GroupMember>, finish: boolean, hasRobot: boolean }
|
result: { ids: string[], infos: Map<string, GroupMember>, finish: boolean, hasRobot: boolean }
|
||||||
@@ -225,7 +232,16 @@ export interface NodeIKernelGroupService {
|
|||||||
|
|
||||||
getGroupStatisticInfo(groupCode: string): unknown;
|
getGroupStatisticInfo(groupCode: string): unknown;
|
||||||
|
|
||||||
getGroupRemainAtTimes(groupCode: string): number;
|
getGroupRemainAtTimes(groupCode: string): Promise<Omit<GeneralCallResult, 'result'> & {
|
||||||
|
errCode: number,
|
||||||
|
atInfo: {
|
||||||
|
canAtAll: boolean
|
||||||
|
RemainAtAllCountForUin: number
|
||||||
|
RemainAtAllCountForGroup: number
|
||||||
|
atTimesMsg: string
|
||||||
|
canNotAtAllMsg: ''
|
||||||
|
}
|
||||||
|
}>;
|
||||||
|
|
||||||
getJoinGroupNoVerifyFlag(groupCode: string): unknown;
|
getJoinGroupNoVerifyFlag(groupCode: string): unknown;
|
||||||
|
|
||||||
@@ -239,7 +255,7 @@ export interface NodeIKernelGroupService {
|
|||||||
|
|
||||||
setMemberShutUp(groupCode: string, memberTimes: { uid: string, timeStamp: number }[]): Promise<void>;
|
setMemberShutUp(groupCode: string, memberTimes: { uid: string, timeStamp: number }[]): Promise<void>;
|
||||||
|
|
||||||
getGroupRecommendContactArkJson(groupCode: string): unknown;
|
getGroupRecommendContactArkJson(groupCode: string): Promise<GeneralCallResult & { arkJson: string }>;
|
||||||
|
|
||||||
getJoinGroupLink(param: {
|
getJoinGroupLink(param: {
|
||||||
groupCode: string,
|
groupCode: string,
|
||||||
|
@@ -172,7 +172,7 @@ export interface NodeIKernelMsgService {
|
|||||||
msgList: RawMessage[]
|
msgList: RawMessage[]
|
||||||
}>;
|
}>;
|
||||||
//@deprecated
|
//@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[]
|
msgList: RawMessage[]
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
@@ -186,27 +186,29 @@ export interface NodeIKernelMsgService {
|
|||||||
|
|
||||||
getSingleMsg(Peer: Peer, msgSeq: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
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: {
|
getMsgsByTypeFilter(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilter: {
|
||||||
type: number,
|
type: number,
|
||||||
subtype: Array<number>
|
subtype: Array<number>
|
||||||
}): unknown;
|
}): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||||
|
|
||||||
getMsgsByTypeFilters(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilters: Array<{
|
getMsgsByTypeFilters(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilters: Array<{
|
||||||
type: number,
|
type: number,
|
||||||
subtype: Array<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>;
|
//queryMsgsWithFilterVer2(MsgId: string, MsgTime: string, param: QueryMsgsParams): Promise<unknown>;
|
||||||
|
|
||||||
@@ -327,8 +329,7 @@ export interface NodeIKernelMsgService {
|
|||||||
|
|
||||||
setPttPlayedState(...args: unknown[]): unknown;
|
setPttPlayedState(...args: unknown[]): unknown;
|
||||||
|
|
||||||
//uk1 uk2 true
|
fetchFavEmojiList(str: string, num: number, backward: boolean, forceRefresh: boolean): Promise<GeneralCallResult & {
|
||||||
fetchFavEmojiList(str: string, num: number, uk1: boolean, uk2: boolean): Promise<GeneralCallResult & {
|
|
||||||
emojiInfoList: Array<{
|
emojiInfoList: Array<{
|
||||||
uin: string,
|
uin: string,
|
||||||
emoId: number,
|
emoId: number,
|
||||||
|
@@ -86,7 +86,7 @@ export interface NodeQQNTWrapperUtil {
|
|||||||
|
|
||||||
calcThumbSize(arg0: number, arg1: number, arg2: unknown): unknown;
|
calcThumbSize(arg0: number, arg1: number, arg2: unknown): unknown;
|
||||||
|
|
||||||
fullWordToHalfWord(arg0: string): unknown;
|
fullWordToHalfWord(word: string): unknown;
|
||||||
|
|
||||||
getNTUserDataInfoConfig(): unknown;
|
getNTUserDataInfoConfig(): unknown;
|
||||||
|
|
||||||
|
@@ -41,7 +41,7 @@ export async function NCoreInitFramework(
|
|||||||
online: true,
|
online: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger) as any);
|
loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger));
|
||||||
});
|
});
|
||||||
// 过早进入会导致addKernelMsgListener等Listener添加失败
|
// 过早进入会导致addKernelMsgListener等Listener添加失败
|
||||||
// await sleep(2500);
|
// await sleep(2500);
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import { GroupNotifyMsgStatus } from '@/core';
|
||||||
import BaseAction from '../BaseAction';
|
import BaseAction from '../BaseAction';
|
||||||
import { ActionName } from '../types';
|
import { ActionName } from '../types';
|
||||||
|
|
||||||
@@ -11,18 +12,22 @@ export default class GetGroupAddRequest extends BaseAction<null, OB11GroupReques
|
|||||||
actionName = ActionName.GetGroupIgnoreAddRequest;
|
actionName = ActionName.GetGroupIgnoreAddRequest;
|
||||||
|
|
||||||
async _handle(payload: null): Promise<OB11GroupRequestNotify[] | null> {
|
async _handle(payload: null): Promise<OB11GroupRequestNotify[] | null> {
|
||||||
// const data = await this.core.apis.GroupApi.getGroupIgnoreNotifies();
|
const ignoredNotifies = await this.core.apis.GroupApi.getSingleScreenNotifies(true, 10);
|
||||||
// log(data);
|
const retData: any = {
|
||||||
// const notifies: GroupNotify[] = data.notifies.filter(notify => notify.status === GroupNotifyStatus.WAIT_HANDLE);
|
join_requests: await Promise.all(
|
||||||
// const returnData: OB11GroupRequestNotify[] = [];
|
ignoredNotifies
|
||||||
// for (const notify of notifies) {
|
.filter(notify => notify.type === 7)
|
||||||
// const uin = || (await NTQQUserApi.getUserDetailInfo(notify.user1.uid))?.uin;
|
.map(async SSNotify => ({
|
||||||
// returnData.push({
|
request_id: SSNotify.seq,
|
||||||
// group_id: parseInt(notify.group.groupCode),
|
requester_uin: await this.core.apis.UserApi.getUinByUidV2(SSNotify.user1?.uid),
|
||||||
// user_id: parseInt(uin),
|
requester_nick: SSNotify.user1?.nickName,
|
||||||
// flag: notify.seq
|
group_id: SSNotify.group?.groupCode,
|
||||||
// });
|
group_name: SSNotify.group?.groupName,
|
||||||
// }
|
checked: SSNotify.status !== GroupNotifyMsgStatus.KUNHANDLE,
|
||||||
return null;
|
actor: await this.core.apis.UserApi.getUinByUidV2(SSNotify.user2?.uid) || 0,
|
||||||
|
}))),
|
||||||
|
};
|
||||||
|
|
||||||
|
return retData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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 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;
|
actionName = ActionName.GetProfileLike;
|
||||||
|
|
||||||
async _handle(payload: void) {
|
async _handle(payload: Payload) {
|
||||||
const ret = await this.core.apis.UserApi.getProfileLike(this.core.selfInfo.uid);
|
const start = payload.start ? Number(payload.start) : 0;
|
||||||
const listdata: any[] = ret.info.userLikeInfos[0].favoriteInfo.userInfos;
|
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++) {
|
for (let i = 0; i < listdata.length; i++) {
|
||||||
listdata[i].uin = parseInt((await this.core.apis.UserApi.getUinByUidV2(listdata[i].uid)) || '');
|
listdata[i].uin = parseInt((await this.core.apis.UserApi.getUinByUidV2(listdata[i].uid)) || '');
|
||||||
}
|
}
|
||||||
return listdata;
|
return ret.info.userLikeInfos[0].voteInfo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
22
src/onebot/action/extends/SetGroupSign.ts
Normal file
22
src/onebot/action/extends/SetGroupSign.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import BaseAction from '../BaseAction';
|
||||||
|
import { ActionName } from '../types';
|
||||||
|
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||||
|
|
||||||
|
const SchemaData = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
group_id: { type: 'string' },
|
||||||
|
},
|
||||||
|
required: ['group_id'],
|
||||||
|
} as const satisfies JSONSchema;
|
||||||
|
|
||||||
|
type Payload = FromSchema<typeof SchemaData>;
|
||||||
|
|
||||||
|
export class SetGroupSign extends BaseAction<Payload, any> {
|
||||||
|
actionName = ActionName.SetGroupSign;
|
||||||
|
payloadSchema = SchemaData;
|
||||||
|
|
||||||
|
async _handle(payload: Payload) {
|
||||||
|
return await this.core.apis.PacketApi.sendGroupSignPacket(payload.group_id);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,16 +1,15 @@
|
|||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||||
import BaseAction from '../BaseAction';
|
import BaseAction from '../BaseAction';
|
||||||
import { ActionName } from '../types';
|
import { ActionName, BaseCheckResult } from '../types';
|
||||||
import { ChatType, Peer } from '@/core';
|
import { ChatType, Peer } from '@/core';
|
||||||
|
|
||||||
const SchemaData = {
|
const SchemaData = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
eventType: { type: 'string' },
|
event_type: { type: 'number' },
|
||||||
group_id: { type: 'string' },
|
user_id: { type: ['number', 'string'] },
|
||||||
user_id: { type: 'string' },
|
|
||||||
},
|
},
|
||||||
required: ['eventType'],
|
required: ['event_type','user_id'],
|
||||||
} as const satisfies JSONSchema;
|
} as const satisfies JSONSchema;
|
||||||
|
|
||||||
type Payload = FromSchema<typeof SchemaData>;
|
type Payload = FromSchema<typeof SchemaData>;
|
||||||
@@ -19,23 +18,12 @@ export class SetInputStatus extends BaseAction<Payload, any> {
|
|||||||
actionName = ActionName.SetInputStatus;
|
actionName = ActionName.SetInputStatus;
|
||||||
|
|
||||||
async _handle(payload: Payload) {
|
async _handle(payload: Payload) {
|
||||||
let peer: Peer;
|
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
||||||
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');
|
if (!uid) throw new Error('uid is empty');
|
||||||
peer = {
|
const peer = {
|
||||||
chatType: ChatType.KCHATTYPEC2C,
|
chatType: ChatType.KCHATTYPEC2C,
|
||||||
peerUid: uid,
|
peerUid: uid,
|
||||||
};
|
};
|
||||||
} else {
|
return await this.core.apis.MsgApi.sendShowInputStatusReq(peer, payload.event_type);
|
||||||
throw new Error('请指定 group_id 或 user_id');
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.core.apis.MsgApi.sendShowInputStatusReq(peer, parseInt(payload.eventType));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -38,11 +38,10 @@ export default class SetAvatar extends BaseAction<Payload, null> {
|
|||||||
throw `头像${payload.file}设置失败,api无返回`;
|
throw `头像${payload.file}设置失败,api无返回`;
|
||||||
}
|
}
|
||||||
// log(`头像设置返回:${JSON.stringify(ret)}`)
|
// log(`头像设置返回:${JSON.stringify(ret)}`)
|
||||||
// @ts-ignore
|
if (ret.result as number == 1004022) {
|
||||||
if (ret['result'] == 1004022) {
|
|
||||||
throw `头像${payload.file}设置失败,文件可能不是图片格式`;
|
throw `头像${payload.file}设置失败,文件可能不是图片格式`;
|
||||||
} else if (ret['result'] != 0) {
|
} else if (ret.result != 0) {
|
||||||
throw `头像${payload.file}设置失败,未知的错误,${ret['result']}:${ret['errMsg']}`;
|
throw `头像${payload.file}设置失败,未知的错误,${ret.result}:${ret.errMsg}`;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fs.unlink(path, () => { });
|
fs.unlink(path, () => { });
|
||||||
|
@@ -5,9 +5,9 @@ import { ActionName } from '../types';
|
|||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||||
import { OB11MessageImage, OB11MessageVideo } from '@/onebot/types';
|
import { OB11MessageImage, OB11MessageVideo } from '@/onebot/types';
|
||||||
|
|
||||||
export interface GetFilePayload {
|
// interface GetFilePayload {
|
||||||
file: string; // 文件名或者fileUuid
|
// file: string; // 文件名或者fileUuid
|
||||||
}
|
// }
|
||||||
|
|
||||||
export interface GetFileResponse {
|
export interface GetFileResponse {
|
||||||
file?: string; // path
|
file?: string; // path
|
||||||
@@ -16,19 +16,25 @@ export interface GetFileResponse {
|
|||||||
file_name?: string;
|
file_name?: string;
|
||||||
base64?: string;
|
base64?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetFileBase_PayloadSchema = {
|
const GetFileBase_PayloadSchema = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
file: { type: 'string' },
|
file: { type: 'string' },
|
||||||
|
file_id: { type: 'string' }
|
||||||
},
|
},
|
||||||
required: ['file'],
|
oneOf: [
|
||||||
|
{ required: ['file'] },
|
||||||
|
{ required: ['file_id'] }
|
||||||
|
]
|
||||||
} as const satisfies JSONSchema;
|
} as const satisfies JSONSchema;
|
||||||
|
|
||||||
|
export type GetFilePayload = FromSchema<typeof GetFileBase_PayloadSchema>;
|
||||||
|
|
||||||
export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
|
export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
|
||||||
payloadSchema: any = GetFileBase_PayloadSchema;
|
payloadSchema = GetFileBase_PayloadSchema;
|
||||||
|
|
||||||
async _handle(payload: GetFilePayload): Promise<GetFileResponse> {
|
async _handle(payload: GetFilePayload): Promise<GetFileResponse> {
|
||||||
|
payload.file ||= payload.file_id || '';
|
||||||
//接收消息标记模式
|
//接收消息标记模式
|
||||||
const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file);
|
const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file);
|
||||||
if (contextMsgFile) {
|
if (contextMsgFile) {
|
||||||
@@ -115,27 +121,6 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetFile_PayloadSchema = {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
file_id: { type: 'string' },
|
|
||||||
file: { type: 'string' },
|
|
||||||
},
|
|
||||||
required: ['file_id'],
|
|
||||||
} as const satisfies JSONSchema;
|
|
||||||
|
|
||||||
type GetFile_Payload_Internal = FromSchema<typeof GetFile_PayloadSchema>;
|
|
||||||
|
|
||||||
interface GetFile_Payload extends GetFile_Payload_Internal {
|
|
||||||
file: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class GetFile extends GetFileBase {
|
export default class GetFile extends GetFileBase {
|
||||||
actionName = ActionName.GetFile;
|
actionName = ActionName.GetFile;
|
||||||
payloadSchema = GetFile_PayloadSchema;
|
|
||||||
|
|
||||||
async _handle(payload: GetFile_Payload): Promise<GetFileResponse> {
|
|
||||||
payload.file = payload.file_id;
|
|
||||||
return super._handle(payload);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -27,7 +27,7 @@ export class GetGroupFileUrl extends GetPacketStatusDepends<Payload, GetGroupFil
|
|||||||
if (contextMsgFile?.fileUUID) {
|
if (contextMsgFile?.fileUUID) {
|
||||||
return {
|
return {
|
||||||
url: await this.core.apis.PacketApi.sendGroupFileDownloadReq(+payload.group_id, contextMsgFile.fileUUID)
|
url: await this.core.apis.PacketApi.sendGroupFileDownloadReq(+payload.group_id, contextMsgFile.fileUUID)
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
throw new Error('real fileUUID not found!');
|
throw new Error('real fileUUID not found!');
|
||||||
}
|
}
|
||||||
|
@@ -5,9 +5,11 @@ import { promises as fs } from 'fs';
|
|||||||
import { decode } from 'silk-wasm';
|
import { decode } from 'silk-wasm';
|
||||||
const FFMPEG_PATH = process.env.FFMPEG_PATH || 'ffmpeg';
|
const FFMPEG_PATH = process.env.FFMPEG_PATH || 'ffmpeg';
|
||||||
|
|
||||||
interface Payload extends GetFilePayload {
|
const out_format = ['mp3' , 'amr' , 'wma' , 'm4a' , 'spx' , 'ogg' , 'wav' , 'flac'];
|
||||||
out_format: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac';
|
|
||||||
}
|
type Payload = {
|
||||||
|
out_format : string
|
||||||
|
} & GetFilePayload
|
||||||
|
|
||||||
export default class GetRecord extends GetFileBase {
|
export default class GetRecord extends GetFileBase {
|
||||||
actionName = ActionName.GetRecord;
|
actionName = ActionName.GetRecord;
|
||||||
@@ -17,12 +19,19 @@ export default class GetRecord extends GetFileBase {
|
|||||||
if (payload.out_format && typeof payload.out_format === 'string') {
|
if (payload.out_format && typeof payload.out_format === 'string') {
|
||||||
const inputFile = res.file;
|
const inputFile = res.file;
|
||||||
if (!inputFile) throw new Error('file not found');
|
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 pcmFile = `${inputFile}.pcm`;
|
||||||
const outputFile = `${inputFile}.${payload.out_format}`;
|
const outputFile = `${inputFile}.${payload.out_format}`;
|
||||||
try {
|
try {
|
||||||
await fs.access(inputFile);
|
await fs.access(inputFile);
|
||||||
|
try {
|
||||||
|
await fs.access(outputFile);
|
||||||
|
} catch (error) {
|
||||||
await this.decodeFile(inputFile, pcmFile);
|
await this.decodeFile(inputFile, pcmFile);
|
||||||
await this.convertFile(pcmFile, outputFile, payload.out_format);
|
await this.convertFile(pcmFile, outputFile, payload.out_format);
|
||||||
|
}
|
||||||
const base64Data = await fs.readFile(outputFile, { encoding: 'base64' });
|
const base64Data = await fs.readFile(outputFile, { encoding: 'base64' });
|
||||||
res.file = outputFile;
|
res.file = outputFile;
|
||||||
res.url = outputFile;
|
res.url = outputFile;
|
||||||
@@ -48,7 +57,8 @@ export default class GetRecord extends GetFileBase {
|
|||||||
|
|
||||||
private convertFile(inputFile: string, outputFile: string, format: string): Promise<void> {
|
private convertFile(inputFile: string, outputFile: string, format: string): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
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) => {
|
ffmpeg.on('close', (code) => {
|
||||||
if (code === 0) {
|
if (code === 0) {
|
||||||
|
27
src/onebot/action/go-cqhttp/GetGroupAtAllRemain.ts
Normal file
27
src/onebot/action/go-cqhttp/GetGroupAtAllRemain.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import BaseAction from '../BaseAction';
|
||||||
|
import { ActionName } from '../types';
|
||||||
|
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||||
|
|
||||||
|
const SchemaData = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
group_id: { type: ['number', 'string'] }
|
||||||
|
},
|
||||||
|
required: ['group_id'],
|
||||||
|
} as const satisfies JSONSchema;
|
||||||
|
type Payload = FromSchema<typeof SchemaData>;
|
||||||
|
|
||||||
|
export class GoCQHTTPGetGroupAtAllRemain extends BaseAction<Payload, any> {
|
||||||
|
actionName = ActionName.GoCQHTTP_GetGroupAtAllRemain;
|
||||||
|
payloadSchema = SchemaData;
|
||||||
|
|
||||||
|
async _handle(payload: Payload) {
|
||||||
|
const ret = await this.core.apis.GroupApi.getGroupRemainAtTimes(payload.group_id.toString());
|
||||||
|
const data = {
|
||||||
|
can_at_all: ret.atInfo.canAtAll,
|
||||||
|
remain_at_all_count_for_group: ret.atInfo.RemainAtAllCountForGroup,
|
||||||
|
remain_at_all_count_for_uin: ret.atInfo.RemainAtAllCountForUin
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
21
src/onebot/action/go-cqhttp/GoCQHTTPCheckUrlSafely.ts
Normal file
21
src/onebot/action/go-cqhttp/GoCQHTTPCheckUrlSafely.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import BaseAction from '../BaseAction';
|
||||||
|
import { ActionName } from '../types';
|
||||||
|
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||||
|
|
||||||
|
const SchemaData = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
url: { type: 'string' },
|
||||||
|
},
|
||||||
|
required: ['url'],
|
||||||
|
} as const satisfies JSONSchema;
|
||||||
|
type Payload = FromSchema<typeof SchemaData>;
|
||||||
|
|
||||||
|
export class GoCQHTTPCheckUrlSafely extends BaseAction<Payload, any> {
|
||||||
|
actionName = ActionName.GoCQHTTP_CheckUrlSafely;
|
||||||
|
payloadSchema = SchemaData;
|
||||||
|
|
||||||
|
async _handle(payload: Payload) {
|
||||||
|
return { level: 1 };
|
||||||
|
}
|
||||||
|
}
|
38
src/onebot/action/go-cqhttp/GoCQHTTPDeleteFriend.ts
Normal file
38
src/onebot/action/go-cqhttp/GoCQHTTPDeleteFriend.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import BaseAction from '../BaseAction';
|
||||||
|
import { ActionName } from '../types';
|
||||||
|
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||||
|
|
||||||
|
const SchemaData = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
friend_id: { type: ['string', 'number'] },
|
||||||
|
temp_block: { type: 'boolean' },
|
||||||
|
temp_both_del: { type: 'boolean' },
|
||||||
|
},
|
||||||
|
required: ['friend_id'],
|
||||||
|
} as const satisfies JSONSchema;
|
||||||
|
type Payload = FromSchema<typeof SchemaData>;
|
||||||
|
|
||||||
|
export class GoCQHTTPDeleteFriend extends BaseAction<Payload, any> {
|
||||||
|
actionName = ActionName.GoCQHTTP_DeleteFriend;
|
||||||
|
payloadSchema = SchemaData;
|
||||||
|
|
||||||
|
async _handle(payload: Payload) {
|
||||||
|
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.friend_id.toString());
|
||||||
|
|
||||||
|
if (!uid) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
message: '好友不存在',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const isBuddy = await this.core.apis.FriendApi.isBuddy(uid);
|
||||||
|
if (!isBuddy) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
message: '不是好友',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return await this.core.apis.FriendApi.delBuudy(uid, payload.temp_block, payload.temp_both_del);
|
||||||
|
}
|
||||||
|
}
|
28
src/onebot/action/go-cqhttp/GoCQHTTPGetModelShow.ts
Normal file
28
src/onebot/action/go-cqhttp/GoCQHTTPGetModelShow.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import BaseAction from '../BaseAction';
|
||||||
|
import { ActionName } from '../types';
|
||||||
|
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||||
|
|
||||||
|
const SchemaData = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
model: { type: 'string' },
|
||||||
|
}
|
||||||
|
} as const satisfies JSONSchema;
|
||||||
|
type Payload = FromSchema<typeof SchemaData>;
|
||||||
|
|
||||||
|
export class GoCQHTTPGetModelShow extends BaseAction<Payload, any> {
|
||||||
|
actionName = ActionName.GoCQHTTP_GetModelShow;
|
||||||
|
payloadSchema = SchemaData;
|
||||||
|
|
||||||
|
async _handle(payload: Payload) {
|
||||||
|
if (!payload.model) {
|
||||||
|
payload.model = 'napcat';
|
||||||
|
}
|
||||||
|
return [{
|
||||||
|
variants: {
|
||||||
|
model_show: "napcat",
|
||||||
|
need_pay: false
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
19
src/onebot/action/go-cqhttp/GoCQHTTPSetModelShow.ts
Normal file
19
src/onebot/action/go-cqhttp/GoCQHTTPSetModelShow.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import BaseAction from '../BaseAction';
|
||||||
|
import { ActionName } from '../types';
|
||||||
|
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||||
|
|
||||||
|
const SchemaData = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {},
|
||||||
|
} as const satisfies JSONSchema;
|
||||||
|
type Payload = FromSchema<typeof SchemaData>;
|
||||||
|
|
||||||
|
//兼容性代码
|
||||||
|
export class GoCQHTTPSetModelShow extends BaseAction<Payload, any> {
|
||||||
|
actionName = ActionName.GoCQHTTP_SetModelShow;
|
||||||
|
payloadSchema = SchemaData;
|
||||||
|
|
||||||
|
async _handle(payload: Payload) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@@ -31,15 +31,15 @@ export default class SetGroupPortrait extends BaseAction<Payload, any> {
|
|||||||
}
|
}
|
||||||
if (path) {
|
if (path) {
|
||||||
await checkFileReceived(path, 5000); // 文件不存在QQ会崩溃,需要提前判断
|
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, () => { });
|
fs.unlink(path, () => { });
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
throw `头像${payload.file}设置失败,api无返回`;
|
throw `头像${payload.file}设置失败,api无返回`;
|
||||||
}
|
}
|
||||||
if (ret['result'] == 1004022) {
|
if (ret.result as number == 1004022) {
|
||||||
throw `头像${payload.file}设置失败,文件可能不是图片格式或权限不足`;
|
throw `头像${payload.file}设置失败,文件可能不是图片格式或权限不足`;
|
||||||
} else if (ret['result'] != 0) {
|
} else if (ret.result != 0) {
|
||||||
throw `头像${payload.file}设置失败,未知的错误,${ret['result']}:${ret['errMsg']}`;
|
throw `头像${payload.file}设置失败,未知的错误,${ret.result}:${ret.errMsg}`;
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
} else {
|
} else {
|
||||||
|
@@ -1,10 +0,0 @@
|
|||||||
import BaseAction from '../BaseAction';
|
|
||||||
import { ActionName } from '../types';
|
|
||||||
|
|
||||||
export default class SetModelShow extends BaseAction<null, null> {
|
|
||||||
actionName = ActionName.SetModelShow;
|
|
||||||
|
|
||||||
async _handle(payload: null): Promise<null> {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -21,7 +21,14 @@ export class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
|
|||||||
|
|
||||||
async _handle(payload: Payload) {
|
async _handle(payload: Payload) {
|
||||||
const groupIdStr = payload.group_id.toString();
|
const groupIdStr = payload.group_id.toString();
|
||||||
const groupMembers = await this.core.apis.GroupApi.getGroupMembersV2(groupIdStr);
|
const noCache = payload.no_cache ? this.stringToBoolean(payload.no_cache) : false;
|
||||||
|
const memberCache = this.core.apis.GroupApi.groupMemberCache;
|
||||||
|
let groupMembers;
|
||||||
|
if (noCache) {
|
||||||
|
groupMembers = await this.core.apis.GroupApi.getGroupMembersV2(groupIdStr);
|
||||||
|
} else {
|
||||||
|
groupMembers = memberCache.get(groupIdStr) ?? await this.core.apis.GroupApi.getGroupMembersV2(groupIdStr);
|
||||||
|
}
|
||||||
|
|
||||||
const memberPromises = Array.from(groupMembers.values()).map(item =>
|
const memberPromises = Array.from(groupMembers.values()).map(item =>
|
||||||
OB11Entities.groupMember(groupIdStr, item)
|
OB11Entities.groupMember(groupIdStr, item)
|
||||||
@@ -30,4 +37,7 @@ export class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
|
|||||||
const MemberMap = new Map(_groupMembers.map(member => [member.user_id, member]));
|
const MemberMap = new Map(_groupMembers.map(member => [member.user_id, member]));
|
||||||
return Array.from(MemberMap.values());
|
return Array.from(MemberMap.values());
|
||||||
}
|
}
|
||||||
|
stringToBoolean(str: string | boolean): boolean {
|
||||||
|
return typeof str === 'boolean' ? str : str.toLowerCase() === "true";
|
||||||
|
}
|
||||||
}
|
}
|
@@ -18,6 +18,6 @@ export class GroupPoke extends GetPacketStatusDepends<Payload, any> {
|
|||||||
payloadSchema = SchemaData;
|
payloadSchema = SchemaData;
|
||||||
|
|
||||||
async _handle(payload: Payload) {
|
async _handle(payload: Payload) {
|
||||||
await this.core.apis.PacketApi.sendPokePacket(+payload.group_id, +payload.user_id);
|
await this.core.apis.PacketApi.sendPokePacket(+payload.user_id, +payload.group_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -71,7 +71,6 @@ import { FetchUserProfileLike } from './extends/FetchUserProfileLike';
|
|||||||
import { NapCatCore } from '@/core';
|
import { NapCatCore } from '@/core';
|
||||||
import { NapCatOneBot11Adapter } from '@/onebot';
|
import { NapCatOneBot11Adapter } from '@/onebot';
|
||||||
import GetGuildProfile from './guild/GetGuildProfile';
|
import GetGuildProfile from './guild/GetGuildProfile';
|
||||||
import SetModelShow from './go-cqhttp/SetModelShow';
|
|
||||||
import { SetInputStatus } from './extends/SetInputStatus';
|
import { SetInputStatus } from './extends/SetInputStatus';
|
||||||
import { GetCSRF } from './system/GetCSRF';
|
import { GetCSRF } from './system/GetCSRF';
|
||||||
import { DelGroupNotice } from './group/DelGroupNotice';
|
import { DelGroupNotice } from './group/DelGroupNotice';
|
||||||
@@ -91,6 +90,15 @@ import { GetGroupShutList } from './group/GetGroupShutList';
|
|||||||
import { GetGroupMemberList } from './group/GetGroupMemberList';
|
import { GetGroupMemberList } from './group/GetGroupMemberList';
|
||||||
import { GetGroupFileUrl } from "@/onebot/action/file/GetGroupFileUrl";
|
import { GetGroupFileUrl } from "@/onebot/action/file/GetGroupFileUrl";
|
||||||
import { GetPacketStatus } from "@/onebot/action/packet/GetPacketStatus";
|
import { GetPacketStatus } from "@/onebot/action/packet/GetPacketStatus";
|
||||||
|
import { FriendPoke } from "@/onebot/action/user/FriendPoke";
|
||||||
|
import { GetCredentials } from './system/GetCredentials';
|
||||||
|
import { SetGroupSign } from './extends/SetGroupSign';
|
||||||
|
import { GoCQHTTPGetGroupAtAllRemain } from './go-cqhttp/GetGroupAtAllRemain';
|
||||||
|
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";
|
||||||
|
|
||||||
|
|
||||||
export type ActionMap = Map<string, BaseAction<any, any>>;
|
export type ActionMap = Map<string, BaseAction<any, any>>;
|
||||||
@@ -113,6 +121,7 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
|
|||||||
new SetQQAvatar(obContext, core),
|
new SetQQAvatar(obContext, core),
|
||||||
new TranslateEnWordToZn(obContext, core),
|
new TranslateEnWordToZn(obContext, core),
|
||||||
new GetGroupRootFiles(obContext, core),
|
new GetGroupRootFiles(obContext, core),
|
||||||
|
new SetGroupSign(obContext, core),
|
||||||
// onebot11
|
// onebot11
|
||||||
new SendLike(obContext, core),
|
new SendLike(obContext, core),
|
||||||
new GetMsg(obContext, core),
|
new GetMsg(obContext, core),
|
||||||
@@ -147,6 +156,8 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
|
|||||||
new GetRobotUinRange(obContext, core),
|
new GetRobotUinRange(obContext, core),
|
||||||
new GetFriendWithCategory(obContext, core),
|
new GetFriendWithCategory(obContext, core),
|
||||||
//以下为go-cqhttp api
|
//以下为go-cqhttp api
|
||||||
|
new GoCQHTTPDeleteFriend(obContext, core),
|
||||||
|
new GoCQHTTPCheckUrlSafely(obContext, core),
|
||||||
new GetOnlineClient(obContext, core),
|
new GetOnlineClient(obContext, core),
|
||||||
new OCRImage(obContext, core),
|
new OCRImage(obContext, core),
|
||||||
new IOCRImage(obContext, core),
|
new IOCRImage(obContext, core),
|
||||||
@@ -154,6 +165,7 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
|
|||||||
new SendGroupNotice(obContext, core),
|
new SendGroupNotice(obContext, core),
|
||||||
new GetGroupNotice(obContext, core),
|
new GetGroupNotice(obContext, core),
|
||||||
new GetGroupEssence(obContext, core),
|
new GetGroupEssence(obContext, core),
|
||||||
|
new GoCQHTTPGetGroupAtAllRemain(obContext, core),
|
||||||
new GoCQHTTPSendForwardMsg(obContext, core),
|
new GoCQHTTPSendForwardMsg(obContext, core),
|
||||||
new GoCQHTTPSendGroupForwardMsg(obContext, core),
|
new GoCQHTTPSendGroupForwardMsg(obContext, core),
|
||||||
new GoCQHTTPSendPrivateForwardMsg(obContext, core),
|
new GoCQHTTPSendPrivateForwardMsg(obContext, core),
|
||||||
@@ -176,9 +188,12 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
|
|||||||
new FetchCustomFace(obContext, core),
|
new FetchCustomFace(obContext, core),
|
||||||
new GoCQHTTPUploadPrivateFile(obContext, core),
|
new GoCQHTTPUploadPrivateFile(obContext, core),
|
||||||
new GetGuildProfile(obContext, core),
|
new GetGuildProfile(obContext, core),
|
||||||
new SetModelShow(obContext, core),
|
new GoCQHTTPGetModelShow(obContext, core),
|
||||||
|
new GoCQHTTPSetModelShow(obContext, core),
|
||||||
|
new GoCQHTTPCheckUrlSafely(obContext, core),
|
||||||
new SetInputStatus(obContext, core),
|
new SetInputStatus(obContext, core),
|
||||||
new GetCSRF(obContext, core),
|
new GetCSRF(obContext, core),
|
||||||
|
new GetCredentials(obContext, core),
|
||||||
new DelGroupNotice(obContext, core),
|
new DelGroupNotice(obContext, core),
|
||||||
new DeleteGroupFile(obContext, core),
|
new DeleteGroupFile(obContext, core),
|
||||||
new CreateGroupFileFolder(obContext, core),
|
new CreateGroupFileFolder(obContext, core),
|
||||||
@@ -189,12 +204,14 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
|
|||||||
new FetchUserProfileLike(obContext, core),
|
new FetchUserProfileLike(obContext, core),
|
||||||
new GetPacketStatus(obContext, core),
|
new GetPacketStatus(obContext, core),
|
||||||
new GroupPoke(obContext, core),
|
new GroupPoke(obContext, core),
|
||||||
|
new FriendPoke(obContext, core),
|
||||||
new GetUserStatus(obContext, core),
|
new GetUserStatus(obContext, core),
|
||||||
new GetRkey(obContext, core),
|
new GetRkey(obContext, core),
|
||||||
new SetSpecialTittle(obContext, core),
|
new SetSpecialTittle(obContext, core),
|
||||||
// new UploadForwardMsg(obContext, core),
|
// new UploadForwardMsg(obContext, core),
|
||||||
new GetGroupShutList(obContext, core),
|
new GetGroupShutList(obContext, core),
|
||||||
new GetGroupFileUrl(obContext, core),
|
new GetGroupFileUrl(obContext, core),
|
||||||
|
new GetMiniAppArk(obContext, core),
|
||||||
];
|
];
|
||||||
const actionMap = new Map();
|
const actionMap = new Map();
|
||||||
for (const action of actionHandlers) {
|
for (const action of actionHandlers) {
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { ChatType, Peer } from '@/core/entities';
|
import { ChatType, Peer } from '@/core/entities';
|
||||||
|
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||||
import BaseAction from '../BaseAction';
|
import BaseAction from '../BaseAction';
|
||||||
import { ActionName } from '../types';
|
import { ActionName } from '../types';
|
||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
|
||||||
|
|
||||||
const SchemaData = {
|
const SchemaData = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -49,23 +49,14 @@ export class MarkGroupMsgAsRead extends MarkMsgAsRead {
|
|||||||
actionName = ActionName.MarkGroupMsgAsRead;
|
actionName = ActionName.MarkGroupMsgAsRead;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class GoCQHTTPMarkMsgAsRead extends MarkMsgAsRead {
|
||||||
interface Payload {
|
|
||||||
message_id: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class GoCQHTTPMarkMsgAsRead extends BaseAction<Payload, null> {
|
|
||||||
actionName = ActionName.GoCQHTTP_MarkMsgAsRead;
|
actionName = ActionName.GoCQHTTP_MarkMsgAsRead;
|
||||||
|
|
||||||
async _handle(payload: Payload): Promise<null> {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MarkAllMsgAsRead extends BaseAction<Payload, null> {
|
export class MarkAllMsgAsRead extends BaseAction<any, null> {
|
||||||
actionName = ActionName._MarkAllMsgAsRead;
|
actionName = ActionName._MarkAllMsgAsRead;
|
||||||
|
|
||||||
async _handle(payload: Payload): Promise<null> {
|
async _handle(): Promise<null> {
|
||||||
await this.core.apis.MsgApi.markAllMsgAsRead();
|
await this.core.apis.MsgApi.markAllMsgAsRead();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@@ -11,9 +11,10 @@ import {decodeCQCode} from '@/onebot/cqcode';
|
|||||||
import { MessageUnique } from '@/common/message-unique';
|
import { MessageUnique } from '@/common/message-unique';
|
||||||
import { ChatType, ElementType, NapCatCore, Peer, RawMessage, SendArkElement, SendMessageElement } from '@/core';
|
import { ChatType, ElementType, NapCatCore, Peer, RawMessage, SendArkElement, SendMessageElement } from '@/core';
|
||||||
import BaseAction from '../BaseAction';
|
import BaseAction from '../BaseAction';
|
||||||
import {rawMsgWithSendMsg} from "@/core/packet/msg/converter";
|
import { rawMsgWithSendMsg } from "@/core/packet/message/converter";
|
||||||
import {PacketMsg} from "@/core/packet/msg/message";
|
import { PacketMsg } from "@/core/packet/message/message";
|
||||||
import {PacketMultiMsgElement} from "@/core/packet/msg/element";
|
import { ForwardMsgBuilder } from "@/common/forward-msg-builder";
|
||||||
|
import { stringifyWithBigInt } from "@/common/helper";
|
||||||
|
|
||||||
export interface ReturnDataType {
|
export interface ReturnDataType {
|
||||||
message_id: number;
|
message_id: number;
|
||||||
@@ -115,10 +116,18 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (getSpecialMsgNum(payload, OB11MessageDataType.node)) {
|
if (getSpecialMsgNum(payload, OB11MessageDataType.node)) {
|
||||||
const packetMode = this.core.apis.PacketApi.available
|
const packetMode = this.core.apis.PacketApi.available;
|
||||||
const returnMsgAndResId = packetMode
|
let returnMsgAndResId: { message: RawMessage | null, res_id?: string } | null;
|
||||||
? await this.handleForwardedNodesPacket(peer, messages as OB11MessageNode[])
|
try {
|
||||||
|
returnMsgAndResId = packetMode
|
||||||
|
? await this.handleForwardedNodesPacket(peer, messages as OB11MessageNode[], payload.source, payload.news, payload.summary, payload.prompt)
|
||||||
: await this.handleForwardedNodes(peer, messages as OB11MessageNode[]);
|
: await this.handleForwardedNodes(peer, messages as OB11MessageNode[]);
|
||||||
|
} catch (e) {
|
||||||
|
throw Error(packetMode ? `发送伪造合并转发消息失败: ${e}` : `发送合并转发消息失败: ${e}`);
|
||||||
|
}
|
||||||
|
if (!returnMsgAndResId) {
|
||||||
|
throw Error('发送合并转发消息失败:returnMsgAndResId 为空!');
|
||||||
|
}
|
||||||
if (returnMsgAndResId.message) {
|
if (returnMsgAndResId.message) {
|
||||||
const msgShortId = MessageUnique.createUniqueMsgId({
|
const msgShortId = MessageUnique.createUniqueMsgId({
|
||||||
guildId: '',
|
guildId: '',
|
||||||
@@ -126,8 +135,9 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
chatType: peer.chatType,
|
chatType: peer.chatType,
|
||||||
}, (returnMsgAndResId.message)!.msgId);
|
}, (returnMsgAndResId.message)!.msgId);
|
||||||
return { message_id: msgShortId!, res_id: returnMsgAndResId.res_id };
|
return { message_id: msgShortId!, res_id: returnMsgAndResId.res_id };
|
||||||
|
} else if (returnMsgAndResId.res_id && !returnMsgAndResId.message) {
|
||||||
|
throw Error(`发送转发消息(res_id:${returnMsgAndResId.res_id} 失败`);
|
||||||
}
|
}
|
||||||
throw Error('发送转发消息失败');
|
|
||||||
} else {
|
} else {
|
||||||
// if (getSpecialMsgNum(payload, OB11MessageDataType.music)) {
|
// if (getSpecialMsgNum(payload, OB11MessageDataType.music)) {
|
||||||
// const music: OB11MessageCustomMusic = messages[0] as OB11MessageCustomMusic;
|
// const music: OB11MessageCustomMusic = messages[0] as OB11MessageCustomMusic;
|
||||||
@@ -143,49 +153,95 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
return { message_id: returnMsg!.id! };
|
return { message_id: returnMsg!.id! };
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleForwardedNodesPacket(msgPeer: Peer, messageNodes: OB11MessageNode[]): Promise<{
|
private async uploadForwardedNodesPacket(msgPeer: Peer, messageNodes: OB11MessageNode[], source?: string, news?: {
|
||||||
message: RawMessage | null,
|
text: string
|
||||||
|
}[], summary?: string, prompt?: string, parentMeta?: {
|
||||||
|
user_id: string,
|
||||||
|
nickname: string,
|
||||||
|
}, dp: number = 0): Promise<{
|
||||||
|
finallySendElements: SendArkElement,
|
||||||
res_id?: string
|
res_id?: string
|
||||||
}> {
|
} | null> {
|
||||||
const logger = this.core.context.logger;
|
const logger = this.core.context.logger;
|
||||||
const packetMsg: PacketMsg[] = [];
|
const packetMsg: PacketMsg[] = [];
|
||||||
for (const node of messageNodes) {
|
for (const node of messageNodes) {
|
||||||
if ((node.data.id && typeof node.data.content !== "string") || !node.data.id) {
|
if (dp >= 3) {
|
||||||
const OB11Data = normalize(node.data.content);
|
logger.logWarn('转发消息深度超过3层,将停止解析!');
|
||||||
const {sendElements} = await this.obContext.apis.MsgApi.createSendElements(OB11Data, msgPeer);
|
break;
|
||||||
const packetMsgElements: rawMsgWithSendMsg = {
|
|
||||||
senderUin: node.data.user_id ?? +this.core.selfInfo.uin,
|
|
||||||
senderName: node.data.nickname,
|
|
||||||
groupId: msgPeer.chatType === ChatType.KCHATTYPEGROUP ? +msgPeer.peerUid : undefined,
|
|
||||||
time: Date.now(),
|
|
||||||
msg: sendElements,
|
|
||||||
}
|
}
|
||||||
logger.logDebug(`handleForwardedNodesPacket 开始转换 ${JSON.stringify(packetMsgElements)}`);
|
if (!node.data.id) {
|
||||||
|
const OB11Data = normalize(node.type === OB11MessageDataType.node ? node.data.content : node);
|
||||||
|
let sendElements: SendMessageElement[];
|
||||||
|
|
||||||
|
if (getSpecialMsgNum({ message: OB11Data }, OB11MessageDataType.node)) {
|
||||||
|
const uploadReturnData = await this.uploadForwardedNodesPacket(msgPeer, OB11Data as OB11MessageNode[], node.data.source, node.data.news, node.data.summary, node.data.prompt, {
|
||||||
|
user_id: (node.data.user_id || node.data.uin)?.toString() ?? parentMeta?.user_id ?? this.core.selfInfo.uin,
|
||||||
|
nickname: (node.data.nickname || node.data.name) ?? parentMeta?.nickname ?? "QQ用户",
|
||||||
|
}, dp + 1);
|
||||||
|
sendElements = uploadReturnData?.finallySendElements ? [uploadReturnData.finallySendElements] : [];
|
||||||
|
} else {
|
||||||
|
const sendElementsCreateReturn = await this.obContext.apis.MsgApi.createSendElements(OB11Data, msgPeer);
|
||||||
|
sendElements = sendElementsCreateReturn.sendElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
const packetMsgElements: rawMsgWithSendMsg = {
|
||||||
|
senderUin: Number((node.data.user_id || node.data.uin) ?? parentMeta?.user_id) || +this.core.selfInfo.uin,
|
||||||
|
senderName: (node.data.nickname || node.data.name) ?? parentMeta?.nickname ?? "QQ用户",
|
||||||
|
groupId: msgPeer.chatType === ChatType.KCHATTYPEGROUP ? +msgPeer.peerUid : undefined,
|
||||||
|
time: Number(node.data.time) || Date.now(),
|
||||||
|
msg: sendElements,
|
||||||
|
};
|
||||||
|
logger.logDebug(`handleForwardedNodesPacket[SendRaw] 开始转换 ${stringifyWithBigInt(packetMsgElements)}`);
|
||||||
const transformedMsg = this.core.apis.PacketApi.packetSession?.packer.packetConverter.rawMsgWithSendMsgToPacketMsg(packetMsgElements);
|
const transformedMsg = this.core.apis.PacketApi.packetSession?.packer.packetConverter.rawMsgWithSendMsgToPacketMsg(packetMsgElements);
|
||||||
logger.logDebug(`handleForwardedNodesPacket 转换为 ${JSON.stringify(transformedMsg)}`);
|
logger.logDebug(`handleForwardedNodesPacket[SendRaw] 转换为 ${stringifyWithBigInt(transformedMsg)}`);
|
||||||
|
packetMsg.push(transformedMsg!);
|
||||||
|
} else if (node.data.id) {
|
||||||
|
const id = node.data.id;
|
||||||
|
const nodeMsg = MessageUnique.getMsgIdAndPeerByShortId(+id) || MessageUnique.getPeerByMsgId(id);
|
||||||
|
if (!nodeMsg) {
|
||||||
|
logger.logError.bind(this.core.context.logger)('转发消息失败,未找到消息', id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const msg = (await this.core.apis.MsgApi.getMsgsByMsgId(nodeMsg.Peer, [nodeMsg.MsgId])).msgList[0];
|
||||||
|
logger.logDebug(`handleForwardedNodesPacket[PureRaw] 开始转换 ${stringifyWithBigInt(msg)}`);
|
||||||
|
await this.core.apis.FileApi.downloadRawMsgMedia([msg]);
|
||||||
|
const transformedMsg = this.core.apis.PacketApi.packetSession?.packer.packetConverter.rawMsgToPacketMsg(msg, msgPeer);
|
||||||
|
logger.logDebug(`handleForwardedNodesPacket[PureRaw] 转换为 ${stringifyWithBigInt(transformedMsg)}`);
|
||||||
packetMsg.push(transformedMsg!);
|
packetMsg.push(transformedMsg!);
|
||||||
} else {
|
} else {
|
||||||
logger.logDebug(`handleForwardedNodesPacket 跳过元素 ${JSON.stringify(node)}`);
|
logger.logDebug(`handleForwardedNodesPacket 跳过元素 ${stringifyWithBigInt(node)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (packetMsg.length === 0) {
|
||||||
|
logger.logWarn('handleForwardedNodesPacket 元素为空!');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
const resid = await this.core.apis.PacketApi.sendUploadForwardMsg(packetMsg, msgPeer.chatType === ChatType.KCHATTYPEGROUP ? +msgPeer.peerUid : 0);
|
const resid = await this.core.apis.PacketApi.sendUploadForwardMsg(packetMsg, msgPeer.chatType === ChatType.KCHATTYPEGROUP ? +msgPeer.peerUid : 0);
|
||||||
const forwardJson = new PacketMultiMsgElement({
|
const forwardJson = ForwardMsgBuilder.fromPacketMsg(resid, packetMsg, source, news, summary, prompt);
|
||||||
elementType: ElementType.STRUCTLONGMSG,
|
return {
|
||||||
elementId: "",
|
finallySendElements: {
|
||||||
structLongMsgElement: {
|
|
||||||
xmlContent: "",
|
|
||||||
resId: resid
|
|
||||||
}
|
|
||||||
}, packetMsg).JSON;
|
|
||||||
const finallySendElements = {
|
|
||||||
elementType: ElementType.ARK,
|
elementType: ElementType.ARK,
|
||||||
elementId: "",
|
elementId: "",
|
||||||
arkElement: {
|
arkElement: {
|
||||||
bytesData: JSON.stringify(forwardJson),
|
bytesData: JSON.stringify(forwardJson),
|
||||||
},
|
},
|
||||||
} as SendArkElement
|
} as SendArkElement,
|
||||||
const returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(msgPeer, [finallySendElements], [], true).catch(_ => undefined)
|
res_id: resid,
|
||||||
return {message: returnMsg ?? null, res_id: resid};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleForwardedNodesPacket(msgPeer: Peer, messageNodes: OB11MessageNode[], source?: string, news?: {
|
||||||
|
text: string
|
||||||
|
}[], summary?: string, prompt?: string): Promise<{
|
||||||
|
message: RawMessage | null,
|
||||||
|
res_id?: string
|
||||||
|
}> {
|
||||||
|
const uploadReturnData = await this.uploadForwardedNodesPacket(msgPeer, messageNodes, source, news, summary, prompt);
|
||||||
|
const res_id = uploadReturnData?.res_id;
|
||||||
|
const finallySendElements = uploadReturnData?.finallySendElements;
|
||||||
|
if (!finallySendElements) throw Error('转发消息失败,生成节点为空');
|
||||||
|
const returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(msgPeer, [finallySendElements], [], true).catch(_ => undefined);
|
||||||
|
return { message: returnMsg ?? null, res_id };
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleForwardedNodes(destPeer: Peer, messageNodes: OB11MessageNode[]): Promise<{
|
private async handleForwardedNodes(destPeer: Peer, messageNodes: OB11MessageNode[]): Promise<{
|
||||||
|
@@ -5,21 +5,19 @@ import {ActionName, BaseCheckResult} from '../types';
|
|||||||
export abstract class GetPacketStatusDepends<PT, RT> extends BaseAction<PT, RT> {
|
export abstract class GetPacketStatusDepends<PT, RT> extends BaseAction<PT, RT> {
|
||||||
actionName = ActionName.GetPacketStatus;
|
actionName = ActionName.GetPacketStatus;
|
||||||
|
|
||||||
protected async check(): Promise<BaseCheckResult>{
|
protected async check(payload: PT): Promise<BaseCheckResult>{
|
||||||
if (!this.core.apis.PacketApi.available) {
|
if (!this.core.apis.PacketApi.available) {
|
||||||
return {
|
return {
|
||||||
valid: false,
|
valid: false,
|
||||||
message: "packetServer不可用,请参照文档 https://napneko.github.io/config/advanced 检查packetServer状态或进行配置!",
|
message: "packetServer不可用,请参照文档 https://napneko.github.io/config/advanced 检查packetServer状态或进行配置!",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
return await super.check(payload);
|
||||||
return {
|
|
||||||
valid: true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GetPacketStatus extends GetPacketStatusDepends<any, null> {
|
export class GetPacketStatus extends GetPacketStatusDepends<any, null> {
|
||||||
async _handle(payload: any) {
|
async _handle(payload: any) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,8 +5,12 @@ export class GetCSRF extends BaseAction<any, any> {
|
|||||||
actionName = ActionName.GetCSRF;
|
actionName = ActionName.GetCSRF;
|
||||||
|
|
||||||
async _handle(payload: any) {
|
async _handle(payload: any) {
|
||||||
|
const sKey = await this.core.apis.UserApi.getSKey();
|
||||||
|
if (!sKey) {
|
||||||
|
throw new Error('SKey is undefined');
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
token: '',
|
token: +this.core.apis.WebApi.getBknFromSKey(sKey),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
31
src/onebot/action/system/GetCredentials.ts
Normal file
31
src/onebot/action/system/GetCredentials.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import BaseAction from '../BaseAction';
|
||||||
|
import { ActionName } from '../types';
|
||||||
|
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||||
|
|
||||||
|
interface Response {
|
||||||
|
cookies: string,
|
||||||
|
token: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const SchemaData = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
domain: { type: 'string' },
|
||||||
|
},
|
||||||
|
required: ['domain'],
|
||||||
|
} as const satisfies JSONSchema;
|
||||||
|
|
||||||
|
type Payload = FromSchema<typeof SchemaData>;
|
||||||
|
|
||||||
|
export class GetCredentials extends BaseAction<Payload, Response> {
|
||||||
|
actionName = ActionName.GetCredentials;
|
||||||
|
payloadSchema = SchemaData;
|
||||||
|
|
||||||
|
async _handle(payload: Payload) {
|
||||||
|
const cookiesObject = await this.core.apis.UserApi.getCookies(payload.domain);
|
||||||
|
//把获取到的cookiesObject转换成 k=v; 格式字符串拼接在一起
|
||||||
|
const cookies = Object.entries(cookiesObject).map(([key, value]) => `${key}=${value}`).join('; ');
|
||||||
|
const bkn = cookiesObject?.skey ? this.core.apis.WebApi.getBknFromCookie(cookiesObject) : '';
|
||||||
|
return { cookies: cookies, token: +bkn };
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
export type BaseCheckResult = ValidCheckResult | InvalidCheckResult
|
export type BaseCheckResult = ValidCheckResult | InvalidCheckResult;
|
||||||
|
|
||||||
export interface ValidCheckResult {
|
export interface ValidCheckResult {
|
||||||
valid: true;
|
valid: true;
|
||||||
@@ -14,117 +14,128 @@ export interface InvalidCheckResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum ActionName {
|
export enum ActionName {
|
||||||
// 以下为扩展napcat扩展
|
|
||||||
Unknown = 'unknown',
|
|
||||||
GroupPoke = 'group_poke',
|
|
||||||
SharePeer = 'ArkSharePeer',
|
|
||||||
ShareGroupEx = 'ArkShareGroup',
|
|
||||||
RebootNormal = 'reboot_normal',//无快速登录重新启动
|
|
||||||
GetRobotUinRange = 'get_robot_uin_range',
|
|
||||||
SetOnlineStatus = 'set_online_status',
|
|
||||||
GetFriendsWithCategory = 'get_friends_with_category',
|
|
||||||
GetGroupIgnoreAddRequest = 'get_group_ignore_add_request',
|
|
||||||
SetQQAvatar = 'set_qq_avatar',
|
|
||||||
GetConfig = 'get_config',
|
|
||||||
SetConfig = 'set_config',
|
|
||||||
Debug = 'debug',
|
|
||||||
GetFile = 'get_file',
|
|
||||||
ForwardFriendSingleMsg = 'forward_friend_single_msg',
|
|
||||||
ForwardGroupSingleMsg = 'forward_group_single_msg',
|
|
||||||
TranslateEnWordToZn = 'translate_en2zh',
|
|
||||||
GetGroupFileCount = 'get_group_file_count',
|
|
||||||
GetGroupFileList = 'get_group_file_list',
|
|
||||||
SetGroupFileFolder = 'set_group_file_folder',
|
|
||||||
DelGroupFile = 'del_group_file',
|
|
||||||
DelGroupFileFolder = 'del_group_file_folder',
|
|
||||||
// onebot 11
|
// onebot 11
|
||||||
Reboot = 'set_restart',
|
SendPrivateMsg = 'send_private_msg',
|
||||||
|
SendGroupMsg = 'send_group_msg',
|
||||||
|
SendMsg = 'send_msg',
|
||||||
|
DeleteMsg = 'delete_msg',
|
||||||
|
GetMsg = 'get_msg',
|
||||||
|
GoCQHTTP_GetForwardMsg = 'get_forward_msg',
|
||||||
SendLike = 'send_like',
|
SendLike = 'send_like',
|
||||||
|
SetGroupKick = 'set_group_kick',
|
||||||
|
SetGroupBan = 'set_group_ban',
|
||||||
|
// SetGroupAnoymousBan = 'set_group_anonymous_ban',
|
||||||
|
SetGroupWholeBan = 'set_group_whole_ban',
|
||||||
|
SetGroupAdmin = 'set_group_admin',
|
||||||
|
// SetGroupAnoymous = 'set_group_anonymous',
|
||||||
|
SetGroupCard = 'set_group_card',
|
||||||
|
SetGroupName = 'set_group_name',
|
||||||
|
SetGroupLeave = 'set_group_leave',
|
||||||
|
SetSpecialTittle = 'set_group_special_title',
|
||||||
|
SetFriendAddRequest = 'set_friend_add_request',
|
||||||
|
SetGroupAddRequest = 'set_group_add_request',
|
||||||
GetLoginInfo = 'get_login_info',
|
GetLoginInfo = 'get_login_info',
|
||||||
|
GoCQHTTP_GetStrangerInfo = 'get_stranger_info',
|
||||||
GetFriendList = 'get_friend_list',
|
GetFriendList = 'get_friend_list',
|
||||||
GetGroupInfo = 'get_group_info',
|
GetGroupInfo = 'get_group_info',
|
||||||
GetGroupList = 'get_group_list',
|
GetGroupList = 'get_group_list',
|
||||||
GetGroupMemberInfo = 'get_group_member_info',
|
GetGroupMemberInfo = 'get_group_member_info',
|
||||||
GetGroupMemberList = 'get_group_member_list',
|
GetGroupMemberList = 'get_group_member_list',
|
||||||
GetMsg = 'get_msg',
|
|
||||||
SendMsg = 'send_msg',
|
|
||||||
SendGroupMsg = 'send_group_msg',
|
|
||||||
SendPrivateMsg = 'send_private_msg',
|
|
||||||
DeleteMsg = 'delete_msg',
|
|
||||||
SetMsgEmojiLike = 'set_msg_emoji_like',
|
|
||||||
SetGroupAddRequest = 'set_group_add_request',
|
|
||||||
SetFriendAddRequest = 'set_friend_add_request',
|
|
||||||
SetGroupLeave = 'set_group_leave',
|
|
||||||
GetVersionInfo = 'get_version_info',
|
|
||||||
GetStatus = 'get_status',
|
|
||||||
CanSendRecord = 'can_send_record',
|
|
||||||
CanSendImage = 'can_send_image',
|
|
||||||
SetGroupKick = 'set_group_kick',
|
|
||||||
SetGroupBan = 'set_group_ban',
|
|
||||||
SetGroupWholeBan = 'set_group_whole_ban',
|
|
||||||
SetGroupAdmin = 'set_group_admin',
|
|
||||||
SetGroupCard = 'set_group_card',
|
|
||||||
SetGroupName = 'set_group_name',
|
|
||||||
GetImage = 'get_image',
|
|
||||||
GetRecord = 'get_record',
|
|
||||||
CleanCache = 'clean_cache',
|
|
||||||
GetCookies = 'get_cookies',
|
|
||||||
// 以下为go-cqhttp api
|
|
||||||
GoCQHTTP_HandleQuickAction = '.handle_quick_operation',
|
|
||||||
GetGroupHonorInfo = 'get_group_honor_info',
|
GetGroupHonorInfo = 'get_group_honor_info',
|
||||||
GoCQHTTP_GetEssenceMsg = 'get_essence_msg_list',
|
GetCookies = 'get_cookies',
|
||||||
GoCQHTTP_SendGroupNotice = '_send_group_notice',
|
GetCSRF = 'get_csrf_token',
|
||||||
GoCQHTTP_GetGroupNotice = '_get_group_notice',
|
GetCredentials = 'get_credentials',
|
||||||
GoCQHTTP_SendForwardMsg = 'send_forward_msg',
|
GetRecord = 'get_record',
|
||||||
|
GetImage = 'get_image',
|
||||||
|
CanSendImage = 'can_send_image',
|
||||||
|
CanSendRecord = 'can_send_record',
|
||||||
|
GetStatus = 'get_status',
|
||||||
|
GetVersionInfo = 'get_version_info',
|
||||||
|
// Reboot = 'set_restart',
|
||||||
|
// CleanCache = 'clean_cache',
|
||||||
|
|
||||||
|
// go-cqhttp
|
||||||
|
SetQQProfile = 'set_qq_profile',
|
||||||
|
// QidianGetAccountInfo = 'qidian_get_account_info',
|
||||||
|
GoCQHTTP_GetModelShow = '_get_model_show',
|
||||||
|
GoCQHTTP_SetModelShow = '_set_model_show',
|
||||||
|
GetOnlineClient = 'get_online_clients',
|
||||||
|
// GetUnidirectionalFriendList = 'get_unidirectional_friend_list',
|
||||||
|
GoCQHTTP_DeleteFriend = 'delete_friend',
|
||||||
|
// DeleteUnidirectionalFriendList = 'delete_unidirectional_friend',
|
||||||
|
GoCQHTTP_MarkMsgAsRead = 'mark_msg_as_read',
|
||||||
GoCQHTTP_SendGroupForwardMsg = 'send_group_forward_msg',
|
GoCQHTTP_SendGroupForwardMsg = 'send_group_forward_msg',
|
||||||
GoCQHTTP_SendPrivateForwardMsg = 'send_private_forward_msg',
|
GoCQHTTP_SendPrivateForwardMsg = 'send_private_forward_msg',
|
||||||
GoCQHTTP_GetStrangerInfo = 'get_stranger_info',
|
GoCQHTTP_GetGroupMsgHistory = 'get_group_msg_history',
|
||||||
GoCQHTTP_MarkMsgAsRead = 'mark_msg_as_read',
|
OCRImage = 'ocr_image',
|
||||||
GetGuildList = 'get_guild_list',
|
IOCRImage = '.ocr_image',
|
||||||
MarkPrivateMsgAsRead = 'mark_private_msg_as_read',
|
GetGroupSystemMsg = 'get_group_system_msg',
|
||||||
MarkGroupMsgAsRead = 'mark_group_msg_as_read',
|
GoCQHTTP_GetEssenceMsg = 'get_essence_msg_list',
|
||||||
|
GoCQHTTP_GetGroupAtAllRemain = 'get_group_at_all_remain',
|
||||||
|
SetGroupPortrait = 'set_group_portrait',
|
||||||
|
SetEssenceMsg = 'set_essence_msg',
|
||||||
|
DelEssenceMsg = 'delete_essence_msg',
|
||||||
|
GoCQHTTP_SendGroupNotice = '_send_group_notice',
|
||||||
|
GoCQHTTP_GetGroupNotice = '_get_group_notice',
|
||||||
GoCQHTTP_UploadGroupFile = 'upload_group_file',
|
GoCQHTTP_UploadGroupFile = 'upload_group_file',
|
||||||
GOCQHTTP_DeleteGroupFile = 'delete_group_file',
|
GOCQHTTP_DeleteGroupFile = 'delete_group_file',
|
||||||
GOCQHTTP_GetGroupFileUrl = 'get_group_file_url',
|
|
||||||
GoCQHTTP_CreateGroupFileFolder = 'create_group_file_folder',
|
GoCQHTTP_CreateGroupFileFolder = 'create_group_file_folder',
|
||||||
GoCQHTTP_DeleteGroupFileFolder = 'delete_group_file_folder',
|
GoCQHTTP_DeleteGroupFileFolder = 'delete_group_folder',
|
||||||
GoCQHTTP_GetGroupFileSystemInfo = 'get_group_file_system_info',
|
GoCQHTTP_GetGroupFileSystemInfo = 'get_group_file_system_info',
|
||||||
GoCQHTTP_GetGroupRootFiles = 'get_group_root_files',
|
GoCQHTTP_GetGroupRootFiles = 'get_group_root_files',
|
||||||
GoCQHTTP_GetGroupFilesByFolder = 'get_group_files_by_folder',
|
GoCQHTTP_GetGroupFilesByFolder = 'get_group_files_by_folder',
|
||||||
|
GOCQHTTP_GetGroupFileUrl = 'get_group_file_url',
|
||||||
|
GOCQHTTP_UploadPrivateFile = 'upload_private_file',
|
||||||
|
// GOCQHTTP_ReloadEventFilter = 'reload_event_filter',
|
||||||
GoCQHTTP_DownloadFile = 'download_file',
|
GoCQHTTP_DownloadFile = 'download_file',
|
||||||
GoCQHTTP_GetGroupMsgHistory = 'get_group_msg_history',
|
GoCQHTTP_CheckUrlSafely = 'check_url_safely',
|
||||||
GoCQHTTP_GetForwardMsg = 'get_forward_msg',
|
GoCQHTTP_GetWordSlices = '.get_word_slices',
|
||||||
|
GoCQHTTP_HandleQuickAction = '.handle_quick_operation',
|
||||||
|
|
||||||
|
// 以下为扩展napcat扩展
|
||||||
|
Unknown = 'unknown',
|
||||||
|
SharePeer = 'ArkSharePeer',
|
||||||
|
ShareGroupEx = 'ArkShareGroup',
|
||||||
|
// RebootNormal = 'reboot_normal', //无快速登录重新启动
|
||||||
|
GetRobotUinRange = 'get_robot_uin_range',
|
||||||
|
SetOnlineStatus = 'set_online_status',
|
||||||
|
GetFriendsWithCategory = 'get_friends_with_category',
|
||||||
|
SetQQAvatar = 'set_qq_avatar',
|
||||||
|
GetFile = 'get_file',
|
||||||
|
ForwardFriendSingleMsg = 'forward_friend_single_msg',
|
||||||
|
ForwardGroupSingleMsg = 'forward_group_single_msg',
|
||||||
|
TranslateEnWordToZn = 'translate_en2zh',
|
||||||
|
SetMsgEmojiLike = 'set_msg_emoji_like',
|
||||||
|
GoCQHTTP_SendForwardMsg = 'send_forward_msg',
|
||||||
|
MarkPrivateMsgAsRead = 'mark_private_msg_as_read',
|
||||||
|
MarkGroupMsgAsRead = 'mark_group_msg_as_read',
|
||||||
GetFriendMsgHistory = 'get_friend_msg_history',
|
GetFriendMsgHistory = 'get_friend_msg_history',
|
||||||
GetGroupIgnoredNotifies = 'get_group_ignored_notifies',
|
|
||||||
GetOnlineClient = 'get_online_clients',
|
|
||||||
OCRImage = 'ocr_image',
|
|
||||||
IOCRImage = '.ocr_image',
|
|
||||||
SetQQProfile = 'set_qq_profile',
|
|
||||||
CreateCollection = 'create_collection',
|
CreateCollection = 'create_collection',
|
||||||
GetCollectionList = 'get_collection_list',
|
GetCollectionList = 'get_collection_list',
|
||||||
SetLongNick = 'set_self_longnick',
|
SetLongNick = 'set_self_longnick',
|
||||||
SetEssenceMsg = 'set_essence_msg',
|
|
||||||
DelEssenceMsg = 'delete_essence_msg',
|
|
||||||
GetRecentContact = 'get_recent_contact',
|
GetRecentContact = 'get_recent_contact',
|
||||||
_MarkAllMsgAsRead = '_mark_all_as_read',
|
_MarkAllMsgAsRead = '_mark_all_as_read',
|
||||||
GetProfileLike = 'get_profile_like',
|
GetProfileLike = 'get_profile_like',
|
||||||
SetGroupPortrait = 'set_group_portrait',
|
|
||||||
FetchCustomFace = 'fetch_custom_face',
|
FetchCustomFace = 'fetch_custom_face',
|
||||||
GOCQHTTP_UploadPrivateFile = 'upload_private_file',
|
|
||||||
TestApi01 = 'test_api_01',
|
|
||||||
FetchEmojiLike = 'fetch_emoji_like',
|
FetchEmojiLike = 'fetch_emoji_like',
|
||||||
GetGuildProfile = 'get_guild_service_profile',
|
|
||||||
SetModelShow = '_set_model_show',
|
|
||||||
SetInputStatus = 'set_input_status',
|
SetInputStatus = 'set_input_status',
|
||||||
GetCSRF = 'get_csrf_token',
|
GetGroupInfoEx = 'get_group_info_ex',
|
||||||
|
GetGroupIgnoreAddRequest = 'get_group_ignore_add_request',
|
||||||
DelGroupNotice = '_del_group_notice',
|
DelGroupNotice = '_del_group_notice',
|
||||||
GetGroupInfoEx = "get_group_info_ex",
|
FetchUserProfileLike = 'fetch_user_profile_like',
|
||||||
GetGroupSystemMsg = 'get_group_system_msg',
|
FriendPoke = 'friend_poke',
|
||||||
FetchUserProfileLike = "fetch_user_profile_like",
|
GroupPoke = 'group_poke',
|
||||||
GetPacketStatus = 'nc_get_packet_status',
|
GetPacketStatus = 'nc_get_packet_status',
|
||||||
GetUserStatus = "nc_get_user_status",
|
GetUserStatus = 'nc_get_user_status',
|
||||||
GetRkey = "nc_get_rkey",
|
GetRkey = 'nc_get_rkey',
|
||||||
SetSpecialTittle = "set_group_special_title",
|
GetGroupShutList = 'get_group_shut_list',
|
||||||
|
|
||||||
|
GetGuildList = 'get_guild_list',
|
||||||
|
GetGuildProfile = 'get_guild_service_profile',
|
||||||
|
|
||||||
|
GetGroupIgnoredNotifies = 'get_group_ignored_notifies',
|
||||||
|
|
||||||
|
SetGroupSign = "set_group_sign",
|
||||||
|
GetMiniAppArk = "get_mini_app_ark",
|
||||||
// UploadForwardMsg = "upload_forward_msg",
|
// UploadForwardMsg = "upload_forward_msg",
|
||||||
GetGroupShutList = "get_group_shut_list",
|
|
||||||
}
|
}
|
||||||
|
22
src/onebot/action/user/FriendPoke.ts
Normal file
22
src/onebot/action/user/FriendPoke.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { ActionName } from '../types';
|
||||||
|
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||||
|
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
|
||||||
|
|
||||||
|
const SchemaData = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
user_id: { type: ['number', 'string'] },
|
||||||
|
},
|
||||||
|
required: ['user_id'],
|
||||||
|
} as const satisfies JSONSchema;
|
||||||
|
|
||||||
|
type Payload = FromSchema<typeof SchemaData>;
|
||||||
|
|
||||||
|
export class FriendPoke extends GetPacketStatusDepends<Payload, any> {
|
||||||
|
actionName = ActionName.FriendPoke;
|
||||||
|
payloadSchema = SchemaData;
|
||||||
|
|
||||||
|
async _handle(payload: Payload) {
|
||||||
|
await this.core.apis.PacketApi.sendPokePacket(+payload.user_id);
|
||||||
|
}
|
||||||
|
}
|
@@ -24,7 +24,6 @@ import {
|
|||||||
OB11MessageData,
|
OB11MessageData,
|
||||||
OB11MessageDataType,
|
OB11MessageDataType,
|
||||||
OB11MessageFileBase,
|
OB11MessageFileBase,
|
||||||
OB11MessageForward,
|
|
||||||
} from '@/onebot';
|
} from '@/onebot';
|
||||||
import { OB11Entities } from '@/onebot/entities';
|
import { OB11Entities } from '@/onebot/entities';
|
||||||
import { EventType } from '@/onebot/event/OB11BaseEvent';
|
import { EventType } from '@/onebot/event/OB11BaseEvent';
|
||||||
@@ -35,6 +34,7 @@ import fs from 'node:fs';
|
|||||||
import fsPromise from 'node:fs/promises';
|
import fsPromise from 'node:fs/promises';
|
||||||
import { OB11FriendAddNoticeEvent } from '@/onebot/event/notice/OB11FriendAddNoticeEvent';
|
import { OB11FriendAddNoticeEvent } from '@/onebot/event/notice/OB11FriendAddNoticeEvent';
|
||||||
import { decodeSysMessage } from '@/core/packet/proto/old/ProfileLike';
|
import { decodeSysMessage } from '@/core/packet/proto/old/ProfileLike';
|
||||||
|
import { ForwardMsgBuilder } from "@/common/forward-msg-builder";
|
||||||
|
|
||||||
type RawToOb11Converters = {
|
type RawToOb11Converters = {
|
||||||
[Key in keyof MessageElement as Key extends `${string}Element` ? Key : never]: (
|
[Key in keyof MessageElement as Key extends `${string}Element` ? Key : never]: (
|
||||||
@@ -119,7 +119,7 @@ export class OneBotMsgApi {
|
|||||||
url: await this.core.apis.FileApi.getImageUrl(element),
|
url: await this.core.apis.FileApi.getImageUrl(element),
|
||||||
path: element.filePath,
|
path: element.filePath,
|
||||||
file_size: element.fileSize,
|
file_size: element.fileSize,
|
||||||
file_unique: element.fileName
|
file_unique: element.md5HexStr ?? element.fileName,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@@ -142,7 +142,7 @@ export class OneBotMsgApi {
|
|||||||
url: pathToFileURL(element.filePath).href,
|
url: pathToFileURL(element.filePath).href,
|
||||||
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, element.fileUuid, "." + element.fileName),
|
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, element.fileUuid, "." + element.fileName),
|
||||||
file_size: element.fileSize,
|
file_size: element.fileSize,
|
||||||
file_unique: element.fileName,
|
file_unique: element.fileMd5 ?? element.fileSha ?? element.fileName,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -203,7 +203,7 @@ export class OneBotMsgApi {
|
|||||||
guildId: '',
|
guildId: '',
|
||||||
};
|
};
|
||||||
if (!records || !element.replyMsgTime || !element.senderUidStr) {
|
if (!records || !element.replyMsgTime || !element.senderUidStr) {
|
||||||
this.core.context.logger.logError.bind(this.core.context.logger)('获取不到引用的消息', element.replayMsgSeq);
|
this.core.context.logger.logError.bind(this.core.context.logger)('似乎是旧版客户端,获取不到引用的消息', element.replayMsgSeq);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,11 +217,41 @@ export class OneBotMsgApi {
|
|||||||
if (records.peerUin === '284840486' || records.peerUin === '1094950020') {
|
if (records.peerUin === '284840486' || records.peerUin === '1094950020') {
|
||||||
return createReplyData(records.msgId);
|
return createReplyData(records.msgId);
|
||||||
}
|
}
|
||||||
const replyMsg = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV2(peer, element.replayMsgSeq, element.replyMsgTime, [element.senderUidStr]))
|
let replyMsgList = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV2(peer, element.replayMsgSeq, records.msgTime, [element.senderUidStr])).msgList;
|
||||||
.msgList.find(msg => msg.msgRandom === records.msgRandom);
|
let replyMsg = replyMsgList.find(msg => msg.msgRandom === records.msgRandom);
|
||||||
|
|
||||||
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
|
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
|
||||||
this.core.context.logger.logError.bind(this.core.context.logger)('获取不到引用的消息', element.replayMsgSeq);
|
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: ',
|
||||||
|
element.replayMsgSeq,
|
||||||
|
',消息长度:',
|
||||||
|
replyMsgList.length
|
||||||
|
);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return createReplyData(replyMsg.msgId);
|
return createReplyData(replyMsg.msgId);
|
||||||
@@ -284,7 +314,7 @@ export class OneBotMsgApi {
|
|||||||
url: videoDownUrl ?? pathToFileURL(element.filePath).href,
|
url: videoDownUrl ?? pathToFileURL(element.filePath).href,
|
||||||
file_id: fileCode,
|
file_id: fileCode,
|
||||||
file_size: element.fileSize,
|
file_size: element.fileSize,
|
||||||
file_unique: element.fileName,
|
file_unique: element.videoMd5 ?? element.thumbMd5 ?? element.fileName,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -304,17 +334,17 @@ export class OneBotMsgApi {
|
|||||||
url: pathToFileURL(element.filePath).href,
|
url: pathToFileURL(element.filePath).href,
|
||||||
file_id: fileCode,
|
file_id: fileCode,
|
||||||
file_size: element.fileSize,
|
file_size: element.fileSize,
|
||||||
file_unique: element.fileName
|
file_unique: element.fileUuid
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
multiForwardMsgElement: async (_, msg) => {
|
multiForwardMsgElement: async (_, msg) => {
|
||||||
const message_data: OB11MessageForward = {
|
// const message_data: OB11MessageForward = {
|
||||||
data: {} as any,
|
// data: {} as any,
|
||||||
type: OB11MessageDataType.forward,
|
// type: OB11MessageDataType.forward,
|
||||||
};
|
// };
|
||||||
message_data.data.id = msg.msgId;
|
// message_data.data.id = msg.msgId;
|
||||||
const parentMsgPeer = msg.parentMsgPeer ?? {
|
const parentMsgPeer = msg.parentMsgPeer ?? {
|
||||||
chatType: msg.chatType,
|
chatType: msg.chatType,
|
||||||
guildId: '',
|
guildId: '',
|
||||||
@@ -465,6 +495,7 @@ export class OneBotMsgApi {
|
|||||||
},
|
},
|
||||||
}) => ({
|
}) => ({
|
||||||
elementType: ElementType.MFACE,
|
elementType: ElementType.MFACE,
|
||||||
|
elementId: '',
|
||||||
marketFaceElement: {
|
marketFaceElement: {
|
||||||
emojiPackageId: emoji_package_id,
|
emojiPackageId: emoji_package_id,
|
||||||
emojiId: emoji_id,
|
emojiId: emoji_id,
|
||||||
@@ -600,7 +631,13 @@ export class OneBotMsgApi {
|
|||||||
|
|
||||||
[OB11MessageDataType.node]: async () => undefined,
|
[OB11MessageDataType.node]: async () => undefined,
|
||||||
|
|
||||||
[OB11MessageDataType.forward]: async () => undefined,
|
[OB11MessageDataType.forward]: async ({ data }, context) => {
|
||||||
|
const jsonData = ForwardMsgBuilder.fromResId(data.id);
|
||||||
|
return this.ob11ToRawConverters.json({
|
||||||
|
data: { data: JSON.stringify(jsonData) },
|
||||||
|
type: OB11MessageDataType.json
|
||||||
|
}, context);
|
||||||
|
},
|
||||||
|
|
||||||
[OB11MessageDataType.xml]: async () => undefined,
|
[OB11MessageDataType.xml]: async () => undefined,
|
||||||
|
|
||||||
@@ -617,12 +654,20 @@ export class OneBotMsgApi {
|
|||||||
|
|
||||||
[OB11MessageDataType.miniapp]: async () => undefined,
|
[OB11MessageDataType.miniapp]: async () => undefined,
|
||||||
|
|
||||||
[OB11MessageDataType.contact]: async ({ data }, context) => {
|
[OB11MessageDataType.contact]: async ({ data: { type = "qq", id } }, context) => {
|
||||||
const arkJson = await this.core.apis.UserApi.getBuddyRecommendContactArkJson(data.id.toString(), '');
|
if (type === "qq") {
|
||||||
|
const arkJson = await this.core.apis.UserApi.getBuddyRecommendContactArkJson(id.toString(), '');
|
||||||
return this.ob11ToRawConverters.json({
|
return this.ob11ToRawConverters.json({
|
||||||
data: { data: arkJson.arkMsg },
|
data: { data: arkJson.arkMsg },
|
||||||
type: OB11MessageDataType.json
|
type: OB11MessageDataType.json
|
||||||
}, context);
|
}, context);
|
||||||
|
} else if (type === "group") {
|
||||||
|
const arkJson = await this.core.apis.GroupApi.getGroupRecommendContactArkJson(id.toString());
|
||||||
|
return this.ob11ToRawConverters.json({
|
||||||
|
data: { data: arkJson.arkJson },
|
||||||
|
type: OB11MessageDataType.json
|
||||||
|
}, context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -711,9 +756,12 @@ export class OneBotMsgApi {
|
|||||||
async (element) => {
|
async (element) => {
|
||||||
for (const key in element) {
|
for (const key in element) {
|
||||||
if (keyCanBeParsed(key, this.rawToOb11Converters) && element[key]) {
|
if (keyCanBeParsed(key, this.rawToOb11Converters) && element[key]) {
|
||||||
const parsedElement = await this.rawToOb11Converters[key]?.(
|
const converters = this.rawToOb11Converters[key] as (
|
||||||
// eslint-disable-next-line
|
element: Exclude<MessageElement[keyof RawToOb11Converters], null | undefined>,
|
||||||
// @ts-ignore
|
msg: RawMessage,
|
||||||
|
elementWrapper: MessageElement,
|
||||||
|
) => PromiseLike<OB11MessageData | null>;
|
||||||
|
const parsedElement = await converters?.(
|
||||||
element[key],
|
element[key],
|
||||||
msg,
|
msg,
|
||||||
element,
|
element,
|
||||||
@@ -762,9 +810,11 @@ export class OneBotMsgApi {
|
|||||||
if (ignoreTypes.includes(sendMsg.type)) {
|
if (ignoreTypes.includes(sendMsg.type)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const callResult = this.ob11ToRawConverters[sendMsg.type](
|
const converter = this.ob11ToRawConverters[sendMsg.type] as (
|
||||||
// eslint-disable-next-line
|
sendMsg: Extract<OB11MessageData, { type: OB11MessageData['type'] }>,
|
||||||
// @ts-ignore
|
context: MessageContext,
|
||||||
|
) => Promise<SendMessageElement | undefined>;
|
||||||
|
const callResult = converter(
|
||||||
sendMsg,
|
sendMsg,
|
||||||
{ peer, deleteAfterSentFiles },
|
{ peer, deleteAfterSentFiles },
|
||||||
)?.catch(undefined);
|
)?.catch(undefined);
|
||||||
|
@@ -68,13 +68,13 @@ export function encodeCQCode(data: OB11MessageData) {
|
|||||||
|
|
||||||
let result = '[CQ:' + data.type;
|
let result = '[CQ:' + data.type;
|
||||||
for (const name in data.data) {
|
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) {
|
if (value === undefined) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const text = value.toString();
|
const text = value?.toString();
|
||||||
result += `,${name}=${CQCodeEscape(text)}`;
|
if (text) result += `,${name}=${CQCodeEscape(text)}`;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// If it can't be converted, skip this name-value pair
|
// If it can't be converted, skip this name-value pair
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,7 @@ import {
|
|||||||
GroupNotifyMsgStatus,
|
GroupNotifyMsgStatus,
|
||||||
GroupNotifyMsgType,
|
GroupNotifyMsgType,
|
||||||
InstanceContext,
|
InstanceContext,
|
||||||
|
MsgSourceType,
|
||||||
NapCatCore,
|
NapCatCore,
|
||||||
NodeIKernelBuddyListener,
|
NodeIKernelBuddyListener,
|
||||||
NodeIKernelGroupListener,
|
NodeIKernelGroupListener,
|
||||||
@@ -303,8 +304,10 @@ export class NapCatOneBot11Adapter {
|
|||||||
},
|
},
|
||||||
m.msgId,
|
m.msgId,
|
||||||
);
|
);
|
||||||
|
// if (m.sourceType == MsgSourceType.K_DOWN_SOURCETYPE_AIOINNER) {
|
||||||
await this.emitMsg(m)
|
await this.emitMsg(m)
|
||||||
.catch(e => this.context.logger.logError.bind(this.context.logger)('处理消息失败', e));
|
.catch(e => this.context.logger.logError.bind(this.context.logger)('处理消息失败', e));
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -338,7 +341,7 @@ export class NapCatOneBot11Adapter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.context.session.getMsgService().addKernelMsgListener(
|
this.context.session.getMsgService().addKernelMsgListener(
|
||||||
proxiedListenerOf(msgListener, this.context.logger) as any,
|
proxiedListenerOf(msgListener, this.context.logger),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,7 +370,7 @@ export class NapCatOneBot11Adapter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.context.session.getBuddyService().addKernelBuddyListener(
|
this.context.session.getBuddyService().addKernelBuddyListener(
|
||||||
proxiedListenerOf(buddyListener, this.context.logger) as any,
|
proxiedListenerOf(buddyListener, this.context.logger),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -64,9 +64,9 @@ export class OB11PassiveHttpAdapter implements IOB11NetworkAdapter {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.app.use((req, res, next) => this.authorize(this.token, req, res, next));
|
this.app.use((req, res, next) => this.authorize(this.token, req, res, next));
|
||||||
// @ts-ignore
|
this.app.use(async (req, res, _) => {
|
||||||
this.app.use((req, res) => this.handleRequest(req, res));
|
await this.handleRequest(req, res);
|
||||||
|
});
|
||||||
this.server.listen(this.port, () => {
|
this.server.listen(this.port, () => {
|
||||||
this.core.context.logger.log(`[OneBot] [HTTP Server Adapter] Start On Port ${this.port}`);
|
this.core.context.logger.log(`[OneBot] [HTTP Server Adapter] Start On Port ${this.port}`);
|
||||||
});
|
});
|
||||||
|
@@ -119,11 +119,25 @@ export class OB11PassiveWebSocketAdapter implements IOB11NetworkAdapter {
|
|||||||
|
|
||||||
async close() {
|
async close() {
|
||||||
this.isOpen = false;
|
this.isOpen = false;
|
||||||
this.wsServer.close();
|
this.wsServer.close((err) => {
|
||||||
|
if (err) {
|
||||||
|
this.logger.logError.bind(this.logger)('[OneBot] [WebSocket Server] Error closing server:', err.message);
|
||||||
|
} else {
|
||||||
|
this.logger.log.bind(this.logger)('[OneBot] [WebSocket Server] Server Closed');
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
if (this.heartbeatIntervalId) {
|
if (this.heartbeatIntervalId) {
|
||||||
clearInterval(this.heartbeatIntervalId);
|
clearInterval(this.heartbeatIntervalId);
|
||||||
this.heartbeatIntervalId = null;
|
this.heartbeatIntervalId = null;
|
||||||
}
|
}
|
||||||
|
await this.wsClientsMutex.runExclusive(async () => {
|
||||||
|
this.wsClients.forEach((wsClient) => {
|
||||||
|
wsClient.close();
|
||||||
|
});
|
||||||
|
this.wsClients = [];
|
||||||
|
this.wsClientWithEvent = [];
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private registerHeartBeat() {
|
private registerHeartBeat() {
|
||||||
|
@@ -16,8 +16,8 @@ export interface OB11Message {
|
|||||||
message_id: number,
|
message_id: number,
|
||||||
message_seq: number, // 和message_id一样
|
message_seq: number, // 和message_id一样
|
||||||
real_id: number,
|
real_id: number,
|
||||||
user_id: number,
|
user_id: number | string, // number
|
||||||
group_id?: number,
|
group_id?: number | string, // number
|
||||||
message_type: 'private' | 'group',
|
message_type: 'private' | 'group',
|
||||||
sub_type?: 'friend' | 'group' | 'normal',
|
sub_type?: 'friend' | 'group' | 'normal',
|
||||||
sender: OB11Sender,
|
sender: OB11Sender,
|
||||||
@@ -85,6 +85,7 @@ export interface OB11MessageText {
|
|||||||
export interface OB11MessageContext {
|
export interface OB11MessageContext {
|
||||||
type: OB11MessageDataType.contact,
|
type: OB11MessageDataType.contact,
|
||||||
data: {
|
data: {
|
||||||
|
type:"qq"|"group",
|
||||||
id: string,
|
id: string,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,9 +149,16 @@ export interface OB11MessageNode {
|
|||||||
type: OB11MessageDataType.node;
|
type: OB11MessageDataType.node;
|
||||||
data: {
|
data: {
|
||||||
id?: string
|
id?: string
|
||||||
user_id?: number
|
user_id?: number | string // number
|
||||||
|
uin?: number | string // number, compatible with go-cqhttp
|
||||||
nickname: string
|
nickname: string
|
||||||
|
name?: string // compatible with go-cqhttp
|
||||||
content: OB11MessageMixType
|
content: OB11MessageMixType
|
||||||
|
source?: string,
|
||||||
|
news?: { text: string }[],
|
||||||
|
summary?: string,
|
||||||
|
prompt?: string
|
||||||
|
time?: string
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,6 +228,11 @@ export interface OB11PostSendMsg {
|
|||||||
message: OB11MessageMixType;
|
message: OB11MessageMixType;
|
||||||
messages?: OB11MessageMixType; // 兼容 go-cqhttp
|
messages?: OB11MessageMixType; // 兼容 go-cqhttp
|
||||||
auto_escape?: boolean | string
|
auto_escape?: boolean | string
|
||||||
|
source?: string,
|
||||||
|
news?: { text: string }[],
|
||||||
|
summary?: string,
|
||||||
|
prompt?: string
|
||||||
|
time?: string
|
||||||
}
|
}
|
||||||
export interface OB11PostContext {
|
export interface OB11PostContext {
|
||||||
message_type?: 'private' | 'group'
|
message_type?: 'private' | 'group'
|
||||||
|
@@ -165,7 +165,7 @@ export async function NCoreInitShell() {
|
|||||||
logger.logError.bind(logger)('[Core] [Login] Login Error , ErrInfo: ', args);
|
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();
|
const isConnect = loginService.connect();
|
||||||
if (!isConnect) {
|
if (!isConnect) {
|
||||||
logger.logError.bind(logger)('核心登录服务连接失败!');
|
logger.logError.bind(logger)('核心登录服务连接失败!');
|
||||||
|
@@ -30,7 +30,7 @@ async function onSettingWindowCreated(view: Element) {
|
|||||||
SettingItem(
|
SettingItem(
|
||||||
'<span id="napcat-update-title">Napcat</span>',
|
'<span id="napcat-update-title">Napcat</span>',
|
||||||
undefined,
|
undefined,
|
||||||
SettingButton('V3.0.1', 'napcat-update-button', 'secondary'),
|
SettingButton('V3.3.12', 'napcat-update-button', 'secondary'),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
SettingList([
|
SettingList([
|
||||||
|
@@ -164,7 +164,7 @@ async function onSettingWindowCreated(view) {
|
|||||||
SettingItem(
|
SettingItem(
|
||||||
'<span id="napcat-update-title">Napcat</span>',
|
'<span id="napcat-update-title">Napcat</span>',
|
||||||
void 0,
|
void 0,
|
||||||
SettingButton("V3.0.1", "napcat-update-button", "secondary")
|
SettingButton("V3.3.27", "napcat-update-button", "secondary")
|
||||||
)
|
)
|
||||||
]),
|
]),
|
||||||
SettingList([
|
SettingList([
|
||||||
|
Reference in New Issue
Block a user