Bewerbungen zusätzlich per Discord-Webhook direkt in echte Kanäle posten
Ticketanizers Inbound-Ticket-API erzeugt bei API-Tickets nachweislich keinen echten Discord-Kanal (nur Web-Inbox + Rollen-Ping im Log-Kanal, auch nach manuellem Uebernehmen nicht) - das wurde heute gemeinsam mit Dogi live in der Ticketanizer-Inbox und in Discord verifiziert. Deshalb zusaetzlich ein normaler Discord-Webhook pro Zielkanal, der die komplette Bewerbung garantiert sichtbar direkt postet. Kanal-Zuordnung (von Dogi aus Discord kopiert): modi -> #modi-bewerbung (DISCORD_WEBHOOK_MODI) kooperation -> #kooperations-bewerbung (DISCORD_WEBHOOK_KOOPERATION) scout + creator -> #scout-bewerbung gemeinsam (DISCORD_WEBHOOK_SCOUT_MANAGER) - Neu: cloudflare-worker/src/lib/discord-webhooks.js - applications.js: postet jetzt parallel zu Ticketanizer (Promise.all, weiterhin ueber ctx.waitUntil im Hintergrund, blockiert nie die Antwort) - Alle 3 Secrets bereits live gesetzt und deployed - Getestet mit allen 4 Bewerbungstypen (modi/kooperation/scout/creator) - laut Log alle erfolgreich gepostet, Testdaten aus D1 wieder entfernt - README.md: Einschraenkung von Ticketanizer dokumentiert + neue Webhook-Einrichtungsanleitung
This commit is contained in:
@@ -143,6 +143,48 @@ Ticketanizer-Dashboard unter **Panels**:
|
|||||||
eröffnen). Das Nachladen von Staff-Antworten (Schritt 3, "pollen") ist
|
eröffnen). Das Nachladen von Staff-Antworten (Schritt 3, "pollen") ist
|
||||||
hier nicht eingebaut, da eine Bewerbung ein einmaliger Vorgang ist und
|
hier nicht eingebaut, da eine Bewerbung ein einmaliger Vorgang ist und
|
||||||
kein dauerhaft laufender Chat.
|
kein dauerhaft laufender Chat.
|
||||||
|
- **Wichtige Einschränkung (2026-08-01 festgestellt):** Über die Inbound-
|
||||||
|
Ticket-API angelegte Tickets landen bei Ticketanizer nur in der
|
||||||
|
**Web-Inbox** (eigene Weboberfläche, siehe Dashboard → "Inbox") und lösen
|
||||||
|
lediglich einen Rollen-Ping im Log-Kanal aus — es entsteht **kein**
|
||||||
|
echter, sichtbarer Ticket-Kanal in Discord (auch nicht nach manuellem
|
||||||
|
"Übernehmen" in der Inbox). Deshalb zusätzlich der Discord-Webhook-Weg
|
||||||
|
weiter unten, der die Bewerbung garantiert sichtbar direkt in den
|
||||||
|
passenden Kanal postet.
|
||||||
|
|
||||||
|
## Bewerbungen direkt in Discord-Kanälen (Webhooks)
|
||||||
|
|
||||||
|
Zusätzlich zu Ticketanizer wird jede Bewerbung als formatierte Nachricht
|
||||||
|
(Embed) direkt per normalem Discord-Webhook in den passenden Kanal gepostet —
|
||||||
|
das ist der Weg, der garantiert sichtbar in Discord ankommt (siehe
|
||||||
|
Einschränkung oben).
|
||||||
|
|
||||||
|
### Kanal-Zuordnung (von Dogi aus Discord kopiert, 2026-08-01)
|
||||||
|
|
||||||
|
| Bewerbung auf der Website | Discord-Kanal | Secret |
|
||||||
|
|---|---|---|
|
||||||
|
| Modi | `#modi-bewerbung` | `DISCORD_WEBHOOK_MODI` |
|
||||||
|
| Kooperation | `#kooperations-bewerbung` | `DISCORD_WEBHOOK_KOOPERATION` |
|
||||||
|
| Scout **und** Creator | `#scout-bewerbung` (gemeinsam) | `DISCORD_WEBHOOK_SCOUT_MANAGER` |
|
||||||
|
|
||||||
|
### Einrichtung (pro Kanal ca. 1 Minute)
|
||||||
|
|
||||||
|
1. Im jeweiligen Discord-Kanal: Kanal bearbeiten → **Integrationen** →
|
||||||
|
**Webhooks** → **Neuer Webhook** → **Webhook-URL kopieren**.
|
||||||
|
2. Als Secret hinterlegen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx wrangler secret put DISCORD_WEBHOOK_MODI
|
||||||
|
npx wrangler secret put DISCORD_WEBHOOK_KOOPERATION
|
||||||
|
npx wrangler secret put DISCORD_WEBHOOK_SCOUT_MANAGER
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Danach einmal `npx wrangler deploy`.
|
||||||
|
|
||||||
|
Genau wie bei Ticketanizer: **rein informativ, kein Ausfallpunkt.** Fehlt
|
||||||
|
eine Webhook-URL oder ist Discord kurz nicht erreichbar, wird die Bewerbung
|
||||||
|
trotzdem ganz normal gespeichert. Die Zuordnung Typ → Webhook steht in
|
||||||
|
`src/lib/discord-webhooks.js` (`WEBHOOK_ENV_KEYS`).
|
||||||
|
|
||||||
## Wer den Owner-Code kennen darf
|
## Wer den Owner-Code kennen darf
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
/* =====================================================================
|
||||||
|
lib/discord-webhooks.js — Bewerbungen zusätzlich als ECHTE, sichtbare
|
||||||
|
Nachricht direkt in den passenden Discord-Kanal posten.
|
||||||
|
|
||||||
|
Hintergrund: Die Ticketanizer-Inbound-Ticket-API (siehe ticketanizer.js)
|
||||||
|
legt zwar zuverlässig ein Ticket in der Web-Inbox an und pingt eine Rolle
|
||||||
|
im Log-Kanal, erzeugt aber KEINEN echten, sichtbaren Ticket-Kanal in
|
||||||
|
Discord (das ist offenbar bewusstes Ticketanizer-Verhalten für
|
||||||
|
API-Tickets, siehe README). Deshalb zusätzlich hierüber ein stinknormaler
|
||||||
|
Discord-Webhook, der die komplette Bewerbung als Embed direkt in den
|
||||||
|
Zielkanal postet — 100% garantiert sichtbar, unabhängig von Ticketanizer.
|
||||||
|
|
||||||
|
Kanal-Zuordnung (von Dogi aus Discord kopiert, 2026-08-01):
|
||||||
|
modi → #modi-bewerbung
|
||||||
|
kooperation → #kooperations-bewerbung
|
||||||
|
scout → #scout-bewerbung (gemeinsam mit creator)
|
||||||
|
creator → #scout-bewerbung (gemeinsam mit scout — Wunsch von Dogi)
|
||||||
|
|
||||||
|
Secrets (siehe README.md):
|
||||||
|
DISCORD_WEBHOOK_MODI
|
||||||
|
DISCORD_WEBHOOK_KOOPERATION
|
||||||
|
DISCORD_WEBHOOK_SCOUT_MANAGER (für scout UND creator)
|
||||||
|
|
||||||
|
Rein informativ, kein Ausfallpunkt: Fehlt eine Webhook-URL, oder ist
|
||||||
|
Discord kurz nicht erreichbar, wird die Bewerbung trotzdem ganz normal
|
||||||
|
in der eigenen Datenbank gespeichert.
|
||||||
|
===================================================================== */
|
||||||
|
|
||||||
|
const WEBHOOK_ENV_KEYS = {
|
||||||
|
modi: "DISCORD_WEBHOOK_MODI",
|
||||||
|
kooperation: "DISCORD_WEBHOOK_KOOPERATION",
|
||||||
|
scout: "DISCORD_WEBHOOK_SCOUT_MANAGER",
|
||||||
|
creator: "DISCORD_WEBHOOK_SCOUT_MANAGER",
|
||||||
|
};
|
||||||
|
|
||||||
|
const TYPE_LABELS = {
|
||||||
|
modi: "🩵 Neue Bewerbung — Modi (Team Dogi)",
|
||||||
|
kooperation: "🤝 Neue Kooperationsanfrage",
|
||||||
|
scout: "🔎 Neue Bewerbung — Scout (Spicy Media)",
|
||||||
|
creator: "🎬 Neue Bewerbung — Creator / Manager-Bewerbung (Spicy Media)",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Farbe je Bewerbungsart, passend zur Farbwelt auf der Website
|
||||||
|
// (babyblau = Dogfather/Team-Dogi-Community, lila = Kooperation, rot = Spicy Media).
|
||||||
|
const TYPE_COLORS = {
|
||||||
|
modi: 0x8fd9ea,
|
||||||
|
kooperation: 0x8b5cf6,
|
||||||
|
scout: 0xd94a5c,
|
||||||
|
creator: 0xd94a5c,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Menschlich lesbare Feldbeschriftungen für alle Formularfelder, die aktuell
|
||||||
|
// auf der Website vorkommen (siehe bewerben-*.html, jeweils name="...").
|
||||||
|
const FIELD_LABELS = {
|
||||||
|
name: "Name",
|
||||||
|
email: "E-Mail",
|
||||||
|
kontakt: "Kontakt (Discord/TikTok)",
|
||||||
|
erfahrung: "Erfahrung",
|
||||||
|
verfuegbarkeit: "Verfügbarkeit",
|
||||||
|
warum: "Warum möchtest du dabei sein?",
|
||||||
|
platform: "Plattform",
|
||||||
|
content: "Content-Art",
|
||||||
|
firma: "Firma / Projekt",
|
||||||
|
idee: "Idee / Anliegen",
|
||||||
|
};
|
||||||
|
|
||||||
|
function feldWert(wert) {
|
||||||
|
const text = String(wert ?? "").trim();
|
||||||
|
if (!text) return "_(leer)_";
|
||||||
|
// Discord-Embed-Feldwerte sind auf 1024 Zeichen begrenzt.
|
||||||
|
return text.length > 1000 ? text.slice(0, 1000) + "…" : text;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function bewerbungAlsDiscordNachrichtPosten(env, type, data) {
|
||||||
|
const envKey = WEBHOOK_ENV_KEYS[type];
|
||||||
|
const webhookUrl = envKey && env[envKey];
|
||||||
|
if (!webhookUrl) {
|
||||||
|
console.error(`[discord-webhook] Keine Webhook-URL für "${type}" hinterlegt (${envKey}) — übersprungen.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fields = Object.entries(data || {})
|
||||||
|
.filter(([, v]) => String(v ?? "").trim() !== "")
|
||||||
|
.map(([key, value]) => ({
|
||||||
|
name: FIELD_LABELS[key] || key,
|
||||||
|
value: feldWert(value),
|
||||||
|
inline: String(value).length < 40,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const embed = {
|
||||||
|
title: TYPE_LABELS[type] || "Neue Bewerbung",
|
||||||
|
color: TYPE_COLORS[type] ?? 0x8fd9ea,
|
||||||
|
fields: fields.length ? fields : [{ name: "Hinweis", value: "Keine Angaben übermittelt." }],
|
||||||
|
footer: { text: "DOGFATHER UNIVERSE — automatisch von der Homepage" },
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const antwort = await fetch(webhookUrl, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ embeds: [embed] }),
|
||||||
|
});
|
||||||
|
if (!antwort.ok) {
|
||||||
|
const antwortText = await antwort.text().catch(() => "");
|
||||||
|
console.error(`[discord-webhook] Fehler beim Posten (${type}): HTTP ${antwort.status} — ${antwortText}`);
|
||||||
|
} else {
|
||||||
|
console.log(`[discord-webhook] Nachricht gepostet (${type}).`);
|
||||||
|
}
|
||||||
|
} catch (fehler) {
|
||||||
|
// Discord nicht erreichbar o.ä. — die Bewerbung selbst ist trotzdem
|
||||||
|
// sicher in der eigenen Datenbank gelandet, das darf hier nicht scheitern.
|
||||||
|
console.error(`[discord-webhook] Ausnahme beim Posten (${type}):`, fehler && fehler.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import { hasPermission } from "../lib/permissions.js";
|
|||||||
import { logAction } from "../lib/audit.js";
|
import { logAction } from "../lib/audit.js";
|
||||||
import { json } from "../lib/http.js";
|
import { json } from "../lib/http.js";
|
||||||
import { bewerbungAlsTicketAnlegen } from "../lib/ticketanizer.js";
|
import { bewerbungAlsTicketAnlegen } from "../lib/ticketanizer.js";
|
||||||
|
import { bewerbungAlsDiscordNachrichtPosten } from "../lib/discord-webhooks.js";
|
||||||
|
|
||||||
const ALLOWED_TYPES = ["creator", "scout", "kooperation", "modi"];
|
const ALLOWED_TYPES = ["creator", "scout", "kooperation", "modi"];
|
||||||
|
|
||||||
@@ -46,13 +47,20 @@ export async function submitApplication(request, env, ctx) {
|
|||||||
.bind(id, type, JSON.stringify(data), nowIso())
|
.bind(id, type, JSON.stringify(data), nowIso())
|
||||||
.run();
|
.run();
|
||||||
|
|
||||||
// Zusätzlich (rein informativ, blockiert die Bewerbung selbst nie) als
|
// Zusätzlich (rein informativ, blockiert die Bewerbung selbst nie):
|
||||||
// echtes Ticket bei Ticketanizer im passenden Bereich anlegen.
|
// 1) echtes Ticket bei Ticketanizer im passenden Bereich anlegen
|
||||||
const ticketVersand = bewerbungAlsTicketAnlegen(env, type, data);
|
// (Web-Inbox zum Bearbeiten/Antworten)
|
||||||
|
// 2) direkte, garantiert sichtbare Discord-Nachricht in den passenden
|
||||||
|
// Kanal posten (Ticketanizer erzeugt bei API-Tickets keinen echten
|
||||||
|
// Discord-Kanal, siehe discord-webhooks.js)
|
||||||
|
const zusatzVersand = Promise.all([
|
||||||
|
bewerbungAlsTicketAnlegen(env, type, data),
|
||||||
|
bewerbungAlsDiscordNachrichtPosten(env, type, data),
|
||||||
|
]);
|
||||||
if (ctx && typeof ctx.waitUntil === "function") {
|
if (ctx && typeof ctx.waitUntil === "function") {
|
||||||
ctx.waitUntil(ticketVersand);
|
ctx.waitUntil(zusatzVersand);
|
||||||
} else {
|
} else {
|
||||||
await ticketVersand.catch(() => {});
|
await zusatzVersand.catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
return json({ ok: true });
|
return json({ ok: true });
|
||||||
|
|||||||
Reference in New Issue
Block a user