Skip to content

Commit

Permalink
Showing 9 changed files with 237 additions and 23 deletions.
50 changes: 29 additions & 21 deletions src/Discord.php
Original file line number Diff line number Diff line change
@@ -48,6 +48,8 @@ class Discord
private Http $http;
public Rest $rest;

private Bucket $activityBucket;

public function __construct(
private string $token,
$options = [],
@@ -171,26 +173,26 @@ private function handlePayload(Payload $payload)
$this->handleEvent($payload);
break;

/**
* Resume event
*/
/**
* Resume event
*/
case 7:
$this->reconnect(true, true);
break;

/**
* Invalid session
*/
/**
* Invalid session
*/
case 9:
$this->reconnect(
false,
isset($payload->d) && $payload->d === true
);
break;

/**
* Hello event
*/
/**
* Hello event
*/
case 10:
if ($this->shouldIdentify) {
$this->identify();
@@ -201,9 +203,9 @@ private function handlePayload(Payload $payload)
$this->handleHello($this->mapper->map($payload->d, new Hello()));
break;

/**
* Acknowledgement of heartbeat
*/
/**
* Acknowledgement of heartbeat
*/
case 11:
$this->cancelScheduledReconnect();
break;
@@ -262,17 +264,23 @@ public function updatePresence(
bool $afk = false,
?int $since = null
): void {
$presenceUpdate = [
'status' => $status->value,
'activities' => array_map(fn (ActivityBuilder $builder) => $builder->get(), $activities),
'afk' => $afk,
];

if (!is_null($since)) {
$presenceUpdate['since'] = $since;
if (!isset($this->activityBucket)) {
$this->activityBucket = new Bucket($this->loop, 5, 21);
}

$this->sendPayload($presenceUpdate);
$this->activityBucket->run(function () use ($status, $activities, $afk, $since) {
$presenceUpdate = [
'status' => $status->value,
'activities' => array_map(fn (ActivityBuilder $builder) => $builder->get(), $activities),
'afk' => $afk,
];

if (!is_null($since)) {
$presenceUpdate['since'] = $since;
}

$this->sendPayload($presenceUpdate);
});
}

/**
13 changes: 13 additions & 0 deletions src/Enums/Gateway/ActivityType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Exan\Dhp\Enums\Gateway;

enum ActivityType: int
{
case GAME = 0;
case STREAMING = 1;
case LISTENING = 2;
case WATCHING = 3;
case CUSTOM = 4;
case COMPETING = 5;
}
2 changes: 1 addition & 1 deletion src/Parts/Emoji.php
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@ public function getPartial(): array
return [
'id' => $this->id,
'name' => isset($this->name) ? $this->name : null,
'animated' => isset($this->animated),
'animated' => isset($this->animated) && $this->animated,
];
}

45 changes: 44 additions & 1 deletion src/Websocket/Helpers/ActivityBuilder.php
Original file line number Diff line number Diff line change
@@ -2,10 +2,53 @@

namespace Exan\Dhp\Websocket\Helpers;

use Exan\Dhp\Enums\Gateway\ActivityType;
use Exan\Dhp\Parts\Emoji;

/**
* @see https://discord.com/developers/docs/topics/gateway-events#activity-object
*/
class ActivityBuilder
{
private $data = [];

public function setName(string $name): ActivityBuilder
{
$this->data['name'] = $name;

return $this;
}

/**
* @see https://discord.com/developers/docs/topics/gateway-events#activity-object-activity-types
*/
public function setType(ActivityType $type): ActivityBuilder
{
$this->data['type'] = $type->value;

return $this;
}

/**
* Only for streaming activity type
* Supports youtube & twitch
*/
public function setUrl(string $url): ActivityBuilder
{
$this->data['url'] = $url;

return $this;
}

public function setEmoji(Emoji $emoji): ActivityBuilder
{
$this->data['emoji'] = $emoji->getPartial();

return $this;
}

public function get(): array
{
return [];
return $this->data;
}
}
3 changes: 3 additions & 0 deletions tests/Discord/DiscordTestCase.php
Original file line number Diff line number Diff line change
@@ -44,6 +44,9 @@ protected function setUp(): void
$this->loop
);

$bucketMock = Mockery::mock('overload:Exan\Dhp\Bucket');
$bucketMock->shouldReceive('run')->andReturnUsing(fn ($fn) => $fn());

$websocketMock = Mockery::mock('overload:Exan\Dhp\Websocket');
$websocketMock->shouldReceive('on')->andReturnUsing(function (string $event, callable $handler) {
$this->websocketHandlers[$event] = $handler;
29 changes: 29 additions & 0 deletions tests/Discord/HandlesHeartbeatTest.php
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@

namespace Tests\Exan\Dhp\Discord;

use Exan\Dhp\Const\Events;
use Mockery;
use React\EventLoop\TimerInterface;
use Tests\Exan\Dhp\Discord\DiscordTestCase;
@@ -25,6 +26,8 @@ protected function setUp(): void

$this->loop->shouldReceive('addPeriodicTimer')->andReturnUsing(function ($interval, callable $callback) {
$callback();

return Mockery::mock(TimerInterface::class);
});

$this->timerInterface = Mockery::mock(TimerInterface::class);
@@ -88,4 +91,30 @@ public function testShouldNotCancelTimerIfNoneIsSet()

$this->loop->shouldNotHaveReceived('cancelTimer');
}

public function testShouldStopHeartBeatForReconnect()
{
$this->mockIncomingMessage([
'op' => 10,
'd' => [
'heartbeat_interval' => 20000
]
]);

$this->mockIncomingMessage([
'op' => 0,
't' => Events::READY,
's' => 1,
'd' => [
'session_id' => '::session id::',
'resume_gateway_url' => '::resume gateway url::'
]
]);

$this->mockIncomingMessage([
'op' => 7,
]);

$this->loop->shouldHaveReceived('cancelTimer');
}
}
69 changes: 69 additions & 0 deletions tests/Discord/MissedHeartBeatTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

declare(strict_types=1);

namespace Tests\Exan\Dhp\Discord;

use Exan\Dhp\Const\Events;
use Mockery;
use React\EventLoop\TimerInterface;
use Tests\Exan\Dhp\Discord\DiscordTestCase;

/**
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
*/
final class StopsHeartBeatsTest extends DiscordTestCase
{
protected function setUp(): void
{
parent::setUp();

$this->mockIncomingMessage([
'op' => 0,
't' => Events::READY,
's' => 1,
'd' => [
'session_id' => '::session id::',
'resume_gateway_url' => '::resume gateway url::'
]
]);

$this->loop->shouldReceive('addPeriodicTimer', 'addTimer')
->andReturnUsing(function ($interval, callable $callback) {
$callback();

return Mockery::mock(TimerInterface::class);
});

$this->loop->shouldReceive('cancelTimer');
}

public function testReconnectWhenHeartBeatNotAcknowledged()
{
$this->mockIncomingMessage([
'op' => 10,
'd' => [
'heartbeat_interval' => 20000
]
]);

$this->discord->websocket->shouldHaveReceived('close', [1001, 'reconnecting']);

$this->mockIncomingMessage([
'op' => 10,
'd' => [
'heartbeat_interval' => 20000,
],
]);

$this->assertMessageSent([
'op' => 6,
'd' => [
'token' => '::token::',
'session_id' => '::session id::',
'seq' => 1,
]
]);
}
}
8 changes: 8 additions & 0 deletions tests/Parts/EmojiTest.php
Original file line number Diff line number Diff line change
@@ -33,5 +33,13 @@ public function testPartialEmoji()
'name' => '::name::',
'animated' => true,
], $emoji->getPartial());

$emoji = Emoji::get('::id::', '::name::', false);

$this->assertEquals([
'id' => '::id::',
'name' => '::name::',
'animated' => false,
], $emoji->getPartial());
}
}
41 changes: 41 additions & 0 deletions tests/Websocket/Helpers/ActivityBuilderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace Tests\Exan\Dhp\Websocket\Helpers;

use Exan\Dhp\Enums\Gateway\ActivityType;
use Exan\Dhp\Parts\Emoji;
use Exan\Dhp\Websocket\Helpers\ActivityBuilder;
use PHPUnit\Framework\TestCase;

class ActivityBuilderTest extends TestCase
{
public function testSetName(): void
{
$activityBuilder = new ActivityBuilder();
$activityBuilder->setName('Test Activity');
$this->assertEquals(['name' => 'Test Activity'], $activityBuilder->get());
}

public function testSetType(): void
{
$activityBuilder = new ActivityBuilder();
$activityBuilder->setType(ActivityType::GAME);
$this->assertEquals(['type' => ActivityType::GAME->value], $activityBuilder->get());
}

public function testSetUrl(): void
{
$activityBuilder = new ActivityBuilder();
$activityBuilder->setUrl('https://www.test.com');
$this->assertEquals(['url' => 'https://www.test.com'], $activityBuilder->get());
}

public function testSetEmoji(): void
{
$activityBuilder = new ActivityBuilder();
$emoji = Emoji::get('::emoji id::', '::emoji name::');

$activityBuilder->setEmoji($emoji);
$this->assertEquals(['emoji' => $emoji->getPartial()], $activityBuilder->get());
}
}

0 comments on commit c47870f

Please sign in to comment.