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:
@@ -0,0 +1,135 @@
|
||||
// Community-Features: Starboard (⭐ → Best-of-Kanal) + Screenshot-Galerie
|
||||
import { mkdirSync } from 'node:fs';
|
||||
import { writeFile, rm } from 'node:fs/promises';
|
||||
import { join, dirname, resolve } from 'node:path';
|
||||
import { EmbedBuilder, Events } from 'discord.js';
|
||||
import {
|
||||
hasStarboard, saveStarboard,
|
||||
saveGalleryItem, hasGalleryItem, deleteGalleryItem,
|
||||
} from '../db.js';
|
||||
import { starboardChannelId, starboardThreshold, screenshotChannelId } from '../runtime-settings.js';
|
||||
import { config } from '../config.js';
|
||||
|
||||
const BRAND_YELLOW = 0xf5c518;
|
||||
const MAX_GALLERY_IMAGES = 4;
|
||||
|
||||
// Galerie-Bilder liegen neben der SQLite: data/gallery/
|
||||
export const galleryDir = join(dirname(resolve(config.dbPath)), 'gallery');
|
||||
mkdirSync(galleryDir, { recursive: true });
|
||||
|
||||
/* ── Screenshot-Galerie ────────────────────────────── */
|
||||
|
||||
/**
|
||||
* Nachricht aus dem Screenshot-Kanal archivieren (Bilder lokal speichern).
|
||||
* @returns {Promise<boolean>} true, wenn neu gespeichert
|
||||
*/
|
||||
export async function archiveGalleryMessage(message) {
|
||||
if (hasGalleryItem(message.id)) return false;
|
||||
|
||||
const files = [];
|
||||
for (const att of message.attachments?.values?.() ?? []) {
|
||||
if (files.length >= MAX_GALLERY_IMAGES) break;
|
||||
if (!att.contentType?.startsWith('image/')) continue;
|
||||
const ext = (att.name?.split('.').pop() || 'png').toLowerCase().replace(/[^a-z0-9]/g, '') || 'png';
|
||||
const file = `${message.id}_${files.length}.${ext}`;
|
||||
try {
|
||||
const res = await fetch(att.url);
|
||||
if (!res.ok) continue;
|
||||
await writeFile(join(galleryDir, file), Buffer.from(await res.arrayBuffer()));
|
||||
files.push(file);
|
||||
} catch (error) {
|
||||
console.error(`[galerie] Bild-Download fehlgeschlagen: ${error.message}`);
|
||||
}
|
||||
}
|
||||
if (files.length === 0) return false;
|
||||
|
||||
return saveGalleryItem({
|
||||
message_id: message.id,
|
||||
author_name: message.member?.displayName ?? message.author?.username ?? 'unbekannt',
|
||||
images: JSON.stringify(files),
|
||||
posted_at: message.createdAt.toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
/** Galerie-Eintrag inkl. Bild-Dateien entfernen */
|
||||
export async function removeGalleryMessage(messageId) {
|
||||
const { deleted, images } = deleteGalleryItem(messageId);
|
||||
for (const file of images) {
|
||||
await rm(join(galleryDir, file), { force: true });
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/* ── Starboard ─────────────────────────────────────── */
|
||||
|
||||
async function handleStarReaction(reaction) {
|
||||
const boardChannelId = starboardChannelId();
|
||||
if (!boardChannelId) return;
|
||||
if (reaction.emoji.name !== '⭐') return;
|
||||
|
||||
if (reaction.partial) await reaction.fetch();
|
||||
let message = reaction.message;
|
||||
if (message.partial) message = await message.fetch();
|
||||
|
||||
// Nicht das Starboard selbst boarden, Dedupe, Schwellwert
|
||||
if (message.channelId === boardChannelId) return;
|
||||
if (hasStarboard(message.id)) return;
|
||||
if ((reaction.count ?? 0) < starboardThreshold()) return;
|
||||
|
||||
const board = await message.client.channels.fetch(boardChannelId);
|
||||
if (!board?.isTextBased()) return;
|
||||
|
||||
const image = [...message.attachments.values()].find((a) =>
|
||||
a.contentType?.startsWith('image/')
|
||||
);
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(BRAND_YELLOW)
|
||||
.setAuthor({
|
||||
name: message.member?.displayName ?? message.author?.username ?? 'unbekannt',
|
||||
iconURL: message.author?.displayAvatarURL?.() ?? undefined,
|
||||
})
|
||||
.setDescription(
|
||||
`${(message.content || '').slice(0, 1800) || '*[Bild]*'}\n\n[→ Zum Original](${message.url})`
|
||||
)
|
||||
.setFooter({ text: `⭐ ${reaction.count} • #${message.channel?.name ?? ''}` })
|
||||
.setTimestamp(message.createdAt);
|
||||
if (image) embed.setImage(image.url);
|
||||
|
||||
const boardMessage = await board.send({ embeds: [embed] });
|
||||
saveStarboard(message.id, boardMessage.id);
|
||||
console.log(`[starboard] ${message.id} geboardet (${reaction.count} ⭐)`);
|
||||
}
|
||||
|
||||
/* ── Listener-Registrierung ────────────────────────── */
|
||||
|
||||
export function registerCommunityListeners(client) {
|
||||
client.on(Events.MessageReactionAdd, async (reaction) => {
|
||||
try {
|
||||
await handleStarReaction(reaction);
|
||||
} catch (error) {
|
||||
console.error('[starboard] Fehler:', error);
|
||||
}
|
||||
});
|
||||
|
||||
client.on(Events.MessageCreate, async (message) => {
|
||||
if (message.channelId !== screenshotChannelId()) return;
|
||||
try {
|
||||
if (await archiveGalleryMessage(message)) {
|
||||
console.log(`[galerie] Screenshot archiviert (${message.id})`);
|
||||
await message.react('⭐').catch(() => {});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[galerie] Archivierung fehlgeschlagen:', error);
|
||||
}
|
||||
});
|
||||
|
||||
client.on(Events.MessageDelete, async (message) => {
|
||||
try {
|
||||
if (await removeGalleryMessage(message.id)) {
|
||||
console.log(`[galerie] Eintrag entfernt (${message.id})`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[galerie] Löschung fehlgeschlagen:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user