folx-tv/server/horoscope-generator.ts
Folx Ops 0a2f477654 Kozmicni dogodki: generiraj posamicno (6 klicev) + boljse parsanje
Array klic je padel (Failed to parse) ker gpt-5-mini ni vrnil cistega JSON
arraya. Zdaj 6 locenih klicev z {...} objektom (isti vzorec kot horoskop),
markdown fence stripping in vecji token limit.
2026-06-07 15:16:21 +00:00

217 lines
8.4 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 OpenAI from "openai";
import { db } from "./db";
import { dailyHoroscopes, cosmicEvents } from "@shared/schema";
import { eq, and } from "drizzle-orm";
const openai = new OpenAI({
apiKey: process.env.AI_INTEGRATIONS_OPENAI_API_KEY,
baseURL: process.env.AI_INTEGRATIONS_OPENAI_BASE_URL,
});
const SIGN_NAMES = [
"Widder", "Stier", "Zwillinge", "Krebs", "Löwe", "Jungfrau",
"Waage", "Skorpion", "Schütze", "Steinbock", "Wassermann", "Fische"
];
function getTodayStr(): string {
const d = new Date();
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
}
export async function getHoroscopesForToday(): Promise<any[]> {
const today = getTodayStr();
const existing = await db.select().from(dailyHoroscopes).where(eq(dailyHoroscopes.dateStr, today));
if (existing.length === 12) return existing;
return [];
}
export async function generateDailyHoroscopes(): Promise<void> {
const today = getTodayStr();
const existing = await db.select().from(dailyHoroscopes).where(eq(dailyHoroscopes.dateStr, today));
if (existing.length >= 12) {
console.log(`Horoscopes for ${today} already exist.`);
return;
}
console.log(`Generating horoscopes for ${today}...`);
for (let i = 0; i < SIGN_NAMES.length; i++) {
const signName = SIGN_NAMES[i];
const alreadyExists = existing.find(h => h.signIndex === i);
if (alreadyExists) continue;
try {
const response = await openai.chat.completions.create({
model: "gpt-5-mini",
messages: [
{
role: "system",
content: `Du bist ein erfahrener Astrologe, der tägliche Horoskope für eine deutschsprachige Volksmusik- und Schlager-Nachrichtenwebsite schreibt. Dein Stil ist warm, ermutigend und poetisch. Du beziehst manchmal Musik, Natur und alpine Kultur in deine Texte ein. Schreibe immer auf Deutsch. Das heutige Datum ist ${today}.`
},
{
role: "user",
content: `Erstelle ein ausführliches Tageshoroskop für das Sternzeichen ${signName} für heute (${today}).
Antworte NUR mit einem JSON-Objekt in diesem exakten Format (kein Markdown, keine Erklärung):
{
"general": "Ausführlicher allgemeiner Tagestext, mindestens 4-5 Sätze über die allgemeine Energie, Stimmung und Möglichkeiten des Tages.",
"love": "Ausführlicher Text über Liebe und Partnerschaft, mindestens 3-4 Sätze mit konkreten Ratschlägen für Singles und Paare.",
"career": "Ausführlicher Text über Beruf und Finanzen, mindestens 3-4 Sätze mit konkreten Tipps.",
"health": "Ausführlicher Text über Gesundheit und Wohlbefinden, mindestens 3-4 Sätze.",
"tip": "Ein konkreter, umsetzbarer Tipp des Tages in 1-2 Sätzen.",
"weekly": "Ausführliche Wochenvorschau, mindestens 4-5 Sätze mit Hinweisen für jeden Wochentag.",
"monthly": "Ausführliche Monatsvorschau, mindestens 4-5 Sätze über die wichtigsten Themen des Monats."
}`
}
],
max_completion_tokens: 2000,
});
const content = response.choices[0]?.message?.content || "";
const jsonMatch = content.match(/\{[\s\S]*\}/);
if (!jsonMatch) {
console.error(`Failed to parse horoscope for ${signName}`);
continue;
}
const parsed = JSON.parse(jsonMatch[0]);
await db.insert(dailyHoroscopes).values({
signIndex: i,
signName,
dateStr: today,
general: parsed.general || "",
love: parsed.love || "",
career: parsed.career || "",
health: parsed.health || "",
tip: parsed.tip || "",
weekly: parsed.weekly || "",
monthly: parsed.monthly || "",
});
console.log(`Generated horoscope for ${signName}`);
} catch (err: any) {
console.error(`Error generating horoscope for ${signName}:`, err.message);
}
}
console.log(`Horoscope generation complete for ${today}.`);
}
export async function getOrGenerateHoroscope(signIndex: number): Promise<any | null> {
const today = getTodayStr();
const [existing] = await db.select().from(dailyHoroscopes)
.where(and(eq(dailyHoroscopes.dateStr, today), eq(dailyHoroscopes.signIndex, signIndex)));
if (existing) return existing;
return null;
}
const VALID_ICONS = ["mercury", "venus", "moon", "newmoon", "sun", "saturn", "jupiter", "mars"];
const VALID_TYPES = ["retrograde", "moon", "transit", "season"];
export async function getCosmicEventsForToday(): Promise<any[]> {
const today = getTodayStr();
const rows = await db.select().from(cosmicEvents)
.where(eq(cosmicEvents.dateStr, today));
return rows
.sort((a, b) => a.position - b.position)
.map((r) => ({
title: r.title,
description: r.description,
dateRange: r.dateRange,
icon: r.icon,
type: r.type,
affectedSigns: JSON.parse(r.affectedSigns || "[]"),
}));
}
export async function generateCosmicEvents(): Promise<void> {
const today = getTodayStr();
const existing = await db.select().from(cosmicEvents)
.where(eq(cosmicEvents.dateStr, today));
if (existing.length >= 6) {
console.log(`Cosmic events for ${today} already exist.`);
return;
}
console.log(`Generating cosmic events for ${today}...`);
const d = new Date();
const monthName = d.toLocaleDateString("de-DE", { month: "long", year: "numeric" });
const eventSlots = [
"die aktuelle Tierkreis-Saison (Sonne im aktuellen Sternzeichen), type=season, icon=sun",
"die aktuelle Mondphase (Vollmond oder Neumond, je nachdem was näher ist), type=moon, icon=moon oder newmoon",
"ein aktueller Venus-Transit (Venus im aktuellen Zeichen), type=transit, icon=venus",
"ein aktueller Mars- oder Merkur-Aspekt (inkl. eventueller Retrograde), type=transit oder retrograde, icon=mars oder mercury",
"ein aktueller Saturn-Transit, type=transit, icon=saturn",
"ein aktueller Jupiter-Transit, type=transit, icon=jupiter",
];
const results: any[] = [];
for (let i = 0; i < eventSlots.length; i++) {
try {
const response = await openai.chat.completions.create({
model: "gpt-5-mini",
messages: [
{
role: "system",
content: `Du bist ein erfahrener Astrologe für eine deutschsprachige Volksmusik- und Schlager-Website. Du kennst die realen astronomischen Daten. Heute ist der ${today} (${monthName}). Beschreibe nur Ereignisse, die rund um dieses Datum tatsächlich aktiv sind, mit realistischen Datumsangaben. Antworte ausschließlich auf Deutsch.`,
},
{
role: "user",
content: `Beschreibe folgendes aktuelles kosmisches Ereignis für heute (${today}): ${eventSlots[i]}.
Antworte NUR mit einem JSON-Objekt (kein Markdown, keine Erklärung) in diesem exakten Format:
{
"title": "Kurzer Titel, z.B. 'Vollmond im Skorpion' oder 'Zwillinge-Saison'",
"description": "3-4 Sätze über die Bedeutung dieses Ereignisses und seinen Einfluss.",
"dateRange": "Realistische Datumsangabe passend zum heutigen Datum, z.B. '21. Mai 20. Juni 2026' oder '11. Juni 2026'",
"icon": "EINER VON: mercury, venus, moon, newmoon, sun, saturn, jupiter, mars",
"type": "EINER VON: retrograde, moon, transit, season",
"affectedSigns": ["3-4 betroffene Sternzeichen auf Deutsch"]
}`,
},
],
max_completion_tokens: 3000,
});
let content = response.choices[0]?.message?.content || "";
content = content.replace(/```json/gi, "").replace(/```/g, "").trim();
const jsonMatch = content.match(/\{[\s\S]*\}/);
if (!jsonMatch) {
console.error(`Failed to parse cosmic event ${i}. Raw:`, content.slice(0, 300));
continue;
}
const ev = JSON.parse(jsonMatch[0]);
const icon = VALID_ICONS.includes(ev.icon) ? ev.icon : "moon";
const type = VALID_TYPES.includes(ev.type) ? ev.type : "transit";
const signs = Array.isArray(ev.affectedSigns) ? ev.affectedSigns : [];
await db.insert(cosmicEvents).values({
dateStr: today,
position: i,
title: String(ev.title || "").slice(0, 120),
description: String(ev.description || ""),
dateRange: String(ev.dateRange || "").slice(0, 120),
icon,
type,
affectedSigns: JSON.stringify(signs),
});
results.push(ev.title);
} catch (err: any) {
console.error(`Error generating cosmic event ${i}:`, err.message);
}
}
console.log(`Generated ${results.length} cosmic events for ${today}.`);
}