Tabela de conteúdos

Passo 02: Sistema de Canais de Notificação - Telegram Webhook

⚠️ INSTRUÇÕES IMPORTANTES ANTES DE COMEÇAR

Para evitar erros durante a implementação, siga EXATAMENTE estas etapas:

1. Ler o template COMPLETO da página

2. Identificar TODOS os arquivos mencionados

3. Verificar a estrutura EXATA de cada arquivo

4. Implementar linha por linha conforme template

🚨 ATENÇÃO:

📋 Visão Geral

Sistema de canais de notificação que permite comunicação com diferentes serviços externos. Focado no canal do Telegram, mas extensível para outros canais. Depende do sistema de logging e pode ser reutilizado em outros projetos.

🚀 Comando de Implementação

implementar sistema canais notificacao telegram

⚙️ Pré-requisitos

  1. Sistema de Logging implementado
  2. Laravel 12 instalado
  3. PHP 8.2+ configurado
  4. Guzzle HTTP para requisições
  5. Bot Token do Telegram

📁 Arquivos do Módulo

🏗️ Contracts

🔧 Services

⚙️ Configurações

🔧 Implementação Passo a Passo

Passo 1: Criar Interface

<?php
 
namespace App\Contracts;
 
interface NotificationChannelInterface
{
    /**
     * Send text message
     *
     * @param string $message
     * @param string|null $recipient
     * @return array
     */
    public function sendTextMessage(string $message, ?string $recipient = null): array;
 
    /**
     * Send notification with data
     *
     * @param array $data
     * @return array
     */
    public function sendNotification(array $data): array;
 
    /**
     * Test connection
     *
     * @return array
     */
    public function testConnection(): array;
 
    /**
     * Get channel name
     *
     * @return string
     */
    public function getChannelName(): string;
 
    /**
     * Check if channel is enabled
     *
     * @return bool
     */
    public function isEnabled(): bool;
}

Passo 2: Implementar Canal do Telegram

<?php
 
namespace App\Services\Channels;
 
use App\Contracts\NotificationChannelInterface;
use App\Contracts\LoggingServiceInterface;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use App\Contracts\MessageFlowTrackerInterface;
use App\Contracts\MessageTrackingInterface;
 
class TelegramChannel implements NotificationChannelInterface, MessageTrackingInterface
{
    private string $botToken;
    private string $apiUrl;
    private array $recipients;
 
    public function __construct(
        private MessageFlowTrackerInterface $flowTracker,
        private ?LoggingServiceInterface $loggingService = null
    ) {
        $this->initializeTracking();
        $this->botToken = config('services.telegram.bot_token', '');
        $this->apiUrl = "https://api.telegram.org/bot{$this->botToken}";
        $this->recipients = config('services.telegram.recipients', []);
    }
 
    /**
     * Send text message via Telegram
     *
     * @param string $message
     * @param string|null $recipient
     * @return array
     */
    public function sendTextMessage(string $message, ?string $recipient = null): array
    {
        $this->trackMethod('sendTextMessage', ['message' => $message, 'recipient' => $recipient]);
 
        if (!$this->isEnabled()) {
            $result = [
                'success' => false,
                'error' => 'Telegram channel is disabled'
            ];
 
            $this->endMethod('sendTextMessage', ['result' => $result, 'status' => 'disabled']);
            return $result;
        }
 
        try {
            if ($recipient) {
                return $this->sendToRecipient($recipient, $message);
            }
 
            // Send to all configured recipients
            if (empty($this->recipients)) {
                $result = [
                    'success' => false,
                    'error' => 'No Telegram recipients configured'
                ];
 
                $this->endMethod('sendTextMessage', ['result' => $result, 'status' => 'no_recipients']);
                return $result;
            }
 
            $results = [];
            foreach ($this->recipients as $recipient) {
                $results[$recipient] = $this->sendToRecipient($recipient, $message);
            }
 
            $successCount = count(array_filter($results, fn($r) => $r['success']));
 
            $result = [
                'success' => $successCount > 0,
                'sent_to' => $successCount,
                'total_recipients' => count($results),
                'results' => $results
            ];
 
            $this->endMethod('sendTextMessage', ['result' => $result, 'status' => 'success']);
            return $result;
 
        } catch (\Exception $e) {
            Log::error('Telegram channel error', [
                'error' => $e->getMessage(),
                'message' => $message
            ]);
 
            $result = [
                'success' => false,
                'error' => $e->getMessage()
            ];
 
            $this->endMethod('sendTextMessage', ['result' => $result, 'error' => $e->getMessage()]);
            return $result;
        }
    }
 
    /**
     * Send message with inline keyboard via Telegram
     *
     * @param string $message
     * @param string $chatId
     * @param array $keyboard
     * @return array
     */
    public function sendMessageWithKeyboard(string $message, string $chatId, array $keyboard): array
    {
        $this->trackMethod('sendMessageWithKeyboard', ['message' => $message, 'chat_id' => $chatId, 'keyboard' => $keyboard]);
        if (!$this->isEnabled()) {
            $result = [
                'success' => false,
                'error' => 'Telegram channel is disabled'
            ];
            $this->endMethod('sendMessageWithKeyboard', ['result' => $result, 'status' => 'disabled']);
            return $result;
        }
 
        try {
            $response = Http::post("{$this->apiUrl}/sendMessage", [
                'chat_id' => $chatId,
                'text' => $message,
                'parse_mode' => 'Markdown',
                'reply_markup' => [
                    'inline_keyboard' => $keyboard
                ]
            ]);
 
            if ($response->successful()) {
                $data = $response->json();
                $result = [
                    'success' => true,
                    'message_id' => $data['result']['message_id'] ?? null,
                    'response' => $data
                ];
                $this->endMethod('sendMessageWithKeyboard', ['result' => $result, 'status' => 'success']);
                return $result;
            }
 
            $result = [
                'success' => false,
                'error' => $response->json()['description'] ?? 'Unknown error',
                'status' => $response->status()
            ];
            $this->endMethod('sendMessageWithKeyboard', ['result' => $result, 'status' => 'http_error']);
            return $result;
 
        } catch (\Exception $e) {
            Log::error('Telegram keyboard message error', [
                'error' => $e->getMessage(),
                'message' => $message,
                'chat_id' => $chatId
            ]);
 
            $result = [
                'success' => false,
                'error' => $e->getMessage()
            ];
            $this->endMethod('sendMessageWithKeyboard', ['result' => $result, 'error' => $e->getMessage()]);
            return $result;
        }
    }
 
    /**
     * Answer callback query (for inline keyboard buttons)
     *
     * @param string $callbackQueryId
     * @param string|null $text
     * @param bool $showAlert
     * @return array
     */
    public function answerCallbackQuery(string $callbackQueryId, ?string $text = null, bool $showAlert = false): array
    {
        if (!$this->isEnabled()) {
            return [
                'success' => false,
                'error' => 'Telegram channel is disabled'
            ];
        }
 
        try {
            $data = [
                'callback_query_id' => $callbackQueryId
            ];
 
            if ($text) {
                $data['text'] = $text;
            }
 
            if ($showAlert) {
                $data['show_alert'] = $showAlert;
            }
 
            $response = Http::post("{$this->apiUrl}/answerCallbackQuery", $data);
 
            if ($response->successful()) {
                $data = $response->json();
                return [
                    'success' => true,
                    'response' => $data
                ];
            }
 
            return [
                'success' => false,
                'error' => $response->json()['description'] ?? 'Unknown error',
                'status' => $response->status()
            ];
 
        } catch (\Exception $e) {
            Log::error('Telegram answer callback query error', [
                'error' => $e->getMessage(),
                'callback_query_id' => $callbackQueryId
            ]);
 
            return [
                'success' => false,
                'error' => $e->getMessage()
            ];
        }
    }
 
    /**
     * Send notification with data via Telegram
     *
     * @param array $data
     * @return array
     */
    public function sendNotification(array $data): array
    {
        if (!$this->isEnabled()) {
            return [
                'success' => false,
                'error' => 'Telegram channel is disabled'
            ];
        }
 
        try {
            $message = $this->formatNotificationMessage($data);
            return $this->sendTextMessage($message);
        } catch (\Exception $e) {
            Log::error('Telegram notification error', [
                'error' => $e->getMessage(),
                'data' => $data
            ]);
 
            return [
                'success' => false,
                'error' => $e->getMessage()
            ];
        }
    }
 
    /**
     * Test Telegram connection
     *
     * @return array
     */
    public function testConnection(): array
    {
        if (!$this->isEnabled()) {
            return [
                'success' => false,
                'error' => 'Telegram channel is disabled'
            ];
        }
 
        try {
            $response = Http::get("{$this->apiUrl}/getMe");
 
            if ($response->successful()) {
                $data = $response->json();
                return [
                    'success' => true,
                    'bot_name' => $data['result']['first_name'] ?? 'Unknown',
                    'bot_username' => $data['result']['username'] ?? 'Unknown',
                    'bot_id' => $data['result']['id'] ?? 'Unknown'
                ];
            }
 
            return [
                'success' => false,
                'error' => $response->json()['description'] ?? 'Unknown error',
                'status' => $response->status()
            ];
 
        } catch (\Exception $e) {
            return [
                'success' => false,
                'error' => $e->getMessage()
            ];
        }
    }
 
    /**
     * Get channel name
     *
     * @return string
     */
    public function getChannelName(): string
    {
        return 'telegram';
    }
 
    /**
     * Check if Telegram channel is enabled
     *
     * @return bool
     */
    public function isEnabled(): bool
    {
        return config('services.telegram.enabled', true) &&
               !empty($this->botToken) &&
               !empty($this->recipients);
    }
 
    /**
     * Send message to specific recipient
     *
     * @param string $chatId
     * @param string $message
     * @return array
     */
    private function sendToRecipient(string $chatId, string $message): array
    {
        try {
            $response = Http::post("{$this->apiUrl}/sendMessage", [
                'chat_id' => $chatId,
                'text' => $message,
                'parse_mode' => 'Markdown'
            ]);
 
            if ($response->successful()) {
                $data = $response->json();
                return [
                    'success' => true,
                    'message_id' => $data['result']['message_id'] ?? null,
                    'response' => $data
                ];
            }
 
            return [
                'success' => false,
                'error' => $response->json()['description'] ?? 'Unknown error',
                'status' => $response->status()
            ];
 
        } catch (\Exception $e) {
            return [
                'success' => false,
                'error' => $e->getMessage()
            ];
        }
    }
 
    /**
     * Format notification message for Telegram
     *
     * @param array $data
     * @return string
     */
    private function formatNotificationMessage(array $data): string
    {
        $status = $data['status'] ?? 'unknown';
        $branch = $data['branch'] ?? 'unknown';
        $commit = $data['commit'] ?? 'unknown';
        $message = $data['message'] ?? 'no message';
        $timestamp = $data['timestamp'] ?? now()->format('d/m/Y H:i:s');
 
        $emoji = $status === 'success' ? '✅' : ($status === 'error' ? '❌' : '⚠️');
        $statusText = $status === 'success' ? 'SUCESSO' : ($status === 'error' ? 'ERRO' : 'ATENÇÃO');
 
        return "🚀 *DEPLOY NOTIFICATION*\n\n" .
               "{$emoji} *Status:* {$statusText}\n" .
               "🌿 *Branch:* {$branch}\n" .
               "🔗 *Commit:* {$commit}\n" .
               "💬 *Message:* {$message}\n" .
               "⏰ *Timestamp:* {$timestamp}\n\n" .
               "Sistema: Rei do Óleo";
    }
 
    /**
     * Get file info from Telegram
     */
    public function getFile(string $fileId): array
    {
        if (!$this->isEnabled()) {
            return [
                'success' => false,
                'error' => 'Telegram channel is disabled'
            ];
        }
 
        try {
            $response = Http::get("{$this->apiUrl}/getFile", [
                'file_id' => $fileId
            ]);
 
            if ($response->successful()) {
                $data = $response->json();
                return [
                    'success' => true,
                    'file_path' => $data['result']['file_path'] ?? null,
                    'file_size' => $data['result']['file_size'] ?? null,
                    'file_id' => $data['result']['file_id'] ?? null
                ];
            }
 
            return [
                'success' => false,
                'error' => $response->json()['description'] ?? 'Unknown error',
                'status' => $response->status()
            ];
 
        } catch (\Exception $e) {
            Log::error('Telegram get file error', [
                'error' => $e->getMessage(),
                'file_id' => $fileId
            ]);
 
            return [
                'success' => false,
                'error' => $e->getMessage()
            ];
        }
    }
 
    /**
     * Send typing indicator
     */
    public function sendTypingIndicator(string $chatId): array
    {
        if (!$this->isEnabled()) {
            return [
                'success' => false,
                'error' => 'Telegram channel is disabled'
            ];
        }
 
        try {
            $response = Http::post("{$this->apiUrl}/sendChatAction", [
                'chat_id' => $chatId,
                'action' => 'typing'
            ]);
 
            return [
                'success' => $response->successful(),
                'status' => $response->status()
            ];
 
        } catch (\Exception $e) {
            return [
                'success' => false,
                'error' => $e->getMessage()
            ];
        }
    }
 
    /**
     * Send document via Telegram
     */
    public function sendDocument(string $chatId, string $filePath, string $caption = ''): array
    {
        $this->trackMethod('sendDocument', ['chat_id' => $chatId, 'file_path' => $filePath, 'caption' => $caption]);
 
        if (!$this->isEnabled()) {
            $result = [
                'success' => false,
                'error' => 'Telegram channel is disabled'
            ];
 
            $this->endMethod('sendDocument', ['result' => $result, 'status' => 'disabled']);
            return $result;
        }
 
        if (!file_exists($filePath)) {
            $result = [
                'success' => false,
                'error' => 'File not found: ' . $filePath
            ];
 
            $this->endMethod('sendDocument', ['result' => $result, 'status' => 'file_not_found']);
            return $result;
        }
 
        try {
            // Send upload document indicator
            $this->sendUploadDocumentIndicator($chatId);
 
            $response = Http::attach(
                'document', file_get_contents($filePath), basename($filePath)
            )->post("{$this->apiUrl}/sendDocument", [
                'chat_id' => $chatId,
                'caption' => $caption,
                'parse_mode' => 'Markdown'
            ]);
 
            $result = [
                'success' => $response->successful(),
                'status' => $response->status(),
                'data' => $response->json(),
                'response_body' => $response->body()
            ];
 
            if (!$response->successful()) {
                if ($this->loggingService) {
                    $this->loggingService->logTelegramEvent('document_send_failed', [
                        'chat_id' => $chatId,
                        'file_path' => $filePath,
                        'status' => $response->status(),
                        'response' => $response->body()
                    ], 'error');
                } else {
                    Log::error('Failed to send document via Telegram', [
                        'chat_id' => $chatId,
                        'file_path' => $filePath,
                        'status' => $response->status(),
                        'response' => $response->body()
                    ]);
                }
            } else {
                if ($this->loggingService) {
                    $this->loggingService->logTelegramEvent('document_sent_successfully', [
                        'chat_id' => $chatId,
                        'file_name' => basename($filePath),
                        'file_size' => filesize($filePath)
                    ], 'info');
                } else {
                    Log::info('Document sent successfully via Telegram', [
                        'chat_id' => $chatId,
                        'file_name' => basename($filePath),
                        'file_size' => filesize($filePath)
                    ]);
                }
            }
 
            $this->endMethod('sendDocument', ['result' => $result]);
            return $result;
 
        } catch (\Exception $e) {
            if ($this->loggingService) {
                $this->loggingService->logTelegramEvent('document_send_exception', [
                    'chat_id' => $chatId,
                    'file_path' => $filePath,
                    'error' => $e->getMessage()
                ], 'error');
            } else {
                Log::error('Exception while sending document via Telegram', [
                    'chat_id' => $chatId,
                    'file_path' => $filePath,
                    'error' => $e->getMessage()
                ]);
            }
 
            $result = [
                'success' => false,
                'error' => $e->getMessage()
            ];
 
            $this->endMethod('sendDocument', ['result' => $result, 'status' => 'exception']);
            return $result;
        }
    }
 
    /**
     * Send upload document indicator
     */
    public function sendUploadDocumentIndicator(string $chatId): array
    {
        if (!$this->isEnabled()) {
            return [
                'success' => false,
                'error' => 'Telegram channel is disabled'
            ];
        }
 
        try {
            $response = Http::post("{$this->apiUrl}/sendChatAction", [
                'chat_id' => $chatId,
                'action' => 'upload_document'
            ]);
 
            return [
                'success' => $response->successful(),
                'status' => $response->status()
            ];
 
        } catch (\Exception $e) {
            return [
                'success' => false,
                'error' => $e->getMessage()
            ];
        }
    }
 
    // ========================================
    // MessageTrackingInterface Implementation
    // ========================================
 
    /**
     * Inicializa o sistema de tracking
     */
    public function initializeTracking(): void
    {
        // Tracking já é inicializado no construtor
    }
 
    /**
     * Inicia o tracking de um método
     */
    public function trackMethod(string $methodName, array $inputData = [], array $outputData = []): void
    {
        $className = class_basename($this);
        $this->flowTracker->trackMethod($className, $methodName, $inputData, $outputData);
    }
 
    /**
     * Finaliza o tracking de um método
     */
    public function endMethod(string $methodName, array $outputData = []): void
    {
        $className = class_basename($this);
        $this->flowTracker->endMethod($className, $methodName, $outputData);
    }
}

Passo 3: Configurar Services

#config/services.php

    ...
 
    'telegram' => [
        'enabled' => env('TELEGRAM_ENABLED', false),
        'bot_token' => env('TELEGRAM_BOT_TOKEN'),
        'webhook_secret' => env('TELEGRAM_WEBHOOK_SECRET', ''),
        'recipients' => array_filter(explode(',', env('TELEGRAM_RECIPIENTS', ''))),
    ],
 
    ...

Passo 4: Registrar no Container

#app/Providers/TelegramServiceProvider.php

<?php
 
namespace App\Providers;
 
use Illuminate\Support\ServiceProvider;
use App\Services\Channels\TelegramChannel;
 
 
class TelegramServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     */
    public function register(): void
    {
        $this->app->singleton(TelegramChannel::class);
    }
 
    /**
     * Bootstrap services.
     */
    public function boot(): void
    {
        //
    }
}

Passo 5: Registrar Provider

#config/app.php

'providers' => [
    // ... outros providers
    App\Providers\TelegramServiceProvider::class,
],

✅ Validação do Módulo

Checklist de Implementação

  1. [ ] Interface `NotificationChannelInterface` criada
  2. [ ] Canal `TelegramChannel` implementado
  3. [ ] Configuração `services.php` atualizada
  4. [ ] Registro no container de dependência
  5. [ ] Integração com sistema de logging
  6. [ ] Suporte a tracking de mensagens

Comandos de Validação

# Verificar configuração
php artisan config:cache
php artisan config:show services.telegram
 
# Testar canal
php artisan tinker
# >>> $channel = app(\App\Contracts\NotificationChannelInterface::class);
# >>> $channel->isAvailable();
# >>> $channel->getBotInfo();