Skip to content

Commit

Permalink
Poll permissions, events, and other related changes (#1226)
Browse files Browse the repository at this point in the history
* ✨ Implement the Poll object `Parts`

* 🧑‍💻 Add a `poll` attribute to the `Message` part

* 🧑‍💻 Add poll support to the `MessageBuilder`

* ✨ Add event support to polls

* 🧑‍💻 Allow passing an array to `Poll::addAnswer()`

* 🎨 Clean up classes

* 🎨 Update the poll expire endpoint constant

* 🎨 Improve docblock wording

* 🚚 Move the `Poll` object part to the `Channel` namespace
* 🚚 Move the `Poll` create object art to the `Poll` namespace

* 🎨 Improve `Poll::addAnswer` when passed an array

* 🎨 Fix total answer count in `Poll:addAnswer`

* 🎨 Push answers into a collection

* ♻ Move `Poll::getAnswerVoters()` to `PollAnswer::getVoters()`

* 🎨 Improve docblock wording

* ⏪ Revert version change

* 🩹 Fix return types

* 🩹 Remove unused prop docblock

* 🎨 Update `PollAnswer::getVoters` to be uniform with `Reaction::getUsers`

* 🚧 Utilize caching in the `MESSAGE_POLL_VOTE_ADD` and `MESSAGE_POLL_VOTE_REMOVE` events

* 🔧 Update the endpoint for `PollAnswer::getVoters`

* 🚧 Create a repository for poll answers
* 🎨 Add docblock for event fillable attributes
* 🎨 Improve return types
* 🎨 Rename `Poll::end` to `Poll::expire` for parity with the Discord API

* 🎨 Remove unnecessary linebreak

* 🔧 Update the `Poll::expire` endpoint

* 🎨 Handle setting the `answers` attribute

---------

Co-authored-by: Brandon <[email protected]>
  • Loading branch information
valzargaming and Log1x authored Sep 27, 2024
1 parent af54bcf commit e584be6
Show file tree
Hide file tree
Showing 17 changed files with 910 additions and 5 deletions.
25 changes: 20 additions & 5 deletions src/Discord/Builders/MessageBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Discord\Http\Exceptions\RequestFailedException;
use Discord\Parts\Channel\Attachment;
use Discord\Parts\Channel\Message;
use Discord\Parts\Channel\Poll\Poll;
use Discord\Parts\Embed\Embed;
use Discord\Parts\Guild\Sticker;
use JsonSerializable;
Expand Down Expand Up @@ -61,6 +62,7 @@ class MessageBuilder implements JsonSerializable
* @var string|null
*/
private $avatar_url;

/**
* Whether the message is text-to-speech.
*
Expand Down Expand Up @@ -132,9 +134,9 @@ class MessageBuilder implements JsonSerializable
private $enforce_nonce;

/**
* A poll!
* The poll for the message.
*
* @var mixed
* @var Poll|null
*/
private $poll;

Expand Down Expand Up @@ -207,11 +209,11 @@ public function setEnforceNonce(bool $enforce_nonce = true): self
/**
* Sets the poll of the message.
*
* @param mixed $poll Poll object.
* @param Poll|null $poll
*
* @return $this
*/
public function setPoll($poll): self
public function setPoll(Poll|null $poll): self
{
$this->poll = $poll;

Expand Down Expand Up @@ -703,6 +705,11 @@ public function jsonSerialize(): ?array
$empty = false;
}

if (isset($this->poll)) {
$body['poll'] = $this->poll;
$empty = false;
}

if (isset($this->allowed_mentions)) {
$body['allowed_mentions'] = $this->allowed_mentions;
}
Expand Down Expand Up @@ -732,7 +739,15 @@ public function jsonSerialize(): ?array
if (isset($this->flags)) {
$body['flags'] = $this->flags;
} elseif ($empty) {
throw new RequestFailedException('You cannot send an empty message. Set the content or add an embed or file.');
throw new RequestFailedException('You cannot send an empty message. Set the content or add an embed, file or poll.');
}

if (isset($this->enforce_nonce)) {
$body['enforce_nonce'] = $this->enforce_nonce;
}

if (isset($this->poll)) {
$body['poll'] = $this->poll;
}

if (isset($this->enforce_nonce)) {
Expand Down
17 changes: 17 additions & 0 deletions src/Discord/Parts/Channel/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Carbon\Carbon;
use Discord\Builders\MessageBuilder;
use Discord\Helpers\Collection;
use Discord\Parts\Channel\Poll;
use Discord\Parts\Embed\Embed;
use Discord\Parts\Guild\Emoji;
use Discord\Parts\Guild\Role;
Expand Down Expand Up @@ -76,6 +77,7 @@
* @property Collection|Sticker[]|null $sticker_items Stickers attached to the message.
* @property int|null $position A generally increasing integer (there may be gaps or duplicates) that represents the approximate position of the message in a thread, it can be used to estimate the relative position of the message in a thread in company with `total_message_sent` on parent thread.
* @property object|null $role_subscription_data Data of the role subscription purchase or renewal that prompted this `ROLE_SUBSCRIPTION_PURCHASE` message.
* @property Poll|null $poll The poll attached to the message.
*
* @property-read bool $crossposted Message has been crossposted.
* @property-read bool $is_crosspost Message is a crosspost from another channel.
Expand Down Expand Up @@ -212,6 +214,7 @@ class Message extends Part
'sticker_items',
'position',
'role_subscription_data',
'poll',

// @internal
'guild_id',
Expand Down Expand Up @@ -715,6 +718,20 @@ protected function getStickerItemsAttribute(): ?Collection
return $sticker_items;
}

/**
* Returns the poll attribute.
*
* @return Poll|null
*/
protected function getPollAttribute(): ?Poll
{
if (! isset($this->attributes['poll'])) {
return null;
}

return $this->factory->part(Poll::class, (array) $this->attributes['poll'] + ['channel_id' => $this->channel_id, 'message_id' => $this->id], true);
}

/**
* Returns the message link attribute.
*
Expand Down
140 changes: 140 additions & 0 deletions src/Discord/Parts/Channel/Poll.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
<?php

/*
* This file is a part of the DiscordPHP project.
*
* Copyright (c) 2015-present David Cole <[email protected]>
*
* This file is subject to the MIT license that is bundled
* with this source code in the LICENSE.md file.
*/

namespace Discord\Parts\Channel;

use Carbon\Carbon;
use Discord\Helpers\Collection;
use Discord\Http\Endpoint;
use Discord\Parts\Channel\Poll\PollAnswer;
use Discord\Parts\Channel\Poll\PollMedia;
use Discord\Parts\Channel\Poll\PollResults;
use Discord\Parts\Part;
use Discord\Repository\Channel\PollAnswerRepository;
use React\Promise\ExtendedPromiseInterface;

/**
* A message poll.
*
* @link https://discord.com/developers/docs/resources/poll#poll-object
*
* @since 10.0.0
*
* @property PollMedia $question The question of the poll. Only text is supported.
* @property PollAnswerRepository $answers Each of the answers available in the poll.
* @property Carbon $expiry The time when the poll ends.
* @property bool $allow_multiselect Whether a user can select multiple answers.
* @property int $layout_type The layout type of the poll.
* @property PollResults|null $results The results of the poll.
*
* @property string $channel_id The ID of the channel the poll is in.
* @property string $message_id The ID of the message the poll is in.
*/
class Poll extends Part
{
/**
* {@inheritdoc}
*/
protected $fillable = [
'question',
'expiry',
'allow_multiselect',
'layout_type',
'results',

// events
'channel_id',
'message_id',

// repositories
'answers',
];

/**
* {@inheritdoc}
*/
protected $repositories = [
'answers' => PollAnswerRepository::class,
];

/**
* Sets the answers attribute.
*
* @param array $answers
*/
protected function setAnswersAttribute(array $answers): void
{
foreach ($answers as $answer) {
/** @var ?PollAnswer */
if ($part = $this->answers->offsetGet($answer->answer_id)) {
$part->fill($answer);
} else {
/** @var PollAnswer */
$part = $this->answers->create($answer);
}

$this->answers->pushItem($part);
}

$this->attributes['answers'] = $answers;
}

/**
* Returns the question attribute.
*
* @return PollMedia
*/
protected function getQuestionAttribute(): PollMedia
{
return $this->factory->part(PollMedia::class, (array) $this->attributes['question'], true);
}

/**
* Return the expiry attribute.
*
* @return Carbon
*
* @throws \Exception
*/
protected function getExpiryAttribute(): Carbon
{
return Carbon::parse($this->attributes['expiry']);
}

/**
* Returns the results attribute.
*
* @return PollResults|null
*/
protected function getResultsAttribute(): ?PollResults
{
if (! isset($this->attributes['results'])) {
return null;
}

return $this->factory->part(PollResults::class, (array) $this->attributes['results'], true);
}

/**
* Expire the poll.
*
* @link https://discord.com/developers/docs/resources/poll#end-poll
*
* @return ExtendedPromiseInterface<Message>
*/
public function expire(): ExtendedPromiseInterface
{
return $this->http->post(Endpoint::bind(Endpoint::MESSAGE_POLL_EXPIRE, $this->channel_id, $this->message_id))
->then(function ($response) {
return $this->factory->create(Message::class, (array) $response, true);
});
}
}
Loading

0 comments on commit e584be6

Please sign in to comment.