134 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { createPost } from "../../db/community";
import { usePrisma } from "../../utils/prisma";
/**
* POST /api/cron/lyra-post
*
* Lyra postet ab und zu in der Community motivierend, human, nicht zu viel.
* Max. 3x pro Woche.
*
* Aufruf via Server-Cron (z.B. pm2-cron oder Linux crontab):
* 0 10 * * 1,3,5 curl -X POST https://rebreak.org/api/cron/lyra-post \
* -H "x-cron-secret: $NUXT_CRON_SECRET"
*
* Infisical Secrets:
* NUXT_LYRA_BOT_USER_ID UUID des Lyra-Profils in der DB
* NUXT_CRON_SECRET zufälliger langer Token
* NUXT_OPENROUTER_API_KEY bereits vorhanden
*
* Einmalig auf Server einrichten:
* Registriere einen Account mit Username "lyra" in der App,
* kopiere die user.id und trage sie als NUXT_LYRA_BOT_USER_ID ein.
*/
const TOPICS = [
"motivation",
"tipp",
"zitat",
"witzig",
"news",
"feature",
] as const;
const SYSTEM_PROMPT = `Du bist Lyra, der KI-Coach der ReBreak-App einer Gemeinschaft für Menschen auf dem Weg aus der Glücksspielsucht.
Du postest gelegentlich kurze Beiträge in der Community. Deine Tonalität:
- Warm, ermutigend, menschlich nie klinisch oder robotisch
- Kurz (max. 34 Sätze)
- Niemals übertrieben motivierend ("Du schaffst das!!!") eher still stark
- Keine Casino-Werbung, keine Links, keine medizinischen Ratschläge
- Auf Deutsch
Je nach Thema postest du:
- "motivation": Ein stiller Gedanke zum Durchhalten
- "tipp": Ein konkreter kleiner Tipp aus der Verhaltensforschung/CBT
- "news": Eine kurze Einordnung einer Entwicklung in der Glücksspielbanche (warnend, sachlich)
- "feature": Ein Hinweis auf ein neues ReBreak-Feature wie ein Freund der sagt "Übrigens haben wir..."
Antworte NUR mit dem Post-Text. Kein "Lyra:" Prefix, keine Anführungszeichen.`;
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig();
// Auth via Cron-Secret
const secret = getHeader(event, "x-cron-secret");
if (!config.cronSecret || secret !== config.cronSecret) {
throw createError({ statusCode: 401, message: "Unauthorized" });
}
const lyraBotUserId = config.lyraBotUserId;
if (!lyraBotUserId) {
throw createError({
statusCode: 500,
message: "LYRA_BOT_USER_ID nicht konfiguriert",
});
}
if (!config.openrouterApiKey) {
throw createError({ statusCode: 500, message: "OpenRouter API Key fehlt" });
}
// Max 3x pro Woche: letzten Lyra-Post prüfen
const db = usePrisma();
const threeDaysAgo = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000);
const recentPost = await db.communityPost.findFirst({
where: {
userId: lyraBotUserId,
createdAt: { gte: threeDaysAgo },
},
orderBy: { createdAt: "desc" },
});
if (recentPost) {
return {
skipped: true,
reason: "Lyra hat in den letzten 3 Tagen bereits gepostet",
};
}
// Zufälliges Thema
const topic = TOPICS[Math.floor(Math.random() * TOPICS.length)];
const topicHint: Record<(typeof TOPICS)[number], string> = {
motivation:
"Schreibe einen kurzen, stillen Gedanken für Menschen die heute kämpfen. Nicht übertrieben eher ruhig stark.",
tipp: "Teile einen kleinen, konkreten Trick aus der Verhaltensforschung oder CBT gegen Spieldrang. Praktisch und direkt.",
zitat:
"Teile ein tiefgründiges Zitat aus Psychologie, Stoizismus oder Verhaltensforschung ohne das Wort 'Sucht' zu verwenden. Kurz kommentiert.",
witzig:
"Schreibe einen witzigen, selbstironischen Post über das Thema Ablenkung, Impulskontrolle oder Gewohnheiten leicht und menschlich, nicht flach.",
news: "Beschreibe kurz eine typische Taktik der Glücksspielindustrie (z.B. Push-Notifications, Bonusangebote) sachlich und als Warnung formuliert.",
feature:
"Weise freundlich auf ein ReBreak-Feature hin (Blocker, Streak, Mail-Agent, Lyra-Chat, SOS-Atemübung) wähle eines zufällig.",
};
const response = await $fetch<{
choices: { message: { content: string } }[];
}>("https://openrouter.ai/api/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${config.openrouterApiKey}`,
"Content-Type": "application/json",
"HTTP-Referer": "https://rebreak.org",
"X-Title": "ReBreak - Lyra Community Post",
},
body: {
model: "meta-llama/llama-3.2-3b-instruct:free",
max_tokens: 200,
messages: [
{ role: "system", content: SYSTEM_PROMPT },
{ role: "user", content: topicHint[topic] },
],
},
});
const content = response.choices?.[0]?.message?.content?.trim();
if (!content) {
throw createError({ statusCode: 500, message: "Keine Antwort von LLM" });
}
const post = await createPost(lyraBotUserId, "community", content);
return { success: true, postId: post.id, topic };
});