Skip to content

Commit

Permalink
feat(discord): add basic support for slash commands
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Jul 2, 2023
1 parent 3d6c6b4 commit e7569fd
Show file tree
Hide file tree
Showing 9 changed files with 301 additions and 154 deletions.
53 changes: 40 additions & 13 deletions adapters/discord/src/bot.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { Bot, Context, Fragment, h, Quester, Schema, SendOptions, Universal } from '@satorijs/satori'
import { adaptChannel, adaptGuild, adaptMessage, adaptUser, decodeRole, encodeRole } from './utils'
import { Bot, Context, Fragment, h, Logger, pick, Quester, Schema, SendOptions, Universal } from '@satorijs/satori'
import { decodeChannel, decodeGuild, decodeMessage, decodeRole, decodeUser, encodeRole } from './utils'
import * as Discord from './utils'
import { DiscordMessageEncoder } from './message'
import { Internal, Webhook } from './types'
import { WsClient } from './ws'

// @ts-ignore
import { version } from '../package.json'

const logger = new Logger('discord')

export class DiscordBot extends Bot<DiscordBot.Config> {
static MessageEncoder = DiscordMessageEncoder

Expand Down Expand Up @@ -58,7 +61,7 @@ export class DiscordBot extends Bot<DiscordBot.Config> {

async getSelf() {
const data = await this.internal.getCurrentUser()
return adaptUser(data)
return decodeUser(data)
}

async deleteMessage(channelId: string, messageId: string) {
Expand All @@ -79,35 +82,35 @@ export class DiscordBot extends Bot<DiscordBot.Config> {

async getMessage(channelId: string, messageId: string) {
const data = await this.internal.getChannelMessage(channelId, messageId)
return await adaptMessage(this, data)
return await decodeMessage(this, data)
}

async getMessageList(channelId: string, before?: string) {
// doesn't include `before` message
// 从旧到新
const data = (await this.internal.getChannelMessages(channelId, { before, limit: 50 })).reverse()
return await Promise.all(data.map(data => adaptMessage(this, data)))
return await Promise.all(data.map(data => decodeMessage(this, data)))
}

async getUser(userId: string) {
const data = await this.internal.getUser(userId)
return adaptUser(data)
return decodeUser(data)
}

async getGuildMemberList(guildId: string) {
const data = await this.internal.listGuildMembers(guildId)
return data.map(v => adaptUser(v.user))
return data.map(v => decodeUser(v.user))
}

async getChannel(channelId: string) {
const data = await this.internal.getChannel(channelId)
return adaptChannel(data)
return decodeChannel(data)
}

async getGuildMember(guildId: string, userId: string) {
const member = await this.internal.getGuildMember(guildId, userId)
return {
...adaptUser(member.user),
...decodeUser(member.user),
nickname: member.nick,
}
}
Expand All @@ -118,17 +121,17 @@ export class DiscordBot extends Bot<DiscordBot.Config> {

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

async getGuildList() {
const data = await this.internal.getCurrentUserGuilds()
return data.map(adaptGuild)
return data.map(decodeGuild)
}

async getChannelList(guildId: string) {
const data = await this.internal.getGuildChannels(guildId)
return data.map(adaptChannel)
return data.map(decodeChannel)
}

createReaction(channelId: string, messageId: string, emoji: string) {
Expand All @@ -153,7 +156,7 @@ export class DiscordBot extends Bot<DiscordBot.Config> {

async getReactions(channelId: string, messageId: string, emoji: string) {
const data = await this.internal.getReactions(channelId, messageId, emoji)
return data.map(adaptUser)
return data.map(decodeUser)
}

setGuildMemberRole(guildId: string, userId: string, roleId: string) {
Expand Down Expand Up @@ -188,6 +191,30 @@ export class DiscordBot extends Bot<DiscordBot.Config> {
})
return this.sendMessage(channel.id, content, null, options)
}

async syncCommands(commands: Universal.Command[]) {
const local = Object.fromEntries(commands.map(cmd => [cmd.name, cmd] as const))
const remote = Object.fromEntries((await this.internal.getGlobalApplicationCommands(this.selfId))
.filter(cmd => cmd.type === Discord.ApplicationCommand.Type.CHAT_INPUT)
.map(cmd => [cmd.name, cmd] as const))

for (const key in { ...local, ...remote }) {
if (!local[key]) {
logger.debug('deleting command %s', key)
await this.internal.deleteGlobalApplicationCommand(this.selfId, remote[key].id)
continue
}

const data = Discord.encodeCommand(local[key])
logger.debug('upsert command: %s', local[key].name)
logger.debug(data, remote[key])
if (!remote[key]) {
await this.internal.createGlobalApplicationCommand(this.selfId, data)
} else if (remote[key] && JSON.stringify(data) !== JSON.stringify(pick(remote[key], ['name', 'description', 'description_localizations', 'options']))) {
await this.internal.editGlobalApplicationCommand(this.selfId, remote[key].id, data)
}
}
}
}

export namespace DiscordBot {
Expand Down
3 changes: 1 addition & 2 deletions adapters/discord/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { DiscordBot } from './bot'
import * as Discord from './types'
import * as Discord from './utils'

export { Discord }

export * from './bot'
export * from './message'
export * from './utils'
export * from './ws'

export default DiscordBot
Expand Down
40 changes: 25 additions & 15 deletions adapters/discord/src/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Dict, h, Logger, MessageEncoder, Quester, Schema, segment, Session, Uni
import FormData from 'form-data'
import { DiscordBot } from './bot'
import { Channel, Message } from './types'
import { adaptMessage, sanitize } from './utils'
import { decodeMessage, sanitize } from './utils'

type RenderMode = 'default' | 'figure'

Expand All @@ -25,25 +25,35 @@ export class DiscordMessageEncoder extends MessageEncoder<DiscordBot> {
private figure: h = null
private mode: RenderMode = 'default'

async post(data?: any, headers?: any) {
try {
let url = `/channels/${this.channelId}/messages`
if (this.stack[0].author.nickname || this.stack[0].author.avatar || (this.stack[0].type === 'forward' && !this.stack[0].threadCreated)) {
private async getUrl() {
const input = this.options.session.discord
if (input?.t === 'INTERACTION_CREATE') {
// 消息交互
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) {
const webhook = await this.ensureWebhook()
url = `/webhooks/${webhook.id}/${webhook.token}?wait=true`
return `/webhooks/${webhook.id}/${webhook.token}?wait=true&thread_id=${this.stack[0].channel?.id}`
} else {
return `/channels/${this.stack[0].channel.id}/messages`
}
if (this.stack[0].type === 'forward' && this.stack[0].channel?.id) {
// 发送到子区
if (this.stack[1].author.nickname || this.stack[1].author.avatar) {
const webhook = await this.ensureWebhook()
url = `/webhooks/${webhook.id}/${webhook.token}?wait=true&thread_id=${this.stack[0].channel?.id}`
} else {
url = `/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)) {
const webhook = await this.ensureWebhook()
return `/webhooks/${webhook.id}/${webhook.token}?wait=true`
} else {
return `/channels/${this.channelId}/messages`
}
}
}

async post(data?: any, headers?: any) {
try {
const url = await this.getUrl()
const result = await this.bot.http.post<Message>(url, data, { headers })
const session = this.bot.session()
const message = await adaptMessage(this.bot, result, session)
const message = await decodeMessage(this.bot, result, session)
session.app.emit(session, 'send', session)
this.results.push(session)

Expand Down
17 changes: 10 additions & 7 deletions adapters/discord/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,13 @@ export type snowflake = string
export type timestamp = string

/** @see https://discord.com/developers/docs/reference#locales */
export type Locale =
| 'da' | 'de' | 'en-GB' | 'en-US' | 'es-ES'
| 'fr' | 'hr' | 'it' | 'lt' | 'hu'
| 'nl' | 'no' | 'pl' | 'pt-BR' | 'ro'
| 'fi' | 'sv-SE' | 'vi' | 'tr' | 'cs'
| 'el' | 'bg' | 'ru' | 'uk' | 'hi'
| 'th' | 'zh-CN' | 'ja' | 'zh-TW' | 'ko'
export type Locale = typeof Locale[number]

export const Locale = [
'da', 'de', 'en-GB', 'en-US', 'es-ES',
'fr', 'hr', 'it', 'lt', 'hu',
'nl', 'no', 'pl', 'pt-BR', 'ro',
'fi', 'sv-SE', 'vi', 'tr', 'cs',
'el', 'bg', 'ru', 'uk', 'hi',
'th', 'zh-CN', 'ja', 'zh-TW', 'ko',
] as const
Loading

0 comments on commit e7569fd

Please sign in to comment.