From 6f91d202b9342b4de11aaa6b2fd702917a59236f Mon Sep 17 00:00:00 2001 From: Shigma Date: Thu, 31 Oct 2024 13:57:32 +0800 Subject: [PATCH] feat(lark): support input form / behavior --- adapters/lark/src/message.ts | 82 ++++++++--- adapters/lark/src/types/message/content.ts | 156 +++++++++++++++++++-- adapters/lark/src/utils.ts | 3 + 3 files changed, 209 insertions(+), 32 deletions(-) diff --git a/adapters/lark/src/message.ts b/adapters/lark/src/message.ts index 8924e287..ada13f4c 100644 --- a/adapters/lark/src/message.ts +++ b/adapters/lark/src/message.ts @@ -1,4 +1,4 @@ -import { Context, h, MessageEncoder } from '@satorijs/core' +import { Context, Dict, h, MessageEncoder } from '@satorijs/core' import { LarkBot } from './bot' import { BaseResponse, Lark, MessageContent } from './types' import { extractIdType } from './utils' @@ -9,7 +9,7 @@ export class LarkMessageEncoder extends MessageEnco private richContent: MessageContent.RichText.Paragraph[] = [] private card: MessageContent.Card | undefined private noteElements: MessageContent.Card.NoteElement.InnerElement[] | undefined - private actionElements: MessageContent.Card.ActionElement.InnerElement[] = [] + private actionElements: MessageContent.Card.Element[] = [] async post(data?: any) { try { @@ -44,7 +44,7 @@ export class LarkMessageEncoder extends MessageEnco private flushText(button = false) { if ((this.textContent || !button) && this.actionElements.length) { - this.card?.elements.push({ tag: 'action', actions: this.actionElements }) + this.card!.elements.push({ tag: 'action', actions: this.actionElements, layout: 'flow' }) this.actionElements = [] } if (this.textContent) { @@ -60,9 +60,10 @@ export class LarkMessageEncoder extends MessageEnco async flush() { this.flushText() - if (!this.card.elements && !this.richContent.length) return + if (!this.card && !this.richContent.length) return if (this.card) { + this.bot.logger.debug('card', JSON.stringify(this.card.elements)) await this.post({ msg_type: 'interactive', content: JSON.stringify({ @@ -129,6 +130,27 @@ export class LarkMessageEncoder extends MessageEnco }) } + private createBehavior(attrs: Dict) { + const behaviors: MessageContent.Card.ActionBehavior[] = [] + if (attrs.type === 'link') { + behaviors.push({ + type: 'open_url', + default_url: attrs.href, + }) + } else if (attrs.type === 'input') { + behaviors.push({ + type: 'callback', + value: { + _satori_type: 'command', + content: attrs.text, + }, + }) + } else if (attrs.type === 'action') { + // TODO + } + return behaviors.length ? behaviors : undefined + } + async visit(element: h) { const { type, attrs, children } = element if (type === 'text') { @@ -168,25 +190,40 @@ export class LarkMessageEncoder extends MessageEnco this.flushText() this.richContent.push([{ tag: 'hr' }]) this.card?.elements.push({ tag: 'hr' }) - } else if (type === 'button') { - this.flushText(true) - const behaviors: MessageContent.Card.ActionBehavior[] = [] - if (attrs.type === 'link') { - behaviors.push({ - type: 'open_url', - default_url: attrs.href, - }) - } else if (attrs.type === 'input') { - behaviors.push({ - type: 'callback', - value: { - _satori_type: 'command', - content: attrs.text, - }, + } else if (type === 'form') { + this.flushText() + const length = this.card?.elements.length + await this.render(children) + if (this.card?.elements.length > length) { + const elements = this.card?.elements.slice(length) + this.card.elements.push({ + tag: 'form', + name: attrs.name || 'Form', + elements, }) - } else if (attrs.type === 'action') { - // TODO } + } else if (type === 'input') { + this.flushText() + this.card?.elements.push({ + tag: 'action', + actions: [{ + tag: 'input', + name: attrs.name, + width: attrs.width, + label: attrs.label && { + tag: 'plain_text', + content: attrs.label, + }, + placeholder: attrs.placeholder && { + tag: 'plain_text', + content: attrs.placeholder, + }, + behaviors: this.createBehavior(attrs), + }], + }) + } else if (type === 'button') { + this.card ??= { elements: [] } + this.flushText(true) await this.render(children) this.actionElements.push({ tag: 'button', @@ -194,7 +231,8 @@ export class LarkMessageEncoder extends MessageEnco tag: 'plain_text', content: this.textContent, }, - behaviors, + disabled: attrs.disabled, + behaviors: this.createBehavior(attrs), }) this.textContent = '' } else if (type === 'button-group') { diff --git a/adapters/lark/src/types/message/content.ts b/adapters/lark/src/types/message/content.ts index 5e16f9e6..9aee5a26 100644 --- a/adapters/lark/src/types/message/content.ts +++ b/adapters/lark/src/types/message/content.ts @@ -328,13 +328,15 @@ export namespace MessageContent { export type InnerElement = IconElement | PlainTextElement | BaseImageElement } - export interface ActionElement extends BaseElement<'action'> { - actions: ActionElement.InnerElement[] - layout?: 'bisected' | 'trisection' | 'flow' + export interface FormElement extends BaseElement<'form'> { + name: string + elements: Element[] + confirm?: ConfirmElement } - export namespace ActionElement { - export type InnerElement = ButtonElement + export interface ActionElement extends BaseElement<'action'> { + actions: Element[] + layout?: 'bisected' | 'trisection' | 'flow' } export type ActionBehavior = @@ -354,26 +356,157 @@ export namespace MessageContent { value: Record } - export interface ButtonElement extends BaseElement<'button'> { + export interface BaseButtonElement extends BaseElement<'button'> { text: PlainTextElement - type?: ButtonElement.Type size?: ButtonElement.Size - width?: ButtonElement.Width icon?: IconElement - hover_tips?: PlainTextElement disabled?: boolean + behaviors?: ActionBehavior[] + } + + export interface ButtonElement extends BaseButtonElement { + type?: ButtonElement.Type + width?: ButtonElement.Width + hover_tips?: PlainTextElement disabled_tips?: PlainTextElement confirm?: { title: PlainTextElement text: PlainTextElement } - behaviors?: ActionBehavior[] // form-related fields name?: string required?: boolean action_type?: 'link' | 'request' | 'multi' | 'form_submit' | 'form_reset' } + export interface ConfirmElement { + title: PlainTextElement + text: PlainTextElement + } + + export interface InputElement extends BaseElement<'input'> { + name: string + required?: boolean + placeholder?: PlainTextElement + default_value?: string + disabled?: boolean + width?: 'default' | 'fill' | string + max_length?: number + input_type?: 'text' | 'multiline_text' | 'password' + show_icon?: boolean + rows?: number + auto_resize?: boolean + max_rows?: number + label?: PlainTextElement + label_position?: 'top' | 'left' + value?: string | object + behaviors?: ActionBehavior[] + confirm?: ConfirmElement + fallback?: { + tag?: string + text?: PlainTextElement + } + } + + export interface OverflowElement extends BaseElement<'overflow'> { + width?: 'default' | 'fill' | string + options: OverflowElement.Option[] + value?: object + confirm?: ConfirmElement + } + + export namespace OverflowElement { + export interface Option { + text?: PlainTextElement + multi_url?: URLs + value?: string + } + } + + export interface BaseSelectElement extends BaseElement { + type?: 'default' | 'text' + name?: string + required?: boolean + disabled?: boolean + placeholder?: PlainTextElement + width?: 'default' | 'fill' | string + confirm?: ConfirmElement + } + + export interface OptionElement { + text: PlainTextElement + icon?: IconElement + value: string + } + + export interface SelectElement extends BaseSelectElement<'select_static'> { + options: OptionElement[] + initial_option?: string + } + + export interface MultiSelectElement extends BaseSelectElement<'multi_select_static'> { + options: OptionElement[] + selected_values?: string[] + } + + export interface SelectPersonElement extends BaseSelectElement<'select_person'> { + options?: { value: string }[] + } + + export interface MultiSelectPersonElement extends BaseSelectElement<'multi_select_person'> { + options?: { value: string }[] + selected_values?: string[] + } + + export interface DatePickerElement extends BaseSelectElement<'date_picker'> { + initial_date?: string + value?: object + } + + export interface TimePickerElement extends BaseSelectElement<'picker_time'> { + initial_time?: string + value?: object + } + + export interface DateTimePickerElement extends BaseSelectElement<'picker_datetime'> { + initial_datetime?: string + value?: object + } + + export interface CheckerElement extends BaseElement<'checker'> { + name?: string + checked?: boolean + disabled?: boolean + text?: CheckerElement.TextElement + overall_checkable?: boolean + button_area?: { + pc_display_rule?: 'always' | 'on_hover' + buttons?: CheckerElement.ButtonElement[] + } + checked_style?: { + show_strikethrough?: boolean + opacity?: number + } + margin?: string + padding?: string + confirm?: ConfirmElement + behaviors?: ActionBehavior[] + hover_tips?: PlainTextElement + disable_tips?: PlainTextElement + } + + export namespace CheckerElement { + export interface TextElement extends PlainTextElement { + text_size?: 'normal' | 'heading' | 'notation' + text_color?: string + text_align?: TextAlign + } + + export interface ButtonElement extends BaseButtonElement { + type: 'text' | 'primary_text' | 'danger_text' + } + } + export namespace ButtonElement { export type Size = 'tiny' | 'small' | 'medium' | 'large' export type Width = 'default' | 'fill' | string @@ -389,6 +522,9 @@ export namespace MessageContent { | ChartElement | TableElement | ImageElement + | FormElement + | InputElement + | ButtonElement } export interface Template { diff --git a/adapters/lark/src/utils.ts b/adapters/lark/src/utils.ts index c22a6917..2f4f9d9e 100644 --- a/adapters/lark/src/utils.ts +++ b/adapters/lark/src/utils.ts @@ -106,6 +106,9 @@ export async function adaptSession(bot: LarkBot, body: Eve for (const [key, value] of Object.entries(options)) { content += ` --${key} ${value}` } + if (body.event.action.input_value) { + content += ` ${body.event.action.input_value}` + } session.content = content session.messageId = body.event.context.open_message_id session.channelId = body.event.context.open_chat_id