Rollen-Ping beim Devlog: Erwähnung + Abo-Button

- 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 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 10:39:06 +02:00
co-authored by Claude Opus 4.8
parent be1fc227d0
commit 247e8c805c
5 changed files with 107 additions and 6 deletions
+29 -1
View File
@@ -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');
}
+22 -3
View File
@@ -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 = [];