andrey-tech/sendpulse-api-php

Простая обертка на PHP7+ для работы с API сервиса SendPulse с троттлингом запросов и логированием в файл

1.1.0 2021-02-08 07:16 UTC

This package is auto-updated.

Last update: 2024-11-08 15:47:03 UTC


README

SendPulse logo
Latest Stable Version Total Downloads License

Простая обертка на PHP7+ для работы с REST API сервиса SendPulse с троттлингом запросов, выводом отладочной информации в STDOUT и логированием в файл.

Содержание

Требования

  • PHP >= 7.0;
  • класс HTTP >= 3.0 - НТТР(S) клиент с троттлингом запросов, поддержкой маркера BOM в теле сообщения формата JSON и выводом отладочной информации о запросах и ответах в STDOUT;
  • класс DebugLogger >= 2.0 - логгер, сохраняющий отладочную информацию в файл вместе с данными об объеме используемой оперативной памяти и прошедшем времени;
  • произвольный автозагрузчик классов, реализующий стандарт PSR-4.

Установка

Установка через composer:

$ composer require andrey-tech/sendpulse-api-php:"^1.1"

или добавить

"andrey-tech/sendpulse-api-php": "^1.1"

в секцию require файла composer.json.

Класс SendPulseAPI

Для работы с REST API SendPulse используется класс \App\SendPulse\SendPulseAPI.
При возникновении ошибок выбрасывается исключение класса \App\SendPulse\SendPulseAPIException.
В настоящее в классе частично реализованы методы для работы со следующими сервисами SendPulse:

Авторизация пользователя

Методы для авторизации пользователя находится в трейте \App\SendPulse\Auth:

  • auth(string $clientId, string $clientSecret) :string Выполняет авторизацию клиента и возвращает персональный ключ (токен), который также сохраняется в хранилище токенов.
    • $clientId - ID клиента (пользователя);
    • $clientSecret - секрет клиента (пользователя).
use App\SendPulse\SendPulseAPI;
use App\SendPulse\SendPulseAPIException;
use App\SendPulse\TokenStorage\TokenStorageException;

try {
    $clientId = 'acbdef0123456789abcdef0123456789';
    $clientSecret = 'acbdef0123456789abcdef0123456789';

    $sendPulse = new SendPulseAPI();
    $sendPulse->auth($clientId, $clientSecret);

} catch (SendPulseAPIException $e) {
    printf('Ошибка (%d): %s' . PHP_EOL, $e->getCode(), $e->getMessage());
} catch (TokenStorageException $e) {
    printf('Ошибка (%d): %s' . PHP_EOL, $e->getCode(), $e->getMessage());
} catch (Exception $e) {
    printf('Ошибка (%d): %s' . PHP_EOL, $e->getCode(), $e->getMessage());
}

Получение нового токена, по истечении его срока действия, происходит автоматически, когда в ответ на запрос к API SendPulse возвращается ответ с HTTP-статусом 401 Unauthorized.

Хранение токена

Сохранение и загрузка токена выполняется с помощью классов, реализующих интерфейс \App\SendPulse\TokenStorage\TokenStorageInterface.

Интерфейс TokenStorageInterface

Интерфейс \App\SendPulse\TokenStorage\TokenStorageInterface определяет два метода:

  • save(string $token, string $clientId, string $clientSecret) :void Сохраняет токен.
    • $token - токен;
    • $clientId - ID клиента;
    • $clientSecret - секрет клиента.
  • load(string $clientId, string $clientSecret) :?string Загружает токен и возвращает его. Метод должен возвращать null, когда сохраненный токен отсутствует.
    • $clientId - ID клиента;
    • $clientSecret - секрет клиента.
  • hasToken(string $clientId, string $clientSecret) :bool Проверяет существование токена для заданного сочетания $clientId и $clientSecret.
    • $clientId - ID клиента;
    • $clientSecret - секрет клиента.

Класс TokenStorage

По умолчанию для сохранения и загрузки токенов используется класс \App\SendPulse\TokenStorage\TokenStorage, который хранит токены в отдельных файлах для каждого сочетания $clientId и $clientSecret, использованного при авторизации в методе auth(). Класс реализует интерфейс \App\SendPulse\TokenStorage\TokenStorageInterface и содержит собственные методы:

  • __construct(string $storageFolder = 'tokens/') Конструктор класса.
    • $storageFolder - каталог для хранения файлов токенов.

Дополнительные параметры устанавливаются через публичные свойства класса \App\SendPulse\TokenStorage\TokenStorage:

При возникновении ошибок выбрасыватся исключение класса \App\SendPulse\TokenStorage\TokenStorageException.

Использование собственного класса для сохранения токенов

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

use App\SendPulse\SendPulseAPI;
use App\SendPulse\SendPulseAPIException;
use App\SendPulse\TokenStorage\TokenStorageException;

try {
    $clientId = 'acbdef0123456789abcdef0123456789';
    $clientSecret = 'acbdef0123456789abcdef0123456789';

    $sendPulse = new SendPulseAPI();

    // Устанавливаем собственный объект класса, обеспечивающего хранение токенов в базе данных
    $sendPulse->tokenStorage = new \App\SendPulse\TokenStorage\DatabaseStorage();

    $sendPulse->auth($clientId, $clientSecret);

} catch (SendPulseAPIException $e) {
    printf('Ошибка (%d): %s' . PHP_EOL, $e->getCode(), $e->getMessage());
} catch (TokenStorageException $e) {
    printf('Ошибка (%d): %s' . PHP_EOL, $e->getCode(), $e->getMessage());
} catch (Exception $e) {
    printf('Ошибка (%d): %s' . PHP_EOL, $e->getCode(), $e->getMessage());
}

Структура собственного класса \App\SendPulse\TokenStorage\DatabaseStorage:

namespace App\SendPulse\TokenStorage;

class DatabaseStorage implements TokenStorageInterface
{
    /**
     * Сохраняет токен
     * @param string  $token Токен
     * @param string $clientId ID клиента
     * @param string $clientSecret Секрет клиента
     * @return void
     * @throws TokenStorageException
     */
    public function save(string $token, string $clientId, string $clientSecret)
    {
        // Здесь токен сохраняется в базе данных
    }

    /**
     * Загружает токен
     * @param string $clientId ID клиента
     * @param string $clientSecret Секрет клиента
     * @return array|null
     * @throws TokenStorageException
     */
    public function load(string $clientId, string $clientSecret)
    {
        // Здесь токен извлекается из базы данных
    }

    /**
     * Проверяет существуют ли токен для заданного ID клиента и секрета клиента
     * @param string $clientId ID клиента
     * @param string $clientSecret Секрет клиента
     * @return bool
     * @throws TokenStorageException
     */
    public function hasToken(string $clientId, string $clientSecret): bool
    {
        // Здесь проверяется существование токена в базе данных
    }
}

Методы для работы с адресными книгами

Методы для работы с адресными книгами находятся в трейте \App\SendPulse\Addressbooks:

Примеры работы с адресными книгами:

use App\SendPulse\SendPulseAPI;
use App\SendPulse\SendPulseAPIException;
use App\SendPulse\TokenStorage\TokenStorageException;

try {
    $clientId = 'acbdef0123456789abcdef0123456789';
    $clientSecret = 'acbdef0123456789abcdef0123456789';

    $sendPulse = new SendPulseAPI();
    $sendPulse->auth($clientId, $clientSecret);

    // Загружаем все адресные книги
    $num = 0;
    $generator = $sendPulse->getAllAddressbooks();
    foreach ($generator as $addressbooks) {
        foreach ($addressbooks as $addressbook) {
            $num++;
            echo "[{$num}] {$addressbook['id']}: {$addressbook['name']}" . PHP_EOL;
        }
    }

    // Получаем информацию об адресной книге по ID книги
    $addressbookId = 20143254;
    $response = $this->getAddressbook($addressbookId);
    print_r($response);

    // Добавляем новую адресную книгу
    $addressbookId = $sendPulse->addAddressbook([
        'bookName' => 'Тестовая адресная книга'
    ]);
    print_r($addressbookId);

    // Формируем список контактов для адресной книги
    $emails = [
        [
            'email' => 'test1@example.com',
            'variables' => [
                'Name' => 'Тест контакт 1',
                'Phone' => '+79450000001'
            ]
        ],
        [
            'email' => 'test2@example.com',
            'variables' => [
                'Name' => 'Тест контакт 2',
                'Phone' => '+79450000002'
            ]
        ],
        [
            'email' => 'test3@example.com',
            'variables' => [
                'Name' => 'Тест контакт 3',
                'Phone' => '+79450000003'
            ]
        ]
    ];

    // Добавляем контакты в адресную книгу
    $response = $sendPulse->addAddressbookEmails($addressbookId, $emails);
    print_r($response);

    // Получаем первые 100 контактов из адресной книги
    $response = $sendPulse->getAddressbookEmails($addressbookId);
    print_r($response);

    // Получаем количество email адресов в адресной книге
    $response = $sendPulse->getAddressbookEmailsTotal($addressbookId);
    print_r($response);

    // Получаем список переменных для адресной книги
    $response = $sendPulse->getAddresbookVariables($addressbookId);
    print_r($response);

    // Удаляем адресную книгу
    $response = $sendPulse->deleteAddressbook($addressbookId);
    print_r($response);

} catch (SendPulseAPIException $e) {
    printf('Ошибка (%d): %s' . PHP_EOL, $e->getCode(), $e->getMessage());
} catch (TokenStorageException $e) {
    printf('Ошибка (%d): %s' . PHP_EOL, $e->getCode(), $e->getMessage());
} catch (Exception $e) {
    printf('Ошибка (%d): %s' . PHP_EOL, $e->getCode(), $e->getMessage());
}

Методы для работы с кампаниями

Методы для работы с кампаниями находятся в трейте \App\SendPulse\Campaigns:

Примеры работы с кампаниями:

use App\SendPulse\SendPulseAPI;
use App\SendPulse\SendPulseAPIException;
use App\SendPulse\TokenStorage\TokenStorageException;

try {
    $clientId = 'acbdef0123456789abcdef0123456789';
    $clientSecret = 'acbdef0123456789abcdef0123456789';

    $sendPulse = new SendPulseAPI();
    $sendPulse->auth($clientId, $clientSecret);

    // Получаем список все кампаний
    $num = 0;
    $generator = $sendPulse->getAllCampaigns();
    foreach ($generator as $campaigns) {
        foreach ($campaigns as $campaign) {
            $num++;
            echo "[{$num}] {$campaign['id']}: {$campaign['name']}" . PHP_EOL;
        }
    }

    // Получаем информацию о кампании по ID кампании
    $campaignId = 21058230;
    $response = $this->getCampaign($campaignId);
    print_r($response);

    // Формируем параметры новой кампании
    $sendTime = new DateTime();
    $sendTime->add(new DateInterval("PT30M"));
    $params = [
        'sender_name'  => 'Тестовый отправитель',
        'sender_email' => 'test@example.com',
        'name'         => 'Тестовая рассылка',
        'subject'      => 'Тестовая рассылка',
        'template_id'  => 2308544,
        'list_id'      => 79093323,
        'send_date'    => $sendTime->format('Y-m-d H:i:s')
    ];

    // Добавляем новую кампанию
    $campaignId = $sendPulse->addCampaign($params);
    print_r($campaignId);

    // Отменяем отправку запланированной кампании
    $response = $sendPulse->deleteCampaign($campaignId);
    print_r($response);

    // Получаем список всех кампаний, которые создавались по данной адресной книге
    $num = 0;
    $addressbookId = 20143254;
    $generator = $sendPulse->getAllAddressbookCampaigns($addressbookId);
    foreach ($generator as $campaigns) {
        foreach ($campaigns as $campaign) {
            $num++;
            echo "[{$num}] {$campaign['id']}: {$campaign['name']}" . PHP_EOL;
        }
    }

} catch (SendPulseAPIException $e) {
    printf('Ошибка (%d): %s' . PHP_EOL, $e->getCode(), $e->getMessage());
} catch (TokenStorageException $e) {
    printf('Ошибка (%d): %s' . PHP_EOL, $e->getCode(), $e->getMessage());
} catch (Exception $e) {
    printf('Ошибка (%d): %s' . PHP_EOL, $e->getCode(), $e->getMessage());
}

Методы для работы с шаблонами

Методы для работы с шаблонами находятся в трейте \App\SendPulse\Templates:

Примеры работы с шаблонами:

use App\SendPulse\SendPulseAPI;
use App\SendPulse\SendPulseAPIException;
use App\SendPulse\TokenStorage\TokenStorageException;

try {
    $clientId = 'acbdef0123456789abcdef0123456789';
    $clientSecret = 'acbdef0123456789abcdef0123456789';

    $sendPulse = new SendPulseAPI();
    $sendPulse->auth($clientId, $clientSecret);

    // Получаем список всех собственных шаблонов
    $response = $sendPulse->getTemplates($owner = 'me');
    $num = 0;
    foreach ($response as $tpl) {
        $num++;
        echo "[{$num}] {$tpl['real_id']}: {$tpl['name']}" . PHP_EOL;
    }

    // Получаем информацию о шаблоне
    $templateId = 1318345;
    $response = $sendPulse->getTemplate($templateId);
    print_r($response);

    // Формируем параметры нового шаблона
    $params = [
        'name' => 'Тестовый шаблон',
        'body' => 'PHA+RXhhbXBsZSB0ZXh0PC9wPg==',
        'lang' => 'ru'
    ];

    // Добавляем шаблон
    $templateId = $sendPulse->addTemplate($params);
    print_r($templateId);

} catch (SendPulseAPIException $e) {
    printf('Ошибка (%d): %s' . PHP_EOL, $e->getCode(), $e->getMessage());
} catch (TokenStorageException $e) {
    printf('Ошибка (%d): %s' . PHP_EOL, $e->getCode(), $e->getMessage());
} catch (Exception $e) {
    printf('Ошибка (%d): %s' . PHP_EOL, $e->getCode(), $e->getMessage());
}

Методы для работы с SMTP сервисом

Методы для работы с SMTP сервисом находятся в трейте \App\SendPulse\Smtp:

Примеры работы с SMTP сервисом:

use App\SendPulse\SendPulseAPI;
use App\SendPulse\SendPulseAPIException;
use App\SendPulse\TokenStorage\TokenStorageException;

try {
    $clientId = 'acbdef0123456789abcdef0123456789';
    $clientSecret = 'acbdef0123456789abcdef0123456789';

    $sendPulse = new SendPulseAPI();
    $sendPulse->auth($clientId, $clientSecret);

    // Формируем параметры письма
    $params = [
        'email' => [
            'html'=> 'PHA+RXhhbXBsZSB0ZXh0PC9wPg==',
            'text' => "Текст письма",
            'subject' => 'Тестовое письмо',
            'from' => [
                'name' => 'Тестовый отправитель',
                'email' => 'sender@example.com'
            ],
        ],
        'to' => [
            [
                'name' => 'Тестовый получатель',
                'email' => 'recipient1@example.com'
            ]
        ]
    ];

    // Отправляем письмо
    $response = $sendPulse->sendEmails($params);
    print_r($response);

} catch (SendPulseAPIException $e) {
    printf('Ошибка (%d): %s' . PHP_EOL, $e->getCode(), $e->getMessage());
} catch (TokenStorageException $e) {
    printf('Ошибка (%d): %s' . PHP_EOL, $e->getCode(), $e->getMessage());
} catch (Exception $e) {
    printf('Ошибка (%d): %s' . PHP_EOL, $e->getCode(), $e->getMessage());
}

Вспомогательные методы класса

  • getLastResponse() Возвращает последний ответ API SendPulse.
  • request(string $method, string $path, array $params = []) ?array Отправляет запрос в API SendPulse и возвращает ответ.
    • $method - метод запроса (GET, POST, PATCH, PUT, DELETE);
    • $path - путь в URL запроса;
    • $params - параметры запроса.
  • getAll(Closure $closure) :\Generator Позволяет загрузить все сущности заданного типа, возвращая генератор.
    • $closure - анонимная функция-замыкание для загрузки сущностей: $closure(int $offset), где $offset - смещение выдачи.

Примеры использования вспомогательных методов:

use App\SendPulse\SendPulseAPI;
use App\SendPulse\SendPulseAPIException;
use App\SendPulse\TokenStorage\TokenStorageException;

try {
    $clientId = 'acbdef0123456789abcdef0123456789';
    $clientSecret = 'acbdef0123456789abcdef0123456789';

    $sendPulse = new SendPulseAPI();
    $sendPulse->auth($clientId, $clientSecret);

    // Получаем черный список email адресов
    $response = $sendPulse->request('GET', '/blacklist');
    print_r($response);

    // Получаем список всех отправленных писем
    $generator = $sendPulse->getAll(function ($offset) use ($sendPulse) {
        return $sendPulse->request('GET', '/smtp/emails', [ 'offset' => $offset ]);
    });
    $num = 0;
    foreach ($generator as $emails) {
        foreach ($emails as $email) {
            $num++;
            echo "[{$num}] {$email['smtp_answer_code_explain']} {$email['recipient']}" . PHP_EOL;
        }
    }

    // Получаем последний ответ API SendPulse
    $response = $sendPulse->getLastResponse();
    print_r($response);

} catch (SendPulseAPIException $e) {
    printf('Ошибка (%d): %s' . PHP_EOL, $e->getCode(), $e->getMessage());
} catch (TokenStorageException $e) {
    printf('Ошибка (%d): %s' . PHP_EOL, $e->getCode(), $e->getMessage());
} catch (Exception $e) {
    printf('Ошибка (%d): %s' . PHP_EOL, $e->getCode(), $e->getMessage());
}

Вспомогательные классы

Класс HTTP

Класс \App\HTTP\HTTP обеспечивает:

  • формирование POST запросов к API SendPulse по протоколу HTTPS;
  • троттлинг запросов к API на требуемом уровне - не более 10-и запросов в секунду;
  • вывод отладочной информации о запросах к API в STDOUT.

При возникновении ошибок выбрасывается исключение с объектом класса \App\HTTP\HTTPException.

Дополнительные параметры

Дополнительные параметры устанавливаются через публичные свойства объекта класса \App\HTTP\HTTP:

Пример использования класса

use App\SendPulse\SendPulseAPI;
use App\SendPulse\SendPulseAPIException;
use App\SendPulse\TokenStorage\TokenStorageException;
use App\HTTP\HTTP;

try {
    $clientId = 'acbdef0123456789abcdef0123456789';
    $clientSecret = 'acbdef0123456789abcdef0123456789';

    $sendPulse = new SendPulseAPI();

    // Устанавливаем максимальный уровень вывода отладочных сообщений в STDOUT
    $sendPulse->http->debugLevel = HTTP::DEBUG_URL |  HTTP::DEBUG_HEADERS | HTTP::DEBUG_CONTENT;

    // Устанавливаем троттлинг запросов на уровне не более 1 запроса в секунду
    $sendPulse->http->throttle = 1;

    // Устанавливаем таймаут обмена данными c API в 30 секунд
    $sendPulse->http->curlTimeout = 30;

    $sendPulse->auth($clientId, $clientSecret);

    // Получаем список отправителей email
    $response = $sendPulse->request('GET', '/senders');
    print_r($response);

} catch (SendPulseAPIException $e) {
    printf('Ошибка (%d): %s' . PHP_EOL, $e->getCode(), $e->getMessage());
} catch (TokenStorageException $e) {
    printf('Ошибка (%d): %s' . PHP_EOL, $e->getCode(), $e->getMessage());
} catch (Exception $e) {
    printf('Ошибка (%d): %s' . PHP_EOL, $e->getCode(), $e->getMessage());
}

Примеры отладочных сообщений

[1] ===> GET https://api.sendpulse.com/addressbooks
GET /addressbooks HTTP/2
Host: api.sendpulse.com
user-agent: HTTP-client/3.x.x
accept: */*
authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiI


[1] <=== RESPONSE 0.4566s (200)
HTTP/2 200
server: nginx/1.14.0 (Ubuntu)
date: Mon, 08 Feb 2021 06:45:38 GMT
content-type: application/json
vary: Accept-Encoding
x-powered-by: PHP/7.1.33
cache-control: private, must-revalidate
pragma: no-cache
expires: -1
access-control-allow-origin: *
access-control-allow-methods: GET, POST, DELETE, PUT, OPTIONS
access-control-allow-headers: Authorization,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range
access-control-expose-headers: Content-Length,Content-Range
x-sp-pr: lpr8

[{"id":89206572,"name":"Addressbook 1","all_email_qty":694,"active_email_qty":693,"inactive_email_qty":0,"creationdate":"2021-02-05 08:48:44","status":0,"status_explain":"Active"}

Класс DebugLogger

Класс \App\DebugLogger\DebugLogger обеспечивает логирование запросов и ответов к API SendPulse в файл.
При возникновении ошибок выбрасывается исключение с объектом класса \App\DebugLogger\DebugLoggerException.

Методы класса

  • static instance(string $logFileName = 'debug.log') :self
    Возвращает единственный объект данного класса для заданного лог-файла $logFileName.
    • $logFileName - имя лог-файла.
  • save(mixed $info, object $object = null, string $header = null) :void Сохраняет подлежащую логированию информацию в файл.
    • $info - строка, массив или объект для логирования;
    • $object - ссылка на объект класса в котором выполняется логирование;
    • $header - строка заголовка для сохраняемой в лог файл информации.

Дополнительные параметры

Дополнительные параметры устанавливаются через публичные свойства класса \App\DebugLogger\DebugLogger:

Пример использования класса

use App\SendPulse\SendPulseAPI;
use App\SendPulse\SendPulseAPIException;
use App\SendPulse\TokenStorage\TokenStorageException;
use App\DebugLogger\DebugLogger;

try {
    $clientId = 'acbdef0123456789abcdef0123456789';
    $clientSecret = 'acbdef0123456789abcdef0123456789';

    $sendPulse = new SendPulseAPI();

    // Устанавливаем каталог для сохранения лог файлов
    DebugLogger::$logFileDir = 'logs/';

    // Создаем объект класса логирования
    $logFileName = 'debug_sendpulseapi.log';
    $logger = DebugLogger::instance($logFileName);

    // Включаем логирование
    $logger->isActive = true;

    // Устанавливаем логгер
    $sendPulse->setLogger($logger);

    $sendPulse->auth($clientId, $clientSecret);

    // Получаем список отправителей email
    $response = $sendPulse->request('GET', '/senders');
    print_r($response);

} catch (SendPulseAPIException $e) {
    printf('Ошибка (%d): %s' . PHP_EOL, $e->getCode(), $e->getMessage());
} catch (TokenStorageException $e) {
    printf('Ошибка (%d): %s' . PHP_EOL, $e->getCode(), $e->getMessage());
} catch (Exception $e) {
    printf('Ошибка (%d): %s' . PHP_EOL, $e->getCode(), $e->getMessage());
}

Пример результатов логирования

*** cb8lim0 [2021-02-08 06:30:06.701680 +00:00 Δ0.000981 s, 0.65/2.00 MiB] ********************
* Class: App\SendPulse\SendPulseAPI
ЗАПРОС: POST /oauth/access_token
{
    "grant_type": "client_credentials",
    "client_id": "11111111111111111111111111111",
    "client_secret": "22222222222222222222222222222"
}

*** cb8lim0 [2021-02-08 06:30:06.920716 +00:00 Δ0.219036 s, 0.65/2.00 MiB] ********************
* Class: App\SendPulse\SendPulseAPI
ОТВЕТ: POST /oauth/access_token
{
    "token_type": "Bearer",
    "expires_in": 3600,
    "access_token": "3333333333333333333333333333333333"
}

Формат заголовков лога

*** cb8lim0 [2021-02-08 06:30:06.920716 +00:00 Δ0.219036 s, 0.65/2.00 MiB] ********************
* Class: App\SendPulse\SendPulseAPI
  • cb8lim0 - уникальный буквенно-цифровой [a-z0-9]+ идентификатор объекта класса DebugLogger, позволяющий находить в лог файле записи, созданные одним и тем же процессом;
  • 2021-02-08 06:30:06.920716 +00:00 - дата и время сохранения информации с точностью до микросекунд;
  • Δ0.219036 s - время, прошедшее с момента предыдущего сохранения информации в секундах и микросекундах;
  • 0.65/2.00 MiB - данные об используемой оперативной памяти в единицах количества информации с двоичными приставками:
    • 0.65 - максимальный объем памяти, который был выделен PHP-скрипту системой;
    • 2.00 - реальный объем памяти, выделенный PHP-скрипту системой;
  • 'Class: App\SendPulse\SendPulseAPI' - полное имя класса из которого сделано сохранение в лог файл.

Автор

© 2020-2021 andrey-tech

Лицензия

Данный код распространяется на условиях лицензии MIT.