Skip to content

Commit

Permalink
feat(webhook): add support for webhooks in guilds
Browse files Browse the repository at this point in the history
  • Loading branch information
c43721 committed Nov 17, 2024
1 parent affc3ce commit 70eb178
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 5 deletions.
173 changes: 173 additions & 0 deletions src/commands/Admin/webhooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import { type CommandOptions, Command } from "@sapphire/framework";
import { ApplyOptions } from "@sapphire/decorators";
import { EmbedBuilder, codeBlock, InteractionContextType, ChannelType, PermissionFlagsBits } from "discord.js";
import { isNullOrUndefinedOrEmpty } from "@sapphire/utilities";
import { PayloadCommand } from "#lib/structs/commands/PayloadCommand";
import { LanguageKeys } from "#lib/i18n/all";
import PayloadColors from "#utils/colors";
import { guild, webhook } from "#root/drizzle/schema";
import { eq } from "drizzle-orm";
import { fetchT, getLocalizedData } from "@sapphire/plugin-i18next";
import { generate } from "generate-password";
import { sendTest } from "#lib/api/utils/webhook-helper";

@ApplyOptions<CommandOptions>({
description: LanguageKeys.Commands.Webhook.Description,
detailedDescription: LanguageKeys.Commands.Webhook.DetailedDescription,
})
export class UserCommand extends PayloadCommand {
async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
const t = await fetchT(interaction);
const { client } = this.container;

const [guildWebhook] = await this.database
.select({ webhookId: guild.webhookId })
.from(guild)
.where(eq(guild.id, interaction.guildId));

switch (interaction.commandName) {
case "add": {
const channel = interaction.options.getChannel("channel");

const didSucceed = await sendTest(this.container.client, "channels", channel?.id);

if (!didSucceed) {
const embed = new EmbedBuilder({
author: {
name: client.user.username,
iconURL: client.user.displayAvatarURL(),
},
title: t(LanguageKeys.Commands.Webhook.EmbedTitle),
description: t(LanguageKeys.Commands.GuildWebhook.AddFailed),
color: PayloadColors.Payload,
});

await interaction.reply({ embeds: [embed], ephemeral: true });

return;
}

const secret = generate({ numbers: true, length: 24 });

const [createdWebhook] = await this.database
.insert(webhook)
.values({
id: interaction.guildId,
type: "channels",
value: secret,
})
.returning();

await this.database
.insert(guild)
.values({
id: channel.id,
webhookId: createdWebhook.id,
})
.onConflictDoUpdate({
set: {
webhookId: createdWebhook.id,
},
target: guild.id,
});

const embed = new EmbedBuilder({
title: t(LanguageKeys.Commands.Webhook.EmbedTitle),
description: codeBlock(createdWebhook.value),
color: PayloadColors.Payload,
});

await interaction.reply({ embeds: [embed], ephemeral: true });

return;
}
case "delete": {
await this.container.database.delete(webhook).where(eq(webhook.id, interaction.user.id));

await interaction.reply({
content: t(LanguageKeys.Commands.Webhook.DeletedWebhook),
ephemeral: true,
});

return;
}
case "show": {
const embed = new EmbedBuilder({
title: t(LanguageKeys.Commands.Webhook.EmbedTitle),
description: t(LanguageKeys.Commands.Webhook.NoWebhook),
color: PayloadColors.Payload,
});

if (!isNullOrUndefinedOrEmpty(guildWebhook)) {
const [wbhk] = await this.database
.select({ value: webhook.value })
.from(webhook)
.where(eq(webhook.id, guildWebhook.webhookId));

embed.setDescription(codeBlock(wbhk.value));
}

await interaction.reply({ embeds: [embed], ephemeral: true });

return;
}
}
}

public override registerApplicationCommands(registry: Command.Registry) {
const rootNameLocalizations = getLocalizedData(LanguageKeys.Commands.GuildWebhook.Name);
const rootDescriptionLocalizations = getLocalizedData(this.description);

const addNameLocalizations = getLocalizedData(LanguageKeys.Commands.GuildWebhook.AddName);
const addDescriptionLocalizations = getLocalizedData(LanguageKeys.Commands.GuildWebhook.AddDescription);
const channelIdNameLocalizations = getLocalizedData(LanguageKeys.Commands.GuildWebhook.AddChannelIdName);
const channelIdDescriptionLocalizations = getLocalizedData(
LanguageKeys.Commands.GuildWebhook.AddChannelIdDescription,
);

const removeNameLocalizations = getLocalizedData(LanguageKeys.Commands.GuildWebhook.RemoveName);
const removeDescriptionLocalizations = getLocalizedData(LanguageKeys.Commands.GuildWebhook.RemoveDescription);

const showNameLocalizations = getLocalizedData(LanguageKeys.Commands.GuildWebhook.ShowName);
const showDescriptionLocalizations = getLocalizedData(LanguageKeys.Commands.GuildWebhook.ShowDescription);

registry.registerChatInputCommand(builder =>
builder
.setName(this.name)
.setDescription(rootDescriptionLocalizations.localizations["en-US"])
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
.setDescriptionLocalizations(rootDescriptionLocalizations.localizations)
.setNameLocalizations(rootNameLocalizations.localizations)
.addSubcommand(sub =>
sub
.setName("add")
.setDescription(addDescriptionLocalizations.localizations["en-US"])
.setNameLocalizations(addNameLocalizations.localizations)
.setDescriptionLocalizations(addDescriptionLocalizations.localizations)
.addChannelOption(input =>
input
.setName("channel")
.setDescription(channelIdDescriptionLocalizations.localizations["en-US"])
.setNameLocalizations(channelIdNameLocalizations.localizations)
.setDescriptionLocalizations(channelIdDescriptionLocalizations.localizations)
.addChannelTypes(ChannelType.GuildText),
),
)
.addSubcommand(sub =>
sub
.setName("delete")
.setDescription(removeDescriptionLocalizations.localizations["en-US"])
.setNameLocalizations(removeNameLocalizations.localizations)
.setDescriptionLocalizations(removeDescriptionLocalizations.localizations),
)
.addSubcommand(sub =>
sub
.setName("show")
.setDescription(showDescriptionLocalizations.localizations["en-US"])
.setNameLocalizations(showNameLocalizations.localizations)
.setDescriptionLocalizations(showDescriptionLocalizations.localizations),
),
);
}
}
File renamed without changes.
File renamed without changes.
17 changes: 17 additions & 0 deletions src/languages/en-US/commands/guild-webhook.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "guildwebhook",
"description": "Provides a set of actions to work with the Payload webhook integration",
"detailedDescription": {
"usages": [],
"details": "See more on (Github)[https://github.com/payload-bot/payload-logs-plugin]"
},
"addName": "add",
"addDescription": "Creates a webhook for a given channel",
"addChannelIdName": "channelid",
"addChannelIdDescription": "The channel id to send webhooks to",
"addFailed": "Failed to create the webhook",
"removeName": "remove",
"removeDescription": "Removes the webhook",
"showName": "show",
"showDescription": "Shows the webhook secret for the channel"
}
15 changes: 11 additions & 4 deletions src/lib/api/utils/webhook-helper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import config from "#root/config";
import { EmbedColors } from "#utils/colors";
import { capturePage } from "#utils/screenshot";
import { container } from "@sapphire/pieces";
import {
type Client,
type TextChannel,
Expand Down Expand Up @@ -125,15 +126,19 @@ export async function sendLogPreview(client: Client, { logsId, targetId, demosId
}

export async function sendTest(client: Client, scope: WebhookTargetType, id: string) {
if (id == null) {
return false;
}

const target = (await client[scope].fetch(id).catch(() => null)) as TargetReturnType;

if (target == null) {
throw new Error("Bad discord target id");
return false;
}

const embed = new EmbedBuilder({
title: "Webhook Test",
description: "Successful webhook test!",
description: "You will receive log previews in this channel",
footer: {
text: "Rendered from Webhook",
},
Expand All @@ -142,9 +147,11 @@ export async function sendTest(client: Client, scope: WebhookTargetType, id: str
});

try {
await sendWebhook(target, embed, null, null);
const data = await sendWebhook(target, embed, null, null);
return data != null;
} catch (err) {
throw err;
container.logger.error(err);
return false;
}
}

Expand Down
15 changes: 15 additions & 0 deletions src/lib/i18n/all/keys/commands/guild-webhook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { T } from "#lib/types";

export const Name = T<string>("commands/guild-webhook:name");
export const Description = T<string>("commands/guild-webhook:description");
export const DetailedDescription = T<string>("commands/guild-webhook:detailedDescription");

export const AddName = T<string>("commands/guild-webhook:addName");
export const AddDescription = T<string>("commands/guild-webhook:addDescription");
export const AddChannelIdName = T<string>("commands/guild-webhook:addChannelIdName");
export const AddChannelIdDescription = T<string>("commands/guild-webhook:addChannelIdDescription");
export const AddFailed = T<string>("commands/guild-webhook:addFailed");
export const RemoveName = T<string>("commands/guild-webhook:removeName");
export const RemoveDescription = T<string>("commands/guild-webhook:removeDescription");
export const ShowName = T<string>("commands/guild-webhook:removeName");
export const ShowDescription = T<string>("commands/guild-webhook:removeDescription");
1 change: 1 addition & 0 deletions src/lib/i18n/all/keys/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export * as Settings from "./settings.js";
export * as Invite from "./invite.js";
export * as Help from "./help.js";
export * as Webhook from "./webhook.js";
export * as GuildWebhook from "./guild-webhook.js";
2 changes: 1 addition & 1 deletion src/lib/i18n/all/keys/commands/webhook.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FT, T } from "#lib/types";
import { T } from "#lib/types";

export const Name = T<string>("commands/webhook:name");
export const Description = T<string>("commands/webhook:description");
Expand Down

0 comments on commit 70eb178

Please sign in to comment.