Compare commits

...

131 Commits

Author SHA1 Message Date
idranme
936b1d911c Merge pull request #428 from LLOneBot/dev
release: 3.33.0
2024-09-18 20:59:57 +08:00
idranme
58817d1c02 chore: v3.33.0 2024-09-18 20:53:28 +08:00
idranme
2c24422478 feat: support setting remark when agreeing to a friend request 2024-09-18 20:47:45 +08:00
idranme
c2a723380a fix: get_group_member_list API 2024-09-18 19:35:58 +08:00
idranme
156bbaea33 feat: get_group_files_by_folder API 2024-09-18 17:22:09 +08:00
idranme
6c485634e1 feat: get_friend_msg_history API 2024-09-18 16:56:15 +08:00
idranme
f39a9aeafb feat: fetch_custom_face API 2024-09-18 16:11:08 +08:00
idranme
1160cd4b26 feat: fetch_emoji_like API 2024-09-18 15:49:37 +08:00
idranme
9a7ff523dd optimize 2024-09-18 14:07:42 +08:00
idranme
f49995ea97 refactor 2024-09-17 21:04:36 +08:00
idranme
1876dd29ac Merge pull request #423 from LLOneBot/dev
release: 3.32.8
2024-09-17 11:59:57 +08:00
idranme
9944b53266 chore: v3.32.8 2024-09-17 11:55:50 +08:00
idranme
9a791e3a21 fix 2024-09-17 02:17:16 +08:00
idranme
64c5eb6c04 Merge pull request #422 from LLOneBot/dev
release: 3.32.7
2024-09-16 20:48:15 +08:00
idranme
e5750786cb chore: v3.32.7 2024-09-16 20:46:14 +08:00
idranme
18cb46ade5 fix 2024-09-16 20:43:18 +08:00
idranme
e39c89a441 fix 2024-09-16 19:01:59 +08:00
idranme
476d498e44 Merge pull request #417 from LLOneBot/dev
release: 3.32.6
2024-09-15 17:48:35 +08:00
idranme
55446538de chore: v3.32.6 2024-09-15 17:43:10 +08:00
idranme
b965f50653 fix: friend_add event 2024-09-15 16:14:36 +08:00
idranme
2d354c5eda optimize 2024-09-15 14:08:02 +08:00
idranme
536999f296 feat: support for sending contact message segment 2024-09-14 20:13:45 +08:00
idranme
cad09b2ed1 fix 2024-09-14 19:56:46 +08:00
idranme
6be0c11ca2 refactor 2024-09-13 22:58:21 +08:00
idranme
b03bcf9a7c Merge pull request #415 from LLOneBot/dev
release: 3.32.5
2024-09-13 18:59:37 +08:00
idranme
d4f9629af2 chore: v3.32.5 2024-09-13 18:54:56 +08:00
idranme
6d47e2ee80 chore 2024-09-13 18:52:45 +08:00
idranme
506bddb21a chore: style 2024-09-13 17:30:56 +08:00
idranme
91c689baf8 fix 2024-09-13 14:56:30 +08:00
idranme
b7938aaab8 refactor 2024-09-12 22:39:14 +08:00
idranme
b1a892cf4e chore 2024-09-12 18:29:18 +08:00
idranme
9284fc7e8a Merge pull request #413 from LLOneBot/dev
3.32.4
2024-09-12 18:14:23 +08:00
idranme
ceb063143a chore: v3.32.4 2024-09-12 18:11:01 +08:00
idranme
ed55a5a54c optimize 2024-09-12 17:52:21 +08:00
idranme
2c4fdbfa6a fix: upload_group_file API 2024-09-12 16:46:38 +08:00
idranme
1132495eb3 fix: check for updates 2024-09-12 01:11:17 +08:00
idranme
2ac2c68435 chore 2024-09-11 22:13:11 +08:00
idranme
6477366ba6 chore
chore
2024-09-11 21:35:50 +08:00
idranme
1d63473a04 Merge pull request #411 from LLOneBot/dev
3.32.3
2024-09-11 20:52:52 +08:00
idranme
692ba5163e chore: v3.32.3 2024-09-11 20:52:16 +08:00
idranme
8bcea090ec Merge pull request #410 from LLOneBot/main
merge branch
2024-09-11 20:50:38 +08:00
idranme
67568a662d feat: profile_like event 2024-09-11 20:48:32 +08:00
linyuchen
66b3706524 ♻️refactor: rkey server url 2024-09-10 17:35:14 +08:00
idranme
6d82dd1dad chore 2024-09-10 17:16:47 +08:00
idranme
20f2e66b11 chore
chore

chore
2024-09-10 16:59:12 +08:00
idranme
efefb963c0 Merge pull request #409 from LLOneBot/main
merge branch
2024-09-10 15:32:58 +08:00
linyuchen
92d780cf70 fix: download file add referer 2024-09-10 15:01:50 +08:00
idranme
ce6886011f Merge pull request #407 from LLOneBot/dev
3.32.1
2024-09-10 13:44:03 +08:00
idranme
319b275e4f chore: v3.32.1 2024-09-10 13:39:12 +08:00
idranme
9738c3f63c feat: GetProfileLike 2024-09-10 13:36:00 +08:00
idranme
3de054600c chore 2024-09-09 17:06:33 +08:00
idranme
da0ebd3f80 refactor 2024-09-08 23:37:20 +08:00
idranme
d5682a9788 Merge pull request #406 from LLOneBot/dev
3.32.0
2024-09-08 21:11:52 +08:00
idranme
a1298b1c93 optimize 2024-09-08 21:09:03 +08:00
idranme
6fac490d66 chore: v3.32.0 2024-09-08 20:54:32 +08:00
idranme
dc25d83778 feat 2024-09-08 20:50:22 +08:00
idranme
1bcdbba29a feat 2024-09-08 15:56:05 +08:00
idranme
9ecfb6ea0c optimize 2024-09-08 01:18:14 +08:00
idranme
5c68d4de84 Merge pull request #404 from LLOneBot/dev
3.31.10
2024-09-07 21:55:04 +08:00
idranme
c1c22e872e chore: v3.31.10 2024-09-07 21:52:34 +08:00
idranme
709c0b6f7b fix 2024-09-07 21:51:17 +08:00
idranme
dd34286b43 refactor 2024-09-07 17:45:36 +08:00
idranme
80487e31f5 chore 2024-09-07 03:03:33 +08:00
idranme
bdf1c7fd5f chore 2024-09-07 02:56:59 +08:00
idranme
285fc1d33d Merge pull request #398 from LLOneBot/dev
3.31.9
2024-09-06 23:12:47 +08:00
idranme
e14ba108fc chore: v3.31.9 2024-09-06 23:06:43 +08:00
idranme
e0be0bcc77 fix 2024-09-06 23:04:17 +08:00
idranme
c24fa6cea1 chore: improve code quality
chore: improve code quality
2024-09-06 23:04:17 +08:00
idranme
04b2a323a7 chore: improve code quality 2024-09-06 23:03:04 +08:00
idranme
970f1a98ec chore: improve code quality
chore: improve code quality
2024-09-06 23:03:04 +08:00
idranme
3064a6eb7c chore: improve code quality 2024-09-06 22:59:14 +08:00
idranme
f93e2b5a95 chore: improve code quality
chore: improve code quality
2024-09-06 22:59:14 +08:00
idranme
e185e700b7 chore: improve code quality
chore: improve code quality

chore: improve code quality
2024-09-06 22:58:00 +08:00
idranme
eae6e09e22 optimize 2024-09-05 17:16:42 +08:00
idranme
e204bb0957 Merge pull request #395 from LLOneBot/dev
3.31.8
2024-09-05 15:00:57 +08:00
idranme
ed546ace3d chore: v3.31.8 2024-09-05 15:00:05 +08:00
idranme
3c79cffa42 optimize 2024-09-05 14:58:52 +08:00
idranme
acce444dee optimize 2024-09-05 02:26:42 +08:00
idranme
f359e3ea9d fix 2024-09-05 02:20:23 +08:00
idranme
fe99da985f Merge pull request #394 from LLOneBot/dev
3.31.7
2024-09-04 20:35:08 +08:00
idranme
58d5de572c chore: v3.31.7 2024-09-04 20:32:44 +08:00
idranme
b2088824cc feat 2024-09-04 17:15:41 +08:00
idranme
fffa664400 fix: reply message segment 2024-09-04 16:18:48 +08:00
idranme
02e5222f92 feat: SendGroupNotice 2024-09-04 15:42:10 +08:00
idranme
18d253edf6 fix: GroupMsgEmojiLikeEvent 2024-09-04 13:13:49 +08:00
idranme
da8b5e2429 chore 2024-09-04 13:12:39 +08:00
idranme
502be69bc5 feat: SetOnlineStatus 2024-09-04 01:23:25 +08:00
idranme
273d4133eb refactor 2024-09-04 00:44:41 +08:00
idranme
44bfc5aab9 optimize 2024-09-03 21:46:26 +08:00
idranme
050c9d9b54 fix 2024-09-03 21:43:18 +08:00
idranme
7904f45c20 Merge pull request #392 from LLOneBot/dev
3.31.6
2024-09-03 18:38:07 +08:00
idranme
1afdad1452 chore: v3.31.6 2024-09-03 18:34:30 +08:00
idranme
cd930c43b6 feat: GetGroupRootFiles 2024-09-03 15:14:05 +08:00
idranme
b7efbdf239 fix: ws 2024-09-03 13:16:25 +08:00
idranme
56706f3838 chore 2024-09-03 01:24:21 +08:00
idranme
387c9dcb52 refactor 2024-09-03 01:04:16 +08:00
idranme
a7bb55b31c chore 2024-09-02 19:53:18 +08:00
idranme
fbf09e1db4 chore 2024-09-02 19:48:17 +08:00
idranme
9b98f8f33d optimize 2024-09-02 19:30:23 +08:00
idranme
727f399de6 fix: GetGroupMsgHistory 2024-09-02 19:24:27 +08:00
idranme
e03b82fb44 optimize: ci 2024-09-02 18:28:21 +08:00
idranme
ba413b9581 Merge pull request #390 from LLOneBot/dev
3.31.5
2024-09-02 16:42:35 +08:00
idranme
abcec99ce0 chore: v3.31.5 2024-09-02 16:39:36 +08:00
idranme
a7da7ab598 optimize 2024-09-02 01:58:31 +08:00
idranme
5cc8a2b96e fix 2024-09-02 01:46:08 +08:00
idranme
f0d8c851d4 optimize 2024-09-02 01:24:15 +08:00
idranme
828b20e0e8 optimize 2024-09-02 01:05:58 +08:00
idranme
3570349fcd optimize 2024-09-02 00:42:35 +08:00
idranme
ad74854e42 fix 2024-09-01 20:28:12 +08:00
idranme
15e7afed62 Merge pull request #385 from LLOneBot/dev
3.31.4
2024-09-01 18:50:38 +08:00
idranme
bf71328650 chore: v3.31.4 2024-09-01 18:50:09 +08:00
idranme
b3299ba1e3 chore 2024-09-01 15:39:37 +08:00
idranme
d36ea93e63 refactor 2024-09-01 15:26:34 +08:00
idranme
0bd3f8f1a2 feat 2024-09-01 15:26:11 +08:00
idranme
4bf79e021e Merge pull request #383 from LLOneBot/dev
3.31.3
2024-09-01 00:36:41 +08:00
idranme
2dac109e58 chore: v3.31.3 2024-09-01 00:34:08 +08:00
idranme
2637a5da6d chore 2024-08-31 22:59:42 +08:00
idranme
f8b2be246f optimize 2024-08-31 22:55:26 +08:00
idranme
44921e85ad chore 2024-08-31 19:46:35 +08:00
idranme
388e016365 optimize 2024-08-31 19:41:48 +08:00
idranme
a2056a43f3 fix 2024-08-31 01:29:44 +08:00
idranme
a249e0b581 Merge pull request #381 from LLOneBot/dev
3.31.2
2024-08-30 12:47:18 +08:00
idranme
f7343332d7 chore: v3.31.2 2024-08-30 12:46:03 +08:00
idranme
bf17d46157 fix 2024-08-30 12:38:39 +08:00
idranme
3e3f792035 optimize 2024-08-30 03:09:34 +08:00
idranme
d7cc5d68a7 refactor 2024-08-30 02:52:21 +08:00
idranme
64a8efb8df optimize 2024-08-30 02:51:56 +08:00
idranme
6af31c48c4 fix 2024-08-29 20:48:08 +08:00
idranme
6954551cb7 feat 2024-08-29 18:06:53 +08:00
idranme
c71885a29e refactor 2024-08-28 23:57:11 +08:00
idranme
183eab2cf4 optimize 2024-08-28 17:13:26 +08:00
143 changed files with 4947 additions and 4104 deletions

7
.gitattributes vendored Normal file
View File

@@ -0,0 +1,7 @@
* text eol=lf
*.png -text
*.jpg -text
*.ico -text
*.gif -text
*.webp -text

View File

@@ -7,9 +7,6 @@ body:
attributes: attributes:
value: | value: |
欢迎来到 LLOneBot 的 Issue Tracker请填写以下表格来提交 Bug。 欢迎来到 LLOneBot 的 Issue Tracker请填写以下表格来提交 Bug。
在提交新的 Bug 反馈前,请确保您:
* 已经搜索了现有的 issues并且没有找到可以解决您问题的方法
* 不与现有的某一 issue 重复
- type: input - type: input
id: system-version id: system-version
attributes: attributes:
@@ -40,8 +37,6 @@ body:
label: OneBot 客户端 label: OneBot 客户端
description: 连接至 LLOneBot 的客户端版本信息 description: 连接至 LLOneBot 的客户端版本信息
placeholder: Overflow 2.16.0-2cf7991-SNAPSHOT placeholder: Overflow 2.16.0-2cf7991-SNAPSHOT
validations:
required: true
- type: textarea - type: textarea
id: what-happened id: what-happened
attributes: attributes:

View File

@@ -1,4 +1,4 @@
name: 'publish' name: Publish
on: on:
push: push:
tags: tags:
@@ -8,31 +8,36 @@ jobs:
build-and-publish: build-and-publish:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: setup node - name: Setup node
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20
- name: install dependenies - name: Install dependenies
run: | run: |
export ELECTRON_SKIP_BINARY_DOWNLOAD=1 export ELECTRON_SKIP_BINARY_DOWNLOAD=1
npm install npm install
- name: build - name: Build
run: npm run build run: npm run build
- name: zip - name: Compress
run: | run: |
sudo apt install zip -y sudo apt install zip -y
cd ./dist/ cd ./dist/
zip -r ../LLOneBot.zip ./* zip -r ../LLOneBot.zip ./*
- name: publish - name: Extract version from tag
id: get-version
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT"
- name: Release
uses: ncipollo/release-action@v1 uses: ncipollo/release-action@v1
with: with:
artifacts: 'LLOneBot.zip' artifacts: 'LLOneBot.zip'
draft: true draft: true
token: ${{ secrets.RELEASE_TOKEN }} token: ${{ secrets.RELEASE_TOKEN }}
name: LLOneBot v${{ steps.get-version.outputs.VERSION }}

3
.gitignore vendored
View File

@@ -11,5 +11,6 @@ node_modules
dist dist
out out
.idea/
.DS_Store .DS_Store
.idea
.vscode

View File

@@ -15,10 +15,6 @@ TG 群:<https://t.me/+nLZEnpne-pQ1OWFl>
<img src="./doc/image/setting.png" width="400px" alt="设置界面"/> <img src="./doc/image/setting.png" width="400px" alt="设置界面"/>
## HTTP 调用示例
<img src="./doc/image/example.jpg" width="500px" alt="HTTP调用示例"/>
## 支持的 API ## 支持的 API
<https://llonebot.github.io/zh-CN/develop/api> <https://llonebot.github.io/zh-CN/develop/api>
@@ -31,10 +27,10 @@ TG 群:<https://t.me/+nLZEnpne-pQ1OWFl>
- [NapCatQQ](https://github.com/NapNeko/NapCatQQ) - [NapCatQQ](https://github.com/NapNeko/NapCatQQ)
- [LiteLoaderQQNT](https://liteloaderqqnt.github.io/guide/install.html) - [LiteLoaderQQNT](https://liteloaderqqnt.github.io/guide/install.html)
- [chronocat](https://github.com/chrononeko/chronocat) - [Chronocat](https://github.com/chrononeko/chronocat)
- [koishi-plugin-adapter-onebot](https://github.com/koishijs/koishi-plugin-adapter-onebot) - [koishi-plugin-adapter-onebot](https://github.com/koishijs/koishi-plugin-adapter-onebot)
- [silk-wasm](https://github.com/idranme/silk-wasm) - [silk-wasm](https://github.com/idranme/silk-wasm)
## 友链 ## 友链
- [Lagrange.Core](https://github.com/LagrangeDev/Lagrange.Core) 一款用C#实现的NTQQ纯协议跨平台QQ机器人框架 - [Lagrange.Core](https://github.com/LagrangeDev/Lagrange.Core): An Implementation of NTQQ Protocol

View File

@@ -1,6 +1,7 @@
import cp from 'vite-plugin-cp' import cp from 'vite-plugin-cp'
import path from 'node:path' import path from 'node:path'
import './scripts/gen-manifest' import './scripts/gen-manifest'
import type { ElectronViteConfig } from 'electron-vite'
const external = [ const external = [
'silk-wasm', 'silk-wasm',
@@ -12,7 +13,7 @@ function genCpModule(module: string) {
return { src: `./node_modules/${module}`, dest: `dist/node_modules/${module}`, flatten: false } return { src: `./node_modules/${module}`, dest: `dist/node_modules/${module}`, flatten: false }
} }
let config = { const config: ElectronViteConfig = {
main: { main: {
build: { build: {
outDir: 'dist/main', outDir: 'dist/main',
@@ -30,7 +31,6 @@ let config = {
resolve: { resolve: {
alias: { alias: {
'@': path.resolve(__dirname, './src'), '@': path.resolve(__dirname, './src'),
'./lib-cov/fluent-ffmpeg': './lib/fluent-ffmpeg',
}, },
}, },
plugins: [ plugins: [
@@ -39,9 +39,6 @@ let config = {
...external.map(genCpModule), ...external.map(genCpModule),
{ src: './manifest.json', dest: 'dist' }, { src: './manifest.json', dest: 'dist' },
{ src: './icon.webp', dest: 'dist' }, { src: './icon.webp', dest: 'dist' },
// { src: './src/ntqqapi/native/crychic/crychic-win32-x64.node', dest: 'dist/main/' },
// { src: './src/ntqqapi/native/moehook/MoeHoo-win32-x64.node', dest: 'dist/main/' },
// { src: './src/ntqqapi/native/moehook/MoeHoo-linux-x64.node', dest: 'dist/main/' },
], ],
}), }),
], ],

View File

@@ -4,7 +4,7 @@
"name": "LLOneBot", "name": "LLOneBot",
"slug": "LLOneBot", "slug": "LLOneBot",
"description": "实现 OneBot 11 协议,用于 QQ 机器人开发", "description": "实现 OneBot 11 协议,用于 QQ 机器人开发",
"version": "3.31.1", "version": "3.33.0",
"icon": "./icon.webp", "icon": "./icon.webp",
"authors": [ "authors": [
{ {

View File

@@ -7,25 +7,27 @@
"scripts": { "scripts": {
"build": "electron-vite build", "build": "electron-vite build",
"build-mac": "npm run build && npm run deploy-mac", "build-mac": "npm run build && npm run deploy-mac",
"deploy-mac": "cp -r dist/* ~/Library/Containers/com.tencent.qq/Data/LiteLoaderQQNT/plugins/LLOneBot/", "deploy-mac": "cp -r dist/* ~/Library/Containers/com.tencent.qq/Data/Documents/LiteLoaderQQNT/plugins/LLOneBot/",
"build-win": "npm run build && npm run deploy-win", "build-win": "npm run build && npm run deploy-win",
"deploy-win": "cmd /c \"xcopy /C /S /Y dist\\* %USERPROFILE%\\documents\\LiteLoaderQQNT\\plugins\\LLOneBot\\\"", "deploy-win": "cmd /c \"xcopy /C /S /Y dist\\* %LITELOADERQQNT_PROFILE%\\plugins\\LLOneBot\\\"",
"format": "prettier -cw .", "format": "prettier -cw .",
"check": "tsc" "check": "tsc",
"compile:proto": "pbjs --no-create --no-convert --no-encode --no-verify -t static-module -w es6 -p src/ntqqapi/proto -o src/ntqqapi/proto/compiled.js systemMessage.proto profileLikeTip.proto && pbts -o src/ntqqapi/proto/compiled.d.ts src/ntqqapi/proto/compiled.js"
}, },
"author": "", "author": "",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@minatojs/driver-sqlite": "^4.5.0", "@minatojs/driver-sqlite": "^4.6.0",
"compressing": "^1.10.1", "compressing": "^1.10.1",
"cordis": "^3.18.0", "cordis": "^3.18.1",
"cors": "^2.8.5", "cors": "^2.8.5",
"cosmokit": "^1.6.2", "cosmokit": "^1.6.2",
"express": "^4.19.2", "express": "^5.0.0",
"fast-xml-parser": "^4.4.1", "fast-xml-parser": "^4.5.0",
"file-type": "^19.4.1", "file-type": "^19.5.0",
"fluent-ffmpeg": "^2.1.3", "fluent-ffmpeg": "^2.1.3",
"minato": "^3.5.1", "minato": "^3.6.0",
"protobufjs": "^7.4.0",
"silk-wasm": "^3.6.1", "silk-wasm": "^3.6.1",
"ws": "^8.18.0" "ws": "^8.18.0"
}, },
@@ -37,8 +39,9 @@
"@types/ws": "^8.5.12", "@types/ws": "^8.5.12",
"electron": "^31.4.0", "electron": "^31.4.0",
"electron-vite": "^2.3.0", "electron-vite": "^2.3.0",
"typescript": "^5.5.4", "protobufjs-cli": "^1.1.3",
"vite": "^5.4.2", "typescript": "^5.6.2",
"vite": "^5.4.6",
"vite-plugin-cp": "^4.0.8" "vite-plugin-cp": "^4.0.8"
}, },
"packageManager": "yarn@4.4.1" "packageManager": "yarn@4.4.1"

View File

@@ -1,25 +1,8 @@
import fs from 'node:fs' import fs from 'node:fs'
import { Config, OB11Config } from './types'
import path from 'node:path' import path from 'node:path'
import { Config, OB11Config } from './types'
import { selfInfo, DATA_DIR } from './globalVars' import { selfInfo, DATA_DIR } from './globalVars'
import { mergeNewProperties } from './utils/misc'
// 在保证老对象已有的属性不变化的情况下将新对象的属性复制到老对象
function mergeNewProperties(newObj: any, oldObj: any) {
Object.keys(newObj).forEach((key) => {
// 如果老对象不存在当前属性,则直接复制
if (!oldObj.hasOwnProperty(key)) {
oldObj[key] = newObj[key]
} else {
// 如果老对象和新对象的当前属性都是对象,则递归合并
if (typeof oldObj[key] === 'object' && typeof newObj[key] === 'object') {
mergeNewProperties(newObj[key], oldObj[key])
} else if (typeof oldObj[key] === 'object' || typeof newObj[key] === 'object') {
// 属性冲突,有一方不是对象,直接覆盖
oldObj[key] = newObj[key]
}
}
})
}
export class ConfigUtil { export class ConfigUtil {
private readonly configPath: string private readonly configPath: string
@@ -38,7 +21,7 @@ export class ConfigUtil {
} }
reloadConfig(): Config { reloadConfig(): Config {
let ob11Default: OB11Config = { const ob11Default: OB11Config = {
httpPort: 3000, httpPort: 3000,
httpHosts: [], httpHosts: [],
httpSecret: '', httpSecret: '',
@@ -50,9 +33,9 @@ export class ConfigUtil {
enableWsReverse: false, enableWsReverse: false,
messagePostFormat: 'array', messagePostFormat: 'array',
enableHttpHeart: false, enableHttpHeart: false,
enableQOAutoQuote: false listenLocalhost: false
} }
let defaultConfig: Config = { const defaultConfig: Config = {
enableLLOB: true, enableLLOB: true,
ob11: ob11Default, ob11: ob11Default,
heartInterval: 60000, heartInterval: 60000,
@@ -83,7 +66,6 @@ export class ConfigUtil {
this.checkOldConfig(jsonData.ob11, jsonData, 'httpPort', 'http') this.checkOldConfig(jsonData.ob11, jsonData, 'httpPort', 'http')
this.checkOldConfig(jsonData.ob11, jsonData, 'httpHosts', 'hosts') this.checkOldConfig(jsonData.ob11, jsonData, 'httpHosts', 'hosts')
this.checkOldConfig(jsonData.ob11, jsonData, 'wsPort', 'wsPort') this.checkOldConfig(jsonData.ob11, jsonData, 'wsPort', 'wsPort')
// console.log("get config", jsonData);
this.config = jsonData this.config = jsonData
return this.config return this.config
} }
@@ -95,15 +77,15 @@ export class ConfigUtil {
} }
private checkOldConfig( private checkOldConfig(
currentConfig: Config | OB11Config, currentConfig: OB11Config,
oldConfig: Config | OB11Config, oldConfig: Config,
currentKey: string, currentKey: 'httpPort' | 'httpHosts' | 'wsPort',
oldKey: string, oldKey: 'http' | 'hosts' | 'wsPort',
) { ) {
// 迁移旧的配置到新配置,避免用户重新填写配置 // 迁移旧的配置到新配置,避免用户重新填写配置
const oldValue = oldConfig[oldKey] const oldValue = oldConfig[oldKey]
if (oldValue) { if (oldValue) {
currentConfig[currentKey] = oldValue Object.assign(currentConfig, { [currentKey]: oldValue })
delete oldConfig[oldKey] delete oldConfig[oldKey]
} }
} }

View File

@@ -10,7 +10,12 @@ export interface OB11Config {
enableWsReverse?: boolean enableWsReverse?: boolean
messagePostFormat?: 'array' | 'string' messagePostFormat?: 'array' | 'string'
enableHttpHeart?: boolean enableHttpHeart?: boolean
enableQOAutoQuote: boolean // 快速操作回复自动引用原消息 /**
* 快速操作回复自动引用原消息
* @deprecated
*/
enableQOAutoQuote?: boolean
listenLocalhost: boolean
} }
export interface CheckVersion { export interface CheckVersion {
@@ -34,6 +39,12 @@ export interface Config {
ignoreBeforeLoginMsg?: boolean ignoreBeforeLoginMsg?: boolean
/** 单位为秒 */ /** 单位为秒 */
msgCacheExpire?: number msgCacheExpire?: number
/** @deprecated */
http?: string
/** @deprecated */
hosts?: string[]
/** @deprecated */
wsPort?: string
} }
export interface LLOneBotError { export interface LLOneBotError {

View File

@@ -3,7 +3,6 @@ import ffmpeg from 'fluent-ffmpeg'
import fsPromise from 'node:fs/promises' import fsPromise from 'node:fs/promises'
import { decode, encode, getDuration, getWavFileInfo, isWav, isSilk, EncodeResult } from 'silk-wasm' import { decode, encode, getDuration, getWavFileInfo, isWav, isSilk, EncodeResult } from 'silk-wasm'
import { TEMP_DIR } from '../globalVars' import { TEMP_DIR } from '../globalVars'
import { getConfigUtil } from '../config'
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { Readable } from 'node:stream' import { Readable } from 'node:stream'
import { Context } from 'cordis' import { Context } from 'cordis'
@@ -17,8 +16,8 @@ type Input = string | Readable
function convert(ctx: Context, input: Input, options: FFmpegOptions): Promise<Buffer> function convert(ctx: Context, input: Input, options: FFmpegOptions): Promise<Buffer>
function convert(ctx: Context, input: Input, options: FFmpegOptions, outputPath: string): Promise<string> function convert(ctx: Context, input: Input, options: FFmpegOptions, outputPath: string): Promise<string>
function convert(ctx: Context, input: Input, options: FFmpegOptions, outputPath?: string): Promise<Buffer> | Promise<string> { function convert(ctx: Context, input: Input, options: FFmpegOptions, outputPath?: string): Promise<Buffer | string> {
return new Promise<any>((resolve, reject) => { return new Promise((resolve, reject) => {
const chunks: Buffer[] = [] const chunks: Buffer[] = []
let command = ffmpeg(input) let command = ffmpeg(input)
.on('error', err => { .on('error', err => {
@@ -38,7 +37,7 @@ function convert(ctx: Context, input: Input, options: FFmpegOptions, outputPath?
if (options.output) { if (options.output) {
command = command.outputOptions(options.output) command = command.outputOptions(options.output)
} }
const ffmpegPath = getConfigUtil().getConfig().ffmpeg const ffmpegPath: string | undefined = ctx.config.ffmpeg
if (ffmpegPath) { if (ffmpegPath) {
command = command.setFfmpegPath(ffmpegPath) command = command.setFfmpegPath(ffmpegPath)
} }
@@ -85,8 +84,8 @@ export async function encodeSilk(ctx: Context, filePath: string) {
let duration = 1 let duration = 1
try { try {
duration = getDuration(silk) / 1000 duration = getDuration(silk) / 1000
} catch (e: any) { } catch (e) {
ctx.logger.warn('获取语音文件时长失败, 默认为1秒', filePath, e.stack) ctx.logger.warn('获取语音文件时长失败, 默认为1秒', filePath, (e as Error).stack)
} }
return { return {
converted: false, converted: false,
@@ -94,15 +93,15 @@ export async function encodeSilk(ctx: Context, filePath: string) {
duration, duration,
} }
} }
} catch (error: any) { } catch (err) {
ctx.logger.error('convert silk failed', error.stack) ctx.logger.error('convert silk failed', (err as Error).stack)
return {} return {}
} }
} }
type OutFormat = 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac' type OutFormat = 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac'
export async function decodeSilk(ctx: Context, inputFilePath: string, outFormat: OutFormat = 'mp3') { export async function decodeSilk(ctx: Context, inputFilePath: string, outFormat: OutFormat) {
const silk = await fsPromise.readFile(inputFilePath) const silk = await fsPromise.readFile(inputFilePath)
const { data } = await decode(silk, 24000) const { data } = await decode(silk, 24000)
const tmpPath = path.join(TEMP_DIR, path.basename(inputFilePath)) const tmpPath = path.join(TEMP_DIR, path.basename(inputFilePath))

View File

@@ -1,234 +0,0 @@
import { NodeIQQNTWrapperSession } from '@/ntqqapi/wrapper'
import { randomUUID } from 'node:crypto'
interface Internal_MapKey {
timeout: number
createtime: number
func: (...arg: any[]) => any
checker: ((...args: any[]) => boolean) | undefined
}
export class ListenerClassBase {
[key: string]: string
}
export interface ListenerIBase {
new(listener: any): ListenerClassBase
}
// forked from https://github.com/NapNeko/NapCatQQ/blob/6f6b258f22d7563f15d84e7172c4d4cbb547f47e/src/common/utils/EventTask.ts#L20
export class NTEventWrapper {
private ListenerMap: { [key: string]: ListenerIBase } | undefined//ListenerName-Unique -> Listener构造函数
private WrapperSession: NodeIQQNTWrapperSession | undefined//WrapperSession
private ListenerManger: Map<string, ListenerClassBase> = new Map<string, ListenerClassBase>() //ListenerName-Unique -> Listener实例
private EventTask = new Map<string, Map<string, Map<string, Internal_MapKey>>>()//tasks ListenerMainName -> ListenerSubName-> uuid -> {timeout,createtime,func}
public initialised = false
constructor() {
}
createProxyDispatch(ListenerMainName: string) {
const current = this
return new Proxy({}, {
get(target: any, prop: any, receiver: any) {
// console.log('get', prop, typeof target[prop])
if (typeof target[prop] === 'undefined') {
// 如果方法不存在返回一个函数这个函数调用existentMethod
return (...args: any[]) => {
current.dispatcherListener.apply(current, [ListenerMainName, prop, ...args]).then()
}
}
// 如果方法存在,正常返回
return Reflect.get(target, prop, receiver)
}
})
}
init({ ListenerMap, WrapperSession }: { ListenerMap: { [key: string]: typeof ListenerClassBase }, WrapperSession: NodeIQQNTWrapperSession }) {
this.ListenerMap = ListenerMap
this.WrapperSession = WrapperSession
this.initialised = true
}
createEventFunction<T extends (...args: any) => any>(eventName: string): T | undefined {
const eventNameArr = eventName.split('/')
type eventType = {
[key: string]: () => { [key: string]: (...params: Parameters<T>) => Promise<ReturnType<T>> }
}
if (eventNameArr.length > 1) {
const serviceName = 'get' + eventNameArr[0].replace('NodeIKernel', '')
const eventName = eventNameArr[1]
//getNodeIKernelGroupListener,GroupService
//console.log('2', eventName)
const services = (this.WrapperSession as unknown as eventType)[serviceName]()
let event = services[eventName]
//重新绑定this
event = event.bind(services)
if (event) {
return event as T
}
return undefined
}
}
createListenerFunction<T>(listenerMainName: string, uniqueCode: string = ''): T {
const ListenerType = this.ListenerMap![listenerMainName]
let Listener = this.ListenerManger.get(listenerMainName + uniqueCode)
if (!Listener && ListenerType) {
Listener = new ListenerType(this.createProxyDispatch(listenerMainName))
const ServiceSubName = listenerMainName.match(/^NodeIKernel(.*?)Listener$/)![1]
const Service = 'NodeIKernel' + ServiceSubName + 'Service/addKernel' + ServiceSubName + 'Listener'
const addfunc = this.createEventFunction<(listener: T) => number>(Service)
addfunc!(Listener as T)
//console.log(addfunc!(Listener as T))
this.ListenerManger.set(listenerMainName + uniqueCode, Listener)
}
return Listener as T
}
//统一回调清理事件
async dispatcherListener(ListenerMainName: string, ListenerSubName: string, ...args: any[]) {
//console.log("[EventDispatcher]",ListenerMainName, ListenerSubName, ...args)
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.forEach((task, uuid) => {
//console.log(task.func, uuid, task.createtime, task.timeout)
if (task.createtime + task.timeout < Date.now()) {
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.delete(uuid)
return
}
if (task.checker && task.checker(...args)) {
task.func(...args)
}
})
}
async CallNoListenerEvent<EventType extends (...args: any[]) => Promise<any> | any>(EventName = '', timeout: number = 3000, ...args: Parameters<EventType>) {
return new Promise<Awaited<ReturnType<EventType>>>(async (resolve, reject) => {
const EventFunc = this.createEventFunction<EventType>(EventName)
let complete = false
const Timeouter = setTimeout(() => {
if (!complete) {
reject(new Error('NTEvent EventName:' + EventName + ' timeout'))
}
}, timeout)
const retData = await EventFunc!(...args)
complete = true
resolve(retData)
})
}
async RegisterListen<ListenerType extends (...args: any[]) => void>(ListenerName = '', waitTimes = 1, timeout = 5000, checker: (...args: Parameters<ListenerType>) => boolean) {
return new Promise<Parameters<ListenerType>>((resolve, reject) => {
const ListenerNameList = ListenerName.split('/')
const ListenerMainName = ListenerNameList[0]
const ListenerSubName = ListenerNameList[1]
const id = randomUUID()
let complete = 0
let retData: Parameters<ListenerType> | undefined = undefined
const databack = () => {
if (complete == 0) {
reject(new Error(' ListenerName:' + ListenerName + ' timeout'))
} else {
resolve(retData!)
}
}
const Timeouter = setTimeout(databack, timeout)
const eventCallbak = {
timeout: timeout,
createtime: Date.now(),
checker: checker,
func: (...args: Parameters<ListenerType>) => {
complete++
retData = args
if (complete >= waitTimes) {
clearTimeout(Timeouter)
databack()
}
}
}
if (!this.EventTask.get(ListenerMainName)) {
this.EventTask.set(ListenerMainName, new Map())
}
if (!(this.EventTask.get(ListenerMainName)?.get(ListenerSubName))) {
this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map())
}
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak)
this.createListenerFunction(ListenerMainName)
})
}
async CallNormalEvent<EventType extends (...args: any[]) => Promise<any>, ListenerType extends (...args: any[]) => void>
(EventName = '', ListenerName = '', waitTimes = 1, timeout: number = 3000, checker: (...args: Parameters<ListenerType>) => boolean, ...args: Parameters<EventType>) {
return new Promise<[EventRet: Awaited<ReturnType<EventType>>, ...Parameters<ListenerType>]>(async (resolve, reject) => {
const id = randomUUID()
let complete = 0
let retData: Parameters<ListenerType> | undefined = undefined
let retEvent: any = {}
const databack = () => {
if (complete == 0) {
reject(new Error('Timeout: NTEvent EventName:' + EventName + ' ListenerName:' + ListenerName + ' EventRet:\n' + JSON.stringify(retEvent, null, 4) + '\n'))
} else {
resolve([retEvent as Awaited<ReturnType<EventType>>, ...retData!])
}
}
const ListenerNameList = ListenerName.split('/')
const ListenerMainName = ListenerNameList[0]
const ListenerSubName = ListenerNameList[1]
const Timeouter = setTimeout(databack, timeout)
const eventCallbak = {
timeout: timeout,
createtime: Date.now(),
checker: checker,
func: (...args: any[]) => {
complete++
//console.log('func', ...args)
retData = args as Parameters<ListenerType>
if (complete >= waitTimes) {
clearTimeout(Timeouter)
databack()
}
}
}
if (!this.EventTask.get(ListenerMainName)) {
this.EventTask.set(ListenerMainName, new Map())
}
if (!(this.EventTask.get(ListenerMainName)?.get(ListenerSubName))) {
this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map())
}
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak)
this.createListenerFunction(ListenerMainName)
const EventFunc = this.createEventFunction<EventType>(EventName)
retEvent = await EventFunc!(...(args as any[]))
})
}
}
export const NTEventDispatch = new NTEventWrapper()
// 示例代码 快速创建事件
// let NTEvent = new NTEventWrapper()
// let TestEvent = NTEvent.CreatEventFunction<(force: boolean) => Promise<Number>>('NodeIKernelProfileLikeService/GetTest')
// if (TestEvent) {
// TestEvent(true)
// }
// 示例代码 快速创建监听Listener类
// let NTEvent = new NTEventWrapper()
// NTEvent.CreatListenerFunction<NodeIKernelMsgListener>('NodeIKernelMsgListener', 'core')
// 调用接口
//let NTEvent = new NTEventWrapper()
//let ret = await NTEvent.CallNormalEvent<(force: boolean) => Promise<Number>, (data1: string, data2: number) => void>('NodeIKernelProfileLikeService/GetTest', 'NodeIKernelMsgListener/onAddSendMsg', 1, 3000, true)
// 注册监听 解除监听
// NTEventDispatch.RigisterListener('NodeIKernelMsgListener/onAddSendMsg','core',cb)
// NTEventDispatch.UnRigisterListener('NodeIKernelMsgListener/onAddSendMsg','core')
// let GetTest = NTEventDispatch.CreatEvent('NodeIKernelProfileLikeService/GetTest','NodeIKernelMsgListener/onAddSendMsg',Mode)
// GetTest('test')
// always模式
// NTEventDispatch.CreatEvent('NodeIKernelProfileLikeService/GetTest','NodeIKernelMsgListener/onAddSendMsg',Mode,(...args:any[])=>{ console.log(args) })

View File

@@ -4,6 +4,7 @@ import path from 'node:path'
import { TEMP_DIR } from '../globalVars' import { TEMP_DIR } from '../globalVars'
import { randomUUID, createHash } from 'node:crypto' import { randomUUID, createHash } from 'node:crypto'
import { fileURLToPath } from 'node:url' import { fileURLToPath } from 'node:url'
import { fileTypeFromFile } from 'file-type'
export function isGIF(path: string) { export function isGIF(path: string) {
const buffer = Buffer.alloc(4) const buffer = Buffer.alloc(4)
@@ -90,17 +91,26 @@ interface FetchFileRes {
} }
export async function fetchFile(url: string, headersInit?: Record<string, string>): Promise<FetchFileRes> { export async function fetchFile(url: string, headersInit?: Record<string, string>): Promise<FetchFileRes> {
const headers: Record<string, string> = { const headers = new Headers({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36',
'Host': new URL(url).hostname, 'Host': new URL(url).hostname,
...headersInit ...headersInit
} })
const raw = await fetch(url, { headers }).catch((err) => { let raw = await fetch(url, { headers }).catch((err) => {
if (err.cause) { if (err.cause) {
throw err.cause throw err.cause
} }
throw err throw err
}) })
if (raw.status === 403 && !headers.has('Referer')) {
headers.set('Referer', url)
raw = await fetch(url, { headers }).catch((err) => {
if (err.cause) {
throw err.cause
}
throw err
})
}
if (!raw.ok) throw new Error(`statusText: ${raw.statusText}`) if (!raw.ok) throw new Error(`statusText: ${raw.statusText}`)
return { return {
data: Buffer.from(await raw.arrayBuffer()), data: Buffer.from(await raw.arrayBuffer()),
@@ -116,7 +126,7 @@ type Uri2LocalRes = {
isLocal: boolean isLocal: boolean
} }
export async function uri2local(uri: string, filename?: string): Promise<Uri2LocalRes> { export async function uri2local(uri: string, filename?: string, needExt?: boolean): Promise<Uri2LocalRes> {
const { type } = checkUriType(uri) const { type } = checkUriType(uri)
if (type === FileUriType.FileURL) { if (type === FileUriType.FileURL) {
@@ -139,20 +149,32 @@ export async function uri2local(uri: string, filename?: string): Promise<Uri2Loc
} else { } else {
filename ??= randomUUID() filename ??= randomUUID()
} }
const filePath = path.join(TEMP_DIR, filename) let filePath = path.join(TEMP_DIR, filename)
await fsPromise.writeFile(filePath, res.data) await fsPromise.writeFile(filePath, res.data)
if (needExt && !path.extname(filePath)) {
const ext = (await fileTypeFromFile(filePath))?.ext
filename += `.${ext}`
await fsPromise.rename(filePath, `${filePath}.${ext}`)
filePath = `${filePath}.${ext}`
}
return { success: true, errMsg: '', fileName: filename, path: filePath, isLocal: false } return { success: true, errMsg: '', fileName: filename, path: filePath, isLocal: false }
} catch (e: any) { } catch (e) {
const errMsg = `${uri} 下载失败, ${e.message}` const errMsg = `${uri} 下载失败, ${(e as Error).message}`
return { success: false, errMsg, fileName: '', path: '', isLocal: false } return { success: false, errMsg, fileName: '', path: '', isLocal: false }
} }
} }
if (type === FileUriType.OneBotBase64) { if (type === FileUriType.OneBotBase64) {
filename ??= randomUUID() filename ??= randomUUID()
const filePath = path.join(TEMP_DIR, filename) let filePath = path.join(TEMP_DIR, filename)
const base64 = uri.replace(/^base64:\/\//, '') const base64 = uri.replace(/^base64:\/\//, '')
await fsPromise.writeFile(filePath, base64, 'base64') await fsPromise.writeFile(filePath, base64, 'base64')
if (needExt) {
const ext = (await fileTypeFromFile(filePath))?.ext
filename += `.${ext}`
await fsPromise.rename(filePath, `${filePath}.${ext}`)
filePath = `${filePath}.${ext}`
}
return { success: true, errMsg: '', fileName: filename, path: filePath, isLocal: false } return { success: true, errMsg: '', fileName: filename, path: filePath, isLocal: false }
} }
@@ -162,8 +184,14 @@ export async function uri2local(uri: string, filename?: string): Promise<Uri2Loc
if (capture) { if (capture) {
filename ??= randomUUID() filename ??= randomUUID()
const [, _type, base64] = capture const [, _type, base64] = capture
const filePath = path.join(TEMP_DIR, filename) let filePath = path.join(TEMP_DIR, filename)
await fsPromise.writeFile(filePath, base64, 'base64') await fsPromise.writeFile(filePath, base64, 'base64')
if (needExt) {
const ext = (await fileTypeFromFile(filePath))?.ext
filename += `.${ext}`
await fsPromise.rename(filePath, `${filePath}.${ext}`)
filePath = `${filePath}.${ext}`
}
return { success: true, errMsg: '', fileName: filename, path: filePath, isLocal: false } return { success: true, errMsg: '', fileName: filename, path: filePath, isLocal: false }
} }
} }
@@ -175,7 +203,7 @@ export async function copyFolder(sourcePath: string, destPath: string) {
try { try {
const entries = await fsPromise.readdir(sourcePath, { withFileTypes: true }) const entries = await fsPromise.readdir(sourcePath, { withFileTypes: true })
await fsPromise.mkdir(destPath, { recursive: true }) await fsPromise.mkdir(destPath, { recursive: true })
for (let entry of entries) { for (const entry of entries) {
const srcPath = path.join(sourcePath, entry.name) const srcPath = path.join(sourcePath, entry.name)
const dstPath = path.join(destPath, entry.name) const dstPath = path.join(destPath, entry.name)
if (entry.isDirectory()) { if (entry.isDirectory()) {

View File

@@ -2,8 +2,9 @@ import fs from 'fs'
import path from 'node:path' import path from 'node:path'
import { getConfigUtil } from '../config' import { getConfigUtil } from '../config'
import { LOG_DIR } from '../globalVars' import { LOG_DIR } from '../globalVars'
import { Dict } from 'cosmokit'
function truncateString(obj: any, maxLength = 500) { function truncateString(obj: Dict | null, maxLength = 500) {
if (obj !== null && typeof obj === 'object') { if (obj !== null && typeof obj === 'object') {
Object.keys(obj).forEach((key) => { Object.keys(obj).forEach((key) => {
if (typeof obj[key] === 'string') { if (typeof obj[key] === 'string') {
@@ -22,7 +23,7 @@ function truncateString(obj: any, maxLength = 500) {
export const logFileName = `llonebot-${new Date().toLocaleString('zh-CN')}.log`.replace(/\//g, '-').replace(/:/g, '-') export const logFileName = `llonebot-${new Date().toLocaleString('zh-CN')}.log`.replace(/\//g, '-').replace(/:/g, '-')
export function log(...msg: any[]) { export function log(...msg: unknown[]) {
if (!getConfigUtil().getConfig().log) { if (!getConfigUtil().getConfig().log) {
return return
} }

View File

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

View File

@@ -1,4 +1,5 @@
import { QQLevel } from '@/ntqqapi/types' import { QQLevel } from '@/ntqqapi/types'
import { Dict, isNullable } from 'cosmokit'
export function isNumeric(str: string) { export function isNumeric(str: string) {
return /^\d+$/.test(str) return /^\d+$/.test(str)
@@ -14,3 +15,25 @@ export function getBuildVersion(): number {
const version: string = globalThis.LiteLoader.versions.qqnt const version: string = globalThis.LiteLoader.versions.qqnt
return +version.split('-')[1] return +version.split('-')[1]
} }
/** 在保证老对象已有的属性不变化的情况下将新对象的属性复制到老对象 */
export function mergeNewProperties(newObj: Dict, oldObj: Dict) {
Object.keys(newObj).forEach((key) => {
// 如果老对象不存在当前属性,则直接复制
if (!oldObj.hasOwnProperty(key)) {
oldObj[key] = newObj[key]
} else {
// 如果老对象和新对象的当前属性都是对象,则递归合并
if (typeof oldObj[key] === 'object' && typeof newObj[key] === 'object') {
mergeNewProperties(newObj[key], oldObj[key])
} else if (typeof oldObj[key] === 'object' || typeof newObj[key] === 'object') {
// 属性冲突,有一方不是对象,直接覆盖
oldObj[key] = newObj[key]
}
}
})
}
export function filterNullable<T>(array: T[]) {
return array.filter(e => !isNullable(e)) as NonNullable<T>[]
}

View File

@@ -1,106 +1,104 @@
import https from 'node:https' import https from 'node:https'
import http from 'node:http' import http from 'node:http'
import { Dict } from 'cosmokit'
export class RequestUtil { export class RequestUtil {
// 适用于获取服务器下发cookies时获取仅GET // 适用于获取服务器下发cookies时获取仅GET
static async HttpsGetCookies(url: string): Promise<{ [key: string]: string }> { static async HttpsGetCookies(url: string): Promise<{ [key: string]: string }> {
const client = url.startsWith('https') ? https : http; const client = url.startsWith('https') ? https : http
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
client.get(url, (res) => { client.get(url, (res) => {
let cookies: { [key: string]: string } = {}; let cookies: { [key: string]: string } = {}
const handleRedirect = (res: http.IncomingMessage) => { const handleRedirect = (res: http.IncomingMessage) => {
//console.log(res.headers.location);
if (res.statusCode === 301 || res.statusCode === 302) { if (res.statusCode === 301 || res.statusCode === 302) {
if (res.headers.location) { if (res.headers.location) {
const redirectUrl = new URL(res.headers.location, url); const redirectUrl = new URL(res.headers.location, url)
RequestUtil.HttpsGetCookies(redirectUrl.href).then((redirectCookies) => { RequestUtil.HttpsGetCookies(redirectUrl.href).then((redirectCookies) => {
// 合并重定向过程中的cookies // 合并重定向过程中的cookies
//log('redirectCookies', redirectCookies) //log('redirectCookies', redirectCookies)
cookies = { ...cookies, ...redirectCookies }; cookies = { ...cookies, ...redirectCookies }
resolve(cookies); resolve(cookies)
}); })
} else { } else {
resolve(cookies); resolve(cookies)
} }
} else { } else {
resolve(cookies); resolve(cookies)
} }
}; }
res.on('data', () => { }); // Necessary to consume the stream res.on('data', () => { }) // Necessary to consume the stream
res.on('end', () => { res.on('end', () => {
handleRedirect(res); handleRedirect(res)
}); })
if (res.headers['set-cookie']) { if (res.headers['set-cookie']) {
// console.log(res.headers['set-cookie']); //log('set-cookie', url, res.headers['set-cookie'])
//log('set-cookie', url, res.headers['set-cookie']);
res.headers['set-cookie'].forEach((cookie) => { res.headers['set-cookie'].forEach((cookie) => {
const parts = cookie.split(';')[0].split('='); const parts = cookie.split(';')[0].split('=')
const key = parts[0]; const key = parts[0]
const value = parts[1]; const value = parts[1]
if (key && value && key.length > 0 && value.length > 0) { if (key && value && key.length > 0 && value.length > 0) {
cookies[key] = value; cookies[key] = value
} }
}); })
} }
}).on('error', (err) => { }).on('error', (err) => {
reject(err); reject(err)
}); })
}); })
} }
// 请求和回复都是JSON data传原始内容 自动编码json // 请求和回复都是JSON data传原始内容 自动编码json
static async HttpGetJson<T>(url: string, method: string = 'GET', data?: any, headers: Record<string, string> = {}, isJsonRet: boolean = true, isArgJson: boolean = true): Promise<T> { static async HttpGetJson<T>(url: string, method: string = 'GET', data?: unknown, headers: Record<string, string> = {}, isJsonRet: boolean = true, isArgJson: boolean = true): Promise<T> {
let option = new URL(url); const option = new URL(url)
const protocol = url.startsWith('https://') ? https : http; const protocol = url.startsWith('https://') ? https : http
const options = { const options = {
hostname: option.hostname, hostname: option.hostname,
port: option.port, port: option.port,
path: option.href, path: option.href,
method: method, method: method,
headers: headers headers: headers
}; }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const req = protocol.request(options, (res: any) => { const req = protocol.request(options, (res: Dict) => {
let responseBody = ''; let responseBody = ''
res.on('data', (chunk: string | Buffer) => { res.on('data', (chunk: string | Buffer) => {
responseBody += chunk.toString(); responseBody += chunk.toString()
}); })
res.on('end', () => { res.on('end', () => {
try { try {
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) { if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
if (isJsonRet) { if (isJsonRet) {
const responseJson = JSON.parse(responseBody); const responseJson = JSON.parse(responseBody)
resolve(responseJson as T); resolve(responseJson as T)
} else { } else {
resolve(responseBody as T); resolve(responseBody as T)
} }
} else { } else {
reject(new Error(`Unexpected status code: ${res.statusCode}`)); reject(new Error(`Unexpected status code: ${res.statusCode}`))
} }
} catch (parseError) { } catch (parseError) {
reject(parseError); reject(parseError)
} }
}); })
}); })
req.on('error', (error: any) => { req.on('error', (error) => {
reject(error); reject(error)
}); })
if (method === 'POST' || method === 'PUT' || method === 'PATCH') { if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
if (isArgJson) { if (isArgJson) {
req.write(JSON.stringify(data)); req.write(JSON.stringify(data))
} else { } else {
req.write(data); req.write(data)
} }
} }
req.end(); req.end()
}); })
} }
// 请求返回都是原始内容 // 请求返回都是原始内容
static async HttpGetText(url: string, method: string = 'GET', data?: any, headers: Record<string, string> = {}) { static async HttpGetText(url: string, method: string = 'GET', data?: unknown, headers: Record<string, string> = {}) {
return this.HttpGetJson<string>(url, method, data, headers, false, false); return this.HttpGetJson<string>(url, method, data, headers, false, false)
} }
} }

View File

@@ -16,12 +16,11 @@ export class LimitedHashTable<K, V> {
this.keyToValue.set(key, value) this.keyToValue.set(key, value)
this.valueToKey.set(value, key) this.valueToKey.set(value, key)
while (this.keyToValue.size !== this.valueToKey.size) { while (this.keyToValue.size !== this.valueToKey.size) {
console.log('keyToValue.size !== valueToKey.size Error Atom')
this.keyToValue.clear() this.keyToValue.clear()
this.valueToKey.clear() this.valueToKey.clear()
} }
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!
this.valueToKey.delete(this.keyToValue.get(oldestKey)!) this.valueToKey.delete(this.keyToValue.get(oldestKey)!)
this.keyToValue.delete(oldestKey) this.keyToValue.delete(oldestKey)
} }
@@ -56,7 +55,7 @@ export class LimitedHashTable<K, V> {
} }
//获取最近刚写入的几个值 //获取最近刚写入的几个值
getHeads(size: number): { key: K; value: V }[] | undefined { getHeads(size: number): { key: K, value: V }[] | undefined {
const keyList = this.getKeyList() const keyList = this.getKeyList()
if (keyList.length === 0) { if (keyList.length === 0) {
return undefined return undefined

View File

@@ -5,20 +5,22 @@ import { version } from '../../version'
import { copyFolder, log, fetchFile } from '.' import { copyFolder, log, fetchFile } from '.'
import { PLUGIN_DIR, TEMP_DIR } from '../globalVars' import { PLUGIN_DIR, TEMP_DIR } from '../globalVars'
const downloadMirrorHosts = ['https://mirror.ghproxy.com/'] const downloadMirrorHosts = ['https://ghp.ci/']
const checkVersionMirrorHosts = ['https://kkgithub.com'] const releasesMirrorHosts = ['https://kkgithub.com']
export async function checkNewVersion() { export async function checkNewVersion() {
const latestVersionText = await getRemoteVersion() const latestVersionText = await getRemoteVersion()
const latestVersion = latestVersionText.split('.') const latestVersion = latestVersionText.split('.')
//log('llonebot last version', latestVersion) log('LLOneBot latest version', latestVersion)
const currentVersion: string[] = version.split('.') const currentVersion = version.split('.')
//log('llonebot current version', currentVersion) //log('llonebot current version', currentVersion)
for (let k of [0, 1, 2]) { for (const k of [0, 1, 2]) {
if (parseInt(latestVersion[k]) > parseInt(currentVersion[k])) { const latest = parseInt(latestVersion[k])
const current = parseInt(currentVersion[k])
if (latest > current) {
log('') log('')
return { result: true, version: latestVersionText } return { result: true, version: latestVersionText }
} else if (parseInt(latestVersion[k]) < parseInt(currentVersion[k])) { } else if (latest < current) {
break break
} }
} }
@@ -28,7 +30,7 @@ export async function checkNewVersion() {
export async function upgradeLLOneBot() { export async function upgradeLLOneBot() {
const latestVersion = await getRemoteVersion() const latestVersion = await getRemoteVersion()
if (latestVersion && latestVersion != '') { if (latestVersion && latestVersion != '') {
const downloadUrl = 'https://github.com/LLOneBot/LLOneBot/releases/download/v' + latestVersion + '/LLOneBot.zip' const downloadUrl = `https://github.com/LLOneBot/LLOneBot/releases/download/v${latestVersion}/LLOneBot.zip`
const filePath = path.join(TEMP_DIR, './update-' + latestVersion + '.zip') const filePath = path.join(TEMP_DIR, './update-' + latestVersion + '.zip')
let downloadSuccess = false let downloadSuccess = false
// 多镜像下载 // 多镜像下载
@@ -47,14 +49,14 @@ export async function upgradeLLOneBot() {
return false return false
} }
const temp_ver_dir = path.join(TEMP_DIR, 'LLOneBot' + latestVersion) const temp_ver_dir = path.join(TEMP_DIR, 'LLOneBot' + latestVersion)
let uncompressedPromise = async function () { const uncompressedPromise = async function () {
return new Promise<boolean>((resolve, reject) => { return new Promise<boolean>(resolve => {
compressing.zip compressing.zip
.uncompress(filePath, temp_ver_dir) .uncompress(filePath, temp_ver_dir)
.then(() => { .then(() => {
resolve(true) resolve(true)
}) })
.catch((reason: any) => { .catch(reason => {
log('llonebot upgrade failed, ', reason) log('llonebot upgrade failed, ', reason)
if (reason?.errno == -4082) { if (reason?.errno == -4082) {
resolve(true) resolve(true)
@@ -73,26 +75,34 @@ export async function upgradeLLOneBot() {
} }
export async function getRemoteVersion() { export async function getRemoteVersion() {
let Version = '' for (const mirror of releasesMirrorHosts) {
for (let i = 0; i < checkVersionMirrorHosts.length; i++) { const version = await getRemoteVersionByReleasesMirror(mirror)
let mirrorGithub = checkVersionMirrorHosts[i] if (version) {
let tVersion = await getRemoteVersionByMirror(mirrorGithub) return version
if (tVersion && tVersion != '') {
Version = tVersion
break
} }
} }
return Version for (const mirror of downloadMirrorHosts) {
const version = await getRemoteVersionByDownloadMirror(mirror)
if (version) {
return version
}
} }
export async function getRemoteVersionByMirror(mirrorGithub: string) {
let releasePage = 'error'
try {
releasePage = (await fetchFile(mirrorGithub + '/LLOneBot/LLOneBot/releases')).data.toString()
// log("releasePage", releasePage);
if (releasePage === 'error') return ''
return releasePage.match(new RegExp('(?<=(tag/v)).*?(?=("))'))?.[0]
} catch { }
return '' return ''
} }
export async function getRemoteVersionByDownloadMirror(mirrorGithub: string) {
try {
const source = 'https://raw.githubusercontent.com/LLOneBot/LLOneBot/main/src/version.ts'
const page = (await fetchFile(mirrorGithub + source)).data.toString()
return page.match(/(\d+\.\d+\.\d+)/)?.[0]
} catch (e) {
log(e?.toString())
}
}
export async function getRemoteVersionByReleasesMirror(mirrorGithub: string) {
try {
const page = (await fetchFile(mirrorGithub + '/LLOneBot/LLOneBot/releases')).data.toString()
return page.match(new RegExp('(?<=(tag/v)).*?(?=("))'))?.[0]
} catch { }
}

File diff suppressed because one or more lines are too long

7
src/global.d.ts vendored
View File

@@ -2,9 +2,8 @@ import type { LLOneBot } from './preload'
import { Dict } from 'cosmokit' import { Dict } from 'cosmokit'
declare global { declare global {
interface Window { var llonebot: LLOneBot
llonebot: LLOneBot
LiteLoader: Dict
}
var LiteLoader: Dict var LiteLoader: Dict
var authData: Dict | undefined
var navigation: Dict | undefined
} }

View File

@@ -14,28 +14,22 @@ export default class Log {
constructor(ctx: Context, cfg: Config) { constructor(ctx: Context, cfg: Config) {
Logger.targets.splice(0, Logger.targets.length) Logger.targets.splice(0, Logger.targets.length)
if (!cfg.enable) { let enable = cfg.enable
return
}
const file = path.join(LOG_DIR, cfg.filename) const file = path.join(LOG_DIR, cfg.filename)
const refreshNick = ctx.debounce(() => {
const ntUserApi = ctx.get('ntUserApi')
if (ntUserApi && !selfInfo.nick) {
ntUserApi.getSelfNick(true)
}
}, 1000)
const target: Logger.Target = { const target: Logger.Target = {
colors: 0, colors: 0,
record: (record: Logger.Record) => { record: (record: Logger.Record) => {
if (!selfInfo.nick) { if (!enable) {
refreshNick() return
} }
const dateTime = new Date(record.timestamp).toLocaleString() const dateTime = new Date(record.timestamp).toLocaleString()
const userInfo = selfInfo.uin ? `${selfInfo.nick}(${selfInfo.uin})` : '' const content = `${dateTime} [${record.type}] ${selfInfo.nick}(${selfInfo.uin}) | ${record.name} ${record.content}\n\n`
const content = `${dateTime} [${record.type}] ${userInfo} | ${record.name} ${record.content}\n\n`
appendFile(file, content, noop) appendFile(file, content, noop)
}, },
} }
Logger.targets.push(target) Logger.targets.push(target)
ctx.on('llonebot/config-updated', input => {
enable = input.log!
})
} }
} }

View File

@@ -53,15 +53,15 @@ function onLoad() {
fs.mkdirSync(LOG_DIR) fs.mkdirSync(LOG_DIR)
} }
ipcMain.handle(CHANNEL_CHECK_VERSION, async (event, arg) => { ipcMain.handle(CHANNEL_CHECK_VERSION, async () => {
return checkNewVersion() return checkNewVersion()
}) })
ipcMain.handle(CHANNEL_UPDATE, async (event, arg) => { ipcMain.handle(CHANNEL_UPDATE, async () => {
return upgradeLLOneBot() return upgradeLLOneBot()
}) })
ipcMain.handle(CHANNEL_SELECT_FILE, async (event, arg) => { ipcMain.handle(CHANNEL_SELECT_FILE, async () => {
const selectPath = new Promise<string>((resolve, reject) => { const selectPath = new Promise<string>((resolve, reject) => {
dialog dialog
.showOpenDialog({ .showOpenDialog({
@@ -90,10 +90,10 @@ function onLoad() {
} }
}) })
ipcMain.handle(CHANNEL_ERROR, async (event, arg) => { ipcMain.handle(CHANNEL_ERROR, async () => {
const ffmpegOk = await checkFfmpeg(getConfigUtil().getConfig().ffmpeg) const ffmpegOk = await checkFfmpeg(getConfigUtil().getConfig().ffmpeg)
llonebotError.ffmpegError = ffmpegOk ? '' : '没有找到 FFmpeg, 音频只能发送 WAV 和 SILK, 视频尺寸可能异常' llonebotError.ffmpegError = ffmpegOk ? '' : '没有找到 FFmpeg, 音频只能发送 WAV 和 SILK, 视频尺寸可能异常'
let { httpServerError, wsServerError, otherError, ffmpegError } = llonebotError const { httpServerError, wsServerError, otherError, ffmpegError } = llonebotError
let error = `${otherError}\n${httpServerError}\n${wsServerError}\n${ffmpegError}` let error = `${otherError}\n${httpServerError}\n${wsServerError}\n${ffmpegError}`
error = error.replace('\n\n', '\n') error = error.replace('\n\n', '\n')
error = error.trim() error = error.trim()
@@ -101,12 +101,12 @@ function onLoad() {
return error return error
}) })
ipcMain.handle(CHANNEL_GET_CONFIG, async (event, arg) => { ipcMain.handle(CHANNEL_GET_CONFIG, async () => {
const config = getConfigUtil().getConfig() const config = getConfigUtil().getConfig()
return config return config
}) })
ipcMain.handle(CHANNEL_SET_CONFIG, (event, ask: boolean, config: LLOBConfig) => { ipcMain.handle(CHANNEL_SET_CONFIG, (_event, ask: boolean, config: LLOBConfig) => {
return new Promise<boolean>(resolve => { return new Promise<boolean>(resolve => {
if (!ask) { if (!ask) {
getConfigUtil().setConfig(config) getConfigUtil().setConfig(config)
@@ -139,7 +139,7 @@ function onLoad() {
}) })
}) })
ipcMain.on(CHANNEL_LOG, (event, arg) => { ipcMain.on(CHANNEL_LOG, (_event, arg) => {
log(arg) log(arg)
}) })
@@ -175,6 +175,9 @@ function onLoad() {
debug: config.debug!, debug: config.debug!,
reportSelfMessage: config.reportSelfMessage!, reportSelfMessage: config.reportSelfMessage!,
msgCacheExpire: config.msgCacheExpire!, msgCacheExpire: config.msgCacheExpire!,
musicSignUrl: config.musicSignUrl,
enableLocalFile2Url: config.enableLocalFile2Url!,
ffmpeg: config.ffmpeg,
}) })
ctx.start() ctx.start()
ipcMain.on(CHANNEL_SET_CONFIG_CONFIRMED, (event, config: LLOBConfig) => { ipcMain.on(CHANNEL_SET_CONFIG_CONFIRMED, (event, config: LLOBConfig) => {
@@ -199,23 +202,25 @@ function onLoad() {
// 创建窗口时触发 // 创建窗口时触发
function onBrowserWindowCreated(window: BrowserWindow) { function onBrowserWindowCreated(window: BrowserWindow) {
if (window.id !== 2) { if (![2, 4, 6].includes(window.id)) {
return return
} }
if (window.id === 2) {
mainWindow = window mainWindow = window
log('window create', window.webContents.getURL().toString()) }
//log('window create', window.webContents.getURL().toString())
try { try {
hookNTQQApiCall(window) hookNTQQApiCall(window, window.id !== 2)
hookNTQQApiReceive(window) hookNTQQApiReceive(window, window.id !== 2)
} catch (e: any) { } catch (e) {
log('LLOneBot hook error: ', e.toString()) log('LLOneBot hook error: ', String(e))
} }
} }
try { try {
onLoad() onLoad()
} catch (e: any) { } catch (e) {
console.log(e.toString()) console.log(e)
} }
// 这两个函数都是可选的 // 这两个函数都是可选的

View File

@@ -5,7 +5,6 @@ import {
CacheFileListItem, CacheFileListItem,
CacheFileType, CacheFileType,
CacheScanResult, CacheScanResult,
ChatCacheList,
ChatCacheListItemBasic, ChatCacheListItemBasic,
ChatType, ChatType,
ElementType, ElementType,
@@ -14,16 +13,14 @@ import {
PicElement, PicElement,
} from '../types' } from '../types'
import path from 'node:path' import path from 'node:path'
import fs from 'node:fs' import { existsSync } from 'node:fs'
import { ReceiveCmdS } from '../hook' import { ReceiveCmdS } from '../hook'
import { RkeyManager } from '@/ntqqapi/helper/rkey' import { RkeyManager } from '@/ntqqapi/helper/rkey'
import { getSession } from '@/ntqqapi/wrapper' import { getSession } from '@/ntqqapi/wrapper'
import { Peer } from '@/ntqqapi/types/msg' import { OnRichMediaDownloadCompleteParams, Peer } from '@/ntqqapi/types/msg'
import { calculateFileMD5 } from '@/common/utils/file' import { calculateFileMD5 } from '@/common/utils/file'
import { fileTypeFromFile } from 'file-type' import { fileTypeFromFile } from 'file-type'
import fsPromise from 'node:fs/promises' import { copyFile, stat, unlink } from 'node:fs/promises'
import { NTEventDispatch } from '@/common/utils/eventTask'
import { OnRichMediaDownloadCompleteParams } from '@/ntqqapi/listeners'
import { Time } from 'cosmokit' import { Time } from 'cosmokit'
import { Service, Context } from 'cordis' import { Service, Context } from 'cordis'
import { TEMP_DIR } from '@/common/globalVars' import { TEMP_DIR } from '@/common/globalVars'
@@ -40,17 +37,35 @@ export class NTQQFileApi extends Service {
constructor(protected ctx: Context) { constructor(protected ctx: Context) {
super(ctx, 'ntFileApi', true) super(ctx, 'ntFileApi', true)
this.rkeyManager = new RkeyManager(ctx, 'http://napcat-sign.wumiao.wang:2082/rkey') this.rkeyManager = new RkeyManager(ctx, 'https://llob.linyuchen.net/rkey')
} }
/** 27187 TODO */
async getVideoUrl(peer: Peer, msgId: string, elementId: string) { async getVideoUrl(peer: Peer, msgId: string, elementId: string) {
const session = getSession() const session = getSession()
return (await session?.getRichMediaService().getVideoPlayUrlV2(peer, if (session) {
return (await session.getRichMediaService().getVideoPlayUrlV2(
peer,
msgId, msgId,
elementId, elementId,
0, 0,
{ downSourceType: 1, triggerType: 1 }))?.urlResult.domainUrl[0].url { downSourceType: 1, triggerType: 1 }
)).urlResult.domainUrl[0]?.url
} else {
const data = await invoke('nodeIKernelRichMediaService/getVideoPlayUrlV2', [{
peer,
msgId,
elemId: elementId,
videoCodecFormat: 0,
exParams: {
downSourceType: 1,
triggerType: 1
},
}, null])
if (data.result !== 0) {
this.ctx.logger.warn('getVideoUrl', data)
}
return data.urlResult.domainUrl[0]?.url
}
} }
async getFileType(filePath: string) { async getFileType(filePath: string) {
@@ -82,10 +97,7 @@ export class NTQQFileApi extends Service {
file_uuid: '' file_uuid: ''
}) })
} else { } else {
mediaPath = await invoke<string>({ mediaPath = await invoke(NTMethod.MEDIA_FILE_PATH, [{
methodName: NTMethod.MEDIA_FILE_PATH,
args: [
{
path_info: { path_info: {
md5HexStr: fileMd5, md5HexStr: fileMd5,
fileName: fileName, fileName: fileName,
@@ -96,12 +108,10 @@ export class NTQQFileApi extends Service {
downloadType: 1, downloadType: 1,
file_uuid: '', file_uuid: '',
}, },
}, }])
],
})
} }
await fsPromise.copyFile(filePath, mediaPath) await copyFile(filePath, mediaPath)
const fileSize = (await fsPromise.stat(filePath)).size const fileSize = (await stat(filePath)).size
return { return {
md5: fileMd5, md5: fileMd5,
fileName, fileName,
@@ -122,61 +132,18 @@ export class NTQQFileApi extends Service {
force = false force = false
) { ) {
// 用于下载收到的消息中的图片等 // 用于下载收到的消息中的图片等
if (sourcePath && fs.existsSync(sourcePath)) { if (sourcePath && existsSync(sourcePath)) {
if (force) { if (force) {
try { try {
await fsPromise.unlink(sourcePath) await unlink(sourcePath)
} catch { } } catch { }
} else { } else {
return sourcePath return sourcePath
} }
} }
let filePath: string const data = await invoke<{ notifyInfo: OnRichMediaDownloadCompleteParams }>(
if (NTEventDispatch.initialised) { 'nodeIKernelMsgService/downloadRichMedia',
const data = await NTEventDispatch.CallNormalEvent< [
(
params: {
fileModelId: string,
downloadSourceType: number,
triggerType: number,
msgId: string,
chatType: ChatType,
peerUid: string,
elementId: string,
thumbSize: number,
downloadType: number,
filePath: string
}) => Promise<unknown>,
(fileTransNotifyInfo: OnRichMediaDownloadCompleteParams) => void
>(
'NodeIKernelMsgService/downloadRichMedia',
'NodeIKernelMsgListener/onRichMediaDownloadComplete',
1,
timeout,
(arg: OnRichMediaDownloadCompleteParams) => {
if (arg.msgId === msgId) {
return true
}
return false
},
{
fileModelId: '0',
downloadSourceType: 0,
triggerType: 1,
msgId: msgId,
chatType: chatType,
peerUid: peerUid,
elementId: elementId,
thumbSize: 0,
downloadType: 1,
filePath: thumbPath
}
)
filePath = data[1].filePath
} else {
const data = await invoke<{ notifyInfo: OnRichMediaDownloadCompleteParams }>({
methodName: NTMethod.DOWNLOAD_MEDIA,
args: [
{ {
getReq: { getReq: {
fileModelId: '0', fileModelId: '0',
@@ -193,12 +160,13 @@ export class NTQQFileApi extends Service {
}, },
null, null,
], ],
{
cbCmd: ReceiveCmdS.MEDIA_DOWNLOAD_COMPLETE, cbCmd: ReceiveCmdS.MEDIA_DOWNLOAD_COMPLETE,
cmdCB: payload => payload.notifyInfo.msgId === msgId, cmdCB: payload => payload.notifyInfo.msgId === msgId,
timeout timeout
})
filePath = data.notifyInfo.filePath
} }
)
let filePath = data.notifyInfo.filePath
if (filePath.startsWith('\\')) { if (filePath.startsWith('\\')) {
const downloadPath = TEMP_DIR const downloadPath = TEMP_DIR
filePath = path.join(downloadPath, filePath) filePath = path.join(downloadPath, filePath)
@@ -208,11 +176,13 @@ export class NTQQFileApi extends Service {
} }
async getImageSize(filePath: string) { async getImageSize(filePath: string) {
return await invoke<{ width: number; height: number }>({ return await invoke<{ width: number; height: number }>(
NTMethod.IMAGE_SIZE,
[filePath],
{
className: NTClass.FS_API, className: NTClass.FS_API,
methodName: NTMethod.IMAGE_SIZE, }
args: [filePath], )
})
} }
async getImageUrl(element: PicElement) { async getImageUrl(element: PicElement) {
@@ -224,17 +194,17 @@ export class NTQQFileApi extends Service {
const fileMd5 = element.md5HexStr const fileMd5 = element.md5HexStr
if (url) { if (url) {
const UrlParse = new URL(IMAGE_HTTP_HOST + url) //临时解析拼接 const parsedUrl = new URL(IMAGE_HTTP_HOST + url) //临时解析拼接
const imageAppid = UrlParse.searchParams.get('appid') const imageAppid = parsedUrl.searchParams.get('appid')
const isNewPic = imageAppid && ['1406', '1407'].includes(imageAppid) const isNewPic = imageAppid && ['1406', '1407'].includes(imageAppid)
if (isNewPic) { if (isNewPic) {
let UrlRkey = UrlParse.searchParams.get('rkey') let rkey = parsedUrl.searchParams.get('rkey')
if (UrlRkey) { if (rkey) {
return IMAGE_HTTP_HOST_NT + url return IMAGE_HTTP_HOST_NT + url
} }
const rkeyData = await this.rkeyManager.getRkey() const rkeyData = await this.rkeyManager.getRkey()
UrlRkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey rkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey
return IMAGE_HTTP_HOST_NT + url + `${UrlRkey}` return IMAGE_HTTP_HOST_NT + url + rkey
} else { } else {
// 老的图片url不需要rkey // 老的图片url不需要rkey
return IMAGE_HTTP_HOST + url return IMAGE_HTTP_HOST + url
@@ -254,15 +224,7 @@ export class NTQQFileCacheApi extends Service {
} }
async setCacheSilentScan(isSilent: boolean = true) { async setCacheSilentScan(isSilent: boolean = true) {
return await invoke<GeneralCallResult>({ return await invoke<GeneralCallResult>(NTMethod.CACHE_SET_SILENCE, [{ isSilent }, null])
methodName: NTMethod.CACHE_SET_SILENCE,
args: [
{
isSilent,
},
null,
],
})
} }
getCacheSessionPathList() { getCacheSessionPathList() {
@@ -271,110 +233,38 @@ export class NTQQFileCacheApi extends Service {
key: string key: string
value: string value: string
}[] }[]
>({ >(NTMethod.CACHE_PATH_SESSION, [], { className: NTClass.OS_API })
className: NTClass.OS_API,
methodName: NTMethod.CACHE_PATH_SESSION,
})
}
clearCache(cacheKeys: Array<string> = ['tmp', 'hotUpdate']) {
return invoke<any>({
// TODO: 目前还不知道真正的返回值是什么
methodName: NTMethod.CACHE_CLEAR,
args: [
{
keys: cacheKeys,
},
null,
],
})
}
addCacheScannedPaths(pathMap: object = {}) {
return invoke<GeneralCallResult>({
methodName: NTMethod.CACHE_ADD_SCANNED_PATH,
args: [
{
pathMap: { ...pathMap },
},
null,
],
})
} }
scanCache() { scanCache() {
invoke<GeneralCallResult>({ invoke<GeneralCallResult>(ReceiveCmdS.CACHE_SCAN_FINISH, [], { classNameIsRegister: true })
methodName: ReceiveCmdS.CACHE_SCAN_FINISH, return invoke<CacheScanResult>(NTMethod.CACHE_SCAN, [null, null], { timeout: 300 * Time.second })
classNameIsRegister: true,
}).then()
return invoke<CacheScanResult>({
methodName: NTMethod.CACHE_SCAN,
args: [null, null],
timeout: 300 * Time.second,
})
} }
getHotUpdateCachePath() { getHotUpdateCachePath() {
return invoke<string>({ return invoke<string>(NTMethod.CACHE_PATH_HOT_UPDATE, [], { className: NTClass.HOTUPDATE_API })
className: NTClass.HOTUPDATE_API,
methodName: NTMethod.CACHE_PATH_HOT_UPDATE,
})
} }
getDesktopTmpPath() { getDesktopTmpPath() {
return invoke<string>({ return invoke<string>(NTMethod.CACHE_PATH_DESKTOP_TEMP, [], { className: NTClass.BUSINESS_API })
className: NTClass.BUSINESS_API,
methodName: NTMethod.CACHE_PATH_DESKTOP_TEMP,
})
}
getChatCacheList(type: ChatType, pageSize: number = 1000, pageIndex: number = 0) {
return new Promise<ChatCacheList>((res, rej) => {
invoke<ChatCacheList>({
methodName: NTMethod.CACHE_CHAT_GET,
args: [
{
chatType: type,
pageSize,
order: 1,
pageIndex,
},
null,
],
})
.then((list) => res(list))
.catch((e) => rej(e))
})
} }
getFileCacheInfo(fileType: CacheFileType, pageSize: number = 1000, lastRecord?: CacheFileListItem) { getFileCacheInfo(fileType: CacheFileType, pageSize: number = 1000, lastRecord?: CacheFileListItem) {
const _lastRecord = lastRecord ? lastRecord : { fileType: fileType } const _lastRecord = lastRecord ? lastRecord : { fileType: fileType }
return invoke<CacheFileList>({ return invoke<CacheFileList>(NTMethod.CACHE_FILE_GET, [{
methodName: NTMethod.CACHE_FILE_GET,
args: [
{
fileType: fileType, fileType: fileType,
restart: true, restart: true,
pageSize: pageSize, pageSize: pageSize,
order: 1, order: 1,
lastRecord: _lastRecord, lastRecord: _lastRecord,
}, }, null])
null,
],
})
} }
async clearChatCache(chats: ChatCacheListItemBasic[] = [], fileKeys: string[] = []) { async clearChatCache(chats: ChatCacheListItemBasic[] = [], fileKeys: string[] = []) {
return await invoke<GeneralCallResult>({ return await invoke<GeneralCallResult>(NTMethod.CACHE_CHAT_CLEAR, [{
methodName: NTMethod.CACHE_CHAT_CLEAR,
args: [
{
chats, chats,
fileKeys, fileKeys,
}, }, null])
null,
],
})
} }
} }

View File

@@ -2,10 +2,8 @@ import { Friend, FriendV2, SimpleInfo, CategoryFriend } from '../types'
import { ReceiveCmdS } from '../hook' import { ReceiveCmdS } from '../hook'
import { invoke, NTMethod, NTClass } from '../ntcall' import { invoke, NTMethod, NTClass } from '../ntcall'
import { getSession } from '@/ntqqapi/wrapper' import { getSession } from '@/ntqqapi/wrapper'
import { BuddyListReqType, NodeIKernelProfileService } from '../services' import { BuddyListReqType } from '../services'
import { NTEventDispatch } from '@/common/utils/eventTask' import { Dict, pick } from 'cosmokit'
import { LimitedHashTable } from '@/common/utils/table'
import { pick } from 'cosmokit'
import { Service, Context } from 'cordis' import { Service, Context } from 'cordis'
declare module 'cordis' { declare module 'cordis' {
@@ -20,7 +18,7 @@ export class NTQQFriendApi extends Service {
} }
/** 大于或等于 26702 应使用 getBuddyV2 */ /** 大于或等于 26702 应使用 getBuddyV2 */
async getFriends(forced = false) { async getFriends() {
const data = await invoke<{ const data = await invoke<{
data: { data: {
categoryId: number categoryId: number
@@ -28,12 +26,15 @@ export class NTQQFriendApi extends Service {
categroyMbCount: number categroyMbCount: number
buddyList: Friend[] buddyList: Friend[]
}[] }[]
}>({ }>(
'getBuddyList',
[],
{
className: NTClass.NODE_STORE_API, className: NTClass.NODE_STORE_API,
methodName: 'getBuddyList',
cbCmd: ReceiveCmdS.FRIENDS, cbCmd: ReceiveCmdS.FRIENDS,
afterFirstCmd: false, afterFirstCmd: false,
}) }
)
const _friends: Friend[] = [] const _friends: Friend[] = []
for (const item of data.data) { for (const item of data.data) {
_friends.push(...item.buddyList) _friends.push(...item.buddyList)
@@ -41,13 +42,7 @@ export class NTQQFriendApi extends Service {
return _friends return _friends
} }
async handleFriendRequest(flag: string, accept: boolean) { async handleFriendRequest(friendUid: string, reqTime: string, accept: boolean) {
const data = flag.split('|')
if (data.length < 2) {
return
}
const friendUid = data[0]
const reqTime = data[1]
const session = getSession() const session = getSession()
if (session) { if (session) {
return session.getBuddyService().approvalFriendRequest({ return session.getBuddyService().approvalFriendRequest({
@@ -56,18 +51,13 @@ export class NTQQFriendApi extends Service {
accept accept
}) })
} else { } else {
return await invoke({ return await invoke(NTMethod.HANDLE_FRIEND_REQUEST, [{
methodName: NTMethod.HANDLE_FRIEND_REQUEST,
args: [
{
approvalInfo: { approvalInfo: {
friendUid, friendUid,
reqTime, reqTime,
accept, accept,
}, },
}, }])
],
})
} }
} }
@@ -78,56 +68,60 @@ export class NTQQFriendApi extends Service {
const buddyService = session.getBuddyService() const buddyService = session.getBuddyService()
const buddyListV2 = await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) const buddyListV2 = await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL)
uids.push(...buddyListV2.data.flatMap(item => item.buddyUids)) uids.push(...buddyListV2.data.flatMap(item => item.buddyUids))
const data = await NTEventDispatch.CallNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>( const data = await session.getProfileService().getCoreAndBaseInfo('nodeStore', uids)
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids
)
return Array.from(data.values()) return Array.from(data.values())
} else { } else {
const data = await invoke<{ const data = await invoke<{
buddyCategory: CategoryFriend[] buddyCategory: CategoryFriend[]
userSimpleInfos: Record<string, SimpleInfo> userSimpleInfos: Record<string, SimpleInfo>
}>({ }>(
'getBuddyList',
[refresh],
{
className: NTClass.NODE_STORE_API, className: NTClass.NODE_STORE_API,
methodName: 'getBuddyList',
args: [refresh],
cbCmd: ReceiveCmdS.FRIENDS, cbCmd: ReceiveCmdS.FRIENDS,
afterFirstCmd: false, afterFirstCmd: false,
})
const categoryUids: Map<number, string[]> = new Map()
for (const item of data.buddyCategory) {
categoryUids.set(item.categoryId, item.buddyUids)
} }
return Object.values(data.userSimpleInfos).filter(v => v.baseInfo && categoryUids.get(v.baseInfo.categoryId)?.includes(v.uid!)) )
const uids = data.buddyCategory.flatMap(item => item.buddyUids)
return Object.values(data.userSimpleInfos).filter(v => uids.includes(v.uid!))
} }
} }
async getBuddyIdMap(refresh = false): Promise<LimitedHashTable<string, string>> { /** uid => uin */
const retMap: LimitedHashTable<string, string> = new LimitedHashTable<string, string>(5000) async getBuddyIdMap(refresh = false): Promise<Map<string, string>> {
const retMap: Map<string, string> = new Map()
const session = getSession() const session = getSession()
if (session) { if (session) {
const uids: string[] = [] const uids: string[] = []
const buddyService = session?.getBuddyService() const buddyService = session.getBuddyService()
const buddyListV2 = await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) const buddyListV2 = await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL)
uids.push(...buddyListV2.data.flatMap(item => item.buddyUids)) uids.push(...buddyListV2.data.flatMap(item => item.buddyUids))
const data = await NTEventDispatch.CallNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>( const data = await session.getProfileService().getCoreAndBaseInfo('nodeStore', uids)
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids for (const [, item] of data) {
) if (retMap.size > 5000) {
data.forEach((value, key) => { break
retMap.set(value.uin!, value.uid!) }
}) retMap.set(item.uid!, item.uin!)
}
} else { } else {
const data = await invoke<{ const data = await invoke<{
buddyCategory: CategoryFriend[] buddyCategory: CategoryFriend[]
userSimpleInfos: Record<string, SimpleInfo> userSimpleInfos: Record<string, SimpleInfo>
}>({ }>(
'getBuddyList',
[refresh],
{
className: NTClass.NODE_STORE_API, className: NTClass.NODE_STORE_API,
methodName: 'getBuddyList',
args: [refresh],
cbCmd: ReceiveCmdS.FRIENDS, cbCmd: ReceiveCmdS.FRIENDS,
afterFirstCmd: false, afterFirstCmd: false,
}) }
)
for (const item of Object.values(data.userSimpleInfos)) { for (const item of Object.values(data.userSimpleInfos)) {
retMap.set(item.uin!, item.uid!) if (retMap.size > 5000) {
break
}
retMap.set(item.uid!, item.uin!)
} }
} }
return retMap return retMap
@@ -137,19 +131,17 @@ export class NTQQFriendApi extends Service {
const session = getSession() const session = getSession()
if (session) { if (session) {
const uids: string[] = [] const uids: string[] = []
const categoryMap: Map<string, any> = new Map() const categoryMap: Map<string, Dict> = new Map()
const buddyService = session.getBuddyService() const buddyService = session.getBuddyService()
const buddyListV2 = (await buddyService?.getBuddyListV2('0', BuddyListReqType.KNOMAL))?.data const buddyListV2 = (await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL)).data
uids.push( uids.push(
...buddyListV2?.flatMap(item => { ...buddyListV2.flatMap(item => {
item.buddyUids.forEach(uid => { item.buddyUids.forEach(uid => {
categoryMap.set(uid, { categoryId: item.categoryId, categroyName: item.categroyName }) categoryMap.set(uid, { categoryId: item.categoryId, categroyName: item.categroyName })
}) })
return item.buddyUids return item.buddyUids
})!) }))
const data = await NTEventDispatch.CallNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>( const data = await session.getProfileService().getCoreAndBaseInfo('nodeStore', uids)
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids
)
return Array.from(data).map(([key, value]) => { return Array.from(data).map(([key, value]) => {
const category = categoryMap.get(key) const category = categoryMap.get(key)
return category ? { ...value, categoryId: category.categoryId, categroyName: category.categroyName } : value return category ? { ...value, categoryId: category.categoryId, categroyName: category.categroyName } : value
@@ -158,13 +150,15 @@ export class NTQQFriendApi extends Service {
const data = await invoke<{ const data = await invoke<{
buddyCategory: CategoryFriend[] buddyCategory: CategoryFriend[]
userSimpleInfos: Record<string, SimpleInfo> userSimpleInfos: Record<string, SimpleInfo>
}>({ }>(
'getBuddyList',
[refresh],
{
className: NTClass.NODE_STORE_API, className: NTClass.NODE_STORE_API,
methodName: 'getBuddyList',
args: [refresh],
cbCmd: ReceiveCmdS.FRIENDS, cbCmd: ReceiveCmdS.FRIENDS,
afterFirstCmd: false, afterFirstCmd: false,
}) }
)
const category: Map<number, Pick<CategoryFriend, 'buddyUids' | 'categroyName'>> = new Map() const category: Map<number, Pick<CategoryFriend, 'buddyUids' | 'categroyName'>> = new Map()
for (const item of data.buddyCategory) { for (const item of data.buddyCategory) {
category.set(item.categoryId, pick(item, ['buddyUids', 'categroyName'])) category.set(item.categoryId, pick(item, ['buddyUids', 'categroyName']))
@@ -186,13 +180,18 @@ export class NTQQFriendApi extends Service {
if (session) { if (session) {
return session.getBuddyService().isBuddy(uid) return session.getBuddyService().isBuddy(uid)
} else { } else {
return await invoke<boolean>({ return await invoke('nodeIKernelBuddyService/isBuddy', [{ uid }, null])
methodName: 'nodeIKernelBuddyService/isBuddy',
args: [
{ uid },
null,
],
})
} }
} }
async getBuddyRecommendContact(uin: string) {
const ret = await invoke('nodeIKernelBuddyService/getBuddyRecommendContactArkJson', [{ uin }, null])
return ret.arkMsg
}
async setBuddyRemark(uid: string, remark: string) {
return await invoke('nodeIKernelBuddyService/setBuddyRemark', [{
remarkParams: { uid, remark }
}, null])
}
} }

View File

@@ -1,11 +1,18 @@
import { ReceiveCmdS } from '../hook' import { ReceiveCmdS } from '../hook'
import { Group, GroupMember, GroupMemberRole, GroupNotifies, GroupRequestOperateTypes, GroupNotify } from '../types' import {
Group,
GroupMember,
GroupMemberRole,
GroupNotifies,
GroupRequestOperateTypes,
GetFileListParam,
OnGroupFileInfoUpdateParams,
PublishGroupBulletinReq
} from '../types'
import { invoke, NTClass, NTMethod } from '../ntcall' import { invoke, NTClass, NTMethod } from '../ntcall'
import { GeneralCallResult } from '../services' import { GeneralCallResult } from '../services'
import { NTQQWindows } from './window' import { NTQQWindows } from './window'
import { getSession } from '../wrapper' import { getSession } from '../wrapper'
import { NTEventDispatch } from '@/common/utils/eventTask'
import { NodeIKernelGroupListener } from '../listeners'
import { NodeIKernelGroupService } from '../services' import { NodeIKernelGroupService } from '../services'
import { Service, Context } from 'cordis' import { Service, Context } from 'cordis'
import { isNumeric } from '@/common/utils/misc' import { isNumeric } from '@/common/utils/misc'
@@ -17,69 +24,40 @@ declare module 'cordis' {
} }
export class NTQQGroupApi extends Service { export class NTQQGroupApi extends Service {
private groupMembers: Map<string, Map<string, GroupMember>> = new Map<string, Map<string, GroupMember>>() static inject = ['ntWindowApi']
public groupMembers: Map<string, Map<string, GroupMember>> = new Map<string, Map<string, GroupMember>>()
constructor(protected ctx: Context) { constructor(protected ctx: Context) {
super(ctx, 'ntGroupApi', true) super(ctx, 'ntGroupApi', true)
} }
async getGroups(forced = false): Promise<Group[]> { async getGroups(): Promise<Group[]> {
if (NTEventDispatch.initialised) {
type ListenerType = NodeIKernelGroupListener['onGroupListUpdate']
const [, , groupList] = await NTEventDispatch.CallNormalEvent
<(force: boolean) => Promise<any>, ListenerType>
(
'NodeIKernelGroupService/getGroupList',
'NodeIKernelGroupListener/onGroupListUpdate',
1,
5000,
() => true,
forced
)
return groupList
} else {
const result = await invoke<{ const result = await invoke<{
updateType: number updateType: number
groupList: Group[] groupList: Group[]
}>({ }>(
'getGroupList',
[],
{
className: NTClass.NODE_STORE_API, className: NTClass.NODE_STORE_API,
methodName: 'getGroupList',
cbCmd: ReceiveCmdS.GROUPS_STORE, cbCmd: ReceiveCmdS.GROUPS_STORE,
afterFirstCmd: false, afterFirstCmd: false,
}) }
)
return result.groupList return result.groupList
} }
}
async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> { async getGroupMembers(groupCode: string, num = 3000): Promise<Map<string, GroupMember>> {
const session = getSession() const session = getSession()
let result: Awaited<ReturnType<NodeIKernelGroupService['getNextMemberList']>> let result: Awaited<ReturnType<NodeIKernelGroupService['getNextMemberList']>>
if (session) { if (session) {
const groupService = session.getGroupService() const groupService = session.getGroupService()
const sceneId = groupService.createMemberListScene(groupQQ, 'groupMemberList_MainWindow') const sceneId = groupService.createMemberListScene(groupCode, 'groupMemberList_MainWindow')
result = await groupService.getNextMemberList(sceneId, undefined, num) result = await groupService.getNextMemberList(sceneId, undefined, num)
} else { } else {
const sceneId = await invoke<string>({ const sceneId = await invoke(NTMethod.GROUP_MEMBER_SCENE, [{ groupCode, scene: 'groupMemberList_MainWindow' }])
methodName: NTMethod.GROUP_MEMBER_SCENE, result = await invoke(NTMethod.GROUP_MEMBERS, [{ sceneId, num }, null])
args: [
{
groupCode: groupQQ,
scene: 'groupMemberList_MainWindow',
},
],
})
result = await invoke<
ReturnType<NodeIKernelGroupService['getNextMemberList']>
>({
methodName: NTMethod.GROUP_MEMBERS,
args: [
{
sceneId,
num,
},
null,
],
})
} }
if (result.errCode !== 0) { if (result.errCode !== 0) {
throw ('获取群成员列表出错,' + result.errMsg) throw ('获取群成员列表出错,' + result.errMsg)
@@ -90,30 +68,29 @@ export class NTQQGroupApi extends Service {
async getGroupMember(groupCode: string | number, memberUinOrUid: string | number) { async getGroupMember(groupCode: string | number, memberUinOrUid: string | number) {
const groupCodeStr = groupCode.toString() const groupCodeStr = groupCode.toString()
const memberUinOrUidStr = memberUinOrUid.toString() const memberUinOrUidStr = memberUinOrUid.toString()
let members = this.groupMembers.get(groupCodeStr) if (!this.groupMembers.has(groupCodeStr)) {
if (!members) {
try { try {
members = await this.getGroupMembers(groupCodeStr)
// 更新群成员列表 // 更新群成员列表
this.groupMembers.set(groupCodeStr, members) this.groupMembers.set(groupCodeStr, await this.getGroupMembers(groupCodeStr))
} }
catch (e) { catch (e) {
return null return null
} }
} }
let members = this.groupMembers.get(groupCodeStr)!
const getMember = () => { const getMember = () => {
let member: GroupMember | undefined = undefined let member: GroupMember | undefined = undefined
if (isNumeric(memberUinOrUidStr)) { if (isNumeric(memberUinOrUidStr)) {
member = Array.from(members!.values()).find(member => member.uin === memberUinOrUidStr) member = Array.from(members.values()).find(member => member.uin === memberUinOrUidStr)
} else { } else {
member = members!.get(memberUinOrUidStr) member = members.get(memberUinOrUidStr)
} }
return member return member
} }
let member = getMember() let member = getMember()
if (!member) { if (!member) {
members = await this.getGroupMembers(groupCodeStr) this.groupMembers.set(groupCodeStr, await this.getGroupMembers(groupCodeStr))
this.groupMembers.set(groupCodeStr, members) members = this.groupMembers.get(groupCodeStr)!
member = getMember() member = getMember()
} }
return member return member
@@ -129,38 +106,16 @@ export class NTQQGroupApi extends Service {
} }
async getSingleScreenNotifies(num: number) { async getSingleScreenNotifies(num: number) {
if (NTEventDispatch.initialised) { invoke(ReceiveCmdS.GROUP_NOTIFY, [], { classNameIsRegister: true })
const [_retData, _doubt, _seq, notifies] = await NTEventDispatch.CallNormalEvent return (await invoke<GroupNotifies>(
<(arg1: boolean, arg2: string, arg3: number) => Promise<any>, (doubt: boolean, seq: string, notifies: GroupNotify[]) => void> 'nodeIKernelGroupService/getSingleScreenNotifies',
( [{ doubt: false, startSeq: '', number: num }, null],
'NodeIKernelGroupService/getSingleScreenNotifies', {
'NodeIKernelGroupListener/onGroupSingleScreenNotifies',
1,
5000,
() => true,
false,
'',
num,
)
return notifies
} else {
invoke({
methodName: ReceiveCmdS.GROUP_NOTIFY,
classNameIsRegister: true,
})
return (await invoke<GroupNotifies>({
methodName: NTMethod.GET_GROUP_NOTICE,
cbCmd: ReceiveCmdS.GROUP_NOTIFY, cbCmd: ReceiveCmdS.GROUP_NOTIFY,
afterFirstCmd: false, afterFirstCmd: false,
args: [{ doubt: false, startSeq: '', number: num }, null],
})).notifies
} }
} )).notifies
/** 27187 TODO */
async delGroupFile(groupCode: string, files: string[]) {
const session = getSession()
return session?.getRichMediaService().deleteGroupFile(groupCode, [102], files)
} }
async handleGroupRequest(flag: string, operateType: GroupRequestOperateTypes, reason?: string) { async handleGroupRequest(flag: string, operateType: GroupRequestOperateTypes, reason?: string) {
@@ -170,22 +125,17 @@ export class NTQQGroupApi extends Service {
const type = parseInt(flagitem[2]) const type = parseInt(flagitem[2])
const session = getSession() const session = getSession()
if (session) { if (session) {
return session.getGroupService().operateSysNotify( return session.getGroupService().operateSysNotify(false, {
false, operateType, // 2 拒绝
{ targetMsg: {
'operateType': operateType, // 2 拒绝 seq, // 通知序列号
'targetMsg': { type,
'seq': seq, // 通知序列号 groupCode,
'type': type, postscript: reason || ' ' // 仅传空值可能导致处理失败,故默认给个空格
'groupCode': groupCode,
'postscript': reason || ' ' // 仅传空值可能导致处理失败,故默认给个空格
} }
}) })
} else { } else {
return await invoke({ return await invoke(NTMethod.HANDLE_GROUP_REQUEST, [{
methodName: NTMethod.HANDLE_GROUP_REQUEST,
args: [
{
doubt: false, doubt: false,
operateMsg: { operateMsg: {
operateType, operateType,
@@ -196,142 +146,80 @@ export class NTQQGroupApi extends Service {
postscript: reason || ' ' // 仅传空值可能导致处理失败,故默认给个空格 postscript: reason || ' ' // 仅传空值可能导致处理失败,故默认给个空格
}, },
}, },
}, }, null])
null,
],
})
} }
} }
async quitGroup(groupQQ: string) { async quitGroup(groupCode: string) {
const session = getSession() const session = getSession()
if (session) { if (session) {
return session.getGroupService().quitGroup(groupQQ) return session.getGroupService().quitGroup(groupCode)
} else { } else {
return await invoke({ return await invoke(NTMethod.QUIT_GROUP, [{ groupCode }, null])
methodName: NTMethod.QUIT_GROUP,
args: [{ groupCode: groupQQ }, null],
})
} }
} }
async kickMember( async kickMember(
groupQQ: string, groupCode: string,
kickUids: string[], kickUids: string[],
refuseForever = false, refuseForever = false,
kickReason = '', kickReason = '',
) { ) {
const session = getSession() const session = getSession()
if (session) { if (session) {
return session.getGroupService().kickMember(groupQQ, kickUids, refuseForever, kickReason) return session.getGroupService().kickMember(groupCode, kickUids, refuseForever, kickReason)
} else { } else {
return await invoke({ return await invoke(NTMethod.KICK_MEMBER, [{ groupCode, kickUids, refuseForever, kickReason }])
methodName: NTMethod.KICK_MEMBER,
args: [
{
groupCode: groupQQ,
kickUids,
refuseForever,
kickReason,
},
],
})
} }
} }
async banMember(groupQQ: string, memList: Array<{ uid: string, timeStamp: number }>) { async banMember(groupCode: string, memList: Array<{ uid: string, timeStamp: number }>) {
// timeStamp为秒数, 0为解除禁言 // timeStamp为秒数, 0为解除禁言
const session = getSession() const session = getSession()
if (session) { if (session) {
return session.getGroupService().setMemberShutUp(groupQQ, memList) return session.getGroupService().setMemberShutUp(groupCode, memList)
} else { } else {
return await invoke({ return await invoke(NTMethod.MUTE_MEMBER, [{ groupCode, memList }])
methodName: NTMethod.MUTE_MEMBER,
args: [
{
groupCode: groupQQ,
memList,
},
],
})
} }
} }
async banGroup(groupQQ: string, shutUp: boolean) { async banGroup(groupCode: string, shutUp: boolean) {
const session = getSession() const session = getSession()
if (session) { if (session) {
return session.getGroupService().setGroupShutUp(groupQQ, shutUp) return session.getGroupService().setGroupShutUp(groupCode, shutUp)
} else { } else {
return await invoke({ return await invoke(NTMethod.MUTE_GROUP, [{ groupCode, shutUp }, null])
methodName: NTMethod.MUTE_GROUP,
args: [
{
groupCode: groupQQ,
shutUp,
},
null,
],
})
} }
} }
async setMemberCard(groupQQ: string, memberUid: string, cardName: string) { async setMemberCard(groupCode: string, memberUid: string, cardName: string) {
const session = getSession() const session = getSession()
if (session) { if (session) {
return session.getGroupService().modifyMemberCardName(groupQQ, memberUid, cardName) return session.getGroupService().modifyMemberCardName(groupCode, memberUid, cardName)
} else { } else {
return await invoke({ return await invoke(NTMethod.SET_MEMBER_CARD, [{ groupCode, uid: memberUid, cardName }, null])
methodName: NTMethod.SET_MEMBER_CARD,
args: [
{
groupCode: groupQQ,
uid: memberUid,
cardName,
},
null,
],
})
} }
} }
async setMemberRole(groupQQ: string, memberUid: string, role: GroupMemberRole) { async setMemberRole(groupCode: string, memberUid: string, role: GroupMemberRole) {
const session = getSession() const session = getSession()
if (session) { if (session) {
return session.getGroupService().modifyMemberRole(groupQQ, memberUid, role) return session.getGroupService().modifyMemberRole(groupCode, memberUid, role)
} else { } else {
return await invoke({ return await invoke(NTMethod.SET_MEMBER_ROLE, [{ groupCode, uid: memberUid, role }, null])
methodName: NTMethod.SET_MEMBER_ROLE,
args: [
{
groupCode: groupQQ,
uid: memberUid,
role,
},
null,
],
})
} }
} }
async setGroupName(groupQQ: string, groupName: string) { async setGroupName(groupCode: string, groupName: string) {
const session = getSession() const session = getSession()
if (session) { if (session) {
return session.getGroupService().modifyGroupName(groupQQ, groupName, false) return session.getGroupService().modifyGroupName(groupCode, groupName, false)
} else { } else {
return await invoke({ return await invoke(NTMethod.SET_GROUP_NAME, [{ groupCode, groupName }, null])
methodName: NTMethod.SET_GROUP_NAME,
args: [
{
groupCode: groupQQ,
groupName,
},
null,
],
})
} }
} }
async getGroupAtAllRemainCount(groupCode: string) { async getGroupRemainAtTimes(groupCode: string) {
return await invoke< return await invoke<
GeneralCallResult & { GeneralCallResult & {
atInfo: { atInfo: {
@@ -342,44 +230,85 @@ export class NTQQGroupApi extends Service {
canNotAtAllMsg: '' canNotAtAllMsg: ''
} }
} }
>({ >(NTMethod.GROUP_AT_ALL_REMAIN_COUNT, [{ groupCode }, null])
methodName: NTMethod.GROUP_AT_ALL_REMAIN_COUNT,
args: [
{
groupCode,
},
null,
],
})
} }
/** 27187 TODO */ /** 27187 TODO */
async removeGroupEssence(GroupCode: string, msgId: string) { async removeGroupEssence(groupCode: string, msgId: string) {
const session = getSession() const session = getSession()
// 代码没测过 // 代码没测过
// 需要 ob11msgid->msgId + (peer) -> msgSeq + msgRandom // 需要 ob11msgid->msgId + (peer) -> msgSeq + msgRandom
let MsgData = await session?.getMsgService().getMsgsIncludeSelf({ chatType: 2, guildId: '', peerUid: GroupCode }, msgId, 1, false) const data = await session?.getMsgService().getMsgsIncludeSelf({ chatType: 2, guildId: '', peerUid: groupCode }, msgId, 1, false)
let param = { const param = {
groupCode: GroupCode, groupCode: groupCode,
msgRandom: parseInt(MsgData?.msgList[0].msgRandom!), msgRandom: Number(data?.msgList[0].msgRandom),
msgSeq: parseInt(MsgData?.msgList[0].msgSeq!) msgSeq: Number(data?.msgList[0].msgSeq)
} }
// GetMsgByShoretID(ShoretID) -> MsgService.getMsgs(Peer,MsgId,1,false) -> 组出参数 // GetMsgByShoretID(ShoretID) -> MsgService.getMsgs(Peer,MsgId,1,false) -> 组出参数
return session?.getGroupService().removeGroupEssence(param) return session?.getGroupService().removeGroupEssence(param)
} }
/** 27187 TODO */ /** 27187 TODO */
async addGroupEssence(GroupCode: string, msgId: string) { async addGroupEssence(groupCode: string, msgId: string) {
const session = getSession() const session = getSession()
// 代码没测过 // 代码没测过
// 需要 ob11msgid->msgId + (peer) -> msgSeq + msgRandom // 需要 ob11msgid->msgId + (peer) -> msgSeq + msgRandom
let MsgData = await session?.getMsgService().getMsgsIncludeSelf({ chatType: 2, guildId: '', peerUid: GroupCode }, msgId, 1, false) const data = await session?.getMsgService().getMsgsIncludeSelf({ chatType: 2, guildId: '', peerUid: groupCode }, msgId, 1, false)
let param = { const param = {
groupCode: GroupCode, groupCode: groupCode,
msgRandom: parseInt(MsgData?.msgList[0].msgRandom!), msgRandom: Number(data?.msgList[0].msgRandom),
msgSeq: parseInt(MsgData?.msgList[0].msgSeq!) msgSeq: Number(data?.msgList[0].msgSeq)
} }
// GetMsgByShoretID(ShoretID) -> MsgService.getMsgs(Peer,MsgId,1,false) -> 组出参数 // GetMsgByShoretID(ShoretID) -> MsgService.getMsgs(Peer,MsgId,1,false) -> 组出参数
return session?.getGroupService().addGroupEssence(param) return session?.getGroupService().addGroupEssence(param)
} }
async createGroupFileFolder(groupId: string, folderName: string) {
return await invoke('nodeIKernelRichMediaService/createGroupFolder', [{ groupId, folderName }, null])
}
async deleteGroupFileFolder(groupId: string, folderId: string) {
return await invoke('nodeIKernelRichMediaService/deleteGroupFolder', [{ groupId, folderId }, null])
}
async deleteGroupFile(groupId: string, fileIdList: string[], busIdList: number[]) {
return await invoke('nodeIKernelRichMediaService/deleteGroupFile', [{ groupId, busIdList, fileIdList }, null])
}
async getGroupFileList(groupId: string, fileListForm: GetFileListParam) {
invoke('nodeIKernelMsgListener/onGroupFileInfoUpdate', [], { classNameIsRegister: true })
const data = await invoke<{ fileInfo: OnGroupFileInfoUpdateParams }>(
'nodeIKernelRichMediaService/getGroupFileList',
[
{
groupId,
fileListForm
},
null,
],
{
cbCmd: 'nodeIKernelMsgListener/onGroupFileInfoUpdate',
afterFirstCmd: false,
cmdCB: (payload, result) => payload.fileInfo.reqId === result
}
)
return data.fileInfo.item
}
async publishGroupBulletin(groupCode: string, req: PublishGroupBulletinReq) {
const ntUserApi = this.ctx.get('ntUserApi')!
const psKey = (await ntUserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!
return await invoke('nodeIKernelGroupService/publishGroupBulletin', [{ groupCode, psKey, req }, null])
}
async uploadGroupBulletinPic(groupCode: string, path: string) {
const ntUserApi = this.ctx.get('ntUserApi')!
const psKey = (await ntUserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!
return await invoke('nodeIKernelGroupService/uploadGroupBulletinPic', [{ groupCode, psKey, path }, null])
}
async getGroupRecommendContact(groupCode: string) {
const ret = await invoke('nodeIKernelGroupService/getGroupRecommendContactArkJson', [{ groupCode }, null])
return ret.arkJson
}
} }

View File

@@ -1,8 +1,7 @@
import { invoke, NTMethod } from '../ntcall' import { invoke, NTMethod } from '../ntcall'
import { GeneralCallResult, TmpChatInfoApi } from '../services' import { GeneralCallResult } from '../services'
import { RawMessage, SendMessageElement, Peer, ChatType2 } from '../types' import { RawMessage, SendMessageElement, Peer, ChatType2 } from '../types'
import { getSession } from '@/ntqqapi/wrapper' import { getSession } from '@/ntqqapi/wrapper'
import { NTEventDispatch } from '@/common/utils/eventTask'
import { Service, Context } from 'cordis' import { Service, Context } from 'cordis'
import { selfInfo } from '@/common/globalVars' import { selfInfo } from '@/common/globalVars'
@@ -23,6 +22,8 @@ function generateMsgId() {
} }
export class NTQQMsgApi extends Service { export class NTQQMsgApi extends Service {
static inject = ['ntUserApi']
constructor(protected ctx: Context) { constructor(protected ctx: Context) {
super(ctx, 'ntMsgApi', true) super(ctx, 'ntMsgApi', true)
} }
@@ -32,41 +33,20 @@ export class NTQQMsgApi extends Service {
if (session) { if (session) {
return session.getMsgService().getTempChatInfo(chatType, peerUid) return session.getMsgService().getTempChatInfo(chatType, peerUid)
} else { } else {
return await invoke<TmpChatInfoApi>({ return await invoke('nodeIKernelMsgService/getTempChatInfo', [{ chatType, peerUid }, null])
methodName: 'nodeIKernelMsgService/getTempChatInfo',
args: [
{
chatType,
peerUid,
},
null,
],
})
} }
} }
async setEmojiLike(peer: Peer, msgSeq: string, emojiId: string, set: boolean = true) { async setEmojiLike(peer: Peer, msgSeq: string, emojiId: string, setEmoji: boolean = true) {
// 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
emojiId = emojiId.toString()
const session = getSession() const session = getSession()
const emojiType = emojiId.length > 3 ? '2' : '1'
if (session) { if (session) {
return session.getMsgService().setMsgEmojiLikes(peer, msgSeq, emojiId, emojiId.length > 3 ? '2' : '1', set) return session.getMsgService().setMsgEmojiLikes(peer, msgSeq, emojiId, emojiType, setEmoji)
} else { } else {
return await invoke({ return await invoke(NTMethod.EMOJI_LIKE, [{ peer, msgSeq, emojiId, emojiType, setEmoji }, null])
methodName: NTMethod.EMOJI_LIKE,
args: [
{
peer,
msgSeq,
emojiId,
emojiType: emojiId.length > 3 ? '2' : '1',
setEmoji: set,
},
null,
],
})
} }
} }
@@ -75,33 +55,20 @@ export class NTQQMsgApi extends Service {
if (session) { if (session) {
return session.getMsgService().getMultiMsg(peer, rootMsgId, parentMsgId) return session.getMsgService().getMultiMsg(peer, rootMsgId, parentMsgId)
} else { } else {
return await invoke<GeneralCallResult & { msgList: RawMessage[] }>({ return await invoke(NTMethod.GET_MULTI_MSG, [{ peer, rootMsgId, parentMsgId }, null])
methodName: NTMethod.GET_MULTI_MSG,
args: [
{
peer,
rootMsgId,
parentMsgId,
},
null,
],
})
} }
} }
async activateChat(peer: Peer) { async activateChat(peer: Peer) {
return await invoke<GeneralCallResult>({ return await invoke<GeneralCallResult>(NTMethod.ACTIVE_CHAT_PREVIEW, [{ peer, cnt: 20 }, null])
methodName: NTMethod.ACTIVE_CHAT_PREVIEW,
args: [{ peer, cnt: 20 }, null],
})
} }
async activateChatAndGetHistory(peer: Peer) { async activateChatAndGetHistory(peer: Peer) {
return await invoke<GeneralCallResult>({ return await invoke<GeneralCallResult>(NTMethod.ACTIVE_CHAT_HISTORY, [{ peer, cnt: 20 }, null])
methodName: NTMethod.ACTIVE_CHAT_HISTORY, }
// 参数似乎不是这样
args: [{ peer, cnt: 20 }, null], async getAioFirstViewLatestMsgs(peer: Peer, cnt: number) {
}) return await invoke('nodeIKernelMsgService/getAioFirstViewLatestMsgs', [{ peer, cnt }, null])
} }
async getMsgsByMsgId(peer: Peer | undefined, msgIds: string[] | undefined) { async getMsgsByMsgId(peer: Peer | undefined, msgIds: string[] | undefined) {
@@ -111,93 +78,44 @@ export class NTQQMsgApi extends Service {
if (session) { if (session) {
return session.getMsgService().getMsgsByMsgId(peer, msgIds) return session.getMsgService().getMsgsByMsgId(peer, msgIds)
} else { } else {
return await invoke<GeneralCallResult & { return await invoke('nodeIKernelMsgService/getMsgsByMsgId', [{ peer, msgIds }, null])
msgList: RawMessage[]
}>({
methodName: 'nodeIKernelMsgService/getMsgsByMsgId',
args: [
{
peer,
msgIds,
},
null,
],
})
} }
} }
async getMsgHistory(peer: Peer, msgId: string, count: number, isReverseOrder: boolean = false) { async getMsgHistory(peer: Peer, msgId: string, cnt: number, isReverseOrder: boolean = false) {
const session = getSession() const session = getSession()
// 消息时间从旧到新 // 消息时间从旧到新
if (session) { if (session) {
return session.getMsgService().getMsgsIncludeSelf(peer, msgId, count, isReverseOrder) return session.getMsgService().getMsgsIncludeSelf(peer, msgId, cnt, isReverseOrder)
} else { } else {
return await invoke<GeneralCallResult & { msgList: RawMessage[] }>({ return await invoke(NTMethod.HISTORY_MSG, [{ peer, msgId, cnt, queryOrder: isReverseOrder }, null])
methodName: NTMethod.HISTORY_MSG,
args: [
{
peer,
msgId,
cnt: count,
queryOrder: isReverseOrder,
},
null,
],
})
} }
} }
async recallMsg(peer: Peer, msgIds: string[]) { async recallMsg(peer: Peer, msgIds: string[]) {
const session = getSession() const session = getSession()
if (session) { if (session) {
return session.getMsgService().recallMsg({ return session.getMsgService().recallMsg(peer, msgIds)
chatType: peer.chatType,
peerUid: peer.peerUid
}, msgIds)
} else { } else {
return await invoke({ return await invoke(NTMethod.RECALL_MSG, [{ peer, msgIds }, null])
methodName: NTMethod.RECALL_MSG,
args: [
{
peer,
msgIds,
},
null,
],
})
} }
} }
async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) { async sendMsg(peer: Peer, msgElements: SendMessageElement[], timeout = 10000) {
const msgId = generateMsgId() const msgId = generateMsgId()
peer.guildId = msgId peer.guildId = msgId
let msgList: RawMessage[] const data = await invoke<{ msgList: RawMessage[] }>(
if (NTEventDispatch.initialised) { 'nodeIKernelMsgService/sendMsg',
const data = await NTEventDispatch.CallNormalEvent< [
(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<any, any>) => Promise<unknown>, {
(msgList: RawMessage[]) => void msgId: '0',
>(
'NodeIKernelMsgService/sendMsg',
'NodeIKernelMsgListener/onMsgInfoListUpdate',
1,
timeout,
(msgRecords: RawMessage[]) => {
for (const msgRecord of msgRecords) {
if (msgRecord.guildId === msgId && msgRecord.sendStatus === 2) {
return true
}
}
return false
},
'0',
peer, peer,
msgElements, msgElements,
new Map() msgAttributeInfos: new Map()
) },
msgList = data[1] null
} else { ],
const data = await invoke<{ msgList: RawMessage[] }>({ {
methodName: 'nodeIKernelMsgService/sendMsg',
cbCmd: 'nodeIKernelMsgListener/onMsgInfoListUpdate', cbCmd: 'nodeIKernelMsgListener/onMsgInfoListUpdate',
afterFirstCmd: false, afterFirstCmd: false,
cmdCB: payload => { cmdCB: payload => {
@@ -208,25 +126,10 @@ export class NTQQMsgApi extends Service {
} }
return false return false
}, },
args: [
{
msgId: '0',
peer,
msgElements,
msgAttributeInfos: new Map()
},
null
],
timeout timeout
})
msgList = data.msgList
} }
const retMsg = msgList.find(msgRecord => { )
if (msgRecord.guildId === msgId) { return data.msgList.find(msgRecord => msgRecord.guildId === msgId)
return true
}
})
return retMsg!
} }
async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) { async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) {
@@ -234,19 +137,13 @@ export class NTQQMsgApi extends Service {
if (session) { if (session) {
return session.getMsgService().forwardMsg(msgIds, srcPeer, [destPeer], []) return session.getMsgService().forwardMsg(msgIds, srcPeer, [destPeer], [])
} else { } else {
return await invoke<GeneralCallResult>({ return await invoke(NTMethod.FORWARD_MSG, [{
methodName: NTMethod.FORWARD_MSG, msgIds,
args: [
{
msgIds: msgIds,
srcContact: srcPeer, srcContact: srcPeer,
dstContacts: [destPeer], dstContacts: [destPeer],
commentElements: [], commentElements: [],
msgAttributeInfos: new Map(), msgAttributeInfos: new Map(),
}, }, null])
null,
],
})
} }
} }
@@ -256,34 +153,19 @@ export class NTQQMsgApi extends Service {
return { msgId: id, senderShowName } return { msgId: id, senderShowName }
}) })
const selfUid = selfInfo.uid const selfUid = selfInfo.uid
let msgList: RawMessage[] const data = await invoke<{ msgList: RawMessage[] }>(
if (NTEventDispatch.initialised) { 'nodeIKernelMsgService/multiForwardMsgWithComment',
const data = await NTEventDispatch.CallNormalEvent< [
(msgInfo: typeof msgInfos, srcPeer: Peer, destPeer: Peer, comment: Array<any>, attr: Map<any, any>,) => Promise<unknown>, {
(msgList: RawMessage[]) => void
>(
'NodeIKernelMsgService/multiForwardMsgWithComment',
'NodeIKernelMsgListener/onMsgInfoListUpdate',
1,
5000,
(msgRecords: RawMessage[]) => {
for (let msgRecord of msgRecords) {
if (msgRecord.peerUid == destPeer.peerUid && msgRecord.senderUid == selfUid) {
return true
}
}
return false
},
msgInfos, msgInfos,
srcPeer, srcContact: srcPeer,
destPeer, dstContact: destPeer,
[], commentElements: [],
new Map() msgAttributeInfos: new Map(),
) },
msgList = data[1] null,
} else { ],
const data = await invoke<{ msgList: RawMessage[] }>({ {
methodName: 'nodeIKernelMsgService/multiForwardMsgWithComment',
cbCmd: 'nodeIKernelMsgListener/onMsgInfoListUpdate', cbCmd: 'nodeIKernelMsgListener/onMsgInfoListUpdate',
afterFirstCmd: false, afterFirstCmd: false,
cmdCB: payload => { cmdCB: payload => {
@@ -294,25 +176,14 @@ export class NTQQMsgApi extends Service {
} }
return false return false
}, },
args: [
{
msgInfos,
srcContact: srcPeer,
dstContact: destPeer,
commentElements: [],
msgAttributeInfos: new Map(),
},
null,
],
})
msgList = data.msgList
} }
for (const msg of msgList) { )
for (const msg of data.msgList) {
const arkElement = msg.elements.find(ele => ele.arkElement) const arkElement = msg.elements.find(ele => ele.arkElement)
if (!arkElement) { if (!arkElement) {
continue continue
} }
const forwardData: any = JSON.parse(arkElement.arkElement.bytesData) const forwardData = JSON.parse(arkElement.arkElement!.bytesData)
if (forwardData.app != 'com.tencent.multimsg') { if (forwardData.app != 'com.tencent.multimsg') {
continue continue
} }
@@ -323,61 +194,85 @@ export class NTQQMsgApi extends Service {
throw new Error('转发消息超时') throw new Error('转发消息超时')
} }
async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, z: boolean) { async getMsgsBySeqAndCount(peer: Peer, msgSeq: string, count: number, desc: boolean, z: boolean) {
const session = getSession() const session = getSession()
if (session) { if (session) {
return await session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, z) return await session.getMsgService().getMsgsBySeqAndCount(peer, msgSeq, count, desc, z)
} else { } else {
return await invoke<GeneralCallResult & { return await invoke('nodeIKernelMsgService/getMsgsBySeqAndCount', [{
msgList: RawMessage[]
}>({
methodName: 'nodeIKernelMsgService/getMsgsBySeqAndCount',
args: [
{
peer, peer,
cnt: count, cnt: count,
msgSeq: seq, msgSeq,
queryOrder: desc queryOrder: desc
}, }, null])
null,
],
})
} }
} }
/** 27187 TODO */ async getSingleMsg(peer: Peer, msgSeq: string) {
async getLastestMsgByUids(peer: Peer, count = 20, isReverseOrder = false) {
const session = getSession() const session = getSession()
const ret = await session?.getMsgService().queryMsgsWithFilterEx('0', '0', '0', { if (session) {
return await session.getMsgService().getSingleMsg(peer, msgSeq)
} else {
return await invoke('nodeIKernelMsgService/getSingleMsg', [{ peer, msgSeq }, null])
}
}
async queryFirstMsgBySeq(peer: Peer, msgSeq: string) {
return await invoke('nodeIKernelMsgService/queryMsgsWithFilterEx', [{
msgId: '0',
msgTime: '0',
msgSeq,
params: {
chatInfo: peer, chatInfo: peer,
filterMsgType: [], filterMsgType: [],
filterSendersUid: [], filterSendersUid: [],
filterMsgToTime: '0', filterMsgToTime: '0',
filterMsgFromTime: '0', filterMsgFromTime: '0',
isReverseOrder: isReverseOrder, //此参数有点离谱 注意不是本次查询的排序 而是全部消历史信息的排序 默认false 从新消息拉取到旧消息 isReverseOrder: true,
isIncludeCurrent: true, isIncludeCurrent: true,
pageLimit: count, pageLimit: 1,
}) }
return ret }, null])
} }
async getSingleMsg(peer: Peer, seq: string) { async queryMsgsWithFilterExBySeq(peer: Peer, msgSeq: string, filterMsgTime: string, filterSendersUid: string[]) {
const session = getSession() return await invoke('nodeIKernelMsgService/queryMsgsWithFilterEx', [{
if (session) { msgId: '0',
return await session.getMsgService().getSingleMsg(peer, seq) msgTime: '0',
} else { msgSeq,
return await invoke<GeneralCallResult & { params: {
msgList: RawMessage[] chatInfo: peer,
}>({ filterMsgType: [],
methodName: 'nodeIKernelMsgService/getSingleMsg', filterSendersUid,
args: [ filterMsgToTime: filterMsgTime,
{ filterMsgFromTime: filterMsgTime,
isReverseOrder: true,
isIncludeCurrent: true,
pageLimit: 1,
}
}, null])
}
async setMsgRead(peer: Peer) {
return await invoke('nodeIKernelMsgService/setMsgRead', [{ peer }, null])
}
async getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, count: number) {
return await invoke('nodeIKernelMsgService/getMsgEmojiLikesList', [{
peer, peer,
msgSeq: seq, msgSeq,
}, emojiId,
null, emojiType,
], cnt: count
}) }, null])
} }
async fetchFavEmojiList(count: number) {
return await invoke('nodeIKernelMsgService/fetchFavEmojiList', [{
resId: '',
count,
backwardFetch: true,
forceRefresh: true
}, null])
} }
} }

View File

@@ -1,12 +1,9 @@
import { invoke, NTMethod } from '../ntcall' import { invoke } from '../ntcall'
import { GeneralCallResult } from '../services'
import { User, UserDetailInfoByUin, UserDetailInfoByUinV2, UserDetailInfoListenerArg } from '../types' import { User, UserDetailInfoByUin, UserDetailInfoByUinV2, UserDetailInfoListenerArg } from '../types'
import { getBuildVersion } from '@/common/utils' import { getBuildVersion } from '@/common/utils'
import { getSession } from '@/ntqqapi/wrapper' import { getSession } from '@/ntqqapi/wrapper'
import { RequestUtil } from '@/common/utils/request' import { RequestUtil } from '@/common/utils/request'
import { NodeIKernelProfileService, UserDetailSource, ProfileBizType, forceFetchClientKeyRetType } from '../services' import { UserDetailSource, ProfileBizType } from '../services'
import { NodeIKernelProfileListener } from '../listeners'
import { NTEventDispatch } from '@/common/utils/eventTask'
import { Time } from 'cosmokit' import { Time } from 'cosmokit'
import { Service, Context } from 'cordis' import { Service, Context } from 'cordis'
import { selfInfo } from '@/common/globalVars' import { selfInfo } from '@/common/globalVars'
@@ -18,49 +15,29 @@ declare module 'cordis' {
} }
export class NTQQUserApi extends Service { export class NTQQUserApi extends Service {
static inject = ['ntFriendApi', 'ntGroupApi']
constructor(protected ctx: Context) { constructor(protected ctx: Context) {
super(ctx, 'ntUserApi', true) super(ctx, 'ntUserApi', true)
} }
async setQQAvatar(filePath: string) { async setQQAvatar(path: string) {
return await invoke<GeneralCallResult>({ return await invoke(
methodName: NTMethod.SET_QQ_AVATAR, 'nodeIKernelProfileService/setHeader',
args: [ [
{ { path },
path: filePath,
},
null, null,
], ],
{
timeout: 10 * Time.second, // 10秒不一定够 timeout: 10 * Time.second, // 10秒不一定够
}) }
)
} }
async fetchUserDetailInfo(uid: string) { async fetchUserDetailInfo(uid: string) {
let info: UserDetailInfoListenerArg const result = await invoke<{ info: UserDetailInfoListenerArg }>(
if (NTEventDispatch.initialised) { 'nodeIKernelProfileService/fetchUserDetailInfo',
type EventService = NodeIKernelProfileService['fetchUserDetailInfo'] [
type EventListener = NodeIKernelProfileListener['onUserDetailInfoChanged']
const [_retData, profile] = await NTEventDispatch.CallNormalEvent
<EventService, EventListener>
(
'NodeIKernelProfileService/fetchUserDetailInfo',
'NodeIKernelProfileListener/onUserDetailInfoChanged',
1,
5000,
(profile) => profile.uid === uid,
'BuddyProfileStore',
[uid],
UserDetailSource.KSERVER,
[ProfileBizType.KALL]
)
info = profile
} else {
const result = await invoke<{ info: UserDetailInfoListenerArg }>({
methodName: 'nodeIKernelProfileService/fetchUserDetailInfo',
cbCmd: 'nodeIKernelProfileListener/onUserDetailInfoChanged',
afterFirstCmd: false,
cmdCB: payload => payload.info.uid === uid,
args: [
{ {
callFrom: 'BuddyProfileStore', callFrom: 'BuddyProfileStore',
uid: [uid], uid: [uid],
@@ -69,9 +46,13 @@ export class NTQQUserApi extends Service {
}, },
null null
], ],
}) {
info = result.info cbCmd: 'nodeIKernelProfileListener/onUserDetailInfoChanged',
afterFirstCmd: false,
cmdCB: payload => payload.info.uid === uid,
} }
)
const { info } = result
const ret: User = { const ret: User = {
...info.simpleInfo.coreInfo, ...info.simpleInfo.coreInfo,
...info.simpleInfo.status, ...info.simpleInfo.status,
@@ -84,53 +65,27 @@ export class NTQQUserApi extends Service {
return ret return ret
} }
async getUserDetailInfo(uid: string, getLevel = false, withBizInfo = true) { async getUserDetailInfo(uid: string) {
if (getBuildVersion() >= 26702) { if (getBuildVersion() >= 26702) {
return this.fetchUserDetailInfo(uid) return this.fetchUserDetailInfo(uid)
} }
if (NTEventDispatch.initialised) { const result = await invoke<{ info: User }>(
type EventService = NodeIKernelProfileService['getUserDetailInfoWithBizInfo'] 'nodeIKernelProfileService/getUserDetailInfoWithBizInfo',
type EventListener = NodeIKernelProfileListener['onProfileDetailInfoChanged'] [
const [_retData, profile] = await NTEventDispatch.CallNormalEvent
<EventService, EventListener>
(
'NodeIKernelProfileService/getUserDetailInfoWithBizInfo',
'NodeIKernelProfileListener/onProfileDetailInfoChanged',
2,
5000,
(profile) => profile.uid === uid,
uid,
[0]
)
return profile
} else {
const result = await invoke<{ info: User }>({
methodName: 'nodeIKernelProfileService/getUserDetailInfoWithBizInfo',
cbCmd: 'nodeIKernelProfileListener/onProfileDetailInfoChanged',
afterFirstCmd: false,
cmdCB: (payload) => payload.info.uid === uid,
args: [
{ {
uid, uid,
bizList: [0] bizList: [0]
}, },
null, null,
], ],
}) {
cbCmd: 'nodeIKernelProfileListener/onProfileDetailInfoChanged',
afterFirstCmd: false,
cmdCB: (payload) => payload.info.uid === uid,
}
)
return result.info return result.info
} }
}
async getSkey(): Promise<string> {
const clientKeyData = await this.forceFetchClientKey()
if (clientKeyData?.result !== 0) {
throw new Error('获取clientKey失败')
}
const url = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + selfInfo.uin
+ '&clientkey=' + clientKeyData.clientKey
+ '&u1=https%3A%2F%2Fh5.qzone.qq.com%2Fqqnt%2Fqzoneinpcqq%2Ffriend%3Frefresh%3D0%26clientuin%3D0%26darkMode%3D0&keyindex=' + clientKeyData.keyIndex
return (await RequestUtil.HttpsGetCookies(url))?.skey
}
async getCookies(domain: string) { async getCookies(domain: string) {
const clientKeyData = await this.forceFetchClientKey() const clientKeyData = await this.forceFetchClientKey()
@@ -139,10 +94,14 @@ export class NTQQUserApi extends Service {
} }
const uin = selfInfo.uin const uin = selfInfo.uin
const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + uin + '&clientkey=' + clientKeyData.clientKey + '&u1=https%3A%2F%2F' + domain + '%2F' + uin + '%2Finfocenter&keyindex=19%27' const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + uin + '&clientkey=' + clientKeyData.clientKey + '&u1=https%3A%2F%2F' + domain + '%2F' + uin + '%2Finfocenter&keyindex=19%27'
const cookies: { [key: string]: string; } = await RequestUtil.HttpsGetCookies(requestUrl) const cookies: { [key: string]: string } = await RequestUtil.HttpsGetCookies(requestUrl)
return cookies return cookies
} }
async getPSkey(domains: string[]) {
return await invoke('nodeIKernelTipOffService/getPskey', [{ domains, isForNewPCQQ: true }, null])
}
genBkn(sKey: string) { genBkn(sKey: string) {
sKey = sKey || '' sKey = sKey || ''
let hash = 5381 let hash = 5381
@@ -165,9 +124,9 @@ export class NTQQUserApi extends Service {
doLikeTollCount: 0 doLikeTollCount: 0
}) })
} else { } else {
return await invoke<GeneralCallResult & { succCounts: number }>({ return await invoke(
methodName: 'nodeIKernelProfileLikeService/setBuddyProfileLike', 'nodeIKernelProfileLikeService/setBuddyProfileLike',
args: [ [
{ {
doLikeUserInfo: { doLikeUserInfo: {
friendUid: uid, friendUid: uid,
@@ -178,20 +137,35 @@ export class NTQQUserApi extends Service {
}, },
null, null,
], ],
}) )
} }
} }
async getUidByUinV1(Uin: string) { async getUidByUinV1(uin: string) {
const session = getSession() const session = getSession()
// 通用转换开始尝试 // 通用转换开始尝试
let uid = (await session?.getUixConvertService().getUid([Uin]))?.uidInfo.get(Uin) let uid = (await session?.getUixConvertService().getUid([uin]))?.uidInfo.get(uin)
if (!uid) { if (!uid) {
let unveifyUid = (await this.getUserDetailInfoByUin(Uin)).info.uid;//从QQ Native 特殊转换 方法三 for (const membersList of this.ctx.ntGroupApi.groupMembers.values()) { //从群友列表转
if (unveifyUid.indexOf('*') == -1) { for (const member of membersList.values()) {
if (member.uin === uin) {
uid = member.uid
break
}
}
if (uid) break
}
}
if (!uid) {
const unveifyUid = (await this.getUserDetailInfoByUin(uin)).info.uid //特殊转换
if (unveifyUid.indexOf('*') === -1) {
uid = unveifyUid uid = unveifyUid
} }
} }
if (!uid) {
const friends = await this.ctx.ntFriendApi.getFriends() //从好友列表转
uid = friends.find(item => item.uin === uin)?.uid
}
return uid return uid
} }
@@ -205,83 +179,49 @@ export class NTQQUserApi extends Service {
uid = (await session.getUixConvertService().getUid([uin])).uidInfo.get(uin) uid = (await session.getUixConvertService().getUid([uin])).uidInfo.get(uin)
if (uid) return uid if (uid) return uid
} else { } else {
let uid = (await invoke<{ uids: Map<string, string> }>({ let uid = (await invoke('nodeIKernelGroupService/getUidByUins', [{ uin: [uin] }])).uids.get(uin)
methodName: 'nodeIKernelGroupService/getUidByUins',
args: [
{ uin: [uin] },
null,
],
})).uids.get(uin)
if (uid) return uid if (uid) return uid
uid = (await invoke<Map<string, string>>({ uid = (await invoke('nodeIKernelProfileService/getUidByUin', [{ callFrom: 'FriendsServiceImpl', uin: [uin] }])).get(uin)
methodName: 'nodeIKernelProfileService/getUidByUin',
args: [
{
callFrom: 'FriendsServiceImpl',
uin: [uin],
},
null,
],
})).get(uin)
if (uid) return uid if (uid) return uid
uid = (await invoke<{ uidInfo: Map<string, string> }>({ uid = (await invoke('nodeIKernelUixConvertService/getUid', [{ uins: [uin] }])).uidInfo.get(uin)
methodName: 'nodeIKernelUixConvertService/getUid',
args: [
{ uin: [uin] },
null,
],
})).uidInfo.get(uin)
if (uid) return uid if (uid) return uid
} }
const unveifyUid = (await this.getUserDetailInfoByUinV2(uin)).detail.uid //从QQ Native 特殊转换 const unveifyUid = (await this.getUserDetailInfoByUinV2(uin)).detail.uid //从QQ Native 特殊转换
if (unveifyUid.indexOf('*') == -1) return unveifyUid if (unveifyUid.indexOf('*') == -1) return unveifyUid
} }
async getUidByUin(Uin: string) { async getUidByUin(uin: string) {
if (getBuildVersion() >= 26702) { if (getBuildVersion() >= 26702) {
return await this.getUidByUinV2(Uin) return this.getUidByUinV2(uin)
} }
return await this.getUidByUinV1(Uin) return this.getUidByUinV1(uin)
} }
async getUserDetailInfoByUinV2(uin: string) { async getUserDetailInfoByUinV2(uin: string) {
if (NTEventDispatch.initialised) { return await invoke<UserDetailInfoByUinV2>(
return await NTEventDispatch.CallNoListenerEvent 'nodeIKernelProfileService/getUserDetailInfoByUin',
<(Uin: string) => Promise<UserDetailInfoByUinV2>>( [
'NodeIKernelProfileService/getUserDetailInfoByUin',
5000,
uin
)
} else {
return await invoke<UserDetailInfoByUinV2>({
methodName: 'nodeIKernelProfileService/getUserDetailInfoByUin',
args: [
{ uin }, { uin },
null, null,
], ],
})
}
}
async getUserDetailInfoByUin(Uin: string) {
return NTEventDispatch.CallNoListenerEvent
<(Uin: string) => Promise<UserDetailInfoByUin>>(
'NodeIKernelProfileService/getUserDetailInfoByUin',
5000,
Uin
) )
} }
async getUinByUidV1(Uid: string) { async getUserDetailInfoByUin(uin: string) {
const ret = await NTEventDispatch.CallNoListenerEvent return await invoke<UserDetailInfoByUin>(
<(Uin: string[]) => Promise<{ uinInfo: Map<string, string> }>>( 'nodeIKernelProfileService/getUserDetailInfoByUin',
'NodeIKernelUixConvertService/getUin', [
5000, { uin },
[Uid] null,
],
) )
let uin = ret.uinInfo.get(Uid) }
async getUinByUidV1(uid: string) {
const ret = await invoke('nodeIKernelUixConvertService/getUin', [{ uids: [uid] }])
let uin = ret.uinInfo.get(uid)
if (!uin) { if (!uin) {
uin = (await this.getUserDetailInfo(Uid)).uin //从QQ Native 转换 uin = (await this.getUserDetailInfo(uid)).uin //从QQ Native 转换
} }
return uin return uin
} }
@@ -295,46 +235,25 @@ export class NTQQUserApi extends Service {
if (uin) return uin if (uin) return uin
uin = (await session.getUixConvertService().getUin([uid])).uinInfo.get(uid) uin = (await session.getUixConvertService().getUin([uid])).uinInfo.get(uid)
if (uin) return uin if (uin) return uin
return uin
} else { } else {
let uin = (await invoke<{ uins: Map<string, string> }>({ let uin = (await invoke('nodeIKernelGroupService/getUinByUids', [{ uid: [uid] }])).uins.get(uid)
methodName: 'nodeIKernelGroupService/getUinByUids',
args: [
{ uid: [uid] },
null,
],
})).uins.get(uid)
if (uin) return uin if (uin) return uin
uin = (await invoke<Map<string, string>>({ uin = (await invoke('nodeIKernelProfileService/getUinByUid', [{ callFrom: 'FriendsServiceImpl', uid: [uid] }])).get(uid)
methodName: 'nodeIKernelProfileService/getUinByUid',
args: [
{
callFrom: 'FriendsServiceImpl',
uid: [uid],
},
null,
],
})).get(uid)
if (uin) return uin if (uin) return uin
uin = (await invoke<{ uinInfo: Map<string, string> }>({ uin = (await invoke('nodeIKernelUixConvertService/getUin', [{ uids: [uid] }])).uinInfo.get(uid)
methodName: 'nodeIKernelUixConvertService/getUin',
args: [
{ uid: [uid] },
null,
],
})).uinInfo.get(uid)
if (uin) return uin if (uin) return uin
} }
let uin = (await this.ctx.ntFriendApi.getBuddyIdMap(true)).getKey(uid) let uin = (await this.ctx.ntFriendApi.getBuddyIdMap(true)).get(uid)
if (uin) return uin if (uin) return uin
uin = (await this.getUserDetailInfo(uid)).uin //从QQ Native 转换 uin = (await this.getUserDetailInfo(uid)).uin //从QQ Native 转换
return uin
} }
async getUinByUid(Uid: string) { async getUinByUid(uid: string) {
if (getBuildVersion() >= 26702) { if (getBuildVersion() >= 26702) {
return (await this.getUinByUidV2(Uid))! return this.getUinByUidV2(uid)
} }
return await this.getUinByUidV1(Uid) return this.getUinByUidV1(uid)
} }
async forceFetchClientKey() { async forceFetchClientKey() {
@@ -342,12 +261,7 @@ export class NTQQUserApi extends Service {
if (session) { if (session) {
return await session.getTicketService().forceFetchClientKey('') return await session.getTicketService().forceFetchClientKey('')
} else { } else {
return await invoke<forceFetchClientKeyRetType>({ return await invoke('nodeIKernelTicketService/forceFetchClientKey', [{ domain: '' }, null])
methodName: 'nodeIKernelTicketService/forceFetchClientKey',
args: [{
domain: ''
}, null],
})
} }
} }
@@ -361,4 +275,29 @@ export class NTQQUserApi extends Service {
} }
return selfInfo.nick return selfInfo.nick
} }
async setSelfStatus(status: number, extStatus: number, batteryStatus: number) {
return await invoke('nodeIKernelMsgService/setStatus', [{
statusReq: {
status,
extStatus,
batteryStatus,
}
}, null])
}
async getProfileLike(uid: string) {
return await invoke('nodeIKernelProfileLikeService/getBuddyProfileLike', [{
req: {
friendUids: [uid],
basic: 1,
vote: 1,
favorite: 0,
userProfile: 1,
type: 2,
start: 0,
limit: 20,
}
}, null])
}
} }

View File

@@ -1,5 +1,6 @@
import { RequestUtil } from '@/common/utils/request' import { RequestUtil } from '@/common/utils/request'
import { Service, Context } from 'cordis' import { Service, Context } from 'cordis'
import { Dict } from 'cosmokit'
declare module 'cordis' { declare module 'cordis' {
interface Context { interface Context {
@@ -40,7 +41,7 @@ interface WebApiGroupMemberRet {
em: string em: string
cache: number cache: number
adm_num: number adm_num: number
levelname: any levelname: unknown
mems: WebApiGroupMember[] mems: WebApiGroupMember[]
count: number count: number
svr_time: number svr_time: number
@@ -49,56 +50,6 @@ interface WebApiGroupMemberRet {
extmode: number extmode: number
} }
export interface WebApiGroupNoticeFeed {
u: number//发送者
fid: string//fid
pubt: number//时间
msg: {
text: string
text_face: string
title: string,
pics?: {
id: string,
w: string,
h: string
}[]
}
type: number
fn: number
cn: number
vn: number
settings: {
is_show_edit_card: number
remind_ts: number
tip_window_type: number
confirm_required: number
}
read_num: number
is_read: number
is_all_confirm: number
}
export interface WebApiGroupNoticeRet {
ec: number
em: string
ltsm: number
srv_code: number
read_only: number
role: number
feeds: WebApiGroupNoticeFeed[]
group: {
group_id: number
class_ext: number
}
sta: number,
gln: number
tst: number,
ui: any
server_time: number
svrt: number
ad: number
}
interface GroupEssenceMsg { interface GroupEssenceMsg {
group_code: string group_code: string
msg_seq: number msg_seq: number
@@ -109,7 +60,7 @@ interface GroupEssenceMsg {
add_digest_uin: string add_digest_uin: string
add_digest_nick: string add_digest_nick: string
add_digest_time: number add_digest_time: number
msg_content: any[] msg_content: unknown[]
can_be_removed: true can_be_removed: true
} }
@@ -124,21 +75,47 @@ export interface GroupEssenceMsgRet {
} }
} }
interface SetGroupNoticeParams {
groupCode: string
content: string
pinned: number
type: number
isShowEditCard: number
tipWindowType: number
confirmRequired: number
picId: string
imgWidth?: number
imgHeight?: number
}
interface SetGroupNoticeRet {
ec: number
em: string
id: number
ltsm: number
new_fid: string
read_only: number
role: number
srv_code: number
}
export class NTQQWebApi extends Service { export class NTQQWebApi extends Service {
static inject = ['ntUserApi']
constructor(protected ctx: Context) { constructor(protected ctx: Context) {
super(ctx, 'ntWebApi', true) super(ctx, 'ntWebApi', true)
} }
async getGroupMembers(GroupCode: string, cached: boolean = true): Promise<WebApiGroupMember[]> { async getGroupMembers(groupCode: string): Promise<WebApiGroupMember[]> {
const memberData: Array<WebApiGroupMember> = new Array<WebApiGroupMember>() const memberData: Array<WebApiGroupMember> = new Array<WebApiGroupMember>()
const cookieObject = await this.ctx.ntUserApi.getCookies('qun.qq.com') const cookieObject = await this.ctx.ntUserApi.getCookies('qun.qq.com')
const cookieStr = Object.entries(cookieObject).map(([key, value]) => `${key}=${value}`).join('; ') const cookieStr = this.cookieToString(cookieObject)
const retList: Promise<WebApiGroupMemberRet>[] = [] const retList: Promise<WebApiGroupMemberRet>[] = []
const params = new URLSearchParams({ const params = new URLSearchParams({
st: '0', st: '0',
end: '40', end: '40',
sort: '1', sort: '1',
gc: GroupCode, gc: groupCode,
bkn: this.genBkn(cookieObject.skey) bkn: this.genBkn(cookieObject.skey)
}) })
const fastRet = await RequestUtil.HttpGetJson<WebApiGroupMemberRet>(`https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?${params}`, 'POST', '', { 'Cookie': cookieStr }) const fastRet = await RequestUtil.HttpGetJson<WebApiGroupMemberRet>(`https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?${params}`, 'POST', '', { 'Cookie': cookieStr })
@@ -171,132 +148,161 @@ export class NTQQWebApi extends Service {
} }
genBkn(sKey: string) { genBkn(sKey: string) {
sKey = sKey || ''; sKey = sKey || ''
let hash = 5381; let hash = 5381
for (let i = 0; i < sKey.length; i++) { for (let i = 0; i < sKey.length; i++) {
const code = sKey.charCodeAt(i); const code = sKey.charCodeAt(i)
hash = hash + (hash << 5) + code; hash = hash + (hash << 5) + code
} }
return (hash & 0x7FFFFFFF).toString()
return (hash & 0x7FFFFFFF).toString();
} }
//实现未缓存 考虑2h缓存 //实现未缓存 考虑2h缓存
async getGroupHonorInfo(groupCode: string, getType: WebHonorType) { async getGroupHonorInfo(groupCode: string, getType: WebHonorType) {
const getDataInternal = async (Internal_groupCode: string, Internal_type: number) => { const getDataInternal = async (Internal_groupCode: string, Internal_type: number) => {
let url = 'https://qun.qq.com/interactive/honorlist?gc=' + Internal_groupCode + '&type=' + Internal_type.toString(); const url = 'https://qun.qq.com/interactive/honorlist?gc=' + Internal_groupCode + '&type=' + Internal_type.toString()
let res = ''; let resJson
let resJson;
try { try {
res = await RequestUtil.HttpGetText(url, 'GET', '', { 'Cookie': cookieStr }); const res = await RequestUtil.HttpGetText(url, 'GET', '', { 'Cookie': cookieStr })
const match = res.match(/window\.__INITIAL_STATE__=(.*?);/); const match = res.match(/window\.__INITIAL_STATE__=(.*?);/)
if (match) { if (match) {
resJson = JSON.parse(match[1].trim()); resJson = JSON.parse(match[1].trim())
} }
if (Internal_type === 1) { if (Internal_type === 1) {
return resJson?.talkativeList; return resJson?.talkativeList
} else { } else {
return resJson?.actorList; return resJson?.actorList
} }
} catch (e) { } catch (e) {
this.ctx.logger.error('获取当前群荣耀失败', url, e); this.ctx.logger.error('获取当前群荣耀失败', url, e)
} }
return undefined; return undefined
} }
let HonorInfo: any = { group_id: groupCode }; const honorInfo: Dict = { group_id: groupCode }
const cookieObject = await this.ctx.ntUserApi.getCookies('qun.qq.com') const cookieObject = await this.ctx.ntUserApi.getCookies('qun.qq.com')
const cookieStr = Object.entries(cookieObject).map(([key, value]) => `${key}=${value}`).join('; ') const cookieStr = this.cookieToString(cookieObject)
if (getType === WebHonorType.TALKACTIVE || getType === WebHonorType.ALL) { if (getType === WebHonorType.TALKACTIVE || getType === WebHonorType.ALL) {
try { try {
let RetInternal = await getDataInternal(groupCode, 1); const RetInternal = await getDataInternal(groupCode, 1)
if (!RetInternal) { if (!RetInternal) {
throw new Error('获取龙王信息失败'); throw new Error('获取龙王信息失败')
} }
HonorInfo.current_talkative = { honorInfo.current_talkative = {
user_id: RetInternal[0]?.uin, user_id: RetInternal[0]?.uin,
avatar: RetInternal[0]?.avatar, avatar: RetInternal[0]?.avatar,
nickname: RetInternal[0]?.name, nickname: RetInternal[0]?.name,
day_count: 0, day_count: 0,
description: RetInternal[0]?.desc description: RetInternal[0]?.desc
} }
HonorInfo.talkative_list = []; honorInfo.talkative_list = [];
for (const talkative_ele of RetInternal) { for (const talkative_ele of RetInternal) {
HonorInfo.talkative_list.push({ honorInfo.talkative_list.push({
user_id: talkative_ele?.uin, user_id: talkative_ele?.uin,
avatar: talkative_ele?.avatar, avatar: talkative_ele?.avatar,
description: talkative_ele?.desc, description: talkative_ele?.desc,
day_count: 0, day_count: 0,
nickname: talkative_ele?.name nickname: talkative_ele?.name
}); })
} }
} catch (e) { } catch (e) {
this.ctx.logger.error(e); this.ctx.logger.error(e)
} }
} }
if (getType === WebHonorType.PERFROMER || getType === WebHonorType.ALL) { if (getType === WebHonorType.PERFROMER || getType === WebHonorType.ALL) {
try { try {
let RetInternal = await getDataInternal(groupCode, 2); const RetInternal = await getDataInternal(groupCode, 2)
if (!RetInternal) { if (!RetInternal) {
throw new Error('获取群聊之火失败'); throw new Error('获取群聊之火失败')
} }
HonorInfo.performer_list = []; honorInfo.performer_list = []
for (const performer_ele of RetInternal) { for (const performer_ele of RetInternal) {
HonorInfo.performer_list.push({ honorInfo.performer_list.push({
user_id: performer_ele?.uin, user_id: performer_ele?.uin,
nickname: performer_ele?.name, nickname: performer_ele?.name,
avatar: performer_ele?.avatar, avatar: performer_ele?.avatar,
description: performer_ele?.desc description: performer_ele?.desc
}); })
} }
} catch (e) { } catch (e) {
this.ctx.logger.error(e); this.ctx.logger.error(e)
} }
} }
if (getType === WebHonorType.PERFROMER || getType === WebHonorType.ALL) { if (getType === WebHonorType.PERFROMER || getType === WebHonorType.ALL) {
try { try {
let RetInternal = await getDataInternal(groupCode, 3); const RetInternal = await getDataInternal(groupCode, 3)
if (!RetInternal) { if (!RetInternal) {
throw new Error('获取群聊炽焰失败'); throw new Error('获取群聊炽焰失败')
} }
HonorInfo.legend_list = []; honorInfo.legend_list = []
for (const legend_ele of RetInternal) { for (const legend_ele of RetInternal) {
HonorInfo.legend_list.push({ honorInfo.legend_list.push({
user_id: legend_ele?.uin, user_id: legend_ele?.uin,
nickname: legend_ele?.name, nickname: legend_ele?.name,
avatar: legend_ele?.avatar, avatar: legend_ele?.avatar,
desc: legend_ele?.description desc: legend_ele?.description
}); })
} }
} catch (e) { } catch (e) {
this.ctx.logger.error('获取群聊炽焰失败', e); this.ctx.logger.error('获取群聊炽焰失败', e)
} }
} }
if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) { if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) {
try { try {
let RetInternal = await getDataInternal(groupCode, 6); const RetInternal = await getDataInternal(groupCode, 6)
if (!RetInternal) { if (!RetInternal) {
throw new Error('获取快乐源泉失败'); throw new Error('获取快乐源泉失败')
} }
HonorInfo.emotion_list = []; honorInfo.emotion_list = []
for (const emotion_ele of RetInternal) { for (const emotion_ele of RetInternal) {
HonorInfo.emotion_list.push({ honorInfo.emotion_list.push({
user_id: emotion_ele?.uin, user_id: emotion_ele?.uin,
nickname: emotion_ele?.name, nickname: emotion_ele?.name,
avatar: emotion_ele?.avatar, avatar: emotion_ele?.avatar,
desc: emotion_ele?.description desc: emotion_ele?.description
}); })
} }
} catch (e) { } catch (e) {
this.ctx.logger.error('获取快乐源泉失败', e); this.ctx.logger.error('获取快乐源泉失败', e)
} }
} }
//冒尖小春笋好像已经被tx扬了 //冒尖小春笋好像已经被tx扬了
if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) { if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) {
HonorInfo.strong_newbie_list = []; honorInfo.strong_newbie_list = []
} }
return HonorInfo; return honorInfo
}
async setGroupNotice(params: SetGroupNoticeParams): Promise<SetGroupNoticeRet> {
const cookieObject = await this.ctx.ntUserApi.getCookies('qun.qq.com')
const settings = JSON.stringify({
is_show_edit_card: params.isShowEditCard,
tip_window_type: params.tipWindowType,
confirm_required: params.confirmRequired
})
return await RequestUtil.HttpGetJson<SetGroupNoticeRet>(
`https://web.qun.qq.com/cgi-bin/announce/add_qun_notice?${new URLSearchParams({
bkn: this.genBkn(cookieObject.skey),
qid: params.groupCode,
text: params.content,
pinned: params.pinned.toString(),
type: params.type.toString(),
settings: settings,
...(params.picId !== '' && {
pic: params.picId,
imgWidth: params.imgWidth?.toString(),
imgHeight: params.imgHeight?.toString(),
})
})}`,
'POST',
'',
{ 'Cookie': this.cookieToString(cookieObject) }
)
}
private cookieToString(cookieObject: Dict) {
return Object.entries(cookieObject).map(([key, value]) => `${key}=${value}`).join('; ')
} }
} }

View File

@@ -1,6 +1,6 @@
import { invoke, NTClass, NTMethod } from '../ntcall' import { invoke, NTClass, NTMethod } from '../ntcall'
import { GeneralCallResult } from '../services' import { GeneralCallResult } from '../services'
import { ReceiveCmd } from '../hook' import { ReceiveCmdS } from '../hook'
import { BrowserWindow } from 'electron' import { BrowserWindow } from 'electron'
import { Service, Context } from 'cordis' import { Service, Context } from 'cordis'
@@ -38,17 +38,19 @@ export class NTQQWindowApi extends Service {
// 打开窗口并获取对应的下发事件 // 打开窗口并获取对应的下发事件
async openWindow<R = GeneralCallResult>( async openWindow<R = GeneralCallResult>(
ntQQWindow: NTQQWindow, ntQQWindow: NTQQWindow,
args: any[], args: unknown[],
cbCmd: ReceiveCmd | undefined, cbCmd: ReceiveCmdS | undefined,
autoCloseSeconds: number = 2, autoCloseSeconds: number = 2,
) { ) {
const result = await invoke<R>({ const result = await invoke<R>(
NTMethod.OPEN_EXTRA_WINDOW,
[ntQQWindow.windowName, ...args],
{
className: NTClass.WINDOW_API, className: NTClass.WINDOW_API,
methodName: NTMethod.OPEN_EXTRA_WINDOW,
cbCmd, cbCmd,
afterFirstCmd: false, afterFirstCmd: false,
args: [ntQQWindow.windowName, ...args], }
}) )
setTimeout(() => { setTimeout(() => {
for (const w of BrowserWindow.getAllWindows()) { for (const w of BrowserWindow.getAllWindows()) {
// log("close window", w.webContents.getURL()) // log("close window", w.webContents.getURL())

View File

@@ -2,8 +2,6 @@ import fs from 'node:fs'
import { Service, Context } from 'cordis' import { Service, Context } from 'cordis'
import { registerCallHook, registerReceiveHook, ReceiveCmdS } from './hook' import { registerCallHook, registerReceiveHook, ReceiveCmdS } from './hook'
import { MessageUnique } from '../common/utils/messageUnique' import { MessageUnique } from '../common/utils/messageUnique'
import { NTEventDispatch } from '../common/utils/eventTask'
import { wrapperConstructor, getSession } from './wrapper'
import { Config as LLOBConfig } from '../common/types' import { Config as LLOBConfig } from '../common/types'
import { llonebotError } from '../common/globalVars' import { llonebotError } from '../common/globalVars'
import { isNumeric } from '../common/utils/misc' import { isNumeric } from '../common/utils/misc'
@@ -21,6 +19,7 @@ import {
} from './types' } from './types'
import { selfInfo } from '../common/globalVars' import { selfInfo } from '../common/globalVars'
import { version } from '../version' import { version } from '../version'
import { invoke } from './ntcall'
declare module 'cordis' { declare module 'cordis' {
interface Context { interface Context {
@@ -28,17 +27,17 @@ declare module 'cordis' {
} }
interface Events { interface Events {
'nt/message-created': (input: RawMessage[]) => void 'nt/message-created': (input: RawMessage[]) => void
'nt/message-deleted': (input: RawMessage[]) => void 'nt/message-deleted': (input: RawMessage) => void
'nt/message-sent': (input: RawMessage[]) => void 'nt/message-sent': (input: RawMessage) => void
'nt/group-notify': (input: GroupNotify[]) => void 'nt/group-notify': (input: GroupNotify[]) => void
'nt/friend-request': (input: FriendRequest[]) => void 'nt/friend-request': (input: FriendRequest[]) => void
'nt/group-member-info-updated': (input: { groupCode: string; members: GroupMember[] }) => void 'nt/group-member-info-updated': (input: { groupCode: string, members: GroupMember[] }) => void
'nt/friend-list-updated': (input: { groupCode: string; members: GroupMember[] }) => void 'nt/system-message-created': (input: Uint8Array) => void
} }
} }
class Core extends Service { class Core extends Service {
static inject = ['ntMsgApi', 'ntFileApi', 'ntFileCacheApi', 'ntFriendApi', 'ntGroupApi', 'ntUserApi', 'ntWindowApi'] static inject = ['ntMsgApi', 'ntFriendApi', 'ntGroupApi']
constructor(protected ctx: Context, public config: Core.Config) { constructor(protected ctx: Context, public config: Core.Config) {
super(ctx, 'app', true) super(ctx, 'app', true)
@@ -46,13 +45,12 @@ class Core extends Service {
public start() { public start() {
llonebotError.otherError = '' llonebotError.otherError = ''
const WrapperSession = getSession()
if (WrapperSession) {
NTEventDispatch.init({ ListenerMap: wrapperConstructor, WrapperSession })
}
MessageUnique.init(selfInfo.uin) MessageUnique.init(selfInfo.uin)
this.registerListener() this.registerListener()
this.ctx.logger.info(`LLOneBot/${version}`) this.ctx.logger.info(`LLOneBot/${version}`)
this.ctx.on('llonebot/config-updated', input => {
Object.assign(this.config, input)
})
} }
private registerListener() { private registerListener() {
@@ -60,8 +58,8 @@ class Core extends Service {
data: CategoryFriend[] data: CategoryFriend[]
}>(ReceiveCmdS.FRIENDS, (payload) => { }>(ReceiveCmdS.FRIENDS, (payload) => {
type V2data = { userSimpleInfos: Map<string, SimpleInfo> } type V2data = { userSimpleInfos: Map<string, SimpleInfo> }
let friendList: User[] = []; let friendList: User[] = []
if ((payload as any).userSimpleInfos) { if ('userSimpleInfos' in payload) {
friendList = Object.values((payload as unknown as V2data).userSimpleInfos).map((v: SimpleInfo) => { friendList = Object.values((payload as unknown as V2data).userSimpleInfos).map((v: SimpleInfo) => {
return { return {
...v.coreInfo, ...v.coreInfo,
@@ -87,18 +85,18 @@ class Core extends Service {
for (const msgElement of message.elements) { for (const msgElement of message.elements) {
setTimeout(() => { setTimeout(() => {
const picPath = msgElement.picElement?.sourcePath const picPath = msgElement.picElement?.sourcePath
const picThumbPath = [...msgElement.picElement?.thumbPath.values()] const picThumbPath = [...(msgElement.picElement?.thumbPath ?? []).values()]
const pttPath = msgElement.pttElement?.filePath const pttPath = msgElement.pttElement?.filePath
const filePath = msgElement.fileElement?.filePath const filePath = msgElement.fileElement?.filePath
const videoPath = msgElement.videoElement?.filePath const videoPath = msgElement.videoElement?.filePath
const videoThumbPath: string[] = [...msgElement.videoElement.thumbPath?.values()!] const videoThumbPath = [...(msgElement.videoElement?.thumbPath ?? []).values()]
const pathList = [picPath, ...picThumbPath, pttPath, filePath, videoPath, ...videoThumbPath] const pathList = [picPath, ...picThumbPath, pttPath, filePath, videoPath, ...videoThumbPath]
if (msgElement.picElement) { if (msgElement.picElement) {
pathList.push(...Object.values(msgElement.picElement.thumbPath)) pathList.push(...Object.values(msgElement.picElement.thumbPath))
} }
for (const path of pathList) { for (const path of pathList) {
if (path) { if (path) {
fs.unlink(picPath, () => { fs.unlink(path, () => {
this.ctx.logger.info('删除文件成功', path) this.ctx.logger.info('删除文件成功', path)
}) })
} }
@@ -124,15 +122,15 @@ class Core extends Service {
}[] }[]
}>(ReceiveCmdS.RECENT_CONTACT, async (payload) => { }>(ReceiveCmdS.RECENT_CONTACT, async (payload) => {
for (const recentContact of payload.changedRecentContactLists) { for (const recentContact of payload.changedRecentContactLists) {
for (const changedContact of recentContact.changedList) { for (const contact of recentContact.changedList) {
if (activatedPeerUids.includes(changedContact.id)) continue if (activatedPeerUids.includes(contact.id)) continue
activatedPeerUids.push(changedContact.id) activatedPeerUids.push(contact.id)
const peer = { peerUid: changedContact.id, chatType: changedContact.chatType } const peer = { peerUid: contact.id, chatType: contact.chatType }
if (changedContact.chatType === ChatType.temp) { if (contact.chatType === ChatType.temp) {
this.ctx.ntMsgApi.activateChatAndGetHistory(peer).then(() => { this.ctx.ntMsgApi.activateChatAndGetHistory(peer).then(() => {
this.ctx.ntMsgApi.getMsgHistory(peer, '', 20).then(({ msgList }) => { this.ctx.ntMsgApi.getMsgHistory(peer, '', 20).then(({ msgList }) => {
const lastTempMsg = msgList.at(-1) const lastTempMsg = msgList.at(-1)
if (Date.now() / 1000 - parseInt(lastTempMsg?.msgTime!) < 5) { if (Date.now() / 1000 - Number(lastTempMsg?.msgTime) < 5) {
this.ctx.parallel('nt/message-created', [lastTempMsg!]) this.ctx.parallel('nt/message-created', [lastTempMsg!])
} }
}) })
@@ -176,29 +174,26 @@ class Core extends Service {
this.ctx.parallel('nt/message-created', payload.msgList) this.ctx.parallel('nt/message-created', payload.msgList)
}) })
const sentMsgIds = new Map<string, boolean>()
const recallMsgIds: string[] = [] // 避免重复上报 const recallMsgIds: string[] = [] // 避免重复上报
registerReceiveHook<{ msgList: RawMessage[] }>([ReceiveCmdS.UPDATE_MSG], payload => { registerReceiveHook<{ msgList: RawMessage[] }>([ReceiveCmdS.UPDATE_MSG], payload => {
const list = payload.msgList.filter(v => { for (const msg of payload.msgList) {
if (recallMsgIds.includes(v.msgId)) { if (msg.recallTime !== '0' && !recallMsgIds.includes(msg.msgId)) {
return false recallMsgIds.push(msg.msgId)
this.ctx.parallel('nt/message-deleted', msg)
} else if (sentMsgIds.get(msg.msgId)) {
sentMsgIds.delete(msg.msgId)
this.ctx.parallel('nt/message-sent', msg)
}
} }
recallMsgIds.push(v.msgId)
return true
})
this.ctx.parallel('nt/message-deleted', list)
}) })
registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmdS.SELF_SEND_MSG, payload => { registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmdS.SELF_SEND_MSG, payload => {
const { msgId, chatType, peerUid } = payload.msgRecord
const peer = {
chatType,
peerUid
}
MessageUnique.createMsg(peer, msgId)
if (!this.config.reportSelfMessage) { if (!this.config.reportSelfMessage) {
return return
} }
this.ctx.parallel('nt/message-sent', [payload.msgRecord]) sentMsgIds.set(payload.msgRecord.msgId, true)
}) })
const groupNotifyFlags: string[] = [] const groupNotifyFlags: string[] = []
@@ -229,6 +224,14 @@ class Core extends Service {
registerReceiveHook<FriendRequestNotify>(ReceiveCmdS.FRIEND_REQUEST, payload => { registerReceiveHook<FriendRequestNotify>(ReceiveCmdS.FRIEND_REQUEST, payload => {
this.ctx.parallel('nt/friend-request', payload.data.buddyReqs) this.ctx.parallel('nt/friend-request', payload.data.buddyReqs)
}) })
invoke('nodeIKernelMsgListener/onRecvSysMsg', [], { classNameIsRegister: true })
registerReceiveHook<{
msgBuf: number[]
}>('nodeIKernelMsgListener/onRecvSysMsg', payload => {
this.ctx.parallel('nt/system-message-created', Uint8Array.from(payload.msgBuf))
})
} }
} }

View File

@@ -1,3 +1,5 @@
import ffmpeg from 'fluent-ffmpeg'
import faceConfig from './helper/face_config.json'
import { import {
AtType, AtType,
ElementType, ElementType,
@@ -13,22 +15,16 @@ import {
SendTextElement, SendTextElement,
SendVideoElement, SendVideoElement,
} from './types' } from './types'
import { promises as fs } from 'node:fs' import { stat, writeFile, copyFile, unlink } from 'node:fs/promises'
import ffmpeg from 'fluent-ffmpeg'
import { calculateFileMD5, isGIF } from '../common/utils/file' import { calculateFileMD5, isGIF } from '../common/utils/file'
import { defaultVideoThumb, getVideoInfo } from '../common/utils/video' import { defaultVideoThumb, getVideoInfo } from '../common/utils/video'
import { encodeSilk } from '../common/utils/audio' import { encodeSilk } from '../common/utils/audio'
import faceConfig from './helper/face_config.json'
import { Context } from 'cordis' import { Context } from 'cordis'
import { isNullable } from 'cosmokit' import { isNullable } from 'cosmokit'
export const mFaceCache = new Map<string, string>() // emojiId -> faceName //export const mFaceCache = new Map<string, string>() // emojiId -> faceName
export namespace SendMsgElementConstructor {
export function poke(groupCode: string, uin: string) {
return null
}
export namespace SendElementEntities {
export function text(content: string): SendTextElement { export function text(content: string): SendTextElement {
return { return {
elementType: ElementType.TEXT, elementType: ElementType.TEXT,
@@ -103,32 +99,33 @@ export namespace SendMsgElementConstructor {
} }
} }
export async function file(ctx: Context, filePath: string, fileName: string = '', folderId: string = ''): Promise<SendFileElement> { export async function file(ctx: Context, filePath: string, fileName: string, folderId = ''): Promise<SendFileElement> {
const { fileName: _fileName, path, fileSize } = await ctx.ntFileApi.uploadFile(filePath, ElementType.FILE) const fileSize = (await stat(filePath)).size.toString()
if (fileSize === 0) { if (fileSize === '0') {
throw '文件异常,大小为 0' ctx.logger.warn(`文件${fileName}异常,大小为 0`)
throw new Error('文件异常,大小为 0')
} }
const element: SendFileElement = { const element: SendFileElement = {
elementType: ElementType.FILE, elementType: ElementType.FILE,
elementId: '', elementId: '',
fileElement: { fileElement: {
fileName: fileName || _fileName, fileName,
folderId: folderId, folderId,
filePath: path!, filePath,
fileSize: fileSize.toString(), fileSize,
}, },
} }
return element return element
} }
export async function video(ctx: Context, filePath: string, fileName: string = '', diyThumbPath: string = ''): Promise<SendVideoElement> { export async function video(ctx: Context, filePath: string, fileName = '', diyThumbPath = ''): Promise<SendVideoElement> {
try { try {
await fs.stat(filePath) await stat(filePath)
} catch (e) { } catch (e) {
throw `文件${filePath}异常,不存在` throw `文件${filePath}异常,不存在`
} }
ctx.logger.info('复制视频到QQ目录', filePath) ctx.logger.info('复制视频到QQ目录', filePath)
let { fileName: _fileName, path, fileSize, md5 } = await ctx.ntFileApi.uploadFile(filePath, ElementType.VIDEO) const { fileName: _fileName, path, fileSize, md5 } = await ctx.ntFileApi.uploadFile(filePath, ElementType.VIDEO)
ctx.logger.info('复制视频到QQ目录完成', path) ctx.logger.info('复制视频到QQ目录完成', path)
if (fileSize === 0) { if (fileSize === 0) {
@@ -151,7 +148,7 @@ export namespace SendMsgElementConstructor {
filePath, filePath,
} }
try { try {
videoInfo = await getVideoInfo(path) videoInfo = await getVideoInfo(ctx, path)
ctx.logger.info('视频信息', videoInfo) ctx.logger.info('视频信息', videoInfo)
} catch (e) { } catch (e) {
ctx.logger.info('获取视频信息失败', e) ctx.logger.info('获取视频信息失败', e)
@@ -165,7 +162,7 @@ export namespace SendMsgElementConstructor {
function useDefaultThumb() { function useDefaultThumb() {
if (completed) return if (completed) return
ctx.logger.info('获取视频封面失败,使用默认封面') ctx.logger.info('获取视频封面失败,使用默认封面')
fs.writeFile(thumbPath, defaultVideoThumb) writeFile(thumbPath, defaultVideoThumb)
.then(() => { .then(() => {
resolve(thumbPath) resolve(thumbPath)
}) })
@@ -174,9 +171,9 @@ export namespace SendMsgElementConstructor {
setTimeout(useDefaultThumb, 5000) setTimeout(useDefaultThumb, 5000)
ffmpeg(filePath) ffmpeg(filePath)
.on('error', (err) => { .on('error', () => {
if (diyThumbPath) { if (diyThumbPath) {
fs.copyFile(diyThumbPath, thumbPath) copyFile(diyThumbPath, thumbPath)
.then(() => { .then(() => {
completed = true completed = true
resolve(thumbPath) resolve(thumbPath)
@@ -198,14 +195,14 @@ export namespace SendMsgElementConstructor {
resolve(thumbPath) resolve(thumbPath)
}) })
}) })
let thumbPath = new Map() const thumbPath = new Map()
const _thumbPath = await createThumb const _thumbPath = await createThumb
ctx.logger.info('生成视频缩略图', _thumbPath) ctx.logger.info('生成视频缩略图', _thumbPath)
const thumbSize = (await fs.stat(_thumbPath)).size const thumbSize = (await stat(_thumbPath)).size
// log("生成缩略图", _thumbPath) // log("生成缩略图", _thumbPath)
thumbPath.set(0, _thumbPath) thumbPath.set(0, _thumbPath)
const thumbMd5 = await calculateFileMD5(_thumbPath) const thumbMd5 = await calculateFileMD5(_thumbPath)
let element: SendVideoElement = { const element: SendVideoElement = {
elementType: ElementType.VIDEO, elementType: ElementType.VIDEO,
elementId: '', elementId: '',
videoElement: { videoElement: {
@@ -246,7 +243,7 @@ export namespace SendMsgElementConstructor {
throw '文件异常大小为0' throw '文件异常大小为0'
} }
if (converted) { if (converted) {
fs.unlink(silkPath).then() unlink(silkPath)
} }
return { return {
elementType: ElementType.PTT, elementType: ElementType.PTT,
@@ -299,7 +296,7 @@ export namespace SendMsgElementConstructor {
} }
} }
export function mface(emojiPackageId: number, emojiId: string, key: string, faceName: string): SendMarketFaceElement { export function mface(emojiPackageId: number, emojiId: string, key: string, summary?: string): SendMarketFaceElement {
return { return {
elementType: ElementType.MFACE, elementType: ElementType.MFACE,
marketFaceElement: { marketFaceElement: {
@@ -308,14 +305,13 @@ export namespace SendMsgElementConstructor {
emojiPackageId, emojiPackageId,
emojiId, emojiId,
key, key,
faceName: faceName || mFaceCache.get(emojiId) || '[商城表情]', faceName: summary || '[商城表情]',
}, },
} }
} }
export function dice(resultId: number | null): SendFaceElement { export function dice(resultId?: string | number): SendFaceElement {
// 实际测试并不能控制结果 // 实际测试并不能控制结果
// 随机1到6 // 随机1到6
if (isNullable(resultId)) resultId = Math.floor(Math.random() * 6) + 1 if (isNullable(resultId)) resultId = Math.floor(Math.random() * 6) + 1
return { return {
@@ -329,7 +325,7 @@ export namespace SendMsgElementConstructor {
stickerId: '33', stickerId: '33',
sourceType: 1, sourceType: 1,
stickerType: 2, stickerType: 2,
resultId: resultId?.toString(), resultId: resultId.toString(),
surpriseId: '', surpriseId: '',
// "randomType": 1, // "randomType": 1,
}, },
@@ -337,7 +333,7 @@ export namespace SendMsgElementConstructor {
} }
// 猜拳(石头剪刀布)表情 // 猜拳(石头剪刀布)表情
export function rps(resultId: number | null): SendFaceElement { export function rps(resultId?: string | number): SendFaceElement {
// 实际测试并不能控制结果 // 实际测试并不能控制结果
if (isNullable(resultId)) resultId = Math.floor(Math.random() * 3) + 1 if (isNullable(resultId)) resultId = Math.floor(Math.random() * 3) + 1
return { return {
@@ -351,7 +347,7 @@ export namespace SendMsgElementConstructor {
stickerId: '34', stickerId: '34',
sourceType: 1, sourceType: 1,
stickerType: 2, stickerType: 2,
resultId: resultId?.toString(), resultId: resultId.toString(),
surpriseId: '', surpriseId: '',
// "randomType": 1, // "randomType": 1,
}, },

View File

@@ -1,4 +1,4 @@
import { Context } from "cordis" import { Context } from 'cordis'
interface ServerRkeyData { interface ServerRkeyData {
group_rkey: string group_rkey: string
@@ -35,7 +35,7 @@ export class RkeyManager {
return now > this.rkeyData.expired_time return now > this.rkeyData.expired_time
} }
async refreshRkey(): Promise<any> { async refreshRkey() {
//刷新rkey //刷新rkey
this.rkeyData = await this.fetchServerRkey() this.rkeyData = await this.fetchServerRkey()
} }

View File

@@ -2,51 +2,50 @@ import type { BrowserWindow } from 'electron'
import { NTClass, NTMethod } from './ntcall' import { NTClass, NTMethod } from './ntcall'
import { log } from '@/common/utils' import { log } from '@/common/utils'
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { Dict } from 'cosmokit'
export const hookApiCallbacks: Record<string, (apiReturn: any) => void> = {} export const hookApiCallbacks: Record<string, (res: any) => void> = {}
export const ReceiveCmdS = { export enum ReceiveCmdS {
RECENT_CONTACT: 'nodeIKernelRecentContactListener/onRecentContactListChangedVer2', RECENT_CONTACT = 'nodeIKernelRecentContactListener/onRecentContactListChangedVer2',
UPDATE_MSG: 'nodeIKernelMsgListener/onMsgInfoListUpdate', UPDATE_MSG = 'nodeIKernelMsgListener/onMsgInfoListUpdate',
UPDATE_ACTIVE_MSG: 'nodeIKernelMsgListener/onActiveMsgInfoUpdate', UPDATE_ACTIVE_MSG = 'nodeIKernelMsgListener/onActiveMsgInfoUpdate',
NEW_MSG: `nodeIKernelMsgListener/onRecvMsg`, NEW_MSG = 'nodeIKernelMsgListener/onRecvMsg',
NEW_ACTIVE_MSG: `nodeIKernelMsgListener/onRecvActiveMsg`, NEW_ACTIVE_MSG = 'nodeIKernelMsgListener/onRecvActiveMsg',
SELF_SEND_MSG: 'nodeIKernelMsgListener/onAddSendMsg', SELF_SEND_MSG = 'nodeIKernelMsgListener/onAddSendMsg',
USER_INFO: 'nodeIKernelProfileListener/onProfileSimpleChanged', USER_INFO = 'nodeIKernelProfileListener/onProfileSimpleChanged',
USER_DETAIL_INFO: 'nodeIKernelProfileListener/onProfileDetailInfoChanged', USER_DETAIL_INFO = 'nodeIKernelProfileListener/onProfileDetailInfoChanged',
GROUPS: 'nodeIKernelGroupListener/onGroupListUpdate', GROUPS = 'nodeIKernelGroupListener/onGroupListUpdate',
GROUPS_STORE: 'onGroupListUpdate', GROUPS_STORE = 'onGroupListUpdate',
GROUP_MEMBER_INFO_UPDATE: 'nodeIKernelGroupListener/onMemberInfoChange', GROUP_MEMBER_INFO_UPDATE = 'nodeIKernelGroupListener/onMemberInfoChange',
FRIENDS: 'onBuddyListChange', FRIENDS = 'onBuddyListChange',
MEDIA_DOWNLOAD_COMPLETE: 'nodeIKernelMsgListener/onRichMediaDownloadComplete', MEDIA_DOWNLOAD_COMPLETE = 'nodeIKernelMsgListener/onRichMediaDownloadComplete',
UNREAD_GROUP_NOTIFY: 'nodeIKernelGroupListener/onGroupNotifiesUnreadCountUpdated', UNREAD_GROUP_NOTIFY = 'nodeIKernelGroupListener/onGroupNotifiesUnreadCountUpdated',
GROUP_NOTIFY: 'nodeIKernelGroupListener/onGroupSingleScreenNotifies', GROUP_NOTIFY = 'nodeIKernelGroupListener/onGroupSingleScreenNotifies',
FRIEND_REQUEST: 'nodeIKernelBuddyListener/onBuddyReqChange', FRIEND_REQUEST = 'nodeIKernelBuddyListener/onBuddyReqChange',
SELF_STATUS: 'nodeIKernelProfileListener/onSelfStatusChanged', SELF_STATUS = 'nodeIKernelProfileListener/onSelfStatusChanged',
CACHE_SCAN_FINISH: 'nodeIKernelStorageCleanListener/onFinishScan', CACHE_SCAN_FINISH = 'nodeIKernelStorageCleanListener/onFinishScan',
MEDIA_UPLOAD_COMPLETE: 'nodeIKernelMsgListener/onRichMediaUploadComplete', MEDIA_UPLOAD_COMPLETE = 'nodeIKernelMsgListener/onRichMediaUploadComplete',
SKEY_UPDATE: 'onSkeyUpdate', SKEY_UPDATE = 'onSkeyUpdate',
} }
export type ReceiveCmd = string type NTReturnData = [
{
interface NTQQApiReturnData<Payload = unknown> extends Array<any> {
0: {
type: 'request' type: 'request'
eventName: NTClass eventName: NTClass
callbackId?: string callbackId?: string
} },
1: { {
cmdName: ReceiveCmd cmdName: ReceiveCmdS
cmdType: 'event' cmdType: 'event'
payload: Payload payload: unknown
}[] }[]
} ]
const logHook = false const logHook = false
const receiveHooks: Array<{ const receiveHooks: Array<{
method: ReceiveCmd[] method: ReceiveCmdS[]
hookFunc: (payload: any) => void | Promise<void> hookFunc: (payload: any) => void | Promise<void>
id: string id: string
}> = [] }> = []
@@ -56,83 +55,61 @@ const callHooks: Array<{
hookFunc: (callParams: unknown[]) => void | Promise<void> hookFunc: (callParams: unknown[]) => void | Promise<void>
}> = [] }> = []
export function hookNTQQApiReceive(window: BrowserWindow) { export function hookNTQQApiReceive(window: BrowserWindow, onlyLog: boolean) {
const originalSend = window.webContents.send window.webContents.send = new Proxy(window.webContents.send, {
const patchSend = (channel: string, ...args: NTQQApiReturnData) => { apply(target, thisArg, args: [channel: string, ...args: NTReturnData]) {
try { try {
const isLogger = args[0]?.eventName?.startsWith('ns-LoggerApi') if (logHook && !args[1]?.eventName?.startsWith('ns-LoggerApi')) {
if (logHook && !isLogger) { log('received ntqq api message', args)
log(`received ntqq api message: ${channel}`, args)
} }
} catch { } } catch { }
if (args?.[1] instanceof Array) { if (!onlyLog) {
for (const receiveData of args?.[1]) { if (args[2] instanceof Array) {
const ntQQApiMethodName = receiveData.cmdName for (const receiveData of args[2]) {
// log(`received ntqq api message: ${channel} ${ntQQApiMethodName}`, JSON.stringify(receiveData)) const ntMethodName = receiveData.cmdName
for (const hook of receiveHooks) { for (const hook of receiveHooks) {
if (hook.method.includes(ntQQApiMethodName)) { if (hook.method.includes(ntMethodName)) {
new Promise((resolve, reject) => { Promise.resolve(hook.hookFunc(receiveData.payload))
try {
hook.hookFunc(receiveData.payload)
} catch (e: any) {
log('hook error', ntQQApiMethodName, e.stack.toString())
}
resolve(undefined)
}).then()
} }
} }
} }
} }
if (args[0]?.callbackId) { if (args[1]?.callbackId) {
// log("hookApiCallback", hookApiCallbacks, args) const callbackId = args[1].callbackId
const callbackId = args[0].callbackId
if (hookApiCallbacks[callbackId]) { if (hookApiCallbacks[callbackId]) {
// log("callback found") Promise.resolve(hookApiCallbacks[callbackId](args[2]))
new Promise((resolve, reject) => {
hookApiCallbacks[callbackId](args[1])
resolve(undefined)
}).then()
delete hookApiCallbacks[callbackId] delete hookApiCallbacks[callbackId]
} }
} }
originalSend.call(window.webContents, channel, ...args)
} }
window.webContents.send = patchSend return target.apply(thisArg, args)
},
})
} }
export function hookNTQQApiCall(window: BrowserWindow) { export function hookNTQQApiCall(window: BrowserWindow, onlyLog: boolean) {
// 监听调用NTQQApi const webContents = window.webContents as Dict
let webContents = window.webContents as any
const ipc_message_proxy = webContents._events['-ipc-message']?.[0] || webContents._events['-ipc-message'] const ipc_message_proxy = webContents._events['-ipc-message']?.[0] || webContents._events['-ipc-message']
const proxyIpcMsg = new Proxy(ipc_message_proxy, { const proxyIpcMsg = new Proxy(ipc_message_proxy, {
apply(target, thisArg, args) { apply(target, thisArg, args) {
// console.log(thisArg, args); const isLogger = args[3]?.[0]?.eventName?.startsWith('ns-LoggerApi')
let isLogger = false
try {
isLogger = args[3][0].eventName.startsWith('ns-LoggerApi')
} catch (e) { }
if (!isLogger) { if (!isLogger) {
try { try {
logHook && log('call NTQQ api', thisArg, args) logHook && log('call NTQQ api', thisArg, args)
} catch (e) { } } catch (e) { }
if (!onlyLog) {
try { try {
const _args: unknown[] = args[3][1] const _args: unknown[] = args[3][1]
const cmdName: NTMethod = _args[0] as NTMethod const cmdName = _args[0] as NTMethod
const callParams = _args.slice(1) const callParams = _args.slice(1)
callHooks.forEach((hook) => { callHooks.forEach((hook) => {
if (hook.method.includes(cmdName)) { if (hook.method.includes(cmdName)) {
new Promise((resolve, reject) => { Promise.resolve(hook.hookFunc(callParams))
try {
hook.hookFunc(callParams)
} catch (e: any) {
log('hook call error', e, _args)
}
resolve(undefined)
}).then()
} }
}) })
} catch (e) { } } catch { }
}
} }
return target.apply(thisArg, args) return target.apply(thisArg, args)
}, },
@@ -143,20 +120,17 @@ export function hookNTQQApiCall(window: BrowserWindow) {
webContents._events['-ipc-message'] = proxyIpcMsg webContents._events['-ipc-message'] = proxyIpcMsg
} }
const ipc_invoke_proxy = webContents._events['-ipc-invoke']?.[0] || webContents._events['-ipc-invoke'] /*const ipc_invoke_proxy = webContents._events['-ipc-invoke']?.[0] || webContents._events['-ipc-invoke']
const proxyIpcInvoke = new Proxy(ipc_invoke_proxy, { const proxyIpcInvoke = new Proxy(ipc_invoke_proxy, {
apply(target, thisArg, args) { apply(target, thisArg, args) {
// console.log(args);
//HOOK_LOG && log('call NTQQ invoke api', thisArg, args) //HOOK_LOG && log('call NTQQ invoke api', thisArg, args)
args[0]['_replyChannel']['sendReply'] = new Proxy(args[0]['_replyChannel']['sendReply'], { args[0]['_replyChannel']['sendReply'] = new Proxy(args[0]['_replyChannel']['sendReply'], {
apply(sendtarget, sendthisArg, sendargs) { apply(sendtarget, sendthisArg, sendargs) {
sendtarget.apply(sendthisArg, sendargs) sendtarget.apply(sendthisArg, sendargs)
}, },
}) })
let ret = target.apply(thisArg, args) const ret = target.apply(thisArg, args)
/*try { //HOOK_LOG && log('call NTQQ invoke api return', ret)
HOOK_LOG && log('call NTQQ invoke api return', ret)
} catch (e) { }*/
return ret return ret
}, },
}) })
@@ -164,11 +138,11 @@ export function hookNTQQApiCall(window: BrowserWindow) {
webContents._events['-ipc-invoke'][0] = proxyIpcInvoke webContents._events['-ipc-invoke'][0] = proxyIpcInvoke
} else { } else {
webContents._events['-ipc-invoke'] = proxyIpcInvoke webContents._events['-ipc-invoke'] = proxyIpcInvoke
} }*/
} }
export function registerReceiveHook<PayloadType>( export function registerReceiveHook<PayloadType>(
method: ReceiveCmd | ReceiveCmd[], method: string | string[],
hookFunc: (payload: PayloadType) => void, hookFunc: (payload: PayloadType) => void,
): string { ): string {
const id = randomUUID() const id = randomUUID()
@@ -176,7 +150,7 @@ export function registerReceiveHook<PayloadType>(
method = [method] method = [method]
} }
receiveHooks.push({ receiveHooks.push({
method, method: method as ReceiveCmdS[],
hookFunc, hookFunc,
id, id,
}) })

View File

@@ -1,240 +0,0 @@
import { Group, GroupListUpdateType, GroupMember, GroupNotify } from '@/ntqqapi/types'
interface IGroupListener {
onGroupListUpdate(updateType: GroupListUpdateType, groupList: Group[]): void
onGroupExtListUpdate(...args: unknown[]): void
onGroupSingleScreenNotifies(doubt: boolean, seq: string, notifies: GroupNotify[]): void
onGroupNotifiesUpdated(dboubt: boolean, notifies: GroupNotify[]): void
onGroupNotifiesUnreadCountUpdated(...args: unknown[]): void
onGroupDetailInfoChange(...args: unknown[]): void
onGroupAllInfoChange(...args: unknown[]): void
onGroupsMsgMaskResult(...args: unknown[]): void
onGroupConfMemberChange(...args: unknown[]): void
onGroupBulletinChange(...args: unknown[]): void
onGetGroupBulletinListResult(...args: unknown[]): void
onMemberListChange(arg: {
sceneId: string,
ids: string[],
infos: Map<string, GroupMember>,
finish: boolean,
hasRobot: boolean
}): void
onMemberInfoChange(groupCode: string, changeType: number, members: Map<string, GroupMember>): void
onSearchMemberChange(...args: unknown[]): void
onGroupBulletinRichMediaDownloadComplete(...args: unknown[]): void
onGroupBulletinRichMediaProgressUpdate(...args: unknown[]): void
onGroupStatisticInfoChange(...args: unknown[]): void
onJoinGroupNotify(...args: unknown[]): void
onShutUpMemberListChanged(...args: unknown[]): void
onGroupBulletinRemindNotify(...args: unknown[]): void
onGroupFirstBulletinNotify(...args: unknown[]): void
onJoinGroupNoVerifyFlag(...args: unknown[]): void
onGroupArkInviteStateResult(...args: unknown[]): void
// 发现于Win 9.9.9 23159
onGroupMemberLevelInfoChange(...args: unknown[]): void
}
export interface NodeIKernelGroupListener extends IGroupListener {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(listener: IGroupListener): NodeIKernelGroupListener
}
export class GroupListener implements IGroupListener {
// 发现于Win 9.9.9 23159
onGroupMemberLevelInfoChange(...args: unknown[]): void {
}
onGetGroupBulletinListResult(...args: unknown[]) {
}
onGroupAllInfoChange(...args: unknown[]) {
}
onGroupBulletinChange(...args: unknown[]) {
}
onGroupBulletinRemindNotify(...args: unknown[]) {
}
onGroupArkInviteStateResult(...args: unknown[]) {
}
onGroupBulletinRichMediaDownloadComplete(...args: unknown[]) {
}
onGroupConfMemberChange(...args: unknown[]) {
}
onGroupDetailInfoChange(...args: unknown[]) {
}
onGroupExtListUpdate(...args: unknown[]) {
}
onGroupFirstBulletinNotify(...args: unknown[]) {
}
onGroupListUpdate(updateType: GroupListUpdateType, groupList: Group[]) {
}
onGroupNotifiesUpdated(dboubt: boolean, notifies: GroupNotify[]) {
}
onGroupBulletinRichMediaProgressUpdate(...args: unknown[]) {
}
onGroupNotifiesUnreadCountUpdated(...args: unknown[]) {
}
onGroupSingleScreenNotifies(doubt: boolean, seq: string, notifies: GroupNotify[]) {
}
onGroupsMsgMaskResult(...args: unknown[]) {
}
onGroupStatisticInfoChange(...args: unknown[]) {
}
onJoinGroupNotify(...args: unknown[]) {
}
onJoinGroupNoVerifyFlag(...args: unknown[]) {
}
onMemberInfoChange(groupCode: string, changeType: number, members: Map<string, GroupMember>) {
}
onMemberListChange(arg: {
sceneId: string,
ids: string[],
infos: Map<string, GroupMember>, // uid -> GroupMember
finish: boolean,
hasRobot: boolean
}) {
}
onSearchMemberChange(...args: unknown[]) {
}
onShutUpMemberListChanged(...args: unknown[]) {
}
}
export class DebugGroupListener implements IGroupListener {
onGroupMemberLevelInfoChange(...args: unknown[]): void {
console.log('onGroupMemberLevelInfoChange:', ...args)
}
onGetGroupBulletinListResult(...args: unknown[]) {
console.log('onGetGroupBulletinListResult:', ...args)
}
onGroupAllInfoChange(...args: unknown[]) {
console.log('onGroupAllInfoChange:', ...args)
}
onGroupBulletinChange(...args: unknown[]) {
console.log('onGroupBulletinChange:', ...args)
}
onGroupBulletinRemindNotify(...args: unknown[]) {
console.log('onGroupBulletinRemindNotify:', ...args)
}
onGroupArkInviteStateResult(...args: unknown[]) {
console.log('onGroupArkInviteStateResult:', ...args)
}
onGroupBulletinRichMediaDownloadComplete(...args: unknown[]) {
console.log('onGroupBulletinRichMediaDownloadComplete:', ...args)
}
onGroupConfMemberChange(...args: unknown[]) {
console.log('onGroupConfMemberChange:', ...args)
}
onGroupDetailInfoChange(...args: unknown[]) {
console.log('onGroupDetailInfoChange:', ...args)
}
onGroupExtListUpdate(...args: unknown[]) {
console.log('onGroupExtListUpdate:', ...args)
}
onGroupFirstBulletinNotify(...args: unknown[]) {
console.log('onGroupFirstBulletinNotify:', ...args)
}
onGroupListUpdate(...args: unknown[]) {
console.log('onGroupListUpdate:', ...args)
}
onGroupNotifiesUpdated(...args: unknown[]) {
console.log('onGroupNotifiesUpdated:', ...args)
}
onGroupBulletinRichMediaProgressUpdate(...args: unknown[]) {
console.log('onGroupBulletinRichMediaProgressUpdate:', ...args)
}
onGroupNotifiesUnreadCountUpdated(...args: unknown[]) {
console.log('onGroupNotifiesUnreadCountUpdated:', ...args)
}
onGroupSingleScreenNotifies(doubt: boolean, seq: string, notifies: GroupNotify[]) {
console.log('onGroupSingleScreenNotifies:')
}
onGroupsMsgMaskResult(...args: unknown[]) {
console.log('onGroupsMsgMaskResult:', ...args)
}
onGroupStatisticInfoChange(...args: unknown[]) {
console.log('onGroupStatisticInfoChange:', ...args)
}
onJoinGroupNotify(...args: unknown[]) {
console.log('onJoinGroupNotify:', ...args)
}
onJoinGroupNoVerifyFlag(...args: unknown[]) {
console.log('onJoinGroupNoVerifyFlag:', ...args)
}
onMemberInfoChange(groupCode: string, changeType: number, members: Map<string, GroupMember>) {
console.log('onMemberInfoChange:', groupCode, changeType, members)
}
onMemberListChange(...args: unknown[]) {
console.log('onMemberListChange:', ...args)
}
onSearchMemberChange(...args: unknown[]) {
console.log('onSearchMemberChange:', ...args)
}
onShutUpMemberListChanged(...args: unknown[]) {
console.log('onShutUpMemberListChanged:', ...args)
}
}

View File

@@ -1,514 +0,0 @@
import { ChatType, RawMessage } from '@/ntqqapi/types'
export interface OnRichMediaDownloadCompleteParams {
fileModelId: string,
msgElementId: string,
msgId: string,
fileId: string,
fileProgress: string, // '0'
fileSpeed: string, // '0'
fileErrCode: string, // '0'
fileErrMsg: string,
fileDownType: number, // 暂时未知
thumbSize: number,
filePath: string,
totalSize: string,
trasferStatus: number,
step: number,
commonFileInfo: unknown | null,
fileSrvErrCode: string,
clientMsg: string,
businessId: number,
userTotalSpacePerDay: unknown | null,
userUsedSpacePerDay: unknown | null
}
export interface onGroupFileInfoUpdateParamType {
retCode: number
retMsg: string
clientWording: string
isEnd: boolean
item: Array<any>
allFileCount: string
nextIndex: string
reqId: string
}
// {
// sessionType: 1,
// chatType: 100,
// peerUid: 'u_PVQ3tl6K78xxxx',
// groupCode: '809079648',
// fromNick: '拾xxxx,
// sig: '0x'
// }
export interface TempOnRecvParams {
sessionType: number,//1
chatType: ChatType,//100
peerUid: string,//uid
groupCode: string,//gc
fromNick: string,//gc name
sig: string,
}
export interface IKernelMsgListener {
onAddSendMsg(msgRecord: RawMessage): void
onBroadcastHelperDownloadComplete(broadcastHelperTransNotifyInfo: unknown): void
onBroadcastHelperProgressUpdate(broadcastHelperTransNotifyInfo: unknown): void
onChannelFreqLimitInfoUpdate(contact: unknown, z: unknown, freqLimitInfo: unknown): void
onContactUnreadCntUpdate(hashMap: unknown): void
onCustomWithdrawConfigUpdate(customWithdrawConfig: unknown): void
onDraftUpdate(contact: unknown, arrayList: unknown, j2: unknown): void
onEmojiDownloadComplete(emojiNotifyInfo: unknown): void
onEmojiResourceUpdate(emojiResourceInfo: unknown): void
onFeedEventUpdate(firstViewDirectMsgNotifyInfo: unknown): void
onFileMsgCome(arrayList: unknown): void
onFirstViewDirectMsgUpdate(firstViewDirectMsgNotifyInfo: unknown): void
onFirstViewGroupGuildMapping(arrayList: unknown): void
onGrabPasswordRedBag(i2: unknown, str: unknown, i3: unknown, recvdOrder: unknown, msgRecord: unknown): void
onGroupFileInfoAdd(groupItem: unknown): void
onGroupFileInfoUpdate(groupFileListResult: onGroupFileInfoUpdateParamType): void
onGroupGuildUpdate(groupGuildNotifyInfo: unknown): void
onGroupTransferInfoAdd(groupItem: unknown): void
onGroupTransferInfoUpdate(groupFileListResult: unknown): void
onGuildInteractiveUpdate(guildInteractiveNotificationItem: unknown): void
onGuildMsgAbFlagChanged(guildMsgAbFlag: unknown): void
onGuildNotificationAbstractUpdate(guildNotificationAbstractInfo: unknown): void
onHitCsRelatedEmojiResult(downloadRelateEmojiResultInfo: unknown): void
onHitEmojiKeywordResult(hitRelatedEmojiWordsResult: unknown): void
onHitRelatedEmojiResult(relatedWordEmojiInfo: unknown): void
onImportOldDbProgressUpdate(importOldDbMsgNotifyInfo: unknown): void
onInputStatusPush(inputStatusInfo: unknown): void
onKickedOffLine(kickedInfo: unknown): void
onLineDev(arrayList: unknown): void
onLogLevelChanged(j2: unknown): void
onMsgAbstractUpdate(arrayList: unknown): void
onMsgBoxChanged(arrayList: unknown): void
onMsgDelete(contact: unknown, arrayList: unknown): void
onMsgEventListUpdate(hashMap: unknown): void
onMsgInfoListAdd(arrayList: unknown): void
onMsgInfoListUpdate(msgList: RawMessage[]): void
onMsgQRCodeStatusChanged(i2: unknown): void
onMsgRecall(i2: unknown, str: unknown, j2: unknown): void
onMsgSecurityNotify(msgRecord: unknown): void
onMsgSettingUpdate(msgSetting: unknown): void
onNtFirstViewMsgSyncEnd(): void
onNtMsgSyncEnd(): void
onNtMsgSyncStart(): void
onReadFeedEventUpdate(firstViewDirectMsgNotifyInfo: unknown): void
onRecvGroupGuildFlag(i2: unknown): void
onRecvMsg(...arrayList: unknown[]): void
onRecvMsgSvrRspTransInfo(j2: unknown, contact: unknown, i2: unknown, i3: unknown, str: unknown, bArr: unknown): void
onRecvOnlineFileMsg(arrayList: unknown): void
onRecvS2CMsg(arrayList: unknown): void
onRecvSysMsg(arrayList: unknown): void
onRecvUDCFlag(i2: unknown): void
onRichMediaDownloadComplete(fileTransNotifyInfo: OnRichMediaDownloadCompleteParams): void
onRichMediaProgerssUpdate(fileTransNotifyInfo: unknown): void
onRichMediaUploadComplete(fileTransNotifyInfo: unknown): void
onSearchGroupFileInfoUpdate(searchGroupFileResult:
{
result: {
retCode: number,
retMsg: string,
clientWording: string
},
syncCookie: string,
totalMatchCount: number,
ownerMatchCount: number,
isEnd: boolean,
reqId: number,
item: Array<{
groupCode: string,
groupName: string,
uploaderUin: string,
uploaderName: string,
matchUin: string,
matchWords: Array<unknown>,
fileNameHits: Array<{
start: number,
end: number
}>,
fileModelId: string,
fileId: string,
fileName: string,
fileSize: string,
busId: number,
uploadTime: number,
modifyTime: number,
deadTime: number,
downloadTimes: number,
localPath: string
}>
}): void
onSendMsgError(j2: unknown, contact: unknown, i2: unknown, str: unknown): void
onSysMsgNotification(i2: unknown, j2: unknown, j3: unknown, arrayList: unknown): void
onTempChatInfoUpdate(tempChatInfo: TempOnRecvParams): void
onUnreadCntAfterFirstView(hashMap: unknown): void
onUnreadCntUpdate(hashMap: unknown): void
onUserChannelTabStatusChanged(z: unknown): void
onUserOnlineStatusChanged(z: unknown): void
onUserTabStatusChanged(arrayList: unknown): void
onlineStatusBigIconDownloadPush(i2: unknown, j2: unknown, str: unknown): void
onlineStatusSmallIconDownloadPush(i2: unknown, j2: unknown, str: unknown): void
// 第一次发现于Linux
onUserSecQualityChanged(...args: unknown[]): void
onMsgWithRichLinkInfoUpdate(...args: unknown[]): void
onRedTouchChanged(...args: unknown[]): void
// 第一次发现于Win 9.9.9 23159
onBroadcastHelperProgerssUpdate(...args: unknown[]): void
}
export interface NodeIKernelMsgListener extends IKernelMsgListener {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(listener: IKernelMsgListener): NodeIKernelMsgListener
}
export class MsgListener implements IKernelMsgListener {
onAddSendMsg(msgRecord: RawMessage) {
}
onBroadcastHelperDownloadComplete(broadcastHelperTransNotifyInfo: unknown) {
}
onBroadcastHelperProgressUpdate(broadcastHelperTransNotifyInfo: unknown) {
}
onChannelFreqLimitInfoUpdate(contact: unknown, z: unknown, freqLimitInfo: unknown) {
}
onContactUnreadCntUpdate(hashMap: unknown) {
}
onCustomWithdrawConfigUpdate(customWithdrawConfig: unknown) {
}
onDraftUpdate(contact: unknown, arrayList: unknown, j2: unknown) {
}
onEmojiDownloadComplete(emojiNotifyInfo: unknown) {
}
onEmojiResourceUpdate(emojiResourceInfo: unknown) {
}
onFeedEventUpdate(firstViewDirectMsgNotifyInfo: unknown) {
}
onFileMsgCome(arrayList: unknown) {
}
onFirstViewDirectMsgUpdate(firstViewDirectMsgNotifyInfo: unknown) {
}
onFirstViewGroupGuildMapping(arrayList: unknown) {
}
onGrabPasswordRedBag(i2: unknown, str: unknown, i3: unknown, recvdOrder: unknown, msgRecord: unknown) {
}
onGroupFileInfoAdd(groupItem: unknown) {
}
onGroupFileInfoUpdate(groupFileListResult: onGroupFileInfoUpdateParamType) {
}
onGroupGuildUpdate(groupGuildNotifyInfo: unknown) {
}
onGroupTransferInfoAdd(groupItem: unknown) {
}
onGroupTransferInfoUpdate(groupFileListResult: unknown) {
}
onGuildInteractiveUpdate(guildInteractiveNotificationItem: unknown) {
}
onGuildMsgAbFlagChanged(guildMsgAbFlag: unknown) {
}
onGuildNotificationAbstractUpdate(guildNotificationAbstractInfo: unknown) {
}
onHitCsRelatedEmojiResult(downloadRelateEmojiResultInfo: unknown) {
}
onHitEmojiKeywordResult(hitRelatedEmojiWordsResult: unknown) {
}
onHitRelatedEmojiResult(relatedWordEmojiInfo: unknown) {
}
onImportOldDbProgressUpdate(importOldDbMsgNotifyInfo: unknown) {
}
onInputStatusPush(inputStatusInfo: unknown) {
}
onKickedOffLine(kickedInfo: unknown) {
}
onLineDev(arrayList: unknown) {
}
onLogLevelChanged(j2: unknown) {
}
onMsgAbstractUpdate(arrayList: unknown) {
}
onMsgBoxChanged(arrayList: unknown) {
}
onMsgDelete(contact: unknown, arrayList: unknown) {
}
onMsgEventListUpdate(hashMap: unknown) {
}
onMsgInfoListAdd(arrayList: unknown) {
}
onMsgInfoListUpdate(msgList: RawMessage[]) {
}
onMsgQRCodeStatusChanged(i2: unknown) {
}
onMsgRecall(i2: unknown, str: unknown, j2: unknown) {
}
onMsgSecurityNotify(msgRecord: unknown) {
}
onMsgSettingUpdate(msgSetting: unknown) {
}
onNtFirstViewMsgSyncEnd() {
}
onNtMsgSyncEnd() {
}
onNtMsgSyncStart() {
}
onReadFeedEventUpdate(firstViewDirectMsgNotifyInfo: unknown) {
}
onRecvGroupGuildFlag(i2: unknown) {
}
onRecvMsg(arrayList: RawMessage[]) {
}
onRecvMsgSvrRspTransInfo(j2: unknown, contact: unknown, i2: unknown, i3: unknown, str: unknown, bArr: unknown) {
}
onRecvOnlineFileMsg(arrayList: unknown) {
}
onRecvS2CMsg(arrayList: unknown) {
}
onRecvSysMsg(arrayList: unknown) {
}
onRecvUDCFlag(i2: unknown) {
}
onRichMediaDownloadComplete(fileTransNotifyInfo: OnRichMediaDownloadCompleteParams) {
}
onRichMediaProgerssUpdate(fileTransNotifyInfo: unknown) {
}
onRichMediaUploadComplete(fileTransNotifyInfo: unknown) {
}
onSearchGroupFileInfoUpdate(searchGroupFileResult: unknown) {
}
onSendMsgError(j2: unknown, contact: unknown, i2: unknown, str: unknown) {
}
onSysMsgNotification(i2: unknown, j2: unknown, j3: unknown, arrayList: unknown) {
}
onTempChatInfoUpdate(tempChatInfo: TempOnRecvParams) {
}
onUnreadCntAfterFirstView(hashMap: unknown) {
}
onUnreadCntUpdate(hashMap: unknown) {
}
onUserChannelTabStatusChanged(z: unknown) {
}
onUserOnlineStatusChanged(z: unknown) {
}
onUserTabStatusChanged(arrayList: unknown) {
}
onlineStatusBigIconDownloadPush(i2: unknown, j2: unknown, str: unknown) {
}
onlineStatusSmallIconDownloadPush(i2: unknown, j2: unknown, str: unknown) {
}
// 第一次发现于Linux
onUserSecQualityChanged(...args: unknown[]) {
}
onMsgWithRichLinkInfoUpdate(...args: unknown[]) {
}
onRedTouchChanged(...args: unknown[]) {
}
// 第一次发现于Win 9.9.9-23159
onBroadcastHelperProgerssUpdate(...args: unknown[]) {
}
}

View File

@@ -1,44 +0,0 @@
import { User, UserDetailInfoListenerArg } from '@/ntqqapi/types'
interface IProfileListener {
onProfileSimpleChanged(...args: unknown[]): void
onUserDetailInfoChanged(arg: UserDetailInfoListenerArg): void
onProfileDetailInfoChanged(profile: User): void
onStatusUpdate(...args: unknown[]): void
onSelfStatusChanged(...args: unknown[]): void
onStrangerRemarkChanged(...args: unknown[]): void
}
export interface NodeIKernelProfileListener extends IProfileListener {
new(listener: IProfileListener): NodeIKernelProfileListener
}
export class ProfileListener implements IProfileListener {
onUserDetailInfoChanged(arg: UserDetailInfoListenerArg): void {
}
onProfileSimpleChanged(...args: unknown[]) {
}
onProfileDetailInfoChanged(profile: User) {
}
onStatusUpdate(...args: unknown[]) {
}
onSelfStatusChanged(...args: unknown[]) {
}
onStrangerRemarkChanged(...args: unknown[]) {
}
}

View File

@@ -1,3 +0,0 @@
export * from './NodeIKernelProfileListener'
export * from './NodeIKernelGroupListener'
export * from './NodeIKernelMsgListener'

View File

@@ -2,7 +2,20 @@ import { ipcMain } from 'electron'
import { hookApiCallbacks, registerReceiveHook, removeReceiveHook } from './hook' import { hookApiCallbacks, registerReceiveHook, removeReceiveHook } from './hook'
import { log } from '../common/utils/legacyLog' import { log } from '../common/utils/legacyLog'
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { GeneralCallResult } from './services' import {
GeneralCallResult,
NodeIKernelBuddyService,
NodeIKernelProfileService,
NodeIKernelGroupService,
NodeIKernelProfileLikeService,
NodeIKernelMsgService,
NodeIKernelMSFService,
NodeIKernelUixConvertService,
NodeIKernelRichMediaService,
NodeIKernelTicketService,
NodeIKernelTipOffService,
NodeIKernelSearchService,
} from './services'
export enum NTClass { export enum NTClass {
NT_API = 'ns-ntApi', NT_API = 'ns-ntApi',
@@ -19,161 +32,151 @@ export enum NTClass {
} }
export enum NTMethod { export enum NTMethod {
RECENT_CONTACT = 'nodeIKernelRecentContactService/fetchAndSubscribeABatchOfRecentContact',
ACTIVE_CHAT_PREVIEW = 'nodeIKernelMsgService/getAioFirstViewLatestMsgsAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回最新预览消息 ACTIVE_CHAT_PREVIEW = 'nodeIKernelMsgService/getAioFirstViewLatestMsgsAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回最新预览消息
ACTIVE_CHAT_HISTORY = 'nodeIKernelMsgService/getMsgsIncludeSelfAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回历史消息 ACTIVE_CHAT_HISTORY = 'nodeIKernelMsgService/getMsgsIncludeSelfAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回历史消息
HISTORY_MSG = 'nodeIKernelMsgService/getMsgsIncludeSelf', HISTORY_MSG = 'nodeIKernelMsgService/getMsgsIncludeSelf',
GET_MULTI_MSG = 'nodeIKernelMsgService/getMultiMsg', GET_MULTI_MSG = 'nodeIKernelMsgService/getMultiMsg',
DELETE_ACTIVE_CHAT = 'nodeIKernelMsgService/deleteActiveChatByUid', DELETE_ACTIVE_CHAT = 'nodeIKernelMsgService/deleteActiveChatByUid',
ENTER_OR_EXIT_AIO = 'nodeIKernelMsgService/enterOrExitAio', MEDIA_FILE_PATH = 'nodeIKernelMsgService/getRichMediaFilePathForGuild',
RECALL_MSG = 'nodeIKernelMsgService/recallMsg',
EMOJI_LIKE = 'nodeIKernelMsgService/setMsgEmojiLikes',
FORWARD_MSG = 'nodeIKernelMsgService/forwardMsgWithComment',
LIKE_FRIEND = 'nodeIKernelProfileLikeService/setBuddyProfileLike',
SELF_INFO = 'fetchAuthData', SELF_INFO = 'fetchAuthData',
FRIENDS = 'nodeIKernelBuddyService/getBuddyList',
GROUPS = 'nodeIKernelGroupService/getGroupList',
GROUP_MEMBER_SCENE = 'nodeIKernelGroupService/createMemberListScene',
GROUP_MEMBERS = 'nodeIKernelGroupService/getNextMemberList',
GROUP_MEMBERS_INFO = 'nodeIKernelGroupService/getMemberInfo',
USER_INFO = 'nodeIKernelProfileService/getUserSimpleInfo',
USER_DETAIL_INFO = 'nodeIKernelProfileService/getUserDetailInfo',
USER_DETAIL_INFO_WITH_BIZ_INFO = 'nodeIKernelProfileService/getUserDetailInfoWithBizInfo',
FILE_TYPE = 'getFileType', FILE_TYPE = 'getFileType',
FILE_MD5 = 'getFileMd5', FILE_MD5 = 'getFileMd5',
FILE_COPY = 'copyFile', FILE_COPY = 'copyFile',
IMAGE_SIZE = 'getImageSizeFromPath', IMAGE_SIZE = 'getImageSizeFromPath',
FILE_SIZE = 'getFileSize', FILE_SIZE = 'getFileSize',
MEDIA_FILE_PATH = 'nodeIKernelMsgService/getRichMediaFilePathForGuild', CACHE_PATH_HOT_UPDATE = 'getHotUpdateCachePath',
CACHE_PATH_DESKTOP_TEMP = 'getDesktopTmpPath',
CACHE_PATH_SESSION = 'getCleanableAppSessionPathList',
OPEN_EXTRA_WINDOW = 'openExternalWindow',
RECALL_MSG = 'nodeIKernelMsgService/recallMsg', GROUP_MEMBER_SCENE = 'nodeIKernelGroupService/createMemberListScene',
SEND_MSG = 'nodeIKernelMsgService/sendMsg', GROUP_MEMBERS = 'nodeIKernelGroupService/getNextMemberList',
EMOJI_LIKE = 'nodeIKernelMsgService/setMsgEmojiLikes',
DOWNLOAD_MEDIA = 'nodeIKernelMsgService/downloadRichMedia',
FORWARD_MSG = 'nodeIKernelMsgService/forwardMsgWithComment',
MULTI_FORWARD_MSG = 'nodeIKernelMsgService/multiForwardMsgWithComment', // 合并转发
GET_GROUP_NOTICE = 'nodeIKernelGroupService/getSingleScreenNotifies',
HANDLE_GROUP_REQUEST = 'nodeIKernelGroupService/operateSysNotify', HANDLE_GROUP_REQUEST = 'nodeIKernelGroupService/operateSysNotify',
QUIT_GROUP = 'nodeIKernelGroupService/quitGroup', QUIT_GROUP = 'nodeIKernelGroupService/quitGroup',
GROUP_AT_ALL_REMAIN_COUNT = 'nodeIKernelGroupService/getGroupRemainAtTimes', GROUP_AT_ALL_REMAIN_COUNT = 'nodeIKernelGroupService/getGroupRemainAtTimes',
HANDLE_FRIEND_REQUEST = 'nodeIKernelBuddyService/approvalFriendRequest',
KICK_MEMBER = 'nodeIKernelGroupService/kickMember', KICK_MEMBER = 'nodeIKernelGroupService/kickMember',
MUTE_MEMBER = 'nodeIKernelGroupService/setMemberShutUp', MUTE_MEMBER = 'nodeIKernelGroupService/setMemberShutUp',
MUTE_GROUP = 'nodeIKernelGroupService/setGroupShutUp', MUTE_GROUP = 'nodeIKernelGroupService/setGroupShutUp',
SET_MEMBER_CARD = 'nodeIKernelGroupService/modifyMemberCardName', SET_MEMBER_CARD = 'nodeIKernelGroupService/modifyMemberCardName',
SET_MEMBER_ROLE = 'nodeIKernelGroupService/modifyMemberRole', SET_MEMBER_ROLE = 'nodeIKernelGroupService/modifyMemberRole',
PUBLISH_GROUP_BULLETIN = 'nodeIKernelGroupService/publishGroupBulletinBulletin',
SET_GROUP_NAME = 'nodeIKernelGroupService/modifyGroupName', SET_GROUP_NAME = 'nodeIKernelGroupService/modifyGroupName',
SET_GROUP_TITLE = 'nodeIKernelGroupService/modifyMemberSpecialTitle',
ACTIVATE_MEMBER_LIST_CHANGE = 'nodeIKernelGroupListener/onMemberListChange', HANDLE_FRIEND_REQUEST = 'nodeIKernelBuddyService/approvalFriendRequest',
ACTIVATE_MEMBER_INFO_CHANGE = 'nodeIKernelGroupListener/onMemberInfoChange',
GET_MSG_BOX_INFO = 'nodeIKernelMsgService/getABatchOfContactMsgBoxInfo',
GET_GROUP_ALL_INFO = 'nodeIKernelGroupService/getGroupAllInfo',
CACHE_SET_SILENCE = 'nodeIKernelStorageCleanService/setSilentScan', CACHE_SET_SILENCE = 'nodeIKernelStorageCleanService/setSilentScan',
CACHE_ADD_SCANNED_PATH = 'nodeIKernelStorageCleanService/addCacheScanedPaths', CACHE_ADD_SCANNED_PATH = 'nodeIKernelStorageCleanService/addCacheScanedPaths',
CACHE_PATH_HOT_UPDATE = 'getHotUpdateCachePath',
CACHE_PATH_DESKTOP_TEMP = 'getDesktopTmpPath',
CACHE_PATH_SESSION = 'getCleanableAppSessionPathList',
CACHE_SCAN = 'nodeIKernelStorageCleanService/scanCache', CACHE_SCAN = 'nodeIKernelStorageCleanService/scanCache',
CACHE_CLEAR = 'nodeIKernelStorageCleanService/clearCacheDataByKeys', CACHE_CLEAR = 'nodeIKernelStorageCleanService/clearCacheDataByKeys',
CACHE_CHAT_GET = 'nodeIKernelStorageCleanService/getChatCacheInfo', CACHE_CHAT_GET = 'nodeIKernelStorageCleanService/getChatCacheInfo',
CACHE_FILE_GET = 'nodeIKernelStorageCleanService/getFileCacheInfo', CACHE_FILE_GET = 'nodeIKernelStorageCleanService/getFileCacheInfo',
CACHE_CHAT_CLEAR = 'nodeIKernelStorageCleanService/clearChatCacheInfo', CACHE_CHAT_CLEAR = 'nodeIKernelStorageCleanService/clearChatCacheInfo',
OPEN_EXTRA_WINDOW = 'openExternalWindow',
SET_QQ_AVATAR = 'nodeIKernelProfileService/setHeader',
} }
export enum NTChannel { export enum NTChannel {
IPC_UP_1 = 'IPC_UP_1',
IPC_UP_2 = 'IPC_UP_2', IPC_UP_2 = 'IPC_UP_2',
IPC_UP_3 = 'IPC_UP_3', IPC_UP_3 = 'IPC_UP_3',
IPC_UP_1 = 'IPC_UP_1', IPC_UP_4 = 'IPC_UP_4'
} }
interface InvokeParams<ReturnType> { interface NTService {
methodName: string nodeIKernelBuddyService: NodeIKernelBuddyService
nodeIKernelProfileService: NodeIKernelProfileService
nodeIKernelGroupService: NodeIKernelGroupService
nodeIKernelProfileLikeService: NodeIKernelProfileLikeService
nodeIKernelMsgService: NodeIKernelMsgService
nodeIKernelMSFService: NodeIKernelMSFService
nodeIKernelUixConvertService: NodeIKernelUixConvertService
nodeIKernelRichMediaService: NodeIKernelRichMediaService
nodeIKernelTicketService: NodeIKernelTicketService
nodeIKernelTipOffService: NodeIKernelTipOffService
nodeIKernelSearchService: NodeIKernelSearchService
}
interface InvokeOptions<ReturnType> {
className?: NTClass className?: NTClass
channel?: NTChannel channel?: NTChannel
classNameIsRegister?: boolean classNameIsRegister?: boolean
args?: unknown[]
cbCmd?: string | string[] cbCmd?: string | string[]
cmdCB?: (payload: ReturnType) => boolean cmdCB?: (payload: ReturnType, result: unknown) => boolean
afterFirstCmd?: boolean // 是否在methodName调用完之后再去hook cbCmd afterFirstCmd?: boolean // 是否在methodName调用完之后再去hook cbCmd
timeout?: number timeout?: number
} }
export function invoke<ReturnType>(params: InvokeParams<ReturnType>) { export function invoke<
const className = params.className ?? NTClass.NT_API R extends Awaited<ReturnType<Extract<NTService[S][M], (...args: any) => unknown>>>,
const channel = params.channel ?? NTChannel.IPC_UP_2 S extends keyof NTService = any,
const timeout = params.timeout ?? 5000 M extends keyof NTService[S] & string = any
const afterFirstCmd = params.afterFirstCmd ?? true >(method: Extract<unknown, `${S}/${M}`> | string, args: unknown[], options: InvokeOptions<R> = {}) {
const uuid = randomUUID() const className = options.className ?? NTClass.NT_API
const channel = options.channel ?? NTChannel.IPC_UP_2
const timeout = options.timeout ?? 5000
const afterFirstCmd = options.afterFirstCmd ?? true
let eventName = className + '-' + channel[channel.length - 1] let eventName = className + '-' + channel[channel.length - 1]
if (params.classNameIsRegister) { if (options.classNameIsRegister) {
eventName += '-register' eventName += '-register'
} }
const apiArgs = [params.methodName, ...(params.args ?? [])] return new Promise<R>((resolve, reject) => {
//log('callNTQQApi', channel, eventName, apiArgs, uuid) const apiArgs = [method, ...args]
return new Promise((resolve: (data: ReturnType) => void, reject) => { const callbackId = randomUUID()
let success = false const timeoutId = setTimeout(() => {
if (!params.cbCmd) { log(`ntqq api timeout ${channel}, ${eventName}, ${method}`, apiArgs)
reject(`ntqq api timeout ${channel}, ${eventName}, ${method}, ${apiArgs}`)
}, timeout)
if (!options.cbCmd) {
// QQ后端会返回结果并且可以根据uuid识别 // QQ后端会返回结果并且可以根据uuid识别
hookApiCallbacks[uuid] = (r: ReturnType) => { hookApiCallbacks[callbackId] = res => {
success = true clearTimeout(timeoutId)
resolve(r) resolve(res)
} }
} }
else { else {
let result: unknown
// 这里的callback比较特殊QQ后端先返回是否调用成功再返回一条结果数据 // 这里的callback比较特殊QQ后端先返回是否调用成功再返回一条结果数据
const secondCallback = () => { const secondCallback = () => {
const hookId = registerReceiveHook<ReturnType>(params.cbCmd!, (payload) => { const hookId = registerReceiveHook<R>(options.cbCmd!, (payload) => {
// log(methodName, "second callback", cbCmd, payload, cmdCB); if (options.cmdCB) {
if (!!params.cmdCB) { if (options.cmdCB(payload, result)) {
if (params.cmdCB(payload)) {
removeReceiveHook(hookId) removeReceiveHook(hookId)
success = true clearTimeout(timeoutId)
resolve(payload) resolve(payload)
} }
} }
else { else {
removeReceiveHook(hookId) removeReceiveHook(hookId)
success = true clearTimeout(timeoutId)
resolve(payload) resolve(payload)
} }
}) })
} }
!afterFirstCmd && secondCallback() !afterFirstCmd && secondCallback()
hookApiCallbacks[uuid] = (result: GeneralCallResult) => { hookApiCallbacks[callbackId] = (res: GeneralCallResult) => {
if (result?.result === 0 || result === undefined) { result = res
//log(`${params.methodName} callback`, result) if (res?.result === 0 || ['undefined', 'number'].includes(typeof res)) {
afterFirstCmd && secondCallback() afterFirstCmd && secondCallback()
} }
else { else {
log('ntqq api call failed', result) log('ntqq api call failed,', method, res)
reject(`ntqq api call failed, ${result.errMsg}`) clearTimeout(timeoutId)
reject(`ntqq api call failed, ${method}, ${res.errMsg}`)
} }
} }
} }
setTimeout(() => {
if (!success) {
log(`ntqq api timeout ${channel}, ${eventName}, ${params.methodName}`, apiArgs)
reject(`ntqq api timeout ${channel}, ${eventName}, ${params.methodName}, ${apiArgs}`)
}
}, timeout)
ipcMain.emit( ipcMain.emit(
channel, channel,
{ {
sender: { sender: {
send: (..._args: unknown[]) => { send: () => {
}, },
}, },
}, },
{ type: 'request', callbackId: uuid, eventName }, { type: 'request', callbackId, eventName },
apiArgs, apiArgs,
) )
}) })

469
src/ntqqapi/proto/compiled.d.ts vendored Normal file
View File

@@ -0,0 +1,469 @@
import * as $protobuf from "protobufjs";
import Long = require("long");
/** Namespace SysMsg. */
export namespace SysMsg {
/** Properties of a SystemMessage. */
interface ISystemMessage {
/** SystemMessage header */
header?: (SysMsg.ISystemMessageHeader[]|null);
/** SystemMessage msgSpec */
msgSpec?: (SysMsg.ISystemMessageMsgSpec[]|null);
/** SystemMessage bodyWrapper */
bodyWrapper?: (SysMsg.ISystemMessageBodyWrapper|null);
}
/** Represents a SystemMessage. */
class SystemMessage implements ISystemMessage {
/**
* Constructs a new SystemMessage.
* @param [properties] Properties to set
*/
constructor(properties?: SysMsg.ISystemMessage);
/** SystemMessage header. */
public header: SysMsg.ISystemMessageHeader[];
/** SystemMessage msgSpec. */
public msgSpec: SysMsg.ISystemMessageMsgSpec[];
/** SystemMessage bodyWrapper. */
public bodyWrapper?: (SysMsg.ISystemMessageBodyWrapper|null);
/**
* Decodes a SystemMessage message from the specified reader or buffer.
* @param reader Reader or buffer to decode from
* @param [length] Message length if known beforehand
* @returns SystemMessage
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): SysMsg.SystemMessage;
/**
* Decodes a SystemMessage message from the specified reader or buffer, length delimited.
* @param reader Reader or buffer to decode from
* @returns SystemMessage
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): SysMsg.SystemMessage;
/**
* Gets the default type url for SystemMessage
* @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
* @returns The default type url
*/
public static getTypeUrl(typeUrlPrefix?: string): string;
}
/** Properties of a SystemMessageHeader. */
interface ISystemMessageHeader {
/** SystemMessageHeader peerNumber */
peerNumber?: (number|null);
/** SystemMessageHeader peerString */
peerString?: (string|null);
/** SystemMessageHeader uin */
uin?: (number|null);
/** SystemMessageHeader uid */
uid?: (string|null);
}
/** Represents a SystemMessageHeader. */
class SystemMessageHeader implements ISystemMessageHeader {
/**
* Constructs a new SystemMessageHeader.
* @param [properties] Properties to set
*/
constructor(properties?: SysMsg.ISystemMessageHeader);
/** SystemMessageHeader peerNumber. */
public peerNumber: number;
/** SystemMessageHeader peerString. */
public peerString: string;
/** SystemMessageHeader uin. */
public uin: number;
/** SystemMessageHeader uid. */
public uid?: (string|null);
/**
* Decodes a SystemMessageHeader message from the specified reader or buffer.
* @param reader Reader or buffer to decode from
* @param [length] Message length if known beforehand
* @returns SystemMessageHeader
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): SysMsg.SystemMessageHeader;
/**
* Decodes a SystemMessageHeader message from the specified reader or buffer, length delimited.
* @param reader Reader or buffer to decode from
* @returns SystemMessageHeader
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): SysMsg.SystemMessageHeader;
/**
* Gets the default type url for SystemMessageHeader
* @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
* @returns The default type url
*/
public static getTypeUrl(typeUrlPrefix?: string): string;
}
/** Properties of a SystemMessageMsgSpec. */
interface ISystemMessageMsgSpec {
/** SystemMessageMsgSpec msgType */
msgType?: (number|null);
/** SystemMessageMsgSpec subType */
subType?: (number|null);
/** SystemMessageMsgSpec subSubType */
subSubType?: (number|null);
/** SystemMessageMsgSpec msgSeq */
msgSeq?: (number|null);
/** SystemMessageMsgSpec time */
time?: (number|null);
/** SystemMessageMsgSpec other */
other?: (number|null);
}
/** Represents a SystemMessageMsgSpec. */
class SystemMessageMsgSpec implements ISystemMessageMsgSpec {
/**
* Constructs a new SystemMessageMsgSpec.
* @param [properties] Properties to set
*/
constructor(properties?: SysMsg.ISystemMessageMsgSpec);
/** SystemMessageMsgSpec msgType. */
public msgType: number;
/** SystemMessageMsgSpec subType. */
public subType: number;
/** SystemMessageMsgSpec subSubType. */
public subSubType: number;
/** SystemMessageMsgSpec msgSeq. */
public msgSeq: number;
/** SystemMessageMsgSpec time. */
public time: number;
/** SystemMessageMsgSpec other. */
public other: number;
/**
* Decodes a SystemMessageMsgSpec message from the specified reader or buffer.
* @param reader Reader or buffer to decode from
* @param [length] Message length if known beforehand
* @returns SystemMessageMsgSpec
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): SysMsg.SystemMessageMsgSpec;
/**
* Decodes a SystemMessageMsgSpec message from the specified reader or buffer, length delimited.
* @param reader Reader or buffer to decode from
* @returns SystemMessageMsgSpec
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): SysMsg.SystemMessageMsgSpec;
/**
* Gets the default type url for SystemMessageMsgSpec
* @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
* @returns The default type url
*/
public static getTypeUrl(typeUrlPrefix?: string): string;
}
/** Properties of a SystemMessageBodyWrapper. */
interface ISystemMessageBodyWrapper {
/** SystemMessageBodyWrapper body */
body?: (Uint8Array|null);
}
/** Represents a SystemMessageBodyWrapper. */
class SystemMessageBodyWrapper implements ISystemMessageBodyWrapper {
/**
* Constructs a new SystemMessageBodyWrapper.
* @param [properties] Properties to set
*/
constructor(properties?: SysMsg.ISystemMessageBodyWrapper);
/** SystemMessageBodyWrapper body. */
public body: Uint8Array;
/**
* Decodes a SystemMessageBodyWrapper message from the specified reader or buffer.
* @param reader Reader or buffer to decode from
* @param [length] Message length if known beforehand
* @returns SystemMessageBodyWrapper
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): SysMsg.SystemMessageBodyWrapper;
/**
* Decodes a SystemMessageBodyWrapper message from the specified reader or buffer, length delimited.
* @param reader Reader or buffer to decode from
* @returns SystemMessageBodyWrapper
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): SysMsg.SystemMessageBodyWrapper;
/**
* Gets the default type url for SystemMessageBodyWrapper
* @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
* @returns The default type url
*/
public static getTypeUrl(typeUrlPrefix?: string): string;
}
/** Properties of a LikeDetail. */
interface ILikeDetail {
/** LikeDetail txt */
txt?: (string|null);
/** LikeDetail uin */
uin?: (number|null);
/** LikeDetail nickname */
nickname?: (string|null);
}
/** Represents a LikeDetail. */
class LikeDetail implements ILikeDetail {
/**
* Constructs a new LikeDetail.
* @param [properties] Properties to set
*/
constructor(properties?: SysMsg.ILikeDetail);
/** LikeDetail txt. */
public txt: string;
/** LikeDetail uin. */
public uin: number;
/** LikeDetail nickname. */
public nickname: string;
/**
* Decodes a LikeDetail message from the specified reader or buffer.
* @param reader Reader or buffer to decode from
* @param [length] Message length if known beforehand
* @returns LikeDetail
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): SysMsg.LikeDetail;
/**
* Decodes a LikeDetail message from the specified reader or buffer, length delimited.
* @param reader Reader or buffer to decode from
* @returns LikeDetail
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): SysMsg.LikeDetail;
/**
* Gets the default type url for LikeDetail
* @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
* @returns The default type url
*/
public static getTypeUrl(typeUrlPrefix?: string): string;
}
/** Properties of a LikeMsg. */
interface ILikeMsg {
/** LikeMsg count */
count?: (number|null);
/** LikeMsg time */
time?: (number|null);
/** LikeMsg detail */
detail?: (SysMsg.ILikeDetail|null);
}
/** Represents a LikeMsg. */
class LikeMsg implements ILikeMsg {
/**
* Constructs a new LikeMsg.
* @param [properties] Properties to set
*/
constructor(properties?: SysMsg.ILikeMsg);
/** LikeMsg count. */
public count: number;
/** LikeMsg time. */
public time: number;
/** LikeMsg detail. */
public detail?: (SysMsg.ILikeDetail|null);
/**
* Decodes a LikeMsg message from the specified reader or buffer.
* @param reader Reader or buffer to decode from
* @param [length] Message length if known beforehand
* @returns LikeMsg
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): SysMsg.LikeMsg;
/**
* Decodes a LikeMsg message from the specified reader or buffer, length delimited.
* @param reader Reader or buffer to decode from
* @returns LikeMsg
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): SysMsg.LikeMsg;
/**
* Gets the default type url for LikeMsg
* @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
* @returns The default type url
*/
public static getTypeUrl(typeUrlPrefix?: string): string;
}
/** Properties of a ProfileLikeSubTip. */
interface IProfileLikeSubTip {
/** ProfileLikeSubTip msg */
msg?: (SysMsg.ILikeMsg|null);
}
/** Represents a ProfileLikeSubTip. */
class ProfileLikeSubTip implements IProfileLikeSubTip {
/**
* Constructs a new ProfileLikeSubTip.
* @param [properties] Properties to set
*/
constructor(properties?: SysMsg.IProfileLikeSubTip);
/** ProfileLikeSubTip msg. */
public msg?: (SysMsg.ILikeMsg|null);
/**
* Decodes a ProfileLikeSubTip message from the specified reader or buffer.
* @param reader Reader or buffer to decode from
* @param [length] Message length if known beforehand
* @returns ProfileLikeSubTip
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): SysMsg.ProfileLikeSubTip;
/**
* Decodes a ProfileLikeSubTip message from the specified reader or buffer, length delimited.
* @param reader Reader or buffer to decode from
* @returns ProfileLikeSubTip
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): SysMsg.ProfileLikeSubTip;
/**
* Gets the default type url for ProfileLikeSubTip
* @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
* @returns The default type url
*/
public static getTypeUrl(typeUrlPrefix?: string): string;
}
/** Properties of a ProfileLikeTip. */
interface IProfileLikeTip {
/** ProfileLikeTip msgType */
msgType?: (number|null);
/** ProfileLikeTip subType */
subType?: (number|null);
/** ProfileLikeTip content */
content?: (SysMsg.IProfileLikeSubTip|null);
}
/** Represents a ProfileLikeTip. */
class ProfileLikeTip implements IProfileLikeTip {
/**
* Constructs a new ProfileLikeTip.
* @param [properties] Properties to set
*/
constructor(properties?: SysMsg.IProfileLikeTip);
/** ProfileLikeTip msgType. */
public msgType: number;
/** ProfileLikeTip subType. */
public subType: number;
/** ProfileLikeTip content. */
public content?: (SysMsg.IProfileLikeSubTip|null);
/**
* Decodes a ProfileLikeTip message from the specified reader or buffer.
* @param reader Reader or buffer to decode from
* @param [length] Message length if known beforehand
* @returns ProfileLikeTip
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): SysMsg.ProfileLikeTip;
/**
* Decodes a ProfileLikeTip message from the specified reader or buffer, length delimited.
* @param reader Reader or buffer to decode from
* @returns ProfileLikeTip
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): SysMsg.ProfileLikeTip;
/**
* Gets the default type url for ProfileLikeTip
* @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
* @returns The default type url
*/
public static getTypeUrl(typeUrlPrefix?: string): string;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,24 @@
syntax = "proto3";
package SysMsg;
message LikeDetail {
string txt = 1;
uint32 uin = 3;
string nickname = 5;
}
message LikeMsg {
uint32 count = 1;
uint32 time = 2;
LikeDetail detail = 3;
}
message ProfileLikeSubTip {
LikeMsg msg = 14;
}
message ProfileLikeTip {
uint32 msgType = 1;
uint32 subType = 2;
ProfileLikeSubTip content = 203;
}

View File

@@ -0,0 +1,30 @@
syntax = "proto3";
package SysMsg;
message SystemMessage {
repeated SystemMessageHeader header = 1;
repeated SystemMessageMsgSpec msgSpec = 2;
SystemMessageBodyWrapper bodyWrapper = 3;
}
message SystemMessageHeader {
uint32 peerNumber = 1;
string peerString = 2;
uint32 uin = 5;
optional string uid = 6;
}
message SystemMessageMsgSpec {
uint32 msgType = 1;
uint32 subType = 2;
uint32 subSubType = 3;
uint32 msgSeq = 5;
uint32 time = 6;
//uint64 msgId = 12;
uint32 other = 13;
}
message SystemMessageBodyWrapper {
bytes body = 2;
// Find the first [08], or ignore the first 7 bytes?
}

View File

@@ -29,7 +29,7 @@ export interface NodeIKernelBuddyService {
buddyUids: Array<string>//Uids buddyUids: Array<string>//Uids
}>> }>>
addKernelBuddyListener(listener: any): number addKernelBuddyListener(listener: unknown): number
getAllBuddyCount(): number getAllBuddyCount(): number
@@ -119,7 +119,7 @@ export interface NodeIKernelBuddyService {
reportDoubtBuddyReqUnread(): void reportDoubtBuddyReqUnread(): void
getBuddyRecommendContactArkJson(uid: string, phoneNumber: string): Promise<unknown> getBuddyRecommendContactArkJson(uid: string, phoneNumber: string): Promise<GeneralCallResult & { arkMsg: string }>
isNull(): boolean isNull(): boolean
} }

View File

@@ -1,14 +1,12 @@
import { NodeIKernelGroupListener } from '@/ntqqapi/listeners'
import { import {
GroupExtParam, GroupExtParam,
GroupMember, GroupMember,
GroupMemberRole, GroupMemberRole,
GroupNotifyTypes, GroupNotifyType,
GroupRequestOperateTypes, GroupRequestOperateTypes,
} from '@/ntqqapi/types' } from '@/ntqqapi/types'
import { GeneralCallResult } from './common' import { GeneralCallResult } from './common'
import { Dict } from 'cosmokit'
//高版本的接口不应该随意使用 使用应该严格进行pr审核 同时部分ipc中未出现的接口不要过于依赖 应该做好数据兜底
export interface NodeIKernelGroupService { export interface NodeIKernelGroupService {
getMemberCommonInfo(Req: { getMemberCommonInfo(Req: {
@@ -29,8 +27,10 @@ export interface NodeIKernelGroupService {
onlineFlag: string, onlineFlag: string,
realSpecialTitleFlag: number realSpecialTitleFlag: number
}): Promise<unknown> }): Promise<unknown>
//26702 //26702
getGroupMemberLevelInfo(groupCode: string): Promise<unknown> getGroupMemberLevelInfo(groupCode: string): Promise<unknown>
//26702 //26702
getGroupHonorList(groupCodes: Array<string>): unknown getGroupHonorList(groupCodes: Array<string>): unknown
@@ -45,6 +45,7 @@ export interface NodeIKernelGroupService {
errMsg: string, errMsg: string,
uids: Map<string, string> uids: Map<string, string>
}> }>
//26702(其实更早 但是我不知道) //26702(其实更早 但是我不知道)
checkGroupMemberCache(arrayList: Array<string>): Promise<unknown> checkGroupMemberCache(arrayList: Array<string>): Promise<unknown>
@@ -70,12 +71,16 @@ export interface NodeIKernelGroupService {
brief: string brief: string
} }
}): Promise<unknown> }): Promise<unknown>
//26702(其实更早 但是我不知道) //26702(其实更早 但是我不知道)
isEssenceMsg(Req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown> isEssenceMsg(Req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown>
//26702(其实更早 但是我不知道) //26702(其实更早 但是我不知道)
queryCachedEssenceMsg(Req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown> queryCachedEssenceMsg(Req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown>
//26702(其实更早 但是我不知道) //26702(其实更早 但是我不知道)
fetchGroupEssenceList(Req: { groupCode: string, pageStart: number, pageLimit: number }, Arg: unknown): Promise<unknown> fetchGroupEssenceList(Req: { groupCode: string, pageStart: number, pageLimit: number }, Arg: unknown): Promise<unknown>
//26702 //26702
getAllMemberList(groupCode: string, forceFetch: boolean): Promise<{ getAllMemberList(groupCode: string, forceFetch: boolean): Promise<{
errCode: number, errCode: number,
@@ -85,7 +90,7 @@ export interface NodeIKernelGroupService {
uid: string, uid: string,
index: number//0 index: number//0
}>, }>,
infos: {}, infos: Dict,
finish: true, finish: true,
hasRobot: false hasRobot: false
} }
@@ -93,17 +98,26 @@ export interface NodeIKernelGroupService {
setHeader(uid: string, path: string): unknown setHeader(uid: string, path: string): unknown
addKernelGroupListener(listener: NodeIKernelGroupListener): number addKernelGroupListener(listener: unknown): number
removeKernelGroupListener(listenerId: unknown): void removeKernelGroupListener(listenerId: unknown): void
createMemberListScene(groupCode: string, scene: string): string createMemberListScene(groupCode: string, scene: string): string
destroyMemberListScene(SceneId: string): void destroyMemberListScene(sceneId: string): void
//About Arg (a) name: lastId 根据手Q来看为object {index:?(number),uid:string}
getNextMemberList(sceneId: string, a: undefined, num: number): Promise<{ getNextMemberList(sceneId: string, a: undefined, num: number): Promise<{
errCode: number, errMsg: string, errCode: number
result: { ids: string[], infos: Map<string, GroupMember>, finish: boolean, hasRobot: boolean } errMsg: string
result: {
ids: {
uid: string
index: number
}[]
infos: Map<string, GroupMember>
finish: boolean
hasRobot: boolean
}
}> }>
getPrevMemberList(): unknown getPrevMemberList(): unknown
@@ -171,7 +185,7 @@ export interface NodeIKernelGroupService {
clearGroupNotifies(groupCode: string): void clearGroupNotifies(groupCode: string): void
getGroupNotifiesUnreadCount(unknown: Boolean): Promise<GeneralCallResult> getGroupNotifiesUnreadCount(unknown: boolean): Promise<GeneralCallResult>
clearGroupNotifiesUnreadCount(groupCode: string): void clearGroupNotifiesUnreadCount(groupCode: string): void
@@ -181,7 +195,7 @@ export interface NodeIKernelGroupService {
operateType: GroupRequestOperateTypes, // 2 拒绝 operateType: GroupRequestOperateTypes, // 2 拒绝
targetMsg: { targetMsg: {
seq: string, // 通知序列号 seq: string, // 通知序列号
type: GroupNotifyTypes, type: GroupNotifyType,
groupCode: string, groupCode: string,
postscript: string postscript: string
} }
@@ -193,15 +207,16 @@ export interface NodeIKernelGroupService {
deleteGroupBulletin(groupCode: string, seq: string): void deleteGroupBulletin(groupCode: string, seq: string): void
publishGroupBulletin(groupCode: string, pskey: string, data: any): Promise<GeneralCallResult> publishGroupBulletin(groupCode: string, pskey: string, data: unknown): Promise<GeneralCallResult>
publishInstructionForNewcomers(groupCode: string, arg: unknown): void publishInstructionForNewcomers(groupCode: string, arg: unknown): void
uploadGroupBulletinPic(groupCode: string, pskey: string, imagePath: string): Promise<GeneralCallResult & { uploadGroupBulletinPic(groupCode: string, pskey: string, imagePath: string): Promise<{
errCode: number errCode: number
errMsg: string
picInfo?: { picInfo?: {
id: string, id: string
width: number, width: number
height: number height: number
} }
}> }>
@@ -226,7 +241,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(groupCode: string): unknown getJoinGroupLink(groupCode: string): unknown
@@ -238,6 +253,7 @@ export interface NodeIKernelGroupService {
msgRandom: number, msgRandom: number,
msgSeq: number msgSeq: number
}): Promise<unknown> }): Promise<unknown>
//需要提前判断是否存在 高版本新增 //需要提前判断是否存在 高版本新增
removeGroupEssence(param: { removeGroupEssence(param: {
groupCode: string groupCode: string

View File

@@ -1,6 +1,6 @@
import { ElementType, MessageElement, Peer, RawMessage, SendMessageElement } from '@/ntqqapi/types' import { ElementType, MessageElement, Peer, RawMessage, SendMessageElement } from '@/ntqqapi/types'
import { NodeIKernelMsgListener } from '@/ntqqapi/listeners/NodeIKernelMsgListener'
import { GeneralCallResult } from './common' import { GeneralCallResult } from './common'
import { Dict } from 'cosmokit'
export interface QueryMsgsParams { export interface QueryMsgsParams {
chatInfo: Peer, chatInfo: Peer,
@@ -29,16 +29,15 @@ export interface TmpChatInfo {
} }
export interface NodeIKernelMsgService { export interface NodeIKernelMsgService {
generateMsgUniqueId(chatType: number, time: string): string generateMsgUniqueId(chatType: number, time: string): string
addKernelMsgListener(nodeIKernelMsgListener: NodeIKernelMsgListener): number addKernelMsgListener(nodeIKernelMsgListener: unknown): number
sendMsg(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<any, any>): Promise<GeneralCallResult> sendMsg(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<unknown, unknown>): Promise<GeneralCallResult>
recallMsg(peer: Peer, msgIds: string[]): Promise<GeneralCallResult> recallMsg(peer: Peer, msgIds: string[]): Promise<GeneralCallResult>
addKernelMsgImportToolListener(arg: Object): unknown addKernelMsgImportToolListener(arg: Dict): unknown
removeKernelMsgListener(args: unknown): unknown removeKernelMsgListener(args: unknown): unknown
@@ -52,7 +51,7 @@ export interface NodeIKernelMsgService {
getOnLineDev(): void getOnLineDev(): void
kickOffLine(DevInfo: Object): unknown kickOffLine(DevInfo: Dict): unknown
setStatus(args: { status: number, extStatus: number, batteryStatus: number }): Promise<GeneralCallResult> setStatus(args: { status: number, extStatus: number, batteryStatus: number }): Promise<GeneralCallResult>
@@ -81,11 +80,11 @@ export interface NodeIKernelMsgService {
// this.voipToken = bArr2 // this.voipToken = bArr2
// this.profileId = str // this.profileId = str
setToken(arg: Object): unknown setToken(arg: Dict): unknown
switchForeGround(): unknown switchForeGround(): unknown
switchBackGround(arg: Object): unknown switchBackGround(arg: Dict): unknown
//hex //hex
setTokenForMqq(token: string): unknown setTokenForMqq(token: string): unknown
@@ -110,13 +109,12 @@ export interface NodeIKernelMsgService {
resendMsg(...args: unknown[]): unknown resendMsg(...args: unknown[]): unknown
recallMsg(...args: unknown[]): unknown
reeditRecallMsg(...args: unknown[]): unknown reeditRecallMsg(...args: unknown[]): unknown
//调用请检查除开commentElements其余参数不能为null //调用请检查除开commentElements其余参数不能为null
forwardMsg(msgIds: string[], srcContact: Peer, dstContacts: Peer[], commentElements: MessageElement[]): Promise<GeneralCallResult> forwardMsg(msgIds: string[], srcContact: Peer, dstContacts: Peer[], commentElements: MessageElement[]): Promise<GeneralCallResult>
forwardMsgWithComment(...args: unknown[]): unknown forwardMsgWithComment(...args: unknown[]): Promise<GeneralCallResult>
forwardSubMsgWithComment(...args: unknown[]): unknown forwardSubMsgWithComment(...args: unknown[]): unknown
@@ -144,9 +142,9 @@ export interface NodeIKernelMsgService {
addLocalTofuRecordMsg(...args: unknown[]): unknown addLocalTofuRecordMsg(...args: unknown[]): unknown
addLocalRecordMsg(Peer: Peer, msgId: string, ele: MessageElement, attr: Array<any> | number, front: boolean): Promise<unknown> addLocalRecordMsg(Peer: Peer, msgId: string, ele: MessageElement, attr: Array<unknown> | number, front: boolean): Promise<unknown>
deleteMsg(Peer: Peer, msgIds: Array<string>): Promise<any> deleteMsg(Peer: Peer, msgIds: Array<string>): Promise<unknown>
updateElementExtBufForUI(...args: unknown[]): unknown updateElementExtBufForUI(...args: unknown[]): unknown
@@ -170,9 +168,10 @@ export interface NodeIKernelMsgService {
getLastMessageList(peer: Peer[]): Promise<unknown> getLastMessageList(peer: Peer[]): Promise<unknown>
getAioFirstViewLatestMsgs(peer: Peer, num: number): unknown getAioFirstViewLatestMsgs(peer: Peer, num: number): Promise<GeneralCallResult & {
msgList: RawMessage[]
}>
//deprecated 从9.9.15-26702版本开始该接口已经废弃请使用getMsgsEx
getMsgs(peer: Peer, msgId: string, count: unknown, queryOrder: boolean): Promise<unknown> getMsgs(peer: Peer, msgId: string, count: unknown, queryOrder: boolean): Promise<unknown>
getMsgsIncludeSelf(peer: Peer, msgId: string, count: number, queryOrder: boolean): Promise<GeneralCallResult & { getMsgsIncludeSelf(peer: Peer, msgId: string, count: number, queryOrder: boolean): Promise<GeneralCallResult & {
@@ -215,7 +214,7 @@ export interface NodeIKernelMsgService {
getMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): unknown getMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): unknown
getSourceOfReplyMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): unknown getSourceOfReplyMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): unknown
//cnt clientSeq?并不是吧
getMsgsByTypeFilter(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilter: { type: number, subtype: Array<number> }): unknown getMsgsByTypeFilter(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilter: { type: number, subtype: Array<number> }): unknown
getMsgsByTypeFilters(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilters: Array<{ type: number, subtype: Array<number> }>): unknown getMsgsByTypeFilters(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilters: Array<{ type: number, subtype: Array<number> }>): unknown
@@ -224,31 +223,8 @@ export interface NodeIKernelMsgService {
queryMsgsWithFilter(...args: unknown[]): unknown queryMsgsWithFilter(...args: unknown[]): unknown
/**
* @deprecated 该函数已被标记为废弃,请使用新的替代方法。
* 使用过滤条件查询消息列表的版本2接口。
*
* 该函数通过一系列过滤条件来查询特定聊天中的消息列表。这些条件包括消息类型、发送者、时间范围等。
* 函数返回一个Promise解析为查询结果的未知类型对象。
*
* @param MsgId 消息ID用于特定消息的查询。
* @param MsgTime 消息时间,用于指定消息的时间范围。
* @param param 查询参数对象,包含详细的过滤条件和分页信息。
* @param param.chatInfo 聊天信息包括聊天类型和对方用户ID。
* @param param.filterMsgType 需要过滤的消息类型数组,留空表示不过滤。
* @param param.filterSendersUid 需要过滤的发送者用户ID数组。
* @param param.filterMsgFromTime 查询消息的起始时间。
* @param param.filterMsgToTime 查询消息的结束时间。
* @param param.pageLimit 每页的消息数量限制。
* @param param.isReverseOrder 是否按时间顺序倒序返回消息。
* @param param.isIncludeCurrent 是否包含当前页码。
* @returns 返回一个Promise解析为查询结果的未知类型对象。
*/
queryMsgsWithFilterVer2(MsgId: string, MsgTime: string, param: QueryMsgsParams): Promise<unknown>
// this.chatType = i2 // this.chatType = i2
// this.peerUid = str // this.peerUid = str
// this.chatInfo = new ChatInfo() // this.chatInfo = new ChatInfo()
// this.filterMsgType = new ArrayList<>() // this.filterMsgType = new ArrayList<>()
// this.filterSendersUid = new ArrayList<>() // this.filterSendersUid = new ArrayList<>()
@@ -371,8 +347,9 @@ export interface NodeIKernelMsgService {
getFileThumbSavePathForSend(...args: unknown[]): unknown getFileThumbSavePathForSend(...args: unknown[]): unknown
getFileThumbSavePath(...args: unknown[]): unknown getFileThumbSavePath(...args: unknown[]): unknown
//猜测居多 //猜测居多
translatePtt2Text(MsgId: string, Peer: {}, MsgElement: {}): unknown translatePtt2Text(MsgId: string, Peer: Dict, MsgElement: Dict): unknown
setPttPlayedState(...args: unknown[]): unknown setPttPlayedState(...args: unknown[]): unknown
// NodeIQQNTWrapperSession fetchFavEmojiList [ // NodeIQQNTWrapperSession fetchFavEmojiList [
@@ -449,7 +426,7 @@ export interface NodeIKernelMsgService {
getEmojiResourcePath(...args: unknown[]): unknown getEmojiResourcePath(...args: unknown[]): unknown
JoinDragonGroupEmoji(JoinDragonGroupEmojiReq: any/*joinDragonGroupEmojiReq*/): unknown JoinDragonGroupEmoji(JoinDragonGroupEmojiReq: unknown): unknown
getMsgAbstracts(...args: unknown[]): unknown getMsgAbstracts(...args: unknown[]): unknown
@@ -495,16 +472,15 @@ export interface NodeIKernelMsgService {
setMsgEmojiLikes(...args: unknown[]): unknown setMsgEmojiLikes(...args: unknown[]): unknown
getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, cookie: string, bForward: boolean, number: number): Promise<{ getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, cookie: string, bForward: boolean, number: number): Promise<{
result: number, result: number
errMsg: string, errMsg: string
emojiLikesList: emojiLikesList: {
Array<{ tinyId: string
tinyId: string, nickName: string
nickName: string,
headUrl: string headUrl: string
}>, }[]
cookie: string, cookie: string
isLastPage: boolean, isLastPage: boolean
isFirstPage: boolean isFirstPage: boolean
}> }>
@@ -624,7 +600,6 @@ export interface NodeIKernelMsgService {
sendSsoCmdReqByContend(cmd: string, param: string): Promise<unknown> sendSsoCmdReqByContend(cmd: string, param: string): Promise<unknown>
//chattype,uid->Promise<any>
getTempChatInfo(ChatType: number, Uid: string): Promise<TmpChatInfoApi> getTempChatInfo(ChatType: number, Uid: string): Promise<TmpChatInfoApi>
setContactLocalTop(...args: unknown[]): unknown setContactLocalTop(...args: unknown[]): unknown
@@ -655,7 +630,7 @@ export interface NodeIKernelMsgService {
recordEmoji(...args: unknown[]): unknown recordEmoji(...args: unknown[]): unknown
fetchGetHitEmotionsByWord(args: Object): Promise<unknown>//表情推荐? fetchGetHitEmotionsByWord(args: Dict): Promise<unknown>//表情推荐?
deleteAllRoamMsgs(...args: unknown[]): unknown//漫游消息? deleteAllRoamMsgs(...args: unknown[]): unknown//漫游消息?
@@ -687,7 +662,6 @@ export interface NodeIKernelMsgService {
dataMigrationStopOperation(...args: unknown[]): unknown dataMigrationStopOperation(...args: unknown[]): unknown
//新的希望
dataMigrationImportMsgPbRecord(DataMigrationMsgInfo: Array<{ dataMigrationImportMsgPbRecord(DataMigrationMsgInfo: Array<{
extensionData: string //"Hex" extensionData: string //"Hex"
extraData: string //"" extraData: string //""

View File

@@ -1,5 +1,6 @@
import { BuddyProfileLikeReq } from '../types' import { BuddyProfileLikeReq } from '../types'
import { GeneralCallResult } from './common' import { GeneralCallResult } from './common'
import { Dict } from 'cosmokit'
export interface NodeIKernelProfileLikeService { export interface NodeIKernelProfileLikeService {
addKernelProfileLikeListener(listener: NodeIKernelProfileLikeService): void addKernelProfileLikeListener(listener: NodeIKernelProfileLikeService): void
@@ -9,10 +10,20 @@ export interface NodeIKernelProfileLikeService {
setBuddyProfileLike(...args: unknown[]): { result: number, errMsg: string, succCounts: number } setBuddyProfileLike(...args: unknown[]): { result: number, errMsg: string, succCounts: number }
getBuddyProfileLike(req: BuddyProfileLikeReq): Promise<GeneralCallResult & { getBuddyProfileLike(req: BuddyProfileLikeReq): Promise<GeneralCallResult & {
'info': { info: {
'userLikeInfos': Array<any>, userLikeInfos: {
'friendMaxVotes': number, uid: string
'start': number time: string
favoriteInfo: {
total_count: number
last_time: number
today_count: number
userInfos: Dict[]
}
voteInfo: Dict
}[]
friendMaxVotes: number
start: number
} }
}> }>

View File

@@ -33,7 +33,7 @@ export interface NodeIKernelProfileService {
fetchUserDetailInfo(trace: string, uids: string[], arg2: number, arg3: number[]): Promise<unknown> fetchUserDetailInfo(trace: string, uids: string[], arg2: number, arg3: number[]): Promise<unknown>
addKernelProfileListener(listener: any): number addKernelProfileListener(listener: unknown): number
removeKernelProfileListener(listenerId: number): void removeKernelProfileListener(listenerId: number): void
@@ -64,7 +64,7 @@ export interface NodeIKernelProfileService {
modifySelfProfile(...args: unknown[]): Promise<unknown> modifySelfProfile(...args: unknown[]): Promise<unknown>
modifyDesktopMiniProfile(param: any): Promise<GeneralCallResult> modifyDesktopMiniProfile(param: unknown): Promise<GeneralCallResult>
setNickName(NickName: string): Promise<unknown> setNickName(NickName: string): Promise<unknown>
@@ -74,7 +74,7 @@ export interface NodeIKernelProfileService {
setGander(...args: unknown[]): Promise<unknown> setGander(...args: unknown[]): Promise<unknown>
setHeader(arg: string): Promise<unknown> setHeader(arg: string): Promise<GeneralCallResult>
setRecommendImgFlag(...args: unknown[]): Promise<unknown> setRecommendImgFlag(...args: unknown[]): Promise<unknown>
@@ -82,9 +82,9 @@ export interface NodeIKernelProfileService {
getUserDetailInfo(uid: string): Promise<unknown> getUserDetailInfo(uid: string): Promise<unknown>
getUserDetailInfoWithBizInfo(uid: string, Biz: any[]): Promise<GeneralCallResult> getUserDetailInfoWithBizInfo(uid: string, Biz: unknown[]): Promise<GeneralCallResult>
getUserDetailInfoByUin(uin: string): Promise<any> getUserDetailInfoByUin(uin: string): Promise<unknown>
getZplanAvatarInfos(args: string[]): Promise<unknown> getZplanAvatarInfos(args: string[]): Promise<unknown>
@@ -99,7 +99,7 @@ export interface NodeIKernelProfileService {
getProfileQzonePicInfo(uid: string, type: number, force: boolean): Promise<unknown> getProfileQzonePicInfo(uid: string, type: number, force: boolean): Promise<unknown>
//profileService.getCoreInfo("UserRemarkServiceImpl::getStrangerRemarkByUid", arrayList) //profileService.getCoreInfo("UserRemarkServiceImpl::getStrangerRemarkByUid", arrayList)
getCoreInfo(name: string, arg: any[]): unknown getCoreInfo(name: string, arg: unknown[]): unknown
//m429253e12.getOtherFlag("FriendListInfoCache_getKernelDataAndPutCache", new ArrayList<>()) //m429253e12.getOtherFlag("FriendListInfoCache_getKernelDataAndPutCache", new ArrayList<>())
isNull(): boolean isNull(): boolean

View File

@@ -169,18 +169,14 @@ export interface NodeIKernelRichMediaService {
downloadFileForFileInfo(fileInfo: CommonFileInfo[], savePath: string): unknown downloadFileForFileInfo(fileInfo: CommonFileInfo[], savePath: string): unknown
createGroupFolder(GroupCode: string, FolderName: string): Promise<GeneralCallResult & { resultWithGroupItem: { result: any, groupItem: Array<any> } }> createGroupFolder(GroupCode: string, FolderName: string): Promise<GeneralCallResult & { resultWithGroupItem: { result: unknown, groupItem: Array<unknown> } }>
downloadFile(commonFile: CommonFileInfo, arg2: unknown, arg3: unknown, savePath: string): unknown downloadFile(commonFile: CommonFileInfo, arg2: unknown, arg3: unknown, savePath: string): unknown
createGroupFolder(arg1: unknown, arg2: unknown): unknown
downloadGroupFolder(arg1: unknown, arg2: unknown, arg3: unknown): unknown downloadGroupFolder(arg1: unknown, arg2: unknown, arg3: unknown): unknown
renameGroupFolder(arg1: unknown, arg2: unknown, arg3: unknown): unknown renameGroupFolder(arg1: unknown, arg2: unknown, arg3: unknown): unknown
deleteGroupFolder(arg1: unknown, arg2: unknown): unknown
deleteTransferInfo(arg1: unknown, arg2: unknown): unknown deleteTransferInfo(arg1: unknown, arg2: unknown): unknown
cancelTransferTask(arg1: unknown, arg2: unknown, arg3: unknown): unknown cancelTransferTask(arg1: unknown, arg2: unknown, arg3: unknown): unknown
@@ -226,9 +222,9 @@ export interface NodeIKernelRichMediaService {
deleteGroupFile(GroupCode: string, params: Array<number>, Files: Array<string>): Promise<GeneralCallResult & { deleteGroupFile(GroupCode: string, params: Array<number>, Files: Array<string>): Promise<GeneralCallResult & {
transGroupFileResult: { transGroupFileResult: {
result: any result: unknown
successFileIdList: Array<any> successFileIdList: Array<unknown>
failFileIdList: Array<any> failFileIdList: Array<unknown>
} }
}> }>

View File

@@ -1,75 +1,75 @@
import { ChatType } from '../types' import { ChatType } from '../types'
export interface NodeIKernelSearchService { export interface NodeIKernelSearchService {
addKernelSearchListener(...args: any[]): unknown// needs 1 arguments addKernelSearchListener(...args: unknown[]): unknown// needs 1 arguments
removeKernelSearchListener(...args: any[]): unknown// needs 1 arguments removeKernelSearchListener(...args: unknown[]): unknown// needs 1 arguments
searchStranger(...args: any[]): unknown// needs 3 arguments searchStranger(...args: unknown[]): unknown// needs 3 arguments
searchGroup(...args: any[]): unknown// needs 1 arguments searchGroup(...args: unknown[]): unknown// needs 1 arguments
searchLocalInfo(keywords: string, unknown: number/*4*/): unknown searchLocalInfo(keywords: string, unknown: number/*4*/): unknown
cancelSearchLocalInfo(...args: any[]): unknown// needs 3 arguments cancelSearchLocalInfo(...args: unknown[]): unknown// needs 3 arguments
searchBuddyChatInfo(...args: any[]): unknown// needs 2 arguments searchBuddyChatInfo(...args: unknown[]): unknown// needs 2 arguments
searchMoreBuddyChatInfo(...args: any[]): unknown// needs 1 arguments searchMoreBuddyChatInfo(...args: unknown[]): unknown// needs 1 arguments
cancelSearchBuddyChatInfo(...args: any[]): unknown// needs 3 arguments cancelSearchBuddyChatInfo(...args: unknown[]): unknown// needs 3 arguments
searchContact(...args: any[]): unknown// needs 2 arguments searchContact(...args: unknown[]): unknown// needs 2 arguments
searchMoreContact(...args: any[]): unknown// needs 1 arguments searchMoreContact(...args: unknown[]): unknown// needs 1 arguments
cancelSearchContact(...args: any[]): unknown// needs 3 arguments cancelSearchContact(...args: unknown[]): unknown// needs 3 arguments
searchGroupChatInfo(...args: any[]): unknown// needs 3 arguments searchGroupChatInfo(...args: unknown[]): unknown// needs 3 arguments
resetSearchGroupChatInfoSortType(...args: any[]): unknown// needs 3 arguments resetSearchGroupChatInfoSortType(...args: unknown[]): unknown// needs 3 arguments
resetSearchGroupChatInfoFilterMembers(...args: any[]): unknown// needs 3 arguments resetSearchGroupChatInfoFilterMembers(...args: unknown[]): unknown// needs 3 arguments
searchMoreGroupChatInfo(...args: any[]): unknown// needs 1 arguments searchMoreGroupChatInfo(...args: unknown[]): unknown// needs 1 arguments
cancelSearchGroupChatInfo(...args: any[]): unknown// needs 3 arguments cancelSearchGroupChatInfo(...args: unknown[]): unknown// needs 3 arguments
searchChatsWithKeywords(...args: any[]): unknown// needs 3 arguments searchChatsWithKeywords(...args: unknown[]): unknown// needs 3 arguments
searchMoreChatsWithKeywords(...args: any[]): unknown// needs 1 arguments searchMoreChatsWithKeywords(...args: unknown[]): unknown// needs 1 arguments
cancelSearchChatsWithKeywords(...args: any[]): unknown// needs 3 arguments cancelSearchChatsWithKeywords(...args: unknown[]): unknown// needs 3 arguments
searchChatMsgs(...args: any[]): unknown// needs 2 arguments searchChatMsgs(...args: unknown[]): unknown// needs 2 arguments
searchMoreChatMsgs(...args: any[]): unknown// needs 1 arguments searchMoreChatMsgs(...args: unknown[]): unknown// needs 1 arguments
cancelSearchChatMsgs(...args: any[]): unknown// needs 3 arguments cancelSearchChatMsgs(...args: unknown[]): unknown// needs 3 arguments
searchMsgWithKeywords(...args: any[]): unknown// needs 2 arguments searchMsgWithKeywords(...args: unknown[]): unknown// needs 2 arguments
searchMoreMsgWithKeywords(...args: any[]): unknown// needs 1 arguments searchMoreMsgWithKeywords(...args: unknown[]): unknown// needs 1 arguments
cancelSearchMsgWithKeywords(...args: any[]): unknown// needs 3 arguments cancelSearchMsgWithKeywords(...args: unknown[]): unknown// needs 3 arguments
searchFileWithKeywords(keywords: string[], source: number): Promise<string>// needs 2 arguments searchFileWithKeywords(keywords: string[], source: number): Promise<string>// needs 2 arguments
searchMoreFileWithKeywords(...args: any[]): unknown// needs 1 arguments searchMoreFileWithKeywords(...args: unknown[]): unknown// needs 1 arguments
cancelSearchFileWithKeywords(...args: any[]): unknown// needs 3 arguments cancelSearchFileWithKeywords(...args: unknown[]): unknown// needs 3 arguments
searchAtMeChats(...args: any[]): unknown// needs 3 arguments searchAtMeChats(...args: unknown[]): unknown// needs 3 arguments
searchMoreAtMeChats(...args: any[]): unknown// needs 1 arguments searchMoreAtMeChats(...args: unknown[]): unknown// needs 1 arguments
cancelSearchAtMeChats(...args: any[]): unknown// needs 3 arguments cancelSearchAtMeChats(...args: unknown[]): unknown// needs 3 arguments
searchChatAtMeMsgs(...args: any[]): unknown// needs 1 arguments searchChatAtMeMsgs(...args: unknown[]): unknown// needs 1 arguments
searchMoreChatAtMeMsgs(...args: any[]): unknown// needs 1 arguments searchMoreChatAtMeMsgs(...args: unknown[]): unknown// needs 1 arguments
cancelSearchChatAtMeMsgs(...args: any[]): unknown// needs 3 arguments cancelSearchChatAtMeMsgs(...args: unknown[]): unknown// needs 3 arguments
addSearchHistory(param: { addSearchHistory(param: {
type: number,//4 type: number,//4
@@ -120,9 +120,9 @@ export interface NodeIKernelSearchService {
id?: number id?: number
}> }>
removeSearchHistory(...args: any[]): unknown// needs 1 arguments removeSearchHistory(...args: unknown[]): unknown// needs 1 arguments
searchCache(...args: any[]): unknown// needs 3 arguments searchCache(...args: unknown[]): unknown// needs 3 arguments
clearSearchCache(...args: any[]): unknown// needs 1 arguments clearSearchCache(...args: unknown[]): unknown// needs 1 arguments
} }

View File

@@ -36,6 +36,7 @@ export interface Group {
memberUid: string //"u_fbf8N7aeuZEnUiJAbQ9R8Q" memberUid: string //"u_fbf8N7aeuZEnUiJAbQ9R8Q"
} }
members: GroupMember[] // 原始数据是没有这个的,为了方便自己加了这个字段 members: GroupMember[] // 原始数据是没有这个的,为了方便自己加了这个字段
createTime: string
} }
export enum GroupMemberRole { export enum GroupMemberRole {
@@ -61,6 +62,19 @@ export interface GroupMember {
sex?: Sex sex?: Sex
qqLevel?: QQLevel qqLevel?: QQLevel
isChangeRole: boolean isChangeRole: boolean
joinTime: string joinTime: number
lastSpeakTime: string lastSpeakTime: number
memberLevel: number
}
export interface PublishGroupBulletinReq {
text: string
picInfo?: {
id: string
width: number
height: number
}
oldFeedsId: ''
pinned: number
confirmRequired: number
} }

View File

@@ -6,6 +6,7 @@ export interface GetFileListParam {
startIndex: number startIndex: number
sortOrder: number sortOrder: number
showOnlinedocFolder: number showOnlinedocFolder: number
folderId?: string
} }
export enum ElementType { export enum ElementType {
@@ -102,7 +103,7 @@ export interface SendPicElement {
export interface SendReplyElement { export interface SendReplyElement {
elementType: ElementType.REPLY elementType: ElementType.REPLY
elementId: '' elementId: ''
replyElement: ReplyElement replyElement: Partial<ReplyElement>
} }
export interface SendFaceElement { export interface SendFaceElement {
@@ -129,6 +130,12 @@ export interface ReplyElement {
replayMsgId: string replayMsgId: string
senderUin: string senderUin: string
senderUinStr: string senderUinStr: string
sourceMsgIdInRecords: string
senderUid: string
senderUidStr: string
sourceMsgIsIncPic: boolean // 原消息是否有图片
sourceMsgText: string
replyMsgTime: string
} }
export interface FileElement { export interface FileElement {
@@ -275,21 +282,35 @@ export interface PicElement {
thumbPath: Map<number, string> thumbPath: Map<number, string>
picWidth: number picWidth: number
picHeight: number picHeight: number
fileSize: number fileSize: string
fileName: string fileName: string
fileUuid: string fileUuid: string
md5HexStr?: string md5HexStr?: string
} }
export enum GrayTipElementSubType { export enum GrayTipElementSubType {
RECALL = 1, REVOKE = 1,
INVITE_NEW_MEMBER = 12, PROCLAMATION = 2,
MEMBER_NEW_TITLE = 17, EMOJIREPLY = 3,
GROUP = 4,
BUDDY = 5,
FEED = 6,
ESSENCE = 7,
GROUPNOTIFY = 8,
BUDDYNOTIFY = 9,
FILE = 10,
FEEDCHANNELMSG = 11,
XMLMSG = 12,
LOCALMSG = 13,
BLOCK = 14,
AIOOP = 15,
WALLET = 16,
JSON = 17,
} }
export interface GrayTipElement { export interface GrayTipElement {
subElementType: GrayTipElementSubType subElementType: GrayTipElementSubType
revokeElement: { revokeElement?: {
operatorRole: string operatorRole: string
operatorUid: string operatorUid: string
operatorNick: string operatorNick: string
@@ -299,14 +320,14 @@ export interface GrayTipElement {
isSelfOperate?: boolean isSelfOperate?: boolean
wording: string // 自定义的撤回提示语 wording: string // 自定义的撤回提示语
} }
aioOpGrayTipElement: TipAioOpGrayTipElement aioOpGrayTipElement?: TipAioOpGrayTipElement
groupElement: TipGroupElement groupElement?: TipGroupElement
xmlElement: { xmlElement?: {
templId: string templId: string
content: string content: string
} }
jsonGrayTipElement: { jsonGrayTipElement?: {
busiId: number busiId: string
jsonStr: string jsonStr: string
} }
} }
@@ -352,7 +373,7 @@ export interface VideoElement {
thumbHeight?: number thumbHeight?: number
busiType?: 0 // 未知 busiType?: 0 // 未知
subBusiType?: 0 // 未知 subBusiType?: 0 // 未知
thumbPath?: Map<number, any> thumbPath?: Map<number, string>
transferStatus?: 0 // 未知 transferStatus?: 0 // 未知
progress?: 0 // 下载进度? progress?: 0 // 下载进度?
invalidState?: 0 // 未知 invalidState?: 0 // 未知
@@ -471,34 +492,7 @@ export interface RawMessage {
sendStatus?: number // 消息状态别人发的2是已撤回自己发的2是已发送 sendStatus?: number // 消息状态别人发的2是已撤回自己发的2是已发送
recallTime: string // 撤回时间, "0"是没有撤回 recallTime: string // 撤回时间, "0"是没有撤回
records: RawMessage[] records: RawMessage[]
elements: { elements: MessageElement[]
elementId: string
elementType: ElementType
replyElement: {
sourceMsgIdInRecords: string
senderUid: string // 原消息发送者QQ号
sourceMsgIsIncPic: boolean // 原消息是否有图片
sourceMsgText: string
replayMsgSeq: string // 源消息的msgSeq可以通过这个找到源消息的msgId
}
textElement: {
atType: AtType
atUid: string // QQ号
content: string
atNtUid: string // uid号
}
picElement: PicElement
pttElement: PttElement
arkElement: ArkElement
grayTipElement: GrayTipElement
faceElement: FaceElement
videoElement: VideoElement
fileElement: FileElement
marketFaceElement: MarketFaceElement
inlineKeyboardElement: InlineKeyboardElement
markdownElement: MarkdownElement
multiForwardMsgElement: MultiForwardMsgElement
}[]
} }
export interface Peer { export interface Peer {
@@ -513,7 +507,7 @@ export interface MessageElement {
extBufForUI: string //"0x" extBufForUI: string //"0x"
textElement?: TextElement textElement?: TextElement
faceElement?: FaceElement faceElement?: FaceElement
marketFaceElement?: MarkdownElement marketFaceElement?: MarketFaceElement
replyElement?: ReplyElement replyElement?: ReplyElement
picElement?: PicElement picElement?: PicElement
pttElement?: PttElement pttElement?: PttElement
@@ -521,22 +515,96 @@ export interface MessageElement {
grayTipElement?: GrayTipElement grayTipElement?: GrayTipElement
arkElement?: ArkElement arkElement?: ArkElement
fileElement?: FileElement fileElement?: FileElement
liveGiftElement?: null liveGiftElement?: unknown
markdownElement?: MarkdownElement markdownElement?: MarkdownElement
structLongMsgElement?: any structLongMsgElement?: unknown
multiForwardMsgElement?: MultiForwardMsgElement multiForwardMsgElement?: MultiForwardMsgElement
giphyElement?: any giphyElement?: unknown
walletElement?: null walletElement?: unknown
inlineKeyboardElement?: InlineKeyboardElement inlineKeyboardElement?: InlineKeyboardElement
textGiftElement?: null //???? textGiftElement?: unknown //????
calendarElement?: any calendarElement?: unknown
yoloGameResultElement?: any yoloGameResultElement?: unknown
avRecordElement?: any avRecordElement?: unknown
structMsgElement?: null structMsgElement?: unknown
faceBubbleElement?: any faceBubbleElement?: unknown
shareLocationElement?: any shareLocationElement?: unknown
tofuRecordElement?: any tofuRecordElement?: unknown
taskTopMsgElement?: any taskTopMsgElement?: unknown
recommendedMsgElement?: any recommendedMsgElement?: unknown
actionBarElement?: any actionBarElement?: unknown
}
export interface OnRichMediaDownloadCompleteParams {
fileModelId: string
msgElementId: string
msgId: string
fileId: string
fileProgress: string // '0'
fileSpeed: string // '0'
fileErrCode: string // '0'
fileErrMsg: string
fileDownType: number // 暂时未知
thumbSize: number
filePath: string
totalSize: string
trasferStatus: number
step: number
commonFileInfo: unknown
fileSrvErrCode: string
clientMsg: string
businessId: number
userTotalSpacePerDay: unknown
userUsedSpacePerDay: unknown
}
export interface OnGroupFileInfoUpdateParams {
retCode: number
retMsg: string
clientWording: string
isEnd: boolean
item: {
peerId: string
type: number
folderInfo?: {
folderId: string
parentFolderId: string
folderName: string
createTime: number
modifyTime: number
createUin: string
creatorName: string
totalFileCount: number
modifyUin: string
modifyName: string
usedSpace: string
}
fileInfo?: {
fileModelId: string
fileId: string
fileName: string
fileSize: string
busId: number
uploadedSize: string
uploadTime: number
deadTime: number
modifyTime: number
downloadTimes: number
sha: string
sha3: string
md5: string
uploaderLocalPath: string
uploaderName: string
uploaderUin: string
parentFolderId: string
localPath: string
transStatus: number
transType: number
elementId: string
isFolder: boolean
}
}[]
allFileCount: number
nextIndex: number
reqId: number
} }

View File

@@ -1,13 +1,19 @@
export enum GroupNotifyTypes { export enum GroupNotifyType {
INVITE_ME = 1, INVITED_BY_MEMBER = 1,
INVITED_JOIN = 4, // 有人接受了邀请入群 REFUSE_INVITED,
JOIN_REQUEST_BY_INVITED = 5, // 有人邀请了别人入群 REFUSED_BY_ADMINI_STRATOR,
JOIN_REQUEST = 7, AGREED_TOJOIN_DIRECT, // 有人接受了邀请入群
ADMIN_SET = 8, INVITED_NEED_ADMINI_STRATOR_PASS, // 有人邀请了别人入群
KICK_MEMBER = 9, AGREED_TO_JOIN_BY_ADMINI_STRATOR,
MEMBER_EXIT = 11, // 主动退出 REQUEST_JOIN_NEED_ADMINI_STRATOR_PASS,
ADMIN_UNSET = 12, // 我被取消管理员 SET_ADMIN,
ADMIN_UNSET_OTHER = 13, // 其他人取消管理员 KICK_MEMBER_NOTIFY_ADMIN,
KICK_MEMBER_NOTIFY_KICKED,
MEMBER_LEAVE_NOTIFY_ADMIN, // 主动退出
CANCEL_ADMIN_NOTIFY_CANCELED, // 我被取消管理员
CANCEL_ADMIN_NOTIFY_ADMIN, // 其他人取消管理员
TRANSFER_GROUP_NOTIFY_OLDOWNER,
TRANSFER_GROUP_NOTIFY_ADMIN
} }
export interface GroupNotifies { export interface GroupNotifies {
@@ -17,17 +23,18 @@ export interface GroupNotifies {
} }
export enum GroupNotifyStatus { export enum GroupNotifyStatus {
IGNORE = 0, KINIT, // 初始化
WAIT_HANDLE = 1, KUNHANDLE, // 未处理
APPROVE = 2, KAGREED, // 同意
REJECT = 3, KREFUSED, // 拒绝
KIGNORED // 忽略
} }
export interface GroupNotify { export interface GroupNotify {
time: number // 自己添加的字段,时间戳,毫秒, 用于判断收到短时间内收到重复的notify time: number // 自己添加的字段,时间戳,毫秒, 用于判断收到短时间内收到重复的notify
seq: string // 唯一标识符转成数字再除以1000应该就是时间戳 seq: string // 唯一标识符转成数字再除以1000应该就是时间戳
type: GroupNotifyTypes type: GroupNotifyType
status: GroupNotifyStatus // 0是已忽略1是未处理2是已同意 status: GroupNotifyStatus
group: { groupCode: string; groupName: string } group: { groupCode: string; groupName: string }
user1: { uid: string; nickName: string } // 被设置管理员的人 user1: { uid: string; nickName: string } // 被设置管理员的人
user2: { uid: string; nickName: string } // 操作者 user2: { uid: string; nickName: string } // 操作者

View File

@@ -124,7 +124,7 @@ interface VideoInfo {
interface ExtOnlineBusinessInfo { interface ExtOnlineBusinessInfo {
buf: string buf: string
customStatus: any customStatus: unknown
videoBizInfo: VideoBizInfo videoBizInfo: VideoBizInfo
videoInfo: VideoInfo videoInfo: VideoInfo
} }
@@ -142,7 +142,7 @@ interface UserStatus {
termType: number termType: number
netType: number netType: number
iconType: number iconType: number
customStatus: any customStatus: unknown
setTime: string setTime: string
specialFlag: number specialFlag: number
abiFlag: number abiFlag: number
@@ -156,8 +156,8 @@ interface UserStatus {
interface PrivilegeIcon { interface PrivilegeIcon {
jumpUrl: string jumpUrl: string
openIconList: any[] openIconList: unknown[]
closeIconList: any[] closeIconList: unknown[]
} }
interface VasInfo { interface VasInfo {
@@ -180,7 +180,7 @@ interface VasInfo {
fontEffect: number fontEffect: number
newLoverDiamondFlag: number newLoverDiamondFlag: number
extendNameplateId: number extendNameplateId: number
diyNameplateIDs: any[] diyNameplateIDs: unknown[]
vipStartFlag: number vipStartFlag: number
vipDataFlag: number vipDataFlag: number
gameNameplateId: string gameNameplateId: string
@@ -200,8 +200,8 @@ export interface SimpleInfo {
status: UserStatus | null status: UserStatus | null
vasInfo: VasInfo | null vasInfo: VasInfo | null
relationFlags: RelationFlags | null relationFlags: RelationFlags | null
otherFlags: any | null otherFlags: unknown | null
intimate: any | null intimate: unknown | null
} }
interface RelationFlags { interface RelationFlags {
@@ -241,7 +241,7 @@ interface CommonExt {
address: string address: string
regTime: number regTime: number
interest: string interest: string
labels: any[] labels: string[]
qqLevel: QQLevel qqLevel: QQLevel
} }
@@ -323,12 +323,12 @@ export interface UserDetailInfoByUin {
regTime: number regTime: number
interest: string interest: string
termType: number termType: number
labels: any[] labels: unknown[]
qqLevel: { crownNum: number, sunNum: number, moonNum: number, starNum: number } qqLevel: { crownNum: number, sunNum: number, moonNum: number, starNum: number }
isHideQQLevel: number isHideQQLevel: number
privilegeIcon: { jumpUrl: string, openIconList: any[], closeIconList: any[] } privilegeIcon: { jumpUrl: string, openIconList: unknown[], closeIconList: unknown[] }
isHidePrivilegeIcon: number isHidePrivilegeIcon: number
photoWall: { picList: any[] } photoWall: { picList: unknown[] }
vipFlag: boolean vipFlag: boolean
yearVipFlag: boolean yearVipFlag: boolean
svipFlag: boolean svipFlag: boolean

View File

@@ -11,11 +11,11 @@ import {
NodeIKernelTipOffService, NodeIKernelTipOffService,
NodeIKernelSearchService NodeIKernelSearchService
} from './services' } from './services'
import os from 'node:os' import { constants } from 'node:os'
import { Dict } from 'cosmokit'
const Process = require('node:process') const Process = require('node:process')
export interface NodeIQQNTWrapperSession { export interface NodeIQQNTWrapperSession {
[key: string]: any
getBuddyService(): NodeIKernelBuddyService getBuddyService(): NodeIKernelBuddyService
getGroupService(): NodeIKernelGroupService getGroupService(): NodeIKernelGroupService
getProfileService(): NodeIKernelProfileService getProfileService(): NodeIKernelProfileService
@@ -33,58 +33,20 @@ export interface WrapperApi {
NodeIQQNTWrapperSession?: NodeIQQNTWrapperSession NodeIQQNTWrapperSession?: NodeIQQNTWrapperSession
} }
export interface WrapperConstructor {
[key: string]: any
NodeIKernelBuddyListener?: any
NodeIKernelGroupListener?: any
NodeQQNTWrapperUtil?: any
NodeIKernelMsgListener?: any
NodeIQQNTWrapperEngine?: any
NodeIGlobalAdapter?: any
NodeIDependsAdapter?: any
NodeIDispatcherAdapter?: any
NodeIKernelSessionListener?: any
NodeIKernelLoginService?: any
NodeIKernelLoginListener?: any
NodeIKernelProfileService?: any
NodeIKernelProfileListener?: any
}
const wrapperApi: WrapperApi = {} const wrapperApi: WrapperApi = {}
export const wrapperConstructor: WrapperConstructor = {}
const constructor = [
'NodeIKernelBuddyListener',
'NodeIKernelGroupListener',
'NodeQQNTWrapperUtil',
'NodeIKernelMsgListener',
'NodeIQQNTWrapperEngine',
'NodeIGlobalAdapter',
'NodeIDependsAdapter',
'NodeIDispatcherAdapter',
'NodeIKernelSessionListener',
'NodeIKernelLoginService',
'NodeIKernelLoginListener',
'NodeIKernelProfileService',
'NodeIKernelProfileListener',
]
Process.dlopenOrig = Process.dlopen Process.dlopenOrig = Process.dlopen
Process.dlopen = function (module, filename, flags = os.constants.dlopen.RTLD_LAZY) { Process.dlopen = function (module: Dict, filename: string, flags = constants.dlopen.RTLD_LAZY) {
const dlopenRet = this.dlopenOrig(module, filename, flags) const dlopenRet = this.dlopenOrig(module, filename, flags)
for (let export_name in module.exports) { for (const export_name in module.exports) {
module.exports[export_name] = new Proxy(module.exports[export_name], { module.exports[export_name] = new Proxy(module.exports[export_name], {
construct: (target, args, _newTarget) => { construct: (target, args) => {
const ret = new target(...args) const ret = new target(...args)
if (export_name === 'NodeIQQNTWrapperSession') wrapperApi.NodeIQQNTWrapperSession = ret if (export_name === 'NodeIQQNTWrapperSession') wrapperApi.NodeIQQNTWrapperSession = ret
return ret return ret
} }
}) })
if (constructor.includes(export_name)) {
wrapperConstructor[export_name] = module.exports[export_name]
}
} }
return dlopenRet return dlopenRet
} }

View File

@@ -1,54 +1,51 @@
import { ActionName, BaseCheckResult } from './types' import { ActionName } from './types'
import { OB11Response } from './OB11Response' import { OB11Response } from './OB11Response'
import { OB11Return } from '../types' import { OB11Return } from '../types'
import { Context } from 'cordis' import { Context, Schema } from 'cordis'
import type Adapter from '../adapter' import type Adapter from '../adapter'
abstract class BaseAction<PayloadType, ReturnDataType> { abstract class BaseAction<PayloadType, ReturnDataType> {
abstract actionName: ActionName abstract actionName: ActionName
protected ctx: Context protected ctx: Context
payloadSchema?: Schema<PayloadType>
constructor(protected adapter: Adapter) { constructor(protected adapter: Adapter) {
this.ctx = adapter.ctx this.ctx = adapter.ctx
} }
protected async check(payload: PayloadType): Promise<BaseCheckResult> {
return {
valid: true,
}
}
public async handle(payload: PayloadType): Promise<OB11Return<ReturnDataType | null>> { public async handle(payload: PayloadType): Promise<OB11Return<ReturnDataType | null>> {
const result = await this.check(payload) let params: PayloadType
if (!result.valid) { try {
return OB11Response.error(result.message, 400) params = this.payloadSchema ? new this.payloadSchema(payload) : payload
} catch (e) {
return OB11Response.error((e as Error).message, 400)
} }
try { try {
const resData = await this._handle(payload) const resData = await this._handle(params)
return OB11Response.ok(resData) return OB11Response.ok(resData)
} catch (e: any) { } catch (e) {
this.ctx.logger.error('发生错误', e) this.ctx.logger.error('发生错误', e)
return OB11Response.error(e?.toString() || e?.stack?.toString() || '未知错误,可能操作超时', 200) return OB11Response.error((e as Error)?.toString() || (e as Error)?.stack?.toString() || '未知错误,可能操作超时', 200)
} }
} }
public async websocketHandle(payload: PayloadType, echo: any): Promise<OB11Return<ReturnDataType | null>> { public async websocketHandle(payload: PayloadType, echo: unknown): Promise<OB11Return<ReturnDataType | null>> {
const result = await this.check(payload) let params: PayloadType
if (!result.valid) { try {
return OB11Response.error(result.message, 1400) params = this.payloadSchema ? new this.payloadSchema(payload) : payload
} catch (e) {
return OB11Response.error((e as Error).message, 1400)
} }
try { try {
const resData = await this._handle(payload) const resData = await this._handle(params)
return OB11Response.ok(resData, echo) return OB11Response.ok(resData, echo)
} catch (e: any) { } catch (e) {
this.ctx.logger.error('发生错误', e) this.ctx.logger.error('发生错误', e)
return OB11Response.error(e.stack?.toString() || e.toString(), 1200, echo) return OB11Response.error((e as Error)?.stack?.toString() || String(e), 1200, echo)
} }
} }
protected async _handle(payload: PayloadType): Promise<ReturnDataType> { protected abstract _handle(payload: PayloadType): Promise<ReturnDataType>
throw `pleas override ${this.actionName} _handle`
}
} }
export default BaseAction export { BaseAction, Schema }

View File

@@ -13,16 +13,16 @@ export class OB11Response {
} }
} }
static ok<T>(data: T, echo: any = null) { static ok<T>(data: T, echo?: unknown) {
let res = OB11Response.res<T>(data, 'ok', 0) const res = OB11Response.res<T>(data, 'ok', 0)
if (!isNullable(echo)) { if (!isNullable(echo)) {
res.echo = echo res.echo = echo
} }
return res return res
} }
static error(err: string, retcode: number, echo: any = null) { static error(err: string, retcode: number, echo?: unknown) {
let res = OB11Response.res(null, 'failed', retcode, err) const res = OB11Response.res(null, 'failed', retcode, err)
if (!isNullable(echo)) { if (!isNullable(echo)) {
res.echo = echo res.echo = echo
} }

View File

@@ -1,6 +1,5 @@
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import fsPromise from 'node:fs/promises' import { readFile } from 'node:fs/promises'
import { getConfigUtil } from '@/common/config'
import { ActionName } from '../types' import { ActionName } from '../types'
import { Peer, ElementType } from '@/ntqqapi/types' import { Peer, ElementType } from '@/ntqqapi/types'
import { MessageUnique } from '@/common/utils/messageUnique' import { MessageUnique } from '@/common/utils/messageUnique'
@@ -18,13 +17,16 @@ export interface GetFileResponse {
} }
export abstract class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> { export abstract class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
// forked from https://github.com/NapNeko/NapCatQQ/blob/6f6b258f22d7563f15d84e7172c4d4cbb547f47e/src/onebot11/action/file/GetFile.ts#L44 payloadSchema = Schema.object({
protected async _handle(payload: GetFilePayload): Promise<GetFileResponse> { file: Schema.string().required()
const { enableLocalFile2Url } = getConfigUtil().getConfig() })
let fileCache = await MessageUnique.getFileCacheById(String(payload.file)) protected async _handle(payload: GetFilePayload): Promise<GetFileResponse> {
const { enableLocalFile2Url } = this.adapter.config
let fileCache = await MessageUnique.getFileCacheById(payload.file)
if (!fileCache?.length) { if (!fileCache?.length) {
fileCache = await MessageUnique.getFileCacheByName(String(payload.file)) fileCache = await MessageUnique.getFileCacheByName(payload.file)
} }
if (fileCache?.length) { if (fileCache?.length) {
@@ -57,13 +59,13 @@ export abstract class GetFileBase extends BaseAction<GetFilePayload, GetFileResp
if (!findEle) { if (!findEle) {
throw new Error('element not found') throw new Error('element not found')
} }
res.url = await this.ctx.ntFileApi.getImageUrl(findEle.picElement) res.url = await this.ctx.ntFileApi.getImageUrl(findEle.picElement!)
} else if (fileCache[0].elementType === ElementType.VIDEO) { } else if (fileCache[0].elementType === ElementType.VIDEO) {
res.url = await this.ctx.ntFileApi.getVideoUrl(peer, fileCache[0].msgId, fileCache[0].elementId) res.url = await this.ctx.ntFileApi.getVideoUrl(peer, fileCache[0].msgId, fileCache[0].elementId)
} }
if (enableLocalFile2Url && downloadPath && (res.file === res.url || res.url === undefined)) { if (enableLocalFile2Url && downloadPath && (res.file === res.url || res.url === undefined)) {
try { try {
res.base64 = await fsPromise.readFile(downloadPath, 'base64') res.base64 = await readFile(downloadPath, 'base64')
} catch (e) { } catch (e) {
throw new Error('文件下载失败. ' + e) throw new Error('文件下载失败. ' + e)
} }
@@ -77,11 +79,12 @@ export abstract class GetFileBase extends BaseAction<GetFilePayload, GetFileResp
export default class GetFile extends GetFileBase { export default class GetFile extends GetFileBase {
actionName = ActionName.GetFile actionName = ActionName.GetFile
payloadSchema = Schema.object({
file: Schema.string(),
file_id: Schema.string().required()
})
protected async _handle(payload: { file_id: string; file: string }): Promise<GetFileResponse> { protected async _handle(payload: { file_id: string, file: string }): Promise<GetFileResponse> {
if (!payload.file_id) {
throw new Error('file_id 不能为空')
}
payload.file = payload.file_id payload.file = payload.file_id
return super._handle(payload) return super._handle(payload)
} }

View File

@@ -3,11 +3,4 @@ import { ActionName } from '../types'
export default class GetImage extends GetFileBase { export default class GetImage extends GetFileBase {
actionName = ActionName.GetImage actionName = ActionName.GetImage
protected async _handle(payload: { file: string }) {
if (!payload.file) {
throw new Error('参数 file 不能为空')
}
return super._handle(payload)
}
} }

View File

@@ -1,9 +1,9 @@
import path from 'node:path'
import { GetFileBase, GetFilePayload, GetFileResponse } from './GetFile' import { GetFileBase, GetFilePayload, GetFileResponse } from './GetFile'
import { ActionName } from '../types' import { ActionName } from '../types'
import { decodeSilk } from '@/common/utils/audio' import { decodeSilk } from '@/common/utils/audio'
import { getConfigUtil } from '@/common/config' import { Schema } from '../BaseAction'
import path from 'node:path' import { stat, readFile } from 'node:fs/promises'
import fs from 'node:fs'
interface Payload extends GetFilePayload { interface Payload extends GetFilePayload {
out_format: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac' out_format: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac'
@@ -11,14 +11,18 @@ interface Payload extends GetFilePayload {
export default class GetRecord extends GetFileBase { export default class GetRecord extends GetFileBase {
actionName = ActionName.GetRecord actionName = ActionName.GetRecord
payloadSchema = Schema.object({
file: Schema.string().required(),
out_format: Schema.string().default('mp3')
})
protected async _handle(payload: Payload): Promise<GetFileResponse> { protected async _handle(payload: Payload): Promise<GetFileResponse> {
let res = await super._handle(payload) const res = await super._handle(payload)
res.file = await decodeSilk(this.ctx, res.file!, payload.out_format) res.file = await decodeSilk(this.ctx, res.file!, payload.out_format)
res.file_name = path.basename(res.file) res.file_name = path.basename(res.file)
res.file_size = fs.statSync(res.file).size.toString() res.file_size = (await stat(res.file)).size.toString()
if (getConfigUtil().getConfig().enableLocalFile2Url) { if (this.adapter.config.enableLocalFile2Url) {
res.base64 = fs.readFileSync(res.file, 'base64') res.base64 = await readFile(res.file, 'base64')
} }
return res return res
} }

View File

@@ -0,0 +1,21 @@
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
interface Payload {
group_id: number | string
name: string
parent_id?: '/'
}
export class CreateGroupFileFolder extends BaseAction<Payload, null> {
actionName = ActionName.GoCQHTTP_CreateGroupFileFolder
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
name: Schema.string().required(),
})
async _handle(payload: Payload) {
await this.ctx.ntGroupApi.createGroupFileFolder(payload.group_id.toString(), payload.name)
return null
}
}

View File

@@ -1,4 +1,4 @@
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { MessageUnique } from '@/common/utils/messageUnique' import { MessageUnique } from '@/common/utils/messageUnique'
@@ -6,13 +6,13 @@ interface Payload {
message_id: number | string message_id: number | string
} }
export default class GoCQHTTPDelEssenceMsg extends BaseAction<Payload, any> { export class DelEssenceMsg extends BaseAction<Payload, unknown> {
actionName = ActionName.GoCQHTTP_DelEssenceMsg; actionName = ActionName.GoCQHTTP_DelEssenceMsg
payloadSchema = Schema.object({
message_id: Schema.union([Number, String]).required()
})
protected async _handle(payload: Payload): Promise<any> { protected async _handle(payload: Payload) {
if (!payload.message_id) {
throw Error('message_id不能为空')
}
const msg = await MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id) const msg = await MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id)
if (!msg) { if (!msg) {
throw new Error('msg not found') throw new Error('msg not found')

View File

@@ -1,16 +1,22 @@
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
interface Payload { interface Payload {
group_id: string | number group_id: string | number
file_id: string file_id: string
busid?: 102 busid: number
} }
export class GoCQHTTPDelGroupFile extends BaseAction<Payload, void> { export class DelGroupFile extends BaseAction<Payload, null> {
actionName = ActionName.GoCQHTTP_DelGroupFile actionName = ActionName.GoCQHTTP_DelGroupFile
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
file_id: Schema.string().required(),
busid: Schema.number().default(102)
})
async _handle(payload: Payload) { async _handle(payload: Payload) {
await this.ctx.ntGroupApi.delGroupFile(payload.group_id.toString(), [payload.file_id]) await this.ctx.ntGroupApi.deleteGroupFile(payload.group_id.toString(), [payload.file_id], [payload.busid])
return null
} }
} }

View File

@@ -0,0 +1,20 @@
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
interface Payload {
group_id: number | string
folder_id: string
}
export class DelGroupFolder extends BaseAction<Payload, null> {
actionName = ActionName.GoCQHTTP_DelGroupFolder
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
folder_id: Schema.string().required()
})
async _handle(payload: Payload) {
await this.ctx.ntGroupApi.deleteGroupFileFolder(payload.group_id.toString(), payload.folder_id)
return null
}
}

View File

@@ -1,11 +1,12 @@
import BaseAction from '../BaseAction'
import fs from 'fs' import fs from 'fs'
import fsPromise from 'fs/promises' import fsPromise from 'fs/promises'
import path from 'node:path' import path from 'node:path'
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { calculateFileMD5, fetchFile } from '@/common/utils' import { calculateFileMD5, fetchFile } from '@/common/utils'
import { TEMP_DIR } from '@/common/globalVars' import { TEMP_DIR } from '@/common/globalVars'
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { Dict } from 'cosmokit'
interface Payload { interface Payload {
thread_count?: number thread_count?: number
@@ -19,8 +20,13 @@ interface FileResponse {
file: string file: string
} }
export default class GoCQHTTPDownloadFile extends BaseAction<Payload, FileResponse> { export class DownloadFile extends BaseAction<Payload, FileResponse> {
actionName = ActionName.GoCQHTTP_DownloadFile actionName = ActionName.GoCQHTTP_DownloadFile
payloadSchema = Schema.object({
url: String,
base64: String,
headers: Schema.union([String, Schema.array(String)])
})
protected async _handle(payload: Payload): Promise<FileResponse> { protected async _handle(payload: Payload): Promise<FileResponse> {
const isRandomName = !payload.name const isRandomName = !payload.name
@@ -51,7 +57,7 @@ export default class GoCQHTTPDownloadFile extends BaseAction<Payload, FileRespon
} }
getHeaders(headersIn?: string | string[]): Record<string, string> { getHeaders(headersIn?: string | string[]): Record<string, string> {
const headers = {} const headers: Dict = {}
if (typeof headersIn == 'string') { if (typeof headersIn == 'string') {
headersIn = headersIn.split('[\\r\\n]') headersIn = headersIn.split('[\\r\\n]')
} }

View File

@@ -1,8 +1,9 @@
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { OB11ForwardMessage, OB11Message, OB11MessageData } from '../../types' import { OB11ForwardMessage } from '../../types'
import { OB11Constructor } from '../../constructor' import { OB11Entities } from '../../entities'
import { ActionName } from '../types' import { ActionName } from '../types'
import { MessageUnique } from '@/common/utils/messageUnique' import { MessageUnique } from '@/common/utils/messageUnique'
import { filterNullable } from '@/common/utils/misc'
interface Payload { interface Payload {
message_id: string // long msg idgocq message_id: string // long msg idgocq
@@ -10,12 +11,17 @@ interface Payload {
} }
interface Response { interface Response {
messages: (OB11Message & { content: OB11MessageData })[] messages: OB11ForwardMessage[]
} }
export class GoCQHTTGetForwardMsgAction extends BaseAction<Payload, Response> { export class GetForwardMsg extends BaseAction<Payload, Response> {
actionName = ActionName.GoCQHTTP_GetForwardMsg actionName = ActionName.GoCQHTTP_GetForwardMsg
protected async _handle(payload: Payload): Promise<any> { payloadSchema = Schema.object({
message_id: String,
id: String
})
protected async _handle(payload: Payload) {
const msgId = payload.id || payload.message_id const msgId = payload.id || payload.message_id
if (!msgId) { if (!msgId) {
throw Error('message_id不能为空') throw Error('message_id不能为空')
@@ -32,19 +38,22 @@ export class GoCQHTTGetForwardMsgAction extends BaseAction<Payload, Response> {
const msgList = data.msgList const msgList = data.msgList
const messages = await Promise.all( const messages = await Promise.all(
msgList.map(async (msg) => { msgList.map(async (msg) => {
const resMsg = await OB11Constructor.message(this.ctx, msg) const resMsg = await OB11Entities.message(this.ctx, msg)
if (!resMsg) return
resMsg.message_id = MessageUnique.createMsg({ resMsg.message_id = MessageUnique.createMsg({
chatType: msg.chatType, chatType: msg.chatType,
peerUid: msg.peerUid, peerUid: msg.peerUid,
}, msg.msgId)! }, msg.msgId)
return resMsg return resMsg
}), })
) )
messages.map(v => { const forwardMessages = filterNullable(messages)
.map(v => {
const msg = v as Partial<OB11ForwardMessage> const msg = v as Partial<OB11ForwardMessage>
msg.content = msg.message msg.content = msg.message
delete msg.message delete msg.message
return msg as OB11ForwardMessage
}) })
return { messages } return { messages: forwardMessages }
} }
} }

View File

@@ -0,0 +1,28 @@
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
interface Payload {
group_id: number | string
}
interface Response {
can_at_all: boolean
remain_at_all_count_for_group: number
remain_at_all_count_for_uin: number
}
export class GetGroupAtAllRemain extends BaseAction<Payload, Response> {
actionName = ActionName.GoCQHTTP_GetGroupAtAllRemain
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required()
})
async _handle(payload: Payload) {
const data = await this.ctx.ntGroupApi.getGroupRemainAtTimes(payload.group_id.toString())
return {
can_at_all: data.atInfo.canAtAll,
remain_at_all_count_for_group: data.atInfo.RemainAtAllCountForGroup,
remain_at_all_count_for_uin: data.atInfo.RemainAtAllCountForUin
}
}
}

View File

@@ -0,0 +1,54 @@
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
import { OB11GroupFile, OB11GroupFileFolder } from '@/onebot11/types'
interface Payload {
group_id: string | number
folder_id: string
file_count: string | number
}
interface Response {
files: OB11GroupFile[]
folders: OB11GroupFileFolder[]
}
export class GetGroupFilesByFolder extends BaseAction<Payload, Response> {
actionName = ActionName.GoCQHTTP_GetGroupFilesByFolder
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
folder_id: Schema.string().required(),
file_count: Schema.union([Number, String]).default(50)
})
async _handle(payload: Payload) {
const data = await this.ctx.ntGroupApi.getGroupFileList(payload.group_id.toString(), {
sortType: 1,
fileCount: +payload.file_count,
startIndex: 0,
sortOrder: 2,
showOnlinedocFolder: 0,
folderId: payload.folder_id
})
return {
files: data.filter(item => item.fileInfo)
.map(item => {
const file = item.fileInfo!
return {
group_id: +item.peerId,
file_id: file.fileId,
file_name: file.fileName,
busid: file.busId,
file_size: +file.fileSize,
upload_time: file.uploadTime,
dead_time: file.deadTime,
modify_time: file.modifyTime,
download_times: file.downloadTimes,
uploader: +file.uploaderUin,
uploader_name: file.uploaderName
}
}),
folders: []
}
}
}

View File

@@ -1,46 +1,52 @@
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { OB11Message } from '../../types' import { OB11Message } from '../../types'
import { ActionName } from '../types' import { ActionName } from '../types'
import { ChatType } from '@/ntqqapi/types' import { ChatType } from '@/ntqqapi/types'
import { OB11Constructor } from '../../constructor' import { OB11Entities } from '../../entities'
import { RawMessage } from '@/ntqqapi/types' import { RawMessage } from '@/ntqqapi/types'
import { MessageUnique } from '@/common/utils/messageUnique' import { MessageUnique } from '@/common/utils/messageUnique'
import { filterNullable } from '@/common/utils/misc'
interface Payload { interface Payload {
group_id: number | string group_id: number | string
message_seq?: number message_seq?: number | string
count?: number count: number | string
reverseOrder?: boolean reverseOrder: boolean
} }
interface Response { interface Response {
messages: OB11Message[] messages: OB11Message[]
} }
export default class GoCQHTTPGetGroupMsgHistory extends BaseAction<Payload, Response> { export class GetGroupMsgHistory extends BaseAction<Payload, Response> {
actionName = ActionName.GoCQHTTP_GetGroupMsgHistory actionName = ActionName.GoCQHTTP_GetGroupMsgHistory
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
message_seq: Schema.union([Number, String]),
count: Schema.union([Number, String]).default(20),
reverseOrder: Schema.boolean().default(false),
})
protected async _handle(payload: Payload): Promise<Response> { protected async _handle(payload: Payload): Promise<Response> {
const count = payload.count || 20 const { count, reverseOrder } = payload
const isReverseOrder = payload.reverseOrder || true
const peer = { chatType: ChatType.group, peerUid: payload.group_id.toString() } const peer = { chatType: ChatType.group, peerUid: payload.group_id.toString() }
let msgList: RawMessage[] | undefined let msgList: RawMessage[] | undefined
// 包含 message_seq 0 // 包含 message_seq 0
if (!payload.message_seq) { if (!payload.message_seq) {
msgList = (await this.ctx.ntMsgApi.getLastestMsgByUids(peer, count))?.msgList msgList = (await this.ctx.ntMsgApi.getAioFirstViewLatestMsgs(peer, +count)).msgList
} else { } else {
const startMsgId = (await MessageUnique.getMsgIdAndPeerByShortId(payload.message_seq))?.MsgId const startMsgId = (await MessageUnique.getMsgIdAndPeerByShortId(+payload.message_seq))?.MsgId
if (!startMsgId) throw `消息${payload.message_seq}不存在` if (!startMsgId) throw new Error(`消息${payload.message_seq}不存在`)
msgList = (await this.ctx.ntMsgApi.getMsgHistory(peer, startMsgId, count)).msgList msgList = (await this.ctx.ntMsgApi.getMsgHistory(peer, startMsgId, +count)).msgList
} }
if (!msgList?.length) throw '未找到消息' if (!msgList?.length) throw new Error('未找到消息')
if (isReverseOrder) msgList.reverse() if (reverseOrder) msgList.reverse()
await Promise.all( await Promise.all(
msgList.map(async msg => { msgList.map(async msg => {
msg.msgShortId = MessageUnique.createMsg({ chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId) msg.msgShortId = MessageUnique.createMsg({ chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId)
}) })
) )
const ob11MsgList = await Promise.all(msgList.map((msg) => OB11Constructor.message(this.ctx, msg))) const ob11MsgList = await Promise.all(msgList.map((msg) => OB11Entities.message(this.ctx, msg)))
return { messages: ob11MsgList } return { messages: filterNullable(ob11MsgList) }
} }
} }

View File

@@ -0,0 +1,63 @@
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
import { OB11GroupFile, OB11GroupFileFolder } from '../../types'
interface Payload {
group_id: string | number
file_count: string | number
}
interface Response {
files: OB11GroupFile[]
folders: OB11GroupFileFolder[]
}
export class GetGroupRootFiles extends BaseAction<Payload, Response> {
actionName = ActionName.GoCQHTTP_GetGroupRootFiles
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
file_count: Schema.union([Number, String]).default(50),
})
async _handle(payload: Payload) {
const data = await this.ctx.ntGroupApi.getGroupFileList(payload.group_id.toString(), {
sortType: 1,
fileCount: +payload.file_count,
startIndex: 0,
sortOrder: 2,
showOnlinedocFolder: 0,
})
return {
files: data.filter(item => item.fileInfo)
.map(item => {
const file = item.fileInfo!
return {
group_id: +item.peerId,
file_id: file.fileId,
file_name: file.fileName,
busid: file.busId,
file_size: +file.fileSize,
upload_time: file.uploadTime,
dead_time: file.deadTime,
modify_time: file.modifyTime,
download_times: file.downloadTimes,
uploader: +file.uploaderUin,
uploader_name: file.uploaderName
}
}),
folders: data.filter(item => item.folderInfo)
.map(item => {
const folder = item.folderInfo!
return {
group_id: +item.peerId,
folder_id: folder.folderId,
folder_name: folder.folderName,
create_time: folder.createTime,
creator: +folder.createUin,
creator_name: folder.creatorName,
total_file_count: folder.totalFileCount
}
})
}
}
}

View File

@@ -0,0 +1,59 @@
import { BaseAction } from '../BaseAction'
import { GroupNotifyStatus } from '@/ntqqapi/types'
import { ActionName } from '../types'
interface Response {
invited_requests: {
request_id: number
invitor_uin: number
invitor_nick: string
group_id: number
group_name: string
checked: boolean
actor: number
}[]
join_requests: {
request_id: number
requester_uin: number
requester_nick: string
message: string
group_id: number
group_name: string
checked: boolean
actor: number
}[]
}
export class GetGroupSystemMsg extends BaseAction<void, Response> {
actionName = ActionName.GoCQHTTP_GetGroupSystemMsg
async _handle() {
const singleScreenNotifies = await this.ctx.ntGroupApi.getSingleScreenNotifies(10)
const data: Response = { invited_requests: [], join_requests: [] }
for (const notify of singleScreenNotifies) {
if (notify.type == 1) {
data.invited_requests.push({
request_id: +notify.seq,
invitor_uin: Number(await this.ctx.ntUserApi.getUinByUid(notify.user1.uid)),
invitor_nick: notify.user1.nickName,
group_id: +notify.group.groupCode,
group_name: notify.group.groupName,
checked: notify.status !== GroupNotifyStatus.KUNHANDLE,
actor: notify.user2?.uid ? Number(await this.ctx.ntUserApi.getUinByUid(notify.user2.uid)) : 0
})
} else if (notify.type == 7) {
data.join_requests.push({
request_id: +notify.seq,
requester_uin: Number(await this.ctx.ntUserApi.getUinByUid(notify.user1.uid)),
requester_nick: notify.user1.nickName,
message: notify.postscript,
group_id: +notify.group.groupCode,
group_name: notify.group.groupName,
checked: notify.status !== GroupNotifyStatus.KUNHANDLE,
actor: notify.user2?.uid ? Number(await this.ctx.ntUserApi.getUinByUid(notify.user2.uid)) : 0
})
}
}
return data
}
}

View File

@@ -1,6 +1,6 @@
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { OB11User } from '../../types' import { OB11User } from '../../types'
import { OB11Constructor } from '../../constructor' import { OB11Entities } from '../../entities'
import { ActionName } from '../types' import { ActionName } from '../types'
import { getBuildVersion } from '@/common/utils' import { getBuildVersion } from '@/common/utils'
import { OB11UserSex } from '../../types' import { OB11UserSex } from '../../types'
@@ -10,8 +10,11 @@ interface Payload {
user_id: number | string user_id: number | string
} }
export default class GoCQHTTPGetStrangerInfo extends BaseAction<Payload, OB11User> { export class GetStrangerInfo extends BaseAction<Payload, OB11User> {
actionName = ActionName.GoCQHTTP_GetStrangerInfo actionName = ActionName.GoCQHTTP_GetStrangerInfo
payloadSchema = Schema.object({
user_id: Schema.union([Number, String]).required()
})
protected async _handle(payload: Payload): Promise<OB11User> { protected async _handle(payload: Payload): Promise<OB11User> {
if (!(getBuildVersion() >= 26702)) { if (!(getBuildVersion() >= 26702)) {
@@ -33,7 +36,7 @@ export default class GoCQHTTPGetStrangerInfo extends BaseAction<Payload, OB11Use
return ret return ret
} }
const data = { ...extendData, ...(await this.ctx.ntUserApi.getUserDetailInfo(uid)) } const data = { ...extendData, ...(await this.ctx.ntUserApi.getUserDetailInfo(uid)) }
return OB11Constructor.stranger(data) return OB11Entities.stranger(data)
} else { } else {
const user_id = payload.user_id.toString() const user_id = payload.user_id.toString()
const extendData = await this.ctx.ntUserApi.getUserDetailInfoByUinV2(user_id) const extendData = await this.ctx.ntUserApi.getUserDetailInfoByUinV2(user_id)
@@ -52,7 +55,7 @@ export default class GoCQHTTPGetStrangerInfo extends BaseAction<Payload, OB11Use
return ret return ret
} }
const data = { ...extendData, ...(await this.ctx.ntUserApi.getUserDetailInfo(uid)) } const data = { ...extendData, ...(await this.ctx.ntUserApi.getUserDetailInfo(uid)) }
return OB11Constructor.stranger(data) return OB11Entities.stranger(data)
} }
} }
} }

View File

@@ -0,0 +1,23 @@
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
import { MessageUnique } from '@/common/utils/messageUnique'
interface Payload {
message_id: number | string
}
export class MarkMsgAsRead extends BaseAction<Payload, null> {
actionName = ActionName.GoCQHTTP_MarkMsgAsRead
payloadSchema = Schema.object({
message_id: Schema.union([Number, String]).required()
})
protected async _handle(payload: Payload) {
const msg = await MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id)
if (!msg) {
throw new Error('msg not found')
}
await this.ctx.ntMsgApi.setMsgRead(msg.Peer)
return null
}
}

View File

@@ -1,4 +1,4 @@
import BaseAction from '../BaseAction' import { BaseAction } from '../BaseAction'
import { handleQuickOperation, QuickOperation, QuickOperationEvent } from '../../helper/quickOperation' import { handleQuickOperation, QuickOperation, QuickOperationEvent } from '../../helper/quickOperation'
import { ActionName } from '../types' import { ActionName } from '../types'
@@ -7,8 +7,9 @@ interface Payload {
operation: QuickOperation operation: QuickOperation
} }
export class GoCQHTTHandleQuickOperation extends BaseAction<Payload, null> { export class HandleQuickOperation extends BaseAction<Payload, null> {
actionName = ActionName.GoCQHTTP_HandleQuickOperation actionName = ActionName.GoCQHTTP_HandleQuickOperation
protected async _handle(payload: Payload): Promise<null> { protected async _handle(payload: Payload): Promise<null> {
handleQuickOperation(this.ctx, payload.context, payload.operation).catch(e => this.ctx.logger.error(e)) handleQuickOperation(this.ctx, payload.context, payload.operation).catch(e => this.ctx.logger.error(e))
return null return null

View File

@@ -1,21 +1,180 @@
import SendMsg from '../msg/SendMsg' import { unlink } from 'node:fs/promises'
import { OB11PostSendMsg } from '../../types' import { OB11MessageNode } from '../../types'
import { ActionName } from '../types' import { ActionName } from '../types'
import { convertMessage2List } from '../../helper/createMessage' import { BaseAction, Schema } from '../BaseAction'
import { Peer } from '@/ntqqapi/types/msg'
import { ChatType, ElementType, RawMessage, SendMessageElement } from '@/ntqqapi/types'
import { MessageUnique } from '@/common/utils/messageUnique'
import { selfInfo } from '@/common/globalVars'
import { convertMessage2List, createSendElements, sendMsg, createPeer, CreatePeerMode } from '../../helper/createMessage'
export class GoCQHTTPSendForwardMsg extends SendMsg { interface Payload {
user_id?: string | number
group_id?: string | number
messages: OB11MessageNode[]
message_type?: 'group' | 'private'
}
interface Response {
message_id: number
forward_id?: string
}
export class SendForwardMsg extends BaseAction<Payload, Response> {
actionName = ActionName.GoCQHTTP_SendForwardMsg actionName = ActionName.GoCQHTTP_SendForwardMsg
payloadSchema = Schema.object({
user_id: Schema.union([Number, String]),
group_id: Schema.union([Number, String]),
messages: Schema.array(Schema.any()).required(),
message_type: Schema.union(['group', 'private'])
})
protected async check(payload: OB11PostSendMsg) { protected async _handle(payload: Payload) {
if (payload.messages) payload.message = convertMessage2List(payload.messages) let contextMode = CreatePeerMode.Normal
return super.check(payload) if (payload.message_type === 'group') {
contextMode = CreatePeerMode.Group
} else if (payload.message_type === 'private') {
contextMode = CreatePeerMode.Private
}
const peer = await createPeer(this.ctx, payload, contextMode)
const returnMsg = await this.handleForwardNode(peer, payload.messages)
return { message_id: returnMsg.msgShortId! }
}
private async cloneMsg(msg: RawMessage): Promise<RawMessage | undefined> {
this.ctx.logger.info('克隆的目标消息', msg)
const sendElements: SendMessageElement[] = []
for (const ele of msg.elements) {
sendElements.push(ele as SendMessageElement)
}
if (sendElements.length === 0) {
this.ctx.logger.warn('需要clone的消息无法解析将会忽略掉', msg)
}
this.ctx.logger.info('克隆消息', sendElements)
try {
const peer = {
chatType: ChatType.friend,
peerUid: selfInfo.uid
}
const nodeMsg = await this.ctx.ntMsgApi.sendMsg(peer, sendElements)
await this.ctx.sleep(400)
return nodeMsg
} catch (e) {
this.ctx.logger.warn(e, '克隆转发消息失败,将忽略本条消息', msg)
} }
} }
export class GoCQHTTPSendPrivateForwardMsg extends GoCQHTTPSendForwardMsg { // 返回一个合并转发的消息id
private async handleForwardNode(destPeer: Peer, messageNodes: OB11MessageNode[]) {
const selfPeer = {
chatType: ChatType.friend,
peerUid: selfInfo.uid,
}
let nodeMsgIds: string[] = []
// 先判断一遍是不是id和自定义混用
for (const messageNode of messageNodes) {
// 一个node表示一个人的消息
const nodeId = messageNode.data.id
// 有nodeId表示一个子转发消息卡片
if (nodeId) {
const nodeMsg = await MessageUnique.getMsgIdAndPeerByShortId(+nodeId) || await MessageUnique.getPeerByMsgId(nodeId)
if (!nodeMsg) {
this.ctx.logger.warn('转发消息失败,未找到消息', nodeId)
continue
}
nodeMsgIds.push(nodeMsg.MsgId)
}
else {
// 自定义的消息
// 提取消息段发给自己生成消息id
try {
const { sendElements, deleteAfterSentFiles } = await createSendElements(
this.ctx,
convertMessage2List(messageNode.data.content),
destPeer
)
this.ctx.logger.info('开始生成转发节点', sendElements)
const sendElementsSplit: SendMessageElement[][] = []
let splitIndex = 0
for (const ele of sendElements) {
if (!sendElementsSplit[splitIndex]) {
sendElementsSplit[splitIndex] = []
}
if (ele.elementType === ElementType.FILE || ele.elementType === ElementType.VIDEO) {
if (sendElementsSplit[splitIndex].length > 0) {
splitIndex++
}
sendElementsSplit[splitIndex] = [ele]
splitIndex++
}
else {
sendElementsSplit[splitIndex].push(ele)
}
this.ctx.logger.info(sendElementsSplit)
}
// log("分割后的转发节点", sendElementsSplit)
for (const eles of sendElementsSplit) {
const nodeMsg = await sendMsg(this.ctx, selfPeer, eles, [])
if (!nodeMsg) {
this.ctx.logger.warn('转发节点生成失败', eles)
continue
}
nodeMsgIds.push(nodeMsg.msgId)
await this.ctx.sleep(400)
}
deleteAfterSentFiles.map(path => unlink(path))
} catch (e) {
this.ctx.logger.error('生成转发消息节点失败', e)
}
}
}
// 检查srcPeer是否一致不一致则需要克隆成自己的消息, 让所有srcPeer都变成自己的使其保持一致才能够转发
const nodeMsgArray: RawMessage[] = []
let srcPeer: Peer | null = null
let needSendSelf = false
for (const msgId of nodeMsgIds) {
const nodeMsgPeer = await MessageUnique.getPeerByMsgId(msgId)
if (nodeMsgPeer) {
const nodeMsg = (await this.ctx.ntMsgApi.getMsgsByMsgId(nodeMsgPeer.Peer, [msgId])).msgList[0]
srcPeer = srcPeer ?? { chatType: nodeMsg.chatType, peerUid: nodeMsg.peerUid }
if (srcPeer.peerUid !== nodeMsg.peerUid) {
needSendSelf = true
}
nodeMsgArray.push(nodeMsg)
}
}
nodeMsgIds = nodeMsgArray.map((msg) => msg.msgId)
if (needSendSelf) {
for (const msg of nodeMsgArray) {
if (msg.peerUid === selfPeer.peerUid) continue
await this.cloneMsg(msg)
}
}
if (nodeMsgIds.length === 0) {
throw Error('转发消息失败,节点为空')
}
const returnMsg = await this.ctx.ntMsgApi.multiForwardMsg(srcPeer!, destPeer, nodeMsgIds)
returnMsg.msgShortId = MessageUnique.createMsg(destPeer, returnMsg.msgId)
return returnMsg
}
}
export class SendPrivateForwardMsg extends SendForwardMsg {
actionName = ActionName.GoCQHTTP_SendPrivateForwardMsg actionName = ActionName.GoCQHTTP_SendPrivateForwardMsg
protected _handle(payload: Payload) {
payload.message_type = 'private'
return super._handle(payload)
}
} }
export class GoCQHTTPSendGroupForwardMsg extends GoCQHTTPSendForwardMsg { export class SendGroupForwardMsg extends SendForwardMsg {
actionName = ActionName.GoCQHTTP_SendGroupForwardMsg actionName = ActionName.GoCQHTTP_SendGroupForwardMsg
protected _handle(payload: Payload) {
payload.message_type = 'group'
return super._handle(payload)
}
} }

View File

@@ -0,0 +1,58 @@
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
import { unlink } from 'fs/promises'
import { checkFileReceived, uri2local } from '@/common/utils/file'
interface Payload {
group_id: number | string
content: string
image?: string
pinned: number | string //扩展
confirm_required: number | string //扩展
}
export class SendGroupNotice extends BaseAction<Payload, null> {
actionName = ActionName.GoCQHTTP_SendGroupNotice
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
content: Schema.string().required(),
image: Schema.string(),
pinned: Schema.union([Number, String]).default(0),
confirm_required: Schema.union([Number, String]).default(1)
})
async _handle(payload: Payload) {
const groupCode = payload.group_id.toString()
const pinned = +payload.pinned
const confirmRequired = +payload.confirm_required
let picInfo: { id: string, width: number, height: number } | undefined
if (payload.image) {
const { path, isLocal, success, errMsg } = await uri2local(payload.image, undefined, true)
if (!success) {
throw new Error(`设置群公告失败, 错误信息: uri2local: ${errMsg}`)
}
await checkFileReceived(path, 5000) // 文件不存在QQ会崩溃需要提前判断
const result = await this.ctx.ntGroupApi.uploadGroupBulletinPic(groupCode, path)
if (result.errCode !== 0) {
throw new Error(`设置群公告失败, 错误信息: uploadGroupBulletinPic: ${result.errMsg}`)
}
if (!isLocal) {
unlink(path)
}
picInfo = result.picInfo
}
const res = await this.ctx.ntGroupApi.publishGroupBulletin(groupCode, {
text: encodeURIComponent(payload.content),
oldFeedsId: '',
pinned,
confirmRequired,
picInfo
})
if (res.result !== 0) {
throw new Error(`设置群公告失败, 错误信息: ${res.errMsg}`)
}
return null
}
}

View File

@@ -1,4 +1,4 @@
import BaseAction from '../BaseAction' import { BaseAction } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { MessageUnique } from '@/common/utils/messageUnique' import { MessageUnique } from '@/common/utils/messageUnique'
@@ -6,10 +6,10 @@ interface Payload {
message_id: number | string message_id: number | string
} }
export default class GoCQHTTPSetEssenceMsg extends BaseAction<Payload, any> { export class SetEssenceMsg extends BaseAction<Payload, unknown> {
actionName = ActionName.GoCQHTTP_SetEssenceMsg; actionName = ActionName.GoCQHTTP_SetEssenceMsg
protected async _handle(payload: Payload): Promise<any> { protected async _handle(payload: Payload) {
if (!payload.message_id) { if (!payload.message_id) {
throw Error('message_id不能为空') throw Error('message_id不能为空')
} }

View File

@@ -1,69 +1,49 @@
import fs from 'node:fs' import { BaseAction } from '../BaseAction'
import BaseAction from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { SendMsgElementConstructor } from '@/ntqqapi/constructor' import { SendElementEntities } from '@/ntqqapi/entities'
import { ChatType, SendFileElement } from '@/ntqqapi/types'
import { uri2local } from '@/common/utils' import { uri2local } from '@/common/utils'
import { Peer } from '@/ntqqapi/types' import { sendMsg, createPeer, CreatePeerMode } from '../../helper/createMessage'
import { sendMsg } from '../../helper/createMessage'
interface Payload { interface UploadGroupFilePayload {
user_id: number | string group_id: number | string
group_id?: number | string
file: string file: string
name: string name: string
folder?: string folder?: string
folder_id?: string folder_id?: string
} }
export class GoCQHTTPUploadGroupFile extends BaseAction<Payload, null> { export class UploadGroupFile extends BaseAction<UploadGroupFilePayload, null> {
actionName = ActionName.GoCQHTTP_UploadGroupFile actionName = ActionName.GoCQHTTP_UploadGroupFile
protected async _handle(payload: Payload): Promise<null> { protected async _handle(payload: UploadGroupFilePayload): Promise<null> {
let file = payload.file const { success, errMsg, path, fileName } = await uri2local(payload.file)
if (fs.existsSync(file)) { if (!success) {
file = `file://${file}` throw new Error(errMsg)
} }
const downloadResult = await uri2local(file) const file = await SendElementEntities.file(this.ctx, path, payload.name || fileName, payload.folder ?? payload.folder_id)
if (!downloadResult.success) { const peer = await createPeer(this.ctx, payload, CreatePeerMode.Group)
throw new Error(downloadResult.errMsg) await sendMsg(this.ctx, peer, [file], [])
}
const sendFileEle = await SendMsgElementConstructor.file(this.ctx, downloadResult.path, payload.name, payload.folder_id)
await sendMsg(this.ctx, {
chatType: ChatType.group,
peerUid: payload.group_id?.toString()!,
}, [sendFileEle], [], true)
return null return null
} }
} }
export class GoCQHTTPUploadPrivateFile extends BaseAction<Payload, null> { interface UploadPrivateFilePayload {
user_id: number | string
file: string
name: string
}
export class UploadPrivateFile extends BaseAction<UploadPrivateFilePayload, null> {
actionName = ActionName.GoCQHTTP_UploadPrivateFile actionName = ActionName.GoCQHTTP_UploadPrivateFile
async getPeer(payload: Payload): Promise<Peer> { protected async _handle(payload: UploadPrivateFilePayload): Promise<null> {
if (payload.user_id) { const { success, errMsg, path, fileName } = await uri2local(payload.file)
const peerUid = await this.ctx.ntUserApi.getUidByUin(payload.user_id.toString()) if (!success) {
if (!peerUid) { throw new Error(errMsg)
throw `私聊${payload.user_id}不存在`
} }
const isBuddy = await this.ctx.ntFriendApi.isBuddy(peerUid) const sendFileEle = await SendElementEntities.file(this.ctx, path, payload.name || fileName)
return { chatType: isBuddy ? ChatType.friend : ChatType.temp, peerUid } const peer = await createPeer(this.ctx, payload, CreatePeerMode.Private)
} await sendMsg(this.ctx, peer, [sendFileEle], [])
throw '缺少参数 user_id'
}
protected async _handle(payload: Payload): Promise<null> {
const peer = await this.getPeer(payload)
let file = payload.file
if (fs.existsSync(file)) {
file = `file://${file}`
}
const downloadResult = await uri2local(file)
if (!downloadResult.success) {
throw new Error(downloadResult.errMsg)
}
const sendFileEle: SendFileElement = await SendMsgElementConstructor.file(this.ctx, downloadResult.path, payload.name)
await sendMsg(this.ctx, peer, [sendFileEle], [], true)
return null return null
} }
} }

View File

@@ -1,5 +1,5 @@
import { GroupEssenceMsgRet } from '@/ntqqapi/api' import { GroupEssenceMsgRet } from '@/ntqqapi/api'
import BaseAction from '../BaseAction' import { BaseAction } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
interface PayloadType { interface PayloadType {
@@ -10,7 +10,7 @@ interface PayloadType {
export class GetGroupEssence extends BaseAction<PayloadType, GroupEssenceMsgRet | void> { export class GetGroupEssence extends BaseAction<PayloadType, GroupEssenceMsgRet | void> {
actionName = ActionName.GoCQHTTP_GetEssenceMsg actionName = ActionName.GoCQHTTP_GetEssenceMsg
protected async _handle(payload: PayloadType) { protected async _handle() {
throw '此 api 暂不支持' throw '此 api 暂不支持'
} }
} }

View File

@@ -1,17 +1,16 @@
import { WebHonorType } from '@/ntqqapi/api' import { WebHonorType } from '@/ntqqapi/api'
import { ActionName } from '../types' import { ActionName } from '../types'
import BaseAction from '../BaseAction' import { BaseAction } from '../BaseAction'
interface Payload { interface Payload {
group_id: number group_id: number
type?: WebHonorType type?: WebHonorType
} }
export class GetGroupHonorInfo extends BaseAction<Payload, Array<any>> { export class GetGroupHonorInfo extends BaseAction<Payload, unknown> {
actionName = ActionName.GetGroupHonorInfo actionName = ActionName.GetGroupHonorInfo
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
// console.log(await NTQQUserApi.getRobotUinRange())
if (!payload.group_id) { if (!payload.group_id) {
throw '缺少参数group_id' throw '缺少参数group_id'
} }

View File

@@ -1,6 +1,6 @@
import { OB11Group } from '../../types' import { OB11Group } from '../../types'
import { OB11Constructor } from '../../constructor' import { OB11Entities } from '../../entities'
import BaseAction from '../BaseAction' import { BaseAction } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
interface Payload { interface Payload {
@@ -13,7 +13,7 @@ class GetGroupInfo extends BaseAction<Payload, OB11Group> {
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
const group = (await this.ctx.ntGroupApi.getGroups()).find(e => e.groupCode == payload.group_id.toString()) const group = (await this.ctx.ntGroupApi.getGroups()).find(e => e.groupCode == payload.group_id.toString())
if (group) { if (group) {
return OB11Constructor.group(group) return OB11Entities.group(group)
} else { } else {
throw `${payload.group_id}不存在` throw `${payload.group_id}不存在`
} }

View File

@@ -1,6 +1,6 @@
import { OB11Group } from '../../types' import { OB11Group } from '../../types'
import { OB11Constructor } from '../../constructor' import { OB11Entities } from '../../entities'
import BaseAction from '../BaseAction' import { BaseAction } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
interface Payload { interface Payload {
@@ -10,9 +10,9 @@ interface Payload {
class GetGroupList extends BaseAction<Payload, OB11Group[]> { class GetGroupList extends BaseAction<Payload, OB11Group[]> {
actionName = ActionName.GetGroupList actionName = ActionName.GetGroupList
protected async _handle(payload: Payload) { protected async _handle() {
const groupList = await this.ctx.ntGroupApi.getGroups(payload?.no_cache === true || payload?.no_cache === 'true') const groupList = await this.ctx.ntGroupApi.getGroups()
return OB11Constructor.groups(groupList) return OB11Entities.groups(groupList)
} }
} }

View File

@@ -1,6 +1,6 @@
import BaseAction from '../BaseAction' import { BaseAction } from '../BaseAction'
import { OB11GroupMember } from '../../types' import { OB11GroupMember } from '../../types'
import { OB11Constructor } from '../../constructor' import { OB11Entities } from '../../entities'
import { ActionName } from '../types' import { ActionName } from '../types'
import { selfInfo } from '@/common/globalVars' import { selfInfo } from '@/common/globalVars'
import { isNullable } from 'cosmokit' import { isNullable } from 'cosmokit'
@@ -18,11 +18,11 @@ class GetGroupMemberInfo extends BaseAction<Payload, OB11GroupMember> {
if (member) { if (member) {
if (isNullable(member.sex)) { if (isNullable(member.sex)) {
//log('获取群成员详细信息') //log('获取群成员详细信息')
const info = await this.ctx.ntUserApi.getUserDetailInfo(member.uid, true) const info = await this.ctx.ntUserApi.getUserDetailInfo(member.uid)
//log('群成员详细信息结果', info) //log('群成员详细信息结果', info)
Object.assign(member, info) Object.assign(member, info)
} }
const ret = OB11Constructor.groupMember(payload.group_id.toString(), member) const ret = OB11Entities.groupMember(payload.group_id.toString(), member)
const self = await this.ctx.ntGroupApi.getGroupMember(payload.group_id.toString(), selfInfo.uid) const self = await this.ctx.ntGroupApi.getGroupMember(payload.group_id.toString(), selfInfo.uid)
if (self?.role === 3 || self?.role === 4) { if (self?.role === 3 || self?.role === 4) {
const webGroupMembers = await this.ctx.ntWebApi.getGroupMembers(payload.group_id.toString()) const webGroupMembers = await this.ctx.ntWebApi.getGroupMembers(payload.group_id.toString())

View File

@@ -1,6 +1,6 @@
import { OB11GroupMember } from '../../types' import { OB11GroupMember } from '../../types'
import { OB11Constructor } from '../../constructor' import { OB11Entities } from '../../entities'
import BaseAction from '../BaseAction' import { BaseAction } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { selfInfo } from '@/common/globalVars' import { selfInfo } from '@/common/globalVars'
@@ -13,11 +13,16 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
actionName = ActionName.GetGroupMemberList actionName = ActionName.GetGroupMemberList
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
const groupMembers = await this.ctx.ntGroupApi.getGroupMembers(payload.group_id.toString()) const groupCode = payload.group_id.toString()
let groupMembers = await this.ctx.ntGroupApi.getGroupMembers(groupCode)
if (groupMembers.size === 0) {
await this.ctx.sleep(100)
groupMembers = await this.ctx.ntGroupApi.getGroupMembers(groupCode)
}
const groupMembersArr = Array.from(groupMembers.values()) const groupMembersArr = Array.from(groupMembers.values())
let _groupMembers = groupMembersArr.map(item => { const _groupMembers = groupMembersArr.map(item => {
return OB11Constructor.groupMember(payload.group_id.toString(), item) return OB11Entities.groupMember(groupCode, item)
}) })
const MemberMap: Map<number, OB11GroupMember> = new Map<number, OB11GroupMember>() const MemberMap: Map<number, OB11GroupMember> = new Map<number, OB11GroupMember>()
@@ -25,8 +30,8 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
for (let i = 0, len = _groupMembers.length; i < len; i++) { for (let i = 0, len = _groupMembers.length; i < len; i++) {
// 保证基础数据有这个 同时避免群管插件过于依赖这个杀了 // 保证基础数据有这个 同时避免群管插件过于依赖这个杀了
_groupMembers[i].join_time = date _groupMembers[i].join_time ||= date
_groupMembers[i].last_sent_time = date _groupMembers[i].last_sent_time ||= date
MemberMap.set(_groupMembers[i].user_id, _groupMembers[i]) MemberMap.set(_groupMembers[i].user_id, _groupMembers[i])
} }
@@ -34,24 +39,24 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
const isPrivilege = selfRole === 3 || selfRole === 4 const isPrivilege = selfRole === 3 || selfRole === 4
if (isPrivilege) { if (isPrivilege) {
const webGroupMembers = await this.ctx.ntWebApi.getGroupMembers(payload.group_id.toString()) const webGroupMembers = await this.ctx.ntWebApi.getGroupMembers(groupCode)
for (let i = 0, len = webGroupMembers.length; i < len; i++) { for (let i = 0, len = webGroupMembers.length; i < len; i++) {
if (!webGroupMembers[i]?.uin) { if (!webGroupMembers[i]?.uin) {
continue continue
} }
const MemberData = MemberMap.get(webGroupMembers[i]?.uin) const MemberData = MemberMap.get(webGroupMembers[i]?.uin)
if (MemberData) { if (MemberData) {
if (MemberData.join_time === date) {
MemberData.join_time = webGroupMembers[i]?.join_time MemberData.join_time = webGroupMembers[i]?.join_time
MemberData.last_sent_time = webGroupMembers[i]?.last_speak_time MemberData.last_sent_time = webGroupMembers[i]?.last_speak_time
}
MemberData.qage = webGroupMembers[i]?.qage MemberData.qage = webGroupMembers[i]?.qage
MemberData.level = webGroupMembers[i]?.lv.level.toString()
MemberMap.set(webGroupMembers[i]?.uin, MemberData) MemberMap.set(webGroupMembers[i]?.uin, MemberData)
} }
} }
} }
_groupMembers = Array.from(MemberMap.values()) return Array.from(MemberMap.values())
return _groupMembers
} }
} }

View File

@@ -1,10 +1,10 @@
import BaseAction from '../BaseAction' import { BaseAction } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
export default class GetGuildList extends BaseAction<null, null> { export default class GetGuildList extends BaseAction<null, null> {
actionName = ActionName.GetGuildList actionName = ActionName.GetGuildList
protected async _handle(payload: null): Promise<null> { protected async _handle() {
return null return null
} }
} }

View File

@@ -1,14 +1,13 @@
import SendMsg from '../msg/SendMsg' import SendMsg from '../msg/SendMsg'
import { ActionName, BaseCheckResult } from '../types' import { ActionName } from '../types'
import { OB11PostSendMsg } from '../../types' import { OB11PostSendMsg } from '../../types'
class SendGroupMsg extends SendMsg { class SendGroupMsg extends SendMsg {
actionName = ActionName.SendGroupMsg actionName = ActionName.SendGroupMsg
protected async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> { protected _handle(payload: OB11PostSendMsg) {
delete (payload as Partial<OB11PostSendMsg>).user_id
payload.message_type = 'group' payload.message_type = 'group'
return super.check(payload) return super._handle(payload)
} }
} }

View File

@@ -1,4 +1,4 @@
import BaseAction from '../BaseAction' import { BaseAction } from '../BaseAction'
import { GroupRequestOperateTypes } from '@/ntqqapi/types' import { GroupRequestOperateTypes } from '@/ntqqapi/types'
import { ActionName } from '../types' import { ActionName } from '../types'
@@ -14,9 +14,10 @@ export default class SetGroupAddRequest extends BaseAction<Payload, null> {
protected async _handle(payload: Payload): Promise<null> { protected async _handle(payload: Payload): Promise<null> {
const flag = payload.flag.toString() const flag = payload.flag.toString()
const approve = payload.approve?.toString() !== 'false' const approve = payload.approve?.toString() !== 'false'
await this.ctx.ntGroupApi.handleGroupRequest(flag, await this.ctx.ntGroupApi.handleGroupRequest(
flag,
approve ? GroupRequestOperateTypes.approve : GroupRequestOperateTypes.reject, approve ? GroupRequestOperateTypes.approve : GroupRequestOperateTypes.reject,
payload.reason || '' payload.reason
) )
return null return null
} }

View File

@@ -1,4 +1,4 @@
import BaseAction from '../BaseAction' import { BaseAction } from '../BaseAction'
import { GroupMemberRole } from '@/ntqqapi/types' import { GroupMemberRole } from '@/ntqqapi/types'
import { ActionName } from '../types' import { ActionName } from '../types'

View File

@@ -1,4 +1,4 @@
import BaseAction from '../BaseAction' import { BaseAction } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
interface Payload { interface Payload {

View File

@@ -1,4 +1,4 @@
import BaseAction from '../BaseAction' import { BaseAction } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
interface Payload { interface Payload {

View File

@@ -1,4 +1,4 @@
import BaseAction from '../BaseAction' import { BaseAction } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
interface Payload { interface Payload {

View File

@@ -1,4 +1,4 @@
import BaseAction from '../BaseAction' import { BaseAction } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
interface Payload { interface Payload {
@@ -6,10 +6,10 @@ interface Payload {
is_dismiss: boolean is_dismiss: boolean
} }
export default class SetGroupLeave extends BaseAction<Payload, any> { export default class SetGroupLeave extends BaseAction<Payload, void> {
actionName = ActionName.SetGroupLeave actionName = ActionName.SetGroupLeave
protected async _handle(payload: Payload): Promise<any> { protected async _handle(payload: Payload) {
try { try {
await this.ctx.ntGroupApi.quitGroup(payload.group_id.toString()) await this.ctx.ntGroupApi.quitGroup(payload.group_id.toString())
} catch (e) { } catch (e) {

View File

@@ -1,4 +1,4 @@
import BaseAction from '../BaseAction' import { BaseAction } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
interface Payload { interface Payload {

View File

@@ -1,4 +1,4 @@
import BaseAction from '../BaseAction' import { BaseAction } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
interface Payload { interface Payload {

View File

@@ -1,3 +1,4 @@
import type Adapter from '../adapter'
import GetMsg from './msg/GetMsg' import GetMsg from './msg/GetMsg'
import GetLoginInfo from './system/GetLoginInfo' import GetLoginInfo from './system/GetLoginInfo'
import { GetFriendList, GetFriendWithCategory } from './user/GetFriendList' import { GetFriendList, GetFriendWithCategory } from './user/GetFriendList'
@@ -9,17 +10,17 @@ import SendGroupMsg from './group/SendGroupMsg'
import SendPrivateMsg from './msg/SendPrivateMsg' import SendPrivateMsg from './msg/SendPrivateMsg'
import SendMsg from './msg/SendMsg' import SendMsg from './msg/SendMsg'
import DeleteMsg from './msg/DeleteMsg' import DeleteMsg from './msg/DeleteMsg'
import BaseAction from './BaseAction' import { BaseAction } from './BaseAction'
import GetVersionInfo from './system/GetVersionInfo' import GetVersionInfo from './system/GetVersionInfo'
import CanSendRecord from './system/CanSendRecord' import CanSendRecord from './system/CanSendRecord'
import CanSendImage from './system/CanSendImage' import CanSendImage from './system/CanSendImage'
import GetStatus from './system/GetStatus' import GetStatus from './system/GetStatus'
import { import {
GoCQHTTPSendForwardMsg, SendForwardMsg,
GoCQHTTPSendGroupForwardMsg, SendGroupForwardMsg,
GoCQHTTPSendPrivateForwardMsg, SendPrivateForwardMsg,
} from './go-cqhttp/SendForwardMsg' } from './go-cqhttp/SendForwardMsg'
import GoCQHTTPGetStrangerInfo from './go-cqhttp/GetStrangerInfo' import { GetStrangerInfo } from './go-cqhttp/GetStrangerInfo'
import SendLike from './user/SendLike' import SendLike from './user/SendLike'
import SetGroupAddRequest from './group/SetGroupAddRequest' import SetGroupAddRequest from './group/SetGroupAddRequest'
import SetGroupLeave from './group/SetGroupLeave' import SetGroupLeave from './group/SetGroupLeave'
@@ -34,27 +35,38 @@ import SetGroupAdmin from './group/SetGroupAdmin'
import SetGroupCard from './group/SetGroupCard' import SetGroupCard from './group/SetGroupCard'
import GetImage from './file/GetImage' import GetImage from './file/GetImage'
import GetRecord from './file/GetRecord' import GetRecord from './file/GetRecord'
import GoCQHTTPMarkMsgAsRead from './msg/MarkMsgAsRead' import { MarkMsgAsRead } from './go-cqhttp/MarkMsgAsRead'
import CleanCache from './system/CleanCache' import CleanCache from './system/CleanCache'
import { GoCQHTTPUploadGroupFile, GoCQHTTPUploadPrivateFile } from './go-cqhttp/UploadFile' import { UploadGroupFile, UploadPrivateFile } from './go-cqhttp/UploadFile'
import { GetConfigAction, SetConfigAction } from './llonebot/Config' import { GetConfigAction, SetConfigAction } from './llonebot/Config'
import GetGroupAddRequest from './llonebot/GetGroupAddRequest' import GetGroupAddRequest from './llonebot/GetGroupAddRequest'
import SetQQAvatar from './llonebot/SetQQAvatar' import SetQQAvatar from './llonebot/SetQQAvatar'
import GoCQHTTPDownloadFile from './go-cqhttp/DownloadFile' import { DownloadFile } from './go-cqhttp/DownloadFile'
import GoCQHTTPGetGroupMsgHistory from './go-cqhttp/GetGroupMsgHistory' import { GetGroupMsgHistory } from './go-cqhttp/GetGroupMsgHistory'
import GetFile from './file/GetFile' import GetFile from './file/GetFile'
import { GoCQHTTGetForwardMsgAction } from './go-cqhttp/GetForwardMsg' import { GetForwardMsg } from './go-cqhttp/GetForwardMsg'
import { GetCookies } from './user/GetCookie' import { GetCookies } from './user/GetCookie'
import { SetMsgEmojiLike } from './msg/SetMsgEmojiLike' import { SetMsgEmojiLike } from './msg/SetMsgEmojiLike'
import { ForwardFriendSingleMsg, ForwardGroupSingleMsg } from './msg/ForwardSingleMsg' import { ForwardFriendSingleMsg, ForwardGroupSingleMsg } from './msg/ForwardSingleMsg'
import { GetGroupEssence } from './group/GetGroupEssence' import { GetGroupEssence } from './group/GetGroupEssence'
import { GetGroupHonorInfo } from './group/GetGroupHonorInfo' import { GetGroupHonorInfo } from './group/GetGroupHonorInfo'
import { GoCQHTTHandleQuickOperation } from './go-cqhttp/QuickOperation' import { HandleQuickOperation } from './go-cqhttp/QuickOperation'
import GoCQHTTPSetEssenceMsg from './go-cqhttp/SetEssenceMsg' import { SetEssenceMsg } from './go-cqhttp/SetEssenceMsg'
import GoCQHTTPDelEssenceMsg from './go-cqhttp/DelEssenceMsg' import { DelEssenceMsg } from './go-cqhttp/DelEssenceMsg'
import GetEvent from './llonebot/GetEvent' import { GetEvent } from './llonebot/GetEvent'
import { GoCQHTTPDelGroupFile } from './go-cqhttp/DelGroupFile' import { DelGroupFile } from './go-cqhttp/DelGroupFile'
import type Adapter from '../adapter' import { GetGroupSystemMsg } from './go-cqhttp/GetGroupSystemMsg'
import { CreateGroupFileFolder } from './go-cqhttp/CreateGroupFileFolder'
import { DelGroupFolder } from './go-cqhttp/DelGroupFolder'
import { GetGroupAtAllRemain } from './go-cqhttp/GetGroupAtAllRemain'
import { GetGroupRootFiles } from './go-cqhttp/GetGroupRootFiles'
import { SetOnlineStatus } from './llonebot/SetOnlineStatus'
import { SendGroupNotice } from './go-cqhttp/SendGroupNotice'
import { GetProfileLike } from './llonebot/GetProfileLike'
import { FetchEmojiLike } from './llonebot/FetchEmojiLike'
import { FetchCustomFace } from './llonebot/FetchCustomFace'
import { GetFriendMsgHistory } from './llonebot/GetFriendMsgHistory'
import { GetGroupFilesByFolder } from './go-cqhttp/GetGroupFilesByFolder'
export function initActionMap(adapter: Adapter) { export function initActionMap(adapter: Adapter) {
const actionHandlers = [ const actionHandlers = [
@@ -66,6 +78,11 @@ export function initActionMap(adapter: Adapter) {
new SetQQAvatar(adapter), new SetQQAvatar(adapter),
new GetFriendWithCategory(adapter), new GetFriendWithCategory(adapter),
new GetEvent(adapter), new GetEvent(adapter),
new SetOnlineStatus(adapter),
new GetProfileLike(adapter),
new GetFriendMsgHistory(adapter),
new FetchEmojiLike(adapter),
new FetchCustomFace(adapter),
// onebot11 // onebot11
new SendLike(adapter), new SendLike(adapter),
new GetMsg(adapter), new GetMsg(adapter),
@@ -99,26 +116,33 @@ export function initActionMap(adapter: Adapter) {
new SetMsgEmojiLike(adapter), new SetMsgEmojiLike(adapter),
new ForwardFriendSingleMsg(adapter), new ForwardFriendSingleMsg(adapter),
new ForwardGroupSingleMsg(adapter), new ForwardGroupSingleMsg(adapter),
//以下为go-cqhttp api // go-cqhttp
new GetGroupEssence(adapter), new GetGroupEssence(adapter),
new GetGroupHonorInfo(adapter), new GetGroupHonorInfo(adapter),
new GoCQHTTPSendForwardMsg(adapter), new SendForwardMsg(adapter),
new GoCQHTTPSendGroupForwardMsg(adapter), new SendGroupForwardMsg(adapter),
new GoCQHTTPSendPrivateForwardMsg(adapter), new SendPrivateForwardMsg(adapter),
new GoCQHTTPGetStrangerInfo(adapter), new GetStrangerInfo(adapter),
new GoCQHTTPDownloadFile(adapter), new DownloadFile(adapter),
new GetGuildList(adapter), new GetGuildList(adapter),
new GoCQHTTPMarkMsgAsRead(adapter), new MarkMsgAsRead(adapter),
new GoCQHTTPUploadGroupFile(adapter), new UploadGroupFile(adapter),
new GoCQHTTPUploadPrivateFile(adapter), new UploadPrivateFile(adapter),
new GoCQHTTPGetGroupMsgHistory(adapter), new GetGroupMsgHistory(adapter),
new GoCQHTTGetForwardMsgAction(adapter), new GetForwardMsg(adapter),
new GoCQHTTHandleQuickOperation(adapter), new HandleQuickOperation(adapter),
new GoCQHTTPSetEssenceMsg(adapter), new SetEssenceMsg(adapter),
new GoCQHTTPDelEssenceMsg(adapter), new DelEssenceMsg(adapter),
new GoCQHTTPDelGroupFile(adapter) new DelGroupFile(adapter),
new GetGroupSystemMsg(adapter),
new CreateGroupFileFolder(adapter),
new DelGroupFolder(adapter),
new GetGroupAtAllRemain(adapter),
new GetGroupRootFiles(adapter),
new SendGroupNotice(adapter),
new GetGroupFilesByFolder(adapter),
] ]
const actionMap = new Map<string, BaseAction<any, any>>() const actionMap = new Map<string, BaseAction<any, unknown>>()
for (const action of actionHandlers) { for (const action of actionHandlers) {
actionMap.set(action.actionName, action) actionMap.set(action.actionName, action)
actionMap.set(action.actionName + '_async', action) actionMap.set(action.actionName + '_async', action)

View File

@@ -1,17 +1,19 @@
import BaseAction from '../BaseAction' import { BaseAction } from '../BaseAction'
import { Config } from '@/common/types' import { Config } from '@/common/types'
import { ActionName } from '../types' import { ActionName } from '../types'
import { getConfigUtil } from '@/common/config' import { getConfigUtil } from '@/common/config'
export class GetConfigAction extends BaseAction<null, Config> { export class GetConfigAction extends BaseAction<null, Config> {
actionName = ActionName.GetConfig actionName = ActionName.GetConfig
protected async _handle(payload: null): Promise<Config> {
protected async _handle(): Promise<Config> {
return getConfigUtil().getConfig() return getConfigUtil().getConfig()
} }
} }
export class SetConfigAction extends BaseAction<Config, void> { export class SetConfigAction extends BaseAction<Config, void> {
actionName = ActionName.SetConfig actionName = ActionName.SetConfig
protected async _handle(payload: Config): Promise<void> { protected async _handle(payload: Config): Promise<void> {
getConfigUtil().setConfig(payload) getConfigUtil().setConfig(payload)
} }

View File

@@ -1,21 +1,21 @@
import BaseAction from '../BaseAction' import { BaseAction } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
interface Payload { interface Payload {
method: string method: string
args: any[] args: unknown[]
} }
export default class Debug extends BaseAction<Payload, any> { export default class Debug extends BaseAction<Payload, unknown> {
actionName = ActionName.Debug actionName = ActionName.Debug
protected async _handle(payload: Payload): Promise<any> { protected async _handle(payload: Payload) {
this.ctx.logger.info('debug call ntqq api', payload) this.ctx.logger.info('debug call ntqq api', payload)
const { ntMsgApi, ntFileApi, ntFileCacheApi, ntFriendApi, ntGroupApi, ntUserApi, ntWindowApi } = this.ctx const { ntMsgApi, ntFileApi, ntFileCacheApi, ntFriendApi, ntGroupApi, ntUserApi, ntWindowApi } = this.ctx
const ntqqApi = [ntMsgApi, ntFriendApi, ntGroupApi, ntUserApi, ntFileApi, ntFileCacheApi, ntWindowApi] const ntqqApi = [ntMsgApi, ntFriendApi, ntGroupApi, ntUserApi, ntFileApi, ntFileCacheApi, ntWindowApi]
for (const ntqqApiClass of ntqqApi) { for (const ntqqApiClass of ntqqApi) {
const method = ntqqApiClass[payload.method] as Function const method = ntqqApiClass[payload.method as keyof typeof ntqqApiClass]
if (method) { if (method && method instanceof Function) {
const result = method.apply(ntqqApiClass, payload.args) const result = method.apply(ntqqApiClass, payload.args)
if (method.constructor.name === 'AsyncFunction') { if (method.constructor.name === 'AsyncFunction') {
return await result return await result

View File

@@ -0,0 +1,18 @@
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
interface Payload {
count: number | string
}
export class FetchCustomFace extends BaseAction<Payload, string[]> {
actionName = ActionName.FetchCustomFace
payloadSchema = Schema.object({
count: Schema.union([Number, String]).default(48)
})
async _handle(payload: Payload) {
const ret = await this.ctx.ntMsgApi.fetchFavEmojiList(+payload.count)
return ret.emojiInfoList.map(e => e.url)
}
}

View File

@@ -0,0 +1,28 @@
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
import { MessageUnique } from '@/common/utils/messageUnique'
import { Dict } from 'cosmokit'
interface Payload {
emojiId: string
emojiType: string
message_id: string | number
count: string | number
}
export class FetchEmojiLike extends BaseAction<Payload, Dict> {
actionName = ActionName.FetchEmojiLike
payloadSchema = Schema.object({
emojiId: Schema.string().required(),
emojiType: Schema.string().required(),
message_id: Schema.union([Number, String]).required(),
count: Schema.union([Number, String]).default(20)
})
async _handle(payload: Payload) {
const msgInfo = await MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id)
if (!msgInfo) throw new Error('消息不存在')
const { msgSeq } = (await this.ctx.ntMsgApi.getMsgsByMsgId(msgInfo.Peer, [msgInfo.MsgId])).msgList[0]
return await this.ctx.ntMsgApi.getMsgEmojiLikesList(msgInfo.Peer, msgSeq, payload.emojiId, payload.emojiType, +payload.count)
}
}

View File

@@ -1,4 +1,4 @@
import BaseAction from '../BaseAction' import { BaseAction } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { getHttpEvent } from '../../helper/eventForHttp' import { getHttpEvent } from '../../helper/eventForHttp'
import { OB11Message } from '../../types' import { OB11Message } from '../../types'
@@ -11,15 +11,16 @@ interface Payload {
timeout: number timeout: number
} }
export default class GetEvent extends BaseAction<Payload, PostEventType[]> { export class GetEvent extends BaseAction<Payload, PostEventType[]> {
actionName = ActionName.GetEvent actionName = ActionName.GetEvent
protected async _handle(payload: Payload): Promise<PostEventType[]> { protected async _handle(payload: Payload): Promise<PostEventType[]> {
let key = '' let key = ''
if (payload.key) { if (payload.key) {
key = payload.key key = payload.key
} }
let timeout = parseInt(payload.timeout?.toString()) || 0 const timeout = parseInt(payload.timeout?.toString()) || 0
let evts = await getHttpEvent(key, timeout) const evts = await getHttpEvent(key, timeout)
return evts return evts
} }
} }

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