diff --git a/server.js b/server.js index 4df1a2e..325f59a 100644 --- a/server.js +++ b/server.js @@ -38,7 +38,7 @@ app.get("/api/contacts", async (req, res) => { } try { - const limit = 150; + const limit = 100; const suffix = query ? `?query=${encodeURIComponent(query)}&limit=${limit}` : `?limit=${limit}`; const payload = await monicaFetch(`/api/contacts${suffix}`); const contacts = Array.isArray(payload.data) ? payload.data : []; @@ -265,7 +265,7 @@ export async function writeToMonica(entry) { results.push(await monicaWrite("note", "/api/notes", { contact_id: contactId, body: entry.note.body, - is_favorited: Boolean(entry.note.isFavorited), + is_favorited: entry.note.isFavorited ? 1 : 0, })); } @@ -347,17 +347,37 @@ function inferContacts(text, contactsList = []) { if (Array.isArray(contactsList) && contactsList.length > 0) { for (const contact of contactsList) { if (!contact.name) continue; - const nameParts = contact.name.trim().split(/\s+/); - const firstName = nameParts[0]; - const fullName = contact.name.trim(); - const escFullName = fullName.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + // Clean up parentheses from the name (e.g. "Aaron Lukas Lingel (Fludo Schlingel)" -> "Aaron Lukas Lingel") + const cleanContactName = contact.name.replace(/\(.*?\)/g, "").replace(/\s+/g, " ").trim(); + if (!cleanContactName) continue; + + const nameParts = cleanContactName.split(/\s+/).filter(p => p.length > 1); + if (nameParts.length === 0) continue; + + const firstName = nameParts[0]; + const lastName = nameParts[nameParts.length - 1]; + + const escFullName = cleanContactName.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); const escFirstName = firstName.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); const regexFull = new RegExp(`\\b${escFullName}\\b`, 'i'); const regexFirst = new RegExp(`\\b${escFirstName}\\b`, 'i'); - if (regexFull.test(text) || (firstName.length > 2 && regexFirst.test(text))) { + let matches = false; + if (regexFull.test(text)) { + matches = true; + } else if (nameParts.length >= 2) { + const escLastName = lastName.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + const regexLast = new RegExp(`\\b${escLastName}\\b`, 'i'); + if (regexFirst.test(text) && regexLast.test(text)) { + matches = true; + } + } else if (firstName.length > 2 && regexFirst.test(text)) { + matches = true; + } + + if (matches) { if (!candidates.some((item) => item.id === contact.id)) { candidates.push({ id: contact.id, @@ -539,11 +559,14 @@ export function cryptoRandomId() { return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`; } -app.listen(port, () => { - console.log(`Friends listening on http://localhost:${port}`); - if (process.env.TELEGRAM_BOT_TOKEN) { - startTelegramBot(); - } else { - console.log("Telegram Bot not started: TELEGRAM_BOT_TOKEN is not configured in .env"); - } -}); +const isMain = process.argv[1] && (path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url))); +if (isMain) { + app.listen(port, () => { + console.log(`Friends listening on http://localhost:${port}`); + if (process.env.TELEGRAM_BOT_TOKEN) { + startTelegramBot(); + } else { + console.log("Telegram Bot not started: TELEGRAM_BOT_TOKEN is not configured in .env"); + } + }); +} diff --git a/telegram.js b/telegram.js index 14776fb..7aa31d5 100644 --- a/telegram.js +++ b/telegram.js @@ -6,7 +6,9 @@ import { cryptoRandomId, } from "./server.js"; -const monicaBaseUrl = (process.env.MONICA_BASE_URL || "https://crm.mischlabs.de").replace(/\/$/, ""); +function getMonicaBaseUrl() { + return (process.env.MONICA_BASE_URL || "https://crm.mischlabs.de").replace(/\/$/, ""); +} // Helper to escape HTML characters for Telegram HTML parse_mode function escapeHtml(text) { @@ -85,40 +87,64 @@ async function transcribeVoice(audioBuffer) { } const base64Audio = Buffer.from(audioBuffer).toString("base64"); - const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${geminiKey}`; + + // List of models to try in order of preference + const modelsToTry = [ + "gemini-2.5-flash", + "gemini-2.0-flash", + "gemini-1.5-flash-latest", + "gemini-1.5-flash" + ]; - const res = await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - contents: [ - { - parts: [ + let lastError = null; + for (const model of modelsToTry) { + const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${geminiKey}`; + try { + console.log(`[Telegram] Trying voice transcription with model: ${model}`); + const res = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + contents: [ { - inlineData: { - mimeType: "audio/ogg", - data: base64Audio, - }, - }, - { - text: "Transkribiere diese Sprachnachricht wortgetreu in deutschen Text. Gib NUR die Transkription zurück, ohne Einleitung, Kommentare oder sonstige Zusätze. Falls nichts verständlich gesprochen wurde, antworte mit einem leeren Text.", + parts: [ + { + inlineData: { + mimeType: "audio/ogg", + data: base64Audio, + }, + }, + { + text: "Transkribiere diese Sprachnachricht wortgetreu in deutschen Text. Gib NUR die Transkription zurück, ohne Einleitung, Kommentare oder sonstige Zusätze. Falls nichts verständlich gesprochen wurde, antworte mit einem leeren Text.", + }, + ], }, ], - }, - ], - }), - }); + }), + }); - if (!res.ok) { - const errorText = await res.text(); - throw new Error(`Gemini API failed (${res.status}): ${errorText}`); + if (res.status === 404) { + console.warn(`[Telegram] Model ${model} returned 404. Trying next model...`); + continue; + } + + if (!res.ok) { + const errorText = await res.text(); + throw new Error(`Gemini API failed (${res.status}): ${errorText}`); + } + + const data = await res.json(); + const text = data.candidates?.[0]?.content?.parts?.[0]?.text || ""; + return text.trim(); + } catch (err) { + console.error(`[Telegram] Error with model ${model}:`, err.message); + lastError = err; + } } - const data = await res.json(); - const text = data.candidates?.[0]?.content?.parts?.[0]?.text || ""; - return text.trim(); + throw new Error(`All Gemini models failed. Last error: ${lastError ? lastError.message : "unknown"}`); } // Fetch available contacts from Monica to perform NLP name matching @@ -133,7 +159,7 @@ async function fetchMonicaContacts() { } try { - const payload = await monicaFetch("/api/contacts?limit=150"); + const payload = await monicaFetch("/api/contacts?limit=100"); const contacts = Array.isArray(payload.data) ? payload.data : []; return contacts.map((c) => ({ id: c.id, @@ -280,7 +306,7 @@ async function handleUpdate(update) { } if (analysis.contact) { - const contactUrl = `${monicaBaseUrl}/people/${analysis.contact.id}`; + const contactUrl = `${getMonicaBaseUrl()}/people/${analysis.contact.id}`; responseText += `👤 Kontakt: ${escapeHtml(analysis.contact.name)}\n`; } else { responseText += `👤 Kontakt: Keine Zuordnung\n`;