Skip to content

Commit

Permalink
Merge pull request #14 from tbodt/pona
Browse files Browse the repository at this point in the history
QOL improvements
  • Loading branch information
maddysrc authored Jun 23, 2024
2 parents 0835f33 + 0b6e1e5 commit 58700e9
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 136 deletions.
77 changes: 7 additions & 70 deletions src/commands/RestoreCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,19 @@ import {
RegisterBehavior,
} from "@sapphire/framework"
import {
type APIMessage,
ApplicationCommandType,
type BaseMessageOptions,
ButtonStyle,
CategoryChannel,
ChatInputCommandInteraction,
ComponentType,
ContextMenuCommandBuilder,
ContextMenuCommandInteraction,
type GuildBasedChannel,
GuildMember,
Message,
PermissionFlagsBits,
SlashCommandBuilder,
time,
} from "discord.js"
import { getSelf } from "../lib/guilds/getSelf"
import { fetchAndRestoreMessage } from "../lib/messages/fetchAndRestoreMessage"
import {
fetchAndRestoreMessage,
restoreMessageAndReply,
RestoreMode,
} from "../lib/messages/fetchAndRestoreMessage"
import { parseMessageOption } from "../lib/messages/parseMessageOption"
import { restoreMessage } from "../lib/messages/restoreMessage"
import { fetchWebhooks } from "../lib/webhooks/fetchWebhooks"

export class RestoreCommand extends Command {
constructor(context: PieceContext) {
Expand All @@ -44,70 +36,15 @@ export class RestoreCommand extends Command {
const [channelId, messageId] = await parseMessageOption(interaction)
if (!channelId || !messageId) return

fetchAndRestoreMessage(interaction, channelId, messageId, false)
fetchAndRestoreMessage(interaction, channelId, messageId, RestoreMode.Restore)
}

override async contextMenuRun(interaction: ContextMenuCommandInteraction) {
if (!interaction.isMessageContextMenuCommand()) return

await interaction.deferReply({ ephemeral: true })

const response = await restoreMessage(
interaction.targetMessage as APIMessage | Message,
)

const channel = interaction.targetMessage.channel as Exclude<
GuildBasedChannel,
CategoryChannel
>

const components: BaseMessageOptions["components"] = []
const webhookId = interaction.targetMessage.webhookId

if (
webhookId &&
interaction.guild &&
channel
.permissionsFor(await getSelf(interaction.guild))
.has(PermissionFlagsBits.ManageWebhooks)
) {
const member =
interaction.member instanceof GuildMember
? interaction.member
: await interaction.guild.members.fetch(interaction.user.id)

if (
channel.permissionsFor(member).has(PermissionFlagsBits.ManageWebhooks)
) {
const webhooks = await fetchWebhooks(channel)

if (webhooks.some((webhook) => webhook.id === webhookId)) {
components.push({
type: ComponentType.ActionRow,
components: [
{
type: ComponentType.Button,
style: ButtonStyle.Secondary,
label: "Quick Edit",
customId: `@discohook/restore-quick-edit/${channel.id}-${interaction.targetId}`,
},
],
})
}
}
}

await interaction.editReply({
embeds: [
{
title: "Restored message",
description:
`The restored message can be found at ${response.url}. This link ` +
`will expire ${time(new Date(response.expires), "R")}.`,
},
],
components,
})
await restoreMessageAndReply(interaction, interaction.targetMessage, RestoreMode.Open)
}

override async registerApplicationCommands(
Expand Down
4 changes: 2 additions & 2 deletions src/interaction-handlers/RestoreQuickEditHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
type PieceContext,
} from "@sapphire/framework"
import type { ButtonInteraction, Snowflake } from "discord.js"
import { fetchAndRestoreMessage } from "../lib/messages/fetchAndRestoreMessage"
import { fetchAndRestoreMessage, RestoreMode } from "../lib/messages/fetchAndRestoreMessage"

type RestoreQuickEditOptions = {
channelId: Snowflake
Expand All @@ -29,7 +29,7 @@ export class RestoreQuickEditHandler extends InteractionHandler {
interaction,
options.channelId,
options.messageId,
true,
RestoreMode.QuickEdit
)
}

Expand Down
90 changes: 44 additions & 46 deletions src/lib/messages/fetchAndRestoreMessage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
type APIMessage,
type BaseMessageOptions,
ButtonStyle,
CommandInteraction,
Expand All @@ -8,9 +7,8 @@ import {
Message,
MessageComponentInteraction,
PermissionFlagsBits,
PermissionsBitField,
ThreadChannel,
time,
ThreadChannel,
Webhook,
} from "discord.js"
import { getSelf } from "../guilds/getSelf"
Expand All @@ -19,34 +17,39 @@ import { fetchWebhooks } from "../webhooks/fetchWebhooks"
import { fetchMessage } from "./fetchMessage"
import { restoreMessage } from "./restoreMessage"

export const fetchAndRestoreMessage = async (
export enum RestoreMode {
// Just restore the message
Restore,
// Require that the message is from a webhook, and include the webhook
QuickEdit,
// Act like quick edit if the message is from a webhook, and like restore otherwise
Open,
}

export const restoreMessageAndReply = async (
interaction: CommandInteraction | MessageComponentInteraction,
channelId: string,
messageId: string,
quickEdit = false,
message: Message,
mode: RestoreMode = RestoreMode.Restore,
) => {
const message = await fetchMessage(interaction, channelId, messageId)
if (!message) return

const selfPermissions =
"guild" in message.channel && message.channel.guild
? message.channel.permissionsFor(await getSelf(message.channel.guild))
: new PermissionsBitField(PermissionsBitField.Default)

let webhook: Webhook | undefined = undefined
const components: BaseMessageOptions["components"] = []

if (
message.webhookId &&
selfPermissions.has(PermissionFlagsBits.ManageWebhooks)
message.inGuild() &&
// Check permissions for the bot
message.channel
.permissionsFor(await getSelf(message.channel.guild))
.has(PermissionFlagsBits.ManageWebhooks)
) {
// Now check permissions for the member triggering this
const member =
interaction.member instanceof GuildMember
? interaction.member
: await interaction.guild?.members.fetch(interaction.user.id)

if (
member &&
"guild" in message.channel &&
message.channel
.permissionsFor(member)
.has(PermissionFlagsBits.ManageWebhooks)
Expand All @@ -58,23 +61,23 @@ export const fetchAndRestoreMessage = async (
const webhooks = await fetchWebhooks(root!)

webhook = webhooks.find((webhook) => webhook.id === message.webhookId)
if (webhook && !quickEdit) {
if (webhook && mode == RestoreMode.Restore) {
components.push({
type: ComponentType.ActionRow,
components: [
{
type: ComponentType.Button,
style: ButtonStyle.Secondary,
label: "Quick Edit",
customId: `@discohook/restore-quick-edit/${channelId}-${messageId}`,
customId: `@discohook/restore-quick-edit/${message.channelId}-${message.id}`,
},
],
})
}
}
}

if (!webhook && quickEdit) {
if (mode == RestoreMode.QuickEdit && !webhook) {
await reply(interaction, {
content:
"I can't find the webhook this message belongs to, therefore " +
Expand All @@ -84,25 +87,10 @@ export const fetchAndRestoreMessage = async (
}

if (message.content || message.embeds.length > 0) {
const response = await restoreMessage(
message,
quickEdit ? webhook : undefined,
)
await reply(interaction, {
embeds: [
{
title: "Restored message",
description:
`The restored message can be found at ${response.url}. This link ` +
`will expire ${time(new Date(response.expires), "R")}.`,
},
],
components,
})
return
}

if (!webhook) {
message = message
} else if (webhook) {
message = await webhook.fetchMessage(message.id)
} else {
await reply(interaction, {
content:
"I can't read the message because of Discord's privacy restrictions. " +
Expand All @@ -112,20 +100,30 @@ export const fetchAndRestoreMessage = async (
return
}

const webhookMessage = await webhook.fetchMessage(messageId)
const response = await restoreMessage(
webhookMessage as Message | APIMessage,
quickEdit ? webhook : undefined,
)
const editTarget = mode == RestoreMode.Restore ? undefined : webhook
const response = await restoreMessage(message, editTarget)

await reply(interaction, {
embeds: [
{
title: "Restored message",
title: editTarget ? "Opened for editing" : "Restored message",
description:
`The restored message can be found at ${response.url}. This link ` +
`The message editor can be found at ${response.url}. This link ` +
`will expire ${time(new Date(response.expires), "R")}.`,
},
],
components,
})
}

export const fetchAndRestoreMessage = async (
interaction: CommandInteraction | MessageComponentInteraction,
channelId: string,
messageId: string,
mode: RestoreMode = RestoreMode.Restore,
) => {
const message = await fetchMessage(interaction, channelId, messageId)
if (!message) return

restoreMessageAndReply(interaction, message, mode)
}
32 changes: 14 additions & 18 deletions src/lib/messages/restoreMessage.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
import { URL } from "node:url"
import { fetch } from "@sapphire/fetch"
import { deepClone } from "@sapphire/utilities"
import { type APIMessage, Embed, Message, Webhook } from "discord.js"
import { ThreadChannel, Message, Webhook } from "discord.js"

export const restoreMessage = async (
message: APIMessage | Message,
target?: Webhook,
) => {
const embeds = message.embeds.map((embed) => {
if (embed instanceof Embed) {
embed = embed.toJSON()
} else {
embed = deepClone(embed)
}
export const restoreMessage = async (message: Message, target?: Webhook) => {
const embeds = message.embeds.map((embedObject) => {
const embed = embedObject.toJSON()

delete embed.type
delete embed.video
Expand All @@ -28,21 +21,24 @@ export const restoreMessage = async (
return embed
})

let webhookUrl = target?.url
if (webhookUrl && message.channel instanceof ThreadChannel) {
let newUrl = new URL(webhookUrl)
newUrl.searchParams.set("thread_id", message.channel.id)
webhookUrl = newUrl.toString()
}

const data = JSON.stringify({
messages: [
{
data: {
content: message.content || undefined,
embeds: embeds.length === 0 ? undefined : embeds,
},
reference: target
? message instanceof Message
? message.url
: message.id
: undefined,
reference: target ? message.url : undefined,
},
],
targets: target ? [{ url: target.url }] : undefined,
targets: [{ url: webhookUrl }],
})
const encodedData = Buffer.from(data, "utf-8").toString("base64url")
const url = `https://discohook.app/?data=${encodedData}`
Expand Down

0 comments on commit 58700e9

Please sign in to comment.