@satorijs/adapter-lark 3.6.0 → 3.6.2
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/lib/http.d.ts +5 -5
- package/lib/index.cjs +265 -103
- package/lib/index.cjs.map +2 -2
- package/lib/message.d.ts +9 -9
- package/lib/types/api.d.ts +2 -0
- package/lib/types/message/content.d.ts +386 -22
- package/lib/types/message/index.d.ts +18 -16
- package/lib/utils.d.ts +3 -3
- package/package.json +3 -3
- package/src/http.ts +6 -6
- package/src/message.ts +261 -114
- package/src/types/api.ts +2 -0
- package/src/types/message/content.ts +477 -42
- package/src/types/message/index.ts +18 -19
- package/src/utils.ts +21 -6
package/src/message.ts
CHANGED
|
@@ -1,28 +1,27 @@
|
|
|
1
|
-
import { Context, h, MessageEncoder } from '@satorijs/core'
|
|
1
|
+
import { Context, Dict, h, MessageEncoder } from '@satorijs/core'
|
|
2
2
|
import { LarkBot } from './bot'
|
|
3
|
-
import { BaseResponse, Lark, MessageContent
|
|
3
|
+
import { BaseResponse, Lark, MessageContent } from './types'
|
|
4
4
|
import { extractIdType } from './utils'
|
|
5
5
|
|
|
6
|
-
export interface Addition {
|
|
7
|
-
file: MessageContent.MediaContents
|
|
8
|
-
type: MessageType
|
|
9
|
-
}
|
|
10
|
-
|
|
11
6
|
export class LarkMessageEncoder<C extends Context = Context> extends MessageEncoder<C, LarkBot<C>> {
|
|
12
|
-
private quote:
|
|
13
|
-
private
|
|
14
|
-
private
|
|
15
|
-
|
|
16
|
-
private
|
|
7
|
+
private quote: Dict | undefined
|
|
8
|
+
private textContent = ''
|
|
9
|
+
private richContent: MessageContent.RichText.Paragraph[] = []
|
|
10
|
+
private card: MessageContent.Card | undefined
|
|
11
|
+
private noteElements: MessageContent.Card.NoteElement.InnerElement[] | undefined
|
|
12
|
+
private actionElements: MessageContent.Card.Element[] = []
|
|
17
13
|
|
|
18
14
|
async post(data?: any) {
|
|
19
15
|
try {
|
|
20
16
|
let resp: BaseResponse & { data?: Lark.Message }
|
|
21
|
-
if (this.quote) {
|
|
22
|
-
resp = await this.bot.internal.replyImMessage(this.quote,
|
|
17
|
+
if (this.quote?.id) {
|
|
18
|
+
resp = await this.bot.internal.replyImMessage(this.quote.id, {
|
|
19
|
+
...data,
|
|
20
|
+
reply_in_thread: this.quote.replyInThread,
|
|
21
|
+
})
|
|
23
22
|
} else {
|
|
24
23
|
data.receive_id = this.channelId
|
|
25
|
-
resp = await this.bot.internal
|
|
24
|
+
resp = await this.bot.internal.createImMessage(data, {
|
|
26
25
|
receive_id_type: extractIdType(this.channelId),
|
|
27
26
|
})
|
|
28
27
|
}
|
|
@@ -46,131 +45,279 @@ export class LarkMessageEncoder<C extends Context = Context> extends MessageEnco
|
|
|
46
45
|
}
|
|
47
46
|
}
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
if (this.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (this.addition) {
|
|
54
|
-
message = {
|
|
55
|
-
...message,
|
|
56
|
-
...this.addition.file,
|
|
57
|
-
}
|
|
48
|
+
private flushText(button = false) {
|
|
49
|
+
if ((this.textContent || !button) && this.actionElements.length) {
|
|
50
|
+
this.card!.elements.push({ tag: 'action', actions: this.actionElements, layout: 'flow' })
|
|
51
|
+
this.actionElements = []
|
|
58
52
|
}
|
|
59
|
-
if (this.
|
|
60
|
-
|
|
53
|
+
if (this.textContent) {
|
|
54
|
+
this.richContent.push([{ tag: 'md', text: this.textContent }])
|
|
55
|
+
if (this.noteElements) {
|
|
56
|
+
this.noteElements.push({ tag: 'plain_text', content: this.textContent })
|
|
57
|
+
} else if (this.card) {
|
|
58
|
+
this.card.elements.push({ tag: 'markdown', content: this.textContent })
|
|
59
|
+
}
|
|
60
|
+
this.textContent = ''
|
|
61
61
|
}
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async flush() {
|
|
65
|
+
this.flushText()
|
|
66
|
+
if (!this.card && !this.richContent.length) return
|
|
67
|
+
|
|
68
|
+
if (this.card) {
|
|
69
|
+
this.bot.logger.debug('card', JSON.stringify(this.card.elements))
|
|
70
|
+
await this.post({
|
|
71
|
+
msg_type: 'interactive',
|
|
72
|
+
content: JSON.stringify({
|
|
73
|
+
elements: this.card.elements,
|
|
74
|
+
}),
|
|
75
|
+
})
|
|
76
|
+
} else {
|
|
77
|
+
await this.post({
|
|
78
|
+
msg_type: 'post',
|
|
79
|
+
content: JSON.stringify({
|
|
80
|
+
zh_cn: {
|
|
81
|
+
content: this.richContent,
|
|
82
|
+
},
|
|
83
|
+
}),
|
|
84
|
+
})
|
|
64
85
|
}
|
|
65
|
-
await this.post({
|
|
66
|
-
msg_type: this.richText ? 'post' : this.addition ? this.addition.type : 'text',
|
|
67
|
-
content: JSON.stringify(message),
|
|
68
|
-
})
|
|
69
86
|
|
|
70
87
|
// reset cached content
|
|
71
88
|
this.quote = undefined
|
|
72
|
-
this.
|
|
73
|
-
this.
|
|
74
|
-
this.
|
|
89
|
+
this.textContent = ''
|
|
90
|
+
this.richContent = []
|
|
91
|
+
this.card = undefined
|
|
75
92
|
}
|
|
76
93
|
|
|
77
|
-
async
|
|
94
|
+
async createImage(url: string) {
|
|
95
|
+
const { filename, type, data } = await this.bot.assetsQuester.file(url)
|
|
78
96
|
const payload = new FormData()
|
|
97
|
+
payload.append('image', new Blob([data], { type }), filename)
|
|
98
|
+
payload.append('image_type', 'message')
|
|
99
|
+
const { data: { image_key } } = await this.bot.internal.createImImage(payload)
|
|
100
|
+
return image_key
|
|
101
|
+
}
|
|
79
102
|
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
payload
|
|
103
|
+
async sendFile(_type: 'video' | 'audio' | 'file', attrs: any) {
|
|
104
|
+
const url = attrs.src || attrs.url
|
|
105
|
+
const payload = new FormData()
|
|
106
|
+
const { filename, type, data } = await this.bot.assetsQuester.file(url)
|
|
107
|
+
payload.append('file', new Blob([data], { type }), filename)
|
|
108
|
+
payload.append('file_name', filename)
|
|
83
109
|
|
|
84
|
-
if (
|
|
85
|
-
payload.append('
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
110
|
+
if (attrs.duration) {
|
|
111
|
+
payload.append('duration', attrs.duration)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (_type === 'audio') {
|
|
115
|
+
// FIXME: only support opus
|
|
116
|
+
payload.append('file_type', 'opus')
|
|
117
|
+
} else if (_type === 'video') {
|
|
118
|
+
// FIXME: only support mp4
|
|
119
|
+
payload.append('file_type', 'mp4')
|
|
93
120
|
} else {
|
|
94
|
-
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
payload.append('file_type', 'opus')
|
|
98
|
-
msgType = 'audio'
|
|
99
|
-
} else if (type === 'video') {
|
|
100
|
-
// FIXME: only support mp4
|
|
101
|
-
payload.append('file_type', 'mp4')
|
|
102
|
-
msgType = 'media'
|
|
121
|
+
const ext = filename.split('.').pop()
|
|
122
|
+
if (['doc', 'xls', 'ppt', 'pdf'].includes(ext)) {
|
|
123
|
+
payload.append('file_type', ext)
|
|
103
124
|
} else {
|
|
104
|
-
|
|
105
|
-
if (['xls', 'ppt', 'pdf'].includes(ext)) {
|
|
106
|
-
payload.append('file_type', ext)
|
|
107
|
-
} else {
|
|
108
|
-
payload.append('file_type', 'stream')
|
|
109
|
-
}
|
|
125
|
+
payload.append('file_type', 'stream')
|
|
110
126
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const { data: { file_key } } = await this.bot.internal.createImFile(payload)
|
|
130
|
+
await this.post({
|
|
131
|
+
msg_type: _type === 'video' ? 'media' : _type,
|
|
132
|
+
content: JSON.stringify({ file_key }),
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private createBehavior(attrs: Dict) {
|
|
137
|
+
const behaviors: MessageContent.Card.ActionBehavior[] = []
|
|
138
|
+
if (attrs.type === 'link') {
|
|
139
|
+
behaviors.push({
|
|
140
|
+
type: 'open_url',
|
|
141
|
+
default_url: attrs.href,
|
|
142
|
+
})
|
|
143
|
+
} else if (attrs.type === 'input') {
|
|
144
|
+
behaviors.push({
|
|
145
|
+
type: 'callback',
|
|
146
|
+
value: {
|
|
147
|
+
_satori_type: 'command',
|
|
148
|
+
content: attrs.text,
|
|
117
149
|
},
|
|
118
|
-
}
|
|
150
|
+
})
|
|
151
|
+
} else if (attrs.type === 'action') {
|
|
152
|
+
// TODO
|
|
119
153
|
}
|
|
154
|
+
return behaviors.length ? behaviors : undefined
|
|
120
155
|
}
|
|
121
156
|
|
|
122
157
|
async visit(element: h) {
|
|
123
158
|
const { type, attrs, children } = element
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
this.content += `<at user_id="all">${attrs.name ?? '所有人'}</at>`
|
|
132
|
-
} else {
|
|
133
|
-
this.content += `<at user_id="${attrs.id}">${attrs.name}</at>`
|
|
134
|
-
}
|
|
135
|
-
break
|
|
159
|
+
if (type === 'text') {
|
|
160
|
+
this.textContent += attrs.content
|
|
161
|
+
} else if (type === 'at') {
|
|
162
|
+
if (attrs.type === 'all') {
|
|
163
|
+
this.textContent += `<at user_id="all">${attrs.name ?? '所有人'}</at>`
|
|
164
|
+
} else {
|
|
165
|
+
this.textContent += `<at user_id="${attrs.id}">${attrs.name}</at>`
|
|
136
166
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
167
|
+
} else if (type === 'a') {
|
|
168
|
+
await this.render(children)
|
|
169
|
+
if (attrs.href) this.textContent += ` (${attrs.href})`
|
|
170
|
+
} else if (type === 'p') {
|
|
171
|
+
if (!this.textContent.endsWith('\n')) this.textContent += '\n'
|
|
172
|
+
await this.render(children)
|
|
173
|
+
if (!this.textContent.endsWith('\n')) this.textContent += '\n'
|
|
174
|
+
} else if (type === 'br') {
|
|
175
|
+
this.textContent += '\n'
|
|
176
|
+
} else if (type === 'sharp') {
|
|
177
|
+
// platform does not support sharp
|
|
178
|
+
} else if (type === 'quote') {
|
|
179
|
+
await this.flush()
|
|
180
|
+
this.quote = attrs
|
|
181
|
+
} else if (type === 'img' || type === 'image') {
|
|
182
|
+
const image_key = await this.createImage(attrs.src || attrs.url)
|
|
183
|
+
this.textContent += ``
|
|
184
|
+
this.flushText()
|
|
185
|
+
this.richContent.push([{ tag: 'img', image_key }])
|
|
186
|
+
} else if (['video', 'audio', 'file'].includes(type)) {
|
|
187
|
+
await this.flush()
|
|
188
|
+
await this.sendFile(type as any, attrs)
|
|
189
|
+
} else if (type === 'figure' || type === 'message') {
|
|
190
|
+
await this.flush()
|
|
191
|
+
await this.render(children, true)
|
|
192
|
+
} else if (type === 'hr') {
|
|
193
|
+
this.flushText()
|
|
194
|
+
this.richContent.push([{ tag: 'hr' }])
|
|
195
|
+
this.card?.elements.push({ tag: 'hr' })
|
|
196
|
+
} else if (type === 'form') {
|
|
197
|
+
this.flushText()
|
|
198
|
+
const length = this.card?.elements.length
|
|
199
|
+
await this.render(children)
|
|
200
|
+
if (this.card?.elements.length > length) {
|
|
201
|
+
const elements = this.card?.elements.slice(length)
|
|
202
|
+
this.card.elements.push({
|
|
203
|
+
tag: 'form',
|
|
204
|
+
name: attrs.name || 'Form',
|
|
205
|
+
elements,
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
} else if (type === 'input') {
|
|
209
|
+
this.flushText()
|
|
210
|
+
this.card?.elements.push({
|
|
211
|
+
tag: 'action',
|
|
212
|
+
actions: [{
|
|
213
|
+
tag: 'input',
|
|
214
|
+
name: attrs.name,
|
|
215
|
+
width: attrs.width,
|
|
216
|
+
label: attrs.label && {
|
|
217
|
+
tag: 'plain_text',
|
|
218
|
+
content: attrs.label,
|
|
219
|
+
},
|
|
220
|
+
placeholder: attrs.placeholder && {
|
|
221
|
+
tag: 'plain_text',
|
|
222
|
+
content: attrs.placeholder,
|
|
223
|
+
},
|
|
224
|
+
behaviors: this.createBehavior(attrs),
|
|
225
|
+
}],
|
|
226
|
+
})
|
|
227
|
+
} else if (type === 'button') {
|
|
228
|
+
this.card ??= { elements: [] }
|
|
229
|
+
this.flushText(true)
|
|
230
|
+
await this.render(children)
|
|
231
|
+
this.actionElements.push({
|
|
232
|
+
tag: 'button',
|
|
233
|
+
text: {
|
|
234
|
+
tag: 'plain_text',
|
|
235
|
+
content: this.textContent,
|
|
236
|
+
},
|
|
237
|
+
disabled: attrs.disabled,
|
|
238
|
+
behaviors: this.createBehavior(attrs),
|
|
239
|
+
})
|
|
240
|
+
this.textContent = ''
|
|
241
|
+
} else if (type === 'button-group') {
|
|
242
|
+
this.flushText()
|
|
243
|
+
await this.render(children)
|
|
244
|
+
this.flushText()
|
|
245
|
+
} else if (type.startsWith('lark:') || type.startsWith('feishu:')) {
|
|
246
|
+
const tag = type.slice(type.split(':', 1)[0].length + 1)
|
|
247
|
+
if (tag === 'share-chat') {
|
|
248
|
+
await this.flush()
|
|
249
|
+
await this.post({
|
|
250
|
+
msg_type: 'share_chat',
|
|
251
|
+
content: JSON.stringify({ chat_id: attrs.chatId }),
|
|
252
|
+
})
|
|
253
|
+
} else if (tag === 'share-user') {
|
|
254
|
+
await this.flush()
|
|
255
|
+
await this.post({
|
|
256
|
+
msg_type: 'share_user',
|
|
257
|
+
content: JSON.stringify({ user_id: attrs.userId }),
|
|
258
|
+
})
|
|
259
|
+
} else if (tag === 'system') {
|
|
260
|
+
await this.flush()
|
|
143
261
|
await this.render(children)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
262
|
+
await this.post({
|
|
263
|
+
msg_type: 'system',
|
|
264
|
+
content: JSON.stringify({
|
|
265
|
+
type: 'divider',
|
|
266
|
+
params: { divider_text: { text: this.textContent } },
|
|
267
|
+
options: { need_rollup: attrs.needRollup },
|
|
268
|
+
}),
|
|
269
|
+
})
|
|
270
|
+
this.textContent = ''
|
|
271
|
+
} else if (tag === 'card') {
|
|
153
272
|
await this.flush()
|
|
154
|
-
this.
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
273
|
+
this.card = {
|
|
274
|
+
elements: [],
|
|
275
|
+
header: attrs.title && {
|
|
276
|
+
template: attrs.color,
|
|
277
|
+
ud_icon: attrs.icon && {
|
|
278
|
+
tag: 'standard_icon',
|
|
279
|
+
token: attrs.icon,
|
|
280
|
+
},
|
|
281
|
+
title: {
|
|
282
|
+
tag: 'plain_text',
|
|
283
|
+
content: attrs.title,
|
|
284
|
+
},
|
|
285
|
+
subtitle: attrs.subtitle && {
|
|
286
|
+
tag: 'plain_text',
|
|
287
|
+
content: attrs.subtitle,
|
|
288
|
+
},
|
|
289
|
+
},
|
|
165
290
|
}
|
|
166
|
-
break
|
|
167
|
-
case 'figure': // FIXME: treat as message element for now
|
|
168
|
-
case 'message':
|
|
169
|
-
await this.flush()
|
|
170
291
|
await this.render(children, true)
|
|
171
|
-
|
|
172
|
-
|
|
292
|
+
} else if (tag === 'div') {
|
|
293
|
+
this.flushText()
|
|
173
294
|
await this.render(children)
|
|
295
|
+
this.card?.elements.push({
|
|
296
|
+
tag: 'markdown',
|
|
297
|
+
text_align: attrs.align,
|
|
298
|
+
text_size: attrs.size,
|
|
299
|
+
content: this.textContent,
|
|
300
|
+
})
|
|
301
|
+
this.textContent = ''
|
|
302
|
+
} else if (tag === 'note') {
|
|
303
|
+
this.flushText()
|
|
304
|
+
this.noteElements = []
|
|
305
|
+
await this.render(children)
|
|
306
|
+
this.flushText()
|
|
307
|
+
this.card?.elements.push({
|
|
308
|
+
tag: 'note',
|
|
309
|
+
elements: this.noteElements,
|
|
310
|
+
})
|
|
311
|
+
this.noteElements = undefined
|
|
312
|
+
} else if (tag === 'icon') {
|
|
313
|
+
this.flushText()
|
|
314
|
+
this.noteElements?.push({
|
|
315
|
+
tag: 'standard_icon',
|
|
316
|
+
token: attrs.token,
|
|
317
|
+
})
|
|
318
|
+
}
|
|
319
|
+
} else {
|
|
320
|
+
await this.render(children)
|
|
174
321
|
}
|
|
175
322
|
}
|
|
176
323
|
}
|
package/src/types/api.ts
CHANGED
|
@@ -16681,6 +16681,8 @@ export interface ReplyImMessageRequest {
|
|
|
16681
16681
|
content: string
|
|
16682
16682
|
/** 消息类型,包括:text、post、image、file、audio、media、sticker、interactive、share_card、share_user */
|
|
16683
16683
|
msg_type: string
|
|
16684
|
+
/** 是否以话题形式回复。取值为 true 时将以话题形式回复。注意:如果要回复的消息已经是话题形式的消息,则默认以话题形式进行回复。 */
|
|
16685
|
+
reply_in_thread?: boolean
|
|
16684
16686
|
/** 由开发者生成的唯一字符串序列,用于回复消息请求去重;持有相同uuid的请求1小时内至多成功执行一次 */
|
|
16685
16687
|
uuid?: string
|
|
16686
16688
|
}
|