Skip to content
Open
Changes from 2 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
113 changes: 73 additions & 40 deletions src/Discord/Discord.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
use Ratchet\Client\Connector;
use Ratchet\Client\WebSocket;
use Ratchet\RFC6455\Messaging\Message;
use React\Dns\Config\Config as DnsConfig;
use React\EventLoop\Loop;
use React\EventLoop\LoopInterface;
use React\EventLoop\TimerInterface;
Expand Down Expand Up @@ -95,6 +96,29 @@
* @property PrivateChannelRepository $private_channels
* @property SoundRepository $sounds
* @property UserRepository $users
*
* @property array $options
* @property string $options['token']
* @property LoggerInterface $options['logger']
* @property LoopInterface $options['loop']
* @property array|bool $options['loadAllMembers']
* @property array $options['disabledEvents']
* @property bool $options['storeMessages']
* @property array|bool $options['retrieveBans']
* @property int|null $options['large_threshold']
* @property array|null $options['shard']
* @property int|null $options['shard_id']
* @property int|null $options['num_shards']
* @property int|null $options['shardId']
* @property int|null $options['shardCount']
* @property array|null $options['presence']
* @property int $options['intents']
* @property array $options['socket_options']
* @property DnsConfig|string $options['dnsConfig']
* @property string[] $options['cache']
* @property string $options['collection']
* @property bool $options['useTransportCompression']
* @property bool $options['usePayloadCompression']
*/
class Discord
{
Expand Down Expand Up @@ -1711,7 +1735,19 @@ protected function resolveOptions(array $options = []): array
])
->setAllowedTypes('token', 'string')
->setAllowedTypes('logger', ['null', LoggerInterface::class])
->setNormalizer('logger', function ($options, $value) {
if (null === $options['logger']) {
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

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

The normalizer is checking $options['logger'] instead of $value. In OptionsResolver normalizers, the first parameter is the full options array and the second parameter is the current value of the option being normalized. This should be if (null === $value) to correctly check if a logger was provided.

Suggested change
if (null === $options['logger']) {
if (null === $value) {

Copilot uses AI. Check for mistakes.
$streamHandler = new StreamHandler('php://stdout', Level::Debug);
$lineFormatter = new LineFormatter(null, null, true, true);
$streamHandler->setFormatter($lineFormatter);

return new Monolog('DiscordPHP', [$streamHandler]);
}

return $value;
})
->setAllowedTypes('loop', LoopInterface::class)
->setNormalizer('loop', fn ($options, $value) => $value ?? Loop::get())
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

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

The 'loop' option needs a default value in the setDefaults() configuration. Currently, 'loop' is defined but has no default, which means if a user doesn't provide it, the option will be undefined and fail the LoopInterface type check before reaching the normalizer. Add 'loop' => null, to the setDefaults array around line 1717, and update setAllowedTypes on line 1749 to include 'null' as an allowed type: ->setAllowedTypes('loop', ['null', LoopInterface::class]).

Copilot uses AI. Check for mistakes.
->setAllowedTypes('loadAllMembers', ['bool', 'array'])
->setAllowedTypes('disabledEvents', 'array')
->setAllowedTypes('storeMessages', 'bool')
Expand All @@ -1724,8 +1760,44 @@ protected function resolveOptions(array $options = []): array
->setAllowedTypes('shardCount', ['null', 'int'])
->setAllowedTypes('presence', ['null', 'array'])
->setAllowedTypes('intents', ['array', 'int'])
->setNormalizer('intents', function ($options, $value) {
if (is_array($value)) {
$intent = 0;
$validIntents = Intents::getValidIntents();

foreach ($value as $idx => $i) {
if (! in_array($i, $validIntents)) {
throw new IntentException('Given intent at index '.$idx.' is invalid.');
}

$intent |= $i;
}

$value = $intent;
}

return (int) $value;
})
->setAllowedTypes('socket_options', 'array')
->setAllowedTypes('dnsConfig', ['string', \React\Dns\Config\Config::class])
->setNormalizer('socket_options', function ($options, $value) {
// Discord doesn't currently support IPv6
// This prevents xdebug from catching exceptions when trying to fetch IPv6
// for Discord
$value['happy_eyeballs'] = false;

return $value;
})
->setAllowedTypes('dnsConfig', ['string', DnsConfig::class])
->setNormalizer('dnsConfig', function ($options, $value) {
if (null === $value) {
$value = DnsConfig::loadSystemConfigBlocking();
if (! $value->nameservers) {
$value->nameservers[] = '8.8.8.8';
}
}

return $value;
})
Comment on lines +1777 to +1787
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

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

The 'dnsConfig' option needs a default value in the setDefaults() configuration. Currently, 'dnsConfig' is defined but has no default, which means if a user doesn't provide it, the option will be undefined and the normalizer's null check won't be reached. Add 'dnsConfig' => null, to the setDefaults array around line 1717, and update setAllowedTypes on line 1790 to include 'null' as an allowed type: ->setAllowedTypes('dnsConfig', ['null', 'string', DnsConfig::class]).

Copilot uses AI. Check for mistakes.
->setAllowedTypes('collection', 'string')
->setNormalizer('collection', function ($options, $value) {
if (is_string($value) && class_exists($value) && is_subclass_of($value, ExCollectionInterface::class)) {
Expand All @@ -1751,50 +1823,11 @@ protected function resolveOptions(array $options = []): array

$options = $resolver->resolve($options);

$options['loop'] ??= Loop::get();

if (null === $options['logger']) {
$streamHandler = new StreamHandler('php://stdout', Level::Debug);
$lineFormatter = new LineFormatter(null, null, true, true);
$streamHandler->setFormatter($lineFormatter);
$logger = new Monolog('DiscordPHP', [$streamHandler]);
$options['logger'] = $logger;
}

if (! isset($options['dnsConfig'])) {
$dnsConfig = \React\Dns\Config\Config::loadSystemConfigBlocking();
if (! $dnsConfig->nameservers) {
$dnsConfig->nameservers[] = '8.8.8.8';
}

$options['dnsConfig'] = $dnsConfig;
}

if (is_array($options['intents'])) {
$intent = 0;
$validIntents = Intents::getValidIntents();

foreach ($options['intents'] as $idx => $i) {
if (! in_array($i, $validIntents)) {
throw new IntentException('Given intent at index '.$idx.' is invalid.');
}

$intent |= $i;
}

$options['intents'] = $intent;
}

if ($options['loadAllMembers'] && ! ($options['intents'] & Intents::GUILD_MEMBERS)) {
throw new IntentException('You have enabled the `loadAllMembers` option but have not enabled the required `GUILD_MEMBERS` intent.'.
'See the documentation on the `loadAllMembers` property for more information: http://discord-php.github.io/DiscordPHP/#basics');
}

// Discord doesn't currently support IPv6
// This prevents xdebug from catching exceptions when trying to fetch IPv6
// for Discord
$options['socket_options']['happy_eyeballs'] = false;

return $options;
}

Expand Down