Skip to content

Commit 58700e9

Browse files
authored
Merge pull request #14 from tbodt/pona
QOL improvements
2 parents 0835f33 + 0b6e1e5 commit 58700e9

File tree

4 files changed

+67
-136
lines changed

4 files changed

+67
-136
lines changed

src/commands/RestoreCommand.ts

+7-70
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,19 @@ import {
55
RegisterBehavior,
66
} from "@sapphire/framework"
77
import {
8-
type APIMessage,
98
ApplicationCommandType,
10-
type BaseMessageOptions,
11-
ButtonStyle,
12-
CategoryChannel,
139
ChatInputCommandInteraction,
14-
ComponentType,
1510
ContextMenuCommandBuilder,
1611
ContextMenuCommandInteraction,
17-
type GuildBasedChannel,
18-
GuildMember,
19-
Message,
2012
PermissionFlagsBits,
2113
SlashCommandBuilder,
22-
time,
2314
} from "discord.js"
24-
import { getSelf } from "../lib/guilds/getSelf"
25-
import { fetchAndRestoreMessage } from "../lib/messages/fetchAndRestoreMessage"
15+
import {
16+
fetchAndRestoreMessage,
17+
restoreMessageAndReply,
18+
RestoreMode,
19+
} from "../lib/messages/fetchAndRestoreMessage"
2620
import { parseMessageOption } from "../lib/messages/parseMessageOption"
27-
import { restoreMessage } from "../lib/messages/restoreMessage"
28-
import { fetchWebhooks } from "../lib/webhooks/fetchWebhooks"
2921

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

47-
fetchAndRestoreMessage(interaction, channelId, messageId, false)
39+
fetchAndRestoreMessage(interaction, channelId, messageId, RestoreMode.Restore)
4840
}
4941

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

5345
await interaction.deferReply({ ephemeral: true })
5446

55-
const response = await restoreMessage(
56-
interaction.targetMessage as APIMessage | Message,
57-
)
58-
59-
const channel = interaction.targetMessage.channel as Exclude<
60-
GuildBasedChannel,
61-
CategoryChannel
62-
>
63-
64-
const components: BaseMessageOptions["components"] = []
65-
const webhookId = interaction.targetMessage.webhookId
66-
67-
if (
68-
webhookId &&
69-
interaction.guild &&
70-
channel
71-
.permissionsFor(await getSelf(interaction.guild))
72-
.has(PermissionFlagsBits.ManageWebhooks)
73-
) {
74-
const member =
75-
interaction.member instanceof GuildMember
76-
? interaction.member
77-
: await interaction.guild.members.fetch(interaction.user.id)
78-
79-
if (
80-
channel.permissionsFor(member).has(PermissionFlagsBits.ManageWebhooks)
81-
) {
82-
const webhooks = await fetchWebhooks(channel)
83-
84-
if (webhooks.some((webhook) => webhook.id === webhookId)) {
85-
components.push({
86-
type: ComponentType.ActionRow,
87-
components: [
88-
{
89-
type: ComponentType.Button,
90-
style: ButtonStyle.Secondary,
91-
label: "Quick Edit",
92-
customId: `@discohook/restore-quick-edit/${channel.id}-${interaction.targetId}`,
93-
},
94-
],
95-
})
96-
}
97-
}
98-
}
99-
100-
await interaction.editReply({
101-
embeds: [
102-
{
103-
title: "Restored message",
104-
description:
105-
`The restored message can be found at ${response.url}. This link ` +
106-
`will expire ${time(new Date(response.expires), "R")}.`,
107-
},
108-
],
109-
components,
110-
})
47+
await restoreMessageAndReply(interaction, interaction.targetMessage, RestoreMode.Open)
11148
}
11249

11350
override async registerApplicationCommands(

src/interaction-handlers/RestoreQuickEditHandler.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
type PieceContext,
55
} from "@sapphire/framework"
66
import type { ButtonInteraction, Snowflake } from "discord.js"
7-
import { fetchAndRestoreMessage } from "../lib/messages/fetchAndRestoreMessage"
7+
import { fetchAndRestoreMessage, RestoreMode } from "../lib/messages/fetchAndRestoreMessage"
88

99
type RestoreQuickEditOptions = {
1010
channelId: Snowflake
@@ -29,7 +29,7 @@ export class RestoreQuickEditHandler extends InteractionHandler {
2929
interaction,
3030
options.channelId,
3131
options.messageId,
32-
true,
32+
RestoreMode.QuickEdit
3333
)
3434
}
3535

+44-46
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import {
2-
type APIMessage,
32
type BaseMessageOptions,
43
ButtonStyle,
54
CommandInteraction,
@@ -8,9 +7,8 @@ import {
87
Message,
98
MessageComponentInteraction,
109
PermissionFlagsBits,
11-
PermissionsBitField,
12-
ThreadChannel,
1310
time,
11+
ThreadChannel,
1412
Webhook,
1513
} from "discord.js"
1614
import { getSelf } from "../guilds/getSelf"
@@ -19,34 +17,39 @@ import { fetchWebhooks } from "../webhooks/fetchWebhooks"
1917
import { fetchMessage } from "./fetchMessage"
2018
import { restoreMessage } from "./restoreMessage"
2119

22-
export const fetchAndRestoreMessage = async (
20+
export enum RestoreMode {
21+
// Just restore the message
22+
Restore,
23+
// Require that the message is from a webhook, and include the webhook
24+
QuickEdit,
25+
// Act like quick edit if the message is from a webhook, and like restore otherwise
26+
Open,
27+
}
28+
29+
export const restoreMessageAndReply = async (
2330
interaction: CommandInteraction | MessageComponentInteraction,
24-
channelId: string,
25-
messageId: string,
26-
quickEdit = false,
31+
message: Message,
32+
mode: RestoreMode = RestoreMode.Restore,
2733
) => {
28-
const message = await fetchMessage(interaction, channelId, messageId)
29-
if (!message) return
30-
31-
const selfPermissions =
32-
"guild" in message.channel && message.channel.guild
33-
? message.channel.permissionsFor(await getSelf(message.channel.guild))
34-
: new PermissionsBitField(PermissionsBitField.Default)
35-
3634
let webhook: Webhook | undefined = undefined
3735
const components: BaseMessageOptions["components"] = []
36+
3837
if (
3938
message.webhookId &&
40-
selfPermissions.has(PermissionFlagsBits.ManageWebhooks)
39+
message.inGuild() &&
40+
// Check permissions for the bot
41+
message.channel
42+
.permissionsFor(await getSelf(message.channel.guild))
43+
.has(PermissionFlagsBits.ManageWebhooks)
4144
) {
45+
// Now check permissions for the member triggering this
4246
const member =
4347
interaction.member instanceof GuildMember
4448
? interaction.member
4549
: await interaction.guild?.members.fetch(interaction.user.id)
4650

4751
if (
4852
member &&
49-
"guild" in message.channel &&
5053
message.channel
5154
.permissionsFor(member)
5255
.has(PermissionFlagsBits.ManageWebhooks)
@@ -58,23 +61,23 @@ export const fetchAndRestoreMessage = async (
5861
const webhooks = await fetchWebhooks(root!)
5962

6063
webhook = webhooks.find((webhook) => webhook.id === message.webhookId)
61-
if (webhook && !quickEdit) {
64+
if (webhook && mode == RestoreMode.Restore) {
6265
components.push({
6366
type: ComponentType.ActionRow,
6467
components: [
6568
{
6669
type: ComponentType.Button,
6770
style: ButtonStyle.Secondary,
6871
label: "Quick Edit",
69-
customId: `@discohook/restore-quick-edit/${channelId}-${messageId}`,
72+
customId: `@discohook/restore-quick-edit/${message.channelId}-${message.id}`,
7073
},
7174
],
7275
})
7376
}
7477
}
7578
}
7679

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

8689
if (message.content || message.embeds.length > 0) {
87-
const response = await restoreMessage(
88-
message,
89-
quickEdit ? webhook : undefined,
90-
)
91-
await reply(interaction, {
92-
embeds: [
93-
{
94-
title: "Restored message",
95-
description:
96-
`The restored message can be found at ${response.url}. This link ` +
97-
`will expire ${time(new Date(response.expires), "R")}.`,
98-
},
99-
],
100-
components,
101-
})
102-
return
103-
}
104-
105-
if (!webhook) {
90+
message = message
91+
} else if (webhook) {
92+
message = await webhook.fetchMessage(message.id)
93+
} else {
10694
await reply(interaction, {
10795
content:
10896
"I can't read the message because of Discord's privacy restrictions. " +
@@ -112,20 +100,30 @@ export const fetchAndRestoreMessage = async (
112100
return
113101
}
114102

115-
const webhookMessage = await webhook.fetchMessage(messageId)
116-
const response = await restoreMessage(
117-
webhookMessage as Message | APIMessage,
118-
quickEdit ? webhook : undefined,
119-
)
103+
const editTarget = mode == RestoreMode.Restore ? undefined : webhook
104+
const response = await restoreMessage(message, editTarget)
105+
120106
await reply(interaction, {
121107
embeds: [
122108
{
123-
title: "Restored message",
109+
title: editTarget ? "Opened for editing" : "Restored message",
124110
description:
125-
`The restored message can be found at ${response.url}. This link ` +
111+
`The message editor can be found at ${response.url}. This link ` +
126112
`will expire ${time(new Date(response.expires), "R")}.`,
127113
},
128114
],
129115
components,
130116
})
131117
}
118+
119+
export const fetchAndRestoreMessage = async (
120+
interaction: CommandInteraction | MessageComponentInteraction,
121+
channelId: string,
122+
messageId: string,
123+
mode: RestoreMode = RestoreMode.Restore,
124+
) => {
125+
const message = await fetchMessage(interaction, channelId, messageId)
126+
if (!message) return
127+
128+
restoreMessageAndReply(interaction, message, mode)
129+
}

src/lib/messages/restoreMessage.ts

+14-18
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,10 @@
1+
import { URL } from "node:url"
12
import { fetch } from "@sapphire/fetch"
2-
import { deepClone } from "@sapphire/utilities"
3-
import { type APIMessage, Embed, Message, Webhook } from "discord.js"
3+
import { ThreadChannel, Message, Webhook } from "discord.js"
44

5-
export const restoreMessage = async (
6-
message: APIMessage | Message,
7-
target?: Webhook,
8-
) => {
9-
const embeds = message.embeds.map((embed) => {
10-
if (embed instanceof Embed) {
11-
embed = embed.toJSON()
12-
} else {
13-
embed = deepClone(embed)
14-
}
5+
export const restoreMessage = async (message: Message, target?: Webhook) => {
6+
const embeds = message.embeds.map((embedObject) => {
7+
const embed = embedObject.toJSON()
158

169
delete embed.type
1710
delete embed.video
@@ -28,21 +21,24 @@ export const restoreMessage = async (
2821
return embed
2922
})
3023

24+
let webhookUrl = target?.url
25+
if (webhookUrl && message.channel instanceof ThreadChannel) {
26+
let newUrl = new URL(webhookUrl)
27+
newUrl.searchParams.set("thread_id", message.channel.id)
28+
webhookUrl = newUrl.toString()
29+
}
30+
3131
const data = JSON.stringify({
3232
messages: [
3333
{
3434
data: {
3535
content: message.content || undefined,
3636
embeds: embeds.length === 0 ? undefined : embeds,
3737
},
38-
reference: target
39-
? message instanceof Message
40-
? message.url
41-
: message.id
42-
: undefined,
38+
reference: target ? message.url : undefined,
4339
},
4440
],
45-
targets: target ? [{ url: target.url }] : undefined,
41+
targets: [{ url: webhookUrl }],
4642
})
4743
const encodedData = Buffer.from(data, "utf-8").toString("base64url")
4844
const url = `https://discohook.app/?data=${encodedData}`

0 commit comments

Comments
 (0)