diff --git a/.gitignore b/.gitignore index 8dc10f7..84ccda3 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ /vendor /test.php /bin/php-cs-fixer +/.php-cs-fixer.cache diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php new file mode 100644 index 0000000..6386b3d --- /dev/null +++ b/.php-cs-fixer.php @@ -0,0 +1,133 @@ + ['syntax' => 'short'], + 'binary_operator_spaces' => [ + 'default' => 'single_space', + 'operators' => ['=>' => null] + ], + 'blank_line_after_namespace' => true, + 'blank_line_after_opening_tag' => true, + 'blank_line_before_statement' => [ + 'statements' => ['return'] + ], + 'braces' => true, + 'cast_spaces' => true, + 'class_definition' => true, + 'concat_space' => [ + 'spacing' => 'none' + ], + 'declare_equal_normalize' => true, + 'declare_strict_types' => true, + 'elseif' => true, + 'encoding' => true, + 'full_opening_tag' => true, + 'fully_qualified_strict_types' => true, // added by Shift + 'function_declaration' => true, + 'function_typehint_space' => true, + 'heredoc_to_nowdoc' => true, + 'include' => true, + 'increment_style' => ['style' => 'post'], + 'indentation_type' => true, + 'linebreak_after_opening_tag' => true, + 'line_ending' => true, + 'lowercase_cast' => true, + 'lowercase_keywords' => true, + 'lowercase_static_reference' => true, // added from Symfony + 'magic_method_casing' => true, // added from Symfony + 'magic_constant_casing' => true, + 'method_argument_space' => true, + 'native_function_casing' => true, + 'no_alias_functions' => true, + 'no_extra_blank_lines' => [ + 'tokens' => [ + 'extra', + 'throw', + 'use', + 'use_trait', + ] + ], + 'no_blank_lines_after_class_opening' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_closing_tag' => true, + 'no_empty_phpdoc' => true, + 'no_empty_statement' => true, + 'no_leading_import_slash' => true, + 'no_leading_namespace_whitespace' => true, + 'no_mixed_echo_print' => [ + 'use' => 'echo' + ], + 'no_multiline_whitespace_around_double_arrow' => true, + 'multiline_whitespace_before_semicolons' => [ + 'strategy' => 'no_multi_line' + ], + 'no_short_bool_cast' => true, + 'no_singleline_whitespace_before_semicolons' => true, + 'no_spaces_after_function_name' => true, + 'no_spaces_around_offset' => true, + 'no_spaces_inside_parenthesis' => true, + 'no_trailing_comma_in_list_call' => true, + 'no_trailing_comma_in_singleline_array' => true, + 'no_trailing_whitespace' => true, + 'no_trailing_whitespace_in_comment' => true, + 'no_unneeded_control_parentheses' => true, + 'no_unreachable_default_argument_value' => true, + 'no_unused_imports' => true, + 'no_useless_return' => true, + 'no_whitespace_before_comma_in_array' => true, + 'no_whitespace_in_blank_line' => true, + 'normalize_index_brace' => true, + 'not_operator_with_successor_space' => true, + 'object_operator_without_whitespace' => true, + 'ordered_imports' => ['sort_algorithm' => 'alpha'], + 'phpdoc_indent' => true, + 'phpdoc_no_access' => true, + 'phpdoc_no_package' => true, + 'phpdoc_no_useless_inheritdoc' => true, + 'phpdoc_scalar' => true, + 'phpdoc_single_line_var_spacing' => true, + 'phpdoc_summary' => true, + 'phpdoc_to_comment' => true, + 'phpdoc_trim' => true, + 'phpdoc_types' => true, + 'phpdoc_var_without_name' => true, + 'self_accessor' => true, + 'short_scalar_cast' => true, + 'simplified_null_return' => true, + 'single_blank_line_at_eof' => true, + 'single_blank_line_before_namespace' => true, + 'single_class_element_per_statement' => true, + 'single_import_per_statement' => true, + 'single_line_after_imports' => true, + 'single_line_comment_style' => [ + 'comment_types' => ['hash'] + ], + 'single_quote' => true, + 'space_after_semicolon' => true, + 'standardize_not_equals' => true, + 'switch_case_semicolon_to_colon' => true, + 'switch_case_space' => true, + 'ternary_operator_spaces' => true, + 'trailing_comma_in_multiline' => true, + 'trim_array_spaces' => true, + 'unary_operator_spaces' => true, + 'visibility_required' => [ + 'elements' => ['method', 'property'] + ], + 'whitespace_after_comma_in_array' => true, +]; + +$finder = Finder::create() + ->in('src') + ->name('*.php') + ->ignoreDotFiles(true) + ->ignoreVCS(true); + +return (new Config()) + ->setFinder($finder) + ->setRules($rules) + ->setRiskyAllowed(true) + ->setUsingCache(true); diff --git a/.php_cs b/.php_cs deleted file mode 100644 index c295753..0000000 --- a/.php_cs +++ /dev/null @@ -1,13 +0,0 @@ -in(__DIR__.'/src'); -return PhpCsFixer\Config::create() - ->setRules([ - '@Symfony' => true, - 'ordered_imports' => true, - 'array_syntax' => [ - 'syntax' => 'short' - ], - 'ordered_class_elements' => ['use_trait', 'constant_public', 'constant_protected', 'constant_private', 'property_public', 'property_protected', 'property_private', 'construct', 'destruct', 'magic', 'phpunit', 'method_public', 'method_protected', 'method_private'], - ]) - ->setFinder($finder); diff --git a/Readme.md b/Readme.md index e4f2fe9..7fcc35e 100644 --- a/Readme.md +++ b/Readme.md @@ -15,7 +15,7 @@ $message = (new DiscordTextMessage()) ->setUsername('Webhook Test'); $webhook = new DiscordWebhook('https://discordapp.com/api/webhooks/SomeWebHook'); -$webhook->send($message); +$messageData = $webhook->send($message); ``` Sending an embed message @@ -30,7 +30,35 @@ $message = (new DiscordEmbedMessage()) ->setImage('https://example.com/someimage.png'); $webhook = new DiscordWebhook('https://discordapp.com/api/webhooks/SomeWebHook'); -$webhook->send($message); +$messageData = $webhook->send($message); ``` -Sending messages via the webhook class will automatically handle rate limits. \ No newline at end of file +Updating a message +```php +$messageId = '12345'; +$message = (new DiscordTextMessage()) + ->setContent('Hello World') + ->setAvatar('https://example.com/avatar.png') + ->setUsername('Webhook Test'); + +$webhook = new DiscordWebhook('https://discordapp.com/api/webhooks/SomeWebHook'); +$messageData = $webhook->update($messageId, $message); +``` + +Deleting a message +```php +$messageId = '12345'; + +$webhook = new DiscordWebhook('https://discordapp.com/api/webhooks/SomeWebHook'); +$webhook->delete($messageId); +``` + +Getting a message +```php +$messageId = '12345'; + +$webhook = new DiscordWebhook('https://discordapp.com/api/webhooks/SomeWebHook'); +$messageData = $webhook->get($messageId); +``` + +Ratelimits are automatically handled for all calls. diff --git a/composer.json b/composer.json index 1bf8a1b..81a64d1 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ } ], "require": { - "php": "^7.4|^8.0", + "php": "^8.1", "ext-curl": "*", "ext-json": "*" }, @@ -21,13 +21,13 @@ }, "scripts": { "cgl": [ - "bin/php-cs-fixer fix --config .php_cs" + "bin/php-cs-fixer fix --config .php-cs-fixer.php" ] }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.19" - }, "config": { "bin-dir": "bin/" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.8" } } diff --git a/src/Exception/DiscordInvalidResponseException.php b/src/Exception/DiscordInvalidResponseException.php index b6696a4..8d5ad92 100644 --- a/src/Exception/DiscordInvalidResponseException.php +++ b/src/Exception/DiscordInvalidResponseException.php @@ -6,4 +6,21 @@ class DiscordInvalidResponseException extends \Exception { + private array $errorData; + + public function __construct(string $message = '', int $code = 0, ?\Throwable $previous = null, array $errorData = []) + { + parent::__construct($message, $code, $previous); + $this->errorData = $errorData; + } + + public function getErrorData(): array + { + return $this->errorData; + } + + public function getStatusCode(): int + { + return $this->code; + } } diff --git a/src/Message/AbstractDiscordMessage.php b/src/Message/AbstractDiscordMessage.php index 31a7cd9..da0ede6 100644 --- a/src/Message/AbstractDiscordMessage.php +++ b/src/Message/AbstractDiscordMessage.php @@ -1,12 +1,65 @@ content = $content; + + return $this; + } + + public function setAvatar(string $avatar): static + { + $this->avatar = $avatar; + + return $this; + } + + public function setUsername(string $username): static + { + $this->username = $username; + + return $this; + } + + public function getContent(): ?string + { + return $this->content; + } + + public function getAvatar(): ?string + { + return $this->avatar; + } + + public function getUsername(): ?string + { + return $this->username; + } + + public function isTts(): bool + { + return $this->tts; + } + + public function setTts(bool $tts): static + { + $this->tts = $tts; + + return $this; + } - abstract public function jsonSerialize(); + abstract public function jsonSerialize(): array; } diff --git a/src/Message/DiscordEmbedMessage.php b/src/Message/DiscordEmbedMessage.php index d73d64b..c56dff3 100644 --- a/src/Message/DiscordEmbedMessage.php +++ b/src/Message/DiscordEmbedMessage.php @@ -8,9 +8,6 @@ class DiscordEmbedMessage extends AbstractDiscordMessage { - private ?string $content = null; - private ?string $avatar = null; - private ?string $username = null; private ?string $title = null; private ?string $description = null; private ?string $url = null; @@ -24,9 +21,8 @@ class DiscordEmbedMessage extends AbstractDiscordMessage private ?string $author_url = null; private ?string $author_icon = null; private array $fields = []; - private bool $tts = false; - public function toArray(): array + public function jsonSerialize(): array { return [ 'username' => $this->username, @@ -36,7 +32,7 @@ public function toArray(): array 'embeds' => [[ 'title' => $this->title, 'description' => $this->description, - 'timestamp' => null === $this->timestamp ? null : $this->timestamp->format('Y-m-d\TH:i:sP'), + 'timestamp' => $this->timestamp?->format('Y-m-d\TH:i:sP'), 'url' => $this->url, 'color' => $this->color, 'author' => [ @@ -59,48 +55,12 @@ public function toArray(): array ]; } - public function getContent(): ?string - { - return $this->content; - } - - public function setContent(string $content): self - { - $this->content = $content; - - return $this; - } - - public function getAvatar(): ?string - { - return $this->avatar; - } - - public function setAvatar(string $avatar): self - { - $this->avatar = $avatar; - - return $this; - } - - public function getUsername(): ?string - { - return $this->username; - } - - public function setUsername(string $username): self - { - $this->username = $username; - - return $this; - } - public function getTitle(): ?string { return $this->title; } - public function setTitle(string $title): self + public function setTitle(string $title): static { $this->title = $title; @@ -112,7 +72,7 @@ public function getDescription(): ?string return $this->description; } - public function setDescription(string $description): self + public function setDescription(string $description): static { $this->description = $description; @@ -124,7 +84,7 @@ public function getUrl(): ?string return $this->url; } - public function setUrl(string $url): self + public function setUrl(string $url): static { $this->url = $url; @@ -136,7 +96,7 @@ public function getColor(): ?int return $this->color; } - public function setColor(int $color): self + public function setColor(int $color): static { $this->color = $color; @@ -148,7 +108,7 @@ public function getTimestamp(): ?DateTimeInterface return $this->timestamp; } - public function setTimestamp(DateTimeInterface $timestamp): self + public function setTimestamp(DateTimeInterface $timestamp): static { $this->timestamp = $timestamp; @@ -160,7 +120,7 @@ public function getFooterIcon(): ?string return $this->footer_icon; } - public function setFooterIcon(string $footer_icon): self + public function setFooterIcon(string $footer_icon): static { $this->footer_icon = $footer_icon; @@ -172,7 +132,7 @@ public function getFooterText(): ?string return $this->footer_text; } - public function setFooterText(string $footer_text): self + public function setFooterText(string $footer_text): static { $this->footer_text = $footer_text; @@ -184,7 +144,7 @@ public function getThumbnail(): ?string return $this->thumbnail; } - public function setThumbnail(string $thumbnail): self + public function setThumbnail(string $thumbnail): static { $this->thumbnail = $thumbnail; @@ -196,7 +156,7 @@ public function getImage(): ?string return $this->image; } - public function setImage(string $image): self + public function setImage(string $image): static { $this->image = $image; @@ -208,7 +168,7 @@ public function getAuthorName(): ?string return $this->author_name; } - public function setAuthorName(string $author_name): self + public function setAuthorName(string $author_name): static { $this->author_name = $author_name; @@ -220,7 +180,7 @@ public function getAuthorUrl(): ?string return $this->author_url; } - public function setAuthorUrl(string $author_url): self + public function setAuthorUrl(string $author_url): static { $this->author_url = $author_url; @@ -232,7 +192,7 @@ public function getAuthorIcon(): ?string return $this->author_icon; } - public function setAuthorIcon(string $author_icon): self + public function setAuthorIcon(string $author_icon): static { $this->author_icon = $author_icon; @@ -244,31 +204,14 @@ public function getFields(): array return $this->fields; } - public function setFields(array $fields): self + public function setFields(array $fields): static { $this->fields = $fields; return $this; } - public function isTts(): bool - { - return $this->tts; - } - - public function setTts(bool $tts): self - { - $this->tts = $tts; - - return $this; - } - - public function toJson(): string - { - return json_encode($this->toArray(), JSON_THROW_ON_ERROR); - } - - public function addField(string $title, string $value, bool $inLine = false): self + public function addField(string $title, string $value, bool $inLine = false): static { $this->fields[] = [ 'name' => $title, @@ -297,7 +240,7 @@ public function getField(string $title): array return $this->findFieldByTitle($title); } - public function setColorWithHexValue(string $hexValue): self + public function setColorWithHexValue(string $hexValue): static { $hexValue = str_replace('#', '', $hexValue); $this->color = hexdec($hexValue); @@ -305,11 +248,6 @@ public function setColorWithHexValue(string $hexValue): self return $this; } - public function jsonSerialize(): array - { - return $this->toArray(); - } - protected function findFieldByTitle(string $title): array { foreach ($this->fields as $field) { diff --git a/src/Message/DiscordTextMessage.php b/src/Message/DiscordTextMessage.php index 29f87f1..e19319c 100644 --- a/src/Message/DiscordTextMessage.php +++ b/src/Message/DiscordTextMessage.php @@ -6,60 +6,7 @@ class DiscordTextMessage extends AbstractDiscordMessage { - private ?string $content; - private ?string $avatar; - private ?string $username; - private bool $tts = false; - - public function setContent(string $content): self - { - $this->content = $content; - - return $this; - } - - public function setAvatar(string $avatar): self - { - $this->avatar = $avatar; - - return $this; - } - - public function setUsername(string $username): self - { - $this->username = $username; - - return $this; - } - - public function getContent(): ?string - { - return $this->content; - } - - public function getAvatar(): ?string - { - return $this->avatar; - } - - public function getUsername(): ?string - { - return $this->username; - } - - public function isTts(): bool - { - return $this->tts; - } - - public function setTts(bool $tts): self - { - $this->tts = $tts; - - return $this; - } - - public function toArray(): array + public function jsonSerialize(): array { return [ 'content' => $this->content, @@ -68,9 +15,4 @@ public function toArray(): array 'tts' => $this->tts, ]; } - - public function jsonSerialize(): array - { - return $this->toArray(); - } } diff --git a/src/Webhook/DiscordWebhook.php b/src/Webhook/DiscordWebhook.php index b59bfc5..562c1ca 100644 --- a/src/Webhook/DiscordWebhook.php +++ b/src/Webhook/DiscordWebhook.php @@ -14,41 +14,75 @@ class DiscordWebhook */ public const DISCORD_WEBHOOK_URL_PREFIX = 'https://discordapp.com/api/webhooks/'; - private string $webhookUrl; + public function __construct(private readonly string $webhookUrl) + { + } - public function __construct(string $webhookUrl) + public function send(AbstractDiscordMessage $message, bool $wait = true): ?array { - $this->webhookUrl = $webhookUrl; + return $this->runCurl( + $this->webhookUrl.'?wait='.$wait, + 'POST', + $message + ); } - /** - * @throws DiscordInvalidResponseException - */ - public function send(AbstractDiscordMessage $message): int + public function get(string $messageId): array + { + return $this->runCurl( + $this->webhookUrl.'/messages/'.$messageId, + 'GET', + ); + } + + public function update(string $messageId, AbstractDiscordMessage $message): array + { + return $this->runCurl( + $this->webhookUrl.'/messages/'.$messageId, + 'PATCH', + $message + ); + } + + public function delete(string $messageId): void + { + $this->runCurl( + $this->webhookUrl.'/messages/'.$messageId, + 'DELETE', + ); + } + + private function runCurl(string $url, string $method, ?AbstractDiscordMessage $postData = null, array $headers = ['content-type: application/json']): ?array { $sent = false; - while (!$sent) { - $ch = curl_init($this->webhookUrl); - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); - curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($message, JSON_THROW_ON_ERROR)); + while (! $sent) { + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + if ($postData !== null) { + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($postData, JSON_THROW_ON_ERROR)); + } curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_HTTPHEADER, ['content-type: application/json']); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); $response = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if (429 === $code) { - $response = json_decode($response, false, 512, JSON_THROW_ON_ERROR); - usleep($response->retry_after * 1000); + $response = json_decode($response, true, 512, JSON_THROW_ON_ERROR); + usleep($response['retry_after'] * 1000); } else { $sent = true; if ($code < 200 || $code >= 400) { - throw new DiscordInvalidResponseException('Discord Webhook returned invalid response: '.$code.'.', $code); + $response = json_decode($response, true, 512, JSON_THROW_ON_ERROR); + throw new DiscordInvalidResponseException('Discord Webhook returned invalid response: '.$code.'.', $code, errorData: $response); + } + if (! empty($response)) { + return json_decode($response, true, 512, JSON_THROW_ON_ERROR); } } } - return $code; + return null; } }