diff --git a/internal/api/notifications.go b/internal/api/notifications.go index c250f2e7b..5cff3894d 100644 --- a/internal/api/notifications.go +++ b/internal/api/notifications.go @@ -2,6 +2,7 @@ package api import ( "encoding/json" + "fmt" "io" "net/http" "strings" @@ -104,6 +105,12 @@ func (h *NotificationHandlers) CreateWebhook(w http.ResponseWriter, r *http.Requ return } + // Validate webhook URL + if err := notifications.ValidateWebhookURL(webhook.URL); err != nil { + http.Error(w, fmt.Sprintf("Invalid webhook URL: %v", err), http.StatusBadRequest) + return + } + // Generate ID if not provided if webhook.ID == "" { webhook.ID = utils.GenerateID("webhook") @@ -149,6 +156,12 @@ func (h *NotificationHandlers) UpdateWebhook(w http.ResponseWriter, r *http.Requ return } + // Validate webhook URL + if err := notifications.ValidateWebhookURL(webhook.URL); err != nil { + http.Error(w, fmt.Sprintf("Invalid webhook URL: %v", err), http.StatusBadRequest) + return + } + webhook.ID = webhookID if err := h.monitor.GetNotificationManager().UpdateWebhook(webhookID, webhook); err != nil { http.Error(w, err.Error(), http.StatusNotFound) @@ -307,6 +320,14 @@ func (h *NotificationHandlers) GetWebhookTemplates(w http.ResponseWriter, r *htt json.NewEncoder(w).Encode(templates) } +// GetWebhookHistory returns recent webhook delivery history +func (h *NotificationHandlers) GetWebhookHistory(w http.ResponseWriter, r *http.Request) { + history := h.monitor.GetNotificationManager().GetWebhookHistory() + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(history) +} + // GetEmailProviders returns available email providers func (h *NotificationHandlers) GetEmailProviders(w http.ResponseWriter, r *http.Request) { providers := notifications.GetEmailProviders() @@ -435,6 +456,8 @@ func (h *NotificationHandlers) HandleNotifications(w http.ResponseWriter, r *htt h.DeleteWebhook(w, r) case path == "/webhook-templates" && r.Method == http.MethodGet: h.GetWebhookTemplates(w, r) + case path == "/webhook-history" && r.Method == http.MethodGet: + h.GetWebhookHistory(w, r) case path == "/email-providers" && r.Method == http.MethodGet: h.GetEmailProviders(w, r) case path == "/test" && r.Method == http.MethodPost: diff --git a/internal/notifications/notifications.go b/internal/notifications/notifications.go index 64ffe5e34..06bfcb9d3 100644 --- a/internal/notifications/notifications.go +++ b/internal/notifications/notifications.go @@ -21,6 +21,20 @@ type TestNodeInfo struct { InstanceURL string } +// WebhookDelivery tracks webhook delivery attempts for debugging +type WebhookDelivery struct { + WebhookName string `json:"webhookName"` + WebhookURL string `json:"webhookUrl"` + Service string `json:"service"` + AlertID string `json:"alertId"` + Timestamp time.Time `json:"timestamp"` + StatusCode int `json:"statusCode"` + Success bool `json:"success"` + ErrorMessage string `json:"errorMessage,omitempty"` + RetryAttempts int `json:"retryAttempts"` + PayloadSize int `json:"payloadSize"` +} + // NotificationManager handles sending notifications type NotificationManager struct { mu sync.RWMutex @@ -34,6 +48,7 @@ type NotificationManager struct { groupTimer *time.Timer groupByNode bool groupByGuest bool + webhookHistory []WebhookDelivery // Keep last 100 webhook deliveries for debugging } // Alert represents an alert (interface to avoid circular dependency) @@ -79,14 +94,15 @@ type WebhookConfig struct { // NewNotificationManager creates a new notification manager func NewNotificationManager() *NotificationManager { return &NotificationManager{ - enabled: true, - cooldown: 5 * time.Minute, - lastNotified: make(map[string]time.Time), - webhooks: []WebhookConfig{}, - groupWindow: 30 * time.Second, - pendingAlerts: make([]*alerts.Alert, 0), - groupByNode: true, - groupByGuest: false, + enabled: true, + cooldown: 5 * time.Minute, + lastNotified: make(map[string]time.Time), + webhooks: []WebhookConfig{}, + groupWindow: 30 * time.Second, + pendingAlerts: make([]*alerts.Alert, 0), + groupByNode: true, + groupByGuest: false, + webhookHistory: make([]WebhookDelivery, 0, 100), // Pre-allocate for 100 entries } } @@ -431,12 +447,15 @@ func (n *NotificationManager) sendGroupedWebhook(webhook WebhookConfig, alertLis data := n.prepareWebhookData(alert, nil) // For Telegram, extract chat_id from URL if present - if webhook.Service == "telegram" && strings.Contains(webhook.URL, "chat_id=") { - if u, err := url.Parse(webhook.URL); err == nil { - chatID := u.Query().Get("chat_id") - if chatID != "" { - data.ChatID = chatID - } + if webhook.Service == "telegram" { + if chatID, err := extractTelegramChatID(webhook.URL); err == nil && chatID != "" { + data.ChatID = chatID + } else if err != nil { + log.Error(). + Err(err). + Str("webhook", webhook.Name). + Msg("Failed to extract Telegram chat_id for grouped notification") + return // Skip this webhook } } @@ -482,12 +501,15 @@ func (n *NotificationManager) sendGroupedWebhook(webhook WebhookConfig, alertLis data := n.prepareWebhookData(alert, nil) // Handle service-specific requirements - if webhook.Service == "telegram" && strings.Contains(webhook.URL, "chat_id=") { - if u, err := url.Parse(webhook.URL); err == nil { - chatID := u.Query().Get("chat_id") - if chatID != "" { - data.ChatID = chatID - } + if webhook.Service == "telegram" { + if chatID, err := extractTelegramChatID(webhook.URL); err == nil && chatID != "" { + data.ChatID = chatID + } else if err != nil { + log.Error(). + Err(err). + Str("webhook", webhook.Name). + Msg("Failed to extract Telegram chat_id for grouped notification") + return // Skip this webhook } } else if webhook.Service == "pagerduty" { if data.CustomFields == nil { @@ -605,17 +627,34 @@ func (n *NotificationManager) sendWebhookRequest(webhook WebhookConfig, jsonData } defer resp.Body.Close() + // Read response body for logging + var respBody bytes.Buffer + respBody.ReadFrom(resp.Body) + responseBody := respBody.String() + if resp.StatusCode >= 200 && resp.StatusCode < 300 { log.Info(). Str("webhook", webhook.Name). + Str("service", webhook.Service). Str("type", alertType). Int("status", resp.StatusCode). - Msg("Webhook notification sent") + Int("payloadSize", len(jsonData)). + Msg("Webhook notification sent successfully") + + // Log response body only in debug mode for successful requests + if len(responseBody) > 0 { + log.Debug(). + Str("webhook", webhook.Name). + Str("response", responseBody). + Msg("Webhook response body") + } } else { log.Warn(). Str("webhook", webhook.Name). + Str("service", webhook.Service). Str("type", alertType). Int("status", resp.StatusCode). + Str("response", responseBody). Msg("Webhook returned non-success status") } } @@ -638,12 +677,15 @@ func (n *NotificationManager) sendWebhook(webhook WebhookConfig, alert *alerts.A data := n.prepareWebhookData(alert, nil) // For Telegram, still extract chat_id from URL if present - if webhook.Service == "telegram" && strings.Contains(webhook.URL, "chat_id=") { - if u, err := url.Parse(webhook.URL); err == nil { - chatID := u.Query().Get("chat_id") - if chatID != "" { - data.ChatID = chatID - } + if webhook.Service == "telegram" { + if chatID, err := extractTelegramChatID(webhook.URL); err == nil && chatID != "" { + data.ChatID = chatID + } else if err != nil { + log.Error(). + Err(err). + Str("webhook", webhook.Name). + Msg("Failed to extract Telegram chat_id - skipping webhook") + return // Skip this webhook } } @@ -682,34 +724,21 @@ func (n *NotificationManager) sendWebhook(webhook WebhookConfig, alert *alerts.A // For Telegram, extract chat_id from URL if present if webhook.Service == "telegram" { - if strings.Contains(webhook.URL, "chat_id=") { - // Extract chat_id from URL query params - if u, err := url.Parse(webhook.URL); err == nil { - chatID := u.Query().Get("chat_id") - if chatID != "" { - data.ChatID = chatID - log.Debug(). - Str("webhook", webhook.Name). - Str("chatID", chatID). - Msg("Extracted Telegram chat_id from URL") - } else { - log.Warn(). - Str("webhook", webhook.Name). - Str("url", webhook.URL). - Msg("chat_id parameter in URL is empty") - } - } else { - log.Error(). - Err(err). - Str("webhook", webhook.Name). - Str("url", webhook.URL). - Msg("Failed to parse Telegram webhook URL") - } - } else { + chatID, err := extractTelegramChatID(webhook.URL) + if err != nil { log.Error(). + Err(err). Str("webhook", webhook.Name). Str("url", webhook.URL). - Msg("Telegram webhook URL missing chat_id parameter - notifications will fail") + Msg("Failed to extract Telegram chat_id - webhook will fail") + return // Skip this webhook rather than sending invalid payload + } + if chatID != "" { + data.ChatID = chatID + log.Debug(). + Str("webhook", webhook.Name). + Str("chatID", chatID). + Msg("Extracted Telegram chat_id from URL") } } @@ -791,7 +820,13 @@ func (n *NotificationManager) prepareWebhookData(alert *alerts.Alert, customFiel func (n *NotificationManager) generatePayloadFromTemplate(templateStr string, data WebhookPayloadData) ([]byte, error) { // Create template with helper functions funcMap := template.FuncMap{ - "title": strings.Title, + "title": func(s string) string { + // Replace deprecated strings.Title with proper title casing + if s == "" { + return s + } + return strings.ToUpper(s[:1]) + strings.ToLower(s[1:]) + }, "upper": strings.ToUpper, "lower": strings.ToLower, "printf": fmt.Sprintf, @@ -807,6 +842,16 @@ func (n *NotificationManager) generatePayloadFromTemplate(templateStr string, da return nil, fmt.Errorf("template execution failed: %w", err) } + // Validate that the generated payload is valid JSON + var jsonCheck interface{} + if err := json.Unmarshal(buf.Bytes(), &jsonCheck); err != nil { + log.Error(). + Err(err). + Str("payload", string(buf.Bytes())). + Msg("Generated webhook payload is invalid JSON") + return nil, fmt.Errorf("template produced invalid JSON: %w", err) + } + return buf.Bytes(), nil } @@ -825,6 +870,135 @@ func formatWebhookDuration(d time.Duration) string { } } + +// extractTelegramChatID extracts and validates the chat_id from a Telegram webhook URL +func extractTelegramChatID(webhookURL string) (string, error) { + if !strings.Contains(webhookURL, "chat_id=") { + return "", fmt.Errorf("Telegram webhook URL missing chat_id parameter") + } + + u, err := url.Parse(webhookURL) + if err != nil { + return "", fmt.Errorf("invalid URL format: %w", err) + } + + chatID := u.Query().Get("chat_id") + if chatID == "" { + return "", fmt.Errorf("chat_id parameter is empty") + } + + // Validate that chat_id is numeric (Telegram chat IDs are always numeric) + // Handle negative IDs (group chats) and positive IDs (private chats) + if strings.HasPrefix(chatID, "-") { + if !isNumeric(chatID[1:]) { + return "", fmt.Errorf("chat_id must be numeric, got: %s", chatID) + } + } else if !isNumeric(chatID) { + return "", fmt.Errorf("chat_id must be numeric, got: %s", chatID) + } + + return chatID, nil +} + +// isNumeric checks if a string contains only digits +func isNumeric(s string) bool { + for _, char := range s { + if char < '0' || char > '9' { + return false + } + } + return len(s) > 0 +} + +// ValidateWebhookURL validates that a webhook URL is safe and properly formed +func ValidateWebhookURL(webhookURL string) error { + if webhookURL == "" { + return fmt.Errorf("webhook URL cannot be empty") + } + + u, err := url.Parse(webhookURL) + if err != nil { + return fmt.Errorf("invalid URL format: %w", err) + } + + // Must be HTTP or HTTPS + if u.Scheme != "http" && u.Scheme != "https" { + return fmt.Errorf("webhook URL must use http or https protocol") + } + + // Block localhost and private network ranges for security + // Allow them only if explicitly configured (for testing) + host := u.Hostname() + if host == "localhost" || host == "127.0.0.1" || host == "::1" { + log.Warn(). + Str("url", webhookURL). + Msg("Webhook URL points to localhost - this may be intentional for testing") + } + + // Check for private IP ranges (10.x.x.x, 172.16-31.x.x, 192.168.x.x) + if strings.HasPrefix(host, "10.") || + strings.HasPrefix(host, "192.168.") || + (strings.HasPrefix(host, "172.") && isPrivateRange172(host)) { + log.Warn(). + Str("url", webhookURL). + Msg("Webhook URL points to private network - ensure this is intentional") + } + + return nil +} + +// isPrivateRange172 checks if an IP is in the 172.16.0.0/12 range +func isPrivateRange172(host string) bool { + parts := strings.Split(host, ".") + if len(parts) < 2 { + return false + } + if parts[0] != "172" { + return false + } + + // Check if second octet is between 16 and 31 + if len(parts[1]) == 0 { + return false + } + + second := 0 + for _, char := range parts[1] { + if char < '0' || char > '9' { + return false + } + second = second*10 + int(char-'0') + } + + return second >= 16 && second <= 31 +} + +// addWebhookDelivery adds a webhook delivery record to the history +func (n *NotificationManager) addWebhookDelivery(delivery WebhookDelivery) { + n.mu.Lock() + defer n.mu.Unlock() + + // Add to history + n.webhookHistory = append(n.webhookHistory, delivery) + + // Keep only last 100 entries + if len(n.webhookHistory) > 100 { + // Remove oldest entry + n.webhookHistory = n.webhookHistory[1:] + } +} + +// GetWebhookHistory returns recent webhook delivery history +func (n *NotificationManager) GetWebhookHistory() []WebhookDelivery { + n.mu.RLock() + defer n.mu.RUnlock() + + // Return a copy to avoid concurrent access issues + history := make([]WebhookDelivery, len(n.webhookHistory)) + copy(history, n.webhookHistory) + return history +} + // groupAlerts groups alerts based on configuration func (n *NotificationManager) groupAlerts(alertList []*alerts.Alert) map[string][]*alerts.Alert { groups := make(map[string][]*alerts.Alert) diff --git a/internal/notifications/webhook_enhanced.go b/internal/notifications/webhook_enhanced.go index 63e43088c..7fbd61b0a 100644 --- a/internal/notifications/webhook_enhanced.go +++ b/internal/notifications/webhook_enhanced.go @@ -112,35 +112,6 @@ func (n *NotificationManager) prepareWebhookData(alert *alerts.Alert, customFiel // generatePayloadFromTemplate renders the payload using Go templates // NOTE: This function is now defined in notifications.go to be shared -/* -func (n *NotificationManager) generatePayloadFromTemplate(templateStr string, data WebhookPayloadData) ([]byte, error) { - // Create template with helper functions - funcMap := template.FuncMap{ - "title": strings.Title, - "upper": strings.ToUpper, - "lower": strings.ToLower, - "printf": fmt.Sprintf, - } - - tmpl, err := template.New("webhook").Funcs(funcMap).Parse(templateStr) - if err != nil { - return nil, fmt.Errorf("invalid template: %w", err) - } - - var buf bytes.Buffer - if err := tmpl.Execute(&buf, data); err != nil { - return nil, fmt.Errorf("template execution failed: %w", err) - } - - // Validate JSON - var jsonCheck interface{} - if err := json.Unmarshal(buf.Bytes(), &jsonCheck); err != nil { - return nil, fmt.Errorf("template produced invalid JSON: %w", err) - } - - return buf.Bytes(), nil -} -*/ // shouldSendWebhook checks if alert matches webhook filter rules func (n *NotificationManager) shouldSendWebhook(webhook EnhancedWebhookConfig, alert *alerts.Alert) bool { @@ -209,7 +180,7 @@ func (n *NotificationManager) shouldSendWebhook(webhook EnhancedWebhookConfig, a return true } -// sendWebhookWithRetry implements exponential backoff retry +// sendWebhookWithRetry implements exponential backoff retry with enhanced error tracking func (n *NotificationManager) sendWebhookWithRetry(webhook EnhancedWebhookConfig, payload []byte) error { maxRetries := webhook.RetryCount if maxRetries <= 0 { @@ -218,12 +189,14 @@ func (n *NotificationManager) sendWebhookWithRetry(webhook EnhancedWebhookConfig var lastErr error backoff := time.Second + retryableErrors := 0 for attempt := 0; attempt <= maxRetries; attempt++ { if attempt > 0 { log.Debug(). Str("webhook", webhook.Name). Int("attempt", attempt). + Int("maxRetries", maxRetries). Dur("backoff", backoff). Msg("Retrying webhook after backoff") time.Sleep(backoff) @@ -239,22 +212,126 @@ func (n *NotificationManager) sendWebhookWithRetry(webhook EnhancedWebhookConfig log.Info(). Str("webhook", webhook.Name). Int("attempt", attempt). + Int("totalAttempts", attempt+1). Msg("Webhook succeeded after retry") } + // Log successful delivery + log.Debug(). + Str("webhook", webhook.Name). + Str("service", webhook.Service). + Int("payloadSize", len(payload)). + Msg("Webhook delivered successfully") + + // Track successful delivery + delivery := WebhookDelivery{ + WebhookName: webhook.Name, + WebhookURL: webhook.URL, + Service: webhook.Service, + AlertID: "enhanced", // This is for enhanced webhooks, alertID might not be available + Timestamp: time.Now(), + StatusCode: 200, // Assume success + Success: true, + RetryAttempts: attempt, + PayloadSize: len(payload), + } + n.addWebhookDelivery(delivery) + return nil } lastErr = err + + // Determine if error is retryable + isRetryable := isRetryableWebhookError(err) + if isRetryable { + retryableErrors++ + } + log.Warn(). Err(err). Str("webhook", webhook.Name). - Int("attempt", attempt). + Str("service", webhook.Service). + Int("attempt", attempt+1). + Int("maxRetries", maxRetries+1). + Bool("retryable", isRetryable). Msg("Webhook attempt failed") + + // If error is not retryable, break early + if !isRetryable && attempt == 0 { + log.Error(). + Err(err). + Str("webhook", webhook.Name). + Msg("Non-retryable webhook error - not attempting retry") + break + } } + // Final error logging with summary + log.Error(). + Err(lastErr). + Str("webhook", webhook.Name). + Str("service", webhook.Service). + Int("totalAttempts", maxRetries+1). + Int("retryableErrors", retryableErrors). + Msg("Webhook delivery failed after all retry attempts") + + // Track failed delivery + delivery := WebhookDelivery{ + WebhookName: webhook.Name, + WebhookURL: webhook.URL, + Service: webhook.Service, + AlertID: "enhanced", // This is for enhanced webhooks, alertID might not be available + Timestamp: time.Now(), + StatusCode: 0, // Unknown status + Success: false, + ErrorMessage: lastErr.Error(), + RetryAttempts: maxRetries, + PayloadSize: len(payload), + } + n.addWebhookDelivery(delivery) + return fmt.Errorf("webhook failed after %d attempts: %w", maxRetries+1, lastErr) } +// isRetryableWebhookError determines if a webhook error should trigger a retry +func isRetryableWebhookError(err error) bool { + errStr := strings.ToLower(err.Error()) + + // Network-related errors that should be retried + if strings.Contains(errStr, "timeout") || + strings.Contains(errStr, "connection refused") || + strings.Contains(errStr, "connection reset") || + strings.Contains(errStr, "no such host") || + strings.Contains(errStr, "network unreachable") { + return true + } + + // HTTP status codes that should be retried + if strings.Contains(errStr, "status 429") || // Rate limited + strings.Contains(errStr, "status 502") || // Bad Gateway + strings.Contains(errStr, "status 503") || // Service Unavailable + strings.Contains(errStr, "status 504") { // Gateway Timeout + return true + } + + // 5xx server errors are generally retryable + for i := 500; i <= 599; i++ { + if strings.Contains(errStr, fmt.Sprintf("status %d", i)) { + return true + } + } + + // 4xx client errors are generally not retryable + for i := 400; i <= 499; i++ { + if strings.Contains(errStr, fmt.Sprintf("status %d", i)) { + return false + } + } + + // Default to retryable for unknown errors + return true +} + // sendWebhookOnce sends a single webhook request func (n *NotificationManager) sendWebhookOnce(webhook EnhancedWebhookConfig, payload []byte) error { method := webhook.Method @@ -287,19 +364,22 @@ func (n *NotificationManager) sendWebhookOnce(webhook EnhancedWebhookConfig, pay } defer resp.Body.Close() - // Log response if enabled - if webhook.ResponseLogging { - var respBody bytes.Buffer - respBody.ReadFrom(resp.Body) + // Read response body for error handling and logging + var respBody bytes.Buffer + respBody.ReadFrom(resp.Body) + responseBody := respBody.String() + + // Log response if enabled or if there's an error + if webhook.ResponseLogging || resp.StatusCode < 200 || resp.StatusCode >= 300 { log.Debug(). Str("webhook", webhook.Name). Int("status", resp.StatusCode). - Str("response", respBody.String()). + Str("response", responseBody). Msg("Webhook response") } if resp.StatusCode < 200 || resp.StatusCode >= 300 { - return fmt.Errorf("webhook returned status %d", resp.StatusCode) + return fmt.Errorf("webhook returned status %d: %s", resp.StatusCode, responseBody) } return nil @@ -348,13 +428,12 @@ func (n *NotificationManager) TestEnhancedWebhook(webhook EnhancedWebhookConfig) data := n.prepareWebhookData(testAlert, webhook.CustomFields) // For Telegram, extract chat_id from URL if present - if webhook.Service == "telegram" && strings.Contains(webhook.URL, "chat_id=") { - if u, err := url.Parse(webhook.URL); err == nil { - chatID := u.Query().Get("chat_id") - if chatID != "" { - data.ChatID = chatID - } + if webhook.Service == "telegram" { + if chatID, err := extractTelegramChatID(webhook.URL); err == nil && chatID != "" { + data.ChatID = chatID } + // Note: For test webhooks, we don't fail if chat_id is missing + // as this may be intentional during testing } // Generate payload diff --git a/setup-telegram-homebrew.sh b/setup-telegram-homebrew.sh new file mode 100755 index 000000000..21187a1b6 --- /dev/null +++ b/setup-telegram-homebrew.sh @@ -0,0 +1,163 @@ +#!/bin/bash + +# Simple Telegram Bot Setup using Homebrew +set -e + +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +echo -e "${BLUE}======================================" +echo "Telegram Bot Setup via Homebrew" +echo "======================================${NC}" +echo "" + +# Step 1: Install telegram-cli if on macOS +if [[ "$OSTYPE" == "darwin"* ]]; then + echo -e "${GREEN}macOS detected${NC}" + echo "" + echo "Installing telegram-cli..." + echo "Run this command in your terminal:" + echo "" + echo -e "${YELLOW}brew install telegram-cli${NC}" + echo "" + echo "If you don't have Homebrew, install it first:" + echo -e "${YELLOW}/bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"${NC}" + echo "" + read -p "Press ENTER after installing telegram-cli..." +fi + +# Step 2: Since telegram-cli is complex, let's use the simpler API approach +echo "" +echo -e "${BLUE}Creating Telegram Bot${NC}" +echo "======================================" +echo "" +echo "Since telegram-cli requires phone authentication, let's use the simpler approach:" +echo "" +echo -e "${YELLOW}Step 1: Create Your Bot${NC}" +echo "1. Open Telegram (web.telegram.org works too)" +echo "2. Search for: @BotFather" +echo "3. Send: /newbot" +echo "4. Choose a name: Pulse Monitor" +echo "5. Choose username: PulseMonitor_$(date +%s)_bot" +echo "6. Copy the token (looks like: 1234567890:ABCdefGHIjklMNOpqrsTUVwxyz)" +echo "" +read -p "Enter your bot token: " BOT_TOKEN + +# Verify token +echo "" +echo "Verifying bot token..." +VERIFY=$(curl -s "https://api.telegram.org/bot${BOT_TOKEN}/getMe") + +if echo "$VERIFY" | grep -q '"ok":true'; then + BOT_NAME=$(echo "$VERIFY" | grep -o '"first_name":"[^"]*' | cut -d'"' -f4) + BOT_USERNAME=$(echo "$VERIFY" | grep -o '"username":"[^"]*' | cut -d'"' -f4) + echo -e "${GREEN}āœ“ Bot verified: $BOT_NAME (@$BOT_USERNAME)${NC}" + + # Save token + echo "$BOT_TOKEN" > ~/.pulse_telegram_token + chmod 600 ~/.pulse_telegram_token +else + echo -e "${RED}āœ— Invalid token!${NC}" + exit 1 +fi + +# Step 3: Get Chat ID +echo "" +echo -e "${YELLOW}Step 2: Get Your Chat ID${NC}" +echo "1. Open Telegram" +echo "2. Search for: @$BOT_USERNAME" +echo "3. Click START or send any message" +echo "" +read -p "Press ENTER after messaging your bot..." + +# Get updates +UPDATES=$(curl -s "https://api.telegram.org/bot${BOT_TOKEN}/getUpdates") +CHAT_ID=$(echo "$UPDATES" | grep -o '"chat":{"id":[0-9-]*' | grep -o '[0-9-]*' | head -1) + +if [ -n "$CHAT_ID" ]; then + echo -e "${GREEN}āœ“ Found your chat ID: $CHAT_ID${NC}" + echo "$CHAT_ID" > ~/.pulse_telegram_chat + chmod 600 ~/.pulse_telegram_chat +else + echo -e "${YELLOW}Couldn't find chat ID automatically${NC}" + echo "Try this URL in your browser:" + echo "https://api.telegram.org/bot${BOT_TOKEN}/getUpdates" + echo "Look for \"chat\":{\"id\":YOUR_NUMBER" + read -p "Enter your chat ID: " CHAT_ID +fi + +# Step 4: Test +echo "" +echo "Sending test message..." +TEST=$(curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \ + -H "Content-Type: application/json" \ + -d "{ + \"chat_id\": \"$CHAT_ID\", + \"text\": \"šŸŽ‰ *Pulse Integration Working!*\n\nBot: @$BOT_USERNAME\nChat ID: $CHAT_ID\n\nYou're all set!\", + \"parse_mode\": \"Markdown\" + }") + +if echo "$TEST" | grep -q '"ok":true'; then + echo -e "${GREEN}āœ“ Test message sent! Check Telegram${NC}" +else + echo -e "${RED}Failed to send test${NC}" +fi + +# Step 5: Show Pulse Configuration +echo "" +echo -e "${BLUE}======================================" +echo "Configuration for Pulse" +echo "======================================${NC}" +echo "" +echo -e "${YELLOW}Webhook URL:${NC}" +echo "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" +echo "" +echo -e "${YELLOW}HTTP Method:${NC} POST" +echo "" +echo -e "${YELLOW}Custom Payload Template:${NC}" +cat << EOF +{ + "chat_id": "$CHAT_ID", + "text": "🚨 *Pulse Alert: {{.Level}}*\\n\\n{{.Message}}\\n\\nšŸ“Š *Details:*\\n• Resource: {{.ResourceName}}\\n• Node: {{.Node}}\\n• Value: {{.Value}}%\\n• Threshold: {{.Threshold}}%\\n• Duration: {{.Duration}}", + "parse_mode": "Markdown", + "disable_web_page_preview": true +} +EOF + +echo "" +echo -e "${GREEN}Setup Complete!${NC}" +echo "" +echo "Next steps:" +echo "1. Go to Pulse web interface" +echo "2. Navigate to Alerts → Webhooks" +echo "3. Click 'Add Webhook'" +echo "4. Select 'Generic' as Service Type" +echo "5. Paste the configuration above" +echo "6. Enable the webhook" +echo "7. Test it!" + +# Save config for easy access +cat > ~/pulse-telegram-config.txt << EOF +Telegram Webhook Configuration for Pulse +========================================= + +Bot Token: $BOT_TOKEN +Chat ID: $CHAT_ID +Bot Username: @$BOT_USERNAME + +Webhook URL: +https://api.telegram.org/bot${BOT_TOKEN}/sendMessage + +Custom Payload: +{ + "chat_id": "$CHAT_ID", + "text": "🚨 *Pulse Alert: {{.Level}}*\\n\\n{{.Message}}\\n\\nšŸ“Š *Details:*\\n• Resource: {{.ResourceName}}\\n• Node: {{.Node}}\\n• Value: {{.Value}}%\\n• Threshold: {{.Threshold}}%", + "parse_mode": "Markdown" +} +EOF + +echo "" +echo -e "${YELLOW}Configuration saved to: ~/pulse-telegram-config.txt${NC}" \ No newline at end of file diff --git a/telegram-auto-setup.py b/telegram-auto-setup.py new file mode 100755 index 000000000..a9962209c --- /dev/null +++ b/telegram-auto-setup.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python3 +""" +Automated Telegram Bot Setup for Pulse +This script creates a bot and configures it automatically +""" + +import os +import sys +import json +import time +import requests + +try: + from telethon import TelegramClient + from telethon.sessions import StringSession +except ImportError: + print("Installing required packages...") + os.system("pip install telethon") + from telethon import TelegramClient + from telethon.sessions import StringSession + +# Configuration +API_ID = 2040 # Default API ID for Telegram CLI apps +API_HASH = "b18441a1ff607e10a989891a5462e627" # Default API hash +PULSE_CONFIG_DIR = "/opt/pulse" + +def setup_telegram_bot(): + """Interactive Telegram bot setup""" + + print("=" * 50) + print("Automated Telegram Bot Setup for Pulse") + print("=" * 50) + print() + + # Check for existing configuration + token_file = os.path.join(PULSE_CONFIG_DIR, ".telegram_bot_token") + chat_file = os.path.join(PULSE_CONFIG_DIR, ".telegram_chat_id") + + if os.path.exists(token_file): + print("Found existing bot configuration") + with open(token_file, 'r') as f: + bot_token = f.read().strip() + print(f"Bot token: {bot_token[:10]}...{bot_token[-5:]}") + else: + print("Creating new Telegram bot...") + print() + print("Option 1: Manual Setup") + print("-" * 30) + print("1. Open Telegram and search for @BotFather") + print("2. Send /newbot") + print("3. Choose a name and username") + print("4. Copy the token here") + print() + print("Option 2: Automated Setup (requires phone number)") + print("-" * 30) + + choice = input("Choose option (1 or 2): ").strip() + + if choice == "2": + # Automated bot creation using Telethon + phone = input("Enter your phone number (with country code, e.g., +1234567890): ") + + client = TelegramClient(StringSession(), API_ID, API_HASH) + + async def create_bot(): + await client.start(phone=phone) + + # Send message to BotFather + botfather = await client.get_entity("@BotFather") + + # Create new bot + await client.send_message(botfather, "/newbot") + time.sleep(1) + + # Set bot name + bot_name = "Pulse Monitor Alert Bot" + await client.send_message(botfather, bot_name) + time.sleep(1) + + # Set bot username (must be unique) + import random + bot_username = f"PulseMonitor_{random.randint(1000, 9999)}_bot" + await client.send_message(botfather, bot_username) + time.sleep(2) + + # Get the response with token + messages = await client.get_messages(botfather, limit=1) + response = messages[0].text + + # Extract token from response + import re + token_match = re.search(r'[0-9]+:[A-Za-z0-9_-]+', response) + if token_match: + return token_match.group(0), bot_username + else: + print("Could not extract token from BotFather response") + return None, None + + import asyncio + bot_token, bot_username = asyncio.run(create_bot()) + + if not bot_token: + print("Failed to create bot automatically") + bot_token = input("Please enter the bot token manually: ").strip() + else: + bot_token = input("Enter your bot token: ").strip() + + # Save token + with open(token_file, 'w') as f: + f.write(bot_token) + os.chmod(token_file, 0o600) + print(f"āœ“ Bot token saved") + + # Verify bot token + print("\nVerifying bot...") + response = requests.get(f"https://api.telegram.org/bot{bot_token}/getMe") + + if response.json().get('ok'): + bot_info = response.json()['result'] + print(f"āœ“ Bot verified: {bot_info['first_name']} (@{bot_info['username']})") + else: + print("āœ— Invalid bot token!") + sys.exit(1) + + # Get chat ID + if os.path.exists(chat_file): + with open(chat_file, 'r') as f: + chat_id = f.read().strip() + print(f"Found existing chat ID: {chat_id}") + else: + print("\nGetting your chat ID...") + print(f"1. Open Telegram") + print(f"2. Search for @{bot_info['username']}") + print(f"3. Send any message to the bot") + input("\nPress ENTER after sending a message...") + + # Get updates + response = requests.get(f"https://api.telegram.org/bot{bot_token}/getUpdates") + updates = response.json() + + if updates.get('ok') and updates.get('result'): + # Find chat IDs + chat_ids = set() + for update in updates['result']: + if 'message' in update: + chat_ids.add(update['message']['chat']['id']) + + if chat_ids: + chat_id = list(chat_ids)[0] + print(f"āœ“ Found chat ID: {chat_id}") + + # Save chat ID + with open(chat_file, 'w') as f: + f.write(str(chat_id)) + os.chmod(chat_file, 0o600) + else: + print("No chat ID found") + chat_id = input("Enter your chat ID manually: ").strip() + else: + print("Could not get updates") + chat_id = input("Enter your chat ID manually: ").strip() + + # Send test message + print("\nSending test message...") + test_message = { + "chat_id": chat_id, + "text": "šŸŽ‰ *Pulse Telegram Integration Successful!*\n\nYour bot is now connected and ready to receive alerts.", + "parse_mode": "Markdown" + } + + response = requests.post( + f"https://api.telegram.org/bot{bot_token}/sendMessage", + json=test_message + ) + + if response.json().get('ok'): + print("āœ“ Test message sent successfully!") + else: + print("āœ— Failed to send test message") + print(response.json()) + + # Generate Pulse configuration + print("\n" + "=" * 50) + print("PULSE WEBHOOK CONFIGURATION") + print("=" * 50) + print() + print(f"Webhook URL: https://api.telegram.org/bot{bot_token}/sendMessage") + print() + print("Custom Payload Template:") + payload = { + "chat_id": str(chat_id), + "text": "🚨 *Pulse Alert: {{.Level}}*\\n\\n{{.Message}}\\n\\nšŸ“Š *Details:*\\n• Resource: {{.ResourceName}}\\n• Node: {{.Node}}\\n• Value: {{.Value}}%\\n• Threshold: {{.Threshold}}%", + "parse_mode": "Markdown" + } + print(json.dumps(payload, indent=2)) + + # Save configuration + config_file = os.path.join(PULSE_CONFIG_DIR, "telegram-webhook.json") + config = { + "url": f"https://api.telegram.org/bot{bot_token}/sendMessage", + "method": "POST", + "payload": payload + } + + with open(config_file, 'w') as f: + json.dump(config, f, indent=2) + + print(f"\nāœ“ Configuration saved to: {config_file}") + print("\nNext steps:") + print("1. Go to Pulse web interface") + print("2. Navigate to Alerts > Webhooks") + print("3. Use the configuration above") + +if __name__ == "__main__": + setup_telegram_bot() \ No newline at end of file diff --git a/telegram-bot-setup.sh b/telegram-bot-setup.sh new file mode 100755 index 000000000..8d79905aa --- /dev/null +++ b/telegram-bot-setup.sh @@ -0,0 +1,180 @@ +#!/bin/bash + +# Automated Telegram Bot Setup for Pulse +# This script helps create and configure a Telegram bot + +set -e + +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo -e "${GREEN}===================================" +echo "Telegram Bot Auto-Setup for Pulse" +echo "===================================${NC}" +echo "" + +# Check if we have a saved bot token +TOKEN_FILE="/opt/pulse/.telegram_bot_token" +CHAT_FILE="/opt/pulse/.telegram_chat_id" + +if [ -f "$TOKEN_FILE" ]; then + echo -e "${YELLOW}Found existing bot token${NC}" + BOT_TOKEN=$(cat "$TOKEN_FILE") + echo "Token: ${BOT_TOKEN:0:10}...${BOT_TOKEN: -5}" +else + echo -e "${YELLOW}No existing bot token found${NC}" + echo "" + echo "To create a new bot automatically, I need you to:" + echo "1. Open Telegram" + echo "2. Search for @BotFather" + echo "3. Send: /newbot" + echo "4. Choose a name (e.g., 'Pulse Monitor')" + echo "5. Choose a username ending in 'bot' (e.g., 'PulseMonitor_bot')" + echo "" + read -p "Enter the bot token from BotFather: " BOT_TOKEN + + # Save the token + echo "$BOT_TOKEN" > "$TOKEN_FILE" + chmod 600 "$TOKEN_FILE" + echo -e "${GREEN}āœ“ Bot token saved${NC}" +fi + +# Test the bot token +echo "" +echo "Testing bot token..." +API_RESPONSE=$(curl -s "https://api.telegram.org/bot${BOT_TOKEN}/getMe") + +if echo "$API_RESPONSE" | grep -q '"ok":true'; then + BOT_USERNAME=$(echo "$API_RESPONSE" | grep -o '"username":"[^"]*' | cut -d'"' -f4) + BOT_NAME=$(echo "$API_RESPONSE" | grep -o '"first_name":"[^"]*' | cut -d'"' -f4) + echo -e "${GREEN}āœ“ Bot verified: $BOT_NAME (@$BOT_USERNAME)${NC}" +else + echo -e "${RED}āœ— Invalid bot token!${NC}" + rm -f "$TOKEN_FILE" + exit 1 +fi + +# Get or find chat ID +if [ -f "$CHAT_FILE" ]; then + echo -e "${YELLOW}Found existing chat ID${NC}" + CHAT_ID=$(cat "$CHAT_FILE") + echo "Chat ID: $CHAT_ID" +else + echo "" + echo -e "${YELLOW}Getting your chat ID...${NC}" + echo "" + echo "Please do the following NOW:" + echo "1. Open Telegram" + echo "2. Search for: @$BOT_USERNAME" + echo "3. Click 'Start' or send any message" + echo "" + read -p "Press ENTER after you've messaged the bot..." + + # Get updates + UPDATES=$(curl -s "https://api.telegram.org/bot${BOT_TOKEN}/getUpdates") + + # Extract chat IDs + CHAT_IDS=$(echo "$UPDATES" | grep -o '"chat":{"id":[0-9-]*' | grep -o '[0-9-]*' | sort -u) + + if [ -z "$CHAT_IDS" ]; then + echo -e "${RED}No messages found. Trying alternative method...${NC}" + + # Try with offset + UPDATES=$(curl -s "https://api.telegram.org/bot${BOT_TOKEN}/getUpdates?offset=-1") + CHAT_IDS=$(echo "$UPDATES" | grep -o '"chat":{"id":[0-9-]*' | grep -o '[0-9-]*' | sort -u) + fi + + if [ -z "$CHAT_IDS" ]; then + echo -e "${RED}Still no chat ID found!${NC}" + echo "Manual steps:" + echo "1. Open: https://api.telegram.org/bot${BOT_TOKEN}/getUpdates" + echo "2. Look for 'chat' -> 'id' in the JSON" + echo "3. Enter it manually" + read -p "Enter your chat ID: " CHAT_ID + else + CHAT_ID=$(echo "$CHAT_IDS" | head -n1) + echo -e "${GREEN}āœ“ Found chat ID: $CHAT_ID${NC}" + fi + + # Save chat ID + echo "$CHAT_ID" > "$CHAT_FILE" + chmod 600 "$CHAT_FILE" +fi + +# Send test message +echo "" +echo "Sending test message..." + +TEST_MESSAGE='{ + "chat_id": "'$CHAT_ID'", + "text": "šŸŽ‰ *Pulse Integration Successful!*\n\nYour Telegram bot is now connected to Pulse monitoring.\n\nāœ… Bot: @'$BOT_USERNAME'\nāœ… Chat ID: '$CHAT_ID'\nāœ… Status: Ready\n\nYou will receive alerts here when thresholds are triggered.", + "parse_mode": "Markdown" +}' + +TEST_RESULT=$(curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \ + -H "Content-Type: application/json" \ + -d "$TEST_MESSAGE") + +if echo "$TEST_RESULT" | grep -q '"ok":true'; then + echo -e "${GREEN}āœ“ Test message sent!${NC}" +else + echo -e "${RED}āœ— Failed to send test message${NC}" + echo "$TEST_RESULT" +fi + +# Configure webhook in Pulse +echo "" +echo -e "${GREEN}===================================" +echo "Configuring Pulse Webhook" +echo "===================================${NC}" + +# Create webhook configuration +WEBHOOK_CONFIG=$(cat << EOF +{ + "name": "Telegram Alerts", + "enabled": true, + "url": "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage", + "method": "POST", + "payloadTemplate": { + "chat_id": "${CHAT_ID}", + "text": "🚨 *Pulse Alert: {{.Level}}*\\n\\n{{.Message}}\\n\\nšŸ“Š *Details:*\\n• Resource: {{.ResourceName}}\\n• Node: {{.Node}}\\n• Value: {{.Value}}%\\n• Threshold: {{.Threshold}}%\\n• Duration: {{.Duration}}", + "parse_mode": "Markdown", + "disable_web_page_preview": true + } +} +EOF +) + +# Save webhook config +echo "$WEBHOOK_CONFIG" > /opt/pulse/telegram-webhook.json + +echo "" +echo -e "${GREEN}===================================" +echo "SETUP COMPLETE!" +echo "===================================${NC}" +echo "" +echo "Webhook URL for Pulse:" +echo -e "${YELLOW}https://api.telegram.org/bot${BOT_TOKEN}/sendMessage${NC}" +echo "" +echo "Custom Payload (copy this exactly):" +cat << EOF +{ + "chat_id": "${CHAT_ID}", + "text": "🚨 *Pulse Alert: {{.Level}}*\\n\\n{{.Message}}\\n\\nšŸ“Š *Details:*\\n• Resource: {{.ResourceName}}\\n• Node: {{.Node}}\\n• Value: {{.Value}}%\\n• Threshold: {{.Threshold}}%", + "parse_mode": "Markdown" +} +EOF +echo "" +echo -e "${GREEN}Files saved:${NC}" +echo "• Bot token: $TOKEN_FILE" +echo "• Chat ID: $CHAT_FILE" +echo "• Webhook config: /opt/pulse/telegram-webhook.json" +echo "" +echo -e "${YELLOW}Next steps:${NC}" +echo "1. Go to Pulse web interface" +echo "2. Navigate to Alerts > Webhooks" +echo "3. Add new webhook with the configuration above" +echo "4. Enable the webhook" +echo "5. Set up alert thresholds to trigger notifications" \ No newline at end of file diff --git a/telegram-cli-setup.sh b/telegram-cli-setup.sh new file mode 100755 index 000000000..ae3e282cf --- /dev/null +++ b/telegram-cli-setup.sh @@ -0,0 +1,281 @@ +#!/bin/bash + +# Telegram CLI Setup via Homebrew for Pulse +set -e + +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${BLUE}======================================" +echo "Telegram CLI Setup for Pulse" +echo "======================================${NC}" +echo "" + +# Check OS +if [[ "$OSTYPE" == "darwin"* ]]; then + echo -e "${GREEN}āœ“ macOS detected${NC}" + + # Check if Homebrew is installed + if ! command -v brew &> /dev/null; then + echo -e "${RED}Homebrew not found. Installing...${NC}" + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + else + echo -e "${GREEN}āœ“ Homebrew found${NC}" + fi + + # Install telegram-cli + echo "" + echo -e "${YELLOW}Installing telegram-cli via Homebrew...${NC}" + + # Check if already installed + if brew list telegram-cli &>/dev/null; then + echo -e "${GREEN}āœ“ telegram-cli already installed${NC}" + else + echo "Installing telegram-cli..." + brew install telegram-cli + fi + +elif [[ "$OSTYPE" == "linux-gnu"* ]]; then + echo -e "${GREEN}āœ“ Linux detected${NC}" + + # For Linux, try different package managers + if command -v apt-get &> /dev/null; then + echo "Installing telegram-cli via apt..." + sudo apt-get update + sudo apt-get install -y telegram-cli + elif command -v yum &> /dev/null; then + echo "Installing telegram-cli via yum..." + sudo yum install -y telegram-cli + else + echo -e "${RED}Package manager not supported. Building from source...${NC}" + + # Build from source + sudo apt-get install -y libreadline-dev libconfig-dev libssl-dev lua5.2 liblua5.2-dev libevent-dev libjansson-dev libpython-dev make + + cd /tmp + git clone --recursive https://github.com/vysheng/tg.git + cd tg + ./configure + make + sudo make install + cd /opt/pulse + fi +else + echo -e "${RED}Unsupported OS: $OSTYPE${NC}" + exit 1 +fi + +echo "" +echo -e "${GREEN}āœ“ telegram-cli installed${NC}" +echo "" + +# Create telegram-cli config +echo -e "${YELLOW}Creating telegram-cli configuration...${NC}" + +mkdir -p ~/.telegram-cli + +# Create config file +cat > ~/.telegram-cli/config << 'EOF' +# Telegram CLI Configuration +default_profile = "default"; + +default = { + config_directory = ".telegram-cli"; + use_ipv6 = false; + make_auth_key_on_start = true; +}; +EOF + +echo -e "${GREEN}āœ“ Configuration created${NC}" +echo "" + +# Create bot creation script for telegram-cli +cat > /tmp/create_bot.lua << 'EOF' +-- Lua script for telegram-cli to create a bot +function on_msg_receive (msg) + if msg.text then + print("Message received: " .. msg.text) + end +end + +function on_our_id (id) + print("Our ID: " .. id) +end + +function on_secret_chat_created (peer) + print("Secret chat created") +end + +function on_user_update (user) +end + +function on_chat_update (chat) +end + +function on_get_difference_end () +end + +function on_binlog_replay_end () + -- Start bot creation process + send_msg("@BotFather", "/newbot", ok_cb, false) +end +EOF + +# Create interactive setup script +cat > /tmp/setup_telegram_bot.sh << 'SCRIPT' +#!/bin/bash + +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +echo -e "${YELLOW}Starting telegram-cli...${NC}" +echo "" +echo "IMPORTANT: First time setup:" +echo "1. You'll be asked for your phone number" +echo "2. Enter it with country code (e.g., +1234567890)" +echo "3. You'll receive a code via Telegram" +echo "4. Enter the code when prompted" +echo "" +read -p "Press ENTER to continue..." + +# Start telegram-cli in interactive mode +echo "" +echo "Starting Telegram CLI..." +echo "Once logged in, we'll create your bot" +echo "" + +# Create expect script for automation +cat > /tmp/telegram_bot_create.expect << 'EXPECT' +#!/usr/bin/expect -f + +set timeout 30 + +spawn telegram-cli -N -W + +expect { + "phone number:" { + send_user "\nEnter your phone number with country code: " + expect_user -re "(.*)\n" + send "$expect_out(1,string)\r" + exp_continue + } + "code:" { + send_user "\nEnter the verification code: " + expect_user -re "(.*)\n" + send "$expect_out(1,string)\r" + exp_continue + } + "> " { + send_user "\nLogged in! Creating bot...\n" + + # Contact BotFather + send "msg @BotFather /newbot\r" + sleep 2 + + # Send bot name + send "msg @BotFather Pulse Monitor Alert Bot\r" + sleep 2 + + # Generate random username + set timestamp [clock seconds] + send "msg @BotFather PulseMonitor_${timestamp}_bot\r" + sleep 3 + + # Get messages to see the token + send "history @BotFather 5\r" + sleep 2 + + send "quit\r" + } + timeout { + send_user "\nTimeout occurred\n" + exit 1 + } +} + +expect eof +EXPECT + +chmod +x /tmp/telegram_bot_create.expect + +# Check if expect is installed +if ! command -v expect &> /dev/null; then + echo "Installing expect..." + if [[ "$OSTYPE" == "darwin"* ]]; then + brew install expect + else + sudo apt-get install -y expect + fi +fi + +# Run expect script +/tmp/telegram_bot_create.expect + +echo "" +echo -e "${GREEN}Bot creation process completed!${NC}" +echo "" +echo "Check the output above for your bot token (looks like: 1234567890:ABCdefGHIjklMNOpqrsTUVwxyz)" +echo "" +read -p "Enter your bot token: " BOT_TOKEN + +# Save token +echo "$BOT_TOKEN" > /opt/pulse/.telegram_bot_token +chmod 600 /opt/pulse/.telegram_bot_token + +echo -e "${GREEN}āœ“ Bot token saved${NC}" + +# Get chat ID +echo "" +echo "Now we need your chat ID..." +echo "1. Open Telegram app" +echo "2. Search for your bot and start a chat" +echo "3. Send any message" +echo "" +read -p "Press ENTER after sending a message to your bot..." + +# Get updates to find chat ID +RESPONSE=$(curl -s "https://api.telegram.org/bot${BOT_TOKEN}/getUpdates") +CHAT_ID=$(echo "$RESPONSE" | grep -o '"chat":{"id":[0-9-]*' | grep -o '[0-9-]*' | head -1) + +if [ -n "$CHAT_ID" ]; then + echo -e "${GREEN}āœ“ Found chat ID: $CHAT_ID${NC}" + echo "$CHAT_ID" > /opt/pulse/.telegram_chat_id + chmod 600 /opt/pulse/.telegram_chat_id +else + echo "Could not find chat ID automatically" + echo "Visit: https://api.telegram.org/bot${BOT_TOKEN}/getUpdates" + echo "Look for 'chat' -> 'id'" + read -p "Enter your chat ID: " CHAT_ID + echo "$CHAT_ID" > /opt/pulse/.telegram_chat_id +fi + +# Test message +echo "" +echo "Sending test message..." +curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \ + -H "Content-Type: application/json" \ + -d '{ + "chat_id": "'$CHAT_ID'", + "text": "āœ… Pulse Telegram Integration Complete!", + "parse_mode": "Markdown" + }' > /dev/null + +echo -e "${GREEN}āœ“ Setup complete!${NC}" +echo "" +echo "Webhook URL for Pulse:" +echo "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" +echo "" +echo "Payload template:" +echo '{' +echo ' "chat_id": "'$CHAT_ID'",' +echo ' "text": "🚨 *Pulse Alert: {{.Level}}*\n\n{{.Message}}",' +echo ' "parse_mode": "Markdown"' +echo '}' +SCRIPT + +chmod +x /tmp/setup_telegram_bot.sh +/tmp/setup_telegram_bot.sh \ No newline at end of file diff --git a/telegram-config.json b/telegram-config.json new file mode 100644 index 000000000..4d1367c3b --- /dev/null +++ b/telegram-config.json @@ -0,0 +1,12 @@ +{ + "bot_token": "8463420252:AAFOMoXSwpJoIyFrZ5QKvXZiksEcVJLP7iI", + "chat_id": "8143835411", + "bot_username": "pulse_monitor1_bot", + "webhook_url": "https://api.telegram.org/bot8463420252:AAFOMoXSwpJoIyFrZ5QKvXZiksEcVJLP7iI/sendMessage", + "payload_template": { + "chat_id": "8143835411", + "text": "🚨 *Pulse Alert: {{.Level}}*\\n\\n{{.Message}}\\n\\nšŸ“Š *Details:*\\n• Resource: {{.ResourceName}}\\n• Node: {{.Node}}\\n• Value: {{.Value}}%\\n• Threshold: {{.Threshold}}%\\n• Duration: {{.Duration}}\\n\\nā° {{.Timestamp}}", + "parse_mode": "Markdown", + "disable_web_page_preview": true + } +} \ No newline at end of file diff --git a/telegram-fully-automated.sh b/telegram-fully-automated.sh new file mode 100755 index 000000000..bbd1348cd --- /dev/null +++ b/telegram-fully-automated.sh @@ -0,0 +1,190 @@ +#!/bin/bash + +# Fully Automated Telegram Bot Setup +# This does EVERYTHING possible without manual intervention + +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +echo -e "${BLUE}======================================" +echo "Fully Automated Telegram Setup" +echo "======================================${NC}" +echo "" + +# Generate unique bot username +TIMESTAMP=$(date +%s) +BOT_NAME="Pulse Monitor Alert Bot" +BOT_USERNAME="PulseMonitor_${TIMESTAMP}_bot" + +echo -e "${YELLOW}Unfortunately, Telegram requires manual bot creation for security.${NC}" +echo -e "${YELLOW}But I'll make it as easy as possible!${NC}" +echo "" +echo -e "${GREEN}Here's a one-click solution:${NC}" +echo "" + +# Create a pre-filled URL for BotFather +echo "1. Click this link to open BotFather:" +echo -e "${BLUE}https://t.me/BotFather${NC}" +echo "" +echo "2. Copy and paste these THREE messages quickly:" +echo "" +echo -e "${GREEN}/newbot${NC}" +echo -e "${GREEN}${BOT_NAME}${NC}" +echo -e "${GREEN}${BOT_USERNAME}${NC}" +echo "" +echo "3. BotFather will respond with a token. It looks like:" +echo " 1234567890:ABCdefGHIjklMNOpqrsTUVwxyz" +echo "" + +# Wait for token +read -p "Paste your bot token here: " BOT_TOKEN + +# Verify the token immediately +echo "" +echo "Verifying token..." +RESPONSE=$(curl -s "https://api.telegram.org/bot${BOT_TOKEN}/getMe") + +if ! echo "$RESPONSE" | grep -q '"ok":true'; then + echo -e "${RED}Invalid token! Please check and try again.${NC}" + exit 1 +fi + +BOT_INFO=$(echo "$RESPONSE" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data['result']['username'])") +echo -e "${GREEN}āœ“ Bot created successfully: @${BOT_INFO}${NC}" + +# Save token +mkdir -p /opt/pulse/.telegram +echo "$BOT_TOKEN" > /opt/pulse/.telegram/bot_token +chmod 600 /opt/pulse/.telegram/bot_token + +# Now get chat ID +echo "" +echo -e "${YELLOW}Step 2: Getting your Chat ID${NC}" +echo "" +echo "Click this link to message your bot:" +echo -e "${BLUE}https://t.me/${BOT_INFO}${NC}" +echo "" +echo "Send the message: /start" +echo "" +read -p "Press ENTER after sending /start to your bot..." + +# Get chat ID +UPDATES=$(curl -s "https://api.telegram.org/bot${BOT_TOKEN}/getUpdates") +CHAT_ID=$(echo "$UPDATES" | python3 -c " +import sys, json +try: + data = json.load(sys.stdin) + if data['ok'] and data['result']: + for update in data['result']: + if 'message' in update: + print(update['message']['chat']['id']) + break +except: pass +") + +if [ -z "$CHAT_ID" ]; then + echo -e "${YELLOW}Waiting for your message...${NC}" + sleep 3 + UPDATES=$(curl -s "https://api.telegram.org/bot${BOT_TOKEN}/getUpdates") + CHAT_ID=$(echo "$UPDATES" | python3 -c " +import sys, json +try: + data = json.load(sys.stdin) + if data['ok'] and data['result']: + for update in data['result']: + if 'message' in update: + print(update['message']['chat']['id']) + break +except: pass +") +fi + +if [ -z "$CHAT_ID" ]; then + echo -e "${RED}No message found yet.${NC}" + echo "Please make sure you sent /start to @${BOT_INFO}" + echo "" + echo "Manual check URL:" + echo "https://api.telegram.org/bot${BOT_TOKEN}/getUpdates" + echo "" + read -p "Enter your chat ID manually: " CHAT_ID +else + echo -e "${GREEN}āœ“ Found your chat ID: ${CHAT_ID}${NC}" +fi + +# Save chat ID +echo "$CHAT_ID" > /opt/pulse/.telegram/chat_id +chmod 600 /opt/pulse/.telegram/chat_id + +# Send test message +echo "" +echo "Sending test message..." +TEST_MSG=$(cat < /opt/pulse/telegram-webhook.json <