src/Library/TelegramBot/AbstractTelegramBot.php line 401

Open in your IDE?
  1. <?php
  2. namespace App\Library\TelegramBot;
  3. use App\Entity\TelegramMessage;
  4. use App\Entity\TelegramUser;
  5. use App\Exception\TelegramBot\BotWasKickedFromSupergroupChat;
  6. use App\Exception\TelegramBot\MessageCantBeDeleted;
  7. use App\Exception\TelegramBot\MessageCantBeDeletedForEveryone;
  8. use App\Exception\TelegramBot\MessageEditedBySameTextException;
  9. use App\Exception\TelegramBot\MessageIdentifierIsNotSpecified;
  10. use App\Exception\TelegramBot\MessageToDeleteNotFoundException;
  11. use App\Exception\TelegramBot\MessageToEditNotFoundException;
  12. use App\Library\TelegramBot\TelegramObject\Message;
  13. use App\Library\Utils\Other\Other;
  14. use App\Library\Utils\RemoteGetter;
  15. use App\Service\EntityManagerService;
  16. use App\Service\TelegramMessage\TelegramMessageService;
  17. use Doctrine\ORM\EntityManagerInterface;
  18. use Psr\Log\LoggerInterface;
  19. use App\Library\TelegramBot\TelegramObject\Update;
  20. abstract class AbstractTelegramBot
  21. {
  22.     private $apiToken;
  23.     private $baseUrl;
  24.     /**
  25.      * @var string[]
  26.      */
  27.     private $botTelegramIds = [];
  28.     /**
  29.      * @var LoggerInterface
  30.      */
  31.     protected $logger;
  32.     /**
  33.      * @var EntityManagerInterface
  34.      */
  35.     protected $em;
  36.     /**
  37.      * @var TelegramMessageService
  38.      */
  39.     protected $telegramMessageService;
  40.     /**
  41.      * @var EntityManagerService
  42.      */
  43.     protected $emService;
  44.     public function __construct(LoggerInterface $loggerEntityManagerInterface $em,
  45.                                 TelegramMessageService $telegramMessageServiceEntityManagerService $emService)
  46.     {
  47.         $this->logger $logger;
  48.         $this->em $em;
  49.         $this->telegramMessageService $telegramMessageService;
  50.         $this->emService $emService;
  51.         $this->logger->setHandlers([new \Monolog\Handler\StreamHandler(Other::getVarPath("log/telegram_bot.log"))]);
  52.     }
  53.     public function setApiToken($apiToken)
  54.     {
  55.         if (!$apiToken) {
  56.             throw new \Exception("API token is not set");
  57.         }
  58.         if (str_contains($apiToken"_____")) {
  59.             $apiToken substr($apiToken0strpos($apiToken"_____"));
  60.         }
  61.         $this->apiToken $apiToken;
  62.         $this->baseUrl "https://api.telegram.org/bot{$this->apiToken}/";
  63.     }
  64.     /**
  65.      * @param string[] $ids
  66.      * @return void
  67.      */
  68.     public function setBotTelegramIds(array $ids)
  69.     {
  70.         $this->botTelegramIds $ids;
  71.     }
  72.     public function isThisBotTelegramId(string $id): bool
  73.     {
  74.         return in_array($id$this->botTelegramIds);
  75.     }
  76.     /**
  77.      * Обработка полученных данных от бота (сообщения пользователей и т.д.).
  78.      * @param array $update
  79.      * @return void
  80.      * @throws \Exception
  81.      */
  82.     abstract public function handleUpdate($update);
  83.     /**
  84.      * @return void
  85.      */
  86.     abstract public function onUpdatesHandled();
  87.     /**
  88.      * @return void
  89.      */
  90.     abstract public function onMessageSend(TelegramMessage $message);
  91.     public function handleUpdates()
  92.     {
  93.         try {
  94.             $this->getAllUpdates(function ($updates) {
  95.                 foreach ($updates as $update) {
  96.                     $chatId $update["message"]["chat"]["id"] ?? null;
  97.                     $text $update["message"]["text"] ?? null;
  98.                     $this->logger->info("Received message from chat #$chatId$text");
  99.                     $updateObj = new Update($update);
  100.                     $this->_handleUpdate($updateObj);
  101.                     $this->handleUpdate($updateObj);
  102. //                    dd(1);
  103. //                    echo var_export($update, true); return;
  104.                 }
  105.             });
  106.         } catch (\Exception $exception) {
  107.             $this->logger->error($exception->getMessage());
  108.             dump(__FILE__ ":" __LINE__ ": "$exception);
  109.         };
  110.         $this->onUpdatesHandled();
  111.     }
  112.     public function sendInlineKeyboardMessage($chatId$text, array $buttons) {
  113.         $replyMarkup $this->createReplyMarkupFromButtonsArray($buttons);
  114.         return $this->sendMessage($chatId$text$replyMarkup);
  115.     }
  116.     public function createReplyMarkupFromButtonsArray(array $buttons): array
  117.     {
  118.         $inlineKeyboard = [];
  119.         foreach ($buttons as $key => $button) {
  120.             $inlineKeyboard[] = [
  121.                 [
  122.                     "text" => $button,
  123.                     "callback_data" => $key,
  124.                 ]
  125.             ];
  126.         }
  127.         $replyMarkup = [
  128.             'inline_keyboard' => $inlineKeyboard
  129.         ];
  130.         return $replyMarkup;
  131.     }
  132.     private function _handleUpdate(Update $update)
  133.     {
  134.         if ($this->isThisBotAddedToGroupUpdate($update) && $this->getUpdateMessageChatId($update)
  135.             && $this->getUpdateFromUserId($update))
  136.         {
  137.             $this->onBotAddedToGroup($update$this->getUpdateMessageChatId($update), $this->getUpdateFromUserId($update));
  138.         }
  139.     }
  140.     protected function onBotAddedToGroup(Update $updatestring $chatIdstring $userId)
  141.     {
  142.         // Override in child class if needed
  143.     }
  144.     private function isThisBotAddedToGroupUpdate(Update $update): bool
  145.     {
  146.         return $update->getMessage() && $update->getMessage()->getNewChatParticipant()
  147.             && $update->getMessage()->getNewChatParticipant()->getIsBot() === true
  148.             && $update->getMessage()->getNewChatParticipant()->getId()
  149.             && $this->isThisBotTelegramId($update->getMessage()->getNewChatParticipant()->getId());
  150.     }
  151.     public function getUpdateMessageChatId(Update $update): ?string
  152.     {
  153.         if ($update->getMessage() && $update->getMessage()->getChat() && $update->getMessage()->getChat()->getId()) {
  154.             return $update->getMessage()->getChat()->getId();
  155.         }
  156.         return null;
  157.     }
  158.     public function getUpdateFromUserId(Update $update): ?string
  159.     {
  160.         if ($update->getMessage() && $update->getMessage()->getFrom() && $update->getMessage()->getFrom()->getId()) {
  161.             return $update->getMessage()->getFrom()->getId();
  162.         }
  163.         return null;
  164.     }
  165.     private function getUpdates($offset null$limit null$timeout null)
  166.     {
  167.         $url $this->baseUrl 'getUpdates';
  168.         $params = [
  169.             'offset' => $offset,
  170.             'limit' => $limit,
  171.             'timeout' => $timeout,
  172.         ];
  173.         return $this->sendRequest($url$params)["result"];
  174.     }
  175.     private function getLatestUpdates($limit 1000)
  176.     {
  177.         $offset 0;
  178.         $updates = [];
  179.         while (true) {
  180.             $batch $this->getUpdates($offset$limit);
  181.             if (empty($batch)) {
  182.                 break;
  183.             }
  184.             $updates array_merge($updates$batch);
  185.             $offset end($batch)["update_id"] + 1;
  186.         }
  187.         return $updates;
  188.     }
  189.     public function getChatIds(array $updates)
  190.     {
  191.         $chatIds array_map(function ($update) {
  192.             return $update['message']['chat']['id'];
  193.         }, $updates);
  194.         $uniqueChatIds array_unique($chatIds);
  195.         return $uniqueChatIds;
  196.     }
  197.     /**
  198.      * @param int|array $chatId
  199.      * @param $text
  200.      * @throws \Exception
  201.      */
  202.     public function sendMessage($chatId$text$replyMarkup nullstring $imageFileId null): TelegramMessage
  203.     {
  204.         if (!is_array($chatId)) {
  205.             $chatId = [$chatId];
  206.         }
  207.         $result null;
  208.         try {
  209.             foreach ($chatId as $currentChatId) {
  210.                 $url $this->baseUrl 'sendMessage';
  211.                 $params array_merge(
  212.                     [
  213.                         'chat_id' => (int)$currentChatId,
  214.                     ]
  215.                 );
  216.                 if ($text) {
  217.                     $params['text'] = $text;
  218.                     $params['parse_mode'] = "HTML";
  219.                 }
  220.                 if ($imageFileId) {
  221.                     $url $this->baseUrl 'sendPhoto';
  222.                     $params['photo'] = $imageFileId;
  223.                 }
  224.                 if ($replyMarkup) {
  225.                     $params['reply_markup'] = json_encode($replyMarkup);
  226.                 }
  227.                 try {
  228.                     $result $this->sendRequest($url$params);
  229.                 } catch (\Exception $exception) {
  230.                     $this->trySendWarningMessage($exception, (int)$currentChatId);
  231.                     throw $exception;
  232.                 }
  233.                 $this->logger->info("Send message to chat #$currentChatId$text");
  234.             }
  235.         } catch (\Exception $exception) {
  236.             $this->logger->error($exception->getMessage());
  237.             throw $exception;
  238.         }
  239.         $message $this->telegramMessageService->createOrGet($result["result"]);
  240.         $this->onMessageSend($message);
  241.         return $message;
  242.     }
  243.     private function trySendWarningMessage(\Exception $exceptionint $chatId)
  244.     {
  245.         try {
  246.             $text "WARNING: Send message error: " substr($exception->getMessage(), 0100);
  247.             $this->sendMessage($chatId$text);
  248.         } catch (\Exception $exception) {
  249.             //do nothing
  250.         }
  251.     }
  252.     /**
  253.      * @param $text
  254.      * @param int|array $chatId
  255.      * @return void
  256.      * @throws \Exception
  257.      */
  258.     public function trySendMessage($text$chatId)
  259.     {
  260.         try {
  261.             $this->sendMessage($chatId$text);
  262.         } catch (\Exception $exception) {
  263.             //do nothing
  264.         }
  265.     }
  266.     private function sendRequest($url$params null$returnFile false,
  267.                                  string $filePath null, array $sendFilesData null)
  268.     {
  269.         $tunnelUrl "https://webiskit.ru/make-request";
  270.         $params = [
  271.             "url" => $url,
  272.             "post" => $params,
  273.             'returnFile' => $returnFile,
  274.             'sendFilesData' => $sendFilesData
  275.         ];
  276.         $url $tunnelUrl;
  277. //        $ch = curl_init($url);
  278. //
  279. //        if ($params) {
  280. //            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  281. //            curl_setopt($ch, CURLOPT_POST, 1);
  282. //            curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
  283. //        }
  284. //
  285. //        $result = curl_exec($ch);
  286. //
  287. //        if (curl_errno($ch)) {
  288. //            echo 'Error: ' . curl_error($ch);
  289. //        }
  290. //
  291. //        curl_close($ch);
  292. //
  293. //        $result = json_decode($result, true);
  294.         if ($returnFile) {
  295.             $result file_get_contents($urlfalsestream_context_create([
  296.                 'http' => [
  297.                     'method' => 'POST',
  298.                     'header' => "Content-Type: application/json\r\n",
  299.                     'content' => json_encode($params),
  300.                 ],
  301.                 'ssl' => [
  302.                     'verify_peer' => false,
  303.                     'verify_peer_name' => false,
  304.                 ]
  305.             ]));
  306.             file_put_contents($filePath$resultFILE_BINARY);
  307.         } else {
  308.             if ($sendFilesData) {
  309.                 Other::appendTestLog([array_key_last(array_flip(explode("/"__FILE__))) . ":" __LINE__,11111111111]);
  310. //                $result = RemoteGetter::get($url, $sendFilesData, true, false, null,
  311. //                    null, false, false, false, false);
  312. //                $result = RemoteGetter::get($url, ["data" => "image"], true, false, null,
  313. //                    null, false, false, false, true);
  314. //                $result = RemoteGetter::get($url, ["url" => "aaa"]);
  315.                 $ch curl_init($url);
  316.                 $params = [
  317.                     'sendFilesData[sendFilesData]' => new \CURLFile("/home/sariato/www/dev.centr.sariato.ru/public/images/message_photo/2944_ca71acde212c4eeffba2ffb82285b122.jpg"),
  318. //                    "file" => $params["sendFilesData"]["photo"] ?? null,
  319.                     "url" => $params["url"] ?? null
  320.                 ];
  321.                 curl_setopt($chCURLOPT_POSTtrue);
  322.                 curl_setopt($chCURLOPT_POSTFIELDS$params);
  323.                 curl_setopt($chCURLOPT_RETURNTRANSFERtrue); // ⚠️ important
  324.                 curl_setopt($chCURLOPT_SSL_VERIFYPEERfalse);
  325.                 curl_setopt($chCURLOPT_SSL_VERIFYHOSTfalse);
  326. //                curl_setopt($ch, CURLOPT_HTTPHEADER, [
  327. //                    'Content-Type: application/json'
  328. //                ]);
  329.                 $result curl_exec($ch);
  330.                 $st curl_getinfo($chCURLINFO_HTTP_CODE);
  331.                 $err null;
  332.                 if ($result === false) {
  333.                     $err curl_error($ch);
  334.                 }
  335.                 curl_close($ch);
  336.                 Other::appendTestLog([array_key_last(array_flip(explode("/"__FILE__))) . ":" __LINE__,2222222222$result$url$st$err]);
  337.             } else {
  338.                 $result RemoteGetter::get($url$paramstruefalsenull,
  339.                     nullfalsefalsefalsetrue);
  340.             }
  341.         }
  342. //        if ($returnFile) {
  343. //            $base64 = $result["file"]["base64"] ?? null;
  344. //            $extension = $result["file"]["extension"] ?? null;
  345. //            if (!$base64 || !$extension) {
  346. //                throw new \Exception("File data is not valid: " . json_encode($result));
  347. //            }
  348. //            $fileData = base64_decode($base64);
  349. //            $result = file_put_contents($filePath, $fileData, FILE_BINARY);
  350. //            if (!$result) {
  351. //                throw new \Exception("File did not downloaded");
  352. //            }
  353. //            return $result;
  354. //        }
  355.         if (!isset($result["ok"]) || !$result["ok"]) {
  356.             $error = (isset($result['description']) ? "Telegram bot error: " $result['description'] : "Unknown telegram bot error: " $result
  357.                 ". URL: " $url);
  358.             Other::appendTgBotLog([$errorOther::getBacktrace()]);
  359.             throw new \Exception($error);
  360.         }
  361.         return $result;
  362.     }
  363.     public function getAllUpdates(callable $callback$limit 100)
  364.     {
  365.         $offset 0;
  366.         $updates = [];
  367.         while (true) {
  368.             $batch $this->getUpdates($offset$limit);
  369.             if (empty($batch)) {
  370.                 break;
  371.             }
  372.             $updates array_merge($updates$batch);
  373.             $newOffset end($batch)['update_id'] + 1;
  374.             if ($newOffset !== $offset && is_callable($callback)) {
  375.                 $callback($updates);
  376.                 $offset $newOffset;
  377.             }
  378.         }
  379.         return $updates;
  380.     }
  381.     public function setWebhook($webhookUrl)
  382.     {
  383.         $url $this->baseUrl 'setWebhook';
  384.         $params = [
  385.             'url' => $webhookUrl,
  386.         ];
  387.         try {
  388.             $result $this->sendRequest($url$params);
  389.         } catch (\Exception $exception) {
  390.             $this->logger->error($exception->getMessage());
  391.             throw $exception;
  392.         }
  393.         return $result;
  394.     }
  395.     public function clearWebhook()
  396.     {
  397.         return $this->setWebhook(null);
  398.     }
  399.     public function deleteRemoteMessage(string $chatIdstring $messageId)
  400.     {
  401.         $url $this->baseUrl 'deleteMessage';
  402.         $params = [
  403.             'chat_id' => (int)$chatId,
  404.             'message_id' => (int)$messageId
  405.         ];
  406.         try {
  407.             $result $this->sendRequest($url$params);
  408.         } catch (\Exception $exception) {
  409.             $this->logger->error($exception->getMessage());
  410.             if ($exception->getMessage() == MessageToDeleteNotFoundException::message) {
  411.                 throw new MessageToDeleteNotFoundException();
  412.             }
  413.             if ($exception->getMessage() == MessageCantBeDeletedForEveryone::message) {
  414.                 throw new MessageCantBeDeletedForEveryone();
  415.             }
  416.             if ($exception->getMessage() == MessageCantBeDeleted::message) {
  417.                 throw new MessageCantBeDeleted();
  418.             }
  419.             if ($exception->getMessage() == MessageIdentifierIsNotSpecified::message) {
  420.                 throw new MessageIdentifierIsNotSpecified();
  421.             }
  422.             if ($exception->getMessage() == BotWasKickedFromSupergroupChat::message) {
  423.                 throw new BotWasKickedFromSupergroupChat();
  424.             }
  425.             throw $exception;
  426.         }
  427.         return $result;
  428.     }
  429.     public function sendImage($chatIdstring $imagePath): Message
  430.     {
  431.         if (!file_exists($imagePath)) {
  432.             throw new \Exception("Image file not found: " $imagePath);
  433.         }
  434.         $url $this->baseUrl 'sendPhoto';
  435.         $params = [
  436.             'chat_id' => $chatId,
  437.         ];
  438.         try {
  439.             $result $this->sendRequest($url$paramsfalsenull,
  440.                 [
  441.                     'photo' => new \CURLFile($imagePath),
  442.                 ]);
  443.         } catch (\Exception $exception) {
  444.             $this->logger->error($exception->getMessage());
  445.             throw $exception;
  446.         }
  447.         return new Message($result["result"]);
  448.     }
  449.     public function editRemoteMessage($chatId$messageIdstring $text null, array $replyMarkup nullstring $imageFileId null)
  450.     {
  451.         $url $this->baseUrl 'editMessageText';
  452.         $params = [
  453.             'chat_id' => (int)$chatId,
  454.             'message_id' => (int)$messageId,
  455.         ];
  456.         if ($text) {
  457.             $params['text'] = $text;
  458.             $params['parse_mode'] = "HTML";
  459.         }
  460.         if ($replyMarkup) {
  461.             $params['reply_markup'] = json_encode($replyMarkup);
  462.         }
  463.         if ($imageFileId) {
  464.             $url $this->baseUrl 'editMessageMedia';
  465.             $params['media'] = json_encode([
  466.                 'type' => 'photo',
  467.                 'media' => $imageFileId,
  468.             ]);
  469.         }
  470.         try {
  471.             $result $this->sendRequest($url$params);
  472.         } catch (\Exception $exception) {
  473.             $this->logger->error($exception->getMessage());
  474.             if (strpos($exception->getMessage(), MessageEditedBySameTextException::message)) {
  475.                 throw new MessageEditedBySameTextException();
  476.             }
  477.             if (strpos($exception->getMessage(), MessageToEditNotFoundException::message)) {
  478.                 throw new MessageToEditNotFoundException();
  479.             }
  480.             throw $exception;
  481.         }
  482.         return $result;
  483.     }
  484.     public function downloadMessagePhoto(Update $update)
  485.     {
  486.         $photoSizes $update->getMessage()->getPhoto();
  487.         if (!$photoSizes || !is_array($photoSizes) || count($photoSizes) == 0) {
  488.             throw new \Exception("No photo sizes found in message");
  489.         }
  490.         $fileId end($photoSizes)->getFileId();
  491.         if (!$fileId) {
  492.             throw new \Exception("No file id found in photo sizes");
  493.         }
  494.         $relativePath "images/message_photo/{$fileId}.*";
  495.         $existingFiles glob(Other::getRootPath("/public/" $relativePath));
  496.         if ($existingFiles && count($existingFiles) > 0) {
  497.             $relativePath str_replace(Other::getRootPath("/public/"), ""$existingFiles[0]);
  498.             return $relativePath;
  499.         }
  500.         $url $this->baseUrl 'getFile';
  501.         $params = [
  502.             'file_id' => $fileId,
  503.         ];
  504.         try {
  505.             $result $this->sendRequest($url$params);
  506.         } catch (\Exception $exception) {
  507.             $this->logger->error($exception->getMessage());
  508.             throw $exception;
  509.         }
  510.         $url "https://api.telegram.org/file/bot{$this->apiToken}/{$result["result"]["file_path"]}";
  511.         $ext null;
  512.         preg_match("/\.[^.]+$/"$url$ext);
  513.         $relativeImagePath "images/message_photo/" $fileId $ext[0];
  514.         $imagePath Other::getRootPath() . "/public/" $relativeImagePath;
  515.         $dir dirname($imagePath);
  516.         if (!is_dir($dir)) {
  517.             mkdir($dir0777true);
  518.         }
  519.         //rename
  520.         $this->sendRequest($urlnulltrue$imagePath);
  521.         return $relativeImagePath;
  522.     }
  523.     public function getUserProfileImage($userId): ?string
  524.     {
  525.         $url $this->baseUrl 'getUserProfilePhotos';
  526.         $params = [
  527.             'user_id' => (int)$userId,
  528.         ];
  529.         try {
  530.             $result $this->sendRequest($url$params);
  531.         } catch (\Exception $exception) {
  532.             $this->logger->error($exception->getMessage());
  533.             throw $exception;
  534.         }
  535.         $totalCount $result['result']['total_count'];
  536.         if (!$totalCount) {
  537.             return null;
  538.         }
  539.         $fileId $result['result']['photos'][0][1]['file_id'];
  540.         $url $this->baseUrl 'getFile';
  541.         $params = [
  542.             'file_id' => $fileId,
  543.         ];
  544.         try {
  545.             $result $this->sendRequest($url$params);
  546.         } catch (\Exception $exception) {
  547.             $this->logger->error($exception->getMessage());
  548.             throw $exception;
  549.         }
  550.         $url "https://api.telegram.org/file/bot{$this->apiToken}/{$result["result"]["file_path"]}";
  551.         $ext null;
  552.         preg_match("/\.[^.]+$/"$url$ext);
  553.         $relativeImagePath "images/user_profile/" $userId $ext[0];
  554.         $imagePath Other::getRootPath("/public/") . $relativeImagePath;
  555.         $result file_put_contents($imagePathfile_get_contents($url), FILE_BINARY);
  556.         if (!$result) {
  557.             throw new \Exception("Image did not downloaded");
  558.         }
  559.         return $relativeImagePath;
  560.     }
  561.     /**
  562.      * @param $commandParam
  563.      * @param $type
  564.      * @param $chatId
  565.      * @param TelegramUser $user
  566.      * @param $from
  567.      * @param $to
  568.      * @param $canBeNegative
  569.      * @return bool|string
  570.      * @throws \Exception
  571.      */
  572.     protected function checkCommandParamType($commandParam$type$chatIdTelegramUser $user,
  573.                                              $from null$to null$canBeNegative false,
  574.                                              array $literals null$yesNoLiterals false)
  575.     {
  576.         if (mb_strtolower($commandParam"utf8") == "отмена") {
  577.             $this->sendMessage($chatId"Ввод отменен");
  578.             $user->setWaitBotCommandParam(null);
  579.             $this->emService->save($user);
  580.             return "canceled";
  581.         }
  582.         $yesLiterals null;
  583.         $noLiterals null;
  584.         if ($yesNoLiterals) {
  585.             $yesLiterals = ["да""д""yes""y"];
  586.             $noLiterals = ["нет""н""no""n"];
  587.             $yesNoLiterals array_merge($yesLiterals$noLiterals);
  588.             if ($yesNoLiterals) {
  589.                 $literals array_merge(($literals ?: []), ["да""нет"]);
  590.             }
  591.         }
  592.         if ($from === null && !$commandParam) {
  593.             $from 0;
  594.         }
  595.         $redoMessage "";
  596.         switch ($type) {
  597.             case "string":
  598.                 break;
  599.             case "int":
  600.                 if (!preg_match("/^\d+$/"$commandParam) ||
  601.                     ($from !== null && (int)$commandParam $from) ||
  602.                     ($to !== null && (int)$commandParam $to) ||
  603.                     (!$canBeNegative && (int)$commandParam 0))
  604.                 {
  605.                     $redoMessage "Введите целое число";
  606.                     $redoMessage .=
  607.                         ($from !== null " от " $from "") .
  608.                         ($to !== null " до " $to "");
  609.                 }
  610.                 break;
  611.             default:
  612.                 throw new \Exception("Unknown type: " $type);
  613.         }
  614.         if ($literals && !array_filter(array_merge($literals$yesNoLiterals), function ($current) use ($commandParam) {
  615.                 return mb_strtolower($current) == mb_strtolower($commandParam);
  616.             })) {
  617.             $redoMessage .= ($redoMessage ". " "") . "Введите одно из значений (без кавычек): " .
  618.                 array_reduce($literals, function ($all$current) {
  619.                     return $all .= ($all ", " "") . "\"" $current "\"";
  620.                 });
  621.         }
  622.         if ($redoMessage) {
  623.             $redoMessage .= ". Либо введите слово \"отмена\" (без кавычек) для отмены ввода";
  624.             $this->sendMessage($chatId$redoMessage);
  625.             return false;
  626.         }
  627.         if ($yesNoLiterals) {
  628.             return (in_array(mb_strtolower($commandParam), $yesLiterals) ? "yes" :
  629.                 (in_array(mb_strtolower($commandParam), $noLiterals) ? "no" :
  630.                     $commandParam));
  631.         }
  632.         return true;
  633.     }
  634.     public function initUserRequestDataParams(TelegramUser $user)
  635.     {
  636.         $waitBotCommandParamData $user->getWaitBotCommandParam();
  637.         if (!$waitBotCommandParamData) {
  638.             $waitBotCommandParamData = [];
  639.         }
  640.         if (isset($waitBotCommandParamData["userRequestData"])) {
  641.             throw new \Exception("Can't set userRequestData of user property waitBotCommandParam because it" .
  642.                 " is already set");
  643.         }
  644.         $waitBotCommandParamData["userRequestData"] = [
  645.             "currentParamIndex" => -1,
  646.             "enteredData" => [],
  647.         ];
  648.         $user->setWaitBotCommandParam($waitBotCommandParamData);
  649.         $this->emService->save($user);
  650.     }
  651.     public function getRequestNextDataFromUserOptions($options) {
  652.         return array_merge([
  653.             "dataNames" => [], //array|\Closure
  654.             // Example:
  655.             // Variant 1: ["name1", ...].
  656.             // Variant 2: [["name" => "name1", "description" => "descr1"], ...]
  657.             "askUserEnterDataText" => null,
  658.             "enterDataTextDescription" => null,
  659.             "onUserEnterCancel" => null,
  660.             "onCurrentDataEnterComplete" => null,
  661.             "onAllDataEnterComplete" => null,
  662.             "clearWaitBotCommandParamOnAllEnterComplete" => true,
  663.             "sendThankMessageOnAllEnteredData" => true,
  664.         ], $options);
  665.     }
  666.     public function requestNextDataFromUser(array &$sendMessagesTelegramUser $user$text$chatId,
  667.                                             array $options)
  668.     {
  669.         if (!is_array($options["dataNames"])) {
  670.             $options["dataNames"] = $options["dataNames"]();
  671.         } else {
  672.             foreach ($options["dataNames"] as &$dataName) {
  673.                 if (!is_array($dataName)) {
  674.                     $dataName = ["name" => $dataName];
  675.                 }
  676.             }
  677.         }
  678.         if (!$options["askUserEnterDataText"]) {
  679.             $options["askUserEnterDataText"] = "Введите: ";
  680.         }
  681.         if (!$user->getWaitBotCommandParam() || !isset($user->getWaitBotCommandParam()["userRequestData"])) {
  682.             throw new \Exception("userRequestData of user property waitBotCommandParam is not initialized" .
  683.                 ". Call initUserRequestDataParams method first");
  684.         }
  685.         $userRequestData $user->getWaitBotCommandParam()["userRequestData"];
  686.         $saveUserRequestData = function ($userRequestData) use ($user) {
  687.             $data $user->getWaitBotCommandParam();
  688.             $data['userRequestData'] = $userRequestData;
  689.             $user->setWaitBotCommandParam($data);
  690.             $this->emService->save($user);
  691.         };
  692.         $sendNextRequest = function ($paramName) use ($options, &$sendMessages, &$userRequestData$user,
  693.             $saveUserRequestData) {
  694.             $sendMessages[] = $options["askUserEnterDataText"] . $paramName["name"] .
  695.                 ($options["enterDataTextDescription"] ? ". " $options["enterDataTextDescription"] : "") .
  696.                 (isset($paramName["description"]) && $paramName["description"] ? ". {$paramName["description"]}"") .
  697.                 ". Либо введите слово \"отмена\""
  698.                 " (без кавычек) для отмены ввода";
  699.             $saveUserRequestData($userRequestData);
  700.         };
  701.         if ($userRequestData["currentParamIndex"] != -1) {
  702.             $result $this->checkCommandParamType($text"string"$chatId$user);
  703.             if ($result === false || $result === "canceled") {
  704.                 if ($options["onUserEnterCancel"]) {
  705.                     $options["onUserEnterCancel"]();
  706.                 }
  707.                 return;
  708.             }
  709.             $userRequestData["enteredData"][$options["dataNames"][$userRequestData["currentParamIndex"]]["name"]] = $text;
  710.             $userRequestData["currentParamIndex"]++;
  711.             $saveUserRequestData($userRequestData);
  712.             $sendMessages[] = "Принято: " $text;
  713.             if ($userRequestData["currentParamIndex"] >= count($options["dataNames"])) {
  714.                 if ($options["sendThankMessageOnAllEnteredData"]) {
  715.                     $sendMessages[] = "Все данные заполнены. Спасибо!";
  716.                 }
  717.                 $thrownException null;
  718.                 if ($options["onAllDataEnterComplete"]) {
  719.                     try {
  720.                         $options["onAllDataEnterComplete"]();
  721.                     } catch (\Exception $exception) {
  722.                         $thrownException $exception;
  723.                     }
  724.                 }
  725.                 if ($options["clearWaitBotCommandParamOnAllEnterComplete"]) {
  726.                     $user->setWaitBotCommandParam(null);
  727.                     $this->emService->save($user);
  728.                 } else {
  729.                     $data $user->getWaitBotCommandParam();
  730.                     unset($data["userRequestData"]);
  731.                     $user->setWaitBotCommandParam($data);
  732.                     $this->emService->save($user);
  733.                 }
  734.                 if ($thrownException) {
  735.                     throw $thrownException;
  736.                 }
  737.                 return;
  738.             } else {
  739.                 if ($options["onCurrentDataEnterComplete"]) {
  740.                     $options["onCurrentDataEnterComplete"]();
  741.                 }
  742.                 $sendNextRequest($options["dataNames"][$userRequestData["currentParamIndex"]]);
  743.             }
  744.         } else {
  745.             $userRequestData["currentParamIndex"]++;
  746.             $saveUserRequestData($userRequestData);
  747.             $sendNextRequest($options["dataNames"][$userRequestData["currentParamIndex"]]);
  748.         }
  749.     }
  750.     public function getBotCommandUpdateSample()
  751.     {
  752.         return array (
  753.             'update_id' => 954338453,
  754.             'message' =>
  755.                 array (
  756.                     'message_id' => 15,
  757.                     'from' =>
  758.                         array (
  759.                             'id' => "1720609_81",
  760.                             'is_bot' => false,
  761.                             'first_name' => 'Александр',
  762.                             'last_name' => 'Орлов',
  763.                             'username' => 'sariato',
  764.                             'language_code' => 'ru',
  765.                         ),
  766.                     'chat' =>
  767.                         array (
  768.                             'id' => "17206_0981",
  769.                             'first_name' => 'Александр',
  770.                             'last_name' => 'Орлов',
  771.                             'username' => 'sariato',
  772.                             'type' => 'private',
  773.                         ),
  774.                     'date' => 1707404913,
  775.                     'text' => '/sdfsf',
  776.                     'entities' =>
  777.                         array (
  778.                             =>
  779.                                 array (
  780.                                     'offset' => 0,
  781.                                     'length' => 6,
  782.                                     'type' => 'bot_command',
  783.                                 ),
  784.                         ),
  785.                 ),
  786.         );
  787.     }
  788.     public function createBotCommandUpdate($userId$command)
  789.     {
  790.         return array (
  791.             'update_id' => 0,
  792.             'message' =>
  793.                 array (
  794.                     'message_id' => 0,
  795.                     'from' =>
  796.                         array (
  797.                             'id' => $userId,
  798.                             'is_bot' => false,
  799.                             'first_name' => '',
  800.                             'last_name' => '',
  801.                             'username' => '',
  802.                             'language_code' => 'ru',
  803.                         ),
  804.                     'chat' =>
  805.                         array (
  806.                             'id' => $userId,
  807.                             'first_name' => '',
  808.                             'last_name' => '',
  809.                             'username' => '',
  810.                             'type' => 'private',
  811.                         ),
  812.                     'date' => (new \DateTime())->getTimestamp(),
  813.                     'text' => '/' $command,
  814.                     'entities' =>
  815.                         array (
  816.                             =>
  817.                                 array (
  818.                                     'offset' => 0,
  819.                                     'length' => 6,
  820.                                     'type' => 'bot_command',
  821.                                 ),
  822.                         ),
  823.                 ),
  824.         );
  825.     }
  826.     public function sendFile($chatId$filePath$caption null$fileType 'document')
  827.     {
  828.         if (!file_exists($filePath)) {
  829.             throw new \Exception("File does not exist: {$filePath}");
  830.         }
  831.         $url $this->baseUrl "send{$fileType}";
  832.         $params = [
  833.             'chat_id' => (int)$chatId,
  834.             $fileType => new \CURLFile(realpath($filePath)),
  835.         ];
  836.         if ($caption) {
  837.             $params['caption'] = $caption;
  838.         }
  839.         try {
  840.             $result $this->sendRequest($url$params);
  841.         } catch (\Exception $exception) {
  842.             $this->logger->error($exception->getMessage());
  843.             throw $exception;
  844.         }
  845.         return $result;
  846.     }
  847. }