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 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
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<string>();
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,
};
|