* * @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; } }