all repos — stealth-developers @ c5869ac3e5e314f4661c207e5c989f0918da7c18

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,
};