Skip to content

🚧 Initial Discord message components v2 implementation #1294

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 29 commits into from
Apr 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2f0db2b
🚧 Initial Discord message components v2 implementation
Log1x Feb 21, 2025
e1316b6
🚧 Allow passing attachments to file and unfurled media item
Log1x Feb 21, 2025
4ec3f39
🚧 Improve v2 component support
Log1x Feb 21, 2025
c2f2bf2
🎨 Merge flags when setting ephemeral
Log1x Feb 21, 2025
4dcfa4f
🔥 Remove unnecessary component allow list
Log1x Feb 21, 2025
84cd998
🎨 Improve v2 flag handling
Log1x Feb 21, 2025
9336de5
🎨 Add a contract for V2 components
Log1x Feb 22, 2025
cd5b233
poly_strlen
valzargaming Feb 25, 2025
9dd7539
getFlags(): ?int
valzargaming Feb 25, 2025
9a4154f
Add imports for ActionRow and SelectMenu
valzargaming Feb 25, 2025
acefceb
instance to use imports
valzargaming Feb 25, 2025
92cb43e
Update setMedia to accept UnfurledMediaItem as param
valzargaming Feb 25, 2025
f9ce1d4
Thumbnail's getMedia returns UnfurledMediaItem
valzargaming Feb 25, 2025
e653b20
Remove setSpacingSmall and setSpacingLarge macros
valzargaming Feb 25, 2025
6e0464a
setAccessory throws exception for invalid components
valzargaming Feb 25, 2025
1fe77c1
getAccessory(): Component
valzargaming Feb 25, 2025
dd5c67b
FLAG_V2_COMPONENTS => FLAG_IS_V2_COMPONENTS
valzargaming Feb 25, 2025
8f0ee3b
FLAG_V2_COMPONENTS => FLAG_IS_V2_COMPONENTS
valzargaming Feb 25, 2025
fb211d5
Validate component types for Containers
valzargaming Feb 25, 2025
fe13130
Docs for Section addComponent
valzargaming Feb 25, 2025
0c0244f
Doc primitive last
valzargaming Feb 25, 2025
859ef46
getFlags returns 0 if null
valzargaming Feb 25, 2025
35ee22c
Remove undesired nullsafe
valzargaming Feb 25, 2025
a8601c4
Remove loading_state from array_filter
valzargaming Feb 25, 2025
ab0f4a0
Add loading_state to array_merge
valzargaming Feb 25, 2025
118a8ba
Don't send content or embeds if FLAG_IS_V2_COMPONENTS
valzargaming Feb 25, 2025
349b249
Update MessageBuilder's addComponents to work with both v1 and v2 com…
valzargaming Feb 25, 2025
927e6a4
Merge branch 'master' into components-v2
Log1x Mar 23, 2025
2316e24
Bump @since from 10.4.0 to 10.5.0
valzargaming Apr 24, 2025
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
8 changes: 8 additions & 0 deletions src/Discord/Builders/Components/Component.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ abstract class Component implements JsonSerializable
public const TYPE_ROLE_SELECT = 6;
public const TYPE_MENTIONABLE_SELECT = 7;
public const TYPE_CHANNEL_SELECT = 8;
public const TYPE_SECTION = 9;
public const TYPE_TEXT_DISPLAY = 10;
public const TYPE_THUMBNAIL = 11;
public const TYPE_MEDIA_GALLERY = 12;
public const TYPE_FILE = 13;
public const TYPE_SEPARATOR = 14;
public const TYPE_CONTENT_INVENTORY_ENTRY = 16;
public const TYPE_CONTAINER = 17;

/** @deprecated 7.4.0 Use `Component::TYPE_STRING_SELECT` */
public const TYPE_SELECT_MENU = 3;
Expand Down
197 changes: 197 additions & 0 deletions src/Discord/Builders/Components/Container.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
<?php

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

namespace Discord\Builders\Components;

use Discord\Builders\Components\ActionRow;
use Discord\Builders\Components\Section;
use Discord\Builders\Components\TextDisplay;
use Discord\Builders\Components\MediaGallery;
use Discord\Builders\Components\File;
use Discord\Builders\Components\Separator;

/**
* Containers are a new way to group components together.
* You can also specify an accent color (similar to embeds) and spoiler it.
*
* @link https://discord.com/developers/docs/interactions/message-components#container
*
* @since 10.5.0
*/
class Container extends Component implements Contracts\ComponentV2
{
/**
* Array of components.
*
* @var Component[]
*/
private $components = [];

/**
* Accent color for the container.
*
* @var int|null
*/
private $accent_color;

/**
* Whether the container is a spoiler.
*
* @var bool
*/
private $spoiler = false;

/**
* Creates a new container.
*
* @return self
*/
public static function new(): self
{
return new self();
}

/**
* Adds a component to the container.
*
* @param ActionRow|Section|TextDisplay|MediaGallery|File|Separator $component Component to add.
*
* @throws \InvalidArgumentException Component is not a valid type.
* @throws \OverflowException Container exceeds 10 components.
*
* @return $this
*/
public function addComponent(Component $component): self
{
if (count($this->components) >= 10) {
throw new \OverflowException('You can only have 10 components per container.');
}

if (! ( $component instanceof ActionRow || $component instanceof Section || $component instanceof TextDisplay || $component instanceof MediaGallery || $component instanceof File || $component instanceof Separator )) {
throw new \InvalidArgumentException('Invalid component type.');
}

$this->components[] = $component;

return $this;
}

/**
* Add a group of components to the container.
*
* @param Component[] $components Components to add.
*
* @throws \InvalidArgumentException Component is not a valid type.
*
* @return $this
*/
public function addComponents(array $components): self
{
foreach ($components as $component) {
$this->addComponent($component);
}

return $this;
}

/**
* Sets the components for the container.
*
* @param Component[] $components Components to set.
*
* @return $this
*/
public function setComponents(array $components): self
{
$this->components = $components;

return $this;
}

/**
* Sets the accent color for the container.
*
* @param int|null $color Color code for the container.
*
* @return $this
*/
public function setAccentColor(?int $color): self
{
$this->accent_color = $color;

return $this;
}

/**
* Sets whether the container is a spoiler.
*
* @param bool $spoiler Whether the container is a spoiler.
*
* @return $this
*/
public function setSpoiler(bool $spoiler = true): self
{
$this->spoiler = $spoiler;

return $this;
}

/**
* Returns all the components in the container.
*
* @return Component[]
*/
public function getComponents(): array
{
return $this->components;
}

/**
* Returns the accent color for the container.
*
* @return int|null
*/
public function getAccentColor(): ?int
{
return $this->accent_color;
}

/**
* Returns whether the container is a spoiler.
*
* @return bool
*/
public function isSpoiler(): bool
{
return $this->spoiler;
}

/**
* {@inheritDoc}
*/
public function jsonSerialize(): array
{
$data = [
'type' => Component::TYPE_CONTAINER,
'components' => $this->components,
];

if (isset($this->accent_color)) {
$data['accent_color'] = $this->accent_color;
}

if ($this->spoiler) {
$data['spoiler'] = true;
}

return $data;
}
}
22 changes: 22 additions & 0 deletions src/Discord/Builders/Components/Contracts/ComponentV2.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

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

namespace Discord\Builders\Components\Contracts;

/**
* This interface is a contract for V2 components.
*
* @since 10.5.0
*/
interface ComponentV2
{
//
}
125 changes: 125 additions & 0 deletions src/Discord/Builders/Components/File.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?php

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

namespace Discord\Builders\Components;

use Discord\Parts\Channel\Attachment;

/**
* File components allow you to send a file. You can also spoiler it.
*
* @link https://discord.com/developers/docs/interactions/message-components#file
*
* @since 10.5.0
*/
class File extends Component implements Contracts\ComponentV2
{
/**
* The file to be displayed.
*
* @var array
*/
private $file;

/**
* Whether the file is a spoiler.
*
* @var bool
*/
private $spoiler = false;

/**
* Creates a new file component.
*
* @param string|Attachment|null $filename The filename or attachment to reference.
*
* @return self
*/
public static function new(string|Attachment|null $filename = null): self
{
$component = new self();

if ($filename !== null) {
$component->setFile($filename);
}

return $component;
}

/**
* Sets the file to be displayed.
*
* @param string|Attachment $filename The filename or attachment to reference.
*
* @return $this
*/
public function setFile(string|Attachment $filename): self
{
if ($filename instanceof Attachment) {
$filename = $filename->filename;
}

$this->file = ['url' => "attachment://{$filename}"];

return $this;
}

/**
* Sets whether the file is a spoiler.
*
* @param bool $spoiler Whether the file is a spoiler.
*
* @return $this
*/
public function setSpoiler(bool $spoiler = true): self
{
$this->spoiler = $spoiler;

return $this;
}

/**
* Returns the file reference.
*
* @return array
*/
public function getFile(): array
{
return $this->file;
}

/**
* Returns whether the file is a spoiler.
*
* @return bool
*/
public function isSpoiler(): bool
{
return $this->spoiler;
}

/**
* {@inheritDoc}
*/
public function jsonSerialize(): array
{
$data = [
'type' => Component::TYPE_FILE,
'file' => $this->file,
];

if ($this->spoiler) {
$data['spoiler'] = true;
}

return $data;
}
}
Loading