From eef87a5a8a7c5ab91de9fe1011749f7064bca42f Mon Sep 17 00:00:00 2001 From: Maiko Tan Date: Mon, 9 Oct 2023 16:25:18 +0800 Subject: [PATCH] feat: add getMessage and getMessageList --- adapters/lark/src/bot.ts | 12 ++++++ adapters/lark/src/types/message/index.ts | 31 ++++++++++++++ adapters/lark/src/utils.ts | 54 +++++++++++++++++++++++- 3 files changed, 95 insertions(+), 2 deletions(-) diff --git a/adapters/lark/src/bot.ts b/adapters/lark/src/bot.ts index d9bf7a0a..69bdaac4 100644 --- a/adapters/lark/src/bot.ts +++ b/adapters/lark/src/bot.ts @@ -3,6 +3,7 @@ import { Bot, Context, h, Logger, Quester, Schema } from '@satorijs/satori' import { HttpServer } from './http' import { LarkMessageEncoder } from './message' import { Internal } from './types' +import * as Utils from './utils' const logger = new Logger('lark') @@ -77,6 +78,17 @@ export class LarkBot extends Bot { async deleteMessage(channelId: string, messageId: string) { await this.internal.deleteMessage(messageId) } + + async getMessage(channelId: string, messageId: string) { + const data = await this.internal.getMessage(messageId) + return await Utils.decodeMessage(this, data.data) + } + + async getMessageList(channelId: string, before?: string) { + const { data: messages } = await this.internal.getMessageList({ container_id_type: 'chat', container_id: channelId, page_token: before }) + const data = await Promise.all(messages.items.reverse().map(data => Utils.decodeMessage(this, data))) + return { data, next: data[0]?.id } + } } export namespace LarkBot { diff --git a/adapters/lark/src/types/message/index.ts b/adapters/lark/src/types/message/index.ts index b9126562..08c8a23e 100644 --- a/adapters/lark/src/types/message/index.ts +++ b/adapters/lark/src/types/message/index.ts @@ -167,6 +167,32 @@ export interface ReadUser { tenant_key: string } +export interface GetMessageListParams { + /** + * Currently there is only 'chat' available + * @see https://open.larksuite.com/document/server-docs/im-v1/message/list + */ + container_id_type: 'p2p' | 'chat' + /** + * Should be in the format like `oc_234jsi43d3ssi993d43545f` + */ + container_id: string + /** Timestamp in seconds */ + start_time?: string | number + /** Timestamp in seconds */ + end_time?: string | number + /** @default 'ByCreateTimeAsc' */ + sort_type?: 'ByCreateTimeAsc' | 'ByCreateTimeDesc' + /** Range from 1 to 50 */ + page_size?: number + /** + * If the current page is the first page, this field should be omitted. + * Otherwise you could use the `page_token` from the previous response to + * get the next page. + */ + page_token?: string +} + declare module '../internal' { export interface Internal { /** @see https://open.larksuite.com/document/uAjLw4CM/ukTMukTMukTM/reference/im-v1/message/create */ @@ -181,10 +207,15 @@ declare module '../internal' { deleteMessage(message_id: string): Promise /** @see https://open.larksuite.com/document/uAjLw4CM/ukTMukTMukTM/reference/im-v1/message/read_users */ getMessageReadUsers(message_id: string, params: Pagination<{ user_id_type: Lark.UserIdType }>): Promise }> + /** @see https://open.larksuite.com/document/server-docs/im-v1/message/list */ + getMessageList(params: GetMessageListParams): Promise }> } } Internal.define({ + '/im/v1/messages': { + GET: 'getMessageList', + }, '/im/v1/messages?receive_id_type={receive_id_type}': { POST: 'sendMessage', }, diff --git a/adapters/lark/src/utils.ts b/adapters/lark/src/utils.ts index e7df9606..349ade0c 100644 --- a/adapters/lark/src/utils.ts +++ b/adapters/lark/src/utils.ts @@ -1,7 +1,8 @@ import crypto from 'crypto' +import { Message } from '@satorijs/protocol' import { h, Session, trimSlash } from '@satorijs/satori' -import { FeishuBot } from './bot' -import { AllEvents, Events, Lark, MessageContentType, MessageType } from './types' +import { FeishuBot, LarkBot } from './bot' +import { AllEvents, Events, Lark, Message as LarkMessage, MessageContentType, MessageType } from './types' export type Sender = | { @@ -84,6 +85,55 @@ export function adaptSession(bot: FeishuBot, body: AllEvents): Session { return session } +// TODO: This function has many duplicated code with `adaptMessage`, should refactor them +export async function decodeMessage(bot: LarkBot, body: LarkMessage): Promise { + const json = JSON.parse(body.body.content) as MessageContentType + const assetEndpoint = trimSlash(bot.config.selfUrl ?? bot.ctx.root.config.selfUrl) + bot.config.path + '/assets' + const content: h[] = [] + switch (body.msg_type) { + case 'text': { + const text = json.text as string + if (!body.mentions?.length) { + content.push(h.text(text)) + break + } + + // Lark's `at` Element would be `@user_id` in text + text.split(' ').forEach((word) => { + if (word.startsWith('@')) { + const mention = body.mentions.find((mention) => mention.key === word) + content.push(h.at(mention.id, { name: mention.name })) + } else { + content.push(h.text(word)) + } + }) + break + } + case 'image': + content.push(h.image(`${assetEndpoint}/image/${body.message_id}/${json.image_key}?self_id=${bot.selfId}`)) + break + case 'audio': + content.push(h.audio(`${assetEndpoint}/file/${body.message_id}/${json.file_key}?self_id=${bot.selfId}`)) + break + case 'media': + content.push(h.video(`${assetEndpoint}/file/${body.message_id}/${json.file_key}?self_id=${bot.selfId}`, json.image_key)) + break + case 'file': + content.push(h.file(`${assetEndpoint}/file/${body.message_id}/${json.file_key}?self_id=${bot.selfId}`)) + break + } + + return { + timestamp: +body.update_time, + createdAt: +body.create_time, + updatedAt: +body.update_time, + id: body.message_id, + content: content.map((c) => c.toString()).join(' '), + elements: content, + quote: body.upper_message_id ? await bot.getMessage(body.chat_id, body.upper_message_id) : undefined, + } +} + /** * Get ID type from id string * @see https://open.larksuite.com/document/home/user-identity-introduction/introduction