From 36cb0e2a3608ecbb4bef2a96edde985558ab90f2 Mon Sep 17 00:00:00 2001 From: Shigma Date: Tue, 26 Sep 2023 22:04:41 +0800 Subject: [PATCH] feat(core): migrate adapters, fix #155 --- adapters/discord/src/bot.ts | 4 +- adapters/discord/src/message.ts | 20 ++++----- adapters/discord/src/utils.ts | 80 ++++++++++++++++----------------- adapters/mail/src/bot.ts | 2 +- adapters/mail/src/utils.ts | 29 +++++++----- adapters/qqguild/src/bot.ts | 16 +++---- adapters/qqguild/src/utils.ts | 2 - adapters/slack/src/bot.ts | 6 ++- adapters/slack/src/utils.ts | 3 +- packages/core/src/bot.ts | 1 + 10 files changed, 83 insertions(+), 80 deletions(-) diff --git a/adapters/discord/src/bot.ts b/adapters/discord/src/bot.ts index a13fc7a8..ada2ec57 100644 --- a/adapters/discord/src/bot.ts +++ b/adapters/discord/src/bot.ts @@ -88,12 +88,12 @@ export class DiscordBot extends Bot { async getMessage(channelId: string, messageId: string) { const data = await this.internal.getChannelMessage(channelId, messageId) - return await Discord.decodeMessage(this, data) + return await Discord.decodeMessage(this, data, {}) } async getMessageList(channelId: string, before?: string) { const messages = await this.internal.getChannelMessages(channelId, { before, limit: 100 }) - const data = await Promise.all(messages.reverse().map(data => Discord.decodeMessage(this, data, {}, false))) + const data = await Promise.all(messages.reverse().map(data => Discord.decodeMessage(this, data, {}, undefined, false))) return { data, next: data[0]?.id } } diff --git a/adapters/discord/src/message.ts b/adapters/discord/src/message.ts index 5deba21d..8765d839 100644 --- a/adapters/discord/src/message.ts +++ b/adapters/discord/src/message.ts @@ -9,7 +9,7 @@ type RenderMode = 'default' | 'figure' const logger = new Logger('discord') class State { - author: Partial = {} + author: Partial = {} quote: Partial = {} channel: Partial = {} fakeMessageMap: Record = {} // [userInput] = discord messages @@ -33,14 +33,14 @@ export class DiscordMessageEncoder extends MessageEncoder { return `/webhooks/${input.d.application_id}/${input.d.token}` } else if (this.stack[0].type === 'forward' && this.stack[0].channel?.id) { // 发送到子区 - if (this.stack[1].author.nickname || this.stack[1].author.avatar) { + if (this.stack[1].author.name || this.stack[1].author.avatar) { const webhook = await this.ensureWebhook() return `/webhooks/${webhook.id}/${webhook.token}?wait=true&thread_id=${this.stack[0].channel?.id}` } else { return `/channels/${this.stack[0].channel.id}/messages` } } else { - if (this.stack[0].author.nickname || this.stack[0].author.avatar || (this.stack[0].type === 'forward' && !this.stack[0].threadCreated)) { + if (this.stack[0].author.name || this.stack[0].author.avatar || (this.stack[0].type === 'forward' && !this.stack[0].threadCreated)) { const webhook = await this.ensureWebhook() return `/webhooks/${webhook.id}/${webhook.token}?wait=true` } else { @@ -54,7 +54,7 @@ export class DiscordMessageEncoder extends MessageEncoder { const url = await this.getUrl() const result = await this.bot.http.post(url, data, { headers }) const session = this.bot.session() - const message = await decodeMessage(this.bot, result, session) + const message = await decodeMessage(this.bot, result, session.data.message = {}, session.data) session.app.emit(session, 'send', session) this.results.push(session) @@ -265,7 +265,7 @@ export class DiscordMessageEncoder extends MessageEncoder { const parse = (val: string) => val.replace(/\\([\\*_`~|()\[\]])/g, '$1') const message = this.stack[this.stack[0].type === 'forward' ? 1 : 0] - if (!message.author.avatar && !message.author.nickname && this.stack[0].type !== 'forward') { + if (!message.author.avatar && !message.author.name && this.stack[0].type !== 'forward') { // no quote and author, send by bot await this.flush() this.addition.message_reference = { @@ -279,15 +279,15 @@ export class DiscordMessageEncoder extends MessageEncoder { replyId = this.stack[0].fakeMessageMap[attrs.id][0].messageId channelId = this.stack[0].fakeMessageMap[attrs.id][0].channelId } - const quoted = await this.bot.getMessage(channelId, replyId) + const quote = await this.bot.getMessage(channelId, replyId) this.addition.embeds = [{ description: [ - sanitize(parse(quoted.elements.filter(v => v.type === 'text').join('')).slice(0, 30)), - ` [[ ↑ ]](https://discord.com/channels/${this.guildId}/${channelId}/${replyId})`, + sanitize(parse(quote.elements.filter(v => v.type === 'text').join('')).slice(0, 30)), + ` [[ ↑ ]](https://discord.com/channels/${this.guildId}/${channelId}/${replyId})`, ].join('\n\n'), author: { - name: quoted.author.nickname || quoted.author.username, - icon_url: quoted.author.avatar, + name: quote.user.name, + icon_url: quote.user.avatar, }, }] } diff --git a/adapters/discord/src/utils.ts b/adapters/discord/src/utils.ts index d4603b56..81e4f1d6 100644 --- a/adapters/discord/src/utils.ts +++ b/adapters/discord/src/utils.ts @@ -20,12 +20,10 @@ export const decodeUser = (user: Discord.User): Universal.User => ({ isBot: user.bot || false, }) -export const decodeGuildMember = (member: Discord.GuildMember): Universal.GuildMember => ({ - ...decodeUser(member.user), - user: decodeUser(member.user), +export const decodeGuildMember = (member: Partial): Universal.GuildMember => ({ + user: member.user && decodeUser(member.user), name: member.nick, roles: member.roles, - avatar: member.user.avatar, }) export const decodeGuild = (data: Discord.Guild): Universal.Guild => ({ @@ -39,11 +37,6 @@ export const decodeChannel = (data: Discord.Channel): Universal.Channel => ({ type: data.type === Discord.Channel.Type.DM ? Universal.Channel.Type.DIRECT : Universal.Channel.Type.TEXT, }) -export const decodeAuthor = (author: Discord.User): Universal.Author => ({ - ...decodeUser(author), - nickname: author.username, -}) - export const decodeRole = (role: Discord.Role): Universal.GuildRole => ({ ...role, permissions: BigInt(role.permissions), @@ -54,29 +47,25 @@ export const encodeRole = (role: Partial): Partial = {}, reference = true) { +export async function decodeMessage( + bot: DiscordBot, + data: Discord.Message, + message: Universal.Message, + payload: Universal.Message | Universal.EventData = message, + details = true, +) { const { platform } = bot - session.messageId = meta.id - session.channelId = meta.channel_id - session.timestamp = new Date(meta.timestamp).valueOf() || Date.now() - if (meta.author) { - session.author = decodeAuthor(meta.author) - session.userId = meta.author.id - } - if (meta.member?.nick) { - session.author.nickname = meta.member?.nick - } - + message.id = message.messageId = data.id // https://discord.com/developers/docs/reference#message-formatting - session.content = '' - if (meta.content) { - session.content = meta.content + message.content = '' + if (data.content) { + message.content = data.content .replace(/<@[!&]?(.+?)>/g, (_, id) => { - if (meta.mention_roles.includes(id)) { + if (data.mention_roles.includes(id)) { return h('at', { role: id }).toString() } else { - const user = meta.mentions?.find(u => u.id === id || `${u.username}#${u.discriminator}` === id) + const user = data.mentions?.find(u => u.id === id || `${u.username}#${u.discriminator}` === id) return h.at(id, { name: user?.username }).toString() } }) @@ -89,15 +78,15 @@ export async function decodeMessage(bot: DiscordBot, meta: Discord.Message, sess .replace(/@everyone/g, () => h('at', { type: 'all' }).toString()) .replace(/@here/g, () => h('at', { type: 'here' }).toString()) .replace(/<#(.+?)>/g, (_, id) => { - const channel = meta.mention_channels?.find(c => c.id === id) + const channel = data.mention_channels?.find(c => c.id === id) return h.sharp(id, { name: channel?.name }).toString() }) } // embed 的 update event 太阴间了 只有 id embeds channel_id guild_id 四个成员 - if (meta.attachments?.length) { - if (session.content) session.content += ' ' - session.content += meta.attachments.map(v => { + if (data.attachments?.length) { + if (message.content) message.content += ' ' + message.content += data.attachments.map(v => { if (v.height && v.width && v.content_type?.startsWith('image/')) { return h('image', { url: v.url, @@ -125,26 +114,35 @@ export async function decodeMessage(bot: DiscordBot, meta: Discord.Message, sess } }).join('') } - for (const embed of meta.embeds) { + for (const embed of data.embeds) { // not using embed types // https://discord.com/developers/docs/resources/channel#embed-object-embed-types if (embed.image) { - session.content += h('image', { url: embed.image.url, proxy_url: embed.image.proxy_url }) + message.content += h('image', { url: embed.image.url, proxy_url: embed.image.proxy_url }) } if (embed.thumbnail) { - session.content += h('image', { url: embed.thumbnail.url, proxy_url: embed.thumbnail.proxy_url }) + message.content += h('image', { url: embed.thumbnail.url, proxy_url: embed.thumbnail.proxy_url }) } if (embed.video) { - session.content += h('video', { url: embed.video.url, proxy_url: embed.video.proxy_url }) + message.content += h('video', { url: embed.video.url, proxy_url: embed.video.proxy_url }) } } - session.elements = h.parse(session.content) + message.elements = h.parse(message.content) // 遇到过 cross post 的消息在这里不会传消息 id - if (reference && meta.message_reference) { - const { message_id, channel_id } = meta.message_reference - session.quote = await bot.getMessage(channel_id, message_id) + if (details && data.message_reference) { + const { message_id, channel_id } = data.message_reference + message.quote = await bot.getMessage(channel_id, message_id) + } + + if (!payload) return message + payload.channel = { + id: data.channel_id, + type: data.member ? Universal.Channel.Type.TEXT : Universal.Channel.Type.DIRECT, } - return session as Universal.Message + payload.user = decodeUser(data.author) + payload.member = data.member && decodeGuildMember(data.member) + payload.timestamp = new Date(data.timestamp).valueOf() || Date.now() + return message } export function setupMessageGuildId(session: Partial, guildId: string) { @@ -184,7 +182,7 @@ export async function adaptSession(bot: DiscordBot, input: Discord.Gateway.Paylo } catch (e) {} } session.type = 'message' - await decodeMessage(bot, input.d, session) + await decodeMessage(bot, input.d, session.data.message = {}, session.data) // dc 情况特殊 可能有 embeds 但是没有消息主体 // if (!session.content) return } else if (input.t === 'MESSAGE_UPDATE') { @@ -192,7 +190,7 @@ export async function adaptSession(bot: DiscordBot, input: Discord.Gateway.Paylo const message = await bot.internal.getChannelMessage(input.d.channel_id, input.d.id) // Unlike creates, message updates may contain only a subset of the full message object payload // https://discord.com/developers/docs/topics/gateway-events#message-update - await decodeMessage(bot, message, session) + await decodeMessage(bot, message, session.data.message = {}, session.data) const channel = await bot.internal.getChannel(input.d.channel_id) setupMessageGuildId(session, channel.guild_id) // if (!session.content) return diff --git a/adapters/mail/src/bot.ts b/adapters/mail/src/bot.ts index 7f35b4a9..8b530a55 100644 --- a/adapters/mail/src/bot.ts +++ b/adapters/mail/src/bot.ts @@ -18,7 +18,7 @@ export class MailBot extends Bot { } async start() { - this.username = this.config.username + this.user.name = this.config.username await super.start() this.imap = new IMAP( this.config, diff --git a/adapters/mail/src/utils.ts b/adapters/mail/src/utils.ts index db2be5df..5bbe9f94 100644 --- a/adapters/mail/src/utils.ts +++ b/adapters/mail/src/utils.ts @@ -6,18 +6,10 @@ import { MailBot } from './bot' export async function adaptMessage( bot: MailBot, mail: ParsedMail, - message: Universal.Message = {}, + message: Universal.Message, + payload: Universal.Message | Universal.EventData, ): Promise { - message.isDirect = true - message.messageId = mail.messageId - message.userId = mail.from.value[0].address - message.channelId = `private:${message.userId}` - message.guildId = message.userId - message.timestamp = +mail.date - message.author = { - userId: mail.from.value[0].address, - nickname: mail.from.value[0].name, - } + message.id = message.messageId = mail.messageId let content = '' if (!mail.html) { content = segment.escape(mail.text) @@ -114,13 +106,26 @@ export async function adaptMessage( content = content.trim() message.content = content message.elements ||= segment.parse(content) + if (!payload) return message + payload.timestamp = +mail.date + payload.user = { + id: mail.from.value[0].address, + name: mail.from.value[0].name, + } + payload.guild = { + id: payload.user.id, + } + payload.channel = { + id: `private:${payload.user.id}`, + type: Universal.Channel.Type.DIRECT, + } return message } export async function dispatchSession(bot: MailBot, mail: ParsedMail) { const session = bot.session() session.type = 'message' - if (!await adaptMessage(bot, mail, session)) { + if (!await adaptMessage(bot, mail, session.data.message = {}, session.data)) { return null } defineProperty(session, 'mail', mail) diff --git a/adapters/qqguild/src/bot.ts b/adapters/qqguild/src/bot.ts index ffa6ca7a..91ec2b02 100644 --- a/adapters/qqguild/src/bot.ts +++ b/adapters/qqguild/src/bot.ts @@ -28,15 +28,13 @@ export class QQGuildBot extends Bot { adaptMessage(msg: QQGuild.Message) { const { id: messageId, author, guildId, channelId, timestamp } = msg - const session = this.session({ - type: 'message', - guildId, - messageId, - channelId, - timestamp: +timestamp, - }) - session.author = adaptUser(msg.author) - session.userId = author.id + const session = this.session() + session.type = 'message' + session.guildId = guildId + session.messageId = messageId + session.channelId = channelId + session.timestamp = +timestamp + session.data.user = adaptUser(author) // TODO https://github.com/satorijs/satori/blob/fbcf4665c77381ff80c8718106d2282a931d5736/packages/core/src/message.ts#L23 // satori core need set guildId is undefined when isPrivate // this is a temporary solution diff --git a/adapters/qqguild/src/utils.ts b/adapters/qqguild/src/utils.ts index 8a2210d3..60f21529 100644 --- a/adapters/qqguild/src/utils.ts +++ b/adapters/qqguild/src/utils.ts @@ -4,8 +4,6 @@ import * as QQGuild from '@qq-guild-sdk/core' export const adaptGuild = (guild: QQGuild.Guild): Universal.Guild => ({ id: guild.id, name: guild.name, - guildId: guild.id, - guildName: guild.name, }) export const adaptUser = (user: QQGuild.User): Universal.User => ({ diff --git a/adapters/slack/src/bot.ts b/adapters/slack/src/bot.ts index 9835f258..b673ab6d 100644 --- a/adapters/slack/src/bot.ts +++ b/adapters/slack/src/bot.ts @@ -120,13 +120,17 @@ export class SlackBot extends Bot('POST', '/team.info', { team_id: guildId, }) return decodeGuild(team) } + async getGuildList() { + return { data: [await this.getGuild()] } + } + async getGuildMember(guildId: string, userId: string) { const { user } = await this.request<{ user: SlackUser }>('POST', '/users.info', { user: userId, diff --git a/adapters/slack/src/utils.ts b/adapters/slack/src/utils.ts index 88d77395..0973e49e 100644 --- a/adapters/slack/src/utils.ts +++ b/adapters/slack/src/utils.ts @@ -115,7 +115,6 @@ export async function adaptMessage( type: data.channel_type === 'im' ? Universal.Channel.Type.DIRECT : Universal.Channel.Type.TEXT, } } - console.log(data) if ('bot_profile' in data) { payload.user = decodeBotProfile(data.bot_profile as Definitions.BotProfile) } else { @@ -236,7 +235,7 @@ export const decodeChannel = (data: SlackChannel): Universal.Channel => ({ type: data.is_private ? Universal.Channel.Type.DIRECT : Universal.Channel.Type.TEXT, }) -export const decodeGuild = (data: SlackTeam): Universal.Guild => ({ +export const decodeGuild = (data: SlackTeam | Definitions.Team): Universal.Guild => ({ id: data.id, name: data.name, }) diff --git a/packages/core/src/bot.ts b/packages/core/src/bot.ts index 4ed7c448..c7d06ce3 100644 --- a/packages/core/src/bot.ts +++ b/packages/core/src/bot.ts @@ -183,6 +183,7 @@ const iterableMethods = [ for (const name of iterableMethods) { Bot.prototype[name + 'Iter'] = function (this: Bot, ...args: any[]) { let list: List + if (!this[name + 'List']) throw new Error(`not implemented: ${name}List`) const getList = async () => { list = await this[name + 'List'](...args, list?.next) }