setDisplayName('Discord') ->setLogoFileName('logo.png'); } /** * Settings required for module configuration * * @return array */ public function settings() { return [ 'webhookURL' => [ 'FriendlyName' => 'Discord Webhook URL', 'Type' => 'text', 'Description' => 'The Discord Webhook URL that notifications should be sent to.', ], 'companyName' => [ 'FriendlyName' => 'Company Name (Optional)', 'Type' => 'text', 'Description' => 'This will be the name of the user that sends the message in the Discord channel.', 'Placeholder' => Setting::getValue('CompanyName'), ], 'discordColor' => [ 'FriendlyName' => 'Message Colour (Optional)', 'Type' => 'text', 'Description' => 'This is the side line colour for the message. The color code format within this script is standard hex. Exclude the beginning # character if one is present.', 'Placeholder' => '73CB0B', ], 'discordGroupID' => [ 'FriendlyName' => 'Notification Role ID (Optional)', 'Type' => 'text', 'Description' => 'If you\'d like to have a specific group pinged on each message, please place the ID here. An example of a group ID is: 343029528563548162 (Only include the numerical ID and no other formatting)', ], 'discordWebHookAvatar' => [ 'FriendlyName' => 'Webhook Avatar Image (Optional)', 'Type' => 'text', 'Description' => 'Your desired Webhook Avatar. Please make sure you enter a direct link to the image (E.G. https://example.com/avatar.png )', ], ]; } /** * Validate settings for notification module * * This method will be invoked prior to saving any settings via the UI. * * @param array $settings Settings present in configuration modal * * @return boolean * @throws Exception webhookURL value is missing */ public function testConnection($settings): bool { $webhookURL = $settings['webhookURL'] ?? ''; if (empty($webhookURL)) { throw new Exception("You must provide a webhook URL"); } // Validate webhook URL format if (!filter_var($webhookURL, FILTER_VALIDATE_URL) || !preg_match('/^https:\/\/discord(app)?\.com\/api\/webhooks\//', $webhookURL)) { throw new Exception("Invalid Discord webhook URL format"); } return true; } /** * The individual customisable settings for a notification. * * @return array */ public function notificationSettings() { return [ 'message' => [ 'FriendlyName' => 'Customise Message (Optional)', 'Type' => 'text', 'Description' => 'Allows you to customise the primary display message shown in the notification.', ], 'webhookURL' => [ 'FriendlyName' => 'Discord Webhook URL (Optional)', 'Type' => 'text', 'Description' => 'Override the Discord Webhook URL for this notification to send to a different channel.', ], 'discordColor' => [ 'FriendlyName' => 'Message Colour (Optional)', 'Type' => 'text', 'Description' => 'Override the side line colour for the message. The color code format within this script is standard hex. Exclude the beginning # character if one is present.', 'Placeholder' => '73CB0B', ], 'discordGroupID' => [ 'FriendlyName' => 'Notification Role ID (Optional)', 'Type' => 'text', 'Description' => 'If you\'d like to have a specific group pinged for this message type, please place the ID here (this is in addition to the module setting). An example of a group ID is: 343029528563548162 (Only include the numerical ID and no other formatting)', ], ]; } /** * The option values available for a 'dynamic' Type notification setting */ public function getDynamicField($fieldName, $settings) { return []; } /** * Deliver notification * * This method is invoked when rule criteria are met. * * @param NotificationInterface $notification A notification to send * @param array $moduleSettings Configured settings of the notification module * @param array $notificationSettings Configured notification settings set by the triggered rule * */ public function sendNotification(NotificationInterface $notification, $moduleSettings, $notificationSettings) { // Determine message body $messageBody = $notification->getMessage(); if (!empty($notificationSettings["message"])) { $messageBody = $notificationSettings["message"]; } // Determine webhook URL if (!empty($notificationSettings["webhookURL"])) { $webhookURL = $notificationSettings["webhookURL"]; } else { $webhookURL = $moduleSettings["webhookURL"]; } // Determine color if (!empty($notificationSettings["discordColor"])) { $discordColor = hexdec($notificationSettings["discordColor"]); } elseif (!empty($moduleSettings["discordColor"])) { $discordColor = hexdec($moduleSettings["discordColor"]); } else { $discordColor = 7588619; // Default blue color } // Create embed $embed = (new Embed()) ->title(\WHMCS\Input\Sanitize::decode($notification->getTitle())) ->url($notification->getUrl()) ->description($messageBody) ->timestamp(date(\DateTime::ISO8601)) ->color($discordColor) ->footer("WHMCS Notification"); // Add notification attributes as fields foreach ($notification->getAttributes() as $attribute) { $value = $attribute->getValue(); if ($attribute->getUrl()) { $value = "[" . $value . "](" . $attribute->getUrl() . ")"; } $embed->addField((new Field()) ->name($attribute->getLabel()) ->value($value)); } // Prepare role mentions $discordGroupID = ''; $discordGroupIDNotificationSpecific = ''; if (!empty($moduleSettings["discordGroupID"])) { $discordGroupID = "<@&" . $moduleSettings["discordGroupID"] . ">"; } if (!empty($notificationSettings["discordGroupID"])) { $discordGroupIDNotificationSpecific = "<@&" . $notificationSettings["discordGroupID"] . ">"; } // Prepare other settings $companyName = $moduleSettings["companyName"] ?? ''; $discordWebHookAvatar = $moduleSettings["discordWebHookAvatar"] ?? ''; // Create message $message = (new Message()) ->content($discordGroupID . $discordGroupIDNotificationSpecific) ->username($companyName) ->avatarUrl($discordWebHookAvatar) ->embed($embed); // Send the notification $this->call($moduleSettings, $webhookURL, $message->toArray()); } /** * Send HTTP request to Discord webhook * * @param array $settings Module settings * @param string $url Webhook URL * @param array $postdata Data to send * @param bool $throwOnError Whether to throw exception on error * @return mixed * @throws Exception */ protected function call(array $settings, $url, array $postdata = array(), $throwOnError = true) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_HTTPHEADER, array( 'Content-Type: application/json' )); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($postdata)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_TIMEOUT, 10); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1); curl_setopt($ch, CURLOPT_USERAGENT, 'WHMCS Discord Notification Module'); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $curlError = curl_error($ch); curl_close($ch); // Handle cURL errors if ($response === false || !empty($curlError)) { logModuleCall('discord', 'Notification Sending Failed - cURL Error', $postdata, $curlError, null); if ($throwOnError) { throw new Exception("cURL Error: " . $curlError); } return false; } $decoded = json_decode($response, true); // Discord webhook returns 204 on success if ($httpCode == 204) { logModuleCall('discord', 'Notification Successfully Sent', $postdata, $response, $decoded); } else { logModuleCall('discord', 'Notification Sending Failed', $postdata, $response, $decoded); if ($throwOnError) { $errorMessage = "HTTP Error " . $httpCode; if (isset($decoded['message'])) { $errorMessage .= ": " . $decoded['message']; } throw new Exception($errorMessage); } } return $decoded; } }