first commit
This commit is contained in:
94
CLAUDE.md
Normal file
94
CLAUDE.md
Normal file
@@ -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": <most recent location object>,
|
||||||
|
"history": [<array of all location objects>],
|
||||||
|
"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
|
||||||
239
README.md
Normal file
239
README.md
Normal file
@@ -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.
|
||||||
159
index.html
Normal file
159
index.html
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Location Test</title>
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 0; font-family: Arial, sans-serif; }
|
||||||
|
#map { height: 100vh; width: 100%; }
|
||||||
|
.info {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
background: white;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
z-index: 1000;
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
.toggle-btn {
|
||||||
|
margin-top: 15px;
|
||||||
|
padding: 10px 15px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
width: 100%;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.toggle-btn.active {
|
||||||
|
background: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.toggle-btn.inactive {
|
||||||
|
background: #f44336;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.toggle-btn:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
.status-indicator {
|
||||||
|
display: inline-block;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
.status-indicator.active {
|
||||||
|
background: #4CAF50;
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
.status-indicator.inactive {
|
||||||
|
background: #999;
|
||||||
|
}
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.5; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="map"></div>
|
||||||
|
<div class="info">
|
||||||
|
<h3>📍 Location Tracker</h3>
|
||||||
|
<div id="status">Lade...</div>
|
||||||
|
<button id="toggleBtn" class="toggle-btn active" onclick="toggleAutoRefresh()">
|
||||||
|
<span class="status-indicator active"></span>
|
||||||
|
Auto-Refresh: AN
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||||
|
<script>
|
||||||
|
// Karte initialisieren (München)
|
||||||
|
const map = L.map('map').setView([48.1351, 11.5820], 12);
|
||||||
|
|
||||||
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
|
attribution: '© OpenStreetMap'
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
// API URL - anpassen an deine Domain
|
||||||
|
const API_URL = 'https://n8n.unixweb.eu/webhook/location';
|
||||||
|
|
||||||
|
// Auto-Refresh State
|
||||||
|
let autoRefreshEnabled = true;
|
||||||
|
let refreshInterval = null;
|
||||||
|
|
||||||
|
async function loadLocations() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(API_URL);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
document.getElementById('status').innerHTML =
|
||||||
|
`Punkte: ${data.total_points || 0}<br>` +
|
||||||
|
`Status: ${data.success ? '✅ Verbunden' : '❌ Fehler'}`;
|
||||||
|
|
||||||
|
if (data.current) {
|
||||||
|
const loc = data.current;
|
||||||
|
L.marker([loc.latitude, loc.longitude])
|
||||||
|
.addTo(map)
|
||||||
|
.bindPopup(`${loc.marker_label}<br>${loc.display_time}`)
|
||||||
|
.openPopup();
|
||||||
|
|
||||||
|
map.setView([loc.latitude, loc.longitude], 15);
|
||||||
|
|
||||||
|
// Historie als Linie
|
||||||
|
if (data.history && data.history.length > 1) {
|
||||||
|
const coords = data.history.map(h => [h.latitude, h.longitude]);
|
||||||
|
L.polyline(coords, {color: 'blue', weight: 3}).addTo(map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
document.getElementById('status').innerHTML = '❌ Verbindungsfehler';
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleAutoRefresh() {
|
||||||
|
autoRefreshEnabled = !autoRefreshEnabled;
|
||||||
|
const btn = document.getElementById('toggleBtn');
|
||||||
|
const indicator = btn.querySelector('.status-indicator');
|
||||||
|
|
||||||
|
if (autoRefreshEnabled) {
|
||||||
|
// Aktiviere Auto-Refresh
|
||||||
|
btn.className = 'toggle-btn active';
|
||||||
|
btn.innerHTML = '<span class="status-indicator active"></span>Auto-Refresh: AN';
|
||||||
|
startAutoRefresh();
|
||||||
|
} else {
|
||||||
|
// Deaktiviere Auto-Refresh
|
||||||
|
btn.className = 'toggle-btn inactive';
|
||||||
|
btn.innerHTML = '<span class="status-indicator inactive"></span>Auto-Refresh: AUS';
|
||||||
|
stopAutoRefresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startAutoRefresh() {
|
||||||
|
if (refreshInterval) clearInterval(refreshInterval);
|
||||||
|
refreshInterval = setInterval(loadLocations, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopAutoRefresh() {
|
||||||
|
if (refreshInterval) {
|
||||||
|
clearInterval(refreshInterval);
|
||||||
|
refreshInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial laden
|
||||||
|
loadLocations();
|
||||||
|
|
||||||
|
// Auto-refresh starten
|
||||||
|
startAutoRefresh();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
324
tracker.json
Normal file
324
tracker.json
Normal file
@@ -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": []
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user