Add Telegram notification integration for geofencing

Features:
- Multi-channel notifications (Email + Telegram)
- User-configurable notification settings per channel
- Telegram bot integration with rich messages, location pins, and inline buttons
- QR code generation for easy bot access (@myidbot support)
- Admin UI for notification settings management
- Test functionality for Telegram connection
- Comprehensive documentation

Implementation:
- lib/telegram-service.ts: Telegram API integration
- lib/notification-settings-db.ts: Database layer for user notification preferences
- lib/geofence-notifications.ts: Extended for parallel multi-channel delivery
- API routes for settings management and testing
- Admin UI with QR code display and step-by-step instructions
- Database table: UserNotificationSettings

Documentation:
- docs/telegram.md: Technical implementation guide
- docs/telegram-anleitung.md: User guide with @myidbot instructions
- docs/telegram-setup.md: Admin setup guide
- README.md: Updated NPM scripts section

Docker:
- Updated Dockerfile to copy public directory
- Added TELEGRAM_BOT_TOKEN environment variable
- Integrated notification settings initialization in db:init

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-04 14:54:19 +00:00
parent 17aaf130a8
commit 0d1dbeafda
18 changed files with 3200 additions and 21 deletions

170
lib/telegram-service.ts Normal file
View File

@@ -0,0 +1,170 @@
import axios from 'axios';
interface TelegramMessage {
chatId: string;
text: string;
latitude?: number;
longitude?: number;
buttons?: Array<{ text: string; url: string }>;
}
interface GeofenceNotificationParams {
chatId: string;
deviceName: string;
geofenceName: string;
eventType: 'enter' | 'exit';
latitude: number;
longitude: number;
timestamp: string;
mapUrl?: string;
dashboardUrl?: string;
}
class TelegramService {
private botToken: string;
private baseUrl: string;
constructor() {
this.botToken = process.env.TELEGRAM_BOT_TOKEN || '';
if (!this.botToken) {
console.warn('[TelegramService] No TELEGRAM_BOT_TOKEN configured');
}
this.baseUrl = `https://api.telegram.org/bot${this.botToken}`;
}
/**
* Check if Telegram is configured
*/
isConfigured(): boolean {
return !!this.botToken;
}
/**
* Send text message with optional inline buttons
*/
async sendMessage(params: TelegramMessage): Promise<void> {
if (!this.isConfigured()) {
throw new Error('Telegram bot token not configured');
}
const { chatId, text, buttons } = params;
const payload: any = {
chat_id: chatId,
text: text,
parse_mode: 'HTML',
};
// Add inline buttons if provided
if (buttons && buttons.length > 0) {
payload.reply_markup = {
inline_keyboard: [
buttons.map(btn => ({
text: btn.text,
url: btn.url,
})),
],
};
}
const response = await axios.post(`${this.baseUrl}/sendMessage`, payload);
if (!response.data.ok) {
throw new Error(`Telegram API error: ${response.data.description}`);
}
}
/**
* Send location pin on map
*/
async sendLocation(
chatId: string,
latitude: number,
longitude: number
): Promise<void> {
if (!this.isConfigured()) {
throw new Error('Telegram bot token not configured');
}
const response = await axios.post(`${this.baseUrl}/sendLocation`, {
chat_id: chatId,
latitude,
longitude,
});
if (!response.data.ok) {
throw new Error(`Telegram API error: ${response.data.description}`);
}
}
/**
* Send geofence notification (complete with text + location + buttons)
*/
async sendGeofenceNotification(
params: GeofenceNotificationParams
): Promise<void> {
const {
chatId,
deviceName,
geofenceName,
eventType,
latitude,
longitude,
timestamp,
mapUrl,
dashboardUrl,
} = params;
// Format message
const emoji = eventType === 'enter' ? '🟢' : '🔴';
const action = eventType === 'enter' ? 'BETRETEN' : 'VERLASSEN';
const verb = eventType === 'enter' ? 'betreten' : 'verlassen';
const formattedDate = new Date(timestamp).toLocaleString('de-DE', {
dateStyle: 'short',
timeStyle: 'short',
});
const text = `
${emoji} <b>Geofence ${action}</b>
📱 <b>Device:</b> ${deviceName}
📍 <b>Geofence:</b> ${geofenceName}
🕐 <b>Zeit:</b> ${formattedDate}
📊 <b>Ereignis:</b> Hat ${geofenceName} ${verb}
`.trim();
// Prepare inline buttons
const buttons = [];
if (mapUrl) {
buttons.push({ text: '🗺️ Auf Karte zeigen', url: mapUrl });
}
if (dashboardUrl) {
buttons.push({ text: '📊 Dashboard öffnen', url: dashboardUrl });
}
// Send text message with buttons
await this.sendMessage({ chatId, text, buttons });
// Send location pin
await this.sendLocation(chatId, latitude, longitude);
}
/**
* Test connection by sending a simple message
*/
async testConnection(chatId: string): Promise<boolean> {
try {
await this.sendMessage({
chatId,
text: '✅ <b>Telegram Connection Test</b>\n\nDie Verbindung funktioniert!',
});
return true;
} catch (error) {
console.error('[TelegramService] Test failed:', error);
return false;
}
}
}
export const telegramService = new TelegramService();