vk/system/API/LongPoll.php

272 lines
11 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
namespace VK\API;
use \Exception;
use \VK\Core,
\VK\Robots\RobotAbstract;
/**
* LongPoll
*
* @property string $key Ключ к серверу
* @property string $server Сервер
* @property string $ts Идентификатор последнего события
*
* @method public function __construct(object $robot) Инициализация
* @method public function get(int $wait = 25) Получить события
* @method public function handle(callable $function, int $wait = 25) Обработать события
*
* @see https://vk.com/dev/bots_longpoll
* @see https://vk.com/dev/groups.getLongPollServer
* @see https://vk.com/dev/groups.setLongPollSettings
*
* @package VK\API\LongPoll
* @author Арсен Мирзаев <red@hood.su>
*
* @todo Добавить обработку ошибок ($request['errors];)
*/
class LongPoll
{
/**
* Робот
*
* @var string
*/
private RobotAbstract $robot;
/**
* Ключ к серверу
*
* @see $this->get()
*
* @var string
*/
private string $key;
/**
* Сервер (URL)
*
* @see $this->get()
*
* @var string
*/
private string $server;
/**
* Идентификатор последнего события
*
* От него отсчитываются новые, необработанные события
*
* @see $this->get()
*
* @var string
*/
private string $ts;
/**
* Инициализация
*
* @param object $robot Робот
*/
public function __construct(object $robot)
{
// Инициализация робота
if (!$robot->id) {
throw new Exception('Роботу необходимо задать идентификатор ВКонтакте');
}
if (!$robot->token) {
throw new Exception('Роботу необходимо задать токен для доступа к LongPoll');
}
if (!$robot->version) {
throw new Exception('Роботу необходимо задать версию используемого API ВКонтакте');
}
$this->robot = $robot;
// Остановка процессов-дубликатов
if (!file_exists(Core::init()->path['temp'])) {
// Если не существует каталога temp, то создать
mkdir(Core::init()->path['temp'], 0775, true);
}
if (file_exists($lock = Core::init()->path['temp'] . '/' . $this->robot->id . '_' . (int) $this->robot->session . '.longpoll')) {
// Если существует файл-блокировщик, то удалить его
unlink($lock);
}
}
/**
* Установить настройки
*
* Полная настройка и активация LongPoll
*
* @param bool $status = true Активация или деактивация
* @param string ...$params Изменяемые параметры
*
* @return array
*/
public function post(bool $status = true, string ...$params): array
{
// Инициализация настроек
$settings = [
'group_id' => $this->robot->id,
'access_token' => $this->robot->token,
'v' => $this->robot->version,
'api_version' => $this->robot->version
];
// Установка переданных параметров
foreach ($params as $param) {
if ($param === 'group_id' || $param === 'access_token' || $param === 'v' || $param === 'api_version') {
// Блокировка параметров от изменения
continue;
}
if ($status === true && !array_key_exists('enabled', $settings)) {
// Если запущена активация и не был передан параметр статуса LongPoll
// Установка параметра активации LongPoll
$settings['enabled'] = 1;
}
$status = (int) $status;
if ($param === 'all') {
// Если передан параметр: установка ВСЕХ значений
$settings['message_new'] = $status;
$settings['message_reply'] = $status;
$settings['message_allow'] = $status;
$settings['message_deny'] = $status;
$settings['message_edit'] = $status;
$settings['message_typing_state'] = $status;
$settings['photo_new'] = $status;
$settings['audio_new'] = $status;
$settings['video_new'] = $status;
$settings['wall_reply_new'] = $status;
$settings['wall_reply_edit'] = $status;
$settings['wall_reply_delete'] = $status;
$settings['wall_reply_restore'] = $status;
$settings['wall_post_new'] = $status;
$settings['wall_repost'] = $status;
$settings['board_post_new'] = $status;
$settings['board_post_edit'] = $status;
$settings['board_post_restore'] = $status;
$settings['board_post_delete'] = $status;
$settings['photo_comment_new'] = $status;
$settings['photo_comment_edit'] = $status;
$settings['photo_comment_delete'] = $status;
$settings['photo_comment_restore'] = $status;
$settings['video_comment_new'] = $status;
$settings['video_comment_edit'] = $status;
$settings['video_comment_delete'] = $status;
$settings['video_comment_restore'] = $status;
$settings['market_comment_new'] = $status;
$settings['market_comment_edit'] = $status;
$settings['market_comment_delete'] = $status;
$settings['market_comment_restore'] = $status;
$settings['poll_vote_new'] = $status;
$settings['group_join'] = $status;
$settings['group_leave'] = $status;
$settings['group_change_settings'] = $status;
$settings['group_change_photo'] = $status;
$settings['group_officers_edit'] = $status;
$settings['user_block'] = $status;
$settings['user_unblock'] = $status;
$settings['like_add'] = $status;
$settings['like_remove'] = $status;
$settings['message_event'] = $status;
} else {
// Иначе
// Установка значения
$settings[$param] = $status;
}
}
return $this->robot->browser()->api('groups.setLongPollSettings', $settings);
}
/**
* Получить события
*
* @param int $wait Время ожидания новых событий (в секундах)
*
* @return array
*/
public function get(int $wait = 25): array
{
if (empty($this->key) || empty($this->server) || empty($this->ts)) {
// Если не инициализирован LongPoll-сервер
// Запрос на получение доступа и данных LongPoll-сервера
$response = $this->robot->browser()->api('groups.getLongPollServer', [
'group_id' => $this->robot->id,
'access_token' => $this->robot->token,
'v' => $this->robot->version
])['response'];
// Ключ доступа
$this->key = $response['key'];
// Сервер хранящий события
$this->server = $response['server'];
// Идентификатор последнего события
$this->ts = $response['ts'];
}
// Запрос на получение событий
return $this->robot->browser()->post($this->server . '?act=a_check&key=' . $this->key . '&ts=' . $this->ts . '&wait=' . $wait);
}
/**
* Обработать события
*
* Получает и обрабатывает события
*
* @param callable $function Обработка
* @param int $wait Время ожидания новых событий (в секундах)
*
* @return array
*/
public function handle(callable $function, int $wait = 25): array
{
// Файл-блокировщик и PID процесса
$lock = Core::init()->path['temp'] . '/' . $this->robot->id . '_' . (int) $this->robot->session . '.longpoll';
$pid = getmypid();
// Создание или пересоздание файла-блокировщика
file_put_contents($lock, $pid);
do {
// Выполняется пока существует файл-блокировщик
// Запрос на получение событий
$request = $this->get($wait);
// [ВНИМАНИЕ] Соединение будет открыто даже при создании нового процесса LongPoll
if (!file_exists($lock) || (int) fread(fopen($lock, 'r'), filesize($lock)) !== $pid) {
// Проверка существования файла-блокировщика и соответствие его PID
// Завершение работы
break;
}
if (!empty($request['response']['updates'])) {
// Если получены необработанные события
// Обработка событий
$function($request['response']);
}
// Новый идентификатор последнего события
$this->ts = $request['response']['ts'];
} while (true);
return $request;
}
}