Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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