Community-Paket: Playtester-Programm, Starboard, Screenshot-Galerie

- Playtester: /playtester-setup postet Bewerbungs-Embed mit 🧪-Button;
  Toggle vergibt Rolle + DB-Eintrag; Liste + Rollen-Auswahl auf der Setup-Seite
- Starboard: ab konfigurierbarem -Schwellwert Repost als Embed in den
  Best-of-Kanal (Dedupe, kein Selbst-Boarding); neue Intents/Partials für Reaktionen
- Galerie: Bilder aus dem Screenshot-Kanal lokal archiviert (live + /galerie-backfill,
  Delete-Sync, Bot reagiert mit ) → öffentliche /galerie-Seite mit Hover-Grid
- Setup-Seite: Sektion // Community (Rolle, Starboard-Kanal + Schwellwert, Screenshot-Kanal)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 12:34:34 +02:00
co-authored by Claude Opus 4.8
parent 6d8f383aed
commit a3c47656c5
13 changed files with 563 additions and 12 deletions
+41
View File
@@ -0,0 +1,41 @@
// /galerie-backfill — bestehende Screenshots aus dem Kanal nacharchivieren (Admin)
import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from 'discord.js';
import { screenshotChannelId } from '../../runtime-settings.js';
import { archiveGalleryMessage } from '../community.js';
export const data = new SlashCommandBuilder()
.setName('galerie-backfill')
.setDescription('Archiviert alle bisherigen Screenshots aus dem Galerie-Kanal')
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator);
export async function execute(interaction) {
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
const channelId = screenshotChannelId();
if (!channelId) {
await interaction.editReply('❌ Erst auf der Setup-Seite einen Screenshot-Kanal wählen.');
return;
}
const channel = await interaction.client.channels.fetch(channelId);
if (!channel?.isTextBased()) {
await interaction.editReply('❌ Screenshot-Kanal nicht gefunden.');
return;
}
let scanned = 0;
let saved = 0;
let before;
for (;;) {
const batch = await channel.messages.fetch({ limit: 100, before });
if (batch.size === 0) break;
for (const message of batch.values()) {
scanned++;
if (await archiveGalleryMessage(message)) saved++;
}
before = batch.last().id;
}
await interaction.editReply(
`✅ Galerie-Backfill fertig: ${scanned} Nachrichten gescannt, **${saved} Screenshots archiviert**.`
);
}
+44
View File
@@ -0,0 +1,44 @@
// /playtester-setup — postet das Bewerbungs-Embed mit Button in den aktuellen Kanal (Admin)
import {
SlashCommandBuilder, PermissionFlagsBits, MessageFlags,
ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder,
} from 'discord.js';
import { playtesterRoleId } from '../../runtime-settings.js';
export const data = new SlashCommandBuilder()
.setName('playtester-setup')
.setDescription('Postet das Playtester-Bewerbungs-Embed in diesen Kanal')
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator);
export async function execute(interaction) {
if (!playtesterRoleId()) {
await interaction.reply({
content: '❌ Erst auf der Setup-Seite eine Playtester-Rolle wählen.',
flags: MessageFlags.Ephemeral,
});
return;
}
const embed = new EmbedBuilder()
.setColor(0xf5c518)
.setTitle('🧪 Playtester werden')
.setDescription(
'Du willst **EcoGame** vor allen anderen spielen und mit deinem Feedback ' +
'die Entwicklung mitgestalten?\n\n' +
'Ein Klick auf den Button setzt dich auf die Playtester-Liste — ' +
'sobald die Alpha startet, bekommst du deinen Zugang direkt per DM. ' +
'Abmelden geht jederzeit mit einem zweiten Klick.'
)
.setFooter({ text: 'D4RKST3R // PLAYTEST' });
const row = new ActionRowBuilder().addComponents(
new ButtonBuilder()
.setCustomId('playtester_toggle')
.setStyle(ButtonStyle.Success)
.setLabel('Als Playtester bewerben')
.setEmoji('🧪')
);
await interaction.channel.send({ embeds: [embed], components: [row] });
await interaction.reply({ content: '✅ Bewerbungs-Post erstellt.', flags: MessageFlags.Ephemeral });
}