Skip to content

Commit 46855c0

Browse files
authored
Merge pull request #43 from noplanman/19-rule_approval
Rules approval for new users
2 parents fcbdbc3 + 7711914 commit 46855c0

9 files changed

+231
-22
lines changed

.env.example

+4
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ TG_GITHUB_AUTH_TOKEN='github-token'
4242
# Telegram Payments
4343
TG_PAYMENT_PROVIDER_TOKEN='123:TEST:abc'
4444

45+
# Support group activation expiry and ban times
46+
TG_SUPPORT_GROUP_ACTIVATION_EXPIRE_TIME='15 min'
47+
TG_SUPPORT_GROUP_BAN_TIME='1 day'
48+
4549
# URLs
4650
TG_URL_DONATE='https://...'
4751
TG_URL_PATREON='https://...'

commands/CallbackqueryCommand.php

+2-5
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,8 @@ public function execute(): ServerResponse
5151
parse_str($callback_query->getData(), $callback_data);
5252

5353
if ('donate' === $callback_data['command']) {
54-
DonateCommand::createPaymentInvoice(
55-
$callback_query->getFrom()->getId(),
56-
$callback_data['amount'],
57-
$callback_data['currency']
58-
);
54+
return DonateCommand::handleCallbackQuery($callback_query, $callback_data);
55+
}
5956

6057
if ('rules' === $callback_data['command']) {
6158
return RulesCommand::handleCallbackQuery($callback_query, $callback_data);

commands/DonateCommand.php

+22
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use JsonException;
1717
use LitEmoji\LitEmoji;
1818
use Longman\TelegramBot\Commands\UserCommand;
19+
use Longman\TelegramBot\Entities\CallbackQuery;
1920
use Longman\TelegramBot\Entities\InlineKeyboard;
2021
use Longman\TelegramBot\Entities\Payments\LabeledPrice;
2122
use Longman\TelegramBot\Entities\Payments\SuccessfulPayment;
@@ -57,6 +58,27 @@ class DonateCommand extends UserCommand
5758
*/
5859
protected $private_only = true;
5960

61+
/**
62+
* Handle the callback queries regarding the /donate command.
63+
*
64+
* @param CallbackQuery $callback_query
65+
* @param array $callback_data
66+
*
67+
* @return ServerResponse
68+
*/
69+
public static function handleCallbackQuery(CallbackQuery $callback_query, array $callback_data): ServerResponse
70+
{
71+
self::createPaymentInvoice(
72+
$callback_query->getFrom()->getId(),
73+
$callback_data['amount'],
74+
$callback_data['currency']
75+
);
76+
77+
return $callback_query->answer([
78+
'text' => 'Awesome, an invoice has been sent to you.',
79+
]);
80+
}
81+
6082
/**
6183
* @return ServerResponse
6284
* @throws TelegramException

commands/GenericmessageCommand.php

+18
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Longman\TelegramBot\Commands\UserCommands\DonateCommand;
1818
use Longman\TelegramBot\Entities\ServerResponse;
1919
use Longman\TelegramBot\Exception\TelegramException;
20+
use Longman\TelegramBot\Request;
2021

2122
/**
2223
* Generic message command
@@ -51,8 +52,12 @@ public function execute(): ServerResponse
5152

5253
// Handle new chat members.
5354
if ($message->getNewChatMembers()) {
55+
$this->deleteThisMessage(); // Service message.
5456
return $this->getTelegram()->executeCommand('newchatmembers');
5557
}
58+
if ($message->getLeftChatMember()) {
59+
$this->deleteThisMessage(); // Service message.
60+
}
5661

5762
// Handle successful payment of donation.
5863
if ($payment = $message->getSuccessfulPayment()) {
@@ -66,4 +71,17 @@ public function execute(): ServerResponse
6671

6772
return parent::execute();
6873
}
74+
75+
/**
76+
* Delete the current message.
77+
*
78+
* @return ServerResponse
79+
*/
80+
private function deleteThisMessage(): ServerResponse
81+
{
82+
return Request::deleteMessage([
83+
'chat_id' => $this->getMessage()->getChat()->getId(),
84+
'message_id' => $this->getMessage()->getMessageId(),
85+
]);
86+
}
6987
}

commands/NewchatmembersCommand.php

+74-6
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@
1515

1616
use LitEmoji\LitEmoji;
1717
use Longman\TelegramBot\Commands\SystemCommand;
18+
use Longman\TelegramBot\DB;
1819
use Longman\TelegramBot\Entities\ChatMember;
20+
use Longman\TelegramBot\Entities\ChatPermissions;
1921
use Longman\TelegramBot\Entities\InlineKeyboard;
22+
use Longman\TelegramBot\Entities\Message;
2023
use Longman\TelegramBot\Entities\ServerResponse;
2124
use Longman\TelegramBot\Entities\User;
2225
use Longman\TelegramBot\Exception\TelegramException;
@@ -43,6 +46,11 @@ class NewchatmembersCommand extends SystemCommand
4346
*/
4447
protected $version = '0.5.0';
4548

49+
/**
50+
* @var Message
51+
*/
52+
private $message;
53+
4654
/**
4755
* @var int
4856
*/
@@ -64,17 +72,23 @@ class NewchatmembersCommand extends SystemCommand
6472
*/
6573
public function execute(): ServerResponse
6674
{
67-
$message = $this->getMessage();
68-
$this->chat_id = $message->getChat()->getId();
69-
$this->user_id = $message->getFrom()->getId();
75+
$this->message = $this->getMessage();
76+
$this->chat_id = $this->message->getChat()->getId();
77+
$this->user_id = $this->message->getFrom()->getId();
7078

71-
$this->group_name = $message->getChat()->getTitle();
79+
$this->group_name = $this->message->getChat()->getTitle();
7280

7381
['users' => $new_users, 'bots' => $new_bots] = $this->getNewUsersAndBots();
7482

7583
// Kick bots if they weren't added by an admin.
7684
$this->kickDisallowedBots($new_bots);
7785

86+
// Restrict all permissions for new users.
87+
$this->restrictNewUsers($new_users);
88+
89+
// Set the joined date for all new group members.
90+
$this->updateUsersJoinedDate($new_users);
91+
7892
return $this->refreshWelcomeMessage($new_users);
7993
}
8094

@@ -106,7 +120,7 @@ private function refreshWelcomeMessage(array $new_users): ServerResponse
106120
'disable_web_page_preview' => true,
107121
'disable_notification' => true,
108122
'reply_markup' => new InlineKeyboard([
109-
['text' => LitEmoji::encodeUnicode(':orange_book: Read the Rules'), 'url' => 'https://t.me/PHP_Telegram_Support_Bot?start=rules'],
123+
['text' => LitEmoji::encodeUnicode(':orange_book: Read the Rules'), 'url' => 'https://t.me/' . getenv('TG_BOT_USERNAME') . '?start=rules'],
110124
]),
111125
]
112126
);
@@ -156,7 +170,7 @@ private function getNewUsersAndBots(): array
156170
$users = [];
157171
$bots = [];
158172

159-
foreach ($this->getMessage()->getNewChatMembers() as $member) {
173+
foreach ($this->message->getNewChatMembers() as $member) {
160174
if ($member->getIsBot()) {
161175
$bots[] = $member;
162176
continue;
@@ -188,4 +202,58 @@ private function kickDisallowedBots(array $bots): void
188202
]);
189203
}
190204
}
205+
206+
/**
207+
* Write users join date to DB.
208+
*
209+
* @param array $new_users
210+
*
211+
* @return bool
212+
*/
213+
private function updateUsersJoinedDate($new_users): bool
214+
{
215+
$new_users_ids = array_map(static function (User $user) {
216+
return $user->getId();
217+
}, $new_users);
218+
219+
// Update "Joined Date" for new users.
220+
return DB::getPdo()->prepare("
221+
UPDATE " . TB_USER . "
222+
SET `joined_at` = ?
223+
WHERE `id` IN (?)
224+
")->execute([date('Y-m-d H:i:s'), implode(',', $new_users_ids)]);
225+
}
226+
227+
/**
228+
* Restrict permissions in support group for passed users.
229+
*
230+
* @param array $new_users
231+
*
232+
* @return array
233+
*/
234+
private function restrictNewUsers($new_users): array
235+
{
236+
$responses = [];
237+
238+
/** @var User[] $new_users */
239+
foreach ($new_users as $new_user) {
240+
$user_id = $new_user->getId();
241+
$responses[$user_id] = Request::restrictChatMember([
242+
'chat_id' => getenv('TG_SUPPORT_GROUP_ID'),
243+
'user_id' => $user_id,
244+
'permissions' => new ChatPermissions([
245+
'can_send_messages' => false,
246+
'can_send_media_messages' => false,
247+
'can_send_polls' => false,
248+
'can_send_other_messages' => false,
249+
'can_add_web_page_previews' => false,
250+
'can_change_info' => false,
251+
'can_invite_users' => false,
252+
'can_pin_messages' => false,
253+
]),
254+
]);
255+
}
256+
257+
return $responses;
258+
}
191259
}

commands/RulesCommand.php

+18-11
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,7 @@ public static function handleCallbackQuery(CallbackQuery $callback_query, array
7474
]);
7575
}
7676

77-
Request::editMessageReplyMarkup([
78-
'chat_id' => $chat_id,
79-
'message_id' => $message->getMessageId(),
80-
'reply_markup' => new InlineKeyboard([
81-
['text' => LitEmoji::encodeUnicode(':white_check_mark: Ok! Go to Bot Support group...'), 'url' => 'https://t.me/PHP_Telegram_Bot_Support'],
82-
]),
83-
]);
84-
85-
Request::restrictChatMember([
77+
$give_permissions = Request::restrictChatMember([
8678
'chat_id' => getenv('TG_SUPPORT_GROUP_ID'),
8779
'user_id' => $clicked_user_id,
8880
'permissions' => new ChatPermissions([
@@ -93,6 +85,14 @@ public static function handleCallbackQuery(CallbackQuery $callback_query, array
9385
]),
9486
]);
9587

88+
Request::editMessageReplyMarkup([
89+
'chat_id' => $chat_id,
90+
'message_id' => $message->getMessageId(),
91+
'reply_markup' => new InlineKeyboard([
92+
['text' => LitEmoji::encodeUnicode(':white_check_mark: Ok! Go to Bot Support group...'), 'url' => 'https://t.me/' . getenv('TG_SUPPORT_GROUP_ID')],
93+
]),
94+
]);
95+
9696
return $callback_query->answer([
9797
'text' => 'Thanks for agreeing to the rules. You may now post in the support group.',
9898
'show_alert' => true,
@@ -127,7 +127,7 @@ public function execute(): ServerResponse
127127
'disable_web_page_preview' => true,
128128
];
129129

130-
if (!self::userHasAgreedToRules($this->getMessage()->getFrom()->getId())) {
130+
if (!self::hasUserAgreedToRules($this->getMessage()->getFrom()->getId())) {
131131
$text .= PHP_EOL . 'You **must agree** to these rules to post in the support group. Simply click the button below.';
132132
$data['reply_markup'] = new InlineKeyboard([
133133
['text' => LitEmoji::encodeUnicode(':+1: I Agree to the Rules'), 'callback_data' => 'command=rules&action=agree'],
@@ -137,7 +137,14 @@ public function execute(): ServerResponse
137137
return $this->replyToChat(LitEmoji::encodeUnicode($text), $data);
138138
}
139139

140-
protected static function userHasAgreedToRules(int $user_id): bool
140+
/**
141+
* Check if the passed user has agreed to the rules.
142+
*
143+
* @param int $user_id
144+
*
145+
* @return bool
146+
*/
147+
protected static function hasUserAgreedToRules(int $user_id): bool
141148
{
142149
$statement = DB::getPdo()->prepare('
143150
SELECT `activated_at`

cron.php

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the PHP Telegram Support Bot.
5+
*
6+
* (c) PHP Telegram Bot Team (https://github.com/php-telegram-bot)
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace TelegramBot\SupportBot;
15+
16+
use Dotenv\Dotenv;
17+
use Longman\TelegramBot\Telegram;
18+
use Longman\TelegramBot\TelegramLog;
19+
20+
// Composer autoloader.
21+
require_once __DIR__ . '/vendor/autoload.php';
22+
Dotenv::create(__DIR__)->load();
23+
24+
try {
25+
$telegram = new Telegram(getenv('TG_API_KEY'), getenv('TG_BOT_USERNAME'));
26+
$telegram->enableMySql([
27+
'host' => getenv('TG_DB_HOST'),
28+
'port' => getenv('TG_DB_PORT'),
29+
'user' => getenv('TG_DB_USER'),
30+
'password' => getenv('TG_DB_PASSWORD'),
31+
'database' => getenv('TG_DB_DATABASE'),
32+
]);
33+
34+
// Handle expired activations.
35+
Helpers::handleExpiredActivations();
36+
} catch (\Throwable $e) {
37+
TelegramLog::error($e->getMessage());
38+
}

src/Helpers.php

+52
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,56 @@ public static function saveLatestWelcomeMessage($welcome_message_id): void
102102
$new_welcome_message_ids = array_values($welcome_message_ids) + ['latest' => $welcome_message_id];
103103
self::setSimpleOption('welcome_message_ids', $new_welcome_message_ids);
104104
}
105+
106+
/**
107+
* Handle expired activations and kick those users.
108+
*/
109+
public static function handleExpiredActivations(): void
110+
{
111+
$expiry_time = strtotime(getenv('TG_SUPPORT_GROUP_ACTIVATION_EXPIRE_TIME') ?: '15 min');
112+
$expiry_time_in_s = $expiry_time - time();
113+
114+
// If the user is already activated, keep the initial activation date.
115+
$users_to_kick = DB::getPdo()->query("
116+
SELECT `id`
117+
FROM " . TB_USER . "
118+
WHERE `joined_at` < (NOW() - INTERVAL {$expiry_time_in_s} SECOND)
119+
AND `activated_at` IS NULL
120+
");
121+
foreach ($users_to_kick as $user_to_kick) {
122+
self::kickUser((int) $user_to_kick['id']);
123+
}
124+
}
125+
126+
/**
127+
* Kick the passed user.
128+
*
129+
* @param int $user_id
130+
*
131+
* @return bool
132+
*/
133+
protected static function kickUser(int $user_id): bool
134+
{
135+
try {
136+
$ban_time = strtotime(getenv('TG_SUPPORT_GROUP_BAN_TIME') ?: '1 day');
137+
$kick_user = Request::kickChatMember([
138+
'chat_id' => getenv('TG_SUPPORT_GROUP_ID'),
139+
'user_id' => $user_id,
140+
'until_date' => $ban_time,
141+
]);
142+
if ($kick_user->isOk()) {
143+
return DB::getPdo()->prepare("
144+
UPDATE " . TB_USER . "
145+
SET `activated_at` = NULL,
146+
`joined_at` = NULL,
147+
`kicked_at` = NOW()
148+
WHERE `id` = ?
149+
")->execute([$user_id]);
150+
}
151+
} catch (\Throwable $e) {
152+
// Fail silently.
153+
}
154+
155+
return false;
156+
}
105157
}

structure.sql

+3
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,7 @@ CREATE TABLE IF NOT EXISTS `simple_options` (
99
UNIQUE KEY `name` (`name`)
1010
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;
1111

12+
-- Version Unreleased
13+
ALTER TABLE `user` ADD COLUMN `joined_at` TIMESTAMP NULL COMMENT 'Timestamp when the user joined the support group.';
14+
ALTER TABLE `user` ADD COLUMN `kicked_at` TIMESTAMP NULL COMMENT 'Timestamp when the user was kicked from the support group.';
1215
ALTER TABLE `user` ADD COLUMN `activated_at` TIMESTAMP NULL COMMENT 'Timestamp when the user has agreed to the rules and has been allowed to post in the support group.';

0 commit comments

Comments
 (0)