From 22e736ba76843b7eddedd1637e3dcd1e257afc75 Mon Sep 17 00:00:00 2001 From: Shigma Date: Wed, 30 Oct 2024 14:40:42 +0800 Subject: [PATCH] feat(lark): support buttons --- adapters/lark/src/message.ts | 65 ++++++++++++++++++--- adapters/lark/src/types/message/content.ts | 68 ++++++++++++++++++++-- 2 files changed, 121 insertions(+), 12 deletions(-) diff --git a/adapters/lark/src/message.ts b/adapters/lark/src/message.ts index 77af9782..9939397b 100644 --- a/adapters/lark/src/message.ts +++ b/adapters/lark/src/message.ts @@ -8,6 +8,7 @@ export class LarkMessageEncoder extends MessageEnco private textContent = '' private richContent: MessageComponent.RichText.Paragraph[] = [] private cardElements: MessageComponent.Card.Element[] | undefined + private actionElements: MessageComponent.Card.ActionElement[] = [] async post(data?: any) { try { @@ -40,11 +41,16 @@ export class LarkMessageEncoder extends MessageEnco } } - private flushText() { - if (!this.textContent) return - this.richContent.push([{ tag: 'md', text: this.textContent }]) - this.cardElements?.push({ tag: 'markdown', content: this.textContent }) - this.textContent = '' + private flushText(flushAction = false) { + if ((this.textContent || flushAction) && this.actionElements.length) { + this.cardElements?.push({ tag: 'action', actions: this.actionElements }) + this.actionElements = [] + } + if (this.textContent) { + this.richContent.push([{ tag: 'md', text: this.textContent }]) + this.cardElements?.push({ tag: 'markdown', content: this.textContent }) + this.textContent = '' + } } async flush() { @@ -54,7 +60,9 @@ export class LarkMessageEncoder extends MessageEnco if (this.cardElements) { await this.post({ msg_type: 'interactive', - elements: this.cardElements, + content: JSON.stringify({ + elements: this.cardElements, + }), }) } else { await this.post({ @@ -147,10 +155,43 @@ export class LarkMessageEncoder extends MessageEnco } else if (type === 'figure' || type === 'message') { await this.flush() await this.render(children, true) - } else if (type === 'hr' || type === 'lark:hr' || type === 'feishu:hr') { + } else if (type === 'hr') { this.flushText() this.richContent.push([{ tag: 'hr' }]) this.cardElements?.push({ tag: 'hr' }) + } else if (type === 'button') { + this.flushText() + const behaviors: MessageComponent.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 + } + await this.render(children) + this.actionElements.push({ + tag: 'button', + text: { + tag: 'plain_text', + content: this.textContent, + }, + behaviors, + }) + this.textContent = '' + } else if (type === 'button-group') { + this.flushText(true) + await this.render(children) + this.flushText(true) } else if (type.startsWith('lark:') || type.startsWith('feishu:')) { const tag = type.slice(type.split(':', 1)[0].length + 1) if (tag === 'share-chat') { @@ -181,6 +222,16 @@ export class LarkMessageEncoder extends MessageEnco await this.flush() this.cardElements = [] await this.render(children, true) + } else if (tag === 'div') { + this.flushText() + await this.render(children) + this.cardElements?.push({ + tag: 'markdown', + text_align: attrs.align, + text_size: attrs.size, + content: this.textContent, + }) + this.textContent = '' } } else { await this.render(children) diff --git a/adapters/lark/src/types/message/content.ts b/adapters/lark/src/types/message/content.ts index e4613222..024065e2 100644 --- a/adapters/lark/src/types/message/content.ts +++ b/adapters/lark/src/types/message/content.ts @@ -161,8 +161,8 @@ export namespace MessageComponent { /** @see https://open.larksuite.com/document/common-capabilities/message-card/message-cards-content/card-header */ export interface Header { - title: PlainTextElement - subtitle?: PlainTextElement + title: I18nPlainTextElement + subtitle?: I18nPlainTextElement template?: Header.Template icon?: CustomIconElement ud_icon?: StandardIconElement @@ -186,7 +186,13 @@ export namespace MessageComponent { export interface PlainTextElement extends BaseElement<'plain_text'> { content: string + } + + export interface I18nPlainTextElement extends PlainTextElement { i18n?: Record + } + + export interface DivPlainTextElement extends PlainTextElement { text_size?: TextSize text_color?: string text_align?: TextAlign @@ -226,8 +232,8 @@ export namespace MessageComponent { export interface HorizontalRuleElement extends BaseElement<'hr'> {} - export interface ParagraphElement extends BaseElement<'div'> { - text?: PlainTextElement + export interface DivElement extends BaseElement<'div'> { + text?: DivPlainTextElement } export interface MarkdownElement extends BaseElement<'markdown'> { @@ -239,10 +245,62 @@ export namespace MessageComponent { export interface HorizontalRuleElement extends BaseElement<'hr'> {} + export interface ActionModule extends BaseElement<'action'> { + actions: ActionElement[] + layout?: 'bisected' | 'trisection' | 'flow' + } + + export type ActionElement = + | ButtonElement + + export type ActionBehavior = + | OpenURLBehavior + | CallbackBehavior + + export interface OpenURLBehavior { + type: 'open_url' + default_url: string + pc_url?: string + ios_url?: string + android_url?: string + } + + export interface CallbackBehavior { + type: 'callback' + value: Record + } + + export interface ButtonElement extends BaseElement<'button'> { + text: PlainTextElement + type?: ButtonElement.Type + size?: ButtonElement.Size + width?: ButtonElement.Width + icon?: IconElement + hover_tips?: PlainTextElement + disabled?: boolean + 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 namespace ButtonElement { + export type Size = 'tiny' | 'small' | 'medium' | 'large' + export type Width = 'default' | 'fill' | string + export type Type = 'default' | 'primary' | 'danger' | 'text' | 'primary_text' | 'danger_text' | 'primary_filled' | 'danger_filled' | 'laser' + } + export type Element = - | ParagraphElement + | DivElement | MarkdownElement | HorizontalRuleElement + | ActionModule } export interface Template {