Community-Endgame: Alpha-Keys, Bewerbungen, Events, Stats, Triggers, /remind, Social, Temp-Voice + MEE6-Bot-Karte

- Alpha-Keys: Pool im Community-Tab, Ein-Klick-Verteilung per DM an alle Playtester
  (Key bleibt frei, wenn die DM geblockt wird)
- Bewerbungs-Formulare: Builder im neuen Bewerbungen-Tab (bis 5 Fragen),
  Button → Discord-Modal → Review-Embed mit /, Rolle + DM bei Entscheidung
- Events: GuildScheduledEventCreate → Announce-Embed; öffentliche /events-Seite
  aus den Discord-Events (5-min-Cache)
- Server-Stats: activity_daily (Nachrichten/Joins/Leaves) → Balken-Chart auf /level
- Triggers (Auto-Antworten, 30s-Cooldown), /remind (DM-Scheduler),
  Twitch-Live (Helix, App-Creds write-only) + YouTube-RSS-Announcements,
  Temp-Voice (Join to Create, Cleanup bei Leerstand + Start)
- Brand-Tab: MEE6-Style Bot-Identity-Karte (Avatar-Vorschau mit Status-Dot,
  Bot-Name via setUsername, Presence online/idle/dnd, Aktivität)
- Neue Intents: GuildVoiceStates, GuildScheduledEvents; alles smoke-getestet

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-30 01:02:47 +02:00
co-authored by Claude Fable 5
parent f7b261c648
commit e72e6c8991
16 changed files with 1368 additions and 47 deletions
+31
View File
@@ -10,12 +10,41 @@ function xpForLevel(level) {
const MEDALS = ['🥇', '🥈', '🥉'];
/** Nachrichten/Tag der letzten 30 Tage als Balken */
function ActivityChart({ stats }) {
const byDay = new Map(stats.days.map((d) => [d.day, d]));
const days = [];
for (let i = 29; i >= 0; i--) {
const key = new Date(Date.now() - i * 86400000).toISOString().slice(0, 10);
days.push({ key, ...(byDay.get(key) ?? { messages: 0, joins: 0, leaves: 0 }) });
}
const max = Math.max(1, ...days.map((d) => d.messages));
const total = days.reduce((s, d) => s + d.messages, 0);
return (
<div className="roadmap-group" style={{ marginTop: '2.5rem' }}>
<h2 className="settings-title">// Server-Aktivität — {total.toLocaleString('de-DE')} Nachrichten in 30 Tagen · {stats.members} Member</h2>
<div className="act-chart">
{days.map((d) => (
<span
key={d.key}
className="act-bar"
style={{ height: `${Math.max(4, (d.messages / max) * 100)}%` }}
title={`${d.key}: ${d.messages} Nachrichten · +${d.joins}/${d.leaves} Member`}
/>
))}
</div>
</div>
);
}
export default function Level() {
const [rows, setRows] = useState(null);
const [stats, setStats] = useState(null);
const [error, setError] = useState(false);
useEffect(() => {
apiGet('/api/levels').then((d) => setRows(d.levels)).catch(() => setError(true));
apiGet('/api/serverstats').then(setStats).catch(() => {});
window.scrollTo(0, 0);
}, []);
@@ -58,6 +87,8 @@ export default function Level() {
})}
</div>
)}
{stats && <ActivityChart stats={stats} />}
</section>
</>
);