Skip to content

Commit

Permalink
feat(lark): add internal api (#249)
Browse files Browse the repository at this point in the history
  • Loading branch information
XxLittleCxX committed Apr 8, 2024
1 parent ac6d163 commit 82ccacb
Show file tree
Hide file tree
Showing 12 changed files with 32,087 additions and 494 deletions.
48 changes: 33 additions & 15 deletions adapters/lark/src/bot.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Bot, Context, h, Quester, Schema } from '@satorijs/satori'
import { Bot, Context, h, Quester, Schema, Universal } from '@satorijs/satori'

import { HttpServer } from './http'
import { LarkMessageEncoder } from './message'
Expand All @@ -23,7 +23,6 @@ export class LarkBot<C extends Context = Context> extends Bot<C, LarkBot.Config>
this.logger.warn('selfUrl is not set, some features may not work')
}

this.selfId = config.appId
this.http = ctx.http.extend({
endpoint: config.endpoint,
})
Expand All @@ -33,13 +32,29 @@ export class LarkBot<C extends Context = Context> extends Bot<C, LarkBot.Config>
ctx.plugin(HttpServer, this)
}

get appId() {
return this.config.appId
}

async initialize() {
await this.refreshToken()
const { bot } = await this.http.get<{
bot: {
activate_status: number
app_name: string
avatar_url: string
ip_white_list: any[]
open_id: string
}
}>('/bot/v3/info')
this.selfId = bot.open_id
this.user.avatar = bot.avatar_url
this.user.name = bot.app_name
this.online()
}

private async refreshToken() {
const { tenant_access_token: token } = await this.internal.getTenantAccessToken({
const { tenant_access_token: token } = await this.internal.tenantAccessTokenInternalAuth({
app_id: this.config.appId,
app_secret: this.config.appSecret,
})
Expand All @@ -62,53 +77,56 @@ export class LarkBot<C extends Context = Context> extends Bot<C, LarkBot.Config>
}

async editMessage(channelId: string, messageId: string, content: h.Fragment) {
await this.internal.updateMessage(messageId, {
await this.internal.updateImMessage(messageId, {
content: h.normalize(content).join(''),
msg_type: 'text',
})
}

async deleteMessage(channelId: string, messageId: string) {
await this.internal.deleteMessage(messageId)
await this.internal.deleteImMessage(messageId)
}

async getMessage(channelId: string, messageId: string) {
const data = await this.internal.getMessage(messageId)
return await Utils.decodeMessage(this, data.data)
async getMessage(channelId: string, messageId: string, recursive = true) {
const data = await this.internal.getImMessage(messageId)
const message = await Utils.decodeMessage(this, data.data.items[0], recursive)
const im = await this.internal.getImChat(channelId)
message.channel.type = im.data.chat_mode === 'p2p' ? Universal.Channel.Type.DIRECT : Universal.Channel.Type.TEXT
return message
}

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: messages } = await this.internal.listImMessage({ 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 }
}

async getUser(userId: string, guildId?: string) {
const data = await this.internal.getContactUser(userId)
return Utils.decodeUser(data.data)
return Utils.decodeUser(data.data.user)
}

async getChannel(channelId: string) {
const { data } = await this.internal.getGuildInfo(channelId)
return Utils.decodeChannel(data)
const { data } = await this.internal.getImChat(channelId)
return Utils.decodeChannel(channelId, data)
}

async getChannelList(guildId: string) {
return { data: [await this.getChannel(guildId)] }
}

async getGuild(guildId: string) {
const { data } = await this.internal.getGuildInfo(guildId)
const { data } = await this.internal.getImChat(guildId)
return Utils.decodeGuild(data)
}

async getGuildList(after?: string) {
const { data: guilds } = await this.internal.getCurrentUserGuilds({ page_token: after })
const { data: guilds } = await this.internal.listImChat({ page_token: after })
return { data: guilds.items.map(Utils.decodeGuild), next: guilds.page_token }
}

async getGuildMemberList(guildId: string, after?: string) {
const { data: users } = await this.internal.getGuildMembers(guildId, { page_token: after })
const { data: users } = await this.internal.getImChatMembers(guildId, { page_token: after })
const data = users.items.map(v => ({ user: { id: v.member_id, name: v.name }, name: v.name }))
return { data, next: users.page_token }
}
Expand Down
10 changes: 5 additions & 5 deletions adapters/lark/src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export class HttpServer<C extends Context = Context> extends Adapter<C, FeishuBo

// dispatch message
bot.logger.debug('received decryped event: %o', body)
// @TODO: need await?
this.dispatchSession(body)

// Lark requires 200 OK response to make sure event is received
Expand All @@ -91,20 +92,19 @@ export class HttpServer<C extends Context = Context> extends Adapter<C, FeishuBo
params: { type },
responseType: 'stream',
})

ctx.set('content-type', resp.headers.get('content-type'))
ctx.status = 200
ctx.response.headers['Content-Type'] = resp.headers.get('content-type')
ctx.response.body = Readable.fromWeb(resp.data)
})
}

dispatchSession(body: AllEvents): void {
async dispatchSession(body: AllEvents) {
const { header } = body
if (!header) return
const { app_id, event_type } = header
body.type = event_type // add type to body to ease typescript type narrowing
const bot = this.bots.find((bot) => bot.selfId === app_id)
const session = adaptSession(bot, body)
const bot = this.bots.find((bot) => bot.appId === app_id)
const session = await adaptSession(bot, body)
bot.dispatch(session)
}

Expand Down
17 changes: 11 additions & 6 deletions adapters/lark/src/message.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Context, h, MessageEncoder, Quester } from '@satorijs/satori'
import { LarkBot } from './bot'
import { BaseResponse, Message, MessageContent, MessageType } from './types'
import { BaseResponse, Lark, MessageContent, MessageType } from './types'
import { extractIdType } from './utils'

export interface Addition {
Expand All @@ -17,17 +17,21 @@ export class LarkMessageEncoder<C extends Context = Context> extends MessageEnco

async post(data?: any) {
try {
let resp: BaseResponse & { data: Message }
let resp: BaseResponse & { data?: Lark.Message }
if (this.quote) {
resp = await this.bot.internal?.replyMessage(this.quote, data)
resp = await this.bot.internal.replyImMessage(this.quote, data)
} else {
data.receive_id = this.channelId
resp = await this.bot.internal?.sendMessage(extractIdType(this.channelId), data)
resp = await this.bot.internal?.createImMessage(data, {
receive_id_type: extractIdType(this.channelId),
})
}
const session = this.bot.session()
session.messageId = resp.data.message_id
session.timestamp = Number(resp.data.create_time) * 1000
session.userId = resp.data.sender.id
session.channelId = this.channelId
session.guildId = this.guildId
session.app.emit(session, 'send', session)
this.results.push(session.event.message)
} catch (e) {
Expand Down Expand Up @@ -79,7 +83,7 @@ export class LarkMessageEncoder<C extends Context = Context> extends MessageEnco

if (type === 'img' || type === 'image') {
payload.append('image_type', 'message')
const { data } = await this.bot.internal.uploadImage(payload)
const { data } = await this.bot.internal.createImImage(payload)
return {
type: 'image',
file: {
Expand All @@ -105,7 +109,7 @@ export class LarkMessageEncoder<C extends Context = Context> extends MessageEnco
}
}
payload.append('file_name', filename)
const { data } = await this.bot.internal.uploadFile(payload)
const { data } = await this.bot.internal.createImFile(payload)
return {
type: msgType,
file: {
Expand Down Expand Up @@ -157,6 +161,7 @@ export class LarkMessageEncoder<C extends Context = Context> extends MessageEnco
if (attrs.src || attrs.url) {
await this.flush()
this.addition = await this.sendFile(type, attrs.src || attrs.url)
await this.flush()
}
break
case 'figure': // FIXME: treat as message element for now
Expand Down

0 comments on commit 82ccacb

Please sign in to comment.