// 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, brandColor } from '../runtime-settings.js'; import { moduleEnabled } from '../modules.js'; import { tuning } from '../tuning.js'; import { config } from '../config.js'; // 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} true, wenn neu gespeichert */ export async function archiveGalleryMessage(message) { if (!moduleEnabled('gallery')) return; if (hasGalleryItem(message.id)) return false; const files = []; for (const att of message.attachments?.values?.() ?? []) { if (files.length >= tuning('gallery_max_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) { if (!moduleEnabled('starboard')) return; 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(brandColor()) .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); } }); }