From 247e8c805c5c084999a65f5400142f0808103a8e Mon Sep 17 00:00:00 2001 From: D4rkst3r Date: Thu, 23 Jul 2026 10:39:06 +0200 Subject: [PATCH] =?UTF-8?q?Rollen-Ping=20beim=20Devlog:=20Erw=C3=A4hnung?= =?UTF-8?q?=20+=20Abo-Button?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Setting devlog_ping_role_id (Rollen-Dropdown auf der Setup-Seite, leer = aus) - Devlog-Post erwähnt die Rolle (allowedMentions gescoped) und trägt einen 🔔-Button, über den Member die Rolle selbst an-/abmelden (ephemere Bestätigung) - API liefert vergebbare Rollen (ohne @everyone/managed), PUT validiert Co-Authored-By: Claude Opus 4.8 --- frontend/src/pages/Settings.jsx | 17 ++++++++++++++++ src/bot/client.js | 36 +++++++++++++++++++++++++++++++-- src/runtime-settings.js | 5 +++++ src/web/api.js | 30 ++++++++++++++++++++++++++- src/web/devlog-endpoint.js | 25 ++++++++++++++++++++--- 5 files changed, 107 insertions(+), 6 deletions(-) diff --git a/frontend/src/pages/Settings.jsx b/frontend/src/pages/Settings.jsx index b0b625c..d54ccf9 100644 --- a/frontend/src/pages/Settings.jsx +++ b/frontend/src/pages/Settings.jsx @@ -152,6 +152,23 @@ export default function Settings({ me }) { + {/* Devlog */} +
+

// Devlog

+
+ + +
+
+ {/* Commit-Feed */}

// Commit-Feed

diff --git a/src/bot/client.js b/src/bot/client.js index e8f2fe7..bd2aae8 100644 --- a/src/bot/client.js +++ b/src/bot/client.js @@ -1,7 +1,7 @@ // Discord-Client: Commands laden, registrieren und Interactions verarbeiten -import { Client, Collection, Events, GatewayIntentBits, Partials, REST, Routes } from 'discord.js'; +import { Client, Collection, Events, GatewayIntentBits, MessageFlags, Partials, REST, Routes } from 'discord.js'; import { config } from '../config.js'; -import { devlogChannelId } from '../runtime-settings.js'; +import { devlogChannelId, devlogPingRoleId } from '../runtime-settings.js'; import { archiveDevlogMessage, removeDevlog } from './devlog-archive.js'; import * as ping from './commands/ping.js'; import * as devlogBackfill from './commands/devlog-backfill.js'; @@ -58,6 +58,38 @@ export async function startBot() { }); client.on(Events.InteractionCreate, async (interaction) => { + // 🔔-Button unterm Devlog: Ping-Rolle selbst an-/abmelden + if (interaction.isButton() && interaction.customId === 'devlog_ping_toggle') { + const roleId = devlogPingRoleId(); + try { + if (!roleId || !interaction.inGuild()) { + throw new Error('Keine Ping-Rolle konfiguriert'); + } + const member = interaction.member; + const hasRole = member.roles.cache.has(roleId); + if (hasRole) { + await member.roles.remove(roleId); + } else { + await member.roles.add(roleId); + } + await interaction.reply({ + content: hasRole + ? '🔕 Devlog-Benachrichtigungen **abbestellt**.' + : '🔔 Devlog-Benachrichtigungen **abonniert** — du wirst ab jetzt gepingt.', + flags: MessageFlags.Ephemeral, + }); + } catch (error) { + console.error('[ping-rolle] Toggle fehlgeschlagen:', error); + await interaction + .reply({ + content: '❌ Konnte die Rolle nicht ändern — dem Bot fehlt vermutlich „Rollen verwalten" oder die Rolle steht über seiner.', + flags: MessageFlags.Ephemeral, + }) + .catch(() => {}); + } + return; + } + if (!interaction.isChatInputCommand()) return; const command = client.commands.get(interaction.commandName); diff --git a/src/runtime-settings.js b/src/runtime-settings.js index c8f762f..0e0ceac 100644 --- a/src/runtime-settings.js +++ b/src/runtime-settings.js @@ -16,6 +16,11 @@ export function releaseChannelId() { return getSetting('release_channel_id') || null; } +/** Rolle, die bei Devlog-Posts gepingt wird — leer = kein Ping */ +export function devlogPingRoleId() { + return getSetting('devlog_ping_role_id') || null; +} + /** Commit-Feed global an/aus (Default: an). Aus = weiter archivieren, nur nicht posten. */ export function commitFeedEnabled() { return getSetting('commit_feed_enabled') !== '0'; diff --git a/src/web/api.js b/src/web/api.js index ddcb2e4..e376871 100644 --- a/src/web/api.js +++ b/src/web/api.js @@ -3,7 +3,7 @@ import { EmbedBuilder } from 'discord.js'; import { listDevlogs, searchDevlogs, listCommits, listReleases, archiveStats, getSetting, setSetting } from '../db.js'; import { config } from '../config.js'; import { removeDevlog } from '../bot/devlog-archive.js'; -import { commitChannelId, devlogChannelId, releaseChannelId } from '../runtime-settings.js'; +import { commitChannelId, devlogChannelId, releaseChannelId, devlogPingRoleId } from '../runtime-settings.js'; import { getSessionUser, isAdmin } from './auth.js'; const PAGE_SIZE = 20; @@ -116,11 +116,25 @@ ${rssItems} return channels.sort((a, b) => a.name.localeCompare(b.name)); } + /** Rollen, die der Bot vergeben kann (unter seiner höchsten Rolle, nicht managed) */ + function listRoles() { + const roles = []; + for (const guild of client.guilds.cache.values()) { + for (const role of guild.roles?.cache?.values() ?? []) { + if (role.id === guild.id) continue; // @everyone + if (role.managed) continue; // Bot-/Integrations-Rollen + roles.push({ id: role.id, name: role.name, guild: guild.name }); + } + } + return roles.sort((a, b) => a.name.localeCompare(b.name)); + } + function currentSettings() { return { commit_channel_id: commitChannelId(), devlog_channel_id: devlogChannelId(), release_channel_id: releaseChannelId() ?? '', + devlog_ping_role_id: devlogPingRoleId() ?? '', commit_feed_enabled: getSetting('commit_feed_enabled') !== '0', commit_branch_filter: getSetting('commit_branch_filter') ?? '', ignored_repos: getSetting('ignored_repos') ?? '', @@ -133,6 +147,7 @@ ${rssItems} const stats = archiveStats(); return { channels: listChannels(), + roles: listRoles(), settings: currentSettings(), status: { botTag: client.user?.tag ?? null, @@ -162,6 +177,19 @@ ${rssItems} } setSetting(key, value); } + // Ping-Rolle: muss existieren ('' = kein Ping) + if (body.devlog_ping_role_id !== undefined) { + const value = String(body.devlog_ping_role_id); + if (value !== '') { + const exists = [...client.guilds.cache.values()].some((g) => + g.roles?.cache?.has?.(value) + ); + if (!exists) { + return reply.code(400).send({ error: 'devlog_ping_role_id: Rolle nicht gefunden' }); + } + } + setSetting('devlog_ping_role_id', value); + } if (body.commit_feed_enabled !== undefined) { setSetting('commit_feed_enabled', body.commit_feed_enabled ? '1' : '0'); } diff --git a/src/web/devlog-endpoint.js b/src/web/devlog-endpoint.js index bcb691e..8dc4ee4 100644 --- a/src/web/devlog-endpoint.js +++ b/src/web/devlog-endpoint.js @@ -10,7 +10,7 @@ import { join } from 'node:path'; import { ActionRowBuilder, AttachmentBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from 'discord.js'; import { config } from '../config.js'; import { saveDevlog } from '../db.js'; -import { devlogChannelId } from '../runtime-settings.js'; +import { devlogChannelId, devlogPingRoleId } from '../runtime-settings.js'; import { imagesDir } from '../bot/devlog-archive.js'; const BRAND_YELLOW = 0xf5c518; @@ -123,7 +123,7 @@ export function registerDevlogEndpoint(app, client) { } }); - // Link-Buttons unterm Embed: Archiv-Seite + Repo + // Link-Buttons unterm Embed: Archiv-Seite + Repo + Abo-Button für die Ping-Rolle const buttons = new ActionRowBuilder().addComponents( new ButtonBuilder().setStyle(ButtonStyle.Link).setLabel('Devlog-Archiv').setEmoji('🗂️').setURL(groupUrl) ); @@ -133,7 +133,26 @@ export function registerDevlogEndpoint(app, client) { ); } - const message = await channel.send({ embeds, files: attachments, components: [buttons] }); + // Ping-Rolle: Rolle erwähnen + Abo-Button anbieten (Setup-Seite) + const pingRole = devlogPingRoleId(); + if (pingRole) { + buttons.addComponents( + new ButtonBuilder() + .setStyle(ButtonStyle.Secondary) + .setLabel('Benachrichtigungen') + .setEmoji('🔔') + .setCustomId('devlog_ping_toggle') + ); + } + + const message = await channel.send({ + embeds, + files: attachments, + components: [buttons], + ...(pingRole + ? { content: `<@&${pingRole}>`, allowedMentions: { roles: [pingRole] } } + : {}), + }); // Direkt archivieren (der Live-Listener ignoriert eigene Bot-Posts bewusst) const imageFiles = [];