Files
SpigotWatch/commands/add.js
2026-02-25 18:51:08 +01:00

178 lines
6.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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: [] });
},
};