From bf1059886108bdf8d85cad50b9382ed896e4a6be Mon Sep 17 00:00:00 2001 From: Joachim Hummel Date: Fri, 14 Nov 2025 09:58:21 +0000 Subject: [PATCH] first commit --- CLAUDE.md | 94 +++++++++++++++ README.md | 239 +++++++++++++++++++++++++++++++++++++ index.html | 159 +++++++++++++++++++++++++ tracker.json | 324 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 816 insertions(+) create mode 100644 CLAUDE.md create mode 100644 README.md create mode 100644 index.html create mode 100644 tracker.json diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..dfef554 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,94 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Overview + +This repository contains an n8n workflow configuration (`tracker.json`) that implements a Telegram-based location tracking system without a database. The workflow stores location data in a simple JSON file (`/tmp/n8n-locations.json`) on the n8n server. + +## Workflow Architecture + +The workflow has two main execution paths: + +### 1. Location Capture Flow (Telegram Trigger → Storage) +- **Telegram Trigger**: Receives incoming Telegram messages +- **Hat Location?**: Filters messages containing location data +- **Location verarbeiten**: Extracts and formats location data (lat/lon, user info, timestamp) +- **Lade existierende Daten**: Reads existing locations from `/tmp/n8n-locations.json` +- **Merge mit History**: Combines new location with existing data (keeps last 100 entries) +- **Speichere in File**: Writes updated JSON back to `/tmp/n8n-locations.json` +- **Telegram Bestätigung**: Sends confirmation message with location details and map link + +### 2. Location API Flow (Webhook → JSON Response) +- **Webhook - Location API**: Exposes `/location` endpoint with CORS enabled +- **Lade Daten für API**: Reads location data from file +- **Format API Response**: Formats data into structured JSON (current location, history, metadata) +- **JSON Response**: Returns JSON with CORS headers + +## Key Technical Details + +### Data Storage +- Storage location: `/tmp/n8n-locations.json` +- Format: JSON array of location objects +- Max retention: 100 most recent locations (oldest automatically removed) +- Data persistence: File-based (survives n8n restarts but may be lost on system restart due to `/tmp` location) + +### Location Object Structure +```json +{ + "latitude": number, + "longitude": number, + "timestamp": "ISO 8601 string", + "user_id": number, + "first_name": string, + "last_name": string, + "username": string, + "marker_label": string, + "display_time": "de-DE locale string", + "chat_id": number +} +``` + +### API Response Structure +```json +{ + "success": true, + "current": , + "history": [], + "total_points": number, + "last_updated": "ISO 8601 string" +} +``` + +### Web Interface +The workflow sends users a link to `https://web.unixweb.home64.de/tracker/index.html` for viewing locations on a map. This frontend is hosted separately and not included in this repository. + +## Workflow Configuration + +- **Telegram Credentials**: Uses "Telegram account n8n-munich-bot" (ID: dRHgVQKqowQHIait) +- **Webhook IDs**: + - Telegram trigger: `telegram-location-webhook` + - Location API: `location-api-endpoint` +- **Error Workflow**: ID `PhwIkaqyXRasTXDH` (configured but not included in this export) +- **Execution Order**: v1 +- **Caller Policy**: workflowsFromSameOwner + +## Modifying the Workflow + +When editing this workflow: +1. The workflow is active by default - test changes carefully to avoid disrupting live tracking +2. JavaScript code nodes use n8n's code execution environment (not vanilla Node.js) +3. Shell commands execute in n8n's runtime environment - ensure `/tmp` is writable +4. CORS is configured for `*` (all origins) - restrict if needed for security +5. Date formatting uses `de-DE` locale - adjust if different locale needed +6. The 100-entry limit prevents unbounded growth - increase if more history is needed + +## Deployment + +To use this workflow: +1. Import `tracker.json` into n8n instance +2. Configure Telegram bot credentials +3. Ensure n8n has write permissions to `/tmp/n8n-locations.json` +4. Activate the workflow +5. Send a location via Telegram to test the capture flow +6. Access the webhook endpoint to verify API functionality diff --git a/README.md b/README.md new file mode 100644 index 0000000..302fc25 --- /dev/null +++ b/README.md @@ -0,0 +1,239 @@ +# Telegram Location Tracker + +Ein n8n-Workflow zur Verfolgung von Standorten über Telegram, ohne Datenbank-Anforderungen. + +## Überblick + +Dieser Workflow ermöglicht es Nutzern, ihre Standorte über einen Telegram-Bot zu teilen. Die Standortdaten werden in einer einfachen JSON-Datei gespeichert und können über eine API abgerufen werden, um sie auf einer Karte anzuzeigen. + +## Funktionen + +- **Standort-Erfassung**: Empfängt Standorte über Telegram und speichert sie automatisch +- **Historien-Verwaltung**: Behält die letzten 100 Standorte +- **API-Endpunkt**: Stellt Standortdaten per REST-API zur Verfügung +- **Web-Oberfläche**: Interaktive Karte mit Leaflet.js zur Visualisierung (index.html) +- **Bestätigungs-Nachrichten**: Sendet Bestätigungen mit Koordinaten und Kartenlink +- **Keine Datenbank**: Verwendet einfache dateibasierte Speicherung + +## Voraussetzungen + +- Eine laufende n8n-Instanz +- Ein Telegram-Bot mit gültigem API-Token +- Schreibrechte für `/tmp/n8n-locations.json` auf dem n8n-Server + +## Installation + +1. **Workflow importieren**: + - Öffne deine n8n-Instanz + - Navigiere zu "Workflows" → "Import from File" + - Wähle die `tracker.json` Datei aus + +2. **Telegram-Bot konfigurieren**: + - Erstelle einen Bot über [@BotFather](https://t.me/botfather) + - Kopiere das API-Token + - In n8n: Gehe zu "Credentials" und füge die Telegram-API-Credentials hinzu + - Weise die Credentials dem "Telegram Trigger" und "Telegram Bestätigung" Node zu + +3. **Workflow aktivieren**: + - Öffne den importierten Workflow + - Klicke auf "Active" um den Workflow zu aktivieren + +4. **Testen**: + - Sende einen Standort an deinen Telegram-Bot + - Du solltest eine Bestätigungsnachricht mit den Koordinaten erhalten + +## Verwendung + +### Standort senden + +1. Öffne den Chat mit deinem Telegram-Bot +2. Klicke auf das Büroklammer-Symbol (Anhang) +3. Wähle "Standort" +4. Sende deinen aktuellen Standort oder wähle einen auf der Karte +5. Der Bot bestätigt den empfangenen Standort mit Details + +### Standorte abrufen + +Der Workflow stellt einen API-Endpunkt zur Verfügung: + +``` +GET https://deine-n8n-instanz.de/webhook/location +``` + +**Beispiel-Antwort**: +```json +{ + "success": true, + "current": { + "latitude": 48.1351, + "longitude": 11.5820, + "timestamp": "2025-11-14T10:30:00.000Z", + "user_id": 123456789, + "first_name": "Max", + "last_name": "Mustermann", + "username": "maxmuster", + "marker_label": "Max Mustermann", + "display_time": "14.11.2025, 11:30:00", + "chat_id": 123456789 + }, + "history": [...], + "total_points": 42, + "last_updated": "2025-11-14T10:30:00.000Z" +} +``` + +### Karten-Ansicht + +Die Bestätigungsnachricht enthält einen Link zur Karten-Ansicht: +``` +https://web.unixweb.home64.de/tracker/index.html +``` + +**Web-Oberfläche (index.html)** + +Das Repository enthält eine vollständige Web-Oberfläche zur Visualisierung der Standortdaten: + +**Features**: +- 📍 Interaktive Karte mit [Leaflet.js](https://leafletjs.com/) +- 🔄 Auto-Refresh alle 5 Sekunden (kann umgeschaltet werden) +- 📊 Aktuellster Standort als Marker mit Popup +- 📈 Standort-Historie als blaue Linie +- ℹ️ Status-Info mit Anzahl der Datenpunkte +- 🎯 Automatische Zentrierung auf aktuellen Standort + +**Verwendung**: +1. Öffne die `index.html` in einem Browser +2. Die Karte lädt automatisch die neuesten Standorte +3. Klicke auf Marker für Details (Name, Zeitstempel) +4. Schalte Auto-Refresh nach Bedarf um + +**Konfiguration**: +Passe die API-URL in `index.html` an deine n8n-Instanz an: +```javascript +// Zeile 85: +const API_URL = 'https://deine-n8n-instanz.de/webhook/location'; +``` + +**Deployment**: +- Hoste die `index.html` auf einem Webserver (Apache, nginx, etc.) +- Oder öffne sie direkt als Datei im Browser (für lokale Tests) +- CORS muss in n8n aktiviert sein (ist standardmäßig der Fall) + +## Workflow-Struktur + +### Standort-Erfassung (Hauptfluss) + +``` +Telegram Trigger + ↓ +Hat Location? (Filter) + ↓ +Location verarbeiten (JS: Daten extrahieren) + ↓ +Lade existierende Daten (Shell: cat JSON-Datei) + ↓ +Merge mit History (JS: Neue Daten hinzufügen) + ↓ +Speichere in File (Shell: JSON schreiben) + ↓ +Telegram Bestätigung (Nachricht an User) +``` + +### API-Endpunkt + +``` +Webhook - Location API + ↓ +Lade Daten für API (Shell: cat JSON-Datei) + ↓ +Format API Response (JS: JSON formatieren) + ↓ +JSON Response (CORS-Header + JSON zurückgeben) +``` + +## Datenspeicherung + +- **Speicherort**: `/tmp/n8n-locations.json` +- **Format**: JSON-Array mit Standort-Objekten +- **Maximale Einträge**: 100 (älteste werden automatisch entfernt) +- **Persistenz**: Die Datei überlebt n8n-Neustarts, kann aber bei System-Neustarts verloren gehen (da in `/tmp`) + +### Empfehlung für Produktion + +Für produktiven Einsatz sollte der Speicherort von `/tmp/n8n-locations.json` zu einem persistenten Pfad geändert werden: + +```javascript +// In den Nodes "Lade existierende Daten" und "Lade Daten für API": +cat /var/lib/n8n/locations.json 2>/dev/null || echo '[]' + +// In dem Node "Speichere in File": +echo '...' > /var/lib/n8n/locations.json +``` + +## Anpassungen + +### Anzahl gespeicherter Standorte ändern + +Im Node "Merge mit History" die Zeile ändern: + +```javascript +// Von 100 zu z.B. 500 ändern: +if (locations.length > 500) { + locations = locations.slice(0, 500); +} +``` + +### Datumsformat ändern + +Im Node "Location verarbeiten" das Locale ändern: + +```javascript +// Von 'de-DE' zu z.B. 'en-US' ändern: +const displayTime = new Date(messageDate * 1000).toLocaleString('en-US'); +``` + +### CORS-Beschränkung + +Im Node "Webhook - Location API" unter Options → Response Headers: + +```javascript +// Aktuell: Alle Origins erlaubt +"Access-Control-Allow-Origin": "*" + +// Besser für Produktion: +"Access-Control-Allow-Origin": "https://deine-domain.de" +``` + +## Sicherheitshinweise + +- Der API-Endpunkt ist öffentlich zugänglich - implementiere ggf. Authentifizierung +- CORS ist für alle Origins geöffnet - beschränke dies in Produktion +- Die Telegram-Bot-Credentials sollten sicher verwahrt werden +- Standortdaten sind sensibel - beachte DSGVO-Anforderungen + +## Fehlerbehebung + +### "Standort gespeichert" wird nicht angezeigt + +- Prüfe, ob der Workflow aktiv ist +- Prüfe die Telegram-Bot-Credentials +- Schau in die Workflow-Execution-Historie für Fehler + +### API gibt leere Daten zurück + +- Prüfe, ob die Datei `/tmp/n8n-locations.json` existiert +- Teste den Shell-Befehl: `cat /tmp/n8n-locations.json` +- Prüfe Dateiberechtigungen (n8n muss lesen können) + +### Standorte gehen nach Neustart verloren + +- Ändere den Speicherort von `/tmp/` zu einem persistenten Pfad +- Siehe "Empfehlung für Produktion" oben + +## Lizenz + +Dieses Projekt steht zur freien Verfügung. + +## Support + +Bei Fragen oder Problemen, erstelle bitte ein Issue in diesem Repository. diff --git a/index.html b/index.html new file mode 100644 index 0000000..475661a --- /dev/null +++ b/index.html @@ -0,0 +1,159 @@ + + + + + + Location Test + + + + +
+
+

📍 Location Tracker

+
Lade...
+ +
+ + + + + + diff --git a/tracker.json b/tracker.json new file mode 100644 index 0000000..ad05e15 --- /dev/null +++ b/tracker.json @@ -0,0 +1,324 @@ +{ + "name": "Telegram Location Tracker - Native (No DB)", + "nodes": [ + { + "parameters": { + "updates": [ + "message" + ], + "additionalFields": {} + }, + "id": "b7769320-830c-4a76-9086-ea4067969ad7", + "name": "Telegram Trigger", + "type": "n8n-nodes-base.telegramTrigger", + "typeVersion": 1.1, + "position": [ + 0, + 16 + ], + "webhookId": "telegram-location-webhook", + "credentials": { + "telegramApi": { + "id": "dRHgVQKqowQHIait", + "name": "Telegram account n8n-munich-bot" + } + } + }, + { + "parameters": { + "conditions": { + "string": [ + { + "value1": "={{ $json.message.location }}", + "operation": "isNotEmpty" + } + ] + } + }, + "id": "57f03b50-3184-4d1e-9d33-f628f93f466b", + "name": "Hat Location?", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [ + 224, + 16 + ] + }, + { + "parameters": { + "jsCode": "// Extrahiere Location-Daten\nconst items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n const location = item.json.message.location;\n const from = item.json.message.from;\n const messageDate = item.json.message.date;\n \n const timestamp = new Date(messageDate * 1000).toISOString();\n const displayTime = new Date(messageDate * 1000).toLocaleString('de-DE');\n \n results.push({\n json: {\n latitude: location.latitude,\n longitude: location.longitude,\n timestamp: timestamp,\n user_id: from.id,\n first_name: from.first_name || '',\n last_name: from.last_name || '',\n username: from.username || '',\n marker_label: `${from.first_name || ''} ${from.last_name || ''}`.trim(),\n display_time: displayTime,\n chat_id: item.json.message.chat.id\n }\n });\n}\n\nreturn results;" + }, + "id": "dafcade6-0563-47b9-bd0d-dff0f61d1dbb", + "name": "Location verarbeiten", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 448, + 0 + ] + }, + { + "parameters": { + "command": "cat /tmp/n8n-locations.json 2>/dev/null || echo '[]'" + }, + "id": "d3b9945b-4d17-40c0-ae30-3d45801bae1e", + "name": "Lade existierende Daten", + "type": "n8n-nodes-base.executeCommand", + "typeVersion": 1, + "position": [ + 672, + 0 + ] + }, + { + "parameters": { + "jsCode": "// Neue Location zu File hinzufügen\nconst newLocation = $('Location verarbeiten').first().json;\n\n// Lade bestehende Locations\nlet locations = [];\ntry {\n const fileContent = $input.first().json.stdout;\n locations = JSON.parse(fileContent);\n} catch (e) {\n locations = [];\n}\n\n// Füge neue Location hinzu\nlocations.unshift(newLocation); // Am Anfang einfügen (neueste zuerst)\n\n// Behalte nur die letzten 100 Einträge\nif (locations.length > 100) {\n locations = locations.slice(0, 100);\n}\n\nreturn [{\n json: {\n locations: locations,\n json_string: JSON.stringify(locations, null, 2),\n current: newLocation,\n total: locations.length\n }\n}];" + }, + "id": "4fadae07-460f-4c29-800b-1fdbffe25991", + "name": "Merge mit History", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 880, + 0 + ] + }, + { + "parameters": { + "command": "=echo '{{ $json.json_string.replace(/'/g, \"'\\\\'''\") }}' > /tmp/n8n-locations.json" + }, + "id": "c519118b-0626-4ace-befc-ee9859d73504", + "name": "Speichere in File", + "type": "n8n-nodes-base.executeCommand", + "typeVersion": 1, + "position": [ + 1104, + 0 + ] + }, + { + "parameters": { + "chatId": "={{ $('Location verarbeiten').first().json.chat_id }}", + "text": "=✅ Standort gespeichert!\n\n📍 Koordinaten:\nLat: {{ $('Location verarbeiten').first().json.latitude }}\nLon: {{ $('Location verarbeiten').first().json.longitude }}\n\n🕐 Zeit: {{ $('Location verarbeiten').first().json.display_time }}\n\n🗺️ Karte:\nhttps://web.unixweb.home64.de/tracker/index.html\n\n📊 Gespeicherte Punkte: {{ $json.total }}", + "additionalFields": {} + }, + "id": "aa023c84-7664-4641-b3fc-cd30b9f69941", + "name": "Telegram Bestätigung", + "type": "n8n-nodes-base.telegram", + "typeVersion": 1.1, + "position": [ + 1328, + 0 + ], + "webhookId": "9df3ad7d-315d-4782-b4ec-4fb154c0b46d", + "credentials": { + "telegramApi": { + "id": "dRHgVQKqowQHIait", + "name": "Telegram account n8n-munich-bot" + } + } + }, + { + "parameters": { + "path": "location", + "responseMode": "lastNode", + "options": { + "allowedOrigins": "*" + } + }, + "id": "b47e8370-3276-4f49-a2c5-25ef07b83ce8", + "name": "Webhook - Location API", + "type": "n8n-nodes-base.webhook", + "typeVersion": 1.1, + "position": [ + 0, + 208 + ], + "webhookId": "location-api-endpoint" + }, + { + "parameters": { + "command": "cat /tmp/n8n-locations.json 2>/dev/null || echo '[]'" + }, + "id": "200dbfff-2932-4e92-9e47-181ff706c0de", + "name": "Lade Daten für API", + "type": "n8n-nodes-base.executeCommand", + "typeVersion": 1, + "position": [ + 224, + 208 + ] + }, + { + "parameters": { + "jsCode": "// Parse locations und baue API Response\nlet locations = [];\ntry {\n const fileContent = $input.first().json.stdout;\n locations = JSON.parse(fileContent);\n} catch (e) {\n locations = [];\n}\n\nconst current = locations.length > 0 ? locations[0] : null;\n\nreturn [{\n json: {\n success: true,\n current: current,\n history: locations,\n total_points: locations.length,\n last_updated: current ? current.timestamp : null\n }\n}];" + }, + "id": "7c56b850-f1dd-4c51-ac8d-4313e199e6e7", + "name": "Format API Response", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 448, + 208 + ] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ $json }}", + "options": { + "responseHeaders": { + "entries": [ + { + "name": "Access-Control-Allow-Origin", + "value": "*" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + } + } + }, + "id": "9b720b0b-b4f8-4599-9e2f-d6e8f67819ab", + "name": "JSON Response", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1, + "position": [ + 672, + 208 + ] + }, + { + "parameters": { + "height": 544, + "width": 1632, + "color": 4 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -80, + -112 + ], + "typeVersion": 1, + "id": "29506fa4-6cb3-403f-bd64-6a2f49a2eb85", + "name": "Sticky Note" + } + ], + "pinData": {}, + "connections": { + "Telegram Trigger": { + "main": [ + [ + { + "node": "Hat Location?", + "type": "main", + "index": 0 + } + ] + ] + }, + "Hat Location?": { + "main": [ + [ + { + "node": "Location verarbeiten", + "type": "main", + "index": 0 + } + ] + ] + }, + "Location verarbeiten": { + "main": [ + [ + { + "node": "Lade existierende Daten", + "type": "main", + "index": 0 + } + ] + ] + }, + "Lade existierende Daten": { + "main": [ + [ + { + "node": "Merge mit History", + "type": "main", + "index": 0 + } + ] + ] + }, + "Merge mit History": { + "main": [ + [ + { + "node": "Speichere in File", + "type": "main", + "index": 0 + } + ] + ] + }, + "Speichere in File": { + "main": [ + [ + { + "node": "Telegram Bestätigung", + "type": "main", + "index": 0 + } + ] + ] + }, + "Webhook - Location API": { + "main": [ + [ + { + "node": "Lade Daten für API", + "type": "main", + "index": 0 + } + ] + ] + }, + "Lade Daten für API": { + "main": [ + [ + { + "node": "Format API Response", + "type": "main", + "index": 0 + } + ] + ] + }, + "Format API Response": { + "main": [ + [ + { + "node": "JSON Response", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": true, + "settings": { + "executionOrder": "v1", + "callerPolicy": "workflowsFromSameOwner", + "availableInMCP": false, + "errorWorkflow": "PhwIkaqyXRasTXDH" + }, + "versionId": "b87a412a-4833-4f0d-aa33-0ba9074238d9", + "meta": { + "instanceId": "12d864c68e4fb5dfd100dc0c683b95f43cd55af7e9efa82e25407fac5a3824a5" + }, + "id": "5dUTiJE61r7opoul", + "tags": [] +}