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 126 127 128 129 130 131 132 133 134 |
import { GuildModel } from "@/database/schemas";
import { Logger } from "@/utils/logging";
import {
ApplicationCommandType,
type Attachment,
type Channel,
ChannelType,
type Client,
ContextMenuCommandBuilder,
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: Channel | null = null;
try {
highlightsChannel = await client.channels.fetch(
guildConfig.highlights_channel,
);
} 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;
}
if (highlightsChannel.type !== ChannelType.GuildText) {
await interaction.editReply({
content: "❌ the highlights channel is not a text channel.",
});
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,
};
|