src/Library/TelegramBot/AbstractTelegramBot.php line 302

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)
  268.     {
  269.         $tunnelUrl "https://webiskit.ru/make-request";
  270.         $params = [
  271.             "url" => $url,
  272.             "post" => $params,
  273.             'returnFile' => $returnFile,
  274.         ];
  275.         $url $tunnelUrl;
  276. //        $ch = curl_init($url);
  277. //
  278. //        if ($params) {
  279. //            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  280. //            curl_setopt($ch, CURLOPT_POST, 1);
  281. //            curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
  282. //        }
  283. //
  284. //        $result = curl_exec($ch);
  285. //
  286. //        if (curl_errno($ch)) {
  287. //            echo 'Error: ' . curl_error($ch);
  288. //        }
  289. //
  290. //        curl_close($ch);
  291. //
  292. //        $result = json_decode($result, true);
  293.         $asJson = !Other::hasArrayCurlFile($params);
  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.             Other::appendTestLog([array_key_last(array_flip(explode("/"__FILE__))) . ":" __LINE__,
  309.                 $params]);
  310.             $result RemoteGetter::get($url$paramstruefalsenull,
  311.                 nullfalsefalsefalse$asJson);
  312.         }
  313. //        if ($returnFile) {
  314. //            $base64 = $result["file"]["base64"] ?? null;
  315. //            $extension = $result["file"]["extension"] ?? null;
  316. //            if (!$base64 || !$extension) {
  317. //                throw new \Exception("File data is not valid: " . json_encode($result));
  318. //            }
  319. //            $fileData = base64_decode($base64);
  320. //            $result = file_put_contents($filePath, $fileData, FILE_BINARY);
  321. //            if (!$result) {
  322. //                throw new \Exception("File did not downloaded");
  323. //            }
  324. //            return $result;
  325. //        }
  326.         if (!isset($result["ok"]) || !$result["ok"]) {
  327.             $error = (isset($result['description']) ? "Telegram bot error: " $result['description'] : "Unknown telegram bot error: " $result
  328.                 ". URL: " $url);
  329.             Other::appendTgBotLog([$errorOther::getBacktrace()]);
  330.             throw new \Exception($error);
  331.         }
  332.         return $result;
  333.     }
  334.     public function getAllUpdates(callable $callback$limit 100)
  335.     {
  336.         $offset 0;
  337.         $updates = [];
  338.         while (true) {
  339.             $batch $this->getUpdates($offset$limit);
  340.             if (empty($batch)) {
  341.                 break;
  342.             }
  343.             $updates array_merge($updates$batch);
  344.             $newOffset end($batch)['update_id'] + 1;
  345.             if ($newOffset !== $offset && is_callable($callback)) {
  346.                 $callback($updates);
  347.                 $offset $newOffset;
  348.             }
  349.         }
  350.         return $updates;
  351.     }
  352.     public function setWebhook($webhookUrl)
  353.     {
  354.         $url $this->baseUrl 'setWebhook';
  355.         $params = [
  356.             'url' => $webhookUrl,
  357.         ];
  358.         try {
  359.             $result $this->sendRequest($url$params);
  360.         } catch (\Exception $exception) {
  361.             $this->logger->error($exception->getMessage());
  362.             throw $exception;
  363.         }
  364.         return $result;
  365.     }
  366.     public function clearWebhook()
  367.     {
  368.         return $this->setWebhook(null);
  369.     }
  370.     public function deleteRemoteMessage(string $chatIdstring $messageId)
  371.     {
  372.         $url $this->baseUrl 'deleteMessage';
  373.         $params = [
  374.             'chat_id' => (int)$chatId,
  375.             'message_id' => (int)$messageId
  376.         ];
  377.         try {
  378.             $result $this->sendRequest($url$params);
  379.         } catch (\Exception $exception) {
  380.             $this->logger->error($exception->getMessage());
  381.             if ($exception->getMessage() == MessageToDeleteNotFoundException::message) {
  382.                 throw new MessageToDeleteNotFoundException();
  383.             }
  384.             if ($exception->getMessage() == MessageCantBeDeletedForEveryone::message) {
  385.                 throw new MessageCantBeDeletedForEveryone();
  386.             }
  387.             if ($exception->getMessage() == MessageCantBeDeleted::message) {
  388.                 throw new MessageCantBeDeleted();
  389.             }
  390.             if ($exception->getMessage() == MessageIdentifierIsNotSpecified::message) {
  391.                 throw new MessageIdentifierIsNotSpecified();
  392.             }
  393.             if ($exception->getMessage() == BotWasKickedFromSupergroupChat::message) {
  394.                 throw new BotWasKickedFromSupergroupChat();
  395.             }
  396.             throw $exception;
  397.         }
  398.         return $result;
  399.     }
  400.     public function sendImage($chatIdstring $imagePath): Message
  401.     {
  402.         if (!file_exists($imagePath)) {
  403.             throw new \Exception("Image file not found: " $imagePath);
  404.         }
  405.         $url $this->baseUrl 'sendPhoto';
  406.         $params = [
  407.             'chat_id' => $chatId,
  408.             'photo' => new \CURLFile($imagePath),
  409.         ];
  410.         try {
  411.             $result $this->sendRequest($url$params);
  412.         } catch (\Exception $exception) {
  413.             $this->logger->error($exception->getMessage());
  414.             throw $exception;
  415.         }
  416.         return new Message($result["result"]);
  417.     }
  418.     public function editRemoteMessage($chatId$messageIdstring $text null, array $replyMarkup nullstring $imageFileId null)
  419.     {
  420.         $url $this->baseUrl 'editMessageText';
  421.         $params = [
  422.             'chat_id' => (int)$chatId,
  423.             'message_id' => (int)$messageId,
  424.         ];
  425.         if ($text) {
  426.             $params['text'] = $text;
  427.             $params['parse_mode'] = "HTML";
  428.         }
  429.         if ($replyMarkup) {
  430.             $params['reply_markup'] = json_encode($replyMarkup);
  431.         }
  432.         if ($imageFileId) {
  433.             $url $this->baseUrl 'editMessageMedia';
  434.             $params['media'] = json_encode([
  435.                 'type' => 'photo',
  436.                 'media' => $imageFileId,
  437.             ]);
  438.         }
  439.         try {
  440.             $result $this->sendRequest($url$params);
  441.         } catch (\Exception $exception) {
  442.             $this->logger->error($exception->getMessage());
  443.             if (strpos($exception->getMessage(), MessageEditedBySameTextException::message)) {
  444.                 throw new MessageEditedBySameTextException();
  445.             }
  446.             if (strpos($exception->getMessage(), MessageToEditNotFoundException::message)) {
  447.                 throw new MessageToEditNotFoundException();
  448.             }
  449.             throw $exception;
  450.         }
  451.         return $result;
  452.     }
  453.     public function downloadMessagePhoto(Update $update)
  454.     {
  455.         $photoSizes $update->getMessage()->getPhoto();
  456.         if (!$photoSizes || !is_array($photoSizes) || count($photoSizes) == 0) {
  457.             throw new \Exception("No photo sizes found in message");
  458.         }
  459.         $fileId end($photoSizes)->getFileId();
  460.         if (!$fileId) {
  461.             throw new \Exception("No file id found in photo sizes");
  462.         }
  463.         $relativePath "images/message_photo/{$fileId}.*";
  464.         $existingFiles glob(Other::getRootPath("/public/" $relativePath));
  465.         if ($existingFiles && count($existingFiles) > 0) {
  466.             $relativePath str_replace(Other::getRootPath("/public/"), ""$existingFiles[0]);
  467.             return $relativePath;
  468.         }
  469.         $url $this->baseUrl 'getFile';
  470.         $params = [
  471.             'file_id' => $fileId,
  472.         ];
  473.         try {
  474.             $result $this->sendRequest($url$params);
  475.         } catch (\Exception $exception) {
  476.             $this->logger->error($exception->getMessage());
  477.             throw $exception;
  478.         }
  479.         $url "https://api.telegram.org/file/bot{$this->apiToken}/{$result["result"]["file_path"]}";
  480.         $ext null;
  481.         preg_match("/\.[^.]+$/"$url$ext);
  482.         $relativeImagePath "images/message_photo/" $fileId $ext[0];
  483.         $imagePath Other::getRootPath() . "/public/" $relativeImagePath;
  484.         $dir dirname($imagePath);
  485.         if (!is_dir($dir)) {
  486.             mkdir($dir0777true);
  487.         }
  488.         //rename
  489.         $this->sendRequest($urlnulltrue$imagePath);
  490.         return $relativeImagePath;
  491.     }
  492.     public function getUserProfileImage($userId): ?string
  493.     {
  494.         $url $this->baseUrl 'getUserProfilePhotos';
  495.         $params = [
  496.             'user_id' => (int)$userId,
  497.         ];
  498.         try {
  499.             $result $this->sendRequest($url$params);
  500.         } catch (\Exception $exception) {
  501.             $this->logger->error($exception->getMessage());
  502.             throw $exception;
  503.         }
  504.         $totalCount $result['result']['total_count'];
  505.         if (!$totalCount) {
  506.             return null;
  507.         }
  508.         $fileId $result['result']['photos'][0][1]['file_id'];
  509.         $url $this->baseUrl 'getFile';
  510.         $params = [
  511.             'file_id' => $fileId,
  512.         ];
  513.         try {
  514.             $result $this->sendRequest($url$params);
  515.         } catch (\Exception $exception) {
  516.             $this->logger->error($exception->getMessage());
  517.             throw $exception;
  518.         }
  519.         $url "https://api.telegram.org/file/bot{$this->apiToken}/{$result["result"]["file_path"]}";
  520.         $ext null;
  521.         preg_match("/\.[^.]+$/"$url$ext);
  522.         $relativeImagePath "images/user_profile/" $userId $ext[0];
  523.         $imagePath Other::getRootPath("/public/") . $relativeImagePath;
  524.         $result file_put_contents($imagePathfile_get_contents($url), FILE_BINARY);
  525.         if (!$result) {
  526.             throw new \Exception("Image did not downloaded");
  527.         }
  528.         return $relativeImagePath;
  529.     }
  530.     /**
  531.      * @param $commandParam
  532.      * @param $type
  533.      * @param $chatId
  534.      * @param TelegramUser $user
  535.      * @param $from
  536.      * @param $to
  537.      * @param $canBeNegative
  538.      * @return bool|string
  539.      * @throws \Exception
  540.      */
  541.     protected function checkCommandParamType($commandParam$type$chatIdTelegramUser $user,
  542.                                              $from null$to null$canBeNegative false,
  543.                                              array $literals null$yesNoLiterals false)
  544.     {
  545.         if (mb_strtolower($commandParam"utf8") == "отмена") {
  546.             $this->sendMessage($chatId"Ввод отменен");
  547.             $user->setWaitBotCommandParam(null);
  548.             $this->emService->save($user);
  549.             return "canceled";
  550.         }
  551.         $yesLiterals null;
  552.         $noLiterals null;
  553.         if ($yesNoLiterals) {
  554.             $yesLiterals = ["да""д""yes""y"];
  555.             $noLiterals = ["нет""н""no""n"];
  556.             $yesNoLiterals array_merge($yesLiterals$noLiterals);
  557.             if ($yesNoLiterals) {
  558.                 $literals array_merge(($literals ?: []), ["да""нет"]);
  559.             }
  560.         }
  561.         if ($from === null && !$commandParam) {
  562.             $from 0;
  563.         }
  564.         $redoMessage "";
  565.         switch ($type) {
  566.             case "string":
  567.                 break;
  568.             case "int":
  569.                 if (!preg_match("/^\d+$/"$commandParam) ||
  570.                     ($from !== null && (int)$commandParam $from) ||
  571.                     ($to !== null && (int)$commandParam $to) ||
  572.                     (!$canBeNegative && (int)$commandParam 0))
  573.                 {
  574.                     $redoMessage "Введите целое число";
  575.                     $redoMessage .=
  576.                         ($from !== null " от " $from "") .
  577.                         ($to !== null " до " $to "");
  578.                 }
  579.                 break;
  580.             default:
  581.                 throw new \Exception("Unknown type: " $type);
  582.         }
  583.         if ($literals && !array_filter(array_merge($literals$yesNoLiterals), function ($current) use ($commandParam) {
  584.                 return mb_strtolower($current) == mb_strtolower($commandParam);
  585.             })) {
  586.             $redoMessage .= ($redoMessage ". " "") . "Введите одно из значений (без кавычек): " .
  587.                 array_reduce($literals, function ($all$current) {
  588.                     return $all .= ($all ", " "") . "\"" $current "\"";
  589.                 });
  590.         }
  591.         if ($redoMessage) {
  592.             $redoMessage .= ". Либо введите слово \"отмена\" (без кавычек) для отмены ввода";
  593.             $this->sendMessage($chatId$redoMessage);
  594.             return false;
  595.         }
  596.         if ($yesNoLiterals) {
  597.             return (in_array(mb_strtolower($commandParam), $yesLiterals) ? "yes" :
  598.                 (in_array(mb_strtolower($commandParam), $noLiterals) ? "no" :
  599.                     $commandParam));
  600.         }
  601.         return true;
  602.     }
  603.     public function initUserRequestDataParams(TelegramUser $user)
  604.     {
  605.         $waitBotCommandParamData $user->getWaitBotCommandParam();
  606.         if (!$waitBotCommandParamData) {
  607.             $waitBotCommandParamData = [];
  608.         }
  609.         if (isset($waitBotCommandParamData["userRequestData"])) {
  610.             throw new \Exception("Can't set userRequestData of user property waitBotCommandParam because it" .
  611.                 " is already set");
  612.         }
  613.         $waitBotCommandParamData["userRequestData"] = [
  614.             "currentParamIndex" => -1,
  615.             "enteredData" => [],
  616.         ];
  617.         $user->setWaitBotCommandParam($waitBotCommandParamData);
  618.         $this->emService->save($user);
  619.     }
  620.     public function getRequestNextDataFromUserOptions($options) {
  621.         return array_merge([
  622.             "dataNames" => [], //array|\Closure
  623.             // Example:
  624.             // Variant 1: ["name1", ...].
  625.             // Variant 2: [["name" => "name1", "description" => "descr1"], ...]
  626.             "askUserEnterDataText" => null,
  627.             "enterDataTextDescription" => null,
  628.             "onUserEnterCancel" => null,
  629.             "onCurrentDataEnterComplete" => null,
  630.             "onAllDataEnterComplete" => null,
  631.             "clearWaitBotCommandParamOnAllEnterComplete" => true,
  632.             "sendThankMessageOnAllEnteredData" => true,
  633.         ], $options);
  634.     }
  635.     public function requestNextDataFromUser(array &$sendMessagesTelegramUser $user$text$chatId,
  636.                                             array $options)
  637.     {
  638.         if (!is_array($options["dataNames"])) {
  639.             $options["dataNames"] = $options["dataNames"]();
  640.         } else {
  641.             foreach ($options["dataNames"] as &$dataName) {
  642.                 if (!is_array($dataName)) {
  643.                     $dataName = ["name" => $dataName];
  644.                 }
  645.             }
  646.         }
  647.         if (!$options["askUserEnterDataText"]) {
  648.             $options["askUserEnterDataText"] = "Введите: ";
  649.         }
  650.         if (!$user->getWaitBotCommandParam() || !isset($user->getWaitBotCommandParam()["userRequestData"])) {
  651.             throw new \Exception("userRequestData of user property waitBotCommandParam is not initialized" .
  652.                 ". Call initUserRequestDataParams method first");
  653.         }
  654.         $userRequestData $user->getWaitBotCommandParam()["userRequestData"];
  655.         $saveUserRequestData = function ($userRequestData) use ($user) {
  656.             $data $user->getWaitBotCommandParam();
  657.             $data['userRequestData'] = $userRequestData;
  658.             $user->setWaitBotCommandParam($data);
  659.             $this->emService->save($user);
  660.         };
  661.         $sendNextRequest = function ($paramName) use ($options, &$sendMessages, &$userRequestData$user,
  662.             $saveUserRequestData) {
  663.             $sendMessages[] = $options["askUserEnterDataText"] . $paramName["name"] .
  664.                 ($options["enterDataTextDescription"] ? ". " $options["enterDataTextDescription"] : "") .
  665.                 (isset($paramName["description"]) && $paramName["description"] ? ". {$paramName["description"]}"") .
  666.                 ". Либо введите слово \"отмена\""
  667.                 " (без кавычек) для отмены ввода";
  668.             $saveUserRequestData($userRequestData);
  669.         };
  670.         if ($userRequestData["currentParamIndex"] != -1) {
  671.             $result $this->checkCommandParamType($text"string"$chatId$user);
  672.             if ($result === false || $result === "canceled") {
  673.                 if ($options["onUserEnterCancel"]) {
  674.                     $options["onUserEnterCancel"]();
  675.                 }
  676.                 return;
  677.             }
  678.             $userRequestData["enteredData"][$options["dataNames"][$userRequestData["currentParamIndex"]]["name"]] = $text;
  679.             $userRequestData["currentParamIndex"]++;
  680.             $saveUserRequestData($userRequestData);
  681.             $sendMessages[] = "Принято: " $text;
  682.             if ($userRequestData["currentParamIndex"] >= count($options["dataNames"])) {
  683.                 if ($options["sendThankMessageOnAllEnteredData"]) {
  684.                     $sendMessages[] = "Все данные заполнены. Спасибо!";
  685.                 }
  686.                 $thrownException null;
  687.                 if ($options["onAllDataEnterComplete"]) {
  688.                     try {
  689.                         $options["onAllDataEnterComplete"]();
  690.                     } catch (\Exception $exception) {
  691.                         $thrownException $exception;
  692.                     }
  693.                 }
  694.                 if ($options["clearWaitBotCommandParamOnAllEnterComplete"]) {
  695.                     $user->setWaitBotCommandParam(null);
  696.                     $this->emService->save($user);
  697.                 } else {
  698.                     $data $user->getWaitBotCommandParam();
  699.                     unset($data["userRequestData"]);
  700.                     $user->setWaitBotCommandParam($data);
  701.                     $this->emService->save($user);
  702.                 }
  703.                 if ($thrownException) {
  704.                     throw $thrownException;
  705.                 }
  706.                 return;
  707.             } else {
  708.                 if ($options["onCurrentDataEnterComplete"]) {
  709.                     $options["onCurrentDataEnterComplete"]();
  710.                 }
  711.                 $sendNextRequest($options["dataNames"][$userRequestData["currentParamIndex"]]);
  712.             }
  713.         } else {
  714.             $userRequestData["currentParamIndex"]++;
  715.             $saveUserRequestData($userRequestData);
  716.             $sendNextRequest($options["dataNames"][$userRequestData["currentParamIndex"]]);
  717.         }
  718.     }
  719.     public function getBotCommandUpdateSample()
  720.     {
  721.         return array (
  722.             'update_id' => 954338453,
  723.             'message' =>
  724.                 array (
  725.                     'message_id' => 15,
  726.                     'from' =>
  727.                         array (
  728.                             'id' => "1720609_81",
  729.                             'is_bot' => false,
  730.                             'first_name' => 'Александр',
  731.                             'last_name' => 'Орлов',
  732.                             'username' => 'sariato',
  733.                             'language_code' => 'ru',
  734.                         ),
  735.                     'chat' =>
  736.                         array (
  737.                             'id' => "17206_0981",
  738.                             'first_name' => 'Александр',
  739.                             'last_name' => 'Орлов',
  740.                             'username' => 'sariato',
  741.                             'type' => 'private',
  742.                         ),
  743.                     'date' => 1707404913,
  744.                     'text' => '/sdfsf',
  745.                     'entities' =>
  746.                         array (
  747.                             =>
  748.                                 array (
  749.                                     'offset' => 0,
  750.                                     'length' => 6,
  751.                                     'type' => 'bot_command',
  752.                                 ),
  753.                         ),
  754.                 ),
  755.         );
  756.     }
  757.     public function createBotCommandUpdate($userId$command)
  758.     {
  759.         return array (
  760.             'update_id' => 0,
  761.             'message' =>
  762.                 array (
  763.                     'message_id' => 0,
  764.                     'from' =>
  765.                         array (
  766.                             'id' => $userId,
  767.                             'is_bot' => false,
  768.                             'first_name' => '',
  769.                             'last_name' => '',
  770.                             'username' => '',
  771.                             'language_code' => 'ru',
  772.                         ),
  773.                     'chat' =>
  774.                         array (
  775.                             'id' => $userId,
  776.                             'first_name' => '',
  777.                             'last_name' => '',
  778.                             'username' => '',
  779.                             'type' => 'private',
  780.                         ),
  781.                     'date' => (new \DateTime())->getTimestamp(),
  782.                     'text' => '/' $command,
  783.                     'entities' =>
  784.                         array (
  785.                             =>
  786.                                 array (
  787.                                     'offset' => 0,
  788.                                     'length' => 6,
  789.                                     'type' => 'bot_command',
  790.                                 ),
  791.                         ),
  792.                 ),
  793.         );
  794.     }
  795.     public function sendFile($chatId$filePath$caption null$fileType 'document')
  796.     {
  797.         if (!file_exists($filePath)) {
  798.             throw new \Exception("File does not exist: {$filePath}");
  799.         }
  800.         $url $this->baseUrl "send{$fileType}";
  801.         $params = [
  802.             'chat_id' => (int)$chatId,
  803.             $fileType => new \CURLFile(realpath($filePath)),
  804.         ];
  805.         if ($caption) {
  806.             $params['caption'] = $caption;
  807.         }
  808.         try {
  809.             $result $this->sendRequest($url$params);
  810.         } catch (\Exception $exception) {
  811.             $this->logger->error($exception->getMessage());
  812.             throw $exception;
  813.         }
  814.         return $result;
  815.     }
  816. }