Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"ext-event": "For a faster, and more performant loop.",
"ext-mbstring": "For accurate calculations of string length when handling non-english characters.",
"ext-fileinfo": "For function mime_content_type().",
"ext-zstd": "For Zstandard compression support.",
"laracord/laracord": "Provides Laracord integration for DiscordPHP."
},
"scripts": {
Expand Down
31 changes: 26 additions & 5 deletions src/Discord/Discord.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,14 @@
use React\Promise\PromiseInterface;
use React\Socket\Connector as SocketConnector;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Zstd\UnCompress\Context as ZstdContext;

use function React\Async\coroutine;
use function React\Promise\all;
use function React\Promise\reject;
use function React\Promise\resolve;
use function Zstd\uncompress_init;
use function Zstd\uncompress_add;

/**
* The Discord client class.
Expand Down Expand Up @@ -314,9 +317,16 @@ class Discord
/**
* zlib decompressor.
*
* @var \InflateContext|false
* @var \InflateContext|false Zlib decompression context
*/
protected $zlibDecompressor;
protected $zlibDecompressor = false;

/**
* zstd decompressor.
*
* @var ZstdContext|false Zstd decompression context when ext-zstd is available
*/
protected $zstdDecompressor = false;

/**
* Tracks the number of payloads the client has sent in the past 60 seconds.
Expand Down Expand Up @@ -739,7 +749,14 @@ public function handleWsMessage(Message $message): void
$payload = $message->getPayload();

if ($message->isBinary()) {
if ($this->zlibDecompressor) {
if ($this->zstdDecompressor !== false) {
$decompressed = uncompress_add($this->zstdDecompressor, $payload);
if ($decompressed !== false) {
$this->processWsMessage($decompressed);
} else {
$this->logger->error('failed to decompress zstd payload', ['payload' => $payload, 'payload hex' => bin2hex($payload)]);
}
} elseif ($this->zlibDecompressor !== false) {
$this->payloadBuffer .= $payload;

if ($message->getPayloadLength() < 4 || substr($payload, -4) !== "\x00\x00\xff\xff") {
Expand Down Expand Up @@ -1652,10 +1669,14 @@ protected function buildParams(Deferred $deferred, string $gateway, ?SessionStar
];

if ($this->useTransportCompression) {
if ($this->zlibDecompressor = inflate_init(ZLIB_ENCODING_DEFLATE)) {
// Prefer zstd-stream if available (better compression), fallback to zlib-stream
if (extension_loaded('zstd') && ($this->zstdDecompressor = uncompress_init())) {
$params['compress'] = 'zstd-stream';
$this->logger->debug('using zstd-stream compression');
} elseif ($this->zlibDecompressor = inflate_init(ZLIB_ENCODING_DEFLATE)) {
$params['compress'] = 'zlib-stream';
$this->logger->debug('using zlib-stream compression');
Comment on lines 1671 to +1678
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New zstd-stream selection and decompression behavior isn’t covered by automated tests. Consider adding a unit test that verifies (a) buildParams prefers zstd-stream when available and falls back to zlib-stream otherwise, and (b) handleWsMessage routes binary payloads through the selected decompressor (you may need to wrap the extension checks/calls to make this testable without ext-zstd in CI).

Copilot uses AI. Check for mistakes.
}
// @todo: add support for zstd-stream
}

$query = http_build_query($params);
Expand Down