Files
WHMCS-Discord-Notifications/modules/notifications/Discord/Discord.php
T
2025-05-31 00:44:18 +02:00

275 lines
10 KiB
PHP

<?php
namespace WHMCS\Module\Notification\Discord;
use WHMCS\Module\Contracts\NotificationModuleInterface;
use WHMCS\Module\Notification\DescriptionTrait;
use WHMCS\Notification\Contracts\NotificationInterface;
use WHMCS\Config\Setting;
use WHMCS\Exception;
// Include the required classes
require_once __DIR__ . '/lib/Embed.php';
require_once __DIR__ . '/lib/Field.php';
require_once __DIR__ . '/lib/Message.php';
/**
* Notification module for delivering notifications via Discord
*/
class Discord implements NotificationModuleInterface
{
use DescriptionTrait;
/**
* Constructor
*/
public function __construct()
{
$this->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;
}
}