diff --git a/composer.json b/composer.json index 43c0ac9..d7f0c33 100644 --- a/composer.json +++ b/composer.json @@ -22,15 +22,18 @@ "issues": "https://git.mirzaev.sexy/mirzaev/vk/issues" }, "require": { - "php": "~8.1", - "psr/log": "~1.0", - "mirzaev/accounts": "~1.2.0", - "monolog/monolog": "~1.6", - "jasny/error-handler": "~0.2", - "guzzlehttp/guzzle": "~7.5" + "php": "^8.1", + "psr/log": "^1.0", + "mirzaev/accounts": "^1.2.0", + "monolog/monolog": "^1.6", + "jasny/error-handler": "^0.2", + "guzzlehttp/guzzle": "^7.5" }, "require-dev": { - "phpunit/phpunit": "~9.5" + "phpunit/phpunit": "^9.5" + }, + "suggest": { + "ext-sodium": "Can be selected in some conditions to increase security" }, "autoload": { "psr-4": { diff --git a/mirzaev/vk/system/api/methods/messages.php b/mirzaev/vk/system/api/methods/messages.php index 7a23327..89b1105 100644 --- a/mirzaev/vk/system/api/methods/messages.php +++ b/mirzaev/vk/system/api/methods/messages.php @@ -5,9 +5,7 @@ declare(strict_types=1); namespace mirzaev\vk\api\methods; // Файлы проекта -use mirzaev\accounts\vk, - mirzaev\vk\robots\robot, - mirzaev\vk\api\data, +use mirzaev\vk\robots\robot, mirzaev\vk\robots\group; // Встроенные библиотеки @@ -17,13 +15,31 @@ use stdClass, /** * Режимы отправки сообщений */ -enum mode +enum send { - /** Быстро - случайный идентификатор (умножение на rand()) */ + /** Обычная отправка */ + case simple; + + /** Проверка отправки */ + case check; +} + +/** + * Режимы генерации идентификатора сессии доставки сообщения + */ +enum generate +{ + /** Генерация: time() */ + case date; + + /** Генерация: rand() */ case random; - /** Надёжно - проверка отправки (поиск сообщения через messages.getById) */ - case search; + /** Генерация: random_bytes(10) */ + case crypto; + + /** Генерация: sodium_crypto_generichash() */ + case hash; } /** @@ -42,19 +58,64 @@ enum mode final class messages extends method { /** - * @var mode $mode Режим отправки сообщений + * @var send $send_mode Режим отправки сообщений */ - protected mode $mode = mode::random; + protected send $send_mode = send::simple; /** - * @var array[int] Сообщения для пересылки + * @var generate $generate_mode Режим генерации идентификатора сессии доставки сообщения */ - protected array $forward; + protected generate $generate_mode = generate::date; /** - * @var int Сообщение для ответа + * @var ?int $lat Географическая ширина */ - protected int $reply; + protected ?int $lat = null; + + /** + * @var ?int $long Географическая долгота + */ + protected ?int $long = null; + + /** + * @var ?int $reply_to Идентификатор сообщения, на которое требуется ответить + */ + protected ?int $reply_to = null; + + /** + * @var ?array $forward_messages Идентификаторы пересылаемых сообщений + */ + protected ?array $forward_messages = null; + + /** + * @var ?string $sticker_id Идентификатор стикера + */ + protected ?string $sticker_id = null; + + /** + * @var ?string $payload Полезная нагрузка + */ + protected ?string $payload = null; + + /** + * @var bool $dont_parse_links Не создавать представление ссылки в сообщении? + */ + protected bool $dont_parse_links = false; + + /** + * @var bool $disable_mentions Отключить уведомление об упоминании в сообщении? + */ + protected bool $disable_mentions = false; + + /** + * @var ?string $intent Интент + */ + protected ?string $intent = null; + + /** + * @var ?string $subscribe_id Число, которое будет использоваться для работы с интентами + */ + protected ?int $subscribe_id = null; /** * Конструктор @@ -123,79 +184,132 @@ final class messages extends method * * @see https://vk.com/dev/messages.send * - * @param int|string|array|null $receiver Получатель + * @param int|string|null $receiver Получатель + * @param ?string $message Сообщение + * @param ?int $lat Географическая ширина + * @param ?int $long Географическая долгота + * @param ?array $attachments Вложения + * @param ?int $reply_to Идентификатор сообщения, на которое требуется ответить + * @param ?array $forward_messages Идентификаторы пересылаемых сообщений + * @param ?forward $forward Пересылаемые сообщения (в другой чат) + * @param ?string $sticker_id Идентификатор стикера + * @param ?keyboard $keyboard Инстанция клавиатуры + * @param ?template $template Инстанция шаблона сообщения + * @param ?string $payload Полезная нагрузка + * @param bool $dont_parse_links Не создавать представление ссылки в сообщении? + * @param bool $disable_mentions Отключить уведомление об упоминании в сообщении? + * @param ?string $intent Интент + * @param ?int $subscribe_id Число, которое будет использоваться для работы с интентами + * @param int|string|null $random_id Идентификатор сессии доставки сообщения (защита от повторных отправок) * * @return int|array Идентификатор успешно отправленного сообщения или ответ сервера (подразумевается ошибка) * - * @todo Написать обработчик ошибок возвращаемых ВКонтакте + * @todo + * 1. Написать обработчик ошибок возвращаемых ВКонтакте + * 2. Добавить параметр forward (не путать с forward_messages) + * 3. Добавить клавиатуру + * 4. Добавить шаблоны сообщений + * 5. Добавить content_source */ - public function send(int|string|array|null $receiver): int|array - { - // Идентификатор - $random_id = time(); - - if ($this->mode === mode::random) { - // Быстрая отправка сообщения - - $random_id *= rand(); - } - - // Реиницилазиция + public function send( + int|string|null $receiver, + ?string $message = null, + ?int $lat = null, + ?int $long = null, + ?array $attachments = null, + ?int $reply_to = null, + ?array $forward_messages = null, + // ?forward $forward = null, + ?string $sticker_id = null, + // ?keyboard $keyboard = null, + // ?template $template = null, + ?string $payload = null, + bool $dont_parse_links = false, + bool $disable_mentions = false, + ?string $intent = null, + ?int $subscribe_id = null, + int|string|null $random_id = null, + ): int|array { + // Реинициализация настроек $this->robot->api->reinit(); - if (is_int($receiver)) { - // Идентификатор + // Инициализация получателя + if (is_int($receiver)) ($id = $receiver - 2000000000) > 0 ? $this->robot->api['peer_id'] = $receiver : $this->robot->api['chat_id'] = $id; + else if (is_array($receiver)) $this->robot->api['peer_ids'] = implode(',', $receiver); + else if (is_string($receiver)) $this->robot->api['domain'] = $receiver; - // Инициализация получателя - $this->robot->api['peer_id'] = $receiver; - } else if (is_array($receiver)) { - // Идентификаторы - - // Инициализация получателя - $this->robot->api['user_ids'] = $receiver; - } else if (is_string($receiver)) { - // Домен - - // Инициализация получателя - $this->robot->api['domain'] = $receiver; - } - - // Инициализация идентификатора сообщения (защита от повторных отправок) в настройках API - $this->robot->api['random_id'] = $random_id; + // Инициализация идентификатора сессии доставки сообщения (защита от повторных отправок) + $this->robot->api['random_id'] = $random_id ?? match ($this->generate_mode) { + generate::date => time(), + generate::random => rand(), + generate::crypto => random_bytes(10), + generate::hash => sodium_crypto_generichash(random_bytes(10)), + default => time() + }; // Инициализация текста в настройках API - $this->robot->api['message'] = $this->text; + if (isset($message)) $this->robot->api['message'] = $message; + else if (isset($this->text)) $this->robot->api['message'] = $this->text; - // Пересылаемые сообщения - if (!empty($this->forwardMessages)) { + // Инициализация широты + if (isset($lat)) $this->robot->api['lat'] = $lat; + else if (isset($this->lat)) $this->robot->api['lat'] = $this->lat; - // Инициализация пересылаемых сообщений в настройках API - $this->robot->api['forward_messages'] = implode(',', $this->forwardMessages); - } + // Инициализация долготы + if (isset($long)) $this->robot->api['long'] = $long; + else if (isset($this->long)) $this->robot->api['long'] = $this->tlongext; - // Ответные сообщения - if (isset($this->ReplyMessage)) { + // Инициализация вложений + if (isset($attachments)) $this->robot->api['attachment'] = implode(',', $attachments); + else if (isset($this->robot->api->data) && $this->robot->api->__get('data') !== []) $this->robot->api['attachment'] = implode(',', $this->robot->api->__get('data')); - // Инициализация идентификатора сообщения на которое обрабатывается ответ в настройках API - $this->robot->api['reply_to'] = $this->ReplyMessage; - } + // Инициализация сообщения, на которое требуется ответить + if (isset($reply_to)) $this->robot->api['reply_to'] = $reply_to; + else if (isset($this->reply)) $this->robot->api['reply_to'] = $this->reply; - // Вложения - if (isset($this->data) && $this->__get('data') !== []) { // !empty($this->data->data) почемуто не работает + // Инициализация пересылаемых сообщений + if (isset($forward_messages)) $this->robot->api['forward_messages'] = implode(',', $forward_messages); + else if (isset($this->forward_messages)) $this->robot->api['forward_messages'] = implode(',', $this->forward_messages); - // Инициализация вложений в настройках API - $this->robot->api['attachment'] = implode(',', $this->__get('data')); - } + // Инициализация стикера + if (isset($sticker_id)) $this->robot->api['sticker_id'] = $sticker_id; + else if (isset($this->sticker_id)) $this->robot->api['sticker_id'] = $this->sticker_id; + + // Инициализация полезной нагрузки + if (isset($payload)) $this->robot->api['payload'] = $payload; + else if (isset($this->payload)) $this->robot->api['payload'] = $this->payload; + + // Инициализация пользовательского соглашения + // $this->robot->api['content_source'] = $this->robot->content_source; + + // Инициализация "не создавать представление ссылки в сообщении?" + if ($dont_parse_links) $this->robot->api['dont_parse_links'] = 1; + else if ($this->dont_parse_links) $this->robot->api['dont_parse_links'] = 1; + + // Инициализация "отключить уведомление об упоминании в сообщении?" + if ($disable_mentions) $this->robot->api['disable_mentions'] = 1; + else if ($this->disable_mentions) $this->robot->api['disable_mentions'] = 1; + + // Инициализация интентов + if (isset($intent)) $this->robot->api['intent'] = $intent; + else if (isset($this->intent)) $this->robot->api['intent'] = $this->intent; + + // Инициализация числа, которое будет использоваться для работы с интентами + if (isset($subscribe_id)) $this->robot->api['subscribe_id'] = $subscribe_id; + else if (isset($this->subscribe_id)) $this->robot->api['subscribe_id'] = $this->subscribe_id; + + // Проверка сформированного сообщения + if (!$this->robot->api->offsetExists('message') && !$this->robot->api->offsetExists('attachment')) throw new Exception('Сообщение должно содержать текст, либо вложение'); // Запрос $request = json_decode($this->robot->browser->request('POST', 'messages.send', ['form_params' => $this->robot->api->settings])->getBody()->getContents()); // Если в ответе ошибка if (isset($request->error)) { - throw new Exception('Вконтакте: ' . $request->error->error_msg, $request->error->error_code); + throw new Exception('ВКонтакте: ' . $request->error->error_msg, $request->error->error_code); } - if ($this->mode === mode::search) { + if ($this->send_mode === send::check) { // Надёжная доставка сообщения if (!empty($request["response"])) { diff --git a/mirzaev/vk/system/api/methods/users.php b/mirzaev/vk/system/api/methods/users.php index 5bf2acc..0307fce 100644 --- a/mirzaev/vk/system/api/methods/users.php +++ b/mirzaev/vk/system/api/methods/users.php @@ -5,10 +5,11 @@ declare(strict_types=1); namespace mirzaev\vk\api\methods; // Файлы проекта -use mirzaev\vk\robots\user; +use mirzaev\vk\robots\robot; // Встроенные библиотеки -use Exception; +use Exception, + stdClass; /** * Пользователь @@ -24,10 +25,10 @@ final class users extends method /** * Конструктор * - * @param user $user Робот + * @param robot $user Робот */ public function __construct( - protected user $robot + protected robot $robot ) { } @@ -36,51 +37,69 @@ final class users extends method * * @see https://dev.vk.com/method/users.get * - * @param array $user_ids Выбор пользователей для запроса информации + * @param array $receiver Выбор пользователей для запроса информации (user_ids) * @param array $fields Выбор дополнительных запрашиваемых полей * @param string $name_case Падеж * - * @return ?array Информация об аккаунте, если получена + * @return stdClass|array|null Информация об аккаунте или массив с информацией об аккаунтах, если найдена * * @todo * 1. Доделать * 2. Написать обработчик ошибок возвращаемых ВКонтакте */ - public function get(array $user_ids = [], array $fields = [], string $name_case = ''): array + public function get(int|string|array $receiver = [], array $fields = [], ?string $name_case = null): stdClass|array|null { // Реиницилазиция $this->robot->api->reinit(); - // Инициализация пользователей для запроса информации - if (!empty($user_ids)) $this->robot->api['user_ids'] = ''; + if (is_int($receiver)) { + // Идентификатор - foreach ($user_ids as $key => $user_id) { - // Перебор пользователей для получения информации + // Инициализация пользователя + $this->robot->api['user_ids'] = $receiver; + } else if (is_array($receiver)) { + // Идентификаторы - // Запись пользователя - $this->robot->api['user_ids'] .= $user_id; + // Инициализация пользователей + $this->robot->api['user_ids'] = ''; - // Запись разделителя - if ($key === array_key_last($user_ids)) break; - else $this->robot->api['user_ids'] .= ', '; + foreach ($receiver as $key => $user_id) { + // Перебор пользователей для получения информации + + // Запись пользователя + $this->robot->api['user_ids'] .= $user_id; + + // Запись разделителя + if ($key === array_key_last($receiver)) break; + else $this->robot->api['user_ids'] .= ', '; + } + } else if (is_string($receiver)) { + // Домен + + // Инициализация пользователя + $this->robot->api['user_ids'] = $receiver; } - // Инициализация дополнительных запрашиваемых полей - if (!empty($fields)) $this->robot->api['fields'] = ''; + if (isset($fields)) { + // Запрошены дополнительные запрашиваемые поля - foreach ($fields as $key => $field) { - // Перебор дополнительных запрашиваемых полей + // Инициализация дополнительных запрашиваемых полей + $this->robot->api['fields'] = ''; - // Запись запрашиваемого дополнительного поля - $this->robot->api['fields'] .= $field; + foreach ($fields as $key => $field) { + // Перебор дополнительных запрашиваемых полей - // Запись разделителя - if ($key === array_key_last($fields)) break; - else $this->robot->api['fields'] .= ', '; + // Запись запрашиваемого дополнительного поля + $this->robot->api['fields'] .= $field; + + // Запись разделителя + if ($key === array_key_last($fields)) break; + else $this->robot->api['fields'] .= ', '; + } } // Инициализация падежа - if (!empty($name_case)) $this->robot->api['name_case'] = $name_case; + if (isset($name_case)) $this->robot->api['name_case'] = $name_case; // Запрос $request = json_decode($this->robot->browser->request('POST', 'users.get', ['form_params' => $this->robot->api->settings])->getBody()->getContents()); @@ -90,6 +109,6 @@ final class users extends method throw new Exception('ВКонтакте: ' . $request->error->error_msg, $request->error->error_code); } - return $request->response; + return is_array($receiver) ? $request->response : (isset($request->response[0]) ? $request->response[0] : null); } } diff --git a/mirzaev/vk/system/api/settings.php b/mirzaev/vk/system/api/settings.php index 539db21..0a79386 100644 --- a/mirzaev/vk/system/api/settings.php +++ b/mirzaev/vk/system/api/settings.php @@ -17,9 +17,6 @@ use mirzaev\vk\robots\robot; * @var array $settings Настройки * @var float $version Версия API * - * @todo - * 1. Создать __isset(), __unset() - * * @package mirzaev\vk\api * @author Arsen Mirzaev Tatyano-Muradovich */ @@ -30,7 +27,7 @@ class settings implements ArrayAccess * * Должна иметь тип string потому, что PHP при стандартных настройках удаляет нули у float */ - protected const VK_API_VERSION_DEFAULT = '5.130'; + protected const VK_API_VERSION_DEFAULT = '5.131'; /** * Конструктор @@ -176,15 +173,39 @@ class settings implements ArrayAccess }; } - // public function __unset(string $name): void - // { - // match ($name) { - // 'settings' => throw new Exception('Запрещено удалять настройки', 500), - // 'robot' => throw new Exception('Запрещено удалять робота', 500), - // 'data', 'attachments' => $this->offsetUnset('attachments'), - // default => $this->offsetUnset($name) - // }; - // } + /** + * Проверить инициализированность свойства + * + * @param string $name Название + * + * @return bool Свойство инициализировано? + */ + public function __isset(string $name): bool + { + return match ($name) { + 'settings' => isset($this->settings), + 'robot' => isset($this->robot), + 'data', 'attachments' => $this->offsetExists('attachments'), + default => $this->offsetExists($name) + }; + } + + /** + * Деинициализированность свойство + * + * @param string $name Название + * + * @return void + */ + public function __unset(string $name): void + { + match ($name) { + 'settings' => throw new Exception('Запрещено деинициализировать настройки', 500), + 'robot' => throw new Exception('Запрещено деинициализировать робота', 500), + 'data', 'attachments' => $this->offsetUnset('attachments'), + default => $this->offsetUnset($name) + }; + } /** * Записать по смещению @@ -236,7 +257,7 @@ class settings implements ArrayAccess /** * Прочитать по смещению */ - public function &offsetGet(mixed $offset): mixed + public function offsetGet(mixed $offset): mixed { if (isset($this->settings)) { if (strcasecmp($offset, 'settings') === 0) { @@ -260,7 +281,11 @@ class settings implements ArrayAccess } /** - * Проверка существования смещения + * Проверить существование смещения + * + * @param mixed $offset Сдвиг + * + * @return bool Смещение существует? */ public function offsetExists(mixed $offset): bool {