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`;