diff --git a/CHANGELOG.md b/CHANGELOG.md index c30b1d4b..c86098a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Telegram Bot API for PHP Change Log +## 0.17 March 1, 2026 + +- New #188: Add `unixTime` and `dateTimeFormat` fields to `MessageEntity` type. +- New #188: Add `DATE_TIME` constant to `MessageEntityType`. +- New #188: Add `tag` field to `ChatMemberMember` and `ChatMemberRestricted` types. +- New #188: Add `canEditTag` field to `ChatMemberRestricted` and `ChatPermissions` types. +- New #188: Add `SetChatMemberTag` method. +- New #188: Add `canManageTags` field to `ChatMemberAdministrator` and `ChatAdministratorRights` types. +- New #188: Add `canManageTags` parameter to `PromoteChatMember` method. +- New #188: Add `senderTag` field to `Message` type. + ## 0.16 February 28, 2026 - Chg #186: Remove `PsrTransport`, `PsrWebhookResponseFactory` and `StreamResourceReader` classes. diff --git a/README.md b/README.md index 682d601d..40a284d2 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ The package provides a simple and convenient way to interact with the Telegram Bot API. -✔️ Telegram Bot API 9.4 (February 9, 2026) is **fully supported**. +✔️ Telegram Bot API 9.5 (March 1, 2026) is **fully supported**. ♻️ **Zero dependencies** — no third-party libraries, only native PHP. diff --git a/src/Constant/MessageEntityType.php b/src/Constant/MessageEntityType.php index 632cdccc..5699c2fc 100644 --- a/src/Constant/MessageEntityType.php +++ b/src/Constant/MessageEntityType.php @@ -31,4 +31,5 @@ final class MessageEntityType public const TEXT_LINK = 'text_link'; public const TEXT_MENTION = 'text_mention'; public const CUSTOM_EMOJI = 'custom_emoji'; + public const DATE_TIME = 'date_time'; } diff --git a/src/Method/PromoteChatMember.php b/src/Method/PromoteChatMember.php index 6bc075e0..b96164ce 100644 --- a/src/Method/PromoteChatMember.php +++ b/src/Method/PromoteChatMember.php @@ -34,6 +34,7 @@ public function __construct( private ?bool $canPinMessages = null, private ?bool $canManageTopics = null, private ?bool $canManageDirectMessages = null, + private ?bool $canManageTags = null, ) {} public function getHttpMethod(): HttpMethod @@ -68,6 +69,7 @@ public function getData(): array 'can_pin_messages' => $this->canPinMessages, 'can_manage_topics' => $this->canManageTopics, 'can_manage_direct_messages' => $this->canManageDirectMessages, + 'can_manage_tags' => $this->canManageTags, ], static fn(mixed $value): bool => $value !== null, ); diff --git a/src/Method/SetChatMemberTag.php b/src/Method/SetChatMemberTag.php new file mode 100644 index 00000000..344e5caa --- /dev/null +++ b/src/Method/SetChatMemberTag.php @@ -0,0 +1,50 @@ + + */ +final readonly class SetChatMemberTag implements MethodInterface +{ + public function __construct( + private int|string $chatId, + private int $userId, + private ?string $tag = null, + ) {} + + public function getHttpMethod(): HttpMethod + { + return HttpMethod::POST; + } + + public function getApiMethod(): string + { + return 'setChatMemberTag'; + } + + public function getData(): array + { + return array_filter( + [ + 'chat_id' => $this->chatId, + 'user_id' => $this->userId, + 'tag' => $this->tag, + ], + static fn(mixed $value): bool => $value !== null, + ); + } + + public function getResultType(): TrueValue + { + return new TrueValue(); + } +} diff --git a/src/TelegramBotApi.php b/src/TelegramBotApi.php index db0024df..532ef520 100644 --- a/src/TelegramBotApi.php +++ b/src/TelegramBotApi.php @@ -115,6 +115,7 @@ use Phptg\BotApi\Method\SetBusinessAccountUsername; use Phptg\BotApi\Method\SetChatAdministratorCustomTitle; use Phptg\BotApi\Method\SetChatDescription; +use Phptg\BotApi\Method\SetChatMemberTag; use Phptg\BotApi\Method\SetChatMenuButton; use Phptg\BotApi\Method\SetChatPermissions; use Phptg\BotApi\Method\SetChatPhoto; @@ -1648,6 +1649,7 @@ public function promoteChatMember( ?bool $canPinMessages = null, ?bool $canManageTopics = null, ?bool $canManageDirectMessages = null, + ?bool $canManageTags = null, ): FailResult|true { return $this->call( new PromoteChatMember( @@ -1669,6 +1671,7 @@ public function promoteChatMember( $canPinMessages, $canManageTopics, $canManageDirectMessages, + $canManageTags, ), ); } @@ -2840,6 +2843,19 @@ public function setChatAdministratorCustomTitle( ); } + /** + * @see https://core.telegram.org/bots/api#setchatmembertag + */ + public function setChatMemberTag( + int|string $chatId, + int $userId, + ?string $tag = null, + ): FailResult|true { + return $this->call( + new SetChatMemberTag($chatId, $userId, $tag), + ); + } + /** * @see https://core.telegram.org/bots/api#setchatdescription */ diff --git a/src/Type/ChatAdministratorRights.php b/src/Type/ChatAdministratorRights.php index 3df3c724..cc46e96a 100644 --- a/src/Type/ChatAdministratorRights.php +++ b/src/Type/ChatAdministratorRights.php @@ -28,6 +28,7 @@ public function __construct( public ?bool $canPinMessages = null, public ?bool $canManageTopics = null, public ?bool $canManageDirectMessages = null, + public ?bool $canManageTags = null, ) {} public function toRequestArray(): array @@ -50,6 +51,7 @@ public function toRequestArray(): array 'can_pin_messages' => $this->canPinMessages, 'can_manage_topics' => $this->canManageTopics, 'can_manage_direct_messages' => $this->canManageDirectMessages, + 'can_manage_tags' => $this->canManageTags, ], static fn(mixed $value): bool => $value !== null, ); diff --git a/src/Type/ChatMemberAdministrator.php b/src/Type/ChatMemberAdministrator.php index 970e8262..c7829374 100644 --- a/src/Type/ChatMemberAdministrator.php +++ b/src/Type/ChatMemberAdministrator.php @@ -31,6 +31,7 @@ public function __construct( public ?bool $canManageTopics = null, public ?string $customTitle = null, public ?bool $canManageDirectMessages = null, + public ?bool $canManageTags = null, ) {} public function getStatus(): string diff --git a/src/Type/ChatMemberMember.php b/src/Type/ChatMemberMember.php index 835cd977..432eec89 100644 --- a/src/Type/ChatMemberMember.php +++ b/src/Type/ChatMemberMember.php @@ -16,6 +16,7 @@ public function __construct( public User $user, public ?DateTimeImmutable $untilDate = null, + public ?string $tag = null, ) {} public function getStatus(): string diff --git a/src/Type/ChatMemberRestricted.php b/src/Type/ChatMemberRestricted.php index 2d6a0184..15390103 100644 --- a/src/Type/ChatMemberRestricted.php +++ b/src/Type/ChatMemberRestricted.php @@ -27,12 +27,14 @@ public function __construct( public bool $canSendPolls, public bool $canSendOtherMessages, public bool $canAddWebPagePreviews, + public bool $canEditTag, public bool $canChangeInfo, public bool $canInviteUsers, public bool $canPinMessages, public bool $canManageTopics, #[ChatMemberUntilDateValue] public DateTimeImmutable|false $untilDate, + public ?string $tag = null, ) {} public function getStatus(): string diff --git a/src/Type/ChatPermissions.php b/src/Type/ChatPermissions.php index bdf85a38..6a67d227 100644 --- a/src/Type/ChatPermissions.php +++ b/src/Type/ChatPermissions.php @@ -22,6 +22,7 @@ public function __construct( public ?bool $canSendPolls = null, public ?bool $canSendOtherMessages = null, public ?bool $canAddWebPagePreviews = null, + public ?bool $canEditTag = null, public ?bool $canChangeInfo = null, public ?bool $canInviteUsers = null, public ?bool $canPinMessages = null, @@ -42,6 +43,7 @@ public function toRequestArray(): array 'can_send_polls' => $this->canSendPolls, 'can_send_other_messages' => $this->canSendOtherMessages, 'can_add_web_page_previews' => $this->canAddWebPagePreviews, + 'can_edit_tag' => $this->canEditTag, 'can_change_info' => $this->canChangeInfo, 'can_invite_users' => $this->canInviteUsers, 'can_pin_messages' => $this->canPinMessages, diff --git a/src/Type/Message.php b/src/Type/Message.php index 3273a833..81e1399a 100644 --- a/src/Type/Message.php +++ b/src/Type/Message.php @@ -37,6 +37,7 @@ public function __construct( public ?Chat $senderChat = null, public ?int $senderBoostCount = null, public ?User $senderBusinessBot = null, + public ?string $senderTag = null, public ?string $businessConnectionId = null, public ?MessageOrigin $forwardOrigin = null, public ?true $isTopicMessage = null, diff --git a/src/Type/MessageEntity.php b/src/Type/MessageEntity.php index 1caee2d5..aba4efd8 100644 --- a/src/Type/MessageEntity.php +++ b/src/Type/MessageEntity.php @@ -4,6 +4,8 @@ namespace Phptg\BotApi\Type; +use Phptg\BotApi\Constant\MessageEntityType; + /** * @see https://core.telegram.org/bots/api#messageentity * @@ -11,6 +13,9 @@ */ final readonly class MessageEntity { + /** + * @param string $type Type of the entity ({@see MessageEntityType}). + */ public function __construct( public string $type, public int $offset, @@ -19,6 +24,8 @@ public function __construct( public ?User $user = null, public ?string $language = null, public ?string $customEmojiId = null, + public ?int $unixTime = null, + public ?string $dateTimeFormat = null, ) {} public function toRequestArray(): array @@ -32,6 +39,8 @@ public function toRequestArray(): array 'user' => $this->user?->toRequestArray(), 'language' => $this->language, 'custom_emoji_id' => $this->customEmojiId, + 'unix_time' => $this->unixTime, + 'date_time_format' => $this->dateTimeFormat, ], static fn(mixed $value): bool => $value !== null, ); diff --git a/tests/Method/PromoteChatMemberTest.php b/tests/Method/PromoteChatMemberTest.php index 33c9561c..569b461f 100644 --- a/tests/Method/PromoteChatMemberTest.php +++ b/tests/Method/PromoteChatMemberTest.php @@ -50,6 +50,7 @@ public function testFull(): void true, true, false, + true, ); assertSame( @@ -72,6 +73,7 @@ public function testFull(): void 'can_pin_messages' => true, 'can_manage_topics' => true, 'can_manage_direct_messages' => false, + 'can_manage_tags' => true, ], $method->getData(), ); diff --git a/tests/Method/SetChatMemberTagTest.php b/tests/Method/SetChatMemberTagTest.php new file mode 100644 index 00000000..808d65d5 --- /dev/null +++ b/tests/Method/SetChatMemberTagTest.php @@ -0,0 +1,56 @@ +getHttpMethod()); + assertSame('setChatMemberTag', $method->getApiMethod()); + assertSame( + [ + 'chat_id' => 1, + 'user_id' => 2, + ], + $method->getData(), + ); + } + + public function testFull(): void + { + $method = new SetChatMemberTag(1, 2, 'test'); + + assertSame(HttpMethod::POST, $method->getHttpMethod()); + assertSame('setChatMemberTag', $method->getApiMethod()); + assertSame( + [ + 'chat_id' => 1, + 'user_id' => 2, + 'tag' => 'test', + ], + $method->getData(), + ); + } + + public function testPrepareResult(): void + { + $method = new SetChatMemberTag(1, 2); + + $preparedResult = TestHelper::createSuccessStubApi(true)->call($method); + + assertTrue($preparedResult); + } +} diff --git a/tests/ParseResult/ValueProcessor/ChatMemberValueTest.php b/tests/ParseResult/ValueProcessor/ChatMemberValueTest.php index 44745608..aa4da919 100644 --- a/tests/ParseResult/ValueProcessor/ChatMemberValueTest.php +++ b/tests/ParseResult/ValueProcessor/ChatMemberValueTest.php @@ -89,6 +89,7 @@ public static function dataBase(): array 'can_send_polls' => false, 'can_send_other_messages' => false, 'can_add_web_page_previews' => false, + 'can_edit_tag' => false, 'can_change_info' => false, 'can_invite_users' => false, 'can_pin_messages' => false, diff --git a/tests/TelegramBotApi/TelegramBotApiTest.php b/tests/TelegramBotApi/TelegramBotApiTest.php index 68e434ed..b057d3c9 100644 --- a/tests/TelegramBotApi/TelegramBotApiTest.php +++ b/tests/TelegramBotApi/TelegramBotApiTest.php @@ -2212,6 +2212,15 @@ public function testSetChatAdministratorCustomTitle(): void assertTrue($result); } + public function testSetChatMemberTag(): void + { + $api = TestHelper::createSuccessStubApi(true); + + $result = $api->setChatMemberTag(1, 2, 'test'); + + assertTrue($result); + } + public function testSetChatPermissions(): void { $api = TestHelper::createSuccessStubApi(true); diff --git a/tests/Type/ChatAdministratorRightsTest.php b/tests/Type/ChatAdministratorRightsTest.php index b3caf745..867cfca5 100644 --- a/tests/Type/ChatAdministratorRightsTest.php +++ b/tests/Type/ChatAdministratorRightsTest.php @@ -35,6 +35,7 @@ public function testBase(): void assertNull($rights->canPinMessages); assertNull($rights->canManageTopics); assertNull($rights->canManageDirectMessages); + assertNull($rights->canManageTags); assertSame( [ @@ -73,6 +74,7 @@ public function testFromTelegramResult(): void 'can_pin_messages' => false, 'can_manage_topics' => true, 'can_manage_direct_messages' => false, + 'can_manage_tags' => true, ], null, ChatAdministratorRights::class); assertTrue($type->isAnonymous); @@ -91,6 +93,7 @@ public function testFromTelegramResult(): void assertFalse($type->canPinMessages); assertTrue($type->canManageTopics); assertFalse($type->canManageDirectMessages); + assertTrue($type->canManageTags); assertSame( [ 'is_anonymous' => true, @@ -109,6 +112,7 @@ public function testFromTelegramResult(): void 'can_pin_messages' => false, 'can_manage_topics' => true, 'can_manage_direct_messages' => false, + 'can_manage_tags' => true, ], $type->toRequestArray(), ); diff --git a/tests/Type/ChatMemberAdministratorTest.php b/tests/Type/ChatMemberAdministratorTest.php index 045b3286..20b95b19 100644 --- a/tests/Type/ChatMemberAdministratorTest.php +++ b/tests/Type/ChatMemberAdministratorTest.php @@ -56,6 +56,7 @@ public function testBase(): void assertNull($member->canManageTopics); assertNull($member->customTitle); assertNull($member->canManageDirectMessages); + assertNull($member->canManageTags); } public function testFromTelegramResult(): void @@ -83,6 +84,7 @@ public function testFromTelegramResult(): void 'can_pin_messages' => true, 'can_manage_topics' => false, 'can_manage_direct_messages' => true, + 'can_manage_tags' => false, 'custom_title' => 'Custom title', ], null, ChatMemberAdministrator::class); @@ -105,5 +107,6 @@ public function testFromTelegramResult(): void assertFalse($member->canManageTopics); assertSame('Custom title', $member->customTitle); assertTrue($member->canManageDirectMessages); + assertFalse($member->canManageTags); } } diff --git a/tests/Type/ChatMemberMemberTest.php b/tests/Type/ChatMemberMemberTest.php index a04fb06c..1d4cca1a 100644 --- a/tests/Type/ChatMemberMemberTest.php +++ b/tests/Type/ChatMemberMemberTest.php @@ -9,6 +9,7 @@ use Phptg\BotApi\Type\ChatMemberMember; use Phptg\BotApi\Type\User; +use function PHPUnit\Framework\assertNull; use function PHPUnit\Framework\assertSame; final class ChatMemberMemberTest extends TestCase @@ -21,6 +22,7 @@ public function testBase(): void assertSame('member', $member->getStatus()); assertSame($user, $member->getUser()); assertSame($user, $member->user); + assertNull($member->tag); } public function testFromTelegramResult(): void @@ -46,6 +48,7 @@ public function testFromTelegramResultFull(): void 'first_name' => 'John', ], 'until_date' => 1724317996, + 'tag' => 'test-tag', ], null, ChatMemberMember::class, @@ -53,5 +56,6 @@ public function testFromTelegramResultFull(): void assertSame(123, $member->user->id); assertSame(1724317996, $member->untilDate?->getTimestamp()); + assertSame('test-tag', $member->tag); } } diff --git a/tests/Type/ChatMemberRestrictedTest.php b/tests/Type/ChatMemberRestrictedTest.php index 4fe63e4f..bed96e7a 100644 --- a/tests/Type/ChatMemberRestrictedTest.php +++ b/tests/Type/ChatMemberRestrictedTest.php @@ -12,6 +12,7 @@ use function PHPUnit\Framework\assertEquals; use function PHPUnit\Framework\assertFalse; +use function PHPUnit\Framework\assertNull; use function PHPUnit\Framework\assertSame; use function PHPUnit\Framework\assertTrue; @@ -37,6 +38,7 @@ public function testBase(): void true, true, true, + true, false, ); @@ -54,11 +56,13 @@ public function testBase(): void assertTrue($member->canSendPolls); assertTrue($member->canSendOtherMessages); assertTrue($member->canAddWebPagePreviews); + assertTrue($member->canEditTag); assertTrue($member->canChangeInfo); assertTrue($member->canInviteUsers); assertTrue($member->canPinMessages); assertTrue($member->canManageTopics); assertFalse($member->untilDate); + assertNull($member->tag); } public function testFromTelegramResult(): void @@ -80,11 +84,13 @@ public function testFromTelegramResult(): void 'can_send_polls' => true, 'can_send_other_messages' => true, 'can_add_web_page_previews' => true, + 'can_edit_tag' => true, 'can_change_info' => true, 'can_invite_users' => true, 'can_pin_messages' => true, 'can_manage_topics' => true, 'until_date' => 123456779, + 'tag' => 'test-tag', ], null, ChatMemberRestricted::class); assertSame(123, $member->user->id); @@ -99,11 +105,13 @@ public function testFromTelegramResult(): void assertTrue($member->canSendPolls); assertTrue($member->canSendOtherMessages); assertTrue($member->canAddWebPagePreviews); + assertTrue($member->canEditTag); assertTrue($member->canChangeInfo); assertTrue($member->canInviteUsers); assertTrue($member->canPinMessages); assertTrue($member->canManageTopics); assertEquals(new DateTimeImmutable('@123456779'), $member->untilDate); + assertSame('test-tag', $member->tag); } public function testFromTelegramResultWithZeroUntilDate(): void @@ -125,6 +133,7 @@ public function testFromTelegramResultWithZeroUntilDate(): void 'can_send_polls' => true, 'can_send_other_messages' => true, 'can_add_web_page_previews' => true, + 'can_edit_tag' => true, 'can_change_info' => true, 'can_invite_users' => true, 'can_pin_messages' => true, @@ -144,6 +153,7 @@ public function testFromTelegramResultWithZeroUntilDate(): void assertTrue($member->canSendPolls); assertTrue($member->canSendOtherMessages); assertTrue($member->canAddWebPagePreviews); + assertTrue($member->canEditTag); assertTrue($member->canChangeInfo); assertTrue($member->canInviteUsers); assertTrue($member->canPinMessages); diff --git a/tests/Type/ChatPermissionsTest.php b/tests/Type/ChatPermissionsTest.php index 5c1391a6..844467d4 100644 --- a/tests/Type/ChatPermissionsTest.php +++ b/tests/Type/ChatPermissionsTest.php @@ -29,6 +29,7 @@ public function testBase(): void assertNull($chatPermissions->canSendPolls); assertNull($chatPermissions->canSendOtherMessages); assertNull($chatPermissions->canAddWebPagePreviews); + assertNull($chatPermissions->canEditTag); assertNull($chatPermissions->canChangeInfo); assertNull($chatPermissions->canInviteUsers); assertNull($chatPermissions->canPinMessages); @@ -49,6 +50,7 @@ public function testFull(): void false, true, true, + true, false, false, true, @@ -67,6 +69,7 @@ public function testFull(): void 'can_send_polls' => false, 'can_send_other_messages' => true, 'can_add_web_page_previews' => true, + 'can_edit_tag' => true, 'can_change_info' => false, 'can_invite_users' => false, 'can_pin_messages' => true, @@ -89,6 +92,7 @@ public function testFromTelegramResult(): void 'can_send_polls' => true, 'can_send_other_messages' => true, 'can_add_web_page_previews' => true, + 'can_edit_tag' => true, 'can_change_info' => true, 'can_invite_users' => true, 'can_pin_messages' => true, @@ -105,6 +109,7 @@ public function testFromTelegramResult(): void assertTrue($chatPermissions->canSendPolls); assertTrue($chatPermissions->canSendOtherMessages); assertTrue($chatPermissions->canAddWebPagePreviews); + assertTrue($chatPermissions->canEditTag); assertTrue($chatPermissions->canChangeInfo); assertTrue($chatPermissions->canInviteUsers); assertTrue($chatPermissions->canPinMessages); diff --git a/tests/Type/MessageEntityTest.php b/tests/Type/MessageEntityTest.php index 87c71891..5f97ae4a 100644 --- a/tests/Type/MessageEntityTest.php +++ b/tests/Type/MessageEntityTest.php @@ -27,6 +27,8 @@ public function testCreate(): void assertNull($messageEntity->user); assertNull($messageEntity->language); assertNull($messageEntity->customEmojiId); + assertNull($messageEntity->unixTime); + assertNull($messageEntity->dateTimeFormat); assertSame( [ @@ -48,6 +50,8 @@ public function testToRequestArray(): void new User(1, false, 'Sergei'), 'ru', 'x6', + 1740000000, + 'd MMMM yyyy', ); assertSame( @@ -63,6 +67,8 @@ public function testToRequestArray(): void ], 'language' => 'ru', 'custom_emoji_id' => 'x6', + 'unix_time' => 1740000000, + 'date_time_format' => 'd MMMM yyyy', ], $messageEntity->toRequestArray(), ); @@ -82,6 +88,8 @@ public function testFromTelegramResult(): void ], 'language' => 'ru', 'custom_emoji_id' => 'x6', + 'unix_time' => 1740000000, + 'date_time_format' => 'd MMMM yyyy', ], null, MessageEntity::class); assertSame('bold', $messageEntity->type); @@ -96,5 +104,7 @@ public function testFromTelegramResult(): void assertSame('ru', $messageEntity->language); assertSame('x6', $messageEntity->customEmojiId); + assertSame(1740000000, $messageEntity->unixTime); + assertSame('d MMMM yyyy', $messageEntity->dateTimeFormat); } } diff --git a/tests/Type/MessageTest.php b/tests/Type/MessageTest.php index cd0ab88e..3e9b24ef 100644 --- a/tests/Type/MessageTest.php +++ b/tests/Type/MessageTest.php @@ -49,6 +49,7 @@ public function testBase(): void assertNull($message->senderChat); assertNull($message->senderBoostCount); assertNull($message->senderBusinessBot); + assertNull($message->senderTag); assertNull($message->businessConnectionId); assertNull($message->forwardOrigin); assertNull($message->isTopicMessage); @@ -175,6 +176,7 @@ public function testFromTelegramResult(): void 'is_bot' => true, 'first_name' => 'JohnBot', ], + 'sender_tag' => 'admin-tag', 'business_connection_id' => 'btest', 'forward_origin' => [ 'type' => 'hidden_user', @@ -659,6 +661,7 @@ public function testFromTelegramResult(): void assertSame(9, $message->senderChat?->id); assertSame(11, $message->senderBoostCount); assertSame(15, $message->senderBusinessBot?->id); + assertSame('admin-tag', $message->senderTag); assertSame('btest', $message->businessConnectionId); assertInstanceOf(MessageOriginHiddenUser::class, $message->forwardOrigin);