onebots 0.4.20 → 0.4.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -1
- package/lib/config.sample.yaml +1 -1
- package/lib/onebot.d.ts +3 -0
- package/lib/service/V11/action/common.d.ts +2 -2
- package/lib/service/V11/action/common.js +8 -3
- package/lib/service/V11/action/friend.d.ts +1 -1
- package/lib/service/V11/action/friend.js +2 -1
- package/lib/service/V11/action/group.d.ts +1 -1
- package/lib/service/V11/action/group.js +2 -1
- package/lib/service/V11/db_sqlite.d.ts +2 -0
- package/lib/service/V11/db_sqlite.js +21 -9
- package/lib/service/V11/index.d.ts +13 -6
- package/lib/service/V11/index.js +28 -19
- package/lib/service/V12/index.d.ts +3 -5
- package/lib/service/V12/index.js +5 -4
- package/lib/service/shareMusicCustom.d.ts +19 -0
- package/lib/service/shareMusicCustom.js +52 -0
- package/lib/service.d.ts +29 -0
- package/lib/service.js +83 -0
- package/lib/types.d.ts +9 -0
- package/lib/types.js +26 -0
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -63,6 +63,7 @@ general: # 通用配置,在单个配置省略时的默认值
|
|
|
63
63
|
reconnect_interval: 3 # 重连间隔 (秒)
|
|
64
64
|
use_http: true # 是否使用 http
|
|
65
65
|
enable_cors: true # 是否允许跨域
|
|
66
|
+
filters: {} # 过滤器配置
|
|
66
67
|
use_ws: true # 是否使用websocket
|
|
67
68
|
http_reverse: [ ] # http上报地址
|
|
68
69
|
ws_reverse: [ ] # 反向ws连接地址
|
|
@@ -74,6 +75,7 @@ general: # 通用配置,在单个配置省略时的默认值
|
|
|
74
75
|
enable_cors: true # 是否允许跨域
|
|
75
76
|
use_http: true # 是否启用http
|
|
76
77
|
use_ws: true # 是否启用 websocket
|
|
78
|
+
filters: {} # 过滤器配置
|
|
77
79
|
webhook: [ ] # http 上报地址
|
|
78
80
|
ws_reverse: [ ] # 反向ws连接地址
|
|
79
81
|
protocol:
|
|
@@ -119,6 +121,7 @@ general: # 通用配置,在单个配置省略时的默认值
|
|
|
119
121
|
| reconnect_interval | number | 3 | 重连间隔 单位:秒 |
|
|
120
122
|
| use_http | boolean | false | 是否使用http协议 |
|
|
121
123
|
| enable_cors | boolean | false | 是否允许跨域 |
|
|
124
|
+
| filters | Filters | {} | 事件过滤器配置 |
|
|
122
125
|
| use_ws | boolean | false | 是否使用ws协议 |
|
|
123
126
|
| http_reverse_url | string[] | - | http上报地址地址 |
|
|
124
127
|
| ws_reverse_url | string[] | - | 反向ws连接地址 |
|
|
@@ -130,10 +133,74 @@ general: # 通用配置,在单个配置省略时的默认值
|
|
|
130
133
|
| request_timeout | number | 15 | 请求超时 单位:秒 |
|
|
131
134
|
| reconnect_interval | number | 3 | 重连间隔 单位:秒 |
|
|
132
135
|
| enable_cors | boolean | false | 是否允许跨域 |
|
|
136
|
+
| filters | Filters | {} | 事件过滤器配置 |
|
|
133
137
|
| use_http | boolean | false | 是否使用http协议 |
|
|
134
138
|
| use_ws | boolean | false | 是否使用ws协议 |
|
|
135
139
|
| webhook_reverse_url | string[] | - | webhook上报地址 |
|
|
136
140
|
| ws_reverse_url | string[] | - | 反向ws连接地址 |
|
|
141
|
+
# 事件过滤器
|
|
142
|
+
## 语法说明
|
|
143
|
+
- `onebots` 的事件过滤器最外层是一个JSON对象,其中的键是键如果是运算法,则值作为运算符的参数,如果不是运算符,则表示对事件数据对象相应 `key` 进行过滤。
|
|
144
|
+
- 过滤规则中任何一个对象, 只有在它的所有项都匹配的情况下, 才会让事件通过(等价于一个 and 运算),如果值为一个数组,则表示事件对应 `key` 值需满足其中一个。
|
|
145
|
+
- 可用逻辑运算符有:`$and` (逻辑与) 、`$or` (逻辑或) 、 `$not` (逻辑非)、`$nor` (逻辑异或)、`$regexp` (文本正则匹配)、`$like` (文本模糊匹配)、`$gt` (数值大于比较)、`$gte` (数值大于等于比较)、`$lt` (数值小于比较)、`$lte` (数值小于等于比较)、`$between` (数值范围比较)
|
|
146
|
+
## 示例
|
|
147
|
+
### 1. 仅上报私聊事件
|
|
148
|
+
```yaml
|
|
149
|
+
filters:
|
|
150
|
+
message_type: private
|
|
151
|
+
```
|
|
152
|
+
### 2. 私聊或指定群聊
|
|
153
|
+
```yaml
|
|
154
|
+
filters:
|
|
155
|
+
$or:
|
|
156
|
+
message_type: private
|
|
157
|
+
group_id:
|
|
158
|
+
- 123456789
|
|
159
|
+
987654321
|
|
160
|
+
```
|
|
161
|
+
### 3. 私聊事件且不是指定用户
|
|
162
|
+
```yaml
|
|
163
|
+
filters:
|
|
164
|
+
message_type: private
|
|
165
|
+
$not:
|
|
166
|
+
user_id:
|
|
167
|
+
- 123456789
|
|
168
|
+
987654321
|
|
169
|
+
```
|
|
170
|
+
### 4. 私聊事件(排除指定用户的事件)或指定群聊事件
|
|
171
|
+
```yaml
|
|
172
|
+
filters:
|
|
173
|
+
$or:
|
|
174
|
+
- message_type: private
|
|
175
|
+
$not:
|
|
176
|
+
user_id: 123456789
|
|
177
|
+
-
|
|
178
|
+
message_type: group
|
|
179
|
+
group_id: 987654321
|
|
180
|
+
```
|
|
181
|
+
### 5. 仅上报消息事件且用户年龄大于18岁
|
|
182
|
+
```yaml
|
|
183
|
+
filters:
|
|
184
|
+
type: message
|
|
185
|
+
sender:
|
|
186
|
+
age:
|
|
187
|
+
$gt: 18
|
|
188
|
+
```
|
|
189
|
+
### 6. 仅上报消息事件且消息内容以!开头的消息
|
|
190
|
+
```yaml
|
|
191
|
+
filters:
|
|
192
|
+
type: message
|
|
193
|
+
raw_message:
|
|
194
|
+
.regexp: '^!|\!'
|
|
195
|
+
```
|
|
196
|
+
### 7. 不上报消息内容包含`cnm`的消息
|
|
197
|
+
```yaml
|
|
198
|
+
filters:
|
|
199
|
+
$not:
|
|
200
|
+
type: message
|
|
201
|
+
raw_message:
|
|
202
|
+
$like: cnm
|
|
203
|
+
```
|
|
137
204
|
# 使用API管理oneBot
|
|
138
205
|
|
|
139
206
|
| url | method | params | desc |
|
|
@@ -144,7 +211,6 @@ general: # 通用配置,在单个配置省略时的默认值
|
|
|
144
211
|
| /add | POST | {uin,...config} | 添加机器人 config 为机器人配置 |
|
|
145
212
|
| /edit | POST | {uin,...config} | 修改机器人配置 config 为机器人配置 |
|
|
146
213
|
| /remove | get | uin,force | 移除机器人,force为true时,将删除机器人data目录 |
|
|
147
|
-
|
|
148
214
|
# 鸣谢
|
|
149
215
|
1. [icqqjs/icqq](https://github.com/icqqjs/icqq) 底层服务支持
|
|
150
216
|
2. [takayama-lily/onebot](https://github.com/takayama-lily/node-onebot) oneBot V11 原先版本
|
package/lib/config.sample.yaml
CHANGED
|
@@ -31,7 +31,7 @@ general: # 通用配置,在单个配置省略时的默认值
|
|
|
31
31
|
# 每个账号的单独配置(用于覆盖通用配置)
|
|
32
32
|
123456789:
|
|
33
33
|
version: V11 # 使用的oneBot版本
|
|
34
|
-
password:'' # 账号密码,未配置则扫码登陆
|
|
34
|
+
password: '' # 账号密码,未配置则扫码登陆
|
|
35
35
|
group_whitelist: [] # 群消息派发白名单,只有数组中的群号才派发,为空则全部派发
|
|
36
36
|
protocol: # 将会覆盖通用配置中的protocol
|
|
37
37
|
platform: 1
|
package/lib/onebot.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { Config as IcqqConfig } from 'icqq';
|
|
|
7
7
|
import { V11 } from "./service/V11";
|
|
8
8
|
import { V12 } from "./service/V12";
|
|
9
9
|
import { MayBeArray } from "./types";
|
|
10
|
+
import { Service } from "./service";
|
|
10
11
|
export declare class NotFoundError extends Error {
|
|
11
12
|
message: string;
|
|
12
13
|
}
|
|
@@ -31,11 +32,13 @@ export declare enum OneBotStatus {
|
|
|
31
32
|
}
|
|
32
33
|
export type OneBotConfig = OneBot.Config<OneBot.Version>;
|
|
33
34
|
export declare namespace OneBot {
|
|
35
|
+
type Filters = {};
|
|
34
36
|
type Version = 'V11' | 'V12';
|
|
35
37
|
type Config<V extends Version = 'V11'> = ({
|
|
36
38
|
version?: V;
|
|
37
39
|
password?: string;
|
|
38
40
|
group_whitelist?: number[];
|
|
41
|
+
filters?: Service.Filters;
|
|
39
42
|
protocol?: IcqqConfig;
|
|
40
43
|
} & (V extends 'V11' ? V11.Config : V12.Config));
|
|
41
44
|
interface Base {
|
|
@@ -10,9 +10,9 @@ export declare class CommonAction {
|
|
|
10
10
|
};
|
|
11
11
|
/**
|
|
12
12
|
* 撤回消息
|
|
13
|
-
* @param message_id {
|
|
13
|
+
* @param message_id {number} 消息id
|
|
14
14
|
*/
|
|
15
|
-
deleteMsg(this: V11, message_id:
|
|
15
|
+
deleteMsg(this: V11, message_id: number): Promise<boolean>;
|
|
16
16
|
/**
|
|
17
17
|
* 获取消息
|
|
18
18
|
* @param message_id {string} 消息id
|
|
@@ -15,10 +15,15 @@ class CommonAction {
|
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
17
|
* 撤回消息
|
|
18
|
-
* @param message_id {
|
|
18
|
+
* @param message_id {number} 消息id
|
|
19
19
|
*/
|
|
20
|
-
deleteMsg(message_id) {
|
|
21
|
-
|
|
20
|
+
async deleteMsg(message_id) {
|
|
21
|
+
if (message_id == 0)
|
|
22
|
+
throw new Error('getMsg: message_id[0] is invalid');
|
|
23
|
+
let msg_entry = await this.db.getMsgById(message_id);
|
|
24
|
+
if (!msg_entry)
|
|
25
|
+
throw new Error(`getMsg: can not find msg[${message_id}] in db`);
|
|
26
|
+
return this.client.deleteMsg(msg_entry.base64_id);
|
|
22
27
|
}
|
|
23
28
|
/**
|
|
24
29
|
* 获取消息
|
|
@@ -8,7 +8,7 @@ export declare class FriendAction {
|
|
|
8
8
|
* @param message {import('icqq').Sendable} 发送的消息
|
|
9
9
|
* @param message_id {string} 引用的消息ID
|
|
10
10
|
*/
|
|
11
|
-
sendPrivateMsg(this: V11, user_id: number, message: string | SegmentElem | SegmentElem[], message_id?: string): Promise<
|
|
11
|
+
sendPrivateMsg(this: V11, user_id: number, message: string | SegmentElem | SegmentElem[], message_id?: string): Promise<any>;
|
|
12
12
|
/**
|
|
13
13
|
* 获取好友列表
|
|
14
14
|
*/
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.FriendAction = void 0;
|
|
4
4
|
const utils_1 = require("../../../service/V11/utils");
|
|
5
|
+
const shareMusicCustom_1 = require("../../../service/shareMusicCustom");
|
|
5
6
|
class FriendAction {
|
|
6
7
|
/**
|
|
7
8
|
* 发送私聊消息
|
|
@@ -13,7 +14,7 @@ class FriendAction {
|
|
|
13
14
|
const msg = message_id ? await this.client.getMsg(message_id) : undefined;
|
|
14
15
|
const { element, quote, music, share } = await utils_1.processMessage.apply(this.client, [message, msg]);
|
|
15
16
|
if (music)
|
|
16
|
-
return await this.client.pickFriend(user_id)
|
|
17
|
+
return await shareMusicCustom_1.shareMusic.call(this.client.pickFriend(user_id), music);
|
|
17
18
|
if (share)
|
|
18
19
|
return await this.client.pickFriend(user_id).shareUrl(music.data);
|
|
19
20
|
if (element.length) {
|
|
@@ -7,7 +7,7 @@ export declare class GroupAction {
|
|
|
7
7
|
* @param message {import('icqq').Sendable} 消息
|
|
8
8
|
* @param message_id {string} 引用的消息ID
|
|
9
9
|
*/
|
|
10
|
-
sendGroupMsg(this: V11, group_id: number, message: string | SegmentElem | SegmentElem[], message_id?: string): Promise<
|
|
10
|
+
sendGroupMsg(this: V11, group_id: number, message: string | SegmentElem | SegmentElem[], message_id?: string): Promise<any>;
|
|
11
11
|
/**
|
|
12
12
|
* 群组踢人
|
|
13
13
|
* @param group_id {number} 群id
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.GroupAction = void 0;
|
|
4
4
|
const utils_1 = require("../../../service/V11/utils");
|
|
5
|
+
const shareMusicCustom_1 = require("../../../service/shareMusicCustom");
|
|
5
6
|
class GroupAction {
|
|
6
7
|
/**
|
|
7
8
|
* 发送群聊消息
|
|
@@ -13,7 +14,7 @@ class GroupAction {
|
|
|
13
14
|
const msg = message_id ? await this.client.getMsg(message_id) : undefined;
|
|
14
15
|
const { element, quote, music, share } = await utils_1.processMessage.apply(this.client, [message, msg]);
|
|
15
16
|
if (music)
|
|
16
|
-
return await this.client.pickGroup(group_id)
|
|
17
|
+
return await shareMusicCustom_1.shareMusic.call(this.client.pickGroup(group_id), music);
|
|
17
18
|
if (share)
|
|
18
19
|
return await this.client.pickGroup(group_id).shareUrl(music.data);
|
|
19
20
|
if (element.length) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { MsgEntry } from "./db_entities";
|
|
2
2
|
import { DataSource, Repository } from "typeorm";
|
|
3
3
|
import { Logger } from "log4js";
|
|
4
|
+
import { AsyncLock } from "../../types";
|
|
4
5
|
export declare class Database {
|
|
5
6
|
logger: Logger;
|
|
6
7
|
dbPath: string;
|
|
@@ -11,6 +12,7 @@ export declare class Database {
|
|
|
11
12
|
msgHistoryPreserveDays: number;
|
|
12
13
|
msgHistoryCheckInterval: number;
|
|
13
14
|
msgRepo: Repository<MsgEntry>;
|
|
15
|
+
dbLock: AsyncLock;
|
|
14
16
|
constructor(dbPath: string, logger: Logger);
|
|
15
17
|
initDB(): Promise<void>;
|
|
16
18
|
/**
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Database = void 0;
|
|
4
4
|
const db_entities_1 = require("./db_entities");
|
|
5
5
|
const typeorm_1 = require("typeorm");
|
|
6
|
+
const types_1 = require("../../types");
|
|
6
7
|
class Database {
|
|
7
8
|
constructor(dbPath, logger) {
|
|
8
9
|
/**
|
|
@@ -12,8 +13,9 @@ class Database {
|
|
|
12
13
|
this.msgHistoryCheckInterval = 1 * 24 * 3600 * 1000; // 历史记录检查间隔
|
|
13
14
|
this.dbPath = dbPath;
|
|
14
15
|
this.logger = logger;
|
|
16
|
+
this.dbLock = new types_1.AsyncLock();
|
|
15
17
|
this.dataSource = new typeorm_1.DataSource({
|
|
16
|
-
type: "
|
|
18
|
+
type: "sqlite",
|
|
17
19
|
database: dbPath,
|
|
18
20
|
entities: [db_entities_1.MsgEntry],
|
|
19
21
|
});
|
|
@@ -39,15 +41,25 @@ class Database {
|
|
|
39
41
|
* @param msgData
|
|
40
42
|
*/
|
|
41
43
|
async addOrUpdateMsg(msgData) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
msgData.
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
await this.dbLock.lock();
|
|
45
|
+
try {
|
|
46
|
+
let msgDataExists = await this.getMsgByParams(msgData.user_id, msgData.group_id, msgData.seq);
|
|
47
|
+
if (msgDataExists) {
|
|
48
|
+
// send_msg() 返回值和同步的 message 消息哪个先来不确定,send_msg 返回值后来时不允许更新数据库
|
|
49
|
+
if (msgData.content.length == 0) {
|
|
50
|
+
return msgDataExists.id;
|
|
51
|
+
}
|
|
52
|
+
msgData.id = msgDataExists.id;
|
|
53
|
+
await this.msgRepo.update({ id: msgData.id }, msgData);
|
|
54
|
+
return msgDataExists.id;
|
|
55
|
+
}
|
|
56
|
+
msgData = await this.msgRepo.save(msgData);
|
|
57
|
+
this.logger.debug(`addMsg with id:${msgData.id}`);
|
|
58
|
+
return msgData.id;
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
this.dbLock.unlock();
|
|
47
62
|
}
|
|
48
|
-
msgData = await this.msgRepo.save(msgData);
|
|
49
|
-
this.logger.debug(`addMsg with id:${msgData.id}`);
|
|
50
|
-
return msgData.id;
|
|
51
63
|
}
|
|
52
64
|
/**
|
|
53
65
|
* 通过 icqq 的 base64 格式的 message_id 获取一个 MsgData 对象
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
/// <reference types="node" />
|
|
3
2
|
import { Client } from "icqq";
|
|
4
3
|
import { Config } from "./config";
|
|
5
4
|
import { Action } from "./action";
|
|
@@ -7,12 +6,11 @@ import { OneBot } from "../../onebot";
|
|
|
7
6
|
import { Logger } from "log4js";
|
|
8
7
|
import { WebSocket, WebSocketServer } from "ws";
|
|
9
8
|
import { Dispose } from "../../types";
|
|
10
|
-
import { EventEmitter } from "events";
|
|
11
9
|
import { Database } from "./db_sqlite";
|
|
12
|
-
|
|
10
|
+
import { Service } from "../../service";
|
|
11
|
+
export declare class V11 extends Service<'V11'> implements OneBot.Base {
|
|
13
12
|
oneBot: OneBot<'V11'>;
|
|
14
13
|
client: Client;
|
|
15
|
-
config: V11.Config;
|
|
16
14
|
action: Action;
|
|
17
15
|
version: string;
|
|
18
16
|
protected timestamp: number;
|
|
@@ -28,7 +26,7 @@ export declare class V11 extends EventEmitter implements OneBot.Base {
|
|
|
28
26
|
logger: Logger;
|
|
29
27
|
wss?: WebSocketServer;
|
|
30
28
|
wsr: Set<WebSocket>;
|
|
31
|
-
constructor(oneBot: OneBot<'V11'>, client: Client, config:
|
|
29
|
+
constructor(oneBot: OneBot<'V11'>, client: Client, config: OneBot.Config<'V11'>);
|
|
32
30
|
start(path?: string): void;
|
|
33
31
|
private startHttp;
|
|
34
32
|
private startHttpReverse;
|
|
@@ -39,7 +37,16 @@ export declare class V11 extends EventEmitter implements OneBot.Base {
|
|
|
39
37
|
system_online(data: any): void;
|
|
40
38
|
dispatch(data: any): Promise<void>;
|
|
41
39
|
private _formatEvent;
|
|
42
|
-
private
|
|
40
|
+
private addMsgToDB;
|
|
41
|
+
/**
|
|
42
|
+
* 从 send_msg_xxx() 调用的返回值中提取消息存入数据库(可以让前端在没有收到同步的message数据前就有能力拿到消息对应的base64_id)
|
|
43
|
+
* (也有可能来的比message慢,后来的话会被数据库忽略)
|
|
44
|
+
* @param user_id 发送者
|
|
45
|
+
* @param group_id 群号,私聊为0
|
|
46
|
+
* @param seq 消息序号
|
|
47
|
+
* @param base64_id icqq返回的base64格式的消息id
|
|
48
|
+
*/
|
|
49
|
+
private addMsgToDBFromSendMsgResult;
|
|
43
50
|
private getReplyMsgIdFromDB;
|
|
44
51
|
private _httpRequestHandler;
|
|
45
52
|
/**
|
package/lib/service/V11/index.js
CHANGED
|
@@ -14,18 +14,17 @@ const icqq_cq_enable_1 = require("icqq-cq-enable");
|
|
|
14
14
|
const onebot_2 = require("../../onebot");
|
|
15
15
|
const http_1 = __importDefault(require("http"));
|
|
16
16
|
const https_1 = __importDefault(require("https"));
|
|
17
|
-
const events_1 = require("events");
|
|
18
17
|
const fs_1 = require("fs");
|
|
19
18
|
const db_sqlite_1 = require("./db_sqlite");
|
|
20
19
|
const path_1 = require("path");
|
|
21
20
|
const app_1 = require("../../server/app");
|
|
22
21
|
const db_entities_1 = require("./db_entities");
|
|
23
|
-
|
|
22
|
+
const service_1 = require("../../service");
|
|
23
|
+
class V11 extends service_1.Service {
|
|
24
24
|
constructor(oneBot, client, config) {
|
|
25
|
-
super();
|
|
25
|
+
super(config);
|
|
26
26
|
this.oneBot = oneBot;
|
|
27
27
|
this.client = client;
|
|
28
|
-
this.config = config;
|
|
29
28
|
this.version = 'V11';
|
|
30
29
|
this.timestamp = Date.now();
|
|
31
30
|
this._queue = [];
|
|
@@ -219,7 +218,7 @@ class V11 extends events_1.EventEmitter {
|
|
|
219
218
|
}
|
|
220
219
|
}
|
|
221
220
|
if (data.message_id) {
|
|
222
|
-
data.message_id = await this.
|
|
221
|
+
data.message_id = await this.addMsgToDB(data);
|
|
223
222
|
}
|
|
224
223
|
if (data.post_type == 'notice' && String(data.notice_type).endsWith('_recall')) {
|
|
225
224
|
this.db.markMsgAsRecalled(data.base64_id);
|
|
@@ -292,7 +291,7 @@ class V11 extends events_1.EventEmitter {
|
|
|
292
291
|
return JSON.stringify(data);
|
|
293
292
|
}
|
|
294
293
|
}
|
|
295
|
-
async
|
|
294
|
+
async addMsgToDB(data) {
|
|
296
295
|
if (!data.sender || !('user_id' in data.sender)) { // eg. notice
|
|
297
296
|
return;
|
|
298
297
|
}
|
|
@@ -312,6 +311,25 @@ class V11 extends events_1.EventEmitter {
|
|
|
312
311
|
msg.content = data.cqCode;
|
|
313
312
|
return await this.db.addOrUpdateMsg(msg);
|
|
314
313
|
}
|
|
314
|
+
/**
|
|
315
|
+
* 从 send_msg_xxx() 调用的返回值中提取消息存入数据库(可以让前端在没有收到同步的message数据前就有能力拿到消息对应的base64_id)
|
|
316
|
+
* (也有可能来的比message慢,后来的话会被数据库忽略)
|
|
317
|
+
* @param user_id 发送者
|
|
318
|
+
* @param group_id 群号,私聊为0
|
|
319
|
+
* @param seq 消息序号
|
|
320
|
+
* @param base64_id icqq返回的base64格式的消息id
|
|
321
|
+
*/
|
|
322
|
+
async addMsgToDBFromSendMsgResult(user_id, group_id, seq, base64_id) {
|
|
323
|
+
let msg = new db_entities_1.MsgEntry();
|
|
324
|
+
msg.base64_id = base64_id;
|
|
325
|
+
msg.seq = seq;
|
|
326
|
+
msg.user_id = user_id;
|
|
327
|
+
msg.nickname = '';
|
|
328
|
+
msg.group_id = group_id;
|
|
329
|
+
msg.group_name = '';
|
|
330
|
+
msg.content = '';
|
|
331
|
+
return await this.db.addOrUpdateMsg(msg);
|
|
332
|
+
}
|
|
315
333
|
async getReplyMsgIdFromDB(data) {
|
|
316
334
|
let group_id = (data.message_type === 'group') ? data.group_id : 0;
|
|
317
335
|
let msg = await this.db.getMsgByParams(data.source.user_id, group_id, data.source.seq);
|
|
@@ -568,19 +586,10 @@ class V11 extends events_1.EventEmitter {
|
|
|
568
586
|
result.data = [...result.data.values()];
|
|
569
587
|
if (result.data?.message)
|
|
570
588
|
result.data.message = (0, icqq_cq_enable_1.toSegment)(result.data.message);
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
* > 但 get_msg 返回的是从数据库获取的message_id,因此是有效的
|
|
576
|
-
*/
|
|
577
|
-
// if (result.data?.message_id && result.data?.seq) {
|
|
578
|
-
// let message_id = result.data?.message_id
|
|
579
|
-
// if(!(typeof message_id == 'number' || /^\d+$/.test(message_id))) {
|
|
580
|
-
// // this.db.set(`KVMap.${result.data.seq}`,result.data.message_id )
|
|
581
|
-
// result.data.message_id = result.data.seq
|
|
582
|
-
// }
|
|
583
|
-
// }
|
|
589
|
+
// send_msg_xxx 时提前把数据写入数据库(也有可能来的比message慢,后来的话会被数据库忽略)
|
|
590
|
+
if (result.status === 'ok' && params.user_id && result.data?.message_id && result.data?.seq) {
|
|
591
|
+
result.data.message_id = await this.addMsgToDBFromSendMsgResult(params.user_id, params.group_id || 0, result.data.seq, result.data.message_id);
|
|
592
|
+
}
|
|
584
593
|
if (echo) {
|
|
585
594
|
result.echo = echo;
|
|
586
595
|
}
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
/// <reference types="node" />
|
|
3
2
|
import { Client, EventMap, MessageElem, Sendable as IcqqCanSend } from "icqq";
|
|
4
3
|
import { Config } from './config';
|
|
5
4
|
import { OneBot } from "../../onebot";
|
|
6
5
|
import { Action } from "./action";
|
|
7
|
-
import { EventEmitter } from "events";
|
|
8
6
|
import { Logger } from "log4js";
|
|
9
7
|
import { WebSocket, WebSocketServer } from "ws";
|
|
10
|
-
|
|
8
|
+
import { Service } from "../../service";
|
|
9
|
+
export declare class V12 extends Service<'V12'> implements OneBot.Base {
|
|
11
10
|
oneBot: OneBot<'V12'>;
|
|
12
11
|
client: Client;
|
|
13
|
-
config: V12.Config;
|
|
14
12
|
version: string;
|
|
15
13
|
action: Action;
|
|
16
14
|
protected timestamp: number;
|
|
@@ -20,7 +18,7 @@ export declare class V12 extends EventEmitter implements OneBot.Base {
|
|
|
20
18
|
wss?: WebSocketServer;
|
|
21
19
|
wsr: Set<WebSocket>;
|
|
22
20
|
private db;
|
|
23
|
-
constructor(oneBot: OneBot<'V12'>, client: Client, config:
|
|
21
|
+
constructor(oneBot: OneBot<'V12'>, client: Client, config: OneBot.Config<'V12'>);
|
|
24
22
|
get history(): V12.Payload<keyof Action>[];
|
|
25
23
|
getFile(file_id: string): V12.FileInfo;
|
|
26
24
|
delFile(file_id: string): boolean;
|
package/lib/service/V12/index.js
CHANGED
|
@@ -9,7 +9,6 @@ const utils_1 = require("../../utils");
|
|
|
9
9
|
const path_1 = require("path");
|
|
10
10
|
const onebot_1 = require("../../onebot");
|
|
11
11
|
const action_1 = require("./action");
|
|
12
|
-
const events_1 = require("events");
|
|
13
12
|
const url_1 = require("url");
|
|
14
13
|
const http_1 = __importDefault(require("http"));
|
|
15
14
|
const https_1 = __importDefault(require("https"));
|
|
@@ -19,12 +18,12 @@ const db_1 = require("../../db");
|
|
|
19
18
|
const app_1 = require("../../server/app");
|
|
20
19
|
const fs_1 = require("fs");
|
|
21
20
|
const message_1 = require("icqq/lib/message");
|
|
22
|
-
|
|
21
|
+
const service_1 = require("../../service");
|
|
22
|
+
class V12 extends service_1.Service {
|
|
23
23
|
constructor(oneBot, client, config) {
|
|
24
|
-
super();
|
|
24
|
+
super(config);
|
|
25
25
|
this.oneBot = oneBot;
|
|
26
26
|
this.client = client;
|
|
27
|
-
this.config = config;
|
|
28
27
|
this.version = 'V12';
|
|
29
28
|
this.timestamp = Date.now();
|
|
30
29
|
this.wsr = new Set();
|
|
@@ -369,6 +368,8 @@ class V12 extends events_1.EventEmitter {
|
|
|
369
368
|
return value + '';
|
|
370
369
|
}),
|
|
371
370
|
};
|
|
371
|
+
if (!this.filterFn(payload))
|
|
372
|
+
return;
|
|
372
373
|
this.emit('dispatch', payload);
|
|
373
374
|
}
|
|
374
375
|
async apply(req) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Contactable } from "icqq/lib/internal";
|
|
2
|
+
import { MusicElem } from "icqq/lib/message";
|
|
3
|
+
import { Encodable } from "icqq/lib/core/protobuf";
|
|
4
|
+
/** 发送音乐分享(允许自定义参数) */
|
|
5
|
+
export declare function shareMusic(this: Contactable, music: MusicElem): Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* 构造频道b77音乐分享
|
|
8
|
+
* @param channel_id {string} 子频道id
|
|
9
|
+
* @param guild_id {string} 频道id
|
|
10
|
+
* @param music 音乐分享数据
|
|
11
|
+
*/
|
|
12
|
+
export declare function buildMusic(channel_id: string, guild_id: string, music: MusicElem): Promise<Encodable>;
|
|
13
|
+
/**
|
|
14
|
+
* 构造b77音乐分享
|
|
15
|
+
* @param target {number} 群id或者好友qq
|
|
16
|
+
* @param bu {0|1} 类型表示:0 为好友 1 为群
|
|
17
|
+
* @param music 音乐分享数据
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildMusic(target: number, bu: 0 | 1, music: MusicElem): Promise<Encodable>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildMusic = exports.shareMusic = void 0;
|
|
4
|
+
const message_1 = require("icqq/lib/message");
|
|
5
|
+
const core_1 = require("icqq/lib/core");
|
|
6
|
+
const process_1 = require("process");
|
|
7
|
+
/** 发送音乐分享(允许自定义参数) */
|
|
8
|
+
async function shareMusic(music) {
|
|
9
|
+
const body = await buildMusic((this.gid || this.uid), this.dm ? 0 : 1, music);
|
|
10
|
+
await this.c.sendOidb("OidbSvc.0xb77_9", core_1.pb.encode(body));
|
|
11
|
+
}
|
|
12
|
+
exports.shareMusic = shareMusic;
|
|
13
|
+
async function buildMusic(target, bu, music) {
|
|
14
|
+
const { appid, package_name, sign, getMusicInfo } = message_1.musicFactory[music.platform];
|
|
15
|
+
let style = 4;
|
|
16
|
+
try {
|
|
17
|
+
let { singer = null, title = null, jumpUrl = null, musicUrl = null, preview = null } = music.id ? await getMusicInfo(music.id) : {};
|
|
18
|
+
singer = music['content'] || music.singer || singer; // 自定义参数优先级高于默认值(gocq的参数名与icqq有区别,做下兼容)
|
|
19
|
+
title = music.title || title;
|
|
20
|
+
jumpUrl = music.jumpUrl || music['url'] || jumpUrl;
|
|
21
|
+
musicUrl = music['voice'] || music['url'] || music.musicUrl || musicUrl;
|
|
22
|
+
preview = music['image'] || music.preview || preview;
|
|
23
|
+
if (!musicUrl)
|
|
24
|
+
style = 0;
|
|
25
|
+
return {
|
|
26
|
+
1: appid,
|
|
27
|
+
2: 1,
|
|
28
|
+
3: style,
|
|
29
|
+
5: {
|
|
30
|
+
1: 1,
|
|
31
|
+
2: "0.0.0",
|
|
32
|
+
3: package_name,
|
|
33
|
+
4: sign
|
|
34
|
+
},
|
|
35
|
+
10: typeof bu === 'string' ? 3 : bu,
|
|
36
|
+
11: target,
|
|
37
|
+
12: {
|
|
38
|
+
10: title,
|
|
39
|
+
11: singer,
|
|
40
|
+
12: "[分享]" + title,
|
|
41
|
+
13: jumpUrl,
|
|
42
|
+
14: preview,
|
|
43
|
+
16: musicUrl,
|
|
44
|
+
},
|
|
45
|
+
19: typeof bu === 'string' ? Number(bu) : undefined
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
throw new Error("unknown music id: " + music.id + ", in platform: " + music.platform + ", with title: " + process_1.title);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
exports.buildMusic = buildMusic;
|
package/lib/service.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { EventEmitter } from "events";
|
|
3
|
+
import { OneBot } from "./onebot";
|
|
4
|
+
import { Dict } from "@zhinjs/shared";
|
|
5
|
+
export interface Service<V extends OneBot.Version> {
|
|
6
|
+
filterFn(event: Dict): boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare class Service<V extends OneBot.Version> extends EventEmitter {
|
|
9
|
+
config: OneBot.Config<V>;
|
|
10
|
+
constructor(config: OneBot.Config<V>);
|
|
11
|
+
}
|
|
12
|
+
export declare namespace Service {
|
|
13
|
+
type MaybeArray<T = any> = T | T[];
|
|
14
|
+
type AttrFilter = {
|
|
15
|
+
[P in keyof Dict]?: MaybeArray | boolean;
|
|
16
|
+
};
|
|
17
|
+
export type Filters = AttrFilter | WithFilter | UnionFilter | ExcludeFilter;
|
|
18
|
+
export type WithFilter = {
|
|
19
|
+
$and: Filters;
|
|
20
|
+
};
|
|
21
|
+
export type UnionFilter = {
|
|
22
|
+
$or: Filters;
|
|
23
|
+
};
|
|
24
|
+
export type ExcludeFilter = {
|
|
25
|
+
$not: Filters;
|
|
26
|
+
};
|
|
27
|
+
export function createFilterFunction(filters: Filters): (event: Dict) => any;
|
|
28
|
+
export {};
|
|
29
|
+
}
|
package/lib/service.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Service = void 0;
|
|
4
|
+
const events_1 = require("events");
|
|
5
|
+
class Service extends events_1.EventEmitter {
|
|
6
|
+
constructor(config) {
|
|
7
|
+
super();
|
|
8
|
+
this.config = config;
|
|
9
|
+
this.filterFn = Service.createFilterFunction(config.filters || {});
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
exports.Service = Service;
|
|
13
|
+
(function (Service) {
|
|
14
|
+
function createFilterFunction(filters) {
|
|
15
|
+
const isLogicKey = (key) => {
|
|
16
|
+
return [
|
|
17
|
+
'$and',
|
|
18
|
+
'$or',
|
|
19
|
+
'$not',
|
|
20
|
+
'$nor',
|
|
21
|
+
'$regexp',
|
|
22
|
+
'$like',
|
|
23
|
+
'$gt',
|
|
24
|
+
'$gte',
|
|
25
|
+
'$lt',
|
|
26
|
+
'$lte',
|
|
27
|
+
'$between',
|
|
28
|
+
].includes(key);
|
|
29
|
+
};
|
|
30
|
+
const filterFn = (event, key, value) => {
|
|
31
|
+
// 如果 key 为 $and、$or、$not、$nor 则递归调用
|
|
32
|
+
if (key === "$and" || key === "$or" || key === "$not" || key === '$nor') {
|
|
33
|
+
if (!value || typeof value !== 'object')
|
|
34
|
+
throw new Error("invalid filter");
|
|
35
|
+
switch (key) {
|
|
36
|
+
case "$and":
|
|
37
|
+
return Array.isArray(value) ? value.every((item) => filterFn(event, key, item)) :
|
|
38
|
+
Object.entries(value).every(([key, value]) => filterFn(event, key, value));
|
|
39
|
+
case "$or":
|
|
40
|
+
return Array.isArray(value) ? value.some((item) => filterFn(event, key, item)) :
|
|
41
|
+
Object.entries(value).some(([key, value]) => filterFn(event, key, value));
|
|
42
|
+
case "$nor":
|
|
43
|
+
return !filterFn(event, '$or', value);
|
|
44
|
+
case "$not":
|
|
45
|
+
return !filterFn(event, '$and', value);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (typeof value === "boolean" && typeof event[key] !== "boolean") {
|
|
49
|
+
return value;
|
|
50
|
+
}
|
|
51
|
+
if (typeof value !== "object") {
|
|
52
|
+
if (key === '$regex' && typeof value === 'string')
|
|
53
|
+
return new RegExp(value).test(String(event));
|
|
54
|
+
if (key === '$like' && typeof value === 'string')
|
|
55
|
+
return String(event).includes(value);
|
|
56
|
+
if (key === '$gt' && typeof value === 'number')
|
|
57
|
+
return Number(event) > value;
|
|
58
|
+
if (key === '$gte' && typeof value === 'number')
|
|
59
|
+
return Number(event) >= value;
|
|
60
|
+
if (key === '$lt' && typeof value === 'number')
|
|
61
|
+
return Number(event) < value;
|
|
62
|
+
if (key === '$lte' && typeof value === 'number')
|
|
63
|
+
return Number(event) <= value;
|
|
64
|
+
return value === event[key];
|
|
65
|
+
}
|
|
66
|
+
if (key === '$between' &&
|
|
67
|
+
Array.isArray(value) &&
|
|
68
|
+
value.length === 2 &&
|
|
69
|
+
value.every((item) => typeof item === 'number')) {
|
|
70
|
+
const [start, end] = value;
|
|
71
|
+
return event >= start && event <= end;
|
|
72
|
+
}
|
|
73
|
+
if (Array.isArray(value)) {
|
|
74
|
+
return value.includes(event[key]);
|
|
75
|
+
}
|
|
76
|
+
return createFilterFunction(value)(isLogicKey(key) ? event : event[key]);
|
|
77
|
+
};
|
|
78
|
+
return (event) => {
|
|
79
|
+
return Object.entries(filters).every(([key, value]) => filterFn(event, key, value));
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
Service.createFilterFunction = createFilterFunction;
|
|
83
|
+
})(Service || (exports.Service = Service = {}));
|
package/lib/types.d.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
1
|
export type LogLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal" | "mark" | "off";
|
|
2
2
|
export type Dispose = () => any;
|
|
3
3
|
export type MayBeArray<T extends any> = T | T[];
|
|
4
|
+
/**
|
|
5
|
+
* 异步锁---
|
|
6
|
+
*/
|
|
7
|
+
export declare class AsyncLock {
|
|
8
|
+
private _lock;
|
|
9
|
+
private _waitList;
|
|
10
|
+
lock(): Promise<void>;
|
|
11
|
+
unlock(): void;
|
|
12
|
+
}
|
package/lib/types.js
CHANGED
|
@@ -1,2 +1,28 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AsyncLock = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* 异步锁---
|
|
6
|
+
*/
|
|
7
|
+
class AsyncLock {
|
|
8
|
+
constructor() {
|
|
9
|
+
this._lock = false;
|
|
10
|
+
this._waitList = [];
|
|
11
|
+
}
|
|
12
|
+
async lock() {
|
|
13
|
+
if (this._lock) {
|
|
14
|
+
await new Promise((resolve) => {
|
|
15
|
+
this._waitList.push(resolve);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
this._lock = true;
|
|
19
|
+
}
|
|
20
|
+
unlock() {
|
|
21
|
+
this._lock = false;
|
|
22
|
+
if (this._waitList.length > 0) {
|
|
23
|
+
let resolve = this._waitList.shift();
|
|
24
|
+
resolve && resolve();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.AsyncLock = AsyncLock;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "onebots",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.22",
|
|
4
4
|
"description": "基于icqq的多例oneBot实现",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=16"
|
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
"build": "tsc --project tsconfig.json && tsc-alias -p tsconfig.json && cp -r src/config.sample.yaml lib/config.sample.yaml",
|
|
15
15
|
"dev": "ts-node-dev -r tsconfig-paths/register ./src/bin.ts -c config.yaml",
|
|
16
16
|
"pub": "npm publish --access public",
|
|
17
|
+
"test": "ts-node-dev -r tsconfig-paths/register ./src/test.ts",
|
|
17
18
|
"docs:dev": "vitepress dev docs --port 8989",
|
|
18
19
|
"docs:build": "vitepress build docs",
|
|
19
|
-
"docs:preview": "vitepress preview docs"
|
|
20
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
20
|
+
"docs:preview": "vitepress preview docs"
|
|
21
21
|
},
|
|
22
22
|
"repository": {
|
|
23
23
|
"type": "git",
|
|
@@ -64,9 +64,9 @@
|
|
|
64
64
|
"koa-bodyparser": "^4.3.0",
|
|
65
65
|
"log4js": "^6.5.2",
|
|
66
66
|
"mime-types": "^2.1.35",
|
|
67
|
-
"ws": "^8.8.0",
|
|
68
|
-
"better-sqlite3": "^8.6.0",
|
|
69
67
|
"reflect-metadata": "^0.1.13",
|
|
70
|
-
"
|
|
68
|
+
"sqlite3": "^5.1.6",
|
|
69
|
+
"typeorm": "^0.3.17",
|
|
70
|
+
"ws": "^8.8.0"
|
|
71
71
|
}
|
|
72
72
|
}
|