import { getGuild } from "@/database/queries"; import { ApplicationCommandType, type Attachment, type Client, ContextMenuCommandBuilder, type GuildTextBasedChannel, type MessageContextMenuCommandInteraction, } from "discord.js"; const commandData = new ContextMenuCommandBuilder() .setName("Highlight Clip") .setType(ApplicationCommandType.Message); function extractVideoLinks(message: { content: string }): string[] { const domains = [ "youtube.com", "youtu.be", "medal.tv", "cdn.discordapp.com", "twitch.tv", "clips.twitch.tv", ]; const tokens = message.content.split(/\s+/); const found = new Set(); for (const raw of tokens) { let t = raw .replace(/^<+/, "") .replace(/>+$/, "") .replace(/^[('"`]+/, "") .replace(/['")`.,;:!]+$/, ""); if (!t) continue; const lower = t.toLowerCase(); if (!domains.some((d) => lower.includes(d))) continue; if (!/^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(t)) { t = `https://${t}`; } try { const url = new URL(t); if (!domains.some((d) => url.hostname.toLowerCase().includes(d))) continue; const normalized = url.href.replace(/\/$/, ""); found.add(normalized); } catch {} } return Array.from(found); } 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 { data: guildConfig, exists } = await getGuild(interaction.guild.id); if (!exists) { await interaction.editReply({ content: "❌ No guild configuration found.", }); return; } if (!guildConfig.highlights_channel_id) { 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_id, )) as GuildTextBasedChannel | null; } catch (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; if (videoLinks.length === 0) videoLinks.push(...files); 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, allowedMentions: { users: [targetMessage.author.id] }, }); await msg.react("⭐"); await interaction.editReply({ content: "✅ Highlight sent!", }); } catch (error) { await interaction.editReply({ content: "❌ Failed to send highlight.", }); } } export default { data: commandData, execute, };