mirror of
https://github.com/LLOneBot/LLOneBot.git
synced 2024-11-22 01:56:33 +00:00
Compare commits
30 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
96d4f79b83 | ||
![]() |
4aadd81e60 | ||
![]() |
57ef8ed3e4 | ||
![]() |
4249f4e088 | ||
![]() |
3d0b90db35 | ||
![]() |
fdaf0e5269 | ||
![]() |
f23abb1d9c | ||
![]() |
72aeefd501 | ||
![]() |
f4a53c5aec | ||
![]() |
f0790b03bb | ||
![]() |
f8bf5afd3d | ||
![]() |
66c823e3bd | ||
![]() |
8f80da8c5b | ||
![]() |
1ceee49d1a | ||
![]() |
c600c38a92 | ||
![]() |
3eda104a78 | ||
![]() |
b8aa3131b0 | ||
![]() |
320aa964f9 | ||
![]() |
0fd75b338f | ||
![]() |
9faa56ec32 | ||
![]() |
c636af0b0e | ||
![]() |
b8af582749 | ||
![]() |
8e09a9e0fd | ||
![]() |
001dfc4db2 | ||
![]() |
a164884b76 | ||
![]() |
58f0a99d0b | ||
![]() |
528c6061e2 | ||
![]() |
f5ac499861 | ||
![]() |
621d9df450 | ||
![]() |
a98ce843ef |
4
.github/workflows/publish.yml
vendored
4
.github/workflows/publish.yml
vendored
@@ -17,7 +17,9 @@ jobs:
|
||||
node-version: 18
|
||||
|
||||
- name: install dependenies
|
||||
run: export ELECTRON_SKIP_BINARY_DOWNLOAD=1 && npm install
|
||||
run: |
|
||||
export ELECTRON_SKIP_BINARY_DOWNLOAD=1
|
||||
npm install
|
||||
|
||||
- name: build
|
||||
run: npm run build
|
||||
|
109
README.md
109
README.md
@@ -9,6 +9,18 @@ TG群:<https://t.me/+nLZEnpne-pQ1OWFl>
|
||||
*V3之后不再需要LLAPI*
|
||||
|
||||
## 安装方法
|
||||
### Linux 容器化快速安装
|
||||
|
||||
执行以下任意脚本,按照提示设置NoVnc密码,即可运行,脚本问题与异常参考 [llonebot-docker](https://github.com/MliKiowa/llonebot-docker) 项目。
|
||||
|
||||
```bash
|
||||
curl https://cdn.jsdelivr.net/gh/MliKiowa/llonebot-docker/fastboot.sh -o fastboot.sh & chmod +x fastboot.sh & sudo sh fastboot.sh
|
||||
```
|
||||
```bash
|
||||
wget -O fastboot.sh https://cdn.jsdelivr.net/gh/MliKiowa/llonebot-docker/fastboot.sh & chmod +x fastboot.sh & sudo sh fastboot.sh
|
||||
```
|
||||
|
||||
### 通用手动安装方法
|
||||
|
||||
1.安装[LiteLoaderQQNT](https://liteloaderqqnt.github.io/guide/install.html)
|
||||
|
||||
@@ -18,6 +30,17 @@ TG群:<https://t.me/+nLZEnpne-pQ1OWFl>
|
||||
|
||||
*插件目录:`LiteLoaderQQNT/plugins`*
|
||||
|
||||
安装后的目录结构如下
|
||||
```
|
||||
├── plugins
|
||||
│ ├── LLOneBot
|
||||
│ │ └── main.js
|
||||
│ │ └── preload.js
|
||||
│ │ └── renderer.js
|
||||
│ │ └── manifest.json
|
||||
│ │ └── node_modules/...
|
||||
```
|
||||
|
||||
## 支持的API
|
||||
|
||||
目前支持的协议
|
||||
@@ -42,6 +65,11 @@ TG群:<https://t.me/+nLZEnpne-pQ1OWFl>
|
||||
- [x] 上报好友、群消息撤回
|
||||
- [x] 上报加群请求
|
||||
- [x] 上报群员人数变动(尚不支持识别群员人数变动原因)
|
||||
- [x] 设置群管理员
|
||||
- [x] 群禁言/全体禁言
|
||||
- [x] 群踢人
|
||||
- [x] 群改群成员名片
|
||||
- [x] 修改群名
|
||||
|
||||
消息格式支持:
|
||||
- [x] cq码
|
||||
@@ -53,34 +81,19 @@ TG群:<https://t.me/+nLZEnpne-pQ1OWFl>
|
||||
- [x] 语音(支持mp3、wav等多种音频格式直接发送)
|
||||
- [x] json消息(只上报)
|
||||
- [x] 转发消息记录(目前只能发不能收)
|
||||
- [ ] 红包
|
||||
- [ ] xml
|
||||
|
||||
支持的api:
|
||||
- [x] get_login_info
|
||||
- [x] send_msg
|
||||
- [x] send_group_msg
|
||||
- [x] send_private_msg
|
||||
- [x] delete_msg
|
||||
- [x] get_group_list
|
||||
- [x] get_group_info
|
||||
- [x] get_group_member_list
|
||||
- [x] get_group_member_info
|
||||
- [x] get_friend_list
|
||||
- [x] set_friend_add_request
|
||||
- [x] get_msg
|
||||
- [x] send_like
|
||||
- [x] set_group_add_request
|
||||
- [x] set_group_leave
|
||||
- [x] get_version_info
|
||||
- [x] get_status
|
||||
- [x] can_send_image
|
||||
- [x] can_send_record
|
||||
|
||||
支持的go-cqhtp api:
|
||||
- [x] send_private_forward_msg
|
||||
- [x] send_group_forward_msg
|
||||
- [x] get_stranger_info
|
||||
- [x] 视频(上报时暂时只有个空的file)
|
||||
- [x] 文件(上报时暂时只有个空的file), type为file, data为{file: uri}, 发送时uri支持http://, file://, base64://
|
||||
```
|
||||
{
|
||||
"type": "file",
|
||||
"data": {
|
||||
"file": "file:///D:/1.txt"
|
||||
}
|
||||
}
|
||||
```
|
||||
- [ ] 发送音乐卡片
|
||||
- [ ] 红包(没有计划支持)
|
||||
- [ ] xml (没有计划支持)
|
||||
|
||||
## 示例
|
||||
|
||||
@@ -99,6 +112,7 @@ TG群:<https://t.me/+nLZEnpne-pQ1OWFl>
|
||||
<summary>调用接口报404</summary>
|
||||
<br/>
|
||||
目前没有支持全部的onebot规范接口,请检查是否调用了不支持的接口
|
||||
-
|
||||
</details>
|
||||
<br/>
|
||||
|
||||
@@ -116,12 +130,49 @@ TG群:<https://t.me/+nLZEnpne-pQ1OWFl>
|
||||
</details>
|
||||
<br/>
|
||||
|
||||
## 支持的onebot v11 api:
|
||||
- [x] get_login_info
|
||||
- [x] send_msg
|
||||
- [x] send_group_msg
|
||||
- [x] send_private_msg
|
||||
- [x] delete_msg
|
||||
- [x] get_group_list
|
||||
- [x] get_group_info
|
||||
- [x] get_group_member_list
|
||||
- [x] get_group_member_info
|
||||
- [x] get_friend_list
|
||||
- [x] set_friend_add_request
|
||||
- [x] get_msg
|
||||
- [x] send_like
|
||||
- [x] set_group_add_request
|
||||
- [x] set_group_leave
|
||||
- [x] set_group_kick
|
||||
- [x] set_group_ban
|
||||
- [x] set_group_whole_ban
|
||||
- [x] set_group_kick
|
||||
- [x] set_group_admin
|
||||
- [x] set_group_card
|
||||
- [x] set_group_name
|
||||
- [x] get_version_info
|
||||
- [x] get_status
|
||||
- [x] can_send_image
|
||||
- [x] can_send_record
|
||||
|
||||
### 支持的go-cqhtp api:
|
||||
- [x] send_private_forward_msg
|
||||
- [x] send_group_forward_msg
|
||||
- [x] get_stranger_info
|
||||
|
||||
## TODO
|
||||
- [x] 重构摆脱LLAPI,目前调用LLAPI只能在renderer进程调用,需重构成在main进程调用
|
||||
- [x] 支持正、反向websocket(感谢@disymayufei的PR)
|
||||
- [x] 转发消息记录
|
||||
- [x] 好友点赞api
|
||||
- [x] 群管理功能,禁言、踢人,改群名片等
|
||||
- [x] 视频消息
|
||||
- [x] 文件消息
|
||||
- [ ] 音乐卡片
|
||||
- [ ] 无头模式
|
||||
|
||||
## onebot11文档
|
||||
<https://11.onebot.dev/>
|
||||
@@ -130,4 +181,4 @@ TG群:<https://t.me/+nLZEnpne-pQ1OWFl>
|
||||
* [LiteLoaderQQNT](https://liteloaderqqnt.github.io/guide/install.html)
|
||||
* [LLAPI](https://github.com/Night-stars-1/LiteLoaderQQNT-Plugin-LLAPI)
|
||||
* 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)
|
||||
|
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"manifest_version": 4,
|
||||
"type": "extension",
|
||||
"name": "LLOneBot",
|
||||
"name": "LLOneBot v3.11.0",
|
||||
"slug": "LLOneBot",
|
||||
"description": "LiteLoaderQQNT的OneBotApi",
|
||||
"version": "3.8.0",
|
||||
"version": "3.11.0",
|
||||
"thumbnail": "./icon.png",
|
||||
"authors": [
|
||||
{
|
||||
@@ -30,4 +30,4 @@
|
||||
"main": "./main.js",
|
||||
"preload": "./preload.js"
|
||||
}
|
||||
}
|
||||
}
|
278
package-lock.json
generated
278
package-lock.json
generated
@@ -9,12 +9,12 @@
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"audio-buffer-from": "^1.1.1",
|
||||
"express": "^4.18.2",
|
||||
"fluent-ffmpeg": "^2.1.2",
|
||||
"json-bigint": "^1.0.0",
|
||||
"music-metadata": "^8.1.4",
|
||||
"silk-wasm": "^3.2.3",
|
||||
"utf-8-validate": "^6.0.3",
|
||||
"uuid": "^9.0.1",
|
||||
"ws": "^8.16.0"
|
||||
},
|
||||
@@ -22,14 +22,14 @@
|
||||
"@babel/preset-env": "^7.23.2",
|
||||
"@types/express": "^4.17.20",
|
||||
"@types/fluent-ffmpeg": "^2.1.24",
|
||||
"@types/node": "^20.11.19",
|
||||
"@types/node": "^20.11.24",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@types/ws": "^8.5.10",
|
||||
"babel-loader": "^9.1.3",
|
||||
"copy-webpack-plugin": "^12.0.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"electron": "^29.0.1",
|
||||
"ts-loader": "^9.5.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.2.2",
|
||||
"webpack": "^5.89.0",
|
||||
"webpack-cli": "^5.1.4"
|
||||
@@ -2007,6 +2007,28 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-support": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "0.3.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.9",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
|
||||
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.0.3",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@discoveryjs/json-ext": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz",
|
||||
@@ -2203,6 +2225,30 @@
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/@tokenizer/token/-/token-0.3.0.tgz",
|
||||
"integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="
|
||||
},
|
||||
"node_modules/@tsconfig/node10": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/@tsconfig/node10/-/node10-1.0.9.tgz",
|
||||
"integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@tsconfig/node12": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/@tsconfig/node12/-/node12-1.0.11.tgz",
|
||||
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@tsconfig/node14": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/@tsconfig/node14/-/node14-1.0.3.tgz",
|
||||
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@tsconfig/node16": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/@tsconfig/node16/-/node16-1.0.4.tgz",
|
||||
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/body-parser": {
|
||||
"version": "1.19.4",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/@types/body-parser/-/body-parser-1.19.4.tgz",
|
||||
@@ -2327,9 +2373,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.11.19",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/@types/node/-/node-20.11.19.tgz",
|
||||
"integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==",
|
||||
"version": "20.11.24",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/@types/node/-/node-20.11.24.tgz",
|
||||
"integrity": "sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
@@ -2637,6 +2683,15 @@
|
||||
"acorn": "^8"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-walk": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/acorn-walk/-/acorn-walk-8.3.2.tgz",
|
||||
"integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
@@ -2716,6 +2771,12 @@
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/arg": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/arg/-/arg-4.1.3.tgz",
|
||||
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
@@ -2726,51 +2787,6 @@
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/async/-/async-3.2.5.tgz",
|
||||
"integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg=="
|
||||
},
|
||||
"node_modules/atob-lite": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/atob-lite/-/atob-lite-2.0.0.tgz",
|
||||
"integrity": "sha1-D+9a1G8b16hQLGVyfwNn1e5D1pY="
|
||||
},
|
||||
"node_modules/audio-buffer": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/audio-buffer/-/audio-buffer-4.0.4.tgz",
|
||||
"integrity": "sha512-phH+MR3G+N/PO5ZKKxx7HlU6vJwAJFa0+FCaTjr/4lUZU/RCjUTqlk3nMJTRy5+b+6cbx8m//EtwZOVI5Ht9+w==",
|
||||
"dependencies": {
|
||||
"audio-context": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/audio-buffer-from": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/audio-buffer-from/-/audio-buffer-from-1.1.1.tgz",
|
||||
"integrity": "sha512-8Wcira24z+26GXDFe7ZFRF1bJm1iWrz8O+XL8iNZxZjxqAPXIoK1IrJiOqStv35ASPbRjG57ZK/T0WO92MDUSg==",
|
||||
"dependencies": {
|
||||
"audio-buffer": "^4.0.4",
|
||||
"audio-context": "^1.0.1",
|
||||
"audio-format": "^2.0.0",
|
||||
"is-audio-buffer": "^1.0.11",
|
||||
"is-plain-obj": "^1.1.0",
|
||||
"pcm-convert": "^1.6.0",
|
||||
"pick-by-alias": "^1.2.0",
|
||||
"string-to-arraybuffer": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/audio-context": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/audio-context/-/audio-context-1.0.3.tgz",
|
||||
"integrity": "sha512-RH3/rM74f2ITlohhjgC7oYZVS97wtv/SEjXLCzEinnrIPIDxc39m2aFc6wmdkM0NYRKo1DMleYPMAIbnTRW0eA=="
|
||||
},
|
||||
"node_modules/audio-format": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/audio-format/-/audio-format-2.3.2.tgz",
|
||||
"integrity": "sha512-5IA2grZhaVhpGxX6lbJm8VVh/SKQULMXXrFxuiodi0zhzDPRB8BJfieo89AclEQv4bDxZRH4lv06qNnxqkFhKQ==",
|
||||
"dependencies": {
|
||||
"is-audio-buffer": "^1.0.11",
|
||||
"is-buffer": "^1.1.5",
|
||||
"is-plain-obj": "^1.1.0",
|
||||
"pick-by-alias": "^1.2.0",
|
||||
"sample-rate": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-loader": {
|
||||
"version": "9.1.3",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/babel-loader/-/babel-loader-9.1.3.tgz",
|
||||
@@ -3282,23 +3298,11 @@
|
||||
"url": "https://opencollective.com/core-js"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-env": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/cross-env/-/cross-env-7.0.3.tgz",
|
||||
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"cross-env": "src/bin/cross-env.js",
|
||||
"cross-env-shell": "src/bin/cross-env-shell.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.14",
|
||||
"npm": ">=6",
|
||||
"yarn": ">=1"
|
||||
}
|
||||
"node_modules/create-require": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/create-require/-/create-require-1.1.1.tgz",
|
||||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
@@ -3413,6 +3417,15 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/diff/-/diff-4.0.2.tgz",
|
||||
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/ee-first/-/ee-first-1.1.1.tgz",
|
||||
@@ -4302,21 +4315,6 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/is-audio-buffer": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/is-audio-buffer/-/is-audio-buffer-1.1.0.tgz",
|
||||
"integrity": "sha512-fmPC/dizJmP4ITCsW5oTQGMJ9wZVE+A/zAe6FQo3XwgERxmXHmm3ON5XkWDAxmyxvsrDmWx3NArpSgamp/59AA=="
|
||||
},
|
||||
"node_modules/is-base64": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/is-base64/-/is-base64-0.1.0.tgz",
|
||||
"integrity": "sha512-WRRyllsGXJM7ZN7gPTCCQ/6wNPTRDwiWdPK66l5sJzcU/oOzcIcRRf0Rux8bkpox/1yjt0F6VJRsQOIG2qz5sg=="
|
||||
},
|
||||
"node_modules/is-buffer": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/is-buffer/-/is-buffer-1.1.6.tgz",
|
||||
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
|
||||
},
|
||||
"node_modules/is-core-module": {
|
||||
"version": "2.13.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
|
||||
@@ -4359,14 +4357,6 @@
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-plain-obj": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
|
||||
"integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-plain-object": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
|
||||
@@ -4541,6 +4531,12 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/make-error": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/make-error/-/make-error-1.3.6.tgz",
|
||||
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/matcher": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/matcher/-/matcher-3.0.0.tgz",
|
||||
@@ -4715,8 +4711,6 @@
|
||||
"version": "4.8.0",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/node-gyp-build/-/node-gyp-build-4.8.0.tgz",
|
||||
"integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"node-gyp-build": "bin.js",
|
||||
"node-gyp-build-optional": "optional.js",
|
||||
@@ -4750,14 +4744,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/object-inspect/-/object-inspect-1.13.1.tgz",
|
||||
@@ -4890,17 +4876,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/pcm-convert": {
|
||||
"version": "1.6.5",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/pcm-convert/-/pcm-convert-1.6.5.tgz",
|
||||
"integrity": "sha512-5CEspU4j8aEQ80AhNbcLfpT0apc93E6endFxahWd4sV70I6PN7LPdz8GoYm/1qr400K9bUVsVA+KxNgbFROZPw==",
|
||||
"dependencies": {
|
||||
"audio-format": "^2.3.2",
|
||||
"is-audio-buffer": "^1.0.11",
|
||||
"is-buffer": "^1.1.5",
|
||||
"object-assign": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/peek-readable": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/peek-readable/-/peek-readable-5.0.0.tgz",
|
||||
@@ -4919,11 +4894,6 @@
|
||||
"integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/pick-by-alias": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/pick-by-alias/-/pick-by-alias-1.2.0.tgz",
|
||||
"integrity": "sha1-X3yysfIabh6ISgyHhVqko3NhEHs="
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/picocolors/-/picocolors-1.0.0.tgz",
|
||||
@@ -5316,11 +5286,6 @@
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/sample-rate": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/sample-rate/-/sample-rate-2.0.1.tgz",
|
||||
"integrity": "sha512-AIK0vVBiAEObmpJOxQu/WCyklnWGqzTSDII4O7nBo+SJHmfgBUiYhgV/Y3Ohz76gfSlU6R5CIAKggj+nAOLSvg=="
|
||||
},
|
||||
"node_modules/schema-utils": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
|
||||
@@ -5552,15 +5517,6 @@
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string-to-arraybuffer": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/string-to-arraybuffer/-/string-to-arraybuffer-1.0.2.tgz",
|
||||
"integrity": "sha512-DaGZidzi93dwjQen5I2osxR9ERS/R7B1PFyufNMnzhj+fmlDQAc1DSDIJVJhgI8Oq221efIMbABUBdPHDRt43Q==",
|
||||
"dependencies": {
|
||||
"atob-lite": "^2.0.0",
|
||||
"is-base64": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strtok3": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/strtok3/-/strtok3-7.0.0.tgz",
|
||||
@@ -5765,6 +5721,49 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-node": {
|
||||
"version": "10.9.2",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/ts-node/-/ts-node-10.9.2.tgz",
|
||||
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@cspotcode/source-map-support": "^0.8.0",
|
||||
"@tsconfig/node10": "^1.0.7",
|
||||
"@tsconfig/node12": "^1.0.7",
|
||||
"@tsconfig/node14": "^1.0.0",
|
||||
"@tsconfig/node16": "^1.0.2",
|
||||
"acorn": "^8.4.1",
|
||||
"acorn-walk": "^8.1.1",
|
||||
"arg": "^4.1.0",
|
||||
"create-require": "^1.1.0",
|
||||
"diff": "^4.0.1",
|
||||
"make-error": "^1.1.1",
|
||||
"v8-compile-cache-lib": "^3.0.1",
|
||||
"yn": "3.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"ts-node": "dist/bin.js",
|
||||
"ts-node-cwd": "dist/bin-cwd.js",
|
||||
"ts-node-esm": "dist/bin-esm.js",
|
||||
"ts-node-script": "dist/bin-script.js",
|
||||
"ts-node-transpile-only": "dist/bin-transpile.js",
|
||||
"ts-script": "dist/bin-script-deprecated.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@swc/core": ">=1.2.50",
|
||||
"@swc/wasm": ">=1.2.50",
|
||||
"@types/node": "*",
|
||||
"typescript": ">=2.7"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@swc/core": {
|
||||
"optional": true
|
||||
},
|
||||
"@swc/wasm": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/type-fest": {
|
||||
"version": "0.13.1",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/type-fest/-/type-fest-0.13.1.tgz",
|
||||
@@ -5922,8 +5921,6 @@
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/utf-8-validate/-/utf-8-validate-6.0.3.tgz",
|
||||
"integrity": "sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA==",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"node-gyp-build": "^4.3.0"
|
||||
},
|
||||
@@ -5956,6 +5953,12 @@
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/v8-compile-cache-lib": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
||||
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/vary/-/vary-1.1.2.tgz",
|
||||
@@ -6164,6 +6167,15 @@
|
||||
"fd-slicer": "~1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/yn": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/yn/-/yn-3.1.1.tgz",
|
||||
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/yocto-queue": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://mirrors.cloud.tencent.com/npm/yocto-queue/-/yocto-queue-1.0.0.tgz",
|
||||
|
@@ -5,10 +5,11 @@
|
||||
"main": "dist/main.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "npm run build-main && npm run build-preload && npm run build-renderer",
|
||||
"build": "npm run build-main && npm run build-preload && npm run build-renderer && npm run build-version",
|
||||
"build-main": "webpack --config webpack.main.config.js",
|
||||
"build-preload": "webpack --config webpack.preload.config.js",
|
||||
"build-renderer": "webpack --config webpack.renderer.config.js",
|
||||
"build-version": "ts-node ./scripts/gen-version.ts",
|
||||
"build-mac": "npm run build && cp manifest.json dist/ && npm run deploy-mac",
|
||||
"deploy-mac": "cp -r dist/* ~/Library/Containers/com.tencent.qq/Data/LiteLoaderQQNT/plugins/LLOnebot/",
|
||||
"build-win": "npm run build && cp manifest.json dist/ && npm run deploy-win",
|
||||
@@ -30,13 +31,14 @@
|
||||
"@babel/preset-env": "^7.23.2",
|
||||
"@types/express": "^4.17.20",
|
||||
"@types/fluent-ffmpeg": "^2.1.24",
|
||||
"@types/node": "^20.11.19",
|
||||
"@types/node": "^20.11.24",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@types/ws": "^8.5.10",
|
||||
"babel-loader": "^9.1.3",
|
||||
"copy-webpack-plugin": "^12.0.2",
|
||||
"electron": "^29.0.1",
|
||||
"ts-loader": "^9.5.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.2.2",
|
||||
"webpack": "^5.89.0",
|
||||
"webpack-cli": "^5.1.4"
|
||||
|
20
scripts/gen-version.ts
Normal file
20
scripts/gen-version.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import fs = require("fs");
|
||||
import path = require("path");
|
||||
import {version} from "../src/version";
|
||||
const manifestPath = path.join(__dirname, "../manifest.json");
|
||||
function readManifest(): any{
|
||||
if (fs.existsSync(manifestPath)){
|
||||
return JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
||||
}
|
||||
}
|
||||
|
||||
function writeManifest(manifest: any){
|
||||
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
||||
}
|
||||
|
||||
let manifest = readManifest();
|
||||
if (version !== manifest.version){
|
||||
manifest.version = version;
|
||||
manifest.name = `LLOneBot v${version}`;
|
||||
writeManifest(manifest);
|
||||
}
|
@@ -2,7 +2,7 @@ import fs from "fs";
|
||||
import {Config, OB11Config} from "./types";
|
||||
import {mergeNewProperties} from "./utils";
|
||||
|
||||
export const HOOK_LOG = false;
|
||||
export const HOOK_LOG= false;
|
||||
|
||||
export class ConfigUtil {
|
||||
private readonly configPath: string;
|
||||
@@ -41,6 +41,7 @@ export class ConfigUtil {
|
||||
log: false,
|
||||
reportSelfMessage: false,
|
||||
autoDeleteFile: false,
|
||||
autoDeleteFileSecond: 60,
|
||||
};
|
||||
|
||||
if (!fs.existsSync(this.configPath)) {
|
||||
|
@@ -1,11 +1,25 @@
|
||||
import {NTQQApi} from '../ntqqapi/ntcall';
|
||||
import {Friend, FriendRequest, Group, GroupMember, GroupNotify, RawMessage, SelfInfo} from "../ntqqapi/types";
|
||||
import {LLOneBotError} from "./types";
|
||||
|
||||
import {
|
||||
FileElement,
|
||||
Friend,
|
||||
FriendRequest,
|
||||
Group,
|
||||
GroupMember,
|
||||
GroupNotify,
|
||||
PicElement, PttElement,
|
||||
RawMessage,
|
||||
SelfInfo, VideoElement
|
||||
} from "../ntqqapi/types";
|
||||
import {FileCache, LLOneBotError} from "./types";
|
||||
export let selfInfo: SelfInfo = {
|
||||
uid: "",
|
||||
uin: "",
|
||||
nick: "",
|
||||
online: true,
|
||||
}
|
||||
export let groups: Group[] = []
|
||||
export let friends: Friend[] = []
|
||||
export let msgHistory: Record<string, RawMessage> = {} // msgId: RawMessage
|
||||
export const version = "3.8.0"
|
||||
export let groupNotifies: Map<string, GroupNotify> = new Map<string, GroupNotify>();
|
||||
export let friendRequests: Map<number, FriendRequest> = new Map<number, FriendRequest>();
|
||||
export let llonebotError: LLOneBotError = {
|
||||
@@ -14,6 +28,8 @@ export let llonebotError: LLOneBotError = {
|
||||
}
|
||||
let globalMsgId = Math.floor(Date.now() / 1000);
|
||||
|
||||
export let fileCache: Map<string, FileCache> = new Map();
|
||||
|
||||
export function addHistoryMsg(msg: RawMessage): boolean {
|
||||
let existMsg = msgHistory[msg.msgId]
|
||||
if (existMsg) {
|
||||
@@ -50,7 +66,11 @@ export async function getGroup(qq: string): Promise<Group | undefined> {
|
||||
return group
|
||||
}
|
||||
|
||||
export async function getGroupMember(groupQQ: string, memberQQ: string, memberUid: string = null) {
|
||||
export async function getGroupMember(groupQQ: string | number, memberQQ: string | number, memberUid: string = null) {
|
||||
groupQQ = groupQQ.toString();
|
||||
if (memberQQ){
|
||||
memberQQ = memberQQ.toString();
|
||||
}
|
||||
const group = await getGroup(groupQQ)
|
||||
if (group) {
|
||||
let filterFunc: (member: GroupMember) => boolean
|
||||
@@ -71,11 +91,7 @@ export async function getGroupMember(groupQQ: string, memberQQ: string, memberUi
|
||||
}
|
||||
}
|
||||
|
||||
export let selfInfo: SelfInfo = {
|
||||
uid: "",
|
||||
uin: "",
|
||||
nick: "",
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function getHistoryMsgBySeq(seq: string) {
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import {FileElement, PicElement, PttElement, VideoElement} from "../ntqqapi/types";
|
||||
|
||||
export interface OB11Config {
|
||||
httpPort: number
|
||||
httpHosts: string[]
|
||||
@@ -19,10 +21,20 @@ export interface Config {
|
||||
reportSelfMessage?: boolean
|
||||
log?: boolean
|
||||
autoDeleteFile?: boolean
|
||||
autoDeleteFileSecond?: number
|
||||
ffmpeg?: string // ffmpeg路径
|
||||
}
|
||||
|
||||
export type LLOneBotError = {
|
||||
ffmpegError?: string
|
||||
otherError?: string
|
||||
}
|
||||
|
||||
|
||||
export interface FileCache{
|
||||
fileName: string,
|
||||
filePath: string,
|
||||
fileSize: string,
|
||||
url?: string,
|
||||
downloadFunc?: () => Promise<void>;
|
||||
}
|
@@ -2,10 +2,9 @@ import * as path from "path";
|
||||
import {selfInfo} from "./data";
|
||||
import {ConfigUtil} from "./config";
|
||||
import util from "util";
|
||||
import {encode, getDuration} from "silk-wasm";
|
||||
import {encode, getDuration, isWav} from "silk-wasm";
|
||||
import fs from 'fs';
|
||||
import {v4 as uuidv4} from "uuid";
|
||||
import {exec} from "node:child_process";
|
||||
import ffmpeg from "fluent-ffmpeg"
|
||||
|
||||
export const CONFIG_DIR = global.LiteLoader.plugins["LLOneBot"].path.data;
|
||||
@@ -138,17 +137,20 @@ export function mergeNewProperties(newObj: any, oldObj: any) {
|
||||
});
|
||||
}
|
||||
|
||||
export function checkFFMPEG(newPath: string=null): Promise<boolean> {
|
||||
export function checkFfmpeg(newPath: string = null): Promise<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const ffmpegPath = newPath || 'ffmpeg'
|
||||
exec(ffmpegPath + ' -version', (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
log('ffmpeg is not installed or not found in PATH:', error);
|
||||
resolve(false)
|
||||
}
|
||||
log('ffmpeg is installed. Version info:', stdout);
|
||||
resolve(true);
|
||||
});
|
||||
if (newPath) {
|
||||
ffmpeg.setFfmpegPath(newPath);
|
||||
ffmpeg.getAvailableFormats((err, formats) => {
|
||||
if (err) {
|
||||
log('ffmpeg is not installed or not found in PATH:', err);
|
||||
resolve(false)
|
||||
} else {
|
||||
log('ffmpeg is installed.');
|
||||
resolve(true);
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -172,33 +174,8 @@ export async function encodeSilk(filePath: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function isWavFile(filePath: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.open(filePath, 'r', (err, fd) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
// 读取前12个字节
|
||||
const buffer = Buffer.alloc(12);
|
||||
fs.read(fd, buffer, 0, 12, 0, (err, bytesRead, buffer) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
fs.close(fd, (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
// 检查RIFF头和WAVE格式标识
|
||||
const isRIFF = buffer.toString('utf8', 0, 4) === 'RIFF';
|
||||
const isWAVE = buffer.toString('utf8', 8, 12) === 'WAVE';
|
||||
resolve(isRIFF && isWAVE);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
async function isWavFile(filePath: string) {
|
||||
return isWav(fs.readFileSync(filePath));
|
||||
}
|
||||
|
||||
async function getAudioSampleRate(filePath: string) {
|
||||
@@ -217,15 +194,15 @@ export async function encodeSilk(filePath: string) {
|
||||
const fileName = path.basename(filePath);
|
||||
const pttPath = path.join(CONFIG_DIR, uuidv4());
|
||||
if (getFileHeader(filePath) !== "02232153494c4b") {
|
||||
log(`语音文件${filePath}需要转换`)
|
||||
log(`语音文件${filePath}需要转换成silk`)
|
||||
const isWav = await isWavFile(filePath);
|
||||
const wavPath = pttPath + ".wav"
|
||||
if (!isWav) {
|
||||
log(`语音文件${filePath}正在转换成wav`)
|
||||
// let voiceData = await fsp.readFile(filePath)
|
||||
const wavPath = pttPath + ".wav"
|
||||
await new Promise((resolve, reject) => {
|
||||
const ffmpegPath = getConfigUtil().getConfig().ffmpeg;
|
||||
if (ffmpegPath){
|
||||
if (ffmpegPath) {
|
||||
ffmpeg.setFfmpegPath(ffmpegPath);
|
||||
}
|
||||
ffmpeg(filePath).toFormat("wav").on('end', function () {
|
||||
@@ -236,31 +213,32 @@ export async function encodeSilk(filePath: string) {
|
||||
reject(err);
|
||||
})
|
||||
.save(wavPath)
|
||||
.on("end", ()=>{
|
||||
.on("end", () => {
|
||||
filePath = wavPath
|
||||
resolve(wavPath);
|
||||
});
|
||||
})
|
||||
const sampleRate = await getAudioSampleRate(filePath) || 44100;
|
||||
const pcm = fs.readFileSync(filePath);
|
||||
const silk = await encode(pcm, sampleRate);
|
||||
fs.writeFileSync(pttPath, silk.data);
|
||||
fs.unlink(wavPath, (err) => {});
|
||||
log(`语音文件${filePath}转换成功!`, pttPath)
|
||||
return {
|
||||
converted: true,
|
||||
path: pttPath,
|
||||
duration: silk.duration,
|
||||
};
|
||||
} else {
|
||||
const pcm = fs.readFileSync(filePath);
|
||||
const duration = getDuration(pcm);
|
||||
return {
|
||||
converted: false,
|
||||
path: filePath,
|
||||
duration: duration,
|
||||
};
|
||||
}
|
||||
const sampleRate = await getAudioSampleRate(filePath) || 44100;
|
||||
const pcm = fs.readFileSync(filePath);
|
||||
const silk = await encode(pcm, sampleRate);
|
||||
fs.writeFileSync(pttPath, silk.data);
|
||||
fs.unlink(wavPath, (err) => {
|
||||
});
|
||||
log(`语音文件${filePath}转换成功!`, pttPath)
|
||||
return {
|
||||
converted: true,
|
||||
path: pttPath,
|
||||
duration: silk.duration,
|
||||
};
|
||||
} else {
|
||||
const pcm = fs.readFileSync(filePath);
|
||||
const duration = getDuration(pcm);
|
||||
return {
|
||||
converted: false,
|
||||
path: filePath,
|
||||
duration: duration,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
log("convert silk failed", error.stack);
|
||||
|
@@ -1,7 +1,7 @@
|
||||
// 运行在 Electron 主进程 下的插件入口
|
||||
|
||||
import {BrowserWindow, dialog, ipcMain} from 'electron';
|
||||
import fs from 'fs';
|
||||
import * as fs from 'fs';
|
||||
import {Config} from "../common/types";
|
||||
import {
|
||||
CHANNEL_ERROR,
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
CHANNEL_SET_CONFIG,
|
||||
} from "../common/channels";
|
||||
import {ob11WebsocketServer} from "../onebot11/server/ws/WebsocketServer";
|
||||
import {checkFFMPEG, CONFIG_DIR, getConfigUtil, log} from "../common/utils";
|
||||
import {checkFfmpeg, CONFIG_DIR, getConfigUtil, log} from "../common/utils";
|
||||
import {
|
||||
addHistoryMsg,
|
||||
friendRequests,
|
||||
@@ -140,8 +140,10 @@ function onLoad() {
|
||||
|
||||
// 检查ffmpeg
|
||||
if (arg.ffmpeg) {
|
||||
checkFFMPEG(arg.ffmpeg).then(success => {
|
||||
llonebotError.ffmpegError = ''
|
||||
checkFfmpeg(arg.ffmpeg).then(success => {
|
||||
if (success){
|
||||
llonebotError.ffmpegError = ''
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -232,7 +234,7 @@ function onLoad() {
|
||||
"unreadCount": number
|
||||
}>(ReceiveCmd.UNREAD_GROUP_NOTIFY, async (payload) => {
|
||||
if (payload.unreadCount) {
|
||||
log("开始获取群通知详情")
|
||||
// log("开始获取群通知详情")
|
||||
let notify: GroupNotifies;
|
||||
try {
|
||||
notify = await NTQQApi.getGroupNotifies();
|
||||
@@ -242,14 +244,23 @@ function onLoad() {
|
||||
}
|
||||
|
||||
const notifies = notify.notifies.slice(0, payload.unreadCount)
|
||||
log("获取群通知详情完成", notifies, payload);
|
||||
// log("获取群通知详情完成", notifies, payload);
|
||||
try {
|
||||
for (const notify of notifies) {
|
||||
notify.time = Date.now();
|
||||
const notifyTime = parseInt(notify.seq) / 1000
|
||||
log(`加群通知时间${notifyTime}`, `LLOneBot启动时间${startTime}`);
|
||||
// log(`加群通知时间${notifyTime}`, `LLOneBot启动时间${startTime}`);
|
||||
if (notifyTime < startTime) {
|
||||
continue;
|
||||
}
|
||||
let existNotify = groupNotifies[notify.seq];
|
||||
if (existNotify){
|
||||
if (Date.now() - existNotify.time < 3000){
|
||||
continue
|
||||
}
|
||||
}
|
||||
log("收到群通知", notify);
|
||||
groupNotifies[notify.seq] = notify;
|
||||
const member1 = await getGroupMember(notify.group.groupCode, null, notify.user1.uid);
|
||||
let member2: GroupMember;
|
||||
if (notify.user2.uid) {
|
||||
@@ -274,7 +285,6 @@ function onLoad() {
|
||||
// postEvent(groupDecreaseEvent, true);
|
||||
} else if ([GroupNotifyTypes.JOIN_REQUEST].includes(notify.type)) {
|
||||
log("有加群请求");
|
||||
groupNotifies[notify.seq] = notify;
|
||||
let groupRequestEvent = new OB11GroupRequestEvent();
|
||||
groupRequestEvent.group_id = parseInt(notify.group.groupCode);
|
||||
let requestQQ = ""
|
||||
@@ -289,6 +299,15 @@ function onLoad() {
|
||||
groupRequestEvent.flag = notify.seq;
|
||||
postOB11Event(groupRequestEvent);
|
||||
}
|
||||
else if(notify.type == GroupNotifyTypes.INVITE_ME){
|
||||
let groupInviteEvent = new OB11GroupRequestEvent();
|
||||
groupInviteEvent.group_id = parseInt(notify.group.groupCode);
|
||||
let user_id = (await NTQQApi.getUserDetailInfo(notify.user2.uid))?.uin
|
||||
groupInviteEvent.user_id = parseInt(user_id);
|
||||
groupInviteEvent.sub_type = "invite";
|
||||
groupInviteEvent.flag = notify.seq;
|
||||
postOB11Event(groupInviteEvent);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
log("解析群通知失败", e.stack);
|
||||
@@ -324,7 +343,7 @@ function onLoad() {
|
||||
NTQQApi.getGroups(true).then()
|
||||
const config = getConfigUtil().getConfig()
|
||||
// 检查ffmpeg
|
||||
checkFFMPEG(config.ffmpeg).then(exist => {
|
||||
checkFfmpeg(config.ffmpeg).then(exist => {
|
||||
if (!exist) {
|
||||
llonebotError.ffmpegError = `没有找到ffmpeg,音频只能发送wav和silk`
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ import {
|
||||
AtType,
|
||||
ElementType,
|
||||
SendFaceElement,
|
||||
SendFileElement,
|
||||
SendPicElement,
|
||||
SendPttElement,
|
||||
SendReplyElement,
|
||||
@@ -9,7 +10,7 @@ import {
|
||||
} from "./types";
|
||||
import {NTQQApi} from "./ntcall";
|
||||
import {encodeSilk} from "../common/utils";
|
||||
import fs from "fs";
|
||||
import * as fs from "fs";
|
||||
|
||||
|
||||
export class SendMsgElementConstructor {
|
||||
@@ -55,7 +56,7 @@ export class SendMsgElementConstructor {
|
||||
}
|
||||
|
||||
static async pic(picPath: string): Promise<SendPicElement> {
|
||||
const {md5, fileName, path, fileSize} = await NTQQApi.uploadFile(picPath);
|
||||
const {md5, fileName, path, fileSize} = await NTQQApi.uploadFile(picPath, ElementType.PIC);
|
||||
const imageSize = await NTQQApi.getImageSize(picPath);
|
||||
const picElement = {
|
||||
md5HexStr: md5,
|
||||
@@ -80,12 +81,40 @@ export class SendMsgElementConstructor {
|
||||
};
|
||||
}
|
||||
|
||||
static async file(filePath: string, isVideo: boolean = false): Promise<SendFileElement> {
|
||||
let picHeight = 0;
|
||||
let picWidth = 0;
|
||||
if (isVideo) {
|
||||
picHeight = 1024;
|
||||
picWidth = 768;
|
||||
}
|
||||
const {md5, fileName, path, fileSize} = await NTQQApi.uploadFile(filePath, ElementType.FILE);
|
||||
let element: SendFileElement = {
|
||||
elementType: ElementType.FILE,
|
||||
elementId: "",
|
||||
fileElement: {
|
||||
fileName,
|
||||
"filePath": path,
|
||||
"fileSize": (fileSize).toString(),
|
||||
picHeight,
|
||||
picWidth
|
||||
}
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
static video(filePath: string): Promise<SendFileElement> {
|
||||
return SendMsgElementConstructor.file(filePath, true);
|
||||
}
|
||||
|
||||
static async ptt(pttPath: string): Promise<SendPttElement> {
|
||||
const {converted, path: silkPath, duration} = await encodeSilk(pttPath);
|
||||
// log("生成语音", silkPath, duration);
|
||||
const {md5, fileName, path, fileSize} = await NTQQApi.uploadFile(silkPath, ElementType.PTT);
|
||||
if (converted){
|
||||
fs.unlink(silkPath, ()=>{});
|
||||
if (converted) {
|
||||
fs.unlink(silkPath, () => {
|
||||
});
|
||||
}
|
||||
return {
|
||||
elementType: ElementType.PTT,
|
||||
|
@@ -2,7 +2,7 @@ import {BrowserWindow} from 'electron';
|
||||
import {getConfigUtil, log, sleep} from "../common/utils";
|
||||
import {NTQQApi, NTQQApiClass, sendMessagePool} from "./ntcall";
|
||||
import {Group, RawMessage, User} from "./types";
|
||||
import {addHistoryMsg, friends, groups, msgHistory} from "../common/data";
|
||||
import {addHistoryMsg, friends, groups, msgHistory, selfInfo} from "../common/data";
|
||||
import {OB11GroupDecreaseEvent} from "../onebot11/event/notice/OB11GroupDecreaseEvent";
|
||||
import {OB11GroupIncreaseEvent} from "../onebot11/event/notice/OB11GroupIncreaseEvent";
|
||||
import {v4 as uuidv4} from "uuid"
|
||||
@@ -24,7 +24,8 @@ export enum ReceiveCmd {
|
||||
MEDIA_DOWNLOAD_COMPLETE = "nodeIKernelMsgListener/onRichMediaDownloadComplete",
|
||||
UNREAD_GROUP_NOTIFY = "nodeIKernelGroupListener/onGroupNotifiesUnreadCountUpdated",
|
||||
GROUP_NOTIFY = "nodeIKernelGroupListener/onGroupSingleScreenNotifies",
|
||||
FRIEND_REQUEST = "nodeIKernelBuddyListener/onBuddyReqChange"
|
||||
FRIEND_REQUEST = "nodeIKernelBuddyListener/onBuddyReqChange",
|
||||
SELF_STATUS = "nodeIKernelProfileListener/onSelfStatusChanged",
|
||||
}
|
||||
|
||||
interface NTQQApiReturnData<PayloadType = unknown> extends Array<any> {
|
||||
@@ -230,7 +231,7 @@ registerReceiveHook<{
|
||||
})
|
||||
|
||||
registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.NEW_MSG, (payload) => {
|
||||
const {autoDeleteFile} = getConfigUtil().getConfig();
|
||||
const {autoDeleteFile, autoDeleteFileSecond} = getConfigUtil().getConfig();
|
||||
for (const message of payload.msgList) {
|
||||
// log("收到新消息,push到历史记录", message)
|
||||
addHistoryMsg(message)
|
||||
@@ -254,7 +255,7 @@ registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.NEW_MSG, (payload
|
||||
});
|
||||
}
|
||||
}
|
||||
}, 60 * 1000)
|
||||
}, autoDeleteFileSecond * 1000)
|
||||
}
|
||||
}
|
||||
const msgIds = Object.keys(msgHistory);
|
||||
@@ -277,3 +278,6 @@ registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmd.SELF_SEND_MSG, ({msgRe
|
||||
}
|
||||
})
|
||||
|
||||
registerReceiveHook<{info: {status: number}}>(ReceiveCmd.SELF_STATUS, (info)=>{
|
||||
selfInfo.online = info.info.status !== 20;
|
||||
})
|
@@ -1,6 +1,6 @@
|
||||
import {ipcMain} from "electron";
|
||||
import {hookApiCallbacks, ReceiveCmd, registerReceiveHook, removeReceiveHook} from "./hook";
|
||||
import {log} from "../common/utils";
|
||||
import {log, sleep} from "../common/utils";
|
||||
import {
|
||||
ChatType,
|
||||
ElementType,
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
FriendRequest,
|
||||
Group,
|
||||
GroupMember,
|
||||
GroupMemberRole,
|
||||
GroupNotifies,
|
||||
GroupNotify,
|
||||
GroupRequestOperateTypes,
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
import * as fs from "fs";
|
||||
import {addHistoryMsg, friendRequests, groupNotifies, msgHistory, selfInfo} from "../common/data";
|
||||
import {v4 as uuidv4} from "uuid"
|
||||
import path from "path";
|
||||
|
||||
interface IPCReceiveEvent {
|
||||
eventName: string
|
||||
@@ -62,6 +64,13 @@ export enum NTQQApiMethod {
|
||||
QUIT_GROUP = "nodeIKernelGroupService/quitGroup",
|
||||
// READ_FRIEND_REQUEST = "nodeIKernelBuddyListener/onDoubtBuddyReqUnreadNumChange"
|
||||
HANDLE_FRIEND_REQUEST = "nodeIKernelBuddyService/approvalFriendRequest",
|
||||
KICK_MEMBER = "nodeIKernelGroupService/kickMember",
|
||||
MUTE_MEMBER = "nodeIKernelGroupService/setMemberShutUp",
|
||||
MUTE_GROUP = "nodeIKernelGroupService/setGroupShutUp",
|
||||
SET_MEMBER_CARD = "nodeIKernelGroupService/modifyMemberCardName",
|
||||
SET_MEMBER_ROLE = "nodeIKernelGroupService/modifyMemberRole",
|
||||
PUBLISH_GROUP_BULLETIN = "nodeIKernelGroupService/publishGroupBulletinBulletin",
|
||||
SET_GROUP_NAME = "nodeIKernelGroupService/modifyGroupName",
|
||||
}
|
||||
|
||||
enum NTQQApiChannel {
|
||||
@@ -331,7 +340,7 @@ export class NTQQApi {
|
||||
}
|
||||
|
||||
// 上传文件到QQ的文件夹
|
||||
static async uploadFile(filePath: string, elementType: ElementType=ElementType.PIC) {
|
||||
static async uploadFile(filePath: string, elementType: ElementType = ElementType.PIC) {
|
||||
const md5 = await NTQQApi.getFileMd5(filePath);
|
||||
let ext = (await NTQQApi.getFileType(filePath))?.ext
|
||||
if (ext) {
|
||||
@@ -339,7 +348,10 @@ export class NTQQApi {
|
||||
} else {
|
||||
ext = ""
|
||||
}
|
||||
const fileName = `${md5}${ext}`;
|
||||
let fileName = `${path.basename(filePath)}`;
|
||||
if (fileName.indexOf(".") === -1) {
|
||||
fileName += ext;
|
||||
}
|
||||
const mediaPath = await callNTQQApi<string>({
|
||||
methodName: NTQQApiMethod.MEDIA_FILE_PATH,
|
||||
args: [{
|
||||
@@ -407,71 +419,57 @@ export class NTQQApi {
|
||||
})
|
||||
}
|
||||
|
||||
static sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = false, timeout = 10000) {
|
||||
const sendTimeout = timeout
|
||||
static async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = false, timeout = 10000) {
|
||||
const peerUid = peer.peerUid;
|
||||
|
||||
return new Promise<RawMessage>((resolve, reject) => {
|
||||
const peerUid = peer.peerUid;
|
||||
let usingTime = 0;
|
||||
let success = false;
|
||||
let isTimeout = false;
|
||||
|
||||
const checkSuccess = () => {
|
||||
if (!success) {
|
||||
sendMessagePool[peerUid] = null;
|
||||
isTimeout = true;
|
||||
reject("发送超时")
|
||||
}
|
||||
// 等待上一个相同的peer发送完
|
||||
let checkLastSendUsingTime = 0;
|
||||
const waitLastSend = async () => {
|
||||
if (checkLastSendUsingTime > timeout) {
|
||||
throw ("发送超时")
|
||||
}
|
||||
setTimeout(checkSuccess, sendTimeout);
|
||||
|
||||
const checkLastSend = () => {
|
||||
let lastSending = sendMessagePool[peerUid]
|
||||
if (sendTimeout < usingTime) {
|
||||
sendMessagePool[peerUid] = null;
|
||||
isTimeout = true;
|
||||
reject("发送超时")
|
||||
}
|
||||
if (!!lastSending) {
|
||||
// log("有正在发送的消息,等待中...")
|
||||
usingTime += 500;
|
||||
setTimeout(checkLastSend, 500);
|
||||
} else {
|
||||
log("可以进行发送消息,设置发送成功回调", sendMessagePool)
|
||||
sendMessagePool[peerUid] = (rawMessage: RawMessage) => {
|
||||
sendMessagePool[peerUid] = null;
|
||||
const checkSendComplete = () => {
|
||||
if (isTimeout) {
|
||||
return reject("发送超时")
|
||||
}
|
||||
if (msgHistory[rawMessage.msgId]?.sendStatus == 2) {
|
||||
log(`给${peerUid}发送消息成功`)
|
||||
success = true;
|
||||
resolve(rawMessage);
|
||||
} else {
|
||||
setTimeout(checkSendComplete, 500)
|
||||
}
|
||||
}
|
||||
if (waitComplete) {
|
||||
checkSendComplete();
|
||||
} else {
|
||||
success = true;
|
||||
log(`给${peerUid}发送消息成功`)
|
||||
resolve(rawMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
let lastSending = sendMessagePool[peer.peerUid]
|
||||
if (lastSending) {
|
||||
// log("有正在发送的消息,等待中...")
|
||||
await sleep(500);
|
||||
checkLastSendUsingTime += 500;
|
||||
return await waitLastSend();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
checkLastSend()
|
||||
callNTQQApi({
|
||||
methodName: NTQQApiMethod.SEND_MSG,
|
||||
args: [{
|
||||
msgId: "0",
|
||||
peer, msgElements,
|
||||
msgAttributeInfos: new Map(),
|
||||
}, null]
|
||||
}).then()
|
||||
})
|
||||
}
|
||||
await waitLastSend();
|
||||
|
||||
let sentMessage: RawMessage = null;
|
||||
sendMessagePool[peerUid] = async (rawMessage: RawMessage) => {
|
||||
delete sendMessagePool[peerUid];
|
||||
sentMessage = rawMessage;
|
||||
}
|
||||
|
||||
let checkSendCompleteUsingTime = 0;
|
||||
const checkSendComplete = async (): Promise<RawMessage> => {
|
||||
if (sentMessage && msgHistory[sentMessage.msgId]?.sendStatus == 2) {
|
||||
// log(`给${peerUid}发送消息成功`)
|
||||
return sentMessage;
|
||||
} else {
|
||||
checkSendCompleteUsingTime += 500;
|
||||
if (checkSendCompleteUsingTime > timeout) {
|
||||
throw ("发送超时")
|
||||
}
|
||||
await sleep(500);
|
||||
return await checkSendComplete()
|
||||
}
|
||||
}
|
||||
|
||||
callNTQQApi({
|
||||
methodName: NTQQApiMethod.SEND_MSG,
|
||||
args: [{
|
||||
msgId: "0",
|
||||
peer, msgElements,
|
||||
msgAttributeInfos: new Map(),
|
||||
}, null]
|
||||
}).then()
|
||||
return checkSendComplete();
|
||||
}
|
||||
|
||||
static multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) {
|
||||
@@ -550,6 +548,7 @@ export class NTQQApi {
|
||||
if (!notify) {
|
||||
throw `${seq}对应的加群通知不存在`
|
||||
}
|
||||
delete groupNotifies[seq];
|
||||
return await callNTQQApi<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.HANDLE_GROUP_REQUEST,
|
||||
args: [
|
||||
@@ -582,7 +581,7 @@ export class NTQQApi {
|
||||
|
||||
static async handleFriendRequest(sourceId: number, accept: boolean,) {
|
||||
const request: FriendRequest = friendRequests[sourceId]
|
||||
if (!request){
|
||||
if (!request) {
|
||||
throw `sourceId ${sourceId}, 对应的好友请求不存在`
|
||||
}
|
||||
const result = await callNTQQApi<GeneralCallResult>({
|
||||
@@ -600,4 +599,89 @@ export class NTQQApi {
|
||||
delete friendRequests[sourceId];
|
||||
return result;
|
||||
}
|
||||
|
||||
static kickMember(groupQQ: string, kickUids: string[], refuseForever: boolean = false, kickReason: string = "") {
|
||||
return callNTQQApi<GeneralCallResult>(
|
||||
{
|
||||
methodName: NTQQApiMethod.KICK_MEMBER,
|
||||
args: [
|
||||
{
|
||||
groupCode: groupQQ,
|
||||
kickUids,
|
||||
refuseForever,
|
||||
kickReason,
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
static banMember(groupQQ: string, memList: { uid: string, timeStamp: number }[]) {
|
||||
// timeStamp为秒数, 0为解除禁言
|
||||
return callNTQQApi<GeneralCallResult>(
|
||||
{
|
||||
methodName: NTQQApiMethod.MUTE_MEMBER,
|
||||
args: [
|
||||
{
|
||||
groupCode: groupQQ,
|
||||
memList,
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
static banGroup(groupQQ: string, shutUp: boolean) {
|
||||
return callNTQQApi<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.MUTE_GROUP,
|
||||
args: [
|
||||
{
|
||||
groupCode: groupQQ,
|
||||
shutUp
|
||||
}, null
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
static setMemberCard(groupQQ: string, memberUid: string, cardName: string) {
|
||||
return callNTQQApi<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.SET_MEMBER_CARD,
|
||||
args: [
|
||||
{
|
||||
groupCode: groupQQ,
|
||||
uid: memberUid,
|
||||
cardName
|
||||
}, null
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
static setMemberRole(groupQQ: string, memberUid: string, role: GroupMemberRole) {
|
||||
return callNTQQApi<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.SET_MEMBER_ROLE,
|
||||
args: [
|
||||
{
|
||||
groupCode: groupQQ,
|
||||
uid: memberUid,
|
||||
role
|
||||
}, null
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
static setGroupName(groupQQ: string, groupName: string) {
|
||||
return callNTQQApi<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.SET_GROUP_NAME,
|
||||
args: [
|
||||
{
|
||||
groupCode: groupQQ,
|
||||
groupName
|
||||
}, null
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
static publishGroupBulletin(groupQQ: string, title: string, content: string) {
|
||||
|
||||
}
|
||||
}
|
@@ -8,7 +8,7 @@ export interface User {
|
||||
}
|
||||
|
||||
export interface SelfInfo extends User {
|
||||
|
||||
online?: boolean;
|
||||
}
|
||||
|
||||
export interface Friend extends User {
|
||||
@@ -45,6 +45,12 @@ export interface Group {
|
||||
members: GroupMember[] // 原始数据是没有这个的,为了方便自己加了这个字段
|
||||
}
|
||||
|
||||
export enum GroupMemberRole {
|
||||
normal = 2,
|
||||
admin = 3,
|
||||
owner = 4
|
||||
}
|
||||
|
||||
export interface GroupMember {
|
||||
avatarPath: string;
|
||||
cardName: string;
|
||||
@@ -53,7 +59,7 @@ export interface GroupMember {
|
||||
nick: string;
|
||||
qid: string;
|
||||
remark: string;
|
||||
role: number; // 群主:4, 管理员:3,群员:2
|
||||
role: GroupMemberRole; // 群主:4, 管理员:3,群员:2
|
||||
shutUpTime: number; // 禁言时间,单位是什么暂时不清楚
|
||||
uid: string; // 加密的字符串
|
||||
uin: string; // QQ号
|
||||
@@ -62,6 +68,7 @@ export interface GroupMember {
|
||||
export enum ElementType {
|
||||
TEXT = 1,
|
||||
PIC = 2,
|
||||
FILE = 3,
|
||||
PTT = 4,
|
||||
FACE = 6,
|
||||
REPLY = 7,
|
||||
@@ -136,7 +143,30 @@ export interface SendFaceElement {
|
||||
faceElement: FaceElement
|
||||
}
|
||||
|
||||
export type SendMessageElement = SendTextElement | SendPttElement | SendPicElement | SendReplyElement | SendFaceElement
|
||||
export interface FileElement {
|
||||
"fileMd5"?: "",
|
||||
"fileName": string,
|
||||
"filePath": string,
|
||||
"fileSize": string,
|
||||
"picHeight"?: number,
|
||||
"picWidth"?: number,
|
||||
"picThumbPath"?: {},
|
||||
"file10MMd5"?: "",
|
||||
"fileSha"?: "",
|
||||
"fileSha3"?: "",
|
||||
"fileUuid"?: "",
|
||||
"fileSubId"?: "",
|
||||
"thumbFileSize"?: number
|
||||
}
|
||||
|
||||
export interface SendFileElement {
|
||||
"elementType": ElementType.FILE,
|
||||
"elementId": "",
|
||||
"fileElement": FileElement
|
||||
}
|
||||
|
||||
export type SendMessageElement = SendTextElement | SendPttElement |
|
||||
SendPicElement | SendReplyElement | SendFaceElement | SendFileElement
|
||||
|
||||
export enum AtType {
|
||||
notAt = 0,
|
||||
@@ -206,10 +236,35 @@ export interface FaceElement {
|
||||
faceType: 1
|
||||
}
|
||||
|
||||
export interface VideoElement {
|
||||
"filePath": string,
|
||||
"fileName": string,
|
||||
"videoMd5": string,
|
||||
"thumbMd5": string
|
||||
"fileTime": 87, // second
|
||||
"thumbSize": 314235, // byte
|
||||
"fileFormat": 2, // 2表示mp4?
|
||||
"fileSize": string, // byte
|
||||
"thumbWidth": number,
|
||||
"thumbHeight": number,
|
||||
"busiType": 0, // 未知
|
||||
"subBusiType": 0, // 未知
|
||||
"thumbPath": Map<number,any>,
|
||||
"transferStatus": 0, // 未知
|
||||
"progress": 0, // 下载进度?
|
||||
"invalidState": 0, // 未知
|
||||
"fileUuid": string, // 可以用于下载链接?
|
||||
"fileSubId": "",
|
||||
"fileBizId": null,
|
||||
"originVideoMd5": "",
|
||||
"import_rich_media_context": null,
|
||||
"sourceVideoCodecFormat": 0
|
||||
}
|
||||
|
||||
export interface RawMessage {
|
||||
msgId: string;
|
||||
msgShortId?: number; // 自己维护的消息id
|
||||
msgTime: string;
|
||||
msgTime: string; // 时间戳,秒
|
||||
msgSeq: string;
|
||||
senderUid: string;
|
||||
senderUin?: string; // 发送者QQ号
|
||||
@@ -222,6 +277,7 @@ export interface RawMessage {
|
||||
recallTime: string; // 撤回时间, "0"是没有撤回
|
||||
elements: {
|
||||
elementId: string,
|
||||
elementType: ElementType;
|
||||
replyElement: {
|
||||
senderUid: string; // 原消息发送者QQ号
|
||||
sourceMsgIsIncPic: boolean; // 原消息是否有图片
|
||||
@@ -239,10 +295,13 @@ export interface RawMessage {
|
||||
arkElement: ArkElement;
|
||||
grayTipElement: GrayTipElement;
|
||||
faceElement: FaceElement;
|
||||
videoElement: VideoElement;
|
||||
fileElement: FileElement;
|
||||
}[];
|
||||
}
|
||||
|
||||
export enum GroupNotifyTypes {
|
||||
INVITE_ME = 1,
|
||||
INVITED_JOIN = 4, // 有人接受了邀请入群
|
||||
JOIN_REQUEST = 7,
|
||||
ADMIN_SET = 8,
|
||||
@@ -258,7 +317,7 @@ export interface GroupNotifies {
|
||||
}
|
||||
|
||||
export interface GroupNotify {
|
||||
|
||||
time: number; // 自己添加的字段,时间戳,毫秒, 用于判断收到短时间内收到重复的notify
|
||||
seq: string, // 转成数字,再除以1000应该就是时间戳?
|
||||
type: GroupNotifyTypes,
|
||||
status: 0, // 未知
|
||||
@@ -276,12 +335,12 @@ export interface GroupNotify {
|
||||
warningTips: string
|
||||
}
|
||||
|
||||
export enum GroupRequestOperateTypes{
|
||||
export enum GroupRequestOperateTypes {
|
||||
approve = 1,
|
||||
reject = 2
|
||||
}
|
||||
|
||||
export interface FriendRequest{
|
||||
export interface FriendRequest {
|
||||
friendUid: string,
|
||||
reqTime: string, // 时间戳,秒
|
||||
extWords: string, // 申请人填写的验证消息
|
||||
@@ -290,7 +349,8 @@ export interface FriendRequest{
|
||||
sourceId: number,
|
||||
groupCode: string
|
||||
}
|
||||
export interface FriendRequestNotify{
|
||||
|
||||
export interface FriendRequestNotify {
|
||||
data: {
|
||||
unreadNums: number,
|
||||
buddyReqs: FriendRequest[]
|
||||
|
47
src/onebot11/action/GetFile.ts
Normal file
47
src/onebot11/action/GetFile.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import BaseAction from "./BaseAction";
|
||||
import {fileCache} from "../../common/data";
|
||||
import {getConfigUtil} from "../../common/utils";
|
||||
import fs from "fs/promises";
|
||||
|
||||
export interface GetFilePayload{
|
||||
file: string // 文件名
|
||||
}
|
||||
|
||||
export interface GetFileResponse{
|
||||
file?: string // path
|
||||
url?: string
|
||||
file_size?: string
|
||||
file_name?: string
|
||||
base64?: string
|
||||
}
|
||||
|
||||
|
||||
export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse>{
|
||||
protected async _handle(payload: GetFilePayload): Promise<GetFileResponse> {
|
||||
const cache = fileCache.get(payload.file)
|
||||
const {autoDeleteFile, enableLocalFile2Url, autoDeleteFileSecond} = getConfigUtil().getConfig()
|
||||
if (!cache) {
|
||||
throw new Error('file not found')
|
||||
}
|
||||
if (cache.downloadFunc) {
|
||||
await cache.downloadFunc()
|
||||
}
|
||||
let res : GetFileResponse= {
|
||||
file: cache.filePath,
|
||||
url: cache.url,
|
||||
file_size: cache.fileSize,
|
||||
file_name: cache.fileName
|
||||
}
|
||||
if (enableLocalFile2Url) {
|
||||
if (!cache.url) {
|
||||
res.base64 = await fs.readFile(cache.filePath, 'base64')
|
||||
}
|
||||
}
|
||||
if (autoDeleteFile) {
|
||||
setTimeout(() => {
|
||||
fs.unlink(cache.filePath)
|
||||
}, autoDeleteFileSecond * 1000)
|
||||
}
|
||||
return res
|
||||
}
|
||||
}
|
7
src/onebot11/action/GetImage.ts
Normal file
7
src/onebot11/action/GetImage.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import {GetFileBase} from "./GetFile";
|
||||
import {ActionName} from "./types";
|
||||
|
||||
|
||||
export default class GetImage extends GetFileBase{
|
||||
actionName = ActionName.GetImage
|
||||
}
|
15
src/onebot11/action/GetRecord.ts
Normal file
15
src/onebot11/action/GetRecord.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import {GetFileBase, GetFilePayload, GetFileResponse} from "./GetFile";
|
||||
import {ActionName} from "./types";
|
||||
|
||||
interface Payload extends GetFilePayload{
|
||||
out_format: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac'
|
||||
}
|
||||
|
||||
export default class GetRecord extends GetFileBase{
|
||||
actionName = ActionName.GetRecord
|
||||
|
||||
protected async _handle(payload: Payload): Promise<GetFileResponse> {
|
||||
let res = super._handle(payload);
|
||||
return res;
|
||||
}
|
||||
}
|
@@ -1,13 +1,14 @@
|
||||
import BaseAction from "./BaseAction";
|
||||
import {OB11Status} from "../types";
|
||||
import {ActionName} from "./types";
|
||||
import {selfInfo} from "../../common/data";
|
||||
|
||||
|
||||
export default class GetStatus extends BaseAction<any, OB11Status> {
|
||||
actionName = ActionName.GetStatus
|
||||
protected async _handle(payload: any): Promise<OB11Status> {
|
||||
return {
|
||||
online: null,
|
||||
online: selfInfo.online,
|
||||
good: true
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import BaseAction from "./BaseAction";
|
||||
import {OB11Version} from "../types";
|
||||
import {version} from "../../common/data";
|
||||
import {ActionName} from "./types";
|
||||
import {version} from "../../version";
|
||||
|
||||
export default class GetVersionInfo extends BaseAction<any, OB11Version>{
|
||||
actionName = ActionName.GetVersionInfo
|
||||
|
@@ -1,5 +1,13 @@
|
||||
import {AtType, ChatType, Group, SendMessageElement} from "../../ntqqapi/types";
|
||||
import {addHistoryMsg, friends, getGroup, getHistoryMsgByShortId, getUidByUin, selfInfo,} from "../../common/data";
|
||||
import {AtType, ChatType, Group, RawMessage, SendMessageElement} from "../../ntqqapi/types";
|
||||
import {
|
||||
addHistoryMsg,
|
||||
friends,
|
||||
getGroup,
|
||||
getGroupMember,
|
||||
getHistoryMsgByShortId,
|
||||
getUidByUin,
|
||||
selfInfo,
|
||||
} from "../../common/data";
|
||||
import {OB11MessageData, OB11MessageDataType, OB11MessageMixType, OB11MessageNode, OB11PostSendMsg} from '../types';
|
||||
import {NTQQApi, Peer} from "../../ntqqapi/ntcall";
|
||||
import {SendMsgElementConstructor} from "../../ntqqapi/constructor";
|
||||
@@ -8,7 +16,6 @@ import BaseAction from "./BaseAction";
|
||||
import {ActionName, BaseCheckResult} from "./types";
|
||||
import * as fs from "fs";
|
||||
import {log} from "../../common/utils";
|
||||
import {v4 as uuidv4} from "uuid"
|
||||
import {decodeCQCode} from "../cqcode";
|
||||
|
||||
function checkSendMessage(sendMsgList: OB11MessageData[]) {
|
||||
@@ -144,15 +151,16 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
chatType: ChatType.friend,
|
||||
peerUid: selfInfo.uid
|
||||
}
|
||||
let nodeIds: string[] = []
|
||||
for (const messageNode of messageNodes){
|
||||
let selfNodeMsgList: RawMessage[] = [];
|
||||
let originalNodeMsgList: RawMessage[] = [];
|
||||
for (const messageNode of messageNodes) {
|
||||
// 一个node表示一个人的消息
|
||||
let nodeId = messageNode.data.id;
|
||||
// 有nodeId表示一个子转发消息卡片
|
||||
if (nodeId) {
|
||||
let nodeMsg = getHistoryMsgByShortId(nodeId);
|
||||
if (nodeMsg){
|
||||
nodeIds.push(nodeMsg.msgId);
|
||||
if (nodeMsg) {
|
||||
originalNodeMsgList.push(nodeMsg);
|
||||
}
|
||||
} else {
|
||||
// 自定义的消息
|
||||
@@ -164,7 +172,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
} = await this.createSendElements(this.convertMessage2List(messageNode.data.content), group);
|
||||
log("开始生成转发节点", sendElements);
|
||||
const nodeMsg = await this.send(selfPeer, sendElements, deleteAfterSentFiles, true);
|
||||
nodeIds.push(nodeMsg.msgId)
|
||||
selfNodeMsgList.push(nodeMsg);
|
||||
log("转发节点生成成功", nodeMsg.msgId);
|
||||
} catch (e) {
|
||||
log("生效转发消息节点失败", e)
|
||||
@@ -172,9 +180,49 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
}
|
||||
}
|
||||
|
||||
let nodeIds: string[] = []
|
||||
// 检查是否需要克隆直接引用消息id的节点
|
||||
let needSendSelf = false;
|
||||
if (selfNodeMsgList.length) {
|
||||
needSendSelf = true
|
||||
} else {
|
||||
needSendSelf = !originalNodeMsgList.every((msg, index) => msg.peerUid === originalNodeMsgList[0].peerUid && msg.recallTime.length < 2)
|
||||
}
|
||||
if (needSendSelf) {
|
||||
nodeIds = selfNodeMsgList.map(msg => msg.msgId);
|
||||
for (const originalNodeMsg of originalNodeMsgList) {
|
||||
if (originalNodeMsg.peerUid === selfInfo.uid && originalNodeMsg.recallTime.length < 2) {
|
||||
nodeIds.push(originalNodeMsg.msgId)
|
||||
} else { // 需要进行克隆
|
||||
let sendElements: SendMessageElement[] = []
|
||||
Object.keys(originalNodeMsg.elements).forEach((eleKey) => {
|
||||
if (eleKey !== "elementId") {
|
||||
sendElements.push(originalNodeMsg.elements[eleKey])
|
||||
}
|
||||
})
|
||||
try {
|
||||
const nodeMsg = await NTQQApi.sendMsg(selfPeer, sendElements, true);
|
||||
nodeIds.push(nodeMsg.msgId)
|
||||
log("克隆转发消息成功")
|
||||
} catch (e) {
|
||||
log("克隆转发消息失败", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
nodeIds = originalNodeMsgList.map(msg => msg.msgId)
|
||||
}
|
||||
|
||||
let srcPeer = selfPeer;
|
||||
if (!needSendSelf) {
|
||||
srcPeer = {
|
||||
chatType: originalNodeMsgList[0].chatType === ChatType.group ? ChatType.group : ChatType.friend,
|
||||
peerUid: originalNodeMsgList[0].peerUid
|
||||
}
|
||||
}
|
||||
// 开发转发
|
||||
try {
|
||||
return await NTQQApi.multiForwardMsg(selfPeer, destPeer, nodeIds)
|
||||
return await NTQQApi.multiForwardMsg(srcPeer, destPeer, nodeIds)
|
||||
} catch (e) {
|
||||
log("forward failed", e)
|
||||
return null;
|
||||
@@ -203,7 +251,8 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
if (atQQ === "all") {
|
||||
sendElements.push(SendMsgElementConstructor.at(atQQ, atQQ, AtType.atAll, "全体成员"))
|
||||
} else {
|
||||
const atMember = group?.members.find(m => m.uin == atQQ)
|
||||
// const atMember = group?.members.find(m => m.uin == atQQ)
|
||||
const atMember = await getGroupMember(group?.groupCode, atQQ);
|
||||
if (atMember) {
|
||||
sendElements.push(SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick))
|
||||
}
|
||||
@@ -230,19 +279,23 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
}
|
||||
break;
|
||||
case OB11MessageDataType.image:
|
||||
case OB11MessageDataType.file:
|
||||
case OB11MessageDataType.video:
|
||||
case OB11MessageDataType.voice: {
|
||||
const file = sendMsg.data?.file
|
||||
if (file) {
|
||||
const {path, isLocal} = (await uri2local(uuidv4(), file))
|
||||
const {path, isLocal} = (await uri2local(file))
|
||||
if (path) {
|
||||
if (!isLocal) { // 只删除http和base64转过来的文件
|
||||
deleteAfterSentFiles.push(path)
|
||||
}
|
||||
if (sendMsg.type === OB11MessageDataType.image) {
|
||||
sendElements.push(await SendMsgElementConstructor.pic(path))
|
||||
} else {
|
||||
sendElements.push(await SendMsgElementConstructor.ptt(path))
|
||||
const constructorMap = {
|
||||
[OB11MessageDataType.image]: SendMsgElementConstructor.pic,
|
||||
[OB11MessageDataType.voice]: SendMsgElementConstructor.ptt,
|
||||
[OB11MessageDataType.video]: SendMsgElementConstructor.video,
|
||||
[OB11MessageDataType.file]: SendMsgElementConstructor.file,
|
||||
}
|
||||
sendElements.push(await constructorMap[sendMsg.type](path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
23
src/onebot11/action/SetGroupAdmin.ts
Normal file
23
src/onebot11/action/SetGroupAdmin.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import BaseAction from "./BaseAction";
|
||||
import {NTQQApi} from "../../ntqqapi/ntcall";
|
||||
import {getGroupMember} from "../../common/data";
|
||||
import {GroupMemberRole} from "../../ntqqapi/types";
|
||||
import {ActionName} from "./types";
|
||||
|
||||
interface Payload{
|
||||
group_id: number,
|
||||
user_id: number,
|
||||
enable: boolean
|
||||
}
|
||||
|
||||
export default class SetGroupAdmin extends BaseAction<Payload, null>{
|
||||
actionName = ActionName.SetGroupAdmin
|
||||
protected async _handle(payload: Payload): Promise<null> {
|
||||
const member = await getGroupMember(payload.group_id, payload.user_id)
|
||||
if(!member){
|
||||
throw `群成员${payload.user_id}不存在`
|
||||
}
|
||||
await NTQQApi.setMemberRole(payload.group_id.toString(), member.uid, payload.enable ? GroupMemberRole.admin : GroupMemberRole.normal)
|
||||
return null
|
||||
}
|
||||
}
|
23
src/onebot11/action/SetGroupBan.ts
Normal file
23
src/onebot11/action/SetGroupBan.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import BaseAction from "./BaseAction";
|
||||
import {NTQQApi} from "../../ntqqapi/ntcall";
|
||||
import {getGroupMember} from "../../common/data";
|
||||
import {ActionName} from "./types";
|
||||
|
||||
interface Payload{
|
||||
group_id: number,
|
||||
user_id: number,
|
||||
duration: number
|
||||
}
|
||||
|
||||
export default class SetGroupBan extends BaseAction<Payload, null>{
|
||||
actionName = ActionName.SetGroupBan
|
||||
protected async _handle(payload: Payload): Promise<null> {
|
||||
const member = await getGroupMember(payload.group_id, payload.user_id)
|
||||
if(!member){
|
||||
throw `群成员${payload.user_id}不存在`
|
||||
}
|
||||
await NTQQApi.banMember(payload.group_id.toString(),
|
||||
[{uid:member.uid, timeStamp: parseInt(payload.duration.toString())}])
|
||||
return null
|
||||
}
|
||||
}
|
23
src/onebot11/action/SetGroupCard.ts
Normal file
23
src/onebot11/action/SetGroupCard.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import BaseAction from "./BaseAction";
|
||||
import {NTQQApi} from "../../ntqqapi/ntcall";
|
||||
import {getGroupMember} from "../../common/data";
|
||||
import {GroupMemberRole} from "../../ntqqapi/types";
|
||||
import {ActionName} from "./types";
|
||||
|
||||
interface Payload{
|
||||
group_id: number,
|
||||
user_id: number,
|
||||
card: string
|
||||
}
|
||||
|
||||
export default class SetGroupCard extends BaseAction<Payload, null>{
|
||||
actionName = ActionName.SetGroupCard
|
||||
protected async _handle(payload: Payload): Promise<null> {
|
||||
const member = await getGroupMember(payload.group_id, payload.user_id)
|
||||
if(!member){
|
||||
throw `群成员${payload.user_id}不存在`
|
||||
}
|
||||
await NTQQApi.setMemberCard(payload.group_id.toString(), member.uid, payload.card || "")
|
||||
return null
|
||||
}
|
||||
}
|
22
src/onebot11/action/SetGroupKick.ts
Normal file
22
src/onebot11/action/SetGroupKick.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import BaseAction from "./BaseAction";
|
||||
import {NTQQApi} from "../../ntqqapi/ntcall";
|
||||
import {getGroupMember} from "../../common/data";
|
||||
import {ActionName} from "./types";
|
||||
|
||||
interface Payload{
|
||||
group_id: number,
|
||||
user_id: number,
|
||||
reject_add_request: boolean
|
||||
}
|
||||
|
||||
export default class SetGroupKick extends BaseAction<Payload, null>{
|
||||
actionName = ActionName.SetGroupKick
|
||||
protected async _handle(payload: Payload): Promise<null> {
|
||||
const member = await getGroupMember(payload.group_id, payload.user_id)
|
||||
if(!member){
|
||||
throw `群成员${payload.user_id}不存在`
|
||||
}
|
||||
await NTQQApi.kickMember(payload.group_id.toString(), [member.uid], !!payload.reject_add_request);
|
||||
return null
|
||||
}
|
||||
}
|
18
src/onebot11/action/SetGroupName.ts
Normal file
18
src/onebot11/action/SetGroupName.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import BaseAction from "./BaseAction";
|
||||
import {NTQQApi} from "../../ntqqapi/ntcall";
|
||||
import {ActionName} from "./types";
|
||||
|
||||
interface Payload {
|
||||
group_id: number,
|
||||
group_name: string
|
||||
}
|
||||
|
||||
export default class SetGroupName extends BaseAction<Payload, null> {
|
||||
actionName = ActionName.SetGroupName
|
||||
|
||||
protected async _handle(payload: Payload): Promise<null> {
|
||||
|
||||
await NTQQApi.setGroupName(payload.group_id.toString(), payload.group_name)
|
||||
return null
|
||||
}
|
||||
}
|
18
src/onebot11/action/SetGroupWholeBan.ts
Normal file
18
src/onebot11/action/SetGroupWholeBan.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import BaseAction from "./BaseAction";
|
||||
import {NTQQApi} from "../../ntqqapi/ntcall";
|
||||
import {ActionName} from "./types";
|
||||
|
||||
interface Payload {
|
||||
group_id: number,
|
||||
enable: boolean
|
||||
}
|
||||
|
||||
export default class SetGroupWholeBan extends BaseAction<Payload, null> {
|
||||
actionName = ActionName.SetGroupWholeBan
|
||||
|
||||
protected async _handle(payload: Payload): Promise<null> {
|
||||
|
||||
await NTQQApi.banGroup(payload.group_id.toString(), !!payload.enable)
|
||||
return null
|
||||
}
|
||||
}
|
@@ -22,6 +22,14 @@ import SetGroupLeave from "./SetGroupLeave";
|
||||
import GetGuildList from "./GetGuildList";
|
||||
import Debug from "./Debug";
|
||||
import SetFriendAddRequest from "./SetFriendAddRequest";
|
||||
import SetGroupWholeBan from "./SetGroupWholeBan";
|
||||
import SetGroupName from "./SetGroupName";
|
||||
import SetGroupBan from "./SetGroupBan";
|
||||
import SetGroupKick from "./SetGroupKick";
|
||||
import SetGroupAdmin from "./SetGroupAdmin";
|
||||
import SetGroupCard from "./SetGroupCard";
|
||||
import GetImage from "./GetImage";
|
||||
import GetRecord from "./GetRecord";
|
||||
|
||||
export const actionHandlers = [
|
||||
new Debug(),
|
||||
@@ -39,6 +47,14 @@ export const actionHandlers = [
|
||||
new CanSendRecord(),
|
||||
new CanSendImage(),
|
||||
new GetStatus(),
|
||||
new SetGroupWholeBan(),
|
||||
new SetGroupBan(),
|
||||
new SetGroupKick(),
|
||||
new SetGroupAdmin(),
|
||||
new SetGroupName(),
|
||||
new SetGroupCard(),
|
||||
new GetImage(),
|
||||
new GetRecord(),
|
||||
|
||||
//以下为go-cqhttp api
|
||||
new GoCQHTTPSendGroupForwardMsg(),
|
||||
|
@@ -34,6 +34,14 @@ export enum ActionName {
|
||||
GetStatus = "get_status",
|
||||
CanSendRecord = "can_send_record",
|
||||
CanSendImage = "can_send_image",
|
||||
SetGroupKick = "set_group_kick",
|
||||
SetGroupBan = "set_group_ban",
|
||||
SetGroupWholeBan = "set_group_whole_ban",
|
||||
SetGroupAdmin = "set_group_admin",
|
||||
SetGroupCard = "set_group_card",
|
||||
SetGroupName = "set_group_name",
|
||||
GetImage = "get_image",
|
||||
GetRecord = "get_record",
|
||||
// 以下为go-cqhttp api
|
||||
GoCQHTTP_SendGroupForwardMsg = "send_group_forward_msg",
|
||||
GoCQHTTP_SendPrivateForwardMsg = "send_private_forward_msg",
|
||||
|
@@ -7,23 +7,23 @@ import {
|
||||
OB11MessageDataType,
|
||||
OB11User
|
||||
} from "./types";
|
||||
import {AtType, ChatType, Group, GroupMember, IMAGE_HTTP_HOST, RawMessage, SelfInfo, User} from '../ntqqapi/types';
|
||||
import {getFriend, getGroupMember, getHistoryMsgBySeq, selfInfo} from '../common/data';
|
||||
import {file2base64, getConfigUtil, log} from "../common/utils";
|
||||
import {NTQQApi} from "../ntqqapi/ntcall";
|
||||
import {EventType} from "./event/OB11BaseEvent";
|
||||
import {encodeCQCode} from "./cqcode";
|
||||
import { AtType, ChatType, Group, GroupMember, IMAGE_HTTP_HOST, RawMessage, SelfInfo, User } from '../ntqqapi/types';
|
||||
import {fileCache, getFriend, getGroupMember, getHistoryMsgBySeq, selfInfo} from '../common/data';
|
||||
import { file2base64, getConfigUtil, log } from "../common/utils";
|
||||
import { NTQQApi } from "../ntqqapi/ntcall";
|
||||
import { EventType } from "./event/OB11BaseEvent";
|
||||
import { encodeCQCode } from "./cqcode";
|
||||
|
||||
|
||||
export class OB11Constructor {
|
||||
static async message(msg: RawMessage): Promise<OB11Message> {
|
||||
|
||||
const {enableLocalFile2Url, ob11: {messagePostFormat}} = getConfigUtil().getConfig()
|
||||
const { enableLocalFile2Url, ob11: { messagePostFormat } } = getConfigUtil().getConfig()
|
||||
const message_type = msg.chatType == ChatType.group ? "group" : "private";
|
||||
const resMsg: OB11Message = {
|
||||
self_id: parseInt(selfInfo.uin),
|
||||
user_id: parseInt(msg.senderUin),
|
||||
time: parseInt(msg.msgTime) || 0,
|
||||
time: parseInt(msg.msgTime) || Date.now(),
|
||||
message_id: msg.msgShortId,
|
||||
real_id: msg.msgId,
|
||||
message_type: msg.chatType == ChatType.group ? "group" : "private",
|
||||
@@ -65,7 +65,7 @@ export class OB11Constructor {
|
||||
if (element.textElement && element.textElement?.atType !== AtType.notAt) {
|
||||
message_data["type"] = OB11MessageDataType.at
|
||||
if (element.textElement.atType == AtType.atAll) {
|
||||
message_data["data"]["mention"] = "all"
|
||||
// message_data["data"]["mention"] = "all"
|
||||
message_data["data"]["qq"] = "all"
|
||||
} else {
|
||||
let atUid = element.textElement.atNtUid
|
||||
@@ -77,7 +77,7 @@ export class OB11Constructor {
|
||||
}
|
||||
}
|
||||
if (atQQ) {
|
||||
message_data["data"]["mention"] = atQQ
|
||||
// message_data["data"]["mention"] = atQQ
|
||||
message_data["data"]["qq"] = atQQ
|
||||
}
|
||||
}
|
||||
@@ -88,17 +88,6 @@ export class OB11Constructor {
|
||||
continue;
|
||||
}
|
||||
message_data["data"]["text"] = text
|
||||
} else if (element.picElement) {
|
||||
message_data["type"] = "image"
|
||||
message_data["data"]["file_id"] = element.picElement.fileUuid
|
||||
message_data["data"]["path"] = element.picElement.sourcePath
|
||||
message_data["data"]["file"] = element.picElement.sourcePath
|
||||
message_data["data"]["url"] = IMAGE_HTTP_HOST + element.picElement.originImageUrl
|
||||
try {
|
||||
await NTQQApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid,
|
||||
element.elementId, element.picElement.thumbPath.get(0), element.picElement.sourcePath)
|
||||
} catch (e) {
|
||||
}
|
||||
} else if (element.replyElement) {
|
||||
message_data["type"] = "reply"
|
||||
const replyMsg = getHistoryMsgBySeq(element.replyElement.replayMsgSeq)
|
||||
@@ -107,10 +96,67 @@ export class OB11Constructor {
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
} else if (element.pttElement) {
|
||||
} else if (element.picElement) {
|
||||
message_data["type"] = "image"
|
||||
// message_data["data"]["file"] = element.picElement.sourcePath
|
||||
message_data["data"]["file"] = element.picElement.fileName
|
||||
// message_data["data"]["path"] = element.picElement.sourcePath
|
||||
message_data["data"]["url"] = IMAGE_HTTP_HOST + element.picElement.originImageUrl
|
||||
// message_data["data"]["file_id"] = element.picElement.fileUuid
|
||||
message_data["data"]["file_size"] = element.picElement.fileSize
|
||||
fileCache.set(element.picElement.fileName, {
|
||||
fileName: element.picElement.fileName,
|
||||
filePath: element.picElement.sourcePath,
|
||||
fileSize: element.picElement.fileSize.toString(),
|
||||
url: IMAGE_HTTP_HOST + element.picElement.originImageUrl,
|
||||
downloadFunc: async () => {
|
||||
await NTQQApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid,
|
||||
element.elementId, element.picElement.thumbPath.get(0), element.picElement.sourcePath)
|
||||
}})
|
||||
// 不在自动下载图片
|
||||
|
||||
} else if (element.videoElement) {
|
||||
message_data["type"] = OB11MessageDataType.video;
|
||||
message_data["data"]["file"] = element.videoElement.fileName
|
||||
message_data["data"]["path"] = element.videoElement.filePath
|
||||
// message_data["data"]["file_id"] = element.videoElement.fileUuid
|
||||
message_data["data"]["file_size"] = element.videoElement.fileSize
|
||||
fileCache.set(element.videoElement.fileName, {
|
||||
fileName: element.videoElement.fileName,
|
||||
filePath: element.videoElement.filePath,
|
||||
fileSize: element.videoElement.fileSize,
|
||||
downloadFunc: async () => {
|
||||
await NTQQApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid,
|
||||
element.elementId, element.videoElement.thumbPath.get(0), element.videoElement.filePath)
|
||||
}})
|
||||
// 怎么拿到url呢
|
||||
} else if (element.fileElement) {
|
||||
message_data["type"] = OB11MessageDataType.file;
|
||||
message_data["data"]["file"] = element.fileElement.fileName
|
||||
// message_data["data"]["path"] = element.fileElement.filePath
|
||||
// message_data["data"]["file_id"] = element.fileElement.fileUuid
|
||||
message_data["data"]["file_size"] = element.fileElement.fileSize
|
||||
fileCache.set(element.fileElement.fileName, {
|
||||
fileName: element.fileElement.fileName,
|
||||
filePath: element.fileElement.filePath,
|
||||
fileSize: element.fileElement.fileSize,
|
||||
downloadFunc: async () => {
|
||||
await NTQQApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid,
|
||||
element.elementId, null, element.fileElement.filePath)
|
||||
}})
|
||||
// 怎么拿到url呢
|
||||
}
|
||||
else if (element.pttElement) {
|
||||
message_data["type"] = OB11MessageDataType.voice;
|
||||
message_data["data"]["file"] = element.pttElement.filePath
|
||||
message_data["data"]["file_id"] = element.pttElement.fileUuid
|
||||
message_data["data"]["file"] = element.pttElement.fileName
|
||||
message_data["data"]["path"] = element.pttElement.filePath
|
||||
// message_data["data"]["file_id"] = element.pttElement.fileUuid
|
||||
message_data["data"]["file_size"] = element.pttElement.fileSize
|
||||
fileCache.set(element.pttElement.fileName, {
|
||||
fileName: element.pttElement.fileName,
|
||||
filePath: element.pttElement.filePath,
|
||||
fileSize: element.pttElement.fileSize,
|
||||
})
|
||||
|
||||
// log("收到语音消息", msg)
|
||||
// window.LLAPI.Ptt2Text(message.raw.msgId, message.peer, messages).then(text => {
|
||||
@@ -125,30 +171,37 @@ export class OB11Constructor {
|
||||
message_data["type"] = OB11MessageDataType.face;
|
||||
message_data["data"]["id"] = element.faceElement.faceIndex.toString();
|
||||
}
|
||||
if (message_data.data.file) {
|
||||
let filePath: string = message_data.data.file;
|
||||
if (!enableLocalFile2Url) {
|
||||
message_data.data.file = "file://" + filePath
|
||||
} else { // 不使用本地路径
|
||||
if (message_data.data.url && !message_data.data.url.startsWith(IMAGE_HTTP_HOST + "/download")) {
|
||||
message_data.data.file = message_data.data.url
|
||||
} else {
|
||||
let {err, data} = await file2base64(filePath);
|
||||
if (err) {
|
||||
log("文件转base64失败", filePath, err)
|
||||
} else {
|
||||
message_data.data.file = "base64://" + data
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if (message_data.data.file) {
|
||||
// let filePath: string = message_data.data.file;
|
||||
// if (!enableLocalFile2Url) {
|
||||
// message_data.data.file = "file://" + filePath
|
||||
// } else { // 不使用本地路径
|
||||
// const ignoreTypes = [OB11MessageDataType.file, OB11MessageDataType.video]
|
||||
// if (!ignoreTypes.includes(message_data.type)) {
|
||||
// if (message_data.data.url && !message_data.data.url.startsWith(IMAGE_HTTP_HOST + "/download")) {
|
||||
// message_data.data.file = message_data.data.url
|
||||
// } else {
|
||||
// let { err, data } = await file2base64(filePath);
|
||||
// if (err) {
|
||||
// log("文件转base64失败", filePath, err)
|
||||
// } else {
|
||||
// message_data.data.file = "base64://" + data
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// message_data.data.file = "file://" + filePath
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
if (message_data.type !== "unknown" && message_data.data) {
|
||||
const cqCode = encodeCQCode(message_data);
|
||||
if (messagePostFormat === 'string') {
|
||||
const cqCode = encodeCQCode(message_data);
|
||||
(resMsg.message as string) += cqCode;
|
||||
resMsg.raw_message += cqCode;
|
||||
} else (resMsg.message as OB11MessageData[]).push(message_data);
|
||||
|
||||
resMsg.raw_message += cqCode;
|
||||
}
|
||||
}
|
||||
resMsg.raw_message = resMsg.raw_message.trim();
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import {selfInfo} from "../../common/data";
|
||||
import { selfInfo } from "../../common/data";
|
||||
|
||||
export enum EventType {
|
||||
META = "meta_event",
|
||||
@@ -10,7 +10,7 @@ export enum EventType {
|
||||
|
||||
|
||||
export abstract class OB11BaseEvent {
|
||||
time = new Date().getTime();
|
||||
time = Math.floor(Date.now() / 1000);
|
||||
self_id = parseInt(selfInfo.uin);
|
||||
post_type: EventType;
|
||||
}
|
@@ -1,7 +1,9 @@
|
||||
import {OB11GroupNoticeEvent} from "../notice/OB11GroupNoticeEvent";
|
||||
import {EventType} from "../OB11BaseEvent";
|
||||
|
||||
|
||||
export class OB11GroupRequestEvent extends OB11GroupNoticeEvent{
|
||||
post_type = EventType.REQUEST;
|
||||
request_type: "group" = "group";
|
||||
sub_type: "add" | "invite" = "add";
|
||||
comment: string;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import {AtType, RawMessage} from "../ntqqapi/types";
|
||||
import {RawMessage} from "../ntqqapi/types";
|
||||
import {EventType} from "./event/OB11BaseEvent";
|
||||
|
||||
export interface OB11User {
|
||||
@@ -85,7 +85,9 @@ export interface OB11Return<DataType> {
|
||||
export enum OB11MessageDataType {
|
||||
text = "text",
|
||||
image = "image",
|
||||
video = "video",
|
||||
voice = "record",
|
||||
file = "file",
|
||||
at = "at",
|
||||
reply = "reply",
|
||||
json = "json",
|
||||
@@ -115,6 +117,14 @@ export interface OB11MessageRecord extends OB11MessageFileBase {
|
||||
type: OB11MessageDataType.voice
|
||||
}
|
||||
|
||||
export interface OB11MessageFile extends OB11MessageFileBase {
|
||||
type: OB11MessageDataType.file
|
||||
}
|
||||
|
||||
export interface OB11MessageVideo extends OB11MessageFileBase {
|
||||
type: OB11MessageDataType.video
|
||||
}
|
||||
|
||||
export interface OB11MessageAt {
|
||||
type: OB11MessageDataType.at
|
||||
data: {
|
||||
@@ -152,7 +162,7 @@ export type OB11MessageData =
|
||||
OB11MessageText |
|
||||
OB11MessageFace |
|
||||
OB11MessageAt | OB11MessageReply |
|
||||
OB11MessageImage | OB11MessageRecord |
|
||||
OB11MessageImage | OB11MessageRecord | OB11MessageFile | OB11MessageVideo |
|
||||
OB11MessageNode
|
||||
|
||||
export interface OB11PostSendMsg {
|
||||
|
@@ -1,10 +1,14 @@
|
||||
import {CONFIG_DIR, isGIF} from "../common/utils";
|
||||
import {v4 as uuidv4} from "uuid";
|
||||
import * as path from 'path';
|
||||
import {OB11MessageData} from "./types";
|
||||
import {fileCache} from "../common/data";
|
||||
|
||||
const fs = require("fs").promises;
|
||||
|
||||
export async function uri2local(fileName: string, uri: string){
|
||||
export async function uri2local(uri: string, fileName: string = null) {
|
||||
if (!fileName) {
|
||||
fileName = uuidv4();
|
||||
}
|
||||
let filePath = path.join(CONFIG_DIR, fileName)
|
||||
let url = new URL(uri);
|
||||
let res = {
|
||||
@@ -33,26 +37,40 @@ export async function uri2local(fileName: string, uri: string){
|
||||
let blob = await fetchRes.blob();
|
||||
let buffer = await blob.arrayBuffer();
|
||||
try {
|
||||
fileName = path.basename(url.pathname) || fileName
|
||||
filePath = path.join(CONFIG_DIR, fileName)
|
||||
await fs.writeFile(filePath, Buffer.from(buffer));
|
||||
} catch (e: any) {
|
||||
res.errMsg = `${url}下载失败,` + e.toString()
|
||||
return res
|
||||
}
|
||||
} else if (url.protocol === "file:"){
|
||||
// await fs.copyFile(url.pathname, filePath);
|
||||
let pathname = decodeURIComponent(url.pathname)
|
||||
if (process.platform === "win32"){
|
||||
filePath = pathname.slice(1)
|
||||
} else {
|
||||
let pathname: string;
|
||||
if (url.protocol === "file:") {
|
||||
// await fs.copyFile(url.pathname, filePath);
|
||||
pathname = decodeURIComponent(url.pathname)
|
||||
if (process.platform === "win32") {
|
||||
filePath = pathname.slice(1)
|
||||
} else {
|
||||
filePath = pathname
|
||||
}
|
||||
}
|
||||
else{
|
||||
filePath = pathname
|
||||
const cache = fileCache.get(uri)
|
||||
if (cache) {
|
||||
filePath = cache.filePath
|
||||
}
|
||||
else{
|
||||
filePath = uri;
|
||||
}
|
||||
}
|
||||
|
||||
res.isLocal = true
|
||||
}
|
||||
else{
|
||||
res.errMsg = `不支持的file协议,` + url.protocol
|
||||
return res
|
||||
}
|
||||
// else{
|
||||
// res.errMsg = `不支持的file协议,` + url.protocol
|
||||
// return res
|
||||
// }
|
||||
if (isGIF(filePath) && !res.isLocal) {
|
||||
await fs.rename(filePath, filePath + ".gif");
|
||||
filePath += ".gif";
|
||||
|
@@ -143,8 +143,8 @@ async function onSettingWindowCreated(view: Element) {
|
||||
</setting-item>
|
||||
<setting-item data-direction="row" class="vertical-list-item">
|
||||
<div>
|
||||
<div>上报文件不采用本地路径</div>
|
||||
<div class="tips">开启后,上报文件(图片语音等)为http链接或base64编码</div>
|
||||
<div>获取文件使用base64编码</div>
|
||||
<div class="tips">开启后,调用/get_image、/get_record时,获取不到url时添加一个base64字段</div>
|
||||
</div>
|
||||
<setting-switch id="switchFileUrl" ${config.enableLocalFile2Url ? "is-active" : ""}></setting-switch>
|
||||
</setting-item>
|
||||
@@ -172,7 +172,12 @@ async function onSettingWindowCreated(view: Element) {
|
||||
<setting-item data-direction="row" class="vertical-list-item">
|
||||
<div>
|
||||
<div>自动删除收到的文件</div>
|
||||
<div class="tips">一分钟后会删除收到的图片语音</div>
|
||||
<div class="tips">
|
||||
收到文件
|
||||
<input id="autoDeleteMin"
|
||||
min="1" style="width: 50px"
|
||||
value="${config.autoDeleteFileSecond || 60}" type="number"/>秒后自动删除
|
||||
</div>
|
||||
</div>
|
||||
<setting-switch id="autoDeleteFile" ${config.autoDeleteFile ? "is-active" : ""}></setting-switch>
|
||||
</setting-item>
|
||||
@@ -338,6 +343,20 @@ async function onSettingWindowCreated(view: Element) {
|
||||
});
|
||||
})
|
||||
|
||||
// 自动保存删除文件延时时间
|
||||
const autoDeleteMinEle = doc.getElementById("autoDeleteMin") as HTMLInputElement;
|
||||
let st = null;
|
||||
autoDeleteMinEle.addEventListener("change", ()=>{
|
||||
if (st){
|
||||
clearTimeout(st)
|
||||
}
|
||||
st = setTimeout(()=>{
|
||||
console.log("auto delete file minute change");
|
||||
config.autoDeleteFileSecond = parseInt(autoDeleteMinEle.value) || 1;
|
||||
window.llonebot.setConfig(config);
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
doc.body.childNodes.forEach(node => {
|
||||
view.appendChild(node);
|
||||
});
|
||||
|
1
src/version.ts
Normal file
1
src/version.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const version = "3.11.0"
|
Reference in New Issue
Block a user