Разработка бота в мессенджере MAX. Часть 1

Во всех мануалах пишут что бота нужно создавать в боте MasterBot, однако у меня он усиленно пишет что «Сейчас создать бота не получится. Попробуйте позже». Удалось создать только в консоли разработчика из https://business.max.ru/self/#/services, для этого нужно подтвердить организацию при помощи ЭЦП ключа. Простым физическим лицам, похоже пока бота не создать.

Документация по разработке бота тут: https://dev.max.ru/docs-api/ , увы не очень удобная. Как делать некоторые вещи я сделать долго путался, а некоторые так и не удалось, пришлось копаться в исходниках их официального клиента API на Python.

Итак, для начала создал класс для работы с мессенджером, который берет на себя всю рутину:

class TMax {
    public $url="";
    public $login="";
    public $password="";
    public function __construct($url,$token) {
        $this->url=$url;
        $this->token=$token;
    }    

    function Reqwest($reqwest,$params,$type="POST"){
        
        if ($type=="GET"){
           $reqwest=$reqwest."?".http_build_query($params);
        };
        if ($type=="POST"){
           $reqwest=$reqwest."?".http_build_query($params);
        };
        
        
        $ch = curl_init($this->url.$reqwest);
        $authorization = "Authorization:".$this->token;

        $this->PutLog("--(R $type) in $reqwest :".json_encode($params));
        
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json',$authorization)); 
        if ($type=="POST"){
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params));
        };
        if ($type=="PATCH"){
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params));            
            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');  
        }
        curl_setopt($ch, CURLOPT_TIMEOUT, 600);
        $res=curl_exec($ch);            

        if(curl_errno($ch)){
            throw new Exception(curl_error($ch));
        };        
        
        $this->PutLog("--(R) out :".json_encode($res));
        $response = json_decode($res);        
        return $response;
        
    }
    
    
    function PutLog($txt){
     if (DEBUG==true){
       $data=Date("m-d-y h:i:s")." ".$txt."\n";  
       file_put_contents(API_LOG_FILE, $data,FILE_APPEND);
       if (DEV_MODE==true){
           echo $data."<br/>";
       };
     };
    }
}

Далее необходимо зарегистрировать вебхук, куда платформа будет посылать обновления по по событиям:

  $url="https://".$_SERVER["SERVER_NAME"].$_SERVER["SCRIPT_URL"];
  echo "-оформляем подписку на текущий URL:$url<br/>";  
  $params=[];
  $params["url"]=$url;
  $params["secret"]=secret;
  $params["update_types"]=["message_created","bot_started"];
  $res=$Max->Reqwest("subscriptions",$params);
  if (isset($res->success)){
      if ($res->success==true){
          echo "-Успешно<br/>";  
      } else {
          echo "-возникла ошибка:".$res->message."<br/>";  
      };
  } else {
    echo "-возникла ошибка. Подробности в логах<br/>";  
  };
  die();

Получить информацию о боте можно так:

if (isset($_GET["BotInfo"])){
    echo "-информация о боте<br/>";  
    $params=[];
    $res=$Max->Reqwest("me",$params,"GET");
    if (isset($res->user_id)){
        echo "<pre>";
        var_dump($res);
    } else {
      echo "-возникла ошибка. Подробности в логах<br/>";  
    };
    
    die();  
}

Установить команды бота:

if (isset($_GET["BotCommand"])){
echo "-устанавливаем доступные команды для бота<br/>";
$params=[];
$params[]=["name"=>"Ку","description"=>"Говорить Ку пацаки.."];
$params[]=["name"=>"ЫЫ","description"=>"Говорить ЫЫ пацаки.."];
$params[]=["name"=>"help","description"=>"Тыгыдын"];
$res=$Max->Reqwest("me",["commands"=>$params],"PATCH");
die();
}

Причем установить то я команды установил, но в самом боте в мессенджере они так и не отображаются. Почем? Пока не выяснено.

В самом вебхуке прописал отлов событий присоединения к боту, и пользователь что-то написал. В этом случае отвечаем ему:

if ($in->update_type=="bot_started"){
  $params=[];
  $params["user_id"]=$in->user->user_id;
  //$params["chat_id"]=$in->chat_id;
  $params["text"]="О! Свежее мясо!";
  $params["format"]="html";
  $res=$Max->Reqwest("messages",$params,"POST");  
};
if ($in->update_type=="message_created"){
  $params=[];
  //$params["user_id"]=$in->message->recipient->user_id;
  $params["chat_id"]=$in->message->recipient->chat_id;
  $params["text"]="Привет человек!";
  $params["format"]="markdown";
  $params["attachments"]=[];
  //$params["link"]=["type"=>"forward","mid"=>$in->message->body->mid];
  $res=$Max->Reqwest("messages",$params,"POST");  
};

Получение GPS координат почтовых отделений по индексу

Задача: получить по известному индексу почтового отделения, его адрес, GPS координаты и другие данные..

Решение: а просто попарсим страницу почты россии: https://www.pochta.ru/offices, где есть возможность ввода индекса, а в ответ на карте отрисовываються отделения. Чуть нырнув в сетевые запросы, получаем вызов:

https://www.pochta.ru/suggestions/v2/postoffice.find-nearest-by-postalcode-vacancies

который используется самим сайтом почты для получения данных. На выходе очень приятный для парсинга выход. В общем всё оформил в виде простого скрипта:

<?php
$indexs=file_get_contents("posts.list");
$indexs=explode("\n", $indexs);
file_put_contents("res.sql", "", FILE_APPEND | LOCK_EX);

foreach ($indexs as $ind) {
    wget https://www.pochta.ru/suggestions/v2/postoffice.find-nearest-by-postalcode-vacancies --header='Content-Type:application/json' --post-data '{"postalCode":"$ind","filters":[],"limit":1,"radius":100,"offset":0,"currentDateTime":"2025-10-28T15:10:00"}' -O tmp.tmp;
    $js= json_decode(file_get_contents("tmp.tmp"));    
    if ($js!=null){
        if (count($js)>0){
          $otd=$js[0];
          $address=$otd->addressSource;
          $latitude=$otd->latitude;
          $longitude=$otd->longitude;
          $sql="udpate posts set name='Почтовое отделение $ind',latitude='$latitude',longitude='$longitude',address='$address' where index='$ind';\n";
          echo $sql;
          file_put_contents("res.sql", $sql, FILE_APPEND | LOCK_EX);          
        };
    };
};

PostgreSQL и выборка из полей содержащих JSON

Postgre позволяет хранить данные в JSON с колонках с типами json и jsonb, которые по сути отличаются только способом хранения. json — в виде текста (хорошо видно визуально, можно обрабатывать запросы как с «текстом») и jsonb — в виде сжатых бинарных данных (занимает меньше места, можно индексировать. Причем если данные хранятся в виде jsonb, то порядок сохранения ключей в JSON не гарантируется. В принципе вполне удобно хранить так данные, если не возможно заранее определить точно структуру таблицы.

Ну и ниже пример работы с такими данными.

Вставка:

insert into epd_expansion (period,ls,orgid,parties) values ('2025-10-01',1,2,'[4,5,6]')

Выборка:

SELECT * FROM public.epd_expansion where parties is not null;

А вот так например, как можно например сделать выборку всех строк, имеющих в массиве partise число 3:

SELECT * FROM public.epd_expansion where parties@>'[3]'::jsonb;

Rustore и логика

На днях столкнулся вплотную с маразмом Rustore, а именно:

  • Однажды загруженное приложение apk удалить из магазина уже невозможно. Можно только скрыть
  • Т.к. приложение скрыто, а не удалено то другой разработчик загрузить это приложение (с этим именем пакета) уже не может.

Как следствие часто возникают ситуации: кто-то скачал apk твоего приложения из любого магазина приложений (например Goole Play), загрузил его в Rustore.. И всё. Ты теперь своё приложение уже никогда не загрузишь сам. Техподдержка отписывается: связывайтесь с тем кто загрузил это приложение первый и пусть передаёт приложение к тебе, написав кучу бумажек. А если связаться не удаётся? Или это вообще злоумышленник-шантажист?

У меня ситуация несколько проще: я когда то загрузил это приложение скачав из Google Play его, т.к. в Rustore его не было — разработчик не выложил. А теперь разработчик и хочет, но не может из-за меня. Вот теперь занимаемся перепиской и бумаготворчеством.. Казалось бы элементарный функционал добавить — на подобии передачи домена. Один код генерирует, другое его вставляет. И всё — передача завершена.

Особенности работы с DOMDocument в php 7 и 8

Разрабатывал давече один скрипт по работе с XML, на рабочем ПК, установленным php 8.2, и был весьма удивлён, что код вида:

$dom = new DOMDocument('1.0', 'utf-8'); 
$dom->loadXML($this->xml_req);
$root = $dom->documentElement;
$root->getElementsByTagName('Signature')->item(0)->remove();

Отлично работает на PHP 8 и не работает на PHP 7. , ругаясь на

Call to undefined method DOMNodeList::remove()

Странно подумал я, почесав затылок. Пришлось переписать код на вида:

        $dom = new DOMDocument('1.0', 'utf-8'); 
        $dom->loadXML($xml_req_template);
        $root = $dom->documentElement;
        
        $parents=$root->getElementsByTagName('Signature');        
        for ($i = $parents->length - 1; $i >= 0; $i--) {
            $parent=$parents->item($i);
            if ($parent && $parent->parentNode) {
                $parent->parentNode->removeChild($parent);
            }
        }

И нигде ведь не попадалось в документации по работе с PHP, что совместимость с php7 сломана. Хотя может плохо смотрел..

1 2 3 4 5 6 310