src/interactions/menus/highlight.ts (view raw)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
import { GuildModel } from "@/database/schemas";
import { Logger } from "@/utils/logging";
import {
ApplicationCommandType,
type Attachment,
type Client,
ContextMenuCommandBuilder,
type GuildTextBasedChannel,
type MessageContextMenuCommandInteraction,
} from "discord.js";
const logger = new Logger("highlight-menu");
const commandData = new ContextMenuCommandBuilder()
.setName("highlight")
.setType(ApplicationCommandType.Message);
function extractVideoLinks(message: { content: string }): string[] {
const urlRegex =
/https?:\/\/(?:www\.)?(?:youtube\.com\/watch\?v=[\w-]+|youtu\.be\/[\w-]+|medal\.tv\/(?:games\/[\w-]+\/clips\/[\w-]+|g\/[\w-]+|clips\/[\w-]+))/gi;
return Array.from(message.content.matchAll(urlRegex)).map((m) => m[0]);
}
function isVideoAttachment(attachment: Attachment): boolean {
return (
typeof attachment.contentType === "string" &&
attachment.contentType.startsWith("video/")
);
}
async function execute(
client: Client,
interaction: MessageContextMenuCommandInteraction,
) {
if (!interaction.guild) {
await interaction.reply({
content: "❌ this command can only be used in a server.",
ephemeral: true,
});
return;
}
await interaction.deferReply({ flags: ["Ephemeral"] });
const guildConfig = await GuildModel.findOne({
guild_id: interaction.guild.id,
});
if (!guildConfig?.highlights_channel) {
await interaction.editReply({
content:
"❌ no highlights channel configured. Use `/config highlight-channel`.",
});
return;
}
const targetMessage = interaction.targetMessage;
if (!targetMessage) {
await interaction.editReply({
content: "❌ couldn't find the target message.",
});
return;
}
const videoAttachments = targetMessage.attachments.filter(isVideoAttachment);
const videoLinks = extractVideoLinks(targetMessage);
if (videoAttachments.size === 0 && videoLinks.length === 0) {
await interaction.editReply({
content:
"❌ no video attachments or supported links found in this message.",
});
return;
}
let highlightsChannel: GuildTextBasedChannel | null = null;
try {
highlightsChannel = (await client.channels.fetch(
guildConfig.highlights_channel,
)) as GuildTextBasedChannel | null;
} catch (e) {
logger.error("failed to fetch highlights channel:", e);
await interaction.editReply({
content: "❌ could not access the highlights channel.",
});
return;
}
if (!highlightsChannel) {
await interaction.editReply({
content: "❌ highlights channel not found.",
});
return;
}
const files = Array.from(videoAttachments.values()).map((a) => a.url);
const author = `<@${targetMessage.author.id}>`;
const jumpUrl = targetMessage.url;
const videoLinksText = videoLinks
.map((link) => `• [video Link](${link})`)
.join(" ");
const description = `:star: new highlight from ${author}!\n-# [jump to message](${jumpUrl}) ${videoLinksText}`;
try {
const msg = await highlightsChannel.send({
content: description,
files: files.length > 0 ? files : undefined,
allowedMentions: { users: [targetMessage.author.id] },
});
await msg.react("⭐");
await interaction.editReply({
content: "✅ highlight sent!",
});
} catch (error) {
logger.error("failed to send highlight:", error);
await interaction.editReply({
content: "❌ failed to send highlight.",
});
}
}
export default {
data: commandData,
execute,
};
|