Skip to content

Commit 13b7d70

Browse files
committed
Implemented the console command for sending messages to all users.
1 parent d23a22c commit 13b7d70

File tree

7 files changed

+268
-31
lines changed

7 files changed

+268
-31
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
namespace App\Console\Commands\Telegram;
4+
5+
use App\Jobs\SendMessageJob;
6+
use App\User;
7+
use Illuminate\Console\Command;
8+
use Illuminate\Database\Eloquent\Builder;
9+
10+
class BroadcastMessageCommand extends Command
11+
{
12+
/**
13+
* The name and signature of the console command.
14+
*
15+
* @var string
16+
*/
17+
protected $signature = 'telegram:broadcast-message
18+
{--only-private : Send message to private chats only}';
19+
20+
/**
21+
* The console command description.
22+
*
23+
* @var string
24+
*/
25+
protected $description = 'Send a message to all users';
26+
27+
/**
28+
* Execute the console command.
29+
*
30+
* @return int
31+
*/
32+
public function handle(): int
33+
{
34+
$message = $this->ask('Enter your message');
35+
36+
User::query()
37+
->when(
38+
$this->option('only-private'),
39+
function (Builder $query) {
40+
$query->where('telegram_chat_id', 'not like', '-%');
41+
}
42+
)
43+
->each(function (User $user) use ($message) {
44+
SendMessageJob::dispatch($user, $message);
45+
});
46+
47+
return 0;
48+
}
49+
}

app/Events/BotWasBlocked.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace App\Events;
4+
5+
use Illuminate\Broadcasting\InteractsWithSockets;
6+
use Illuminate\Foundation\Events\Dispatchable;
7+
use Illuminate\Queue\SerializesModels;
8+
9+
class BotWasBlocked
10+
{
11+
use Dispatchable, InteractsWithSockets, SerializesModels;
12+
13+
/**
14+
* @var string
15+
*/
16+
public string $chatId;
17+
18+
/**
19+
* Create a new event instance.
20+
*
21+
* @param string $chatId
22+
* @return void
23+
*/
24+
public function __construct(string $chatId)
25+
{
26+
$this->chatId = $chatId;
27+
}
28+
}

app/Integrations/Telegram/Entities/OutboundMessage.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
namespace App\Integrations\Telegram\Entities;
44

5+
use App\Events\BotWasBlocked;
56
use App\Facades\TelegramBot;
7+
use App\Integrations\Telegram\Exceptions\TelegramBotException;
68
use App\User;
79
use Illuminate\Contracts\Support\Arrayable;
810

@@ -132,7 +134,15 @@ public function withInlineKeyboard(InlineKeyboard $keyboard): self
132134
*/
133135
public function send(): void
134136
{
135-
TelegramBot::sendMessage($this);
137+
try {
138+
TelegramBot::sendMessage($this);
139+
} catch (TelegramBotException $exception) {
140+
if ($exception->getMessage() === 'Forbidden: bot was blocked by the user') {
141+
event(new BotWasBlocked($this->params['chat_id']));
142+
} else {
143+
throw $exception;
144+
}
145+
}
136146
}
137147

138148
/**

app/Jobs/SendDeploymentNotificationJob.php

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@
66
use App\User;
77
use Illuminate\Bus\Queueable;
88
use Illuminate\Contracts\Queue\ShouldQueue;
9+
use Illuminate\Contracts\Redis\LimiterTimeoutException;
910
use Illuminate\Foundation\Bus\Dispatchable;
1011
use Illuminate\Queue\InteractsWithQueue;
1112
use Illuminate\Queue\SerializesModels;
1213
use Illuminate\Support\Arr;
14+
use Illuminate\Support\Carbon;
15+
use Illuminate\Support\Facades\Redis;
1316
use Illuminate\Support\Fluent;
1417

1518
class SendDeploymentNotificationJob implements ShouldQueue
@@ -38,35 +41,60 @@ public function __construct(User $user, array $deploymentInfo)
3841
$this->deploymentInfo = $deploymentInfo;
3942
}
4043

44+
/**
45+
* Get the tags that should be assigned to the job.
46+
*
47+
* @return array
48+
*/
49+
public function tags(): array
50+
{
51+
return [
52+
'deployment-notification',
53+
'user:'.$this->user->id,
54+
];
55+
}
56+
4157
/**
4258
* Execute the job.
4359
*
4460
* @return void
61+
*
62+
* @throws LimiterTimeoutException
4563
*/
4664
public function handle(): void
4765
{
48-
$server = new Fluent(Arr::get($this->deploymentInfo, 'server'));
49-
$site = new Fluent(Arr::get($this->deploymentInfo, 'site'));
50-
$commit = new Fluent([
51-
'hash' => Arr::get($this->deploymentInfo, 'commit_hash'),
52-
'url' => Arr::get($this->deploymentInfo, 'commit_url'),
53-
'author' => Arr::get($this->deploymentInfo, 'commit_author'),
54-
'message' => Arr::get($this->deploymentInfo, 'commit_message'),
55-
]);
66+
// From official documentation (https://core.telegram.org/bots/faq):
67+
// The API will not allow bulk notifications to more than ~30 users per second.
68+
// Also note that your bot will not be able to send more than 20 messages per minute to the same group.
69+
Redis::throttle('telegram-api')
70+
->allow(25)
71+
->every(1)
72+
->then(function () {
73+
$server = new Fluent(Arr::get($this->deploymentInfo, 'server'));
74+
$site = new Fluent(Arr::get($this->deploymentInfo, 'site'));
75+
$commit = new Fluent([
76+
'hash' => Arr::get($this->deploymentInfo, 'commit_hash'),
77+
'url' => Arr::get($this->deploymentInfo, 'commit_url'),
78+
'author' => Arr::get($this->deploymentInfo, 'commit_author'),
79+
'message' => Arr::get($this->deploymentInfo, 'commit_message'),
80+
]);
5681

57-
$message = [
58-
'*Deployment complete!*',
59-
"*Server:* [{$server->name}](https://forge.laravel.com/servers/{$server->id})",
60-
"*Site:* [{$site->name}](https://forge.laravel.com/servers/{$server->id}/sites/{$site->id})",
61-
"*Status:* {$this->getStatus()}",
62-
"*Commit author:* {$commit->author}",
63-
"*Commit hash:* [$commit->hash]($commit->url)",
64-
"*Commit message:* {$commit->message}",
65-
];
82+
$message = [
83+
'*Deployment complete!*',
84+
"*Server:* [{$server->name}](https://forge.laravel.com/servers/{$server->id})",
85+
"*Site:* [{$site->name}](https://forge.laravel.com/servers/{$server->id}/sites/{$site->id})",
86+
"*Status:* {$this->getStatus()}",
87+
"*Commit author:* {$commit->author}",
88+
"*Commit hash:* [$commit->hash]($commit->url)",
89+
"*Commit message:* {$commit->message}",
90+
];
6691

67-
OutboundMessage::make($this->user, implode("\n", $message))
68-
->parseMode(OutboundMessage::PARSE_MODE_MARKDOWN)
69-
->send();
92+
OutboundMessage::make($this->user, implode("\n", $message))
93+
->parseMode(OutboundMessage::PARSE_MODE_MARKDOWN)
94+
->send();
95+
}, function () {
96+
$this->release(10);
97+
});
7098
}
7199

72100
/**
@@ -89,4 +117,14 @@ protected function getStatus(): string
89117
return $status;
90118
}
91119
}
120+
121+
/**
122+
* Determine the time at which the job should timeout.
123+
*
124+
* @return Carbon
125+
*/
126+
public function retryUntil(): Carbon
127+
{
128+
return now()->addMinutes(10);
129+
}
92130
}

app/Jobs/SendMessageJob.php

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
namespace App\Jobs;
4+
5+
use App\Integrations\Telegram\Entities\OutboundMessage;
6+
use App\User;
7+
use Illuminate\Bus\Queueable;
8+
use Illuminate\Contracts\Queue\ShouldQueue;
9+
use Illuminate\Contracts\Redis\LimiterTimeoutException;
10+
use Illuminate\Foundation\Bus\Dispatchable;
11+
use Illuminate\Queue\InteractsWithQueue;
12+
use Illuminate\Queue\SerializesModels;
13+
use Illuminate\Support\Carbon;
14+
use Illuminate\Support\Facades\Redis;
15+
16+
class SendMessageJob implements ShouldQueue
17+
{
18+
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
19+
20+
/**
21+
* @var User
22+
*/
23+
public User $user;
24+
25+
/**
26+
* @var string
27+
*/
28+
public string $text;
29+
30+
/**
31+
* Create a new job instance.
32+
*
33+
* @param User $user
34+
* @param string $text
35+
* @return void
36+
*/
37+
public function __construct(User $user, string $text)
38+
{
39+
$this->user = $user;
40+
$this->text = $text;
41+
}
42+
43+
/**
44+
* Get the tags that should be assigned to the job.
45+
*
46+
* @return array
47+
*/
48+
public function tags(): array
49+
{
50+
return [
51+
'message',
52+
'user:'.$this->user->id,
53+
];
54+
}
55+
56+
/**
57+
* Execute the job.
58+
*
59+
* @return void
60+
*
61+
* @throws LimiterTimeoutException
62+
*/
63+
public function handle(): void
64+
{
65+
// From official documentation (https://core.telegram.org/bots/faq):
66+
// The API will not allow bulk notifications to more than ~30 users per second.
67+
// Also note that your bot will not be able to send more than 20 messages per minute to the same group.
68+
Redis::throttle('telegram-api')
69+
->allow(25)
70+
->every(1)
71+
->then(function () {
72+
OutboundMessage::make($this->user, $this->text)
73+
->parseMode(OutboundMessage::PARSE_MODE_MARKDOWN)
74+
->send();
75+
}, function () {
76+
$this->release(10);
77+
});
78+
}
79+
80+
/**
81+
* Determine the time at which the job should timeout.
82+
*
83+
* @return Carbon
84+
*/
85+
public function retryUntil(): Carbon
86+
{
87+
return now()->addMinutes(10);
88+
}
89+
}

app/Listeners/DeleteUser.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace App\Listeners;
4+
5+
use App\Events\BotWasBlocked;
6+
use App\User;
7+
use Exception;
8+
9+
class DeleteUser
10+
{
11+
/**
12+
* Handle the event.
13+
*
14+
* @param BotWasBlocked $event
15+
* @return void
16+
*
17+
* @throws Exception
18+
*/
19+
public function handle(BotWasBlocked $event)
20+
{
21+
$user = User::findByTelegramChatId($event->chatId);
22+
23+
if ($user) {
24+
$user->delete();
25+
}
26+
}
27+
}

app/Providers/EventServiceProvider.php

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,19 @@
22

33
namespace App\Providers;
44

5-
use Illuminate\Auth\Events\Registered;
6-
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
75
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
8-
use Illuminate\Support\Facades\Event;
96

107
class EventServiceProvider extends ServiceProvider
118
{
129
/**
13-
* The event listener mappings for the application.
10+
* Determine if events and listeners should be automatically discovered.
1411
*
15-
* @var array
12+
* @return bool
1613
*/
17-
protected $listen = [
18-
Registered::class => [
19-
SendEmailVerificationNotification::class,
20-
],
21-
];
14+
public function shouldDiscoverEvents()
15+
{
16+
return true;
17+
}
2218

2319
/**
2420
* Register any events for your application.

0 commit comments

Comments
 (0)