onebots 0.4.21 → 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 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 原先版本
@@ -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 {string} 消息id
13
+ * @param message_id {number} 消息id
14
14
  */
15
- deleteMsg(this: V11, message_id: string): Promise<boolean>;
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 {string} 消息id
18
+ * @param message_id {number} 消息id
19
19
  */
20
- deleteMsg(message_id) {
21
- return this.client.deleteMsg(message_id);
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
  * 获取消息
@@ -15,7 +15,7 @@ class Database {
15
15
  this.logger = logger;
16
16
  this.dbLock = new types_1.AsyncLock();
17
17
  this.dataSource = new typeorm_1.DataSource({
18
- type: "better-sqlite3",
18
+ type: "sqlite",
19
19
  database: dbPath,
20
20
  entities: [db_entities_1.MsgEntry],
21
21
  });
@@ -47,7 +47,7 @@ class Database {
47
47
  if (msgDataExists) {
48
48
  // send_msg() 返回值和同步的 message 消息哪个先来不确定,send_msg 返回值后来时不允许更新数据库
49
49
  if (msgData.content.length == 0) {
50
- return;
50
+ return msgDataExists.id;
51
51
  }
52
52
  msgData.id = msgDataExists.id;
53
53
  await this.msgRepo.update({ id: msgData.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
- export declare class V11 extends EventEmitter implements OneBot.Base {
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: V11.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;
@@ -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
- class V11 extends events_1.EventEmitter {
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 = [];
@@ -327,6 +326,7 @@ class V11 extends events_1.EventEmitter {
327
326
  msg.user_id = user_id;
328
327
  msg.nickname = '';
329
328
  msg.group_id = group_id;
329
+ msg.group_name = '';
330
330
  msg.content = '';
331
331
  return await this.db.addOrUpdateMsg(msg);
332
332
  }
@@ -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
- export declare class V12 extends EventEmitter implements OneBot.Base {
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: V12.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;
@@ -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
- class V12 extends events_1.EventEmitter {
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) {
@@ -17,8 +17,8 @@ async function buildMusic(target, bu, music) {
17
17
  let { singer = null, title = null, jumpUrl = null, musicUrl = null, preview = null } = music.id ? await getMusicInfo(music.id) : {};
18
18
  singer = music['content'] || music.singer || singer; // 自定义参数优先级高于默认值(gocq的参数名与icqq有区别,做下兼容)
19
19
  title = music.title || title;
20
- jumpUrl = music.jumpUrl || jumpUrl;
21
- musicUrl = music['url'] || music['voice'] || music.musicUrl || musicUrl;
20
+ jumpUrl = music.jumpUrl || music['url'] || jumpUrl;
21
+ musicUrl = music['voice'] || music['url'] || music.musicUrl || musicUrl;
22
22
  preview = music['image'] || music.preview || preview;
23
23
  if (!musicUrl)
24
24
  style = 0;
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "onebots",
3
- "version": "0.4.21",
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
- "typeorm": "^0.3.17"
68
+ "sqlite3": "^5.1.6",
69
+ "typeorm": "^0.3.17",
70
+ "ws": "^8.8.0"
71
71
  }
72
72
  }