import { EmbedBuilder, PermissionsBitField, SlashCommandBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, } from "discord.js"; import fs from "fs/promises"; import { Spiget } from "spiget"; import { generateAvatarLink, generateAuthorURL, generateResourceIconURL, } from "../util/helpers.js"; const spiget = new Spiget("Viper-Network"); export default { name: "add", description: "Richtet einen Listener ein, der Plugin-Updates in einem bestimmten Kanal postet", aliases: [], guild: ["all"], nsfw: false, user_permissions: [PermissionsBitField.Flags.Administrator], bot_permissions: [], args_required: 2, args_usage: "[ressourcen_id] [kanal] Beispiel: vn!add 72678 #ankündigungen", cooldown: 5, data: new SlashCommandBuilder() .setName("add") .setDescription("Richtet einen Listener für Plugin-Updates ein") .addStringOption((opt) => opt.setName("ressourcen_id").setDescription("Spiget Ressourcen-ID").setRequired(true) ) .addChannelOption((opt) => opt.setName("kanal").setDescription("Kanal für Update-Benachrichtigungen").setRequired(true) ), async execute(client, ctx, args) { const resourceArg = ctx.isSlash ? ctx.interaction.options.getString("ressourcen_id") : args[0]; const channel = ctx.isSlash ? ctx.interaction.options.getChannel("kanal") : ctx.mentions?.channels?.first(); if (!channel) { return ctx.reply("Dieser Kanal ist ungültig. Bitte erwähne einen Kanal auf diesem Server."); } if (!ctx.guild.channels.cache.has(channel.id)) { return ctx.reply("Dieser Kanal befindet sich nicht auf diesem Server!"); } let resource; try { resource = await spiget.getResource(resourceArg); } catch (e) { client.logger.error(e); return ctx.reply(`Ups! \`${resourceArg}\` ist keine gültige Ressourcen-ID!`); } let author; try { author = await resource.getAuthor(); } catch (e) { client.logger.error(e); return ctx.reply(`Ups! Der Autor für Ressource \`${resourceArg}\` konnte nicht gefunden werden.`); } let latestVersion; try { const res = await fetch(`https://api.spigotmc.org/legacy/update.php?resource=${resourceArg}`); if (!res.ok) throw new Error(`HTTP ${res.status}`); latestVersion = (await res.text()).trim(); } catch (e) { client.logger.error(e); return ctx.reply("Die neueste Version konnte nicht von SpigotMC abgerufen werden. Bitte versuche es erneut."); } const guildID = ctx.guild.id; const filePath = `./serverdata/${guildID}.json`; // Check existing data & limits let saveData = { watchedResources: [] }; try { const raw = await fs.readFile(filePath, "utf8"); saveData = JSON.parse(raw); const duplicate = saveData.watchedResources.find((r) => r.resourceID == resource.id); if (duplicate) { return ctx.reply(`Diese Ressource wird bereits in <#${duplicate.channelID}> beobachtet.`); } } catch { // Datei existiert noch nicht } const authorURL = generateAuthorURL(author.name, author.id); const authorAvatarURL = generateAvatarLink(author.id); const resourceIconURL = generateResourceIconURL(resource); const resourceURL = `https://spigotmc.org/resources/.${resourceArg}/`; // Build confirmation embed with buttons const confirmEmbed = new EmbedBuilder() .setAuthor({ name: `Autor: ${author.name}`, iconURL: authorAvatarURL, url: authorURL }) .setColor(ctx.guild.members.me.displayHexColor) .setTitle(`Plugin beobachten: ${resource.name}`) .setDescription(`${resource.tag}\n\nSoll dieses Plugin in <#${channel.id}> beobachtet werden?`) .addFields([ { name: "Kanal", value: `<#${channel.id}>`, inline: true }, { name: "Version", value: latestVersion, inline: true }, { name: "Download", value: resourceURL, inline: false }, ]) .setThumbnail(resourceIconURL) .setTimestamp(); const row = new ActionRowBuilder().addComponents( new ButtonBuilder() .setCustomId("add_confirm") .setLabel("✅ Bestätigen") .setStyle(ButtonStyle.Success), new ButtonBuilder() .setCustomId("add_cancel") .setLabel("❌ Abbrechen") .setStyle(ButtonStyle.Danger) ); const confirmMsg = await ctx.reply({ embeds: [confirmEmbed], components: [row], fetchReply: true }); // Wait for button click (30 seconds) let btnInteraction; try { btnInteraction = await confirmMsg.awaitMessageComponent({ filter: (i) => i.user.id === ctx.author.id, time: 30_000, }); } catch { // Timeout const timeoutEmbed = EmbedBuilder.from(confirmEmbed) .setColor("#808080") .setDescription("⏱️ Zeit abgelaufen – Befehl wurde abgebrochen."); return confirmMsg.edit({ embeds: [timeoutEmbed], components: [] }); } if (btnInteraction.customId === "add_cancel") { const cancelEmbed = EmbedBuilder.from(confirmEmbed) .setColor("#FF0000") .setDescription("❌ Abgebrochen."); return btnInteraction.update({ embeds: [cancelEmbed], components: [] }); } // Confirmed – save data saveData.watchedResources.push({ resourceID: resource.id, resourceName: resource.name, channelID: channel.id, lastCheckedVersion: latestVersion, }); try { await fs.mkdir("./serverdata", { recursive: true }); await fs.writeFile(filePath, JSON.stringify(saveData, null, 2)); } catch (e) { client.logger.error(e); return btnInteraction.update({ content: "Beim Speichern der Beobachtungsdaten ist ein Fehler aufgetreten.", components: [] }); } const successEmbed = EmbedBuilder.from(confirmEmbed) .setTitle(`✅ Wird jetzt beobachtet: ${resource.name}`) .setDescription(resource.tag) .setColor("#00FF00"); return btnInteraction.update({ embeds: [successEmbed], components: [] }); }, };