@satorijs/adapter-lark 3.6.1 → 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.
@@ -112,9 +112,10 @@ export declare namespace MessageContent {
112
112
  type Paragraph = InlineElement[] | [BlockElement];
113
113
  }
114
114
  interface Card {
115
- config: Card.Config;
115
+ config?: Card.Config;
116
116
  card_link?: Card.URLs;
117
- elements?: Card.Element[];
117
+ header?: Card.Header;
118
+ elements: Card.Element[];
118
119
  }
119
120
  namespace Card {
120
121
  /** @see https://open.larksuite.com/document/common-capabilities/message-card/getting-started/card-structure/card-configuration */
@@ -174,16 +175,25 @@ export declare namespace MessageContent {
174
175
  namespace TextTagElement {
175
176
  type Color = 'neutral' | 'blue' | 'torqoise' | 'lime' | 'orange' | 'violet' | 'indigo' | 'wathet' | 'green' | 'yellow' | 'red' | 'purple' | 'carmine';
176
177
  }
177
- interface ImageElement extends BaseElement<'image'> {
178
+ interface BaseImageElement extends BaseElement<'image'> {
178
179
  img_key: string;
179
180
  alt?: PlainTextElement;
181
+ }
182
+ interface ImageElement extends BaseImageElement {
180
183
  title?: PlainTextElement;
184
+ transparent?: string;
185
+ preview?: boolean;
186
+ corner_radius?: string;
187
+ scale_type?: 'crop_center' | 'fit_horizontal' | 'crop_top';
188
+ size?: 'large' | 'medium' | 'small' | 'tiny' | 'stretch_without_padding' | 'stretch' | string;
189
+ /** @deprecated */
181
190
  custom_width?: number;
191
+ /** @deprecated */
182
192
  compact_width?: boolean;
193
+ /** @deprecated */
183
194
  mode?: 'crop_center' | 'fit_horizontal' | 'large' | 'medium' | 'small' | 'tiny';
184
- preview?: boolean;
185
195
  }
186
- interface HorizontalRuleElement extends BaseElement<'hr'> {
196
+ interface HRElement extends BaseElement<'hr'> {
187
197
  }
188
198
  interface DivElement extends BaseElement<'div'> {
189
199
  text?: DivPlainTextElement;
@@ -194,13 +204,70 @@ export declare namespace MessageContent {
194
204
  text_align?: TextAlign;
195
205
  href?: Record<string, URLs>;
196
206
  }
197
- interface HorizontalRuleElement extends BaseElement<'hr'> {
198
- }
199
- interface ActionModule extends BaseElement<'action'> {
200
- actions: ActionElement[];
207
+ interface PersonElement extends BaseElement<'person'> {
208
+ user_id: string;
209
+ size?: 'large' | 'medium' | 'small' | 'extra_small';
210
+ }
211
+ interface PersonListElement extends BaseElement<'person_list'> {
212
+ persons: {
213
+ id: string;
214
+ }[];
215
+ size?: 'large' | 'medium' | 'small' | 'extra_small';
216
+ show_name?: boolean;
217
+ show_avatar?: boolean;
218
+ }
219
+ interface ChartElement extends BaseElement<'chart'> {
220
+ chart_spec: {};
221
+ aspect_ratio?: '1:1' | '2:1' | '4:3' | '16:9';
222
+ color_theme?: 'brand' | 'rainbow' | 'complementary' | 'converse' | 'primary';
223
+ preview?: boolean;
224
+ height?: 'auto' | string;
225
+ }
226
+ interface TableElement extends BaseElement<'table'> {
227
+ page_size?: number;
228
+ row_height?: 'low' | 'medium' | 'high' | string;
229
+ header_style?: TableElement.HeaderStyle;
230
+ columns: TableElement.Column[];
231
+ rows: object[];
232
+ }
233
+ namespace TableElement {
234
+ interface HeaderStyle {
235
+ text_align?: TextAlign;
236
+ text_size?: TextSize;
237
+ background_style?: 'grey' | 'none';
238
+ text_color?: 'default' | 'grey';
239
+ bold?: boolean;
240
+ lines?: number;
241
+ }
242
+ interface Column {
243
+ name: string;
244
+ display_name?: string;
245
+ width?: 'auto' | string;
246
+ horizontal_align?: 'left' | 'center' | 'right';
247
+ data_type?: 'text' | 'lark_md' | 'options' | 'number' | 'persons' | 'date' | 'markdown';
248
+ format?: {
249
+ percision?: number;
250
+ symbol?: string;
251
+ separator?: string;
252
+ };
253
+ date_format?: string;
254
+ }
255
+ }
256
+ interface NoteElement extends BaseElement<'note'> {
257
+ elements: NoteElement.InnerElement[];
258
+ }
259
+ namespace NoteElement {
260
+ type InnerElement = IconElement | PlainTextElement | BaseImageElement;
261
+ }
262
+ interface FormElement extends BaseElement<'form'> {
263
+ name: string;
264
+ elements: Element[];
265
+ confirm?: ConfirmElement;
266
+ }
267
+ interface ActionElement extends BaseElement<'action'> {
268
+ actions: Element[];
201
269
  layout?: 'bisected' | 'trisection' | 'flow';
202
270
  }
203
- type ActionElement = ButtonElement;
204
271
  type ActionBehavior = OpenURLBehavior | CallbackBehavior;
205
272
  interface OpenURLBehavior {
206
273
  type: 'open_url';
@@ -213,30 +280,148 @@ export declare namespace MessageContent {
213
280
  type: 'callback';
214
281
  value: Record<string, string>;
215
282
  }
216
- interface ButtonElement extends BaseElement<'button'> {
283
+ interface BaseButtonElement extends BaseElement<'button'> {
217
284
  text: PlainTextElement;
218
- type?: ButtonElement.Type;
219
285
  size?: ButtonElement.Size;
220
- width?: ButtonElement.Width;
221
286
  icon?: IconElement;
222
- hover_tips?: PlainTextElement;
223
287
  disabled?: boolean;
288
+ behaviors?: ActionBehavior[];
289
+ }
290
+ interface ButtonElement extends BaseButtonElement {
291
+ type?: ButtonElement.Type;
292
+ width?: ButtonElement.Width;
293
+ hover_tips?: PlainTextElement;
224
294
  disabled_tips?: PlainTextElement;
225
295
  confirm?: {
226
296
  title: PlainTextElement;
227
297
  text: PlainTextElement;
228
298
  };
229
- behaviors?: ActionBehavior[];
230
299
  name?: string;
231
300
  required?: boolean;
232
301
  action_type?: 'link' | 'request' | 'multi' | 'form_submit' | 'form_reset';
233
302
  }
303
+ interface ConfirmElement {
304
+ title: PlainTextElement;
305
+ text: PlainTextElement;
306
+ }
307
+ interface InputElement extends BaseElement<'input'> {
308
+ name: string;
309
+ required?: boolean;
310
+ placeholder?: PlainTextElement;
311
+ default_value?: string;
312
+ disabled?: boolean;
313
+ width?: 'default' | 'fill' | string;
314
+ max_length?: number;
315
+ input_type?: 'text' | 'multiline_text' | 'password';
316
+ show_icon?: boolean;
317
+ rows?: number;
318
+ auto_resize?: boolean;
319
+ max_rows?: number;
320
+ label?: PlainTextElement;
321
+ label_position?: 'top' | 'left';
322
+ value?: string | object;
323
+ behaviors?: ActionBehavior[];
324
+ confirm?: ConfirmElement;
325
+ fallback?: {
326
+ tag?: string;
327
+ text?: PlainTextElement;
328
+ };
329
+ }
330
+ interface OverflowElement extends BaseElement<'overflow'> {
331
+ width?: 'default' | 'fill' | string;
332
+ options: OverflowElement.Option[];
333
+ value?: object;
334
+ confirm?: ConfirmElement;
335
+ }
336
+ namespace OverflowElement {
337
+ interface Option {
338
+ text?: PlainTextElement;
339
+ multi_url?: URLs;
340
+ value?: string;
341
+ }
342
+ }
343
+ interface BaseSelectElement<T extends string = string> extends BaseElement<T> {
344
+ type?: 'default' | 'text';
345
+ name?: string;
346
+ required?: boolean;
347
+ disabled?: boolean;
348
+ placeholder?: PlainTextElement;
349
+ width?: 'default' | 'fill' | string;
350
+ confirm?: ConfirmElement;
351
+ }
352
+ interface OptionElement {
353
+ text: PlainTextElement;
354
+ icon?: IconElement;
355
+ value: string;
356
+ }
357
+ interface SelectElement extends BaseSelectElement<'select_static'> {
358
+ options: OptionElement[];
359
+ initial_option?: string;
360
+ }
361
+ interface MultiSelectElement extends BaseSelectElement<'multi_select_static'> {
362
+ options: OptionElement[];
363
+ selected_values?: string[];
364
+ }
365
+ interface SelectPersonElement extends BaseSelectElement<'select_person'> {
366
+ options?: {
367
+ value: string;
368
+ }[];
369
+ }
370
+ interface MultiSelectPersonElement extends BaseSelectElement<'multi_select_person'> {
371
+ options?: {
372
+ value: string;
373
+ }[];
374
+ selected_values?: string[];
375
+ }
376
+ interface DatePickerElement extends BaseSelectElement<'date_picker'> {
377
+ initial_date?: string;
378
+ value?: object;
379
+ }
380
+ interface TimePickerElement extends BaseSelectElement<'picker_time'> {
381
+ initial_time?: string;
382
+ value?: object;
383
+ }
384
+ interface DateTimePickerElement extends BaseSelectElement<'picker_datetime'> {
385
+ initial_datetime?: string;
386
+ value?: object;
387
+ }
388
+ interface CheckerElement extends BaseElement<'checker'> {
389
+ name?: string;
390
+ checked?: boolean;
391
+ disabled?: boolean;
392
+ text?: CheckerElement.TextElement;
393
+ overall_checkable?: boolean;
394
+ button_area?: {
395
+ pc_display_rule?: 'always' | 'on_hover';
396
+ buttons?: CheckerElement.ButtonElement[];
397
+ };
398
+ checked_style?: {
399
+ show_strikethrough?: boolean;
400
+ opacity?: number;
401
+ };
402
+ margin?: string;
403
+ padding?: string;
404
+ confirm?: ConfirmElement;
405
+ behaviors?: ActionBehavior[];
406
+ hover_tips?: PlainTextElement;
407
+ disable_tips?: PlainTextElement;
408
+ }
409
+ namespace CheckerElement {
410
+ interface TextElement extends PlainTextElement {
411
+ text_size?: 'normal' | 'heading' | 'notation';
412
+ text_color?: string;
413
+ text_align?: TextAlign;
414
+ }
415
+ interface ButtonElement extends BaseButtonElement {
416
+ type: 'text' | 'primary_text' | 'danger_text';
417
+ }
418
+ }
234
419
  namespace ButtonElement {
235
420
  type Size = 'tiny' | 'small' | 'medium' | 'large';
236
421
  type Width = 'default' | 'fill' | string;
237
422
  type Type = 'default' | 'primary' | 'danger' | 'text' | 'primary_text' | 'danger_text' | 'primary_filled' | 'danger_filled' | 'laser';
238
423
  }
239
- type Element = DivElement | MarkdownElement | HorizontalRuleElement | ActionModule;
424
+ type Element = DivElement | MarkdownElement | HRElement | ActionElement | NoteElement | ChartElement | TableElement | ImageElement | FormElement | InputElement | ButtonElement;
240
425
  }
241
426
  interface Template {
242
427
  type: 'template';
@@ -74,5 +74,21 @@ declare module '../event' {
74
74
  open_chat_id: string;
75
75
  };
76
76
  };
77
+ /**
78
+ * 机器人自定义菜单事件
79
+ * @see https://open.feishu.cn/document/client-docs/bot-v3/events/menu
80
+ */
81
+ 'application.bot.menu_v6': {
82
+ operator: {
83
+ operator_name: string;
84
+ operator_id: {
85
+ union_id: string;
86
+ user_id: string;
87
+ open_id: string;
88
+ };
89
+ };
90
+ event_key: string;
91
+ timestamp: number;
92
+ };
77
93
  }
78
94
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@satorijs/adapter-lark",
3
3
  "description": "Lark (飞书) Adapter for Satorijs",
4
- "version": "3.6.1",
4
+ "version": "3.6.2",
5
5
  "type": "module",
6
6
  "main": "lib/index.cjs",
7
7
  "types": "lib/index.d.ts",
@@ -35,10 +35,10 @@
35
35
  ],
36
36
  "devDependencies": {
37
37
  "@cordisjs/plugin-server": "^0.2.4",
38
- "@satorijs/core": "^4.2.9",
38
+ "@satorijs/core": "^4.2.10",
39
39
  "cordis": "^3.18.1"
40
40
  },
41
41
  "peerDependencies": {
42
- "@satorijs/core": "^4.2.9"
42
+ "@satorijs/core": "^4.2.10"
43
43
  }
44
44
  }
package/src/message.ts CHANGED
@@ -1,23 +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
3
  import { BaseResponse, Lark, MessageContent } from './types'
4
4
  import { extractIdType } from './utils'
5
5
 
6
6
  export class LarkMessageEncoder<C extends Context = Context> extends MessageEncoder<C, LarkBot<C>> {
7
- private quote: string | undefined
7
+ private quote: Dict | undefined
8
8
  private textContent = ''
9
9
  private richContent: MessageContent.RichText.Paragraph[] = []
10
- private cardElements: MessageContent.Card.Element[] | undefined
11
- private actionElements: MessageContent.Card.ButtonElement[] = []
10
+ private card: MessageContent.Card | undefined
11
+ private noteElements: MessageContent.Card.NoteElement.InnerElement[] | undefined
12
+ private actionElements: MessageContent.Card.Element[] = []
12
13
 
13
14
  async post(data?: any) {
14
15
  try {
15
16
  let resp: BaseResponse & { data?: Lark.Message }
16
- if (this.quote) {
17
- resp = await this.bot.internal.replyImMessage(this.quote, data)
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
+ })
18
22
  } else {
19
23
  data.receive_id = this.channelId
20
- resp = await this.bot.internal?.createImMessage(data, {
24
+ resp = await this.bot.internal.createImMessage(data, {
21
25
  receive_id_type: extractIdType(this.channelId),
22
26
  })
23
27
  }
@@ -41,27 +45,32 @@ export class LarkMessageEncoder<C extends Context = Context> extends MessageEnco
41
45
  }
42
46
  }
43
47
 
44
- private flushText(flushAction = false) {
45
- if ((this.textContent || flushAction) && this.actionElements.length) {
46
- this.cardElements?.push({ tag: 'action', actions: this.actionElements })
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' })
47
51
  this.actionElements = []
48
52
  }
49
53
  if (this.textContent) {
50
54
  this.richContent.push([{ tag: 'md', text: this.textContent }])
51
- this.cardElements?.push({ tag: 'markdown', content: 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
+ }
52
60
  this.textContent = ''
53
61
  }
54
62
  }
55
63
 
56
64
  async flush() {
57
65
  this.flushText()
58
- if (!this.cardElements && !this.richContent.length) return
66
+ if (!this.card && !this.richContent.length) return
59
67
 
60
- if (this.cardElements) {
68
+ if (this.card) {
69
+ this.bot.logger.debug('card', JSON.stringify(this.card.elements))
61
70
  await this.post({
62
71
  msg_type: 'interactive',
63
72
  content: JSON.stringify({
64
- elements: this.cardElements,
73
+ elements: this.card.elements,
65
74
  }),
66
75
  })
67
76
  } else {
@@ -79,7 +88,7 @@ export class LarkMessageEncoder<C extends Context = Context> extends MessageEnco
79
88
  this.quote = undefined
80
89
  this.textContent = ''
81
90
  this.richContent = []
82
- this.cardElements = undefined
91
+ this.card = undefined
83
92
  }
84
93
 
85
94
  async createImage(url: string) {
@@ -124,6 +133,27 @@ export class LarkMessageEncoder<C extends Context = Context> extends MessageEnco
124
133
  })
125
134
  }
126
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,
149
+ },
150
+ })
151
+ } else if (attrs.type === 'action') {
152
+ // TODO
153
+ }
154
+ return behaviors.length ? behaviors : undefined
155
+ }
156
+
127
157
  async visit(element: h) {
128
158
  const { type, attrs, children } = element
129
159
  if (type === 'text') {
@@ -147,7 +177,7 @@ export class LarkMessageEncoder<C extends Context = Context> extends MessageEnco
147
177
  // platform does not support sharp
148
178
  } else if (type === 'quote') {
149
179
  await this.flush()
150
- this.quote = attrs.id
180
+ this.quote = attrs
151
181
  } else if (type === 'img' || type === 'image') {
152
182
  const image_key = await this.createImage(attrs.src || attrs.url)
153
183
  this.textContent += `![${attrs.alt ?? '图片'}](${image_key})`
@@ -162,26 +192,41 @@ export class LarkMessageEncoder<C extends Context = Context> extends MessageEnco
162
192
  } else if (type === 'hr') {
163
193
  this.flushText()
164
194
  this.richContent.push([{ tag: 'hr' }])
165
- this.cardElements?.push({ tag: 'hr' })
166
- } else if (type === 'button') {
195
+ this.card?.elements.push({ tag: 'hr' })
196
+ } else if (type === 'form') {
167
197
  this.flushText()
168
- const behaviors: MessageContent.Card.ActionBehavior[] = []
169
- if (attrs.type === 'link') {
170
- behaviors.push({
171
- type: 'open_url',
172
- default_url: attrs.href,
173
- })
174
- } else if (attrs.type === 'input') {
175
- behaviors.push({
176
- type: 'callback',
177
- value: {
178
- _satori_type: 'command',
179
- content: attrs.text,
180
- },
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,
181
206
  })
182
- } else if (attrs.type === 'action') {
183
- // TODO
184
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)
185
230
  await this.render(children)
186
231
  this.actionElements.push({
187
232
  tag: 'button',
@@ -189,13 +234,14 @@ export class LarkMessageEncoder<C extends Context = Context> extends MessageEnco
189
234
  tag: 'plain_text',
190
235
  content: this.textContent,
191
236
  },
192
- behaviors,
237
+ disabled: attrs.disabled,
238
+ behaviors: this.createBehavior(attrs),
193
239
  })
194
240
  this.textContent = ''
195
241
  } else if (type === 'button-group') {
196
- this.flushText(true)
242
+ this.flushText()
197
243
  await this.render(children)
198
- this.flushText(true)
244
+ this.flushText()
199
245
  } else if (type.startsWith('lark:') || type.startsWith('feishu:')) {
200
246
  const tag = type.slice(type.split(':', 1)[0].length + 1)
201
247
  if (tag === 'share-chat') {
@@ -224,18 +270,51 @@ export class LarkMessageEncoder<C extends Context = Context> extends MessageEnco
224
270
  this.textContent = ''
225
271
  } else if (tag === 'card') {
226
272
  await this.flush()
227
- this.cardElements = []
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
+ },
290
+ }
228
291
  await this.render(children, true)
229
292
  } else if (tag === 'div') {
230
293
  this.flushText()
231
294
  await this.render(children)
232
- this.cardElements?.push({
295
+ this.card?.elements.push({
233
296
  tag: 'markdown',
234
297
  text_align: attrs.align,
235
298
  text_size: attrs.size,
236
299
  content: this.textContent,
237
300
  })
238
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
+ })
239
318
  }
240
319
  } else {
241
320
  await this.render(children)
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
  }