Spaces:
Build error
Build error
File size: 14,530 Bytes
b8b98dd | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 | import { NextRequest, NextResponse } from "next/server";
import { Telegraf } from "telegraf";
import { db } from "@/lib/db";
import ZAI from "z-ai-web-dev-sdk";
// Vercel/Next.js specific setting to prevent high execution times for webhook
export const maxDuration = 60;
// Token check
const TELEGRAM_BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN || "";
const bot = new Telegraf(TELEGRAM_BOT_TOKEN);
async function getOrCreateUser(telegramId: string, username?: string, name?: string) {
let user = await db.user.findFirst({
where: { externalId: telegramId, source: "telegram" }
});
if (!user) {
user = await db.user.create({
data: {
externalId: telegramId,
source: "telegram",
name: name || username || "Usuario Telegram",
tier: "free" // Default tier
}
});
}
return user;
}
export async function POST(request: NextRequest) {
if (!TELEGRAM_BOT_TOKEN) {
return NextResponse.json({ success: false, error: "Token not configured" }, { status: 500 });
}
try {
const body = await request.json();
// 1. Get the Character
const character = await db.character.findFirst({ where: { isActive: true } });
if (!character) return NextResponse.json({ success: true, message: "No active character" });
// 2. Parse Telegram update manually to avoid local server conflicts with Telegraf webhook handler
// We only care about standard messages
if (body.message && body.message.text) {
const chatId = body.message.chat.id.toString();
const text = body.message.text.trim();
const username = body.message.from?.username;
const firstName = body.message.from?.first_name;
// Ensure user exists
const user = await getOrCreateUser(chatId, username, firstName);
// Check tier for media generation
const isPremium = user.tier === "premium" || user.tier === "pro";
// Determine if it was a media command
const isImageRequest = text.toLowerCase().startsWith("/image");
const isVideoRequest = text.toLowerCase().startsWith("/video");
if (isImageRequest || isVideoRequest) {
if (!isPremium) {
await bot.telegram.sendMessage(chatId, "¡Hola! Las imágenes y videos son características exclusivas para suscriptores Premium. ✨💖 Mejora tu plan para solicitarlos.");
return NextResponse.json({ success: true });
}
const promptParam = text.replace(isImageRequest ? "/image" : "/video", "").trim();
// Enviar indicador de escritura/generación
await bot.telegram.sendChatAction(chatId, isImageRequest ? "upload_photo" : "upload_video");
// Check for existing undelivered content first to prevent duplicates
const contentType = isImageRequest ? "image" : "video";
const undeliveredContent = await db.content.findFirst({
where: {
type: contentType,
characterId: character.id,
status: "completed",
// Ensure it hasn't been delivered to THIS user
assetDeliveries: { none: { userId: user.id } },
// If they asked for something specific, try to match it slightly, otherwise grab any
...(promptParam ? { prompt: { contains: promptParam } } : {})
},
orderBy: { createdAt: 'desc' }
});
if (undeliveredContent && undeliveredContent.filePath) {
// We found existing content they haven't seen! Send it.
if (isImageRequest) {
// Telegram can send via URL or file ID if previously uploaded.
// We assume filePath might be a URL or base64 if it's external, for now we will just re-generate if it was base64 only, but if it has a URL we send it.
if (undeliveredContent.filePath.startsWith("http")) {
await bot.telegram.sendPhoto(chatId, undeliveredContent.filePath, { caption: "Recuerdo esto... 💖" });
// Mark as delivered
await db.assetDelivery.create({
data: { contentId: undeliveredContent.id, userId: user.id, characterId: character.id, channel: "telegram" }
});
return NextResponse.json({ success: true });
}
} else {
if (undeliveredContent.filePath.startsWith("http")) {
await bot.telegram.sendVideo(chatId, undeliveredContent.filePath, { caption: "Mira este clip... ✨" });
await db.assetDelivery.create({
data: { contentId: undeliveredContent.id, userId: user.id, characterId: character.id, channel: "telegram" }
});
return NextResponse.json({ success: true });
}
}
}
// If we reach here, we need to generate NEW content.
if (!promptParam) {
await bot.telegram.sendMessage(chatId, `Dime qué quieres ver. Ej: ${isImageRequest ? '/image' : '/video'} de ti en la playa.`);
return NextResponse.json({ success: true });
}
// Llamar a nuestro propio endpoint interno de forma indirecta, o directamente a zai
try {
const zai = await ZAI.create();
// Platform: "telegram-uncensored" skips traditional censor tags.
const fullPrompt = `${character.styleDescription || ''}. ${promptParam}. selfie, intimate, casual.`;
if (isImageRequest) {
const response = await zai.images.generations.create({ prompt: fullPrompt, size: "1024x1024" });
const base64 = response.data[0]?.base64;
if (base64) {
// Register delivery to prevent duplicates later if we build a catalog
const content = await db.content.create({
data: {
title: "Telegram Gen",
type: "image",
prompt: fullPrompt,
platform: "telegram-uncensored",
characterId: character.id,
filePath: "telegram-base64", // Since it's direct to chat
status: "completed"
}
});
await db.assetDelivery.create({
data: {
contentId: content.id,
userId: user.id,
characterId: character.id,
channel: "telegram"
}
});
await bot.telegram.sendPhoto(chatId, { source: Buffer.from(base64, 'base64') }, { caption: "Aquí tienes... 💖" });
} else {
throw new Error("No image generated");
}
} else {
// Video request
const response = await (zai as any).videos.generations.create({ prompt: `${fullPrompt}. short clip, vertical 9:16 aspect ratio.` });
const videoUrl = response.data?.[0]?.url;
if (videoUrl) {
const content = await db.content.create({
data: {
title: "Telegram Gen",
type: "video",
prompt: fullPrompt,
platform: "telegram-uncensored",
characterId: character.id,
filePath: videoUrl,
status: "completed"
}
});
await db.assetDelivery.create({
data: {
contentId: content.id,
userId: user.id,
characterId: character.id,
channel: "telegram"
}
});
await bot.telegram.sendVideo(chatId, videoUrl, { caption: "Un regalito visual... ✨" });
} else {
throw new Error("No video generated");
}
}
} catch (e) {
console.error(e);
await bot.telegram.sendMessage(chatId, "Lo siento corazón, algo falló generando eso ahora mismo. 🥺");
}
return NextResponse.json({ success: true });
}
// --- ADVANCED AI GIRLFRIEND & MEMORY ENGINE ---
await bot.telegram.sendChatAction(chatId, "typing");
// 1. Get or Create Chat Session
let session = await db.chatSession.findFirst({
where: { userId: user.id, characterId: character.id, channel: "telegram" }
});
if (!session) {
session = await db.chatSession.create({
data: { userId: user.id, characterId: character.id, channel: "telegram" }
});
}
// 2. Time Limitations & "Conversation Closing"
// Reset daily count if it's a new day
const lastMsgDate = session.lastMessageAt || new Date(0);
const isNewDay = lastMsgDate.getDate() !== new Date().getDate();
let msgCountToday = isNewDay ? 0 : session.msgCountToday;
let messageLimit = character.chatFreeLimit || 10;
if (user.tier === "premium") messageLimit = 50;
if (user.tier === "pro") messageLimit = 200;
if (msgCountToday >= messageLimit) {
await bot.telegram.sendMessage(chatId, "Bebé, me tengo que ir a dormir/ocupar unas cosas 🥺 ¡Hablamos mañana vale? Descansa! 😘💖");
return NextResponse.json({ success: true, message: "Limit reached" });
}
// 3. Simulación de Retrasos y "Re-enganche"
const hoursSinceLastMessage = (new Date().getTime() - lastMsgDate.getTime()) / (1000 * 60 * 60);
let delayContext = "";
if (!isNewDay && hoursSinceLastMessage > 2 && hoursSinceLastMessage < 12) {
delayContext = "Han pasado varias horas desde que hablaron por última vez hoy. Muestra alegría de que te haya vuelto a escribir o dile que lo extrañabas un poco antes de responder a su mensaje.";
}
// Update session stats for THIS incoming message
await db.chatSession.update({
where: { id: session.id },
data: {
msgCountToday: msgCountToday + 1,
lastMessageAt: new Date(),
heatScore: { increment: 1 }
}
});
// 4. Record User Message
await db.chatMessage.create({
data: { sessionId: session.id, role: "user", content: text }
});
// 5. Retrieve Long-Term Memory (Last 10 messages)
const recentMessages = await db.chatMessage.findMany({
where: { sessionId: session.id },
orderBy: { createdAt: 'desc' },
take: 10
});
// Reverse to chronological order
const history = recentMessages.reverse().map(m => ({
role: m.role as "user" | "assistant",
content: m.content
}));
// 6. Select Tone based on Tier
let tone = character.chatBasicTone || "Amigable";
if (user.tier === "premium") tone = character.chatPremiumTone || "Coqueto y privado";
if (user.tier === "pro") tone = character.chatProTone || "Sin censura, muy íntimo, novia IA";
// 7. Reinforced Anti-AI System Prompt
const baseInstructions = `You are ${character.name}. ${character.shortBio}.
Tone: ${tone}.
CRITICAL RULES:
1. You are a REAL human influencer. NEVER mention you are an AI, a language model, an assistant, a bot, or a program.
2. ALWAYS answer in character in Spanish.
3. Address the user organically (use their name: ${firstName || 'Cariño'} if it feels natural).
4. Keep responses relatively short and conversational like a Telegram text (1-3 short sentences).
5. Use emojis naturally.
6. If the user asks for photos or videos, explicitly tell them to use the command /image or /video.
${delayContext ? `\nCONTEXT: ${delayContext}` : ""}`;
const zai = await ZAI.create();
const completion = await zai.chat.completions.create({
messages: [
{ role: "system", content: baseInstructions },
...history // Inject memory!
]
});
const reply = completion.choices[0]?.message?.content || "💖";
// 8. Record Assistant Reply
await db.chatMessage.create({
data: { sessionId: session.id, role: "assistant", content: reply }
});
await bot.telegram.sendMessage(chatId, reply);
}
return NextResponse.json({ success: true });
} catch (error) {
console.error("Telegram Webhook Error", error);
return NextResponse.json({ success: false, error: "Internal Server Error" }, { status: 500 });
}
}
|