onebots 0.4.19 → 0.4.21

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
@@ -83,15 +83,17 @@ general: # 通用配置,在单个配置省略时的默认值
83
83
  # ...其他配置项参考icqq的Config配置
84
84
  # 每个账号的单独配置(用于覆盖通用配置)
85
85
  123456789:
86
+ password: '' # 账号密码,未配置则扫码登陆
86
87
  version: V11 # 使用的oneBot版本
87
88
  # ...其他配置项参见上方对应oneBot版本的通用配置
88
89
  protocol:
89
90
  platform: 2
90
91
  sign_api_addr: '' #你的签名地址
91
- password: '' # 账号密码,未配置则扫码登陆
92
92
  # ...其他配置项参考icqq的Config配置
93
93
  ```
94
+
94
95
  # 配置解释
96
+
95
97
  ## Config
96
98
  | 配置项 | 类型 | 默认值 | desc |
97
99
  |:---------|:-----------------------------|:--------|:-------|
@@ -100,11 +102,12 @@ general: # 通用配置,在单个配置省略时的默认值
100
102
  | general | OneBotConfig | general | 通用配置 |
101
103
  | [number] | OneBotConfig\|OneBotConfig[] | - | 机器人配置 |
102
104
  ## OneBotConfig
103
- | 配置项 | 类型 | 默认值 | desc |
104
- |:---------|:----------|:----------|:-------|
105
- | V11 | ConfigV11 | configV11 | V11配置 |
106
- | V12 | ConfigV12 | configV12 | V12配置 |
107
- | protocol | Config | {} | icqq配置 |
105
+ | 配置项 | 类型 | 默认值 | desc |
106
+ |:---------|:----------|:----------|:-------------------|
107
+ | password | string | - | 账号密码 未填写或填写为空则扫码登陆 |
108
+ | V11 | ConfigV11 | configV11 | V11配置 |
109
+ | V12 | ConfigV12 | configV12 | V12配置 |
110
+ | protocol | Config | {} | icqq配置 |
108
111
  ## ConfigV11
109
112
  | 配置项 | 类型 | 默认值 | desc |
110
113
  |:--------------------|:---------|:-------|:-----------|
@@ -31,6 +31,8 @@ general: # 通用配置,在单个配置省略时的默认值
31
31
  # 每个账号的单独配置(用于覆盖通用配置)
32
32
  123456789:
33
33
  version: V11 # 使用的oneBot版本
34
+ password:'' # 账号密码,未配置则扫码登陆
35
+ group_whitelist: [] # 群消息派发白名单,只有数组中的群号才派发,为空则全部派发
34
36
  protocol: # 将会覆盖通用配置中的protocol
35
37
  platform: 1
36
38
  # 。。。其他配置项参见上方对应oneBot版本的通用配置
package/lib/onebot.d.ts CHANGED
@@ -22,7 +22,8 @@ export declare class OneBot<V extends OneBot.Version> extends EventEmitter {
22
22
  start(): Promise<[boolean, any]>;
23
23
  startListen(): void;
24
24
  stop(force?: boolean): Promise<void>;
25
- dispatch(event: any, data: any): void;
25
+ system_online(event: any, data: any): void;
26
+ dispatch(event: any, data: any): Promise<void>;
26
27
  }
27
28
  export declare enum OneBotStatus {
28
29
  Good = 0,
@@ -34,11 +35,13 @@ export declare namespace OneBot {
34
35
  type Config<V extends Version = 'V11'> = ({
35
36
  version?: V;
36
37
  password?: string;
38
+ group_whitelist?: number[];
37
39
  protocol?: IcqqConfig;
38
40
  } & (V extends 'V11' ? V11.Config : V12.Config));
39
41
  interface Base {
40
42
  start(path?: string): any;
41
43
  stop(): any;
44
+ system_online(...args: any[]): any;
42
45
  dispatch(...args: any[]): any;
43
46
  apply(...args: any[]): any;
44
47
  }
package/lib/onebot.js CHANGED
@@ -162,6 +162,7 @@ class OneBot extends events_1.EventEmitter {
162
162
  });
163
163
  }
164
164
  startListen() {
165
+ this.client.on('system.online', this.system_online.bind(this, "system.online"));
165
166
  this.client.trap('system', this.dispatch.bind(this, 'system'));
166
167
  this.client.trap('notice', this.dispatch.bind(this, 'notice'));
167
168
  this.client.trap('request', this.dispatch.bind(this, 'request'));
@@ -176,20 +177,34 @@ class OneBot extends events_1.EventEmitter {
176
177
  }
177
178
  this.client.logout(force);
178
179
  }
179
- dispatch(event, data) {
180
+ system_online(event, data) {
180
181
  for (const instance of this.instances) {
182
+ instance.system_online(data);
183
+ }
184
+ }
185
+ async dispatch(event, data) {
186
+ let group_id = data["group_id"];
187
+ for (const instance of this.instances) {
188
+ if (group_id) {
189
+ // 群消息派发白名单
190
+ let lst = instance.config.group_whitelist;
191
+ if (lst && lst.length > 0 && !lst.includes(group_id))
192
+ continue;
193
+ }
181
194
  const result = instance.format(event, data);
182
- if (data.source) {
195
+ if (data.source) { // 有 data.source 字段代表这是个回复
183
196
  switch (data.message_type) {
184
197
  case 'group':
185
198
  data.message.unshift({
186
199
  type: 'reply',
200
+ seq: data.source.seq,
187
201
  id: (0, icqq_2.genGroupMessageId)(data.group_id, data.source.user_id, data.source.seq, data.source.rand, data.source.time)
188
202
  });
189
203
  break;
190
204
  case 'private':
191
205
  data.message.unshift({
192
206
  type: 'reply',
207
+ seq: data.source.seq,
193
208
  id: (0, icqq_2.genDmMessageId)(data.source.user_id, data.source.seq, data.source.rand, data.source.time)
194
209
  });
195
210
  break;
@@ -1,6 +1,7 @@
1
1
  /// <reference types="node" />
2
2
  /// <reference types="koa-bodyparser" />
3
3
  import Koa from 'koa';
4
+ import "reflect-metadata";
4
5
  import { Logger } from "log4js";
5
6
  import { Server } from "http";
6
7
  import { Config as IcqqConfig } from "icqq";
package/lib/server/app.js CHANGED
@@ -30,6 +30,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
30
30
  exports.defineConfig = exports.createApp = exports.App = void 0;
31
31
  const koa_1 = __importDefault(require("koa"));
32
32
  const os = __importStar(require("os"));
33
+ require("reflect-metadata");
33
34
  const fs_1 = require("fs");
34
35
  const log4js_1 = require("log4js");
35
36
  const http_1 = require("http");
@@ -1,3 +1,4 @@
1
+ import { Message } from "icqq";
1
2
  import { V11 } from "../../../service/V11";
2
3
  export declare class CommonAction {
3
4
  /**
@@ -16,7 +17,7 @@ export declare class CommonAction {
16
17
  * 获取消息
17
18
  * @param message_id {string} 消息id
18
19
  */
19
- getMsg(this: V11, message_id: string): Promise<import("icqq").GroupMessage | import("icqq").PrivateMessage>;
20
+ getMsg(this: V11, message_id: number): Promise<Message>;
20
21
  /**
21
22
  * 获取合并消息
22
23
  * @param id {string} 合并id
@@ -24,8 +24,16 @@ class CommonAction {
24
24
  * 获取消息
25
25
  * @param message_id {string} 消息id
26
26
  */
27
- getMsg(message_id) {
28
- return this.client.getMsg(message_id);
27
+ async getMsg(message_id) {
28
+ if (message_id == 0)
29
+ throw new Error('getMsg: message_id[0] is invalid');
30
+ let msg_entry = await this.db.getMsgById(message_id);
31
+ if (!msg_entry)
32
+ throw new Error(`getMsg: can not find msg[${message_id}] in db`);
33
+ let msg = await this.client.getMsg(msg_entry.base64_id);
34
+ msg.message_id = String(message_id); // nonebot v11 要求 message_id 是 number 类型
35
+ msg["real_id"] = msg.message_id; // nonebot 的reply类型会检测real_id是否存在,虽然它从未使用
36
+ return msg;
29
37
  }
30
38
  /**
31
39
  * 获取合并消息
@@ -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<void | import("icqq").MessageRet>;
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).shareMusic(music.platform, music.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<void | import("icqq").MessageRet>;
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).shareMusic(music.platform, music.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) {
@@ -0,0 +1,13 @@
1
+ export declare class MsgEntry {
2
+ id?: number;
3
+ base64_id: string;
4
+ seq: number;
5
+ user_id: number;
6
+ nickname: string;
7
+ group_id: number;
8
+ group_name: string;
9
+ content: string;
10
+ recalled: boolean;
11
+ create_time: Date;
12
+ recall_time?: Date;
13
+ }
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.MsgEntry = void 0;
13
+ const typeorm_1 = require("typeorm");
14
+ let MsgEntry = class MsgEntry {
15
+ };
16
+ exports.MsgEntry = MsgEntry;
17
+ __decorate([
18
+ (0, typeorm_1.PrimaryGeneratedColumn)(),
19
+ __metadata("design:type", Number)
20
+ ], MsgEntry.prototype, "id", void 0);
21
+ __decorate([
22
+ (0, typeorm_1.Column)(),
23
+ __metadata("design:type", String)
24
+ ], MsgEntry.prototype, "base64_id", void 0);
25
+ __decorate([
26
+ (0, typeorm_1.Column)(),
27
+ __metadata("design:type", Number)
28
+ ], MsgEntry.prototype, "seq", void 0);
29
+ __decorate([
30
+ (0, typeorm_1.Column)(),
31
+ __metadata("design:type", Number)
32
+ ], MsgEntry.prototype, "user_id", void 0);
33
+ __decorate([
34
+ (0, typeorm_1.Column)(),
35
+ __metadata("design:type", String)
36
+ ], MsgEntry.prototype, "nickname", void 0);
37
+ __decorate([
38
+ (0, typeorm_1.Column)(),
39
+ __metadata("design:type", Number)
40
+ ], MsgEntry.prototype, "group_id", void 0);
41
+ __decorate([
42
+ (0, typeorm_1.Column)(),
43
+ __metadata("design:type", String)
44
+ ], MsgEntry.prototype, "group_name", void 0);
45
+ __decorate([
46
+ (0, typeorm_1.Column)({ length: 1024 }),
47
+ __metadata("design:type", String)
48
+ ], MsgEntry.prototype, "content", void 0);
49
+ __decorate([
50
+ (0, typeorm_1.Column)({ default: false }),
51
+ __metadata("design:type", Boolean)
52
+ ], MsgEntry.prototype, "recalled", void 0);
53
+ __decorate([
54
+ (0, typeorm_1.CreateDateColumn)(),
55
+ __metadata("design:type", Date)
56
+ ], MsgEntry.prototype, "create_time", void 0);
57
+ __decorate([
58
+ (0, typeorm_1.Column)({ nullable: true }),
59
+ __metadata("design:type", Date)
60
+ ], MsgEntry.prototype, "recall_time", void 0);
61
+ exports.MsgEntry = MsgEntry = __decorate([
62
+ (0, typeorm_1.Entity)()
63
+ ], MsgEntry);
@@ -0,0 +1,52 @@
1
+ import { MsgEntry } from "./db_entities";
2
+ import { DataSource, Repository } from "typeorm";
3
+ import { Logger } from "log4js";
4
+ import { AsyncLock } from "../../types";
5
+ export declare class Database {
6
+ logger: Logger;
7
+ dbPath: string;
8
+ dataSource: DataSource;
9
+ /**
10
+ * 消息在数据库中的保留时间
11
+ */
12
+ msgHistoryPreserveDays: number;
13
+ msgHistoryCheckInterval: number;
14
+ msgRepo: Repository<MsgEntry>;
15
+ dbLock: AsyncLock;
16
+ constructor(dbPath: string, logger: Logger);
17
+ initDB(): Promise<void>;
18
+ /**
19
+ * 增加或更新一条消息到数据库
20
+ * @param msgData
21
+ */
22
+ addOrUpdateMsg(msgData: MsgEntry): Promise<number>;
23
+ /**
24
+ * 通过 icqq 的 base64 格式的 message_id 获取一个 MsgData 对象
25
+ * @param base64_id
26
+ * @returns
27
+ */
28
+ getMsgByBase64Id(base64_id: string): Promise<MsgEntry | null>;
29
+ /**
30
+ * 通过 number 类型的 id 自增主键获取一个 MsgData 对象
31
+ * @param id
32
+ * @returns
33
+ */
34
+ getMsgById(id: number): Promise<MsgEntry | null>;
35
+ /**
36
+ * 通过参数从数据库中查找消息
37
+ * @param user_id
38
+ * @param group_id
39
+ * @param seq
40
+ */
41
+ getMsgByParams(user_id: number, group_id: number, seq: number): Promise<MsgEntry | null>;
42
+ /**
43
+ * 将一条消息标记为 recalled
44
+ * @param base64_id
45
+ * @param id
46
+ */
47
+ markMsgAsRecalled(base64_id?: string, id?: number): Promise<void>;
48
+ /**
49
+ * 根据 `msgPreserveDays` 变量定义的保留期限收缩数据库
50
+ */
51
+ shrinkDB(): Promise<void>;
52
+ }
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Database = void 0;
4
+ const db_entities_1 = require("./db_entities");
5
+ const typeorm_1 = require("typeorm");
6
+ const types_1 = require("../../types");
7
+ class Database {
8
+ constructor(dbPath, logger) {
9
+ /**
10
+ * 消息在数据库中的保留时间
11
+ */
12
+ this.msgHistoryPreserveDays = 14; // 历史消息默认存储2周
13
+ this.msgHistoryCheckInterval = 1 * 24 * 3600 * 1000; // 历史记录检查间隔
14
+ this.dbPath = dbPath;
15
+ this.logger = logger;
16
+ this.dbLock = new types_1.AsyncLock();
17
+ this.dataSource = new typeorm_1.DataSource({
18
+ type: "better-sqlite3",
19
+ database: dbPath,
20
+ entities: [db_entities_1.MsgEntry],
21
+ });
22
+ this.initDB();
23
+ }
24
+ async initDB() {
25
+ try {
26
+ await this.dataSource.initialize();
27
+ await this.dataSource.synchronize(false);
28
+ }
29
+ catch (err) {
30
+ this.logger.error(`sqlite [${this.dbPath}] open fail!`, err);
31
+ return;
32
+ }
33
+ this.msgRepo = this.dataSource.getRepository(db_entities_1.MsgEntry);
34
+ this.logger.debug(`sqlite [${this.dbPath}] open success`);
35
+ setInterval(() => {
36
+ this.shrinkDB();
37
+ }, this.msgHistoryCheckInterval);
38
+ }
39
+ /**
40
+ * 增加或更新一条消息到数据库
41
+ * @param msgData
42
+ */
43
+ async addOrUpdateMsg(msgData) {
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;
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();
62
+ }
63
+ }
64
+ /**
65
+ * 通过 icqq 的 base64 格式的 message_id 获取一个 MsgData 对象
66
+ * @param base64_id
67
+ * @returns
68
+ */
69
+ async getMsgByBase64Id(base64_id) {
70
+ let ret = await this.msgRepo.findOneBy({ base64_id: base64_id });
71
+ return ret;
72
+ }
73
+ /**
74
+ * 通过 number 类型的 id 自增主键获取一个 MsgData 对象
75
+ * @param id
76
+ * @returns
77
+ */
78
+ async getMsgById(id) {
79
+ let ret = await this.msgRepo.findOneBy({ id: id });
80
+ return ret;
81
+ }
82
+ /**
83
+ * 通过参数从数据库中查找消息
84
+ * @param user_id
85
+ * @param group_id
86
+ * @param seq
87
+ */
88
+ async getMsgByParams(user_id, group_id, seq) {
89
+ let ret = await this.msgRepo.findOneBy({ user_id: user_id, group_id: group_id, seq: seq });
90
+ return ret;
91
+ }
92
+ /**
93
+ * 将一条消息标记为 recalled
94
+ * @param base64_id
95
+ * @param id
96
+ */
97
+ async markMsgAsRecalled(base64_id, id) {
98
+ if (base64_id || id)
99
+ await this.msgRepo.update(base64_id ? { base64_id: base64_id } : { id: id }, { recalled: true, recall_time: new Date() });
100
+ else
101
+ throw new Error("base64_id 或 id 参数至少一个应该被赋值");
102
+ }
103
+ /**
104
+ * 根据 `msgPreserveDays` 变量定义的保留期限收缩数据库
105
+ */
106
+ async shrinkDB() {
107
+ let dt = new Date();
108
+ dt.setDate(dt.getDate() - this.msgHistoryPreserveDays);
109
+ await this.msgRepo
110
+ .createQueryBuilder()
111
+ .delete()
112
+ .from(db_entities_1.MsgEntry)
113
+ .where("create_time < :dt", { dt: dt })
114
+ .execute();
115
+ }
116
+ }
117
+ exports.Database = Database;
@@ -8,8 +8,7 @@ import { Logger } from "log4js";
8
8
  import { WebSocket, WebSocketServer } from "ws";
9
9
  import { Dispose } from "../../types";
10
10
  import { EventEmitter } from "events";
11
- import { Database } from "../../db";
12
- import { V12 } from "../../service/V12";
11
+ import { Database } from "./db_sqlite";
13
12
  export declare class V11 extends EventEmitter implements OneBot.Base {
14
13
  oneBot: OneBot<'V11'>;
15
14
  client: Client;
@@ -19,11 +18,7 @@ export declare class V11 extends EventEmitter implements OneBot.Base {
19
18
  protected timestamp: number;
20
19
  protected heartbeat?: NodeJS.Timeout;
21
20
  private path;
22
- db: Database<{
23
- eventBuffer: V12.Payload<keyof Action>[];
24
- KVMap: Record<number, string>;
25
- files: Record<string, V12.FileInfo>;
26
- }>;
21
+ db: Database;
27
22
  disposes: Dispose[];
28
23
  protected _queue: Array<{
29
24
  method: keyof Action;
@@ -41,8 +36,20 @@ export declare class V11 extends EventEmitter implements OneBot.Base {
41
36
  private startWsReverse;
42
37
  stop(force?: boolean): Promise<void>;
43
38
  format(_: any, data: any): any;
44
- dispatch(data: any): void;
39
+ system_online(data: any): void;
40
+ dispatch(data: any): Promise<void>;
45
41
  private _formatEvent;
42
+ private addMsgToDB;
43
+ /**
44
+ * 从 send_msg_xxx() 调用的返回值中提取消息存入数据库(可以让前端在没有收到同步的message数据前就有能力拿到消息对应的base64_id)
45
+ * (也有可能来的比message慢,后来的话会被数据库忽略)
46
+ * @param user_id 发送者
47
+ * @param group_id 群号,私聊为0
48
+ * @param seq 消息序号
49
+ * @param base64_id icqq返回的base64格式的消息id
50
+ */
51
+ private addMsgToDBFromSendMsgResult;
52
+ private getReplyMsgIdFromDB;
46
53
  private _httpRequestHandler;
47
54
  /**
48
55
  * 处理ws消息
@@ -16,9 +16,10 @@ const http_1 = __importDefault(require("http"));
16
16
  const https_1 = __importDefault(require("https"));
17
17
  const events_1 = require("events");
18
18
  const fs_1 = require("fs");
19
- const db_1 = require("../../db");
19
+ const db_sqlite_1 = require("./db_sqlite");
20
20
  const path_1 = require("path");
21
21
  const app_1 = require("../../server/app");
22
+ const db_entities_1 = require("./db_entities");
22
23
  class V11 extends events_1.EventEmitter {
23
24
  constructor(oneBot, client, config) {
24
25
  super();
@@ -31,9 +32,8 @@ class V11 extends events_1.EventEmitter {
31
32
  this.queue_running = false;
32
33
  this.wsr = new Set();
33
34
  this.action = new action_1.Action();
34
- this.db = new db_1.Database((0, path_1.join)(app_1.App.configDir, 'data', this.oneBot.uin + '.json'));
35
- this.db.sync({ eventBuffer: [], KVMap: {}, files: {} });
36
35
  this.logger = this.oneBot.app.getLogger(this.oneBot.uin, this.version);
36
+ this.db = new db_sqlite_1.Database((0, path_1.join)(app_1.App.configDir, 'data', this.oneBot.uin + '.db'), this.logger);
37
37
  }
38
38
  start(path) {
39
39
  this.path = `/${this.oneBot.uin}`;
@@ -190,19 +190,27 @@ class V11 extends events_1.EventEmitter {
190
190
  format(_, data) {
191
191
  return data;
192
192
  }
193
- dispatch(data) {
194
- if (!data.post_type)
195
- data.post_type = 'system';
196
- if (data.post_type === 'system') {
197
- }
193
+ system_online(data) {
194
+ this.logger.info("【好友列表】");
195
+ this.client.fl.forEach(item => this.logger.info(`\t${item.nickname}(${item.user_id})`));
196
+ this.logger.info("【群列表】");
197
+ this.client.gl.forEach(item => this.logger.info(`\t${item.group_name}(${item.group_id})`));
198
+ this.logger.info('');
199
+ }
200
+ async dispatch(data) {
201
+ data.post_type = data.post_type || 'system';
198
202
  if (data.message && data.post_type === 'message') {
199
203
  if (this.config.post_message_format === 'array') {
200
204
  data.message = (0, icqq_cq_enable_1.toSegment)(data.message);
205
+ if (data.source) { // reply
206
+ let msg0 = data.message[0];
207
+ msg0.data['id'] = await this.getReplyMsgIdFromDB(data);
208
+ }
201
209
  }
202
210
  else {
203
211
  if (data.source) {
204
- this.db.set(`KVMap.${data.source.seq}`, data.message[0].id);
205
212
  data.message.shift();
213
+ // segment 更好用, cq 一般只用来显示,就不存储真实id了, 有需求的自己去改
206
214
  data.message = (0, icqq_cq_enable_1.toCqcode)(data).replace(/^(\[CQ:reply,id=)(.+?)\]/, `$1${data.source.seq}]`);
207
215
  }
208
216
  else {
@@ -211,12 +219,14 @@ class V11 extends events_1.EventEmitter {
211
219
  }
212
220
  }
213
221
  if (data.message_id) {
214
- this.db.set(`KVMap.${data.seq}`, data.message_id);
215
- data.message_id = data.seq;
222
+ data.message_id = await this.addMsgToDB(data);
223
+ }
224
+ if (data.post_type == 'notice' && String(data.notice_type).endsWith('_recall')) {
225
+ this.db.markMsgAsRecalled(data.base64_id);
216
226
  }
217
227
  if (data.font) {
218
228
  const fontNo = Buffer.from(data.font).readUInt32BE();
219
- this.db.set(`KVMap.${data.fontNo}`, data.font);
229
+ // this.db.set(`KVMap.${data.fontNo}`,data.font)
220
230
  data.font = fontNo;
221
231
  }
222
232
  data.time = Math.floor(Date.now() / 1000);
@@ -282,6 +292,49 @@ class V11 extends events_1.EventEmitter {
282
292
  return JSON.stringify(data);
283
293
  }
284
294
  }
295
+ async addMsgToDB(data) {
296
+ if (!data.sender || !('user_id' in data.sender)) { // eg. notice
297
+ return;
298
+ }
299
+ let msg = new db_entities_1.MsgEntry();
300
+ msg.base64_id = data.message_id;
301
+ msg.seq = data.seq;
302
+ msg.user_id = data.sender.user_id;
303
+ msg.nickname = data.sender.nickname;
304
+ if (data.message_type === 'group') {
305
+ msg.group_id = data.group_id;
306
+ msg.group_name = data["group_name"] || ''; // 可能不存在(gocq默认不发)
307
+ }
308
+ else {
309
+ msg.group_id = 0;
310
+ msg.group_name = '';
311
+ }
312
+ msg.content = data.cqCode;
313
+ return await this.db.addOrUpdateMsg(msg);
314
+ }
315
+ /**
316
+ * 从 send_msg_xxx() 调用的返回值中提取消息存入数据库(可以让前端在没有收到同步的message数据前就有能力拿到消息对应的base64_id)
317
+ * (也有可能来的比message慢,后来的话会被数据库忽略)
318
+ * @param user_id 发送者
319
+ * @param group_id 群号,私聊为0
320
+ * @param seq 消息序号
321
+ * @param base64_id icqq返回的base64格式的消息id
322
+ */
323
+ async addMsgToDBFromSendMsgResult(user_id, group_id, seq, base64_id) {
324
+ let msg = new db_entities_1.MsgEntry();
325
+ msg.base64_id = base64_id;
326
+ msg.seq = seq;
327
+ msg.user_id = user_id;
328
+ msg.nickname = '';
329
+ msg.group_id = group_id;
330
+ msg.content = '';
331
+ return await this.db.addOrUpdateMsg(msg);
332
+ }
333
+ async getReplyMsgIdFromDB(data) {
334
+ let group_id = (data.message_type === 'group') ? data.group_id : 0;
335
+ let msg = await this.db.getMsgByParams(data.source.user_id, group_id, data.source.seq);
336
+ return msg ? msg.id : 0;
337
+ }
285
338
  async _httpRequestHandler(ctx) {
286
339
  if (ctx.method === 'OPTIONS') {
287
340
  return ctx.writeHead(200, {
@@ -375,7 +428,9 @@ class V11 extends events_1.EventEmitter {
375
428
  error: {
376
429
  code, message
377
430
  },
378
- echo: data?.echo
431
+ echo: data?.echo,
432
+ msg: e.message,
433
+ action: data.action
379
434
  }));
380
435
  }
381
436
  });
@@ -447,7 +502,7 @@ class V11 extends events_1.EventEmitter {
447
502
  async apply(req) {
448
503
  let { action, params, echo } = req;
449
504
  if (typeof params.message_id == 'number' || /^\d+$/.test(params.message_id)) {
450
- params.message_id = this.db.get(`KVMap.${params.message_id}`);
505
+ params.message_id = (await this.db.getMsgById(params.message_id)).id; // 调用api时把本地的数字id转为base64发给icqq
451
506
  }
452
507
  action = (0, utils_1.toLine)(action);
453
508
  let is_async = action.includes("_async");
@@ -510,7 +565,7 @@ class V11 extends events_1.EventEmitter {
510
565
  }
511
566
  else {
512
567
  try {
513
- ret = this.action[method].apply(this, args);
568
+ ret = await this.action[method].apply(this, args);
514
569
  }
515
570
  catch (e) {
516
571
  return JSON.stringify(V11.error(e.message));
@@ -531,9 +586,9 @@ class V11 extends events_1.EventEmitter {
531
586
  result.data = [...result.data.values()];
532
587
  if (result.data?.message)
533
588
  result.data.message = (0, icqq_cq_enable_1.toSegment)(result.data.message);
534
- if (result.data?.message_id && result.data?.seq) {
535
- this.db.set(`KVMap.${result.data.seq}`, result.data.message_id);
536
- result.data.message_id = result.data.seq;
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);
537
592
  }
538
593
  if (echo) {
539
594
  result.echo = echo;
@@ -9,8 +9,12 @@ async function processMessage(message, source) {
9
9
  if (quote)
10
10
  (0, utils_1.remove)(elements, quote);
11
11
  let music = elements.find(e => e.type === 'music');
12
- if (music)
12
+ if (music) {
13
13
  (0, utils_1.remove)(elements, music);
14
+ if (String(music.platform) === 'custom') {
15
+ music.platform = music['subtype']; // gocq 的平台数据存储在 subtype 内,兼容 icqq 时要求前端必须发送 id 字段
16
+ }
17
+ }
14
18
  let share = elements.find(e => e.type === 'share');
15
19
  if (share)
16
20
  (0, utils_1.remove)(elements, share);
@@ -47,7 +47,8 @@ export declare class V12 extends EventEmitter implements OneBot.Base {
47
47
  friend: any;
48
48
  member: any;
49
49
  };
50
- dispatch(data: Record<string, any>): void;
50
+ system_online(data: any): void;
51
+ dispatch(data: Record<string, any>): Promise<void>;
51
52
  apply(req: V12.RequestAction): Promise<string>;
52
53
  private httpAuth;
53
54
  private httpRequestHandler;
@@ -351,7 +351,9 @@ class V12 extends events_1.EventEmitter {
351
351
  };
352
352
  return V12.formatPayload(this.oneBot.uin, event, data);
353
353
  }
354
- dispatch(data) {
354
+ system_online(data) {
355
+ }
356
+ async dispatch(data) {
355
357
  const payload = {
356
358
  id: (0, utils_2.uuid)(),
357
359
  impl: 'onebots',
@@ -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 || jumpUrl;
21
+ musicUrl = music['url'] || music['voice'] || 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/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/lib/utils.d.ts CHANGED
@@ -4,6 +4,12 @@ export declare function transformObj(obj: any, callback: any): any;
4
4
  export declare function deepClone<T extends any>(obj: T): T;
5
5
  export declare function pick<T extends object, K extends keyof T>(source: T, keys?: Iterable<K>, forced?: boolean): Pick<T, K>;
6
6
  export declare function omit<T, K extends keyof T>(source: T, keys?: Iterable<K>): Omit<T, K>;
7
+ /**
8
+ * 将驼峰命名替换为下划线分割命名
9
+ * @param name
10
+ * @returns
11
+ * @todo 是否应该改名 ToUnderLine()?
12
+ */
7
13
  export declare function toLine<T extends string>(name: T): string;
8
14
  export interface Class {
9
15
  new (...args: any[]): any;
package/lib/utils.js CHANGED
@@ -114,6 +114,12 @@ function omit(source, keys) {
114
114
  return result;
115
115
  }
116
116
  exports.omit = omit;
117
+ /**
118
+ * 将驼峰命名替换为下划线分割命名
119
+ * @param name
120
+ * @returns
121
+ * @todo 是否应该改名 ToUnderLine()?
122
+ */
117
123
  function toLine(name) {
118
124
  return name.replace(/([A-Z])/g, "_$1").toLowerCase();
119
125
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "onebots",
3
- "version": "0.4.19",
3
+ "version": "0.4.21",
4
4
  "description": "基于icqq的多例oneBot实现",
5
5
  "engines": {
6
6
  "node": ">=16"
@@ -64,6 +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"
67
+ "ws": "^8.8.0",
68
+ "better-sqlite3": "^8.6.0",
69
+ "reflect-metadata": "^0.1.13",
70
+ "typeorm": "^0.3.17"
68
71
  }
69
72
  }