Skip to content

Commit

Permalink
Merge pull request #660 from AndrzejkaNowicki/main
Browse files Browse the repository at this point in the history
Support for handling webhook requests to join chats/channels
  • Loading branch information
fabio-ivona authored Nov 20, 2024
2 parents d87fe58 + b2c00d5 commit 267a5e7
Show file tree
Hide file tree
Showing 12 changed files with 485 additions and 1 deletion.
27 changes: 27 additions & 0 deletions docs/12.features/9.dto.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,3 +233,30 @@ This is a DTO for outgoing data, wraps info about the Document result returned t
## `InlineQueryResultLocation`

This is a DTO for outgoing data, wraps info about the Location result returned to the user

## `ChatInviteLink`

represents an invite link for a chat.

- `->inviteLink()` the invite link. If the link was created by another chat administrator, then the second part of the link will be replaced with “…”
- `->creator()` an instance of [`User`](#user) represents a creator of the link
- `->createsJoinRequest()` *true*, if users joining the chat via the link need to be approved by chat administrators
- `->isPrimary()` *true*, if the link is primary
- `->isRevoked()` *true*, if the link is revoked
- `->name()` (optional) invite link name
- `->expireDate()` (optional) point in time (Unix timestamp) when the link will expire or has been expired
- `->memberLimit()` (optional) The maximum number of users that can be members of the chat simultaneously after joining the chat via this invite link; 1-99999
- `->pendingJoinRequestsCount()` (optional) number of pending join requests created using this link
- `->subscriptionPeriod()` (optional) the number of seconds the subscription will be active for before the next payment
- `->subscriptionPrice()` (optional) the amount of Telegram Stars a user must pay initially and after each subsequent subscription period to be a member of the chat using the link

## `ChatJoinRequest`

represents a join request sent to a chat.

- `->chat()` an instance of [`Chat`](#chat) to which the request was sent
- `->from()` an instance of [`User`](#user) that sent the join request
- `->userChatId()` identifier of a private chat with the user who sent the join request. This number may have more than 32 significant bits and some programming languages may have difficulty/silent defects in interpreting it. But it has at most 52 significant bits, so a 64-bit integer or double-precision float type are safe for storing this identifier. The bot can use this identifier for 5 minutes to send messages until the join request is processed, assuming no other administrator contacted the user.
- `->date()` date the request was sent in Unix time
- `->bio()` (optional) bio of the user
- `->inviteLink()` (optional) an instance of [`ChatInviteLink`](#chat-invite-link) that was used by the user to send the join request
24 changes: 23 additions & 1 deletion docs/15.webhooks/4.webhook-request-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,23 @@ Different kind of result can be sent through the handler:

## Member activities

Telegraph bots can listen for members join/leave activity in chats where they are registered and handle them by overriding `handleChatMemberJoined` and `handleChatMemberLeaved` methods:
Telegraph bots can listen for members join/leave activity in chats where they are registered and handle them by overriding `handleChatJoinRequest`, `handleChatMemberJoined` and `handleChatMemberLeaved` methods:

### Member has sent a request to join

```php
class CustomWebhookHandler extends WebhookHandler
{
protected function handleChatJoinRequest(ChatJoinRequest $chatJoinRequest): void
{
if (someCondition()) {
$this->chat->approveJoinRequest($chatJoinRequest->from()->id());
} else {
$this->chat->declineJoinRequest($chatJoinRequest->from()->id());
}
}
}
```

### Member joined

Expand All @@ -253,3 +269,9 @@ class CustomWebhookHandler extends WebhookHandler
}
}
```

Used DTOs:

- User ([`DefStudio\Telegraph\DTO\User`](../12.features/9.dto.md#user))
- Chat ([`DefStudio\Telegraph\DTO\Chat`](../12.features/9.dto.md#chat))
- ChatJoinRequest ([`DefStudio\Telegraph\DTO\ChatJoinRequest`](../12.features/9.dto.md#chatjoinrequest))
161 changes: 161 additions & 0 deletions src/DTO/ChatInviteLink.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
<?php

declare(strict_types=1);

namespace DefStudio\Telegraph\DTO;

use Carbon\Carbon;
use Carbon\CarbonInterface;
use Illuminate\Contracts\Support\Arrayable;

/**
* @implements Arrayable<string, string|int|bool|array<string, mixed>>
*/
class ChatInviteLink implements Arrayable
{
private string $inviteLink;
private User $creator;
private bool $createsJoinRequest;
private bool $isPrimary;
private bool $isRevoked;
private ?string $name = null;
private ?CarbonInterface $expireDate = null;
private ?int $memberLimit = null;
private ?int $pendingJoinRequestsCount = null;
private ?int $subscriptionPeriod = null;
private ?int $subscriptionPrice = null;

private function __construct()
{
}

/**
* @param array{
* creator: array<string, mixed>,
* invite_link: string,
* creates_join_request: bool,
* is_primary: bool,
* is_revoked: bool,
* name?: string,
* expire_date?: int,
* member_limit?: int,
* pending_join_requests_count?: int,
* subscription_period?: int,
* subscription_price?: int
* } $data
*/
public static function fromArray(array $data): ChatInviteLink
{
$invite = new self();

$invite->inviteLink = $data['invite_link'];

/* @phpstan-ignore-next-line */
$invite->creator = User::fromArray($data['creator']);

$invite->createsJoinRequest = $data['creates_join_request'];

$invite->isPrimary = $data['is_primary'];

$invite->isRevoked = $data['is_revoked'];

if (isset($data['name'])) {
$invite->name = $data['name'];
}

if (isset($data['expire_date'])) {
/* @phpstan-ignore-next-line */
$invite->expireDate = Carbon::createFromTimestamp($data['expire_date']);
}

if (isset($data['member_limit'])) {
$invite->memberLimit = $data['member_limit'];
}

if (isset($data['pending_join_requests_count'])) {
$invite->pendingJoinRequestsCount = $data['pending_join_requests_count'];
}

if (isset($data['subscription_period'])) {
$invite->subscriptionPeriod = $data['subscription_period'];
}

if (isset($data['subscription_price'])) {
$invite->subscriptionPrice = $data['subscription_price'];
}

return $invite;
}

public function inviteLink(): string
{
return $this->inviteLink;
}

public function creator(): User
{
return $this->creator;
}

public function createsJoinRequest(): bool
{
return $this->createsJoinRequest;
}

public function isPrimary(): bool
{
return $this->isPrimary;
}

public function isRevoked(): bool
{
return $this->isRevoked;
}

public function name(): ?string
{
return $this->name;
}

public function expireDate(): ?CarbonInterface
{
return $this->expireDate;
}

public function memberLimit(): ?int
{
return $this->memberLimit;
}

public function pendingJoinRequestsCount(): ?int
{
return $this->pendingJoinRequestsCount;
}

public function subscriptionPeriod(): ?int
{
return $this->subscriptionPeriod;
}

public function subscriptionPrice(): ?int
{
return $this->subscriptionPrice;
}

public function toArray(): array
{
return array_filter([
'invite_link' => $this->inviteLink,
'creator' => $this->creator->toArray(),
'creates_join_request' => $this->createsJoinRequest,
'is_primary' => $this->isPrimary,
'is_revoked' => $this->isRevoked,
'name' => $this->name,
'expire_date' => $this->expireDate?->timestamp,
'member_limit' => $this->memberLimit,
'pending_join_requests_count' => $this->pendingJoinRequestsCount,
'subscription_period' => $this->subscriptionPeriod,
'subscription_price' => $this->subscriptionPrice,
], fn ($value) => $value !== null);
}
}
103 changes: 103 additions & 0 deletions src/DTO/ChatJoinRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php

namespace DefStudio\Telegraph\DTO;

use Carbon\Carbon;
use Carbon\CarbonInterface;
use Illuminate\Contracts\Support\Arrayable;

/**
* @implements Arrayable<string, string|int|bool|array<string, mixed>>
*/
class ChatJoinRequest implements Arrayable
{
private int $userChatId;
private CarbonInterface $date;
private ?string $bio = null;
private ?ChatInviteLink $inviteLink = null;
private Chat $chat;
private User $from;

private function __construct()
{
}

/**
* @param array{
* user_chat_id: int,
* date: int,
* bio?: string,
* invite_link?: array<string, mixed>,
* chat: array<string, mixed>,
* from: array<string, mixed>,
* } $data
*/
public static function fromArray(array $data): ChatJoinRequest
{
$request = new self();

$request->userChatId = $data['user_chat_id'];

/* @phpstan-ignore-next-line */
$request->date = Carbon::createFromTimestamp($data['date']);

if (isset($data['bio'])) {
$request->bio = $data['bio'];
}

if (isset($data['invite_link'])) {
/* @phpstan-ignore-next-line */
$request->inviteLink = ChatInviteLink::fromArray($data['invite_link']);
}

/* @phpstan-ignore-next-line */
$request->chat = Chat::fromArray($data['chat']);

/* @phpstan-ignore-next-line */
$request->from = User::fromArray($data['from']);

return $request;
}

public function userChatId(): int
{
return $this->userChatId;
}

public function date(): CarbonInterface
{
return $this->date;
}

public function bio(): ?string
{
return $this->bio;
}

public function inviteLink(): ?ChatInviteLink
{
return $this->inviteLink;
}

public function chat(): Chat
{
return $this->chat;
}

public function from(): User
{
return $this->from;
}

public function toArray(): array
{
return array_filter([
'user_chat_id' => $this->userChatId,
'date' => $this->date->timestamp,
'bio' => $this->bio,
'invite_link' => $this->inviteLink?->toArray(),
'chat' => $this->chat->toArray(),
'from' => $this->from->toArray(),
], fn ($value) => $value !== null);
}
}
1 change: 1 addition & 0 deletions src/Facades/Telegraph.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
* @method static void assertRepliedWebhook(string $message)
* @method static void assertRepliedWebhookIsAlert()
* @method static void assertStoredFile(string $fileId)
* @method static void assertChatJoinRequestApproved(string $userId)
*
* @see \DefStudio\Telegraph\Telegraph
*/
Expand Down
19 changes: 19 additions & 0 deletions src/Handlers/WebhookHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

use DefStudio\Telegraph\DTO\CallbackQuery;
use DefStudio\Telegraph\DTO\Chat;
use DefStudio\Telegraph\DTO\ChatJoinRequest;
use DefStudio\Telegraph\DTO\InlineQuery;
use DefStudio\Telegraph\DTO\Message;
use DefStudio\Telegraph\DTO\Reaction;
Expand Down Expand Up @@ -40,6 +41,7 @@ abstract class WebhookHandler
protected Message|null $message = null;
protected Reaction|null $reaction = null;
protected CallbackQuery|null $callbackQuery = null;
protected ChatJoinRequest|null $chatJoinRequest = null;

/**
* @var Collection<string, string>|Collection<int, array<string, string>>
Expand Down Expand Up @@ -294,6 +296,16 @@ public function handle(Request $request, TelegraphBot $bot): void
return;
}

if ($this->request->has('chat_join_request')) {
/* @phpstan-ignore-next-line */
$this->chatJoinRequest = ChatJoinRequest::fromArray($this->request->input('chat_join_request'));
$this->setupChat();
/* @phpstan-ignore-next-line */
$this->handleChatJoinRequest($this->chatJoinRequest);

return;
}


if ($this->request->has('callback_query')) {
/* @phpstan-ignore-next-line */
Expand Down Expand Up @@ -321,6 +333,8 @@ protected function setupChat(): void
$telegramChat = $this->message->chat();
} elseif (isset($this->reaction)) {
$telegramChat = $this->reaction->chat();
} elseif (isset($this->chatJoinRequest)) {
$telegramChat = $this->chatJoinRequest->chat();
} else {
$telegramChat = $this->callbackQuery?->message()?->chat();
}
Expand Down Expand Up @@ -410,4 +424,9 @@ protected function getChatName(Chat $chat): string
->append("[", $chat->type(), ']')
->append(" ", $chat->title());
}

protected function handleChatJoinRequest(ChatJoinRequest $chatJoinRequest): void
{
// .. do nothing
}
}
Loading

0 comments on commit 267a5e7

Please sign in to comment.