feat: Integrationen takten und Telegram als dritten Meldeweg

Telegram alle zwei Minuten, Zammad und Spiegel alle fuenfzehn. Wer dem
Bot etwas schickt, erwartet die Aufgabe gleich in der Liste; in Zammad
aendert sich dagegen nichts im Minutentakt.

Meldungen gehen jetzt auch per Telegram hinaus — mit einem Knopf zum
Abhaken, wenn es um genau eine Aufgabe geht. Eine klemmende Integration
meldet sich wie ein klemmender Kalender: erst nach dem dritten
Fehlversuch, dann aber deutlich.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Florian Wilde
2026-09-11 23:04:05 +02:00
co-authored by Claude Opus 5
parent 6dad3331e2
commit bc60e5f864
3 changed files with 123 additions and 9 deletions
+64
View File
@@ -7,6 +7,10 @@ import {
alteNachweiseEntfernen,
benachrichtigungenVerschicken,
} from '@/server/benachrichtigung/versand'
import { zammadAbgleichen } from '@/server/integration/zammad'
import { alteKopplungenEntfernen, telegramAbholen } from '@/server/integration/telegram'
import { spiegelSenden } from '@/server/integration/spiegel'
import { umgebung } from '@/server/umgebung'
import { jetzt } from '@/server/zeitgeber'
/**
@@ -31,6 +35,24 @@ const AUFRAEUM_PLAN = '17 3 * * *'
* dank Wiederholungssperre nur einmal gemeldet.
*/
const MELDUNGS_PLAN = '*/10 * * * *'
/**
* Integrationen laufen im eigenen Takt.
*
* Telegram oefter als der Rest: Wer dem Bot etwas schickt, erwartet die
* Aufgabe gleich in der Liste, nicht in einer Viertelstunde. Zammad und der
* Spiegel duerfen traeger sein — dort aendert sich nichts im Minutentakt.
*/
const TELEGRAM_PLAN = '*/2 * * * *'
const ZAMMAD_PLAN = '*/15 * * * *'
const SPIEGEL_PLAN = '*/15 * * * *'
/**
* Wer im Protokoll steht, wenn niemand geklickt hat.
*
* Ein Job hat keinen Benutzer. Statt den Eintrag wegzulassen, traegt er
* sichtbar den Dienst ein — sonst sieht es aus, als sei nichts passiert.
*/
const DIENST = { id: null, email: 'dienst@intern' }
async function alleKontenAbgleichen(): Promise<void> {
const zeitpunkt = jetzt()
@@ -87,11 +109,53 @@ export function jobsStarten(): void {
})
})
const einst = umgebung()
if (einst.zammadAktiv) {
cron.schedule(ZAMMAD_PLAN, () => {
void zammadAbgleichen(DIENST)
.then((bericht) => {
if (bericht.angelegt > 0 || bericht.geschlossen > 0) {
console.warn(
`Zammad: ${bericht.angelegt} neu, ${bericht.geschlossen} geschlossen.`,
)
}
})
.catch((fehler: unknown) => {
console.error('Zammad-Abgleich abgebrochen:', fehler)
})
})
}
if (einst.telegramAktiv) {
cron.schedule(TELEGRAM_PLAN, () => {
void telegramAbholen(DIENST)
.then((bericht) => {
if (bericht.verworfen > 0) {
// Nie die Chatkennung: Sie gehoert zu einer Person.
console.warn(`Telegram: ${bericht.verworfen} Nachricht(en) verworfen.`)
}
})
.catch((fehler: unknown) => {
console.error('Telegram-Abholen abgebrochen:', fehler)
})
})
}
if (einst.spiegelSendenAktiv) {
cron.schedule(SPIEGEL_PLAN, () => {
void spiegelSenden().catch((fehler: unknown) => {
console.error('Spiegel-Versand abgebrochen:', fehler)
})
})
}
cron.schedule(AUFRAEUM_PLAN, () => {
void Promise.all([
alteTermineEntfernen(),
abgelaufeneChallengesEntfernen(jetzt()),
alteNachweiseEntfernen(jetzt()),
alteKopplungenEntfernen(jetzt()),
])
.then(() => undefined)
.catch((fehler: unknown) => {