Transit into git.mirzaev.sexya
This commit is contained in:
parent
6b56e51dd7
commit
cccbf53008
|
@ -0,0 +1 @@
|
||||||
|
vendor
|
|
@ -0,0 +1,49 @@
|
||||||
|
{
|
||||||
|
"name": "mirzaev/vk-arangodb",
|
||||||
|
"description": "Module for mirzaev/vk framework for storage data in ArangoDB",
|
||||||
|
"keywords": [
|
||||||
|
"ArangoDB",
|
||||||
|
"vk",
|
||||||
|
"module"
|
||||||
|
],
|
||||||
|
"type": "vk-module",
|
||||||
|
"license": "WTFPL",
|
||||||
|
"homepage": "https://git.mirzaev.sexy/mirzaev/vk-arangodb",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Arsen Mirzaev Tatyano-Muradovich",
|
||||||
|
"email": "arsen@mirzaev.sexy",
|
||||||
|
"homepage": "https://mirzaev.sexy",
|
||||||
|
"role": "Programmer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"email": "arsen@mirzaev.sexy",
|
||||||
|
"wiki": "https://git.mirzaev.sexy/mirzaev/vk-arangodb/wiki",
|
||||||
|
"issues": "https://git.mirzaev.sexy/mirzaev/vk-arangodb/issues"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "funding",
|
||||||
|
"url": "https://fund.mirzaev.sexy"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1",
|
||||||
|
"mirzaev/vk": "~4.0.1",
|
||||||
|
"mirzaev/accounts": "1.2.x-dev",
|
||||||
|
"mirzaev/arangodb": "^1.0.0",
|
||||||
|
"triagens/arangodb": "~3.9.x-dev",
|
||||||
|
"guzzlehttp/guzzle": "~7.5"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"mirzaev\\vk\\arangodb\\": "mirzaev/vk/arangodb/system"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"mirzaev\\vk\\arangodb\\tests\\": "mirzaev/vk/arangodb/tests"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1 @@
|
||||||
|
storage/*
|
|
@ -0,0 +1,167 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\vk\arangodb;
|
||||||
|
|
||||||
|
// Файлы проекта
|
||||||
|
use mirzaev\arangodb\collection;
|
||||||
|
|
||||||
|
// Фреймворк ArangoDB
|
||||||
|
use mirzaev\arangodb\document;
|
||||||
|
use mirzaev\vk\arangodb\longpoll;
|
||||||
|
|
||||||
|
// Библиотека для работы с API-сервера ArangoDB
|
||||||
|
use ArangoDBClient\Connection as _connection;
|
||||||
|
use ArangoDBClient\Statement as _statement;
|
||||||
|
use ArangoDBClient\Document as _document;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Журнал
|
||||||
|
*
|
||||||
|
* Используется для хранения информации об изменениях других коллекций
|
||||||
|
* Связывается с отслеживаемой коллекцией ребром
|
||||||
|
*
|
||||||
|
* @param _connection $session Сессия соединения с базой данных
|
||||||
|
* @param _document $document Инстанция (static::COLLECTION_JOURNAL)
|
||||||
|
*/
|
||||||
|
class journal
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Настройки архитектуры базы данных
|
||||||
|
*/
|
||||||
|
const COLLECTION_JOURNAL = 'journal';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Конструктор
|
||||||
|
*
|
||||||
|
* @param _connection $session Сессия соединения с базой данных
|
||||||
|
* @param _document $document Инстанция (static::COLLECTION_JOURNAL)
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
public _connection $session,
|
||||||
|
public _document $document
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Инициализация
|
||||||
|
*
|
||||||
|
* @param _connection $session Сессия соединения с базой данных
|
||||||
|
* @param string $document Идентификатор документа
|
||||||
|
* @param bool $create Создавать коллекции при их отсутствии
|
||||||
|
*
|
||||||
|
* @return static|null Объект с инстанцией журнала
|
||||||
|
*/
|
||||||
|
public static function init(_connection $session, string $document, bool $create = true): ?static
|
||||||
|
{
|
||||||
|
if ($create) {
|
||||||
|
// Запрошено создание коллекций в случае их отсутствия
|
||||||
|
|
||||||
|
// Инициализация коллекций
|
||||||
|
collection::init($session, static::COLLECTION_JOURNAL);
|
||||||
|
collection::init($session, longpoll::COLLECTION_ACCESSED, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($journal = static::search($session, $document))) {
|
||||||
|
// Не найден журнал (подразумевается, что его не существует)
|
||||||
|
|
||||||
|
if (empty($journal = document::write($session, static::COLLECTION_JOURNAL, [
|
||||||
|
'events' => [],
|
||||||
|
'expires' => strtotime('first day of next month 00:00')
|
||||||
|
]))) {
|
||||||
|
// Не удалось создать документ
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty(document::write($session, longpoll::COLLECTION_ACCESSED, ['_from' => $document, '_to' => $journal]))) {
|
||||||
|
// Не удалось создать ребро: {$document} -> ЖУРНАЛ
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Инициализация инстанции журнала и возврат
|
||||||
|
return new static($session, $journal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Поиск журнала
|
||||||
|
*
|
||||||
|
* Находит актуальный документ журнала
|
||||||
|
*
|
||||||
|
* @param _connection $session Сессия соединения с базой данных
|
||||||
|
* @param string $document Идентификатор документа
|
||||||
|
*
|
||||||
|
* @return _document|null Инстанция (static::COLLECTION_JOURNAL) или его идентификатор
|
||||||
|
*/
|
||||||
|
public static function search(_connection $session, string $document): _document|null
|
||||||
|
{
|
||||||
|
// Инициализация ярлыка названия коллекции
|
||||||
|
$collection = static::COLLECTION_JOURNAL;
|
||||||
|
|
||||||
|
// Инициализация ярлыка названия ребра
|
||||||
|
$edge = longpoll::COLLECTION_ACCESSED;
|
||||||
|
|
||||||
|
// Инициализация даты для разделения по журналам
|
||||||
|
$time = time();
|
||||||
|
|
||||||
|
// Поиск журнала
|
||||||
|
$journal = (new _statement(
|
||||||
|
$session,
|
||||||
|
[
|
||||||
|
'query' => <<<AQL
|
||||||
|
FOR a IN $collection
|
||||||
|
LET b = (
|
||||||
|
FOR vertex, edge IN INBOUND a $edge
|
||||||
|
FILTER vertex._id == $document
|
||||||
|
LIMIT 1
|
||||||
|
RETURN vertex
|
||||||
|
)
|
||||||
|
FILTER a.expires >= $time
|
||||||
|
LIMIT 1
|
||||||
|
RETURN a
|
||||||
|
AQL,
|
||||||
|
"batchSize" => 1000,
|
||||||
|
"sanitize" => true
|
||||||
|
]
|
||||||
|
))->execute();
|
||||||
|
|
||||||
|
// Инициализация буфера вывода
|
||||||
|
$buffer = [];
|
||||||
|
|
||||||
|
foreach ($journal as $key => $value) {
|
||||||
|
// Перебор параметров для универсализации вывода
|
||||||
|
|
||||||
|
// Запись в буфер
|
||||||
|
$buffer[$key] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Возврат (подразумевается, что в буфере только 1 значение - инстанция (static::COLLECTION_JOURNAL))
|
||||||
|
return $buffer[0] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Запись в журнал
|
||||||
|
*
|
||||||
|
* @param string $event Событие
|
||||||
|
* @param array $data Дополнительная информация
|
||||||
|
*
|
||||||
|
* @return bool Статус обработки
|
||||||
|
*/
|
||||||
|
public function write(string $event, array $data = []): bool
|
||||||
|
{
|
||||||
|
// Запись в инстанцию документа
|
||||||
|
$this->document->events += [
|
||||||
|
[
|
||||||
|
'date' => time(),
|
||||||
|
'event' => $event,
|
||||||
|
'data' => $data
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
// Запись в базу данных
|
||||||
|
return document::update($this->session, $this->document);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,430 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\vk\arangodb;
|
||||||
|
|
||||||
|
// Файлы проекта
|
||||||
|
use mirzaev\arangodb\connection;
|
||||||
|
use mirzaev\arangodb\collection;
|
||||||
|
use mirzaev\arangodb\terminal;
|
||||||
|
use mirzaev\arangodb\document;
|
||||||
|
use mirzaev\vk\arangodb\traits\HTTP\headers\content\disposition;
|
||||||
|
|
||||||
|
// Библиотека для работы с API-сервера ArangoDB
|
||||||
|
use ArangoDBClient\Document as _document;
|
||||||
|
|
||||||
|
// Библиотека браузера
|
||||||
|
use GuzzleHttp\Client as Guzzle;
|
||||||
|
|
||||||
|
// Встроенные библиотеки
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LongPoll API ВКонтакте
|
||||||
|
*
|
||||||
|
* @todo
|
||||||
|
* 1. Проработать создание индексов в базе данных
|
||||||
|
*/
|
||||||
|
class longpoll
|
||||||
|
{
|
||||||
|
use disposition {
|
||||||
|
disposition::filename as disposition_filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
const COLLECTION_USERS = 'user';
|
||||||
|
const COLLECTION_GROUPS = 'group';
|
||||||
|
const COLLECTION_CHATS = 'chat';
|
||||||
|
const COLLECTION_MESSAGES = 'message';
|
||||||
|
const COLLECTION_ACCESSED = 'accessed';
|
||||||
|
|
||||||
|
public static string $path_storage = __DIR__ . '/storage';
|
||||||
|
public static string $path_storage_accounts = '/accounts';
|
||||||
|
public static string $path_storage_accounts_images = '/images';
|
||||||
|
public static string $path_storage_accounts_videos = '/videos';
|
||||||
|
public static string $path_storage_accounts_audios = '/audios';
|
||||||
|
public static string $path_storage_vk = '/vk';
|
||||||
|
public static string $path_storage_vk_stickers = '/stickers';
|
||||||
|
|
||||||
|
protected bool $journal = true;
|
||||||
|
|
||||||
|
public function __construct(protected connection $connection)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сохранить событие в базу данных
|
||||||
|
*
|
||||||
|
* @param array $updates События
|
||||||
|
*
|
||||||
|
* @return bool Статус сохранения
|
||||||
|
*/
|
||||||
|
public function save(array $update): bool
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Динамический вызов метода-обработчика события
|
||||||
|
if ($this->{$update['type']}($update['object'], $update['group_id'], $update['event_id'])) {
|
||||||
|
// Удалось сохранить в базу данных
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
terminal::write($e->getMessage() . PHP_EOL . $e->getFile() . ':' . $e->getLine());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Событие: "message_new"
|
||||||
|
*
|
||||||
|
* Новое сообщение
|
||||||
|
*
|
||||||
|
* @param array $data Данные сообщения
|
||||||
|
* @param int $group Идентификатор группы
|
||||||
|
* @param string $event Идентификатор события
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function message_new(array $data, int $group, string $event): bool
|
||||||
|
{
|
||||||
|
if ($this->connection->create) {
|
||||||
|
// Запрошено создание коллекций в случае их отсутствия
|
||||||
|
|
||||||
|
collection::init($this->connection->session, static::COLLECTION_USERS);
|
||||||
|
collection::init($this->connection->session, static::COLLECTION_MESSAGES);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($message = document::write($this->connection->session, static::COLLECTION_MESSAGES, static::messages($data, true))) {
|
||||||
|
// Записано сообщение
|
||||||
|
|
||||||
|
// Инициализация коллекции
|
||||||
|
$collection = static::COLLECTION_MESSAGES;
|
||||||
|
|
||||||
|
// Инициализация аккаунтов
|
||||||
|
$from = ('mirzaev\\vk\\arangodb\\vk\\' . static::type($data['message']['from_id']))::init($this->connection->session, $data['message']['from_id']);
|
||||||
|
$to = ('mirzaev\\vk\\arangodb\\vk\\' . static::type($data['message']['peer_id']))::init($this->connection->session, $data['message']['peer_id']);
|
||||||
|
|
||||||
|
if ($from instanceof _document && $to instanceof _document) {
|
||||||
|
// Инициализированы аккаунты
|
||||||
|
|
||||||
|
if ($this->connection->create) {
|
||||||
|
// Запрошено создание коллекций в случае их отсутствия
|
||||||
|
|
||||||
|
collection::init($this->connection->session, static::COLLECTION_ACCESSED, edge: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document::write($this->connection->session, static::COLLECTION_ACCESSED, [], ['_from' => $from->getId(), '_to' => $message])) {
|
||||||
|
// Записано ребро: АККАУНТ (отправитель) -> СООБЩЕНИЕ
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document::write($this->connection->session, static::COLLECTION_ACCESSED, [], ['_from' => $message, '_to' => $to->getId()])) {
|
||||||
|
// Записно ребро: СООБЩЕНИЕ -> АККАУНТ (получатель)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Журналирование
|
||||||
|
if ($this->journal && journal::init($this->connection->session, $message)->write('create', [
|
||||||
|
'account' => $from,
|
||||||
|
'changes' => [
|
||||||
|
'new' => collection::search($this->connection->session, <<<AQL
|
||||||
|
FOR a IN $collection
|
||||||
|
FILTER a._id == $message
|
||||||
|
LIMIT 1
|
||||||
|
RETURN a.data
|
||||||
|
AQL),
|
||||||
|
'old' => null
|
||||||
|
]
|
||||||
|
])) {
|
||||||
|
// Записано ребро: СООБЩЕНИЕ -> СООБЩЕНИЕ
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
throw new Exception('Не удалось сохранить сообщение в базу даннных', 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обработка сообщений
|
||||||
|
*
|
||||||
|
* @param array $messages Сообщения или сообщение
|
||||||
|
* @param bool $download Активация скачивания файлов на сервер
|
||||||
|
* @param bool $clean Активация очистки массива от пустых данных сообщения
|
||||||
|
*
|
||||||
|
* @return array Обработанные сообщения (зависит от входных данных)
|
||||||
|
*
|
||||||
|
* @todo
|
||||||
|
* 1. Переделать $message['vk']['metadata']['action']['cover']
|
||||||
|
* 2. Переделать $message['vk']['metadata']['payload']
|
||||||
|
* 3. Узнать про Notify API и добавить message_tag
|
||||||
|
* 4. В будущем удалить $message['message']['body']
|
||||||
|
* 5. Переделать $message['vk']['metadata']['conversation']['members']['amount_test'] (или удалить)
|
||||||
|
* 6. Разобраться с "Мультидиалогом" для старых версий API и существует ли он в новых
|
||||||
|
*/
|
||||||
|
public static function messages(array $messages, bool $download = false, bool $clean = true): array
|
||||||
|
{
|
||||||
|
if (isset($messages['message'])) {
|
||||||
|
// Передано одно сообщение
|
||||||
|
|
||||||
|
// Инициализация
|
||||||
|
$buffer[] = &$messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($buffer ?? $messages as &$message) {
|
||||||
|
// Перебор сообщений
|
||||||
|
|
||||||
|
$message = [
|
||||||
|
'data' => [
|
||||||
|
'id' => [
|
||||||
|
'global' => $message['message']['id'],
|
||||||
|
'local' => $message['message']['conversation_message_id']
|
||||||
|
],
|
||||||
|
'text' => $message['message']['text'] ?? $message['message']['body'],
|
||||||
|
'forward' => static::messages($message['message']['fwd_messages']),
|
||||||
|
'reply' => static::messages($message['message']['fwd_messages']),
|
||||||
|
'attachments' => static::attachments($message['message']['attachments'], $download, $clean)
|
||||||
|
],
|
||||||
|
'metadata' => [
|
||||||
|
'date' => [
|
||||||
|
'create' => $message['message']['date'] ?? null,
|
||||||
|
'update' => $message['message']['update_time'] ?? null
|
||||||
|
],
|
||||||
|
'action' => [
|
||||||
|
'type' => $message['message']['action'] ?? null,
|
||||||
|
'target' => [
|
||||||
|
'id' => $message['message']['action']['member_id'] ?? null
|
||||||
|
],
|
||||||
|
'text' => $message['message']['action']['text'] ?? null,
|
||||||
|
'email ' => $message['message']['action']['email'] ?? null,
|
||||||
|
'cover ' => $message['message']['action']['photo'] ?? null
|
||||||
|
],
|
||||||
|
'hash' => $message['message']['random_id'] ?? null,
|
||||||
|
'type' => $message['message']['out'] ?? null,
|
||||||
|
'admin' => [
|
||||||
|
'id' => $message['message']['admin_author_id'] ?? null
|
||||||
|
],
|
||||||
|
'pinned' => [
|
||||||
|
'date' => $message['message']['pinned_at'] ?? null
|
||||||
|
],
|
||||||
|
'emoji' => $message['message']['emoji'] ?? null,
|
||||||
|
'readed' => $message['message']['read_state'] ?? null,
|
||||||
|
'listened' => $message['message']['was_listened'] ?? null,
|
||||||
|
'hidden' => $message['message']['is_hidden'] ?? null,
|
||||||
|
'cropped' => $message['message']['is_cropped'] ?? null,
|
||||||
|
'deleted' => $message['message']['deleted'] ?? null,
|
||||||
|
'conversation' =>
|
||||||
|
[
|
||||||
|
'id' => $message['message']['chat_id'] ?? null,
|
||||||
|
'admin' => [
|
||||||
|
'id' => $message['message']['admin_id'] ?? null
|
||||||
|
],
|
||||||
|
'title' => $message['message']['title'] ?? null,
|
||||||
|
'members' => [
|
||||||
|
'amount' => $message['message']['members_count'] ?? null,
|
||||||
|
'amount_test' => $message['message']['users_count'] ?? null
|
||||||
|
],
|
||||||
|
'active' => $message['message']['chat_active'] ?? null,
|
||||||
|
'settings' => [
|
||||||
|
'push' => $message['message']['push_settings'] ?? null
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'important' => $message['message']['important'] ?? null,
|
||||||
|
'source' => [
|
||||||
|
'from' => $message['message']['ref'] ?? null,
|
||||||
|
'data' => $message['message']['ref_source'] ?? null
|
||||||
|
],
|
||||||
|
'payload' => $message['message']['payload'] ?? null,
|
||||||
|
'geo' => [
|
||||||
|
$message['message']['geo'] ?? null
|
||||||
|
],
|
||||||
|
'keyboard' => [
|
||||||
|
'block' => $message['client_info']['keyboard'] ?? null,
|
||||||
|
'inline' => $message['client_info']['inline_keyboard'] ?? null,
|
||||||
|
'buttons' => $message['client_info']['button_actions'] ?? null,
|
||||||
|
],
|
||||||
|
'carousel' => $message['client_info']['carousel'] ?? null,
|
||||||
|
'language' => $message['client_info']['lang_id'] ?? null,
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Очистка массива, если активировано
|
||||||
|
$clean and static::cleaner($messages);
|
||||||
|
|
||||||
|
return $messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обработка вложений
|
||||||
|
*
|
||||||
|
* @param array $attachments Вложения
|
||||||
|
* @param bool $download Активация скачивания файлов на сервер
|
||||||
|
* @param bool $clean Активация очистки массива от пустых данных сообщения
|
||||||
|
*
|
||||||
|
* @return array Обработанные вложения
|
||||||
|
*/
|
||||||
|
public static function attachments(array $attachments, bool $download = false, bool $clean = false): array
|
||||||
|
{
|
||||||
|
foreach ($attachments as &$attachment) {
|
||||||
|
// Перебор вложений
|
||||||
|
|
||||||
|
if ($attachment['type'] === 'photo') {
|
||||||
|
// Изображение
|
||||||
|
|
||||||
|
$attachment = [
|
||||||
|
'data' => [
|
||||||
|
'date' => $attachment['photo']['date'] ?? null,
|
||||||
|
'id' => $attachment['photo']['id'] ?? null,
|
||||||
|
'album' => [
|
||||||
|
'id' => $attachment['photo']['album_id'] ?? null
|
||||||
|
],
|
||||||
|
'account' => [
|
||||||
|
'id' => $attachment['photo']['user_id'] ?? null,
|
||||||
|
'admin' => $attachment['photo']['owner_id'] ?? null
|
||||||
|
],
|
||||||
|
'tags' => $attachment['photo']['has_tags'] ?? null,
|
||||||
|
'access' => [
|
||||||
|
'key' => $attachment['photo']['access_key'] ?? null
|
||||||
|
],
|
||||||
|
'text' => $attachment['photo']['text'] ?? null,
|
||||||
|
'storage' => static::sizes($attachment['photo']['sizes'], $download) ?? null
|
||||||
|
],
|
||||||
|
'metadata' => [
|
||||||
|
'type' => $attachment['type'] ?? null
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Очистка массива, если активировано
|
||||||
|
$clean and static::cleaner($attachments);
|
||||||
|
|
||||||
|
return $attachments;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сортировка размеров файла из вложения
|
||||||
|
*
|
||||||
|
* @param array $sizes Размеры файла согласно спецификации в API
|
||||||
|
* @param bool $download Активация скачивания файлов на сервер
|
||||||
|
* @param int|null $id Идентификатор аккаунта кому принадлежат изображения
|
||||||
|
*
|
||||||
|
* @return array Обработанные размеры
|
||||||
|
*
|
||||||
|
* @see https://vk.com/dev/photo_sizes
|
||||||
|
*/
|
||||||
|
public static function sizes(array $sizes, bool $download = false, int|null $id = null): array
|
||||||
|
{
|
||||||
|
foreach ($sizes as &$size) {
|
||||||
|
// Перебор размеров
|
||||||
|
|
||||||
|
if ($download) {
|
||||||
|
// Запрошено сохранение на сервер
|
||||||
|
|
||||||
|
// Инициализация
|
||||||
|
$browser = new Guzzle();
|
||||||
|
|
||||||
|
// Инициализация директории
|
||||||
|
if (!file_exists($path = static::$path_storage . (isset($id) ? static::$path_storage_accounts . PHP_EOL . $id : static::$path_storage_vk) . static::$path_storage_accounts_images . PHP_EOL . date('Y_m_d', time())))
|
||||||
|
if (!mkdir($path, 0755, true))
|
||||||
|
throw new Exception('Не удалось инициализировать директорию: ' . $path);
|
||||||
|
|
||||||
|
// Генерация временного файла с уникальным дескриптором
|
||||||
|
$file = tempnam($path, '');
|
||||||
|
|
||||||
|
// Сохранение в файл
|
||||||
|
$request = $browser->get($size['url'], ['sink' => $file]);
|
||||||
|
|
||||||
|
var_dump($request->getHeaders());
|
||||||
|
die;
|
||||||
|
|
||||||
|
// Чтение расширения файла
|
||||||
|
$ext = $request->getHeader('Mime-Type');
|
||||||
|
|
||||||
|
// Перезапись
|
||||||
|
rename($file, dirname($file) . PHP_EOL . (static::disposition_filename($request->getHeader('Content-Disposition')[0]) ?? 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Инициализация
|
||||||
|
$size = [
|
||||||
|
'data' => [
|
||||||
|
'width' => [
|
||||||
|
'value' => $size['width'],
|
||||||
|
'unit' => 'px'
|
||||||
|
],
|
||||||
|
'height' => [
|
||||||
|
'value' => $size['height'],
|
||||||
|
'unit' => 'px'
|
||||||
|
],
|
||||||
|
'source' => [
|
||||||
|
'vk' => $size['url']
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'metadata' => [
|
||||||
|
'type' => $size['type']
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sizes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Инициализация
|
||||||
|
public static function cleaner(array &$target): bool
|
||||||
|
{
|
||||||
|
// Инициализация
|
||||||
|
$changes = false;
|
||||||
|
|
||||||
|
foreach ($target as $key => &$value) {
|
||||||
|
// Перебор элементов массива
|
||||||
|
|
||||||
|
if ($value === null || $value === []) {
|
||||||
|
// Пустое значение
|
||||||
|
|
||||||
|
// Удаление из массива по ключу
|
||||||
|
unset($target[$key]);
|
||||||
|
|
||||||
|
// Запись обозначения о том, что были произведены изменения
|
||||||
|
$changes = true;
|
||||||
|
} else if (is_array($value)) {
|
||||||
|
// Элемент является массивом
|
||||||
|
|
||||||
|
// Начало рекурсии (повторяется до тех пор пока производятся изменения за итерацию)
|
||||||
|
while (static::cleaner($value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $changes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function truncate()
|
||||||
|
{
|
||||||
|
collection::truncate($this->connection->session, static::COLLECTION_USERS);
|
||||||
|
collection::truncate($this->connection->session, static::COLLECTION_GROUPS);
|
||||||
|
collection::truncate($this->connection->session, static::COLLECTION_CHATS);
|
||||||
|
collection::truncate($this->connection->session, static::COLLECTION_MESSAGES);
|
||||||
|
collection::truncate($this->connection->session, static::COLLECTION_ACCESSED);
|
||||||
|
collection::truncate($this->connection->session, journal::COLLECTION_JOURNAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Определить тип аккаунта
|
||||||
|
*
|
||||||
|
* @param int $id Идентификатор
|
||||||
|
*
|
||||||
|
* @return string Возвращает 'user', если не прошли другие проверки
|
||||||
|
*/
|
||||||
|
public static function type(int $id): string
|
||||||
|
{
|
||||||
|
// Чат
|
||||||
|
if ($id - 2000000000 >= 0) return static::COLLECTION_CHATS;
|
||||||
|
|
||||||
|
// Группа
|
||||||
|
if (((string) $id)[0] === '-') return static::COLLECTION_GROUPS;
|
||||||
|
|
||||||
|
return static::COLLECTION_USERS;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\vk\arangodb;
|
||||||
|
|
||||||
|
class terminal
|
||||||
|
{
|
||||||
|
protected const PREFIX = 'arangodb';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Запись в вывод
|
||||||
|
*
|
||||||
|
* @param string $text Текст сообщения
|
||||||
|
*/
|
||||||
|
public static function write(string $text): void
|
||||||
|
{
|
||||||
|
echo '[Модуль][' . self::PREFIX . '] ' . $text . PHP_EOL;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\vk\arangodb\traits\HTTP\headers\content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обработка заголовка: "Content-Disposition"
|
||||||
|
*
|
||||||
|
* @see https://developer.mozilla.org/ru/docs/Web/HTTP/Headers/Content-Disposition
|
||||||
|
*/
|
||||||
|
trait disposition
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Поиск значения filename
|
||||||
|
*
|
||||||
|
* @param string $raw Необработанное значение заголовка
|
||||||
|
*/
|
||||||
|
private static function filename(string $raw): ?string
|
||||||
|
{
|
||||||
|
// Инициализация
|
||||||
|
$raw = trim($raw);
|
||||||
|
|
||||||
|
if (strpos($raw, ';') === false) {
|
||||||
|
// Разделитель параметров не найден (пустая строка или отсутствие параметров)
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Инициализация
|
||||||
|
$params = [];
|
||||||
|
$_params = explode(';', $raw);
|
||||||
|
$type = $_params[0];
|
||||||
|
unset($_params[0]);
|
||||||
|
|
||||||
|
if (str_contains($type, 'attachment')) {
|
||||||
|
// В заголовке указано, что это скачиваемый файл
|
||||||
|
|
||||||
|
foreach ($_params as $param) {
|
||||||
|
if (str_contains($param, '=')) {
|
||||||
|
// Параметр подходит по маске: "текст=текст"
|
||||||
|
|
||||||
|
// Чтение и запись в переменные
|
||||||
|
[$key, $value] = explode('=', $param, 2);
|
||||||
|
|
||||||
|
// Запись в буфер
|
||||||
|
$params[trim($key)] = trim($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Инициализация (порядок связан с приоритетом)
|
||||||
|
$masks = ['filename*' => true, 'filename' => false];
|
||||||
|
$targets = [];
|
||||||
|
|
||||||
|
foreach ($masks as $mask => $utf8) {
|
||||||
|
// Перебор значений масок
|
||||||
|
|
||||||
|
if (isset($params[$mask])) {
|
||||||
|
// Параметр из буфера прошел проверку по маске
|
||||||
|
|
||||||
|
// Инициализация
|
||||||
|
$target = trim($params[$mask]);
|
||||||
|
|
||||||
|
// Надо разобраться с этим
|
||||||
|
if ($utf8 && strpos($target, "utf-8''") === 0 && $target = substr($target, strlen("utf-8''")))
|
||||||
|
$target = rawurldecode($target);
|
||||||
|
|
||||||
|
// Надо разобраться с этим
|
||||||
|
if (substr($target, 0, 1) === '"' && substr($target, -1, 1) === '"')
|
||||||
|
$target = substr($target, 1, -1);
|
||||||
|
|
||||||
|
$targets[$mask] = $target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Возврат по приоритету
|
||||||
|
return $targets[$masks[0]] ?? $targets[$masks[1]];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\vk\arangodb\traits\HTTP\headers\content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обработка заголовка: "Content-Type"
|
||||||
|
*
|
||||||
|
* @see https://developer.mozilla.org/ru/docs/Web/HTTP/Headers/Content-Type
|
||||||
|
*/
|
||||||
|
trait type
|
||||||
|
{
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\vk\arangodb\vk;
|
||||||
|
|
||||||
|
use mirzaev\vk\arangodb\longpoll;
|
||||||
|
use mirzaev\vk\arangodb\journal;
|
||||||
|
use mirzaev\arangodb\collection;
|
||||||
|
use mirzaev\arangodb\document;
|
||||||
|
use ArangoDBClient\Document as _document;
|
||||||
|
|
||||||
|
use ArangoDBClient\Connection as _connection;
|
||||||
|
|
||||||
|
class chat
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Инициализация
|
||||||
|
*
|
||||||
|
* @param _connection $session Сессия соединения с базой данных
|
||||||
|
* @param int $id Идентификатор ВКонтакте
|
||||||
|
* @param bool $journal Записывать в журнал
|
||||||
|
* @param bool $create Создавать коллекции при их отсутствии
|
||||||
|
*
|
||||||
|
* @return ?_document Инстанция документа
|
||||||
|
*/
|
||||||
|
public static function init(_connection $session, int $id, bool $journal = true, bool $create = true): ?_document
|
||||||
|
{
|
||||||
|
// Инициализация коллекции
|
||||||
|
$collection = longpoll::COLLECTION_CHATS;
|
||||||
|
|
||||||
|
if ($create) {
|
||||||
|
// Запрошено создание коллекций в случае их отсутствия
|
||||||
|
|
||||||
|
// Создание коллекции
|
||||||
|
collection::init($session, $collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($account = collection::search($session, <<<AQL
|
||||||
|
FOR x IN $collection
|
||||||
|
FILTER x.data.data.id == $id
|
||||||
|
LIMIT 1
|
||||||
|
RETURN x.data
|
||||||
|
AQL)) {
|
||||||
|
// Найден аккаунт
|
||||||
|
|
||||||
|
return $account;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($account = document::write($session, $collection, ['data' => ['id' => $id]])) {
|
||||||
|
// Аккаунт записан
|
||||||
|
|
||||||
|
// Журналирование
|
||||||
|
if ($journal && journal::init($session, $account)->write('create', [
|
||||||
|
'changes' => [
|
||||||
|
'new' => collection::search($session, <<<AQL
|
||||||
|
FOR a IN $collection
|
||||||
|
FILTER a._id == $account
|
||||||
|
LIMIT 1
|
||||||
|
RETURN a.data
|
||||||
|
AQL),
|
||||||
|
'old' => null
|
||||||
|
]
|
||||||
|
])) {
|
||||||
|
// Записаны данные в журнал
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($account = collection::search($session, <<<AQL
|
||||||
|
FOR x IN $collection
|
||||||
|
FILTER x.data.data.id == $id
|
||||||
|
LIMIT 1
|
||||||
|
RETURN x
|
||||||
|
AQL)) {
|
||||||
|
// Найден аккаунт
|
||||||
|
|
||||||
|
return $account;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\vk\arangodb\vk;
|
||||||
|
|
||||||
|
use mirzaev\vk\arangodb\longpoll;
|
||||||
|
use mirzaev\vk\arangodb\journal;
|
||||||
|
use mirzaev\arangodb\collection;
|
||||||
|
use mirzaev\arangodb\document;
|
||||||
|
use ArangoDBClient\Document as _document;
|
||||||
|
|
||||||
|
use ArangoDBClient\Connection as _connection;
|
||||||
|
|
||||||
|
class group
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Инициализация
|
||||||
|
*
|
||||||
|
* @param _connection $session Сессия соединения с базой данных
|
||||||
|
* @param int $id Идентификатор ВКонтакте
|
||||||
|
* @param bool $journal Записывать в журнал
|
||||||
|
* @param bool $create Создавать коллекции при их отсутствии
|
||||||
|
*
|
||||||
|
* @return ?_document Инстанция документа
|
||||||
|
*/
|
||||||
|
public static function init(_connection $session, int $id, bool $journal = true, bool $create = true): ?_document
|
||||||
|
{
|
||||||
|
// Инициализация коллекции
|
||||||
|
$collection = longpoll::COLLECTION_GROUPS;
|
||||||
|
|
||||||
|
if ($create) {
|
||||||
|
// Запрошено создание коллекций в случае их отсутствия
|
||||||
|
|
||||||
|
// Создание коллекции
|
||||||
|
collection::init($session, $collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($account = collection::search($session, <<<AQL
|
||||||
|
FOR x IN $collection
|
||||||
|
FILTER x.data.data.id == $id
|
||||||
|
LIMIT 1
|
||||||
|
RETURN x.data
|
||||||
|
AQL)) {
|
||||||
|
// Найден аккаунт
|
||||||
|
|
||||||
|
return $account;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($account = document::write($session, $collection, ['data' => ['id' => $id]])) {
|
||||||
|
// Аккаунт записан
|
||||||
|
|
||||||
|
// Журналирование
|
||||||
|
if ($journal && journal::init($session, $account)->write('create', [
|
||||||
|
'changes' => [
|
||||||
|
'new' => collection::search($session, <<<AQL
|
||||||
|
FOR a IN $collection
|
||||||
|
FILTER a._id == $account
|
||||||
|
LIMIT 1
|
||||||
|
RETURN a.data
|
||||||
|
AQL),
|
||||||
|
'old' => null
|
||||||
|
]
|
||||||
|
])) {
|
||||||
|
// Записаны данные в журнал
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($account = collection::search($session, <<<AQL
|
||||||
|
FOR x IN $collection
|
||||||
|
FILTER x.data.data.id == $id
|
||||||
|
LIMIT 1
|
||||||
|
RETURN x
|
||||||
|
AQL)) {
|
||||||
|
// Найден аккаунт
|
||||||
|
|
||||||
|
return $account;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\vk\arangodb\vk;
|
||||||
|
|
||||||
|
use mirzaev\vk\arangodb\longpoll;
|
||||||
|
use mirzaev\vk\arangodb\journal;
|
||||||
|
use mirzaev\arangodb\collection;
|
||||||
|
use mirzaev\arangodb\document;
|
||||||
|
use ArangoDBClient\Document as _document;
|
||||||
|
|
||||||
|
use ArangoDBClient\Connection as _connection;
|
||||||
|
|
||||||
|
class user
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Инициализация
|
||||||
|
*
|
||||||
|
* @param _connection $session Сессия соединения с базой данных
|
||||||
|
* @param int $id Идентификатор ВКонтакте
|
||||||
|
* @param bool $journal Записывать в журнал
|
||||||
|
* @param bool $create Создавать коллекции при их отсутствии
|
||||||
|
*
|
||||||
|
* @return ?_document Инстанция документа
|
||||||
|
*/
|
||||||
|
public static function init(_connection $session, int $id, bool $journal = true, bool $create = true): ?_document
|
||||||
|
{
|
||||||
|
// Инициализация коллекции
|
||||||
|
$collection = longpoll::COLLECTION_USERS;
|
||||||
|
|
||||||
|
if ($create) {
|
||||||
|
// Запрошено создание коллекций в случае их отсутствия
|
||||||
|
|
||||||
|
// Создание коллекции
|
||||||
|
collection::init($session, $collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($account = collection::search($session, <<<AQL
|
||||||
|
FOR x IN $collection
|
||||||
|
FILTER x.id == $id
|
||||||
|
LIMIT 1
|
||||||
|
RETURN x
|
||||||
|
AQL)) {
|
||||||
|
// Найден аккаунт
|
||||||
|
|
||||||
|
return $account;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($account = document::write($session, $collection, ['id' => $id])) {
|
||||||
|
// Аккаунт записан
|
||||||
|
|
||||||
|
// Журналирование
|
||||||
|
if ($journal && journal::init($session, $account)->write('create', [
|
||||||
|
'changes' => [
|
||||||
|
'new' => collection::search($session, <<<AQL
|
||||||
|
FOR a IN $collection
|
||||||
|
FILTER a._id == $account
|
||||||
|
LIMIT 1
|
||||||
|
RETURN a.data
|
||||||
|
AQL),
|
||||||
|
'old' => null
|
||||||
|
]
|
||||||
|
])) {
|
||||||
|
// Записаны данные в журнал
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($account = collection::search($session, <<<AQL
|
||||||
|
FOR x IN $collection
|
||||||
|
FILTER x.data.data.id == $id
|
||||||
|
LIMIT 1
|
||||||
|
RETURN x
|
||||||
|
AQL)) {
|
||||||
|
// Найден аккаунт
|
||||||
|
|
||||||
|
return $account;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue