@karinjs/adapter-qqbot 1.0.1
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 +80 -0
- package/config/config.yaml +69 -0
- package/lib/core/api.d.ts +167 -0
- package/lib/core/api.js +434 -0
- package/lib/core/index.d.ts +92 -0
- package/lib/core/index.js +328 -0
- package/lib/core/intents.d.ts +6 -0
- package/lib/core/intents.js +57 -0
- package/lib/core/markdown.d.ts +23 -0
- package/lib/core/markdown.js +251 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +7 -0
- package/lib/types/event.d.ts +232 -0
- package/lib/types/event.js +1 -0
- package/lib/types/index.d.ts +3 -0
- package/lib/types/index.js +3 -0
- package/lib/types/media.d.ts +100 -0
- package/lib/types/media.js +1 -0
- package/lib/types/message.d.ts +85 -0
- package/lib/types/message.js +1 -0
- package/lib/utils/common.d.ts +58 -0
- package/lib/utils/common.js +195 -0
- package/lib/utils/config.d.ts +134 -0
- package/lib/utils/config.js +176 -0
- package/lib/utils/dir.d.ts +5 -0
- package/lib/utils/dir.js +9 -0
- package/lib/utils/index.d.ts +3 -0
- package/lib/utils/index.js +3 -0
- package/package.json +45 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { Attachment } from './media.js';
|
|
2
|
+
/**
|
|
3
|
+
* opcode
|
|
4
|
+
* 客户端操作含义如下:
|
|
5
|
+
* Receive 客户端接收到服务端 push 的消息
|
|
6
|
+
* Send 客户端发送消息
|
|
7
|
+
* Reply 客户端接收到服务端发送的消息之后的回包(HTTP 回调模式)
|
|
8
|
+
*/
|
|
9
|
+
export declare const enum Opcode {
|
|
10
|
+
/** Dispatch [Receive] 服务端进行消息推送 */
|
|
11
|
+
Dispatch = 0,
|
|
12
|
+
/** Heartbeat [Send/Receive] 客户端或服务端发送心跳 */
|
|
13
|
+
Heartbeat = 1,
|
|
14
|
+
/** Identify [Send] 客户端发送鉴权 */
|
|
15
|
+
Identify = 2,
|
|
16
|
+
/** Resume [Send] 客户端恢复连接 */
|
|
17
|
+
Resume = 6,
|
|
18
|
+
/** Reconnect [Receive] 服务端通知客户端重新连接 */
|
|
19
|
+
Reconnect = 7,
|
|
20
|
+
/** Invalid Session [Receive] 当identify或resume的时候,如果参数有错,服务端会返回该消息 */
|
|
21
|
+
InvalidSession = 9,
|
|
22
|
+
/** Hello [Receive] 当客户端与网关建立ws连接之后,网关下发的第一条消息 */
|
|
23
|
+
Hello = 10,
|
|
24
|
+
/** Heartbeat ACK [Receive/Reply] 当发送心跳成功之后,就会收到该消息 */
|
|
25
|
+
HeartbeatACK = 11,
|
|
26
|
+
/** HTTP Callback ACK [Reply] 仅用于 http 回调模式的回包,代表机器人收到了平台推送的数据 */
|
|
27
|
+
HTTPCallbackACK = 12
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 心跳生命周期事件
|
|
31
|
+
*/
|
|
32
|
+
export interface HeartbeatEvent {
|
|
33
|
+
/** opcode */
|
|
34
|
+
op: Opcode.Hello;
|
|
35
|
+
/** 事件内容 */
|
|
36
|
+
d: {
|
|
37
|
+
/** 心跳声明周期 */
|
|
38
|
+
heartbeat_interval: number;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 心跳回包事件
|
|
43
|
+
*/
|
|
44
|
+
export interface HeartbeatACKEvent {
|
|
45
|
+
/** opcode */
|
|
46
|
+
op: Opcode.HeartbeatACK;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 子事件类型
|
|
50
|
+
*/
|
|
51
|
+
export declare const enum EventType {
|
|
52
|
+
/** 在鉴权成功后下发 代表建立连接成功 */
|
|
53
|
+
READY = "READY",
|
|
54
|
+
/** 恢复登录状态后 不发遗漏事件完毕下发 */
|
|
55
|
+
RESUMED = "RESUMED",
|
|
56
|
+
/** 当机器人加入新的guild时 */
|
|
57
|
+
GUILD_CREATE = "GUILD_CREATE",
|
|
58
|
+
/** 当guild资料发生变更时 */
|
|
59
|
+
GUILD_UPDATE = "GUILD_UPDATE",
|
|
60
|
+
/** 当机器人退出guild时 */
|
|
61
|
+
GUILD_DELETE = "GUILD_DELETE",
|
|
62
|
+
/** 当channel被创建时 */
|
|
63
|
+
CHANNEL_CREATE = "CHANNEL_CREATE",
|
|
64
|
+
/** 当channel被更新时 */
|
|
65
|
+
CHANNEL_UPDATE = "CHANNEL_UPDATE",
|
|
66
|
+
/** 当channel被删除时 */
|
|
67
|
+
CHANNEL_DELETE = "CHANNEL_DELETE",
|
|
68
|
+
/** 当成员加入时 */
|
|
69
|
+
GUILD_MEMBER_ADD = "GUILD_MEMBER_ADD",
|
|
70
|
+
/** 当成员资料变更时 */
|
|
71
|
+
GUILD_MEMBER_UPDATE = "GUILD_MEMBER_UPDATE",
|
|
72
|
+
/** 当成员被移除时 */
|
|
73
|
+
GUILD_MEMBER_REMOVE = "GUILD_MEMBER_REMOVE",
|
|
74
|
+
/** 发送消息事件,代表频道内的全部消息,而不只是 at 机器人的消息。内容与 AT_MESSAGE_CREATE 相同 */
|
|
75
|
+
MESSAGE_CREATE = "MESSAGE_CREATE",
|
|
76
|
+
/** 删除(撤回)消息事件 */
|
|
77
|
+
MESSAGE_DELETE = "MESSAGE_DELETE",
|
|
78
|
+
/** 为消息添加表情表态 */
|
|
79
|
+
MESSAGE_REACTION_ADD = "MESSAGE_REACTION_ADD",
|
|
80
|
+
/** 为消息删除表情表态 */
|
|
81
|
+
MESSAGE_REACTION_REMOVE = "MESSAGE_REACTION_REMOVE",
|
|
82
|
+
/** 当收到用户发给机器人的私信消息时 */
|
|
83
|
+
DIRECT_MESSAGE_CREATE = "DIRECT_MESSAGE_CREATE",
|
|
84
|
+
/** 删除(撤回)消息事件 */
|
|
85
|
+
DIRECT_MESSAGE_DELETE = "DIRECT_MESSAGE_DELETE",
|
|
86
|
+
/** 用户单聊发消息给机器人时候 */
|
|
87
|
+
C2C_MESSAGE_CREATE = "C2C_MESSAGE_CREATE",
|
|
88
|
+
/** 用户添加使用机器人 */
|
|
89
|
+
FRIEND_ADD = "FRIEND_ADD",
|
|
90
|
+
/** 用户删除机器人 */
|
|
91
|
+
FRIEND_DEL = "FRIEND_DEL",
|
|
92
|
+
/** 用户在机器人资料卡手动关闭"主动消息"推送 */
|
|
93
|
+
C2C_MSG_REJECT = "C2C_MSG_REJECT",
|
|
94
|
+
/** 用户在机器人资料卡手动开启"主动消息"推送开关 */
|
|
95
|
+
C2C_MSG_RECEIVE = "C2C_MSG_RECEIVE",
|
|
96
|
+
/** 用户在群里@机器人时收到的消息 */
|
|
97
|
+
GROUP_AT_MESSAGE_CREATE = "GROUP_AT_MESSAGE_CREATE",
|
|
98
|
+
/** 机器人被添加到群聊 */
|
|
99
|
+
GROUP_ADD_ROBOT = "GROUP_ADD_ROBOT",
|
|
100
|
+
/** 机器人被移出群聊 */
|
|
101
|
+
GROUP_DEL_ROBOT = "GROUP_DEL_ROBOT",
|
|
102
|
+
/** 群管理员主动在机器人资料页操作关闭通知 */
|
|
103
|
+
GROUP_MSG_REJECT = "GROUP_MSG_REJECT",
|
|
104
|
+
/** 群管理员主动在机器人资料页操作开启通知 */
|
|
105
|
+
GROUP_MSG_RECEIVE = "GROUP_MSG_RECEIVE",
|
|
106
|
+
/** 互动事件创建时 */
|
|
107
|
+
INTERACTION_CREATE = "INTERACTION_CREATE",
|
|
108
|
+
/** 消息审核通过 */
|
|
109
|
+
MESSAGE_AUDIT_PASS = "MESSAGE_AUDIT_PASS",
|
|
110
|
+
/** 消息审核不通过 */
|
|
111
|
+
MESSAGE_AUDIT_REJECT = "MESSAGE_AUDIT_REJECT",
|
|
112
|
+
/** 当用户创建主题时 */
|
|
113
|
+
FORUM_THREAD_CREATE = "FORUM_THREAD_CREATE",
|
|
114
|
+
/** 当用户更新主题时 */
|
|
115
|
+
FORUM_THREAD_UPDATE = "FORUM_THREAD_UPDATE",
|
|
116
|
+
/** 当用户删除主题时 */
|
|
117
|
+
FORUM_THREAD_DELETE = "FORUM_THREAD_DELETE",
|
|
118
|
+
/** 当用户创建帖子时 */
|
|
119
|
+
FORUM_POST_CREATE = "FORUM_POST_CREATE",
|
|
120
|
+
/** 当用户删除帖子时 */
|
|
121
|
+
FORUM_POST_DELETE = "FORUM_POST_DELETE",
|
|
122
|
+
/** 当用户回复评论时 */
|
|
123
|
+
FORUM_REPLY_CREATE = "FORUM_REPLY_CREATE",
|
|
124
|
+
/** 当用户回复评论时 */
|
|
125
|
+
FORUM_REPLY_DELETE = "FORUM_REPLY_DELETE",
|
|
126
|
+
/** 当用户发表审核通过时 */
|
|
127
|
+
FORUM_PUBLISH_AUDIT_RESULT = "FORUM_PUBLISH_AUDIT_RESULT",
|
|
128
|
+
/** 音频开始播放时 */
|
|
129
|
+
AUDIO_START = "AUDIO_START",
|
|
130
|
+
/** 音频播放结束时 */
|
|
131
|
+
AUDIO_FINISH = "AUDIO_FINISH",
|
|
132
|
+
/** 上麦时 */
|
|
133
|
+
AUDIO_ON_MIC = "AUDIO_ON_MIC",
|
|
134
|
+
/** 下麦时 */
|
|
135
|
+
AUDIO_OFF_MIC = "AUDIO_OFF_MIC",
|
|
136
|
+
/** 当收到@机器人的消息时 */
|
|
137
|
+
AT_MESSAGE_CREATE = "AT_MESSAGE_CREATE",
|
|
138
|
+
/** 当频道的消息被删除时 */
|
|
139
|
+
PUBLIC_MESSAGE_DELETE = "PUBLIC_MESSAGE_DELETE"
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* 子事件基类
|
|
143
|
+
*/
|
|
144
|
+
export interface Event {
|
|
145
|
+
/** opcode */
|
|
146
|
+
op: Opcode.Dispatch;
|
|
147
|
+
/** 序列号 */
|
|
148
|
+
s: number;
|
|
149
|
+
/** 事件类型 */
|
|
150
|
+
t: EventType;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* READY子事件
|
|
154
|
+
*/
|
|
155
|
+
export interface ReadyEvent extends Event {
|
|
156
|
+
/** 事件类型 */
|
|
157
|
+
t: EventType.READY;
|
|
158
|
+
/** 事件内容 */
|
|
159
|
+
d: {
|
|
160
|
+
version: number;
|
|
161
|
+
session_id: string;
|
|
162
|
+
user: {
|
|
163
|
+
/** 机器人的user_id */
|
|
164
|
+
id: string;
|
|
165
|
+
/** 机器人的nickname */
|
|
166
|
+
username: string;
|
|
167
|
+
bot: boolean;
|
|
168
|
+
status: number;
|
|
169
|
+
};
|
|
170
|
+
shard: Number[];
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* C2C_MESSAGE_CREATE子事件
|
|
175
|
+
*/
|
|
176
|
+
export interface C2CMessageCreateEvent extends Event {
|
|
177
|
+
/** 事件类型 */
|
|
178
|
+
t: EventType.C2C_MESSAGE_CREATE;
|
|
179
|
+
/** 平台方消息ID 格式: C2C_MESSAGE_CREATE:abc... */
|
|
180
|
+
id: string;
|
|
181
|
+
d: {
|
|
182
|
+
/** 富媒体消息 */
|
|
183
|
+
attachments?: Array<Attachment>;
|
|
184
|
+
/** 发送者信息 */
|
|
185
|
+
author: {
|
|
186
|
+
/** 发送者的id 目前版本和user_openid一致 */
|
|
187
|
+
id: string;
|
|
188
|
+
/** 发送者的user_openid */
|
|
189
|
+
user_openid: string;
|
|
190
|
+
};
|
|
191
|
+
/** 消息内容 */
|
|
192
|
+
content: string;
|
|
193
|
+
/** 消息ID message_id */
|
|
194
|
+
id: string;
|
|
195
|
+
/** 消息发送时间 */
|
|
196
|
+
timestamp: string;
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* GROUP_AT_MESSAGE_CREATE子事件
|
|
201
|
+
*/
|
|
202
|
+
export interface GroupAtMessageCreateEvent extends Event {
|
|
203
|
+
/** 事件类型 */
|
|
204
|
+
t: EventType.GROUP_AT_MESSAGE_CREATE;
|
|
205
|
+
/** 平台方消息ID 格式: GROUP_AT_MESSAGE_CREATE:abc... */
|
|
206
|
+
id: string;
|
|
207
|
+
d: {
|
|
208
|
+
/** 富媒体消息 */
|
|
209
|
+
attachments?: Array<Attachment>;
|
|
210
|
+
/** 发送者信息 */
|
|
211
|
+
author: {
|
|
212
|
+
/** 发送者的id 目前版本和user_openid一致 */
|
|
213
|
+
id: string;
|
|
214
|
+
/** 发送者的user_openid */
|
|
215
|
+
member_openid: string;
|
|
216
|
+
};
|
|
217
|
+
/** 消息内容 */
|
|
218
|
+
content: string;
|
|
219
|
+
/** 群ID group_id 目前版本和group_openid一致 */
|
|
220
|
+
group_id: string;
|
|
221
|
+
/** 消息ID group_openid */
|
|
222
|
+
group_openid: string;
|
|
223
|
+
/** 消息ID message_id */
|
|
224
|
+
id: string;
|
|
225
|
+
/** 消息发送时间 */
|
|
226
|
+
timestamp: string;
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* 所有子事件
|
|
231
|
+
*/
|
|
232
|
+
export type SubEvent = ReadyEvent | C2CMessageCreateEvent | GroupAtMessageCreateEvent;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 需要注意 目前只有私聊可以收到file类型的消息
|
|
3
|
+
*/
|
|
4
|
+
export type Content_type = 'image/gif' | 'image/jpeg' | 'image/png' | 'file';
|
|
5
|
+
/**
|
|
6
|
+
* 富媒体文件类型
|
|
7
|
+
*/
|
|
8
|
+
export declare const enum FileType {
|
|
9
|
+
/**
|
|
10
|
+
* 图片
|
|
11
|
+
*/
|
|
12
|
+
Image = 1,
|
|
13
|
+
/**
|
|
14
|
+
* 视频
|
|
15
|
+
*/
|
|
16
|
+
Video = 2,
|
|
17
|
+
/**
|
|
18
|
+
* 语音
|
|
19
|
+
*/
|
|
20
|
+
Record = 3,
|
|
21
|
+
/**
|
|
22
|
+
* 文件
|
|
23
|
+
*/
|
|
24
|
+
File = 4
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 富媒体基类
|
|
28
|
+
*/
|
|
29
|
+
export interface Attachment {
|
|
30
|
+
/**
|
|
31
|
+
* 类型
|
|
32
|
+
*/
|
|
33
|
+
content_type: Content_type;
|
|
34
|
+
/** 未知作用 */
|
|
35
|
+
content?: string;
|
|
36
|
+
/**
|
|
37
|
+
* 文件名
|
|
38
|
+
*/
|
|
39
|
+
filename: string;
|
|
40
|
+
/**
|
|
41
|
+
* 图片高度
|
|
42
|
+
*/
|
|
43
|
+
height?: number;
|
|
44
|
+
/**
|
|
45
|
+
* 图片宽度
|
|
46
|
+
*/
|
|
47
|
+
width?: number;
|
|
48
|
+
/**
|
|
49
|
+
* 文件大小
|
|
50
|
+
*/
|
|
51
|
+
size: number;
|
|
52
|
+
/**
|
|
53
|
+
* 文件url
|
|
54
|
+
*/
|
|
55
|
+
url: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 富媒体消息结构
|
|
59
|
+
*/
|
|
60
|
+
export interface MediaType {
|
|
61
|
+
/** 文件信息 */
|
|
62
|
+
file_info: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 上传富媒体文件请求参数
|
|
66
|
+
*/
|
|
67
|
+
export interface UploadMediaOptions {
|
|
68
|
+
/**
|
|
69
|
+
* 媒体的文件类型
|
|
70
|
+
* - 1. 图片 png/jpg
|
|
71
|
+
* - 2. 视频 mp4
|
|
72
|
+
* - 3. 语音 silk
|
|
73
|
+
* - 4. 文件
|
|
74
|
+
*/
|
|
75
|
+
file_type: FileType;
|
|
76
|
+
/**
|
|
77
|
+
* 媒体资源的URL
|
|
78
|
+
*/
|
|
79
|
+
url?: string;
|
|
80
|
+
/** 不带前缀的base64 */
|
|
81
|
+
file_data?: string;
|
|
82
|
+
/**
|
|
83
|
+
* 是否直接发送消息到目标端
|
|
84
|
+
* 如果设置为 true,将会消耗主动消息频次
|
|
85
|
+
*/
|
|
86
|
+
srv_send_msg: boolean;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 上传富媒体文件返回数据
|
|
90
|
+
*/
|
|
91
|
+
export interface UploadMediaResponse {
|
|
92
|
+
/** 文件 ID */
|
|
93
|
+
file_uuid: string;
|
|
94
|
+
/** 文件信息,用于发消息接口的 media 字段使用 */
|
|
95
|
+
file_info: string;
|
|
96
|
+
/** 有效期,表示剩余多少秒到期,到期后 file_info 失效,当等于 0 时,表示可长期使用 */
|
|
97
|
+
ttl: number;
|
|
98
|
+
/** 发送消息的唯一ID,当srv_send_msg设置为true时返回 */
|
|
99
|
+
id?: string;
|
|
100
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { ArkType, KeyboardType, MarkdownType } from 'node-karin';
|
|
2
|
+
import { MediaType } from './media.js';
|
|
3
|
+
/**
|
|
4
|
+
* 请求路径类型
|
|
5
|
+
*/
|
|
6
|
+
export declare const enum PathType {
|
|
7
|
+
Groups = "groups",
|
|
8
|
+
Friends = "users"
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* 消息类型
|
|
12
|
+
*/
|
|
13
|
+
export declare const enum MessageType {
|
|
14
|
+
/**
|
|
15
|
+
* 文本消息
|
|
16
|
+
*/
|
|
17
|
+
Text = 0,
|
|
18
|
+
/**
|
|
19
|
+
* markdown消息
|
|
20
|
+
*/
|
|
21
|
+
Markdown = 2,
|
|
22
|
+
/**
|
|
23
|
+
* ark消息
|
|
24
|
+
*/
|
|
25
|
+
Ark = 3,
|
|
26
|
+
/**
|
|
27
|
+
* embed消息
|
|
28
|
+
*/
|
|
29
|
+
Embed = 4,
|
|
30
|
+
/**
|
|
31
|
+
* 富媒体消息
|
|
32
|
+
*/
|
|
33
|
+
Media = 7
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* v2接口枚举
|
|
37
|
+
*/
|
|
38
|
+
export declare const enum V2ApiType {
|
|
39
|
+
/**
|
|
40
|
+
* 发送消息
|
|
41
|
+
*/
|
|
42
|
+
Message = "messages",
|
|
43
|
+
/**
|
|
44
|
+
* 上传富媒体文件
|
|
45
|
+
*/
|
|
46
|
+
Files = "files"
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 被动消息ID类型
|
|
50
|
+
*/
|
|
51
|
+
export declare const enum IdType {
|
|
52
|
+
MsgID = "msg_id",
|
|
53
|
+
EventID = "event_id"
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 被动消息Seq
|
|
57
|
+
*/
|
|
58
|
+
export declare const enum SeqType {
|
|
59
|
+
MsgSeq = "msg_seq"
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 发送消息请求参数
|
|
63
|
+
*/
|
|
64
|
+
export interface SendMessageOptions {
|
|
65
|
+
/** 文本内容 没有传空格 */
|
|
66
|
+
content: string;
|
|
67
|
+
/** 消息类型 */
|
|
68
|
+
msg_type: MessageType;
|
|
69
|
+
/** markdown消息 */
|
|
70
|
+
markdown?: MarkdownType<boolean>;
|
|
71
|
+
/** keyboard按钮消息 */
|
|
72
|
+
keyboard?: KeyboardType<boolean>;
|
|
73
|
+
/** ark消息 */
|
|
74
|
+
ark?: ArkType;
|
|
75
|
+
/** 富媒体消息 */
|
|
76
|
+
media?: MediaType;
|
|
77
|
+
/** 【暂未支持】消息引用 */
|
|
78
|
+
message_reference?: object;
|
|
79
|
+
/** 平台方事件ID,可以用于被动消息发送 */
|
|
80
|
+
[IdType.EventID]?: string;
|
|
81
|
+
/** 平台方消息ID,可以用于被动消息发送 */
|
|
82
|
+
[IdType.MsgID]?: string;
|
|
83
|
+
/** 回复消息的序号 相同的 msg_id + msg_seq 重复发送会失败 */
|
|
84
|
+
[SeqType.MsgSeq]?: number;
|
|
85
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { ButtonElement } from 'node-karin';
|
|
2
|
+
/**
|
|
3
|
+
* 二维码返回参数
|
|
4
|
+
*/
|
|
5
|
+
export interface QRRes {
|
|
6
|
+
/** 二维码base64 */
|
|
7
|
+
base64: string;
|
|
8
|
+
/** 宽度 */
|
|
9
|
+
width: number;
|
|
10
|
+
/** 高度 */
|
|
11
|
+
height: number;
|
|
12
|
+
}
|
|
13
|
+
declare class Common {
|
|
14
|
+
/** ffmpeg */
|
|
15
|
+
ffmpeg: string;
|
|
16
|
+
constructor();
|
|
17
|
+
/**
|
|
18
|
+
* 语音转silk
|
|
19
|
+
* @param voice - 语音文件
|
|
20
|
+
* @returns silk buffer
|
|
21
|
+
*/
|
|
22
|
+
voiceToSilk(voice: string): Promise<string>;
|
|
23
|
+
/**
|
|
24
|
+
* 初始化ffmpeg
|
|
25
|
+
*/
|
|
26
|
+
initFfmpeg(): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* 处理URL
|
|
29
|
+
* @param url - URL
|
|
30
|
+
* @param exclude - 排除的URL
|
|
31
|
+
*/
|
|
32
|
+
getUrls(url: string, exclude?: never[]): string[];
|
|
33
|
+
/**
|
|
34
|
+
* 传入URL数组,转为二维码base64
|
|
35
|
+
* @param urls - URL数组
|
|
36
|
+
*/
|
|
37
|
+
getQrCode(urls: string[]): Promise<QRRes>;
|
|
38
|
+
/**
|
|
39
|
+
* 传入文本 返回处理后的文本、图片
|
|
40
|
+
* @param text - 文本
|
|
41
|
+
*/
|
|
42
|
+
getQQBotText(text: string): Promise<{
|
|
43
|
+
/** 处理后的文本 */
|
|
44
|
+
text: string;
|
|
45
|
+
/** 二维码数据 */
|
|
46
|
+
data?: QRRes;
|
|
47
|
+
}>;
|
|
48
|
+
/**
|
|
49
|
+
* 传入文本 处理文本 返回的按钮为多行按钮
|
|
50
|
+
* @param text - 文本
|
|
51
|
+
*/
|
|
52
|
+
getQQBotButton(text: string): {
|
|
53
|
+
text: string;
|
|
54
|
+
data: ButtonElement[];
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export declare const common: Common;
|
|
58
|
+
export {};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import Jimp from 'jimp';
|
|
3
|
+
import qrcode from 'qrcode';
|
|
4
|
+
import { basename } from './dir.js';
|
|
5
|
+
import { encode, isSilk } from 'silk-wasm';
|
|
6
|
+
import lodash from 'node-karin/lodash';
|
|
7
|
+
import GetUrls from '@karinjs/geturls';
|
|
8
|
+
import { config, execs, logger, common as karinCommon, segment } from 'node-karin';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
class Common {
|
|
11
|
+
/** ffmpeg */
|
|
12
|
+
ffmpeg;
|
|
13
|
+
constructor() {
|
|
14
|
+
this.ffmpeg = '';
|
|
15
|
+
this.initFfmpeg();
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 语音转silk
|
|
19
|
+
* @param voice - 语音文件
|
|
20
|
+
* @returns silk buffer
|
|
21
|
+
*/
|
|
22
|
+
async voiceToSilk(voice) {
|
|
23
|
+
if (!this.ffmpeg)
|
|
24
|
+
throw TypeError('ffmpeg未安装,请安装 ffmpeg 或者 配置ffmpeg_path');
|
|
25
|
+
const time = Date.now();
|
|
26
|
+
const dir = `${process.cwd()}/temp/${basename}/${Date.now()}`;
|
|
27
|
+
let voicePath = dir;
|
|
28
|
+
/** 传入的是路径 则直接使用 */
|
|
29
|
+
if (fs.existsSync(voice)) {
|
|
30
|
+
const buffer = fs.readFileSync(voice);
|
|
31
|
+
if (isSilk(buffer))
|
|
32
|
+
return `base64://${Buffer.from(buffer).toString('base64')}`;
|
|
33
|
+
voicePath = path.resolve(voice);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
/** 保存到本地 */
|
|
37
|
+
const buffer = await karinCommon.buffer(voice);
|
|
38
|
+
if (isSilk(buffer))
|
|
39
|
+
return `base64://${Buffer.from(buffer).toString('base64')}`;
|
|
40
|
+
fs.writeFileSync(dir, buffer);
|
|
41
|
+
}
|
|
42
|
+
const pcmPath = `${dir}.pcm`;
|
|
43
|
+
const silkPath = `${dir}.silk`;
|
|
44
|
+
const command = `${this.ffmpeg} -i ${voicePath} -f s16le -ar 48000 -ac 1 ${pcmPath}`;
|
|
45
|
+
await execs(command);
|
|
46
|
+
logger.info('pcm文件转换成功 开始转码为silk文件');
|
|
47
|
+
const buffer = await fs.promises.readFile(pcmPath);
|
|
48
|
+
const silk = await encode(buffer, 48000);
|
|
49
|
+
logger.info(`silk文件转码成功 音频时长: ${logger.yellow(silk.duration + 'ms')} 转码耗时: ${logger.yellow(Date.now() - time + 'ms')}`);
|
|
50
|
+
/** dev模式下生成silk文件并保留 */
|
|
51
|
+
if (process.env.karin_app_mode === 'dev') {
|
|
52
|
+
await fs.promises.writeFile(silkPath, Buffer.from(silk.data));
|
|
53
|
+
logger.info(`silk文件已生成: ${logger.yellow(silkPath)}`);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
/** 非dev删除文件 */
|
|
57
|
+
fs.promises.unlink(voicePath);
|
|
58
|
+
fs.promises.unlink(pcmPath);
|
|
59
|
+
}
|
|
60
|
+
return `base64://${Buffer.from(silk.data).toString('base64')}`;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 初始化ffmpeg
|
|
64
|
+
*/
|
|
65
|
+
async initFfmpeg() {
|
|
66
|
+
try {
|
|
67
|
+
/** 环境变量 */
|
|
68
|
+
await execs('ffmpeg -version');
|
|
69
|
+
this.ffmpeg = 'ffmpeg';
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
/** 环境变量没有读配置 */
|
|
73
|
+
const ffmpeg_path = config.Config.ffmpeg_path;
|
|
74
|
+
if (!ffmpeg_path)
|
|
75
|
+
return logger.error(logger.red('ffmpeg未安装,请安装 ffmpeg 或者 配置ffmpeg_path'));
|
|
76
|
+
/** 检查文件是否存在 */
|
|
77
|
+
if (!fs.existsSync(ffmpeg_path))
|
|
78
|
+
return logger.error(logger.red('ffmpeg_path配置错误,请检查文件是否存在'));
|
|
79
|
+
this.ffmpeg = `"${ffmpeg_path}"`;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* 处理URL
|
|
84
|
+
* @param url - URL
|
|
85
|
+
* @param exclude - 排除的URL
|
|
86
|
+
*/
|
|
87
|
+
getUrls(url, exclude = []) {
|
|
88
|
+
/** 中文不符合url规范 */
|
|
89
|
+
url = url.replace(/[\u4e00-\u9fa5]/g, '|');
|
|
90
|
+
const urls = GetUrls.getUrls(url, {
|
|
91
|
+
exclude,
|
|
92
|
+
/** 去除 WWW */
|
|
93
|
+
stripWWW: false,
|
|
94
|
+
/** 规范化协议 */
|
|
95
|
+
normalizeProtocol: false,
|
|
96
|
+
/** 移除查询参数 */
|
|
97
|
+
removeQueryParameters: false,
|
|
98
|
+
/** 移除唯一斜杠 */
|
|
99
|
+
removeSingleSlash: false,
|
|
100
|
+
/** 查询参数排序 */
|
|
101
|
+
sortQueryParameters: false,
|
|
102
|
+
/** 去除认证信息 */
|
|
103
|
+
stripAuthentication: false,
|
|
104
|
+
/** 去除文本片段 */
|
|
105
|
+
stripTextFragment: false,
|
|
106
|
+
/** 移除末尾斜杠 */
|
|
107
|
+
removeTrailingSlash: false,
|
|
108
|
+
/** 不进行标准处理url */
|
|
109
|
+
normalize: false,
|
|
110
|
+
});
|
|
111
|
+
return urls;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* 传入URL数组,转为二维码base64
|
|
115
|
+
* @param urls - URL数组
|
|
116
|
+
*/
|
|
117
|
+
async getQrCode(urls) {
|
|
118
|
+
const list = await Promise.all(urls.map(url => qrcode.toDataURL(url)));
|
|
119
|
+
/** 转为jimp图像对象 */
|
|
120
|
+
const Jimps = await Promise.all(list.map(url => {
|
|
121
|
+
return Jimp.read(Buffer.from(url.split(',')[1], 'base64'));
|
|
122
|
+
}));
|
|
123
|
+
/** 获取最长的宽高 */
|
|
124
|
+
const QRwidth = lodash.max(Jimps.map(j => j.bitmap.width)) || Jimps[0].bitmap.width;
|
|
125
|
+
const Qrheight = lodash.sum(Jimps.map(j => j.bitmap.height));
|
|
126
|
+
/** 空白区域高度 */
|
|
127
|
+
const padding = 0;
|
|
128
|
+
/** 二维码图像加上文字 */
|
|
129
|
+
const qrImagesWithText = await Promise.all(Jimps.map(async (qrImage) => {
|
|
130
|
+
/** 增加空白区域 */
|
|
131
|
+
const newImage = new Jimp(QRwidth, Qrheight + padding, 0xffffffff);
|
|
132
|
+
/** 将二维码图像放在空白区域下方 */
|
|
133
|
+
newImage.composite(qrImage, 0, padding);
|
|
134
|
+
return newImage;
|
|
135
|
+
}));
|
|
136
|
+
/** 动态计算每行二维码的数量 */
|
|
137
|
+
const qrPerRow = Math.ceil(Math.sqrt(urls.length));
|
|
138
|
+
/** 计算总行数和合并后图像的总宽度和总高度 */
|
|
139
|
+
const totalRows = Math.ceil(qrImagesWithText.length / qrPerRow);
|
|
140
|
+
const combinedWidth = QRwidth * qrPerRow;
|
|
141
|
+
const combinedHeight = (Qrheight + padding) * totalRows;
|
|
142
|
+
/** 创建合并后的图像对象 */
|
|
143
|
+
const combinedQRImage = new Jimp(combinedWidth, combinedHeight, 0xffffffff);
|
|
144
|
+
/** 将二维码图像合并到合并后的图像对象中 */
|
|
145
|
+
qrImagesWithText.forEach((qrImage, index) => {
|
|
146
|
+
const x = (index % qrPerRow) * QRwidth;
|
|
147
|
+
const y = Math.floor(index / qrPerRow) * (Qrheight + padding);
|
|
148
|
+
combinedQRImage.composite(qrImage, x, y);
|
|
149
|
+
});
|
|
150
|
+
/** 转为base64 */
|
|
151
|
+
const base64 = await combinedQRImage.getBase64Async(Jimp.MIME_PNG);
|
|
152
|
+
/** 宽高 */
|
|
153
|
+
const width = combinedQRImage.bitmap.width;
|
|
154
|
+
const height = combinedQRImage.bitmap.height;
|
|
155
|
+
return {
|
|
156
|
+
base64,
|
|
157
|
+
width,
|
|
158
|
+
height,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* 传入文本 返回处理后的文本、图片
|
|
163
|
+
* @param text - 文本
|
|
164
|
+
*/
|
|
165
|
+
async getQQBotText(text) {
|
|
166
|
+
const urls = this.getUrls(text);
|
|
167
|
+
const data = await this.getQrCode(urls);
|
|
168
|
+
/** 使用for循环来替换字符串 保证一致性 */
|
|
169
|
+
for (const url of urls) {
|
|
170
|
+
text = text.replace(url, '[请扫码查看]');
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
text,
|
|
174
|
+
data: urls.length ? data : undefined,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* 传入文本 处理文本 返回的按钮为多行按钮
|
|
179
|
+
* @param text - 文本
|
|
180
|
+
*/
|
|
181
|
+
getQQBotButton(text) {
|
|
182
|
+
const urls = this.getUrls(text);
|
|
183
|
+
const data = [];
|
|
184
|
+
/** 使用for循环来替换字符串 保证一致性 */
|
|
185
|
+
for (const url of urls) {
|
|
186
|
+
text = text.replace(url, '[请点击按钮查看]');
|
|
187
|
+
data.push(segment.button({ text: url, link: url }));
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
text,
|
|
191
|
+
data,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
export const common = new Common();
|