From f59913910fd37094e2fa2f8c3bb4ed8fdd2deb81 Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Sat, 21 Feb 2026 19:17:46 -0500 Subject: [PATCH] Replace Discord webhook notifications with Matrix (hookshot) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add helpers/NotificationHelper.php: shared Matrix webhook sender that reads MATRIX_WEBHOOK_URL and MATRIX_NOTIFY_USERS from config - Remove sendDiscordWebhook() from TicketController; call NotificationHelper::sendTicketNotification() instead - Replace 60-line Discord embed block in create_ticket_api.php with a single NotificationHelper call - config/config.php: DISCORD_WEBHOOK_URL → MATRIX_WEBHOOK_URL + new MATRIX_NOTIFY_USERS key (comma-separated Matrix user IDs) - .env.example: updated env var names and comments Payload sent to hookshot includes notify_users array so the JS transform can build proper @mention links for each user. Co-Authored-By: Claude Sonnet 4.6 --- .env.example | 12 +++- config/config.php | 6 +- controllers/TicketController.php | 107 +------------------------------ create_ticket_api.php | 72 +++------------------ helpers/NotificationHelper.php | 58 +++++++++++++++++ 5 files changed, 83 insertions(+), 172 deletions(-) create mode 100644 helpers/NotificationHelper.php diff --git a/.env.example b/.env.example index 5d02e12..ab0bf97 100644 --- a/.env.example +++ b/.env.example @@ -7,10 +7,16 @@ DB_USER=tinkertickets DB_PASS=your_password_here DB_NAME=ticketing_system -# Discord Webhook (optional - for notifications) -DISCORD_WEBHOOK_URL= +# Matrix Webhook (optional - for notifications via matrix-hookshot) +# Set to your hookshot generic webhook URL, e.g.: +# https://matrix.lotusguild.org/webhook/ +MATRIX_WEBHOOK_URL= -# Application Domain (required for Discord webhook links) +# Matrix users to @mention on every new ticket (comma-separated Matrix user IDs) +# e.g. @jared:matrix.lotusguild.org,@alice:matrix.lotusguild.org +MATRIX_NOTIFY_USERS= + +# Application Domain (required for Matrix webhook ticket links) # Set this to your public domain (e.g., t.lotusguild.org) APP_DOMAIN= diff --git a/config/config.php b/config/config.php index 34891f5..d47c76c 100644 --- a/config/config.php +++ b/config/config.php @@ -31,8 +31,10 @@ $GLOBALS['config'] = [ 'ASSETS_URL' => '/assets', // Assets URL 'API_URL' => '/api', // API URL - // Discord webhook - 'DISCORD_WEBHOOK_URL' => $envVars['DISCORD_WEBHOOK_URL'] ?? null, + // Matrix webhook (hookshot generic webhook URL) + 'MATRIX_WEBHOOK_URL' => $envVars['MATRIX_WEBHOOK_URL'] ?? null, + // Comma-separated Matrix user IDs to @mention on new tickets (e.g. @jared:matrix.lotusguild.org) + 'MATRIX_NOTIFY_USERS' => $envVars['MATRIX_NOTIFY_USERS'] ?? '', // Domain settings for external integrations (webhooks, links, etc.) // Set APP_DOMAIN in .env to override diff --git a/controllers/TicketController.php b/controllers/TicketController.php index 0f70da3..de45ad6 100644 --- a/controllers/TicketController.php +++ b/controllers/TicketController.php @@ -7,6 +7,7 @@ require_once dirname(__DIR__) . '/models/UserModel.php'; require_once dirname(__DIR__) . '/models/WorkflowModel.php'; require_once dirname(__DIR__) . '/models/TemplateModel.php'; require_once dirname(__DIR__) . '/helpers/UrlHelper.php'; +require_once dirname(__DIR__) . '/helpers/NotificationHelper.php'; class TicketController { private $ticketModel; @@ -110,8 +111,8 @@ class TicketController { $GLOBALS['auditLog']->logTicketCreate($userId, $result['ticket_id'], $ticketData); } - // Send Discord webhook notification for new ticket - $this->sendDiscordWebhook($result['ticket_id'], $ticketData); + // Send Matrix notification for new ticket + NotificationHelper::sendTicketNotification($result['ticket_id'], $ticketData, 'manual'); // Redirect to the new ticket header("Location: " . $GLOBALS['config']['BASE_URL'] . "/ticket/" . $result['ticket_id']); @@ -195,107 +196,5 @@ class TicketController { } } - private function sendDiscordWebhook($ticketId, $ticketData) { - $webhookUrl = $GLOBALS['config']['DISCORD_WEBHOOK_URL'] ?? null; - if (empty($webhookUrl)) { - error_log("Discord webhook URL not configured, skipping webhook for ticket creation"); - return; - } - - // Create ticket URL using validated host - $ticketUrl = UrlHelper::ticketUrl($ticketId); - - // Map priorities to Discord colors (matching API endpoint) - $priorityColors = [ - 1 => 0xDC3545, // P1 Critical - Red - 2 => 0xFD7E14, // P2 High - Orange - 3 => 0x0DCAF0, // P3 Medium - Cyan - 4 => 0x198754, // P4 Low - Green - 5 => 0x6C757D // P5 Info - Gray - ]; - - // Priority labels for display - $priorityLabels = [ - 1 => "P1 - Critical", - 2 => "P2 - High", - 3 => "P3 - Medium", - 4 => "P4 - Low", - 5 => "P5 - Info" - ]; - - $priority = (int)($ticketData['priority'] ?? 4); - $color = $priorityColors[$priority] ?? 0x6C757D; - $priorityLabel = $priorityLabels[$priority] ?? "P{$priority}"; - - $title = $ticketData['title'] ?? 'Untitled'; - $category = $ticketData['category'] ?? 'General'; - $type = $ticketData['type'] ?? 'Issue'; - $status = $ticketData['status'] ?? 'Open'; - - // Extract hostname from title for cleaner display - preg_match('/^\[([^\]]+)\]/', $title, $hostnameMatch); - $sourceHost = $hostnameMatch[1] ?? 'Manual'; - - $embed = [ - 'title' => 'New Ticket Created', - 'description' => "**#{$ticketId}** - {$title}", - 'url' => $ticketUrl, - 'color' => $color, - 'fields' => [ - [ - 'name' => 'Priority', - 'value' => $priorityLabel, - 'inline' => true - ], - [ - 'name' => 'Category', - 'value' => $category, - 'inline' => true - ], - [ - 'name' => 'Type', - 'value' => $type, - 'inline' => true - ], - [ - 'name' => 'Status', - 'value' => $status, - 'inline' => true - ], - [ - 'name' => 'Source', - 'value' => $sourceHost, - 'inline' => true - ] - ], - 'footer' => [ - 'text' => 'Tinker Tickets | Manual Entry' - ], - 'timestamp' => date('c') - ]; - - $payload = [ - 'embeds' => [$embed] - ]; - - // Send webhook - $ch = curl_init($webhookUrl); - curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); - curl_setopt($ch, CURLOPT_POST, 1); - curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_TIMEOUT, 10); - - $webhookResult = curl_exec($ch); - $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); - $curlError = curl_error($ch); - curl_close($ch); - - if ($curlError) { - error_log("Discord webhook cURL error: {$curlError}"); - } elseif ($httpCode !== 204 && $httpCode !== 200) { - error_log("Discord webhook failed for ticket #{$ticketId}. HTTP Code: {$httpCode}"); - } - } } ?> \ No newline at end of file diff --git a/create_ticket_api.php b/create_ticket_api.php index 9549744..8ca211c 100644 --- a/create_ticket_api.php +++ b/create_ticket_api.php @@ -227,66 +227,12 @@ if ($stmt->execute()) { $stmt->close(); $conn->close(); -// Discord webhook notification -if (isset($envVars['DISCORD_WEBHOOK_URL']) && !empty($envVars['DISCORD_WEBHOOK_URL'])) { - $discord_webhook_url = $envVars['DISCORD_WEBHOOK_URL']; - - // Map priorities to Discord colors (decimal format) - $priorityColors = [ - "1" => 0xDC3545, // P1 Critical - Red - "2" => 0xFD7E14, // P2 High - Orange - "3" => 0x0DCAF0, // P3 Medium - Cyan - "4" => 0x198754, // P4 Low - Green - "5" => 0x6C757D // P5 Info - Gray - ]; - - // Priority labels for display - $priorityLabels = [ - "1" => "P1 - Critical", - "2" => "P2 - High", - "3" => "P3 - Medium", - "4" => "P4 - Low", - "5" => "P5 - Info" - ]; - - // Create ticket URL using validated host - $ticketUrl = UrlHelper::ticketUrl($ticket_id); - - // Extract hostname from title for cleaner display - preg_match('/^\[([^\]]+)\]/', $title, $hostnameMatch); - $sourceHost = $hostnameMatch[1] ?? 'Unknown'; - - $discord_data = [ - "embeds" => [[ - "title" => "New Ticket Created", - "description" => "**#{$ticket_id}** - {$title}", - "url" => $ticketUrl, - "color" => $priorityColors[$priority] ?? 0x6C757D, - "fields" => [ - ["name" => "Priority", "value" => $priorityLabels[$priority] ?? "P{$priority}", "inline" => true], - ["name" => "Category", "value" => $category, "inline" => true], - ["name" => "Type", "value" => $type, "inline" => true], - ["name" => "Status", "value" => $status, "inline" => true], - ["name" => "Source", "value" => $sourceHost, "inline" => true] - ], - "footer" => [ - "text" => "Tinker Tickets | Automated Alert" - ], - "timestamp" => date('c') - ]] - ]; - - $ch = curl_init($discord_webhook_url); - curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); - curl_setopt($ch, CURLOPT_POST, 1); - curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($discord_data)); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_TIMEOUT, 10); - $webhookResult = curl_exec($ch); - $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); - curl_close($ch); - - if ($httpCode !== 204 && $httpCode !== 200) { - error_log("Discord webhook failed for ticket #{$ticket_id}. HTTP Code: {$httpCode}"); - } -} +// Matrix webhook notification +require_once __DIR__ . '/helpers/NotificationHelper.php'; +NotificationHelper::sendTicketNotification($ticket_id, [ + 'title' => $title, + 'priority' => $priority, + 'category' => $category, + 'type' => $type, + 'status' => $status, +], 'automated'); diff --git a/helpers/NotificationHelper.php b/helpers/NotificationHelper.php new file mode 100644 index 0000000..410c66b --- /dev/null +++ b/helpers/NotificationHelper.php @@ -0,0 +1,58 @@ + $ticketId, + 'title' => $ticketData['title'] ?? 'Untitled', + 'priority' => (int)($ticketData['priority'] ?? 4), + 'category' => $ticketData['category'] ?? 'General', + 'type' => $ticketData['type'] ?? 'Issue', + 'status' => $ticketData['status'] ?? 'Open', + 'source' => $source, + 'url' => UrlHelper::ticketUrl($ticketId), + 'trigger' => $trigger, + 'notify_users' => $notifyUsers, + ]; + + $ch = curl_init($webhookUrl); + curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_TIMEOUT, 10); + + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $curlError = curl_error($ch); + curl_close($ch); + + if ($curlError) { + error_log("Matrix webhook cURL error for ticket #{$ticketId}: {$curlError}"); + } elseif ($httpCode < 200 || $httpCode >= 300) { + error_log("Matrix webhook failed for ticket #{$ticketId}. HTTP {$httpCode}: {$response}"); + } + } +} +?>