Skip to content

Commit

Permalink
feat(core): migrate adapters, fix #155
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Sep 26, 2023
1 parent 6d6f868 commit 36cb0e2
Show file tree
Hide file tree
Showing 10 changed files with 83 additions and 80 deletions.
4 changes: 2 additions & 2 deletions adapters/discord/src/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,12 @@ export class DiscordBot extends Bot<DiscordBot.Config> {

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 }
}

Expand Down
20 changes: 10 additions & 10 deletions adapters/discord/src/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type RenderMode = 'default' | 'figure'
const logger = new Logger('discord')

class State {
author: Partial<Universal.Author> = {}
author: Partial<Universal.User> = {}
quote: Partial<Universal.Message> = {}
channel: Partial<Channel> = {}
fakeMessageMap: Record<string, Session[]> = {} // [userInput] = discord messages
Expand All @@ -33,14 +33,14 @@ export class DiscordMessageEncoder extends MessageEncoder<DiscordBot> {
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 {
Expand All @@ -54,7 +54,7 @@ export class DiscordMessageEncoder extends MessageEncoder<DiscordBot> {
const url = await this.getUrl()
const result = await this.bot.http.post<Message>(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)

Expand Down Expand Up @@ -265,7 +265,7 @@ export class DiscordMessageEncoder extends MessageEncoder<DiscordBot> {
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 = {
Expand All @@ -279,15 +279,15 @@ export class DiscordMessageEncoder extends MessageEncoder<DiscordBot> {
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)),
`<t:${Math.ceil(quoted.timestamp / 1000)}:R> [[ ↑ ]](https://discord.com/channels/${this.guildId}/${channelId}/${replyId})`,
sanitize(parse(quote.elements.filter(v => v.type === 'text').join('')).slice(0, 30)),
`<t:${Math.ceil(quote.timestamp / 1000)}:R> [[ ↑ ]](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,
},
}]
}
Expand Down
80 changes: 39 additions & 41 deletions adapters/discord/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Discord.GuildMember>): 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 => ({
Expand All @@ -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),
Expand All @@ -54,29 +47,25 @@ export const encodeRole = (role: Partial<Universal.GuildRole>): Partial<Discord.
permissions: role.permissions && '' + role.permissions,
})

export async function decodeMessage(bot: DiscordBot, meta: Discord.Message, session: Partial<Session> = {}, 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()
}
})
Expand All @@ -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,
Expand Down Expand Up @@ -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<Session>, guildId: string) {
Expand Down Expand Up @@ -184,15 +182,15 @@ 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') {
session.type = 'message-updated'
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
Expand Down
2 changes: 1 addition & 1 deletion adapters/mail/src/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class MailBot extends Bot<MailBot.Config> {
}

async start() {
this.username = this.config.username
this.user.name = this.config.username
await super.start()
this.imap = new IMAP(
this.config,
Expand Down
29 changes: 17 additions & 12 deletions adapters/mail/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Universal.Message> {
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)
Expand Down Expand Up @@ -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)
Expand Down
16 changes: 7 additions & 9 deletions adapters/qqguild/src/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,13 @@ export class QQGuildBot extends Bot<QQGuildBot.Config> {

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
Expand Down
2 changes: 0 additions & 2 deletions adapters/qqguild/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 => ({
Expand Down
6 changes: 5 additions & 1 deletion adapters/slack/src/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,13 +120,17 @@ export class SlackBot<T extends SlackBot.Config = SlackBot.Config> extends Bot<T
return { data: channels.map(decodeChannel) }
}

async getGuild(guildId: string) {
async getGuild(guildId?: string) {
const { team } = await this.request<{ team: SlackTeam }>('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,
Expand Down
3 changes: 1 addition & 2 deletions adapters/slack/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
})
1 change: 1 addition & 0 deletions packages/core/src/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ const iterableMethods = [
for (const name of iterableMethods) {
Bot.prototype[name + 'Iter'] = function (this: Bot, ...args: any[]) {
let list: List<any>
if (!this[name + 'List']) throw new Error(`not implemented: ${name}List`)
const getList = async () => {
list = await this[name + 'List'](...args, list?.next)
}
Expand Down

0 comments on commit 36cb0e2

Please sign in to comment.