134 lines
4.7 KiB
TypeScript
134 lines
4.7 KiB
TypeScript
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. 3–4 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 };
|
||
});
|