mirror of
https://github.com/danog/TelegramApiServer.git
synced 2024-11-26 11:54:42 +01:00
Merge pull request #2 from danog/upstream_docker_image
Upstream docker image
This commit is contained in:
commit
151c00516f
30
Dockerfile
30
Dockerfile
@ -1,31 +1,13 @@
|
|||||||
FROM php:8.3-cli
|
FROM danog/madelineproto:latest
|
||||||
|
|
||||||
RUN apt-get update && apt-get upgrade -y
|
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
|
||||||
RUN true \
|
|
||||||
# Install main extension
|
|
||||||
&& apt-get install procps git zip vim libzip-dev libgmp-dev libuv1-dev libssl-dev libnghttp2-dev libffi-dev libicu-dev libonig-dev libxml2-dev libpng-dev -y \
|
|
||||||
&& docker-php-ext-install -j$(nproc) sockets bcmath mysqli pdo_mysql pcntl ffi intl gmp zip gd \
|
|
||||||
# Install additional extension
|
|
||||||
&& mkdir -p /usr/src/php/ext/ && cd /usr/src/php/ext/ \
|
|
||||||
&& pecl bundle uv && pecl bundle igbinary \
|
|
||||||
&& docker-php-ext-install -j$(nproc) uv igbinary \
|
|
||||||
# Install PrimeModule for AuthKey generation speedup
|
|
||||||
&& git clone https://github.com/danog/PrimeModule-ext \
|
|
||||||
&& cd PrimeModule-ext && make -j$(nproc) \
|
|
||||||
&& make install \
|
|
||||||
&& cd ../ \
|
|
||||||
&& rm -rf PrimeModule-ext/ \
|
|
||||||
# Install composer
|
|
||||||
&& curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
|
|
||||||
# Cleanup
|
|
||||||
&& docker-php-source delete \
|
|
||||||
&& apt-get autoremove --purge -y && apt-get autoclean -y && apt-get clean -y \
|
|
||||||
&& rm -rf /usr/src
|
|
||||||
|
|
||||||
COPY --from=ghcr.io/ufoscout/docker-compose-wait:latest /wait /usr/local/bin/docker-compose-wait
|
COPY --from=ghcr.io/ufoscout/docker-compose-wait:latest /wait /usr/local/bin/docker-compose-wait
|
||||||
|
|
||||||
ADD docker/php/conf.d/. "$PHP_INI_DIR/conf.d/"
|
RUN echo 1.0.0 > /tas_version
|
||||||
|
|
||||||
EXPOSE 9503
|
EXPOSE 9503
|
||||||
|
|
||||||
ENTRYPOINT ["./entrypoint.sh"]
|
ENV UV_USE_IO_URING=0
|
||||||
|
|
||||||
|
ENTRYPOINT ["./entrypoint.sh"]
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
FROM xtrime/telegram-api-server:latest
|
FROM xtrime/telegram-api-server:latest
|
||||||
|
|
||||||
ADD docker/php/conf.d/. "$PHP_INI_DIR/conf.d/"
|
ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
|
||||||
|
|
||||||
RUN pecl install xdebug \
|
RUN echo "opcache.jit=disable" >> "$PHP_INI_DIR/conf.d/xdebug.ini"
|
||||||
&& echo "" >> "$PHP_INI_DIR/conf.d/xdebug.ini" \
|
|
||||||
&& echo "zend_extension=xdebug.so" >> "$PHP_INI_DIR/conf.d/xdebug.ini"
|
RUN chmod +x /usr/local/bin/install-php-extensions && \
|
||||||
|
install-php-extensions xdebug && \
|
||||||
|
rm /usr/local/bin/install-php-extensions
|
||||||
|
|
||||||
|
ADD dev/. "$PHP_INI_DIR/conf.d/"
|
||||||
|
|
||||||
EXPOSE 9503
|
EXPOSE 9503
|
||||||
EXPOSE 9003
|
EXPOSE 9003
|
||||||
|
|
||||||
ENTRYPOINT ["./entrypoint.sh"]
|
ENTRYPOINT ["./entrypoint.sh"]
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use Amp\Future\UnhandledFutureError;
|
||||||
|
use Amp\SignalException;
|
||||||
|
use Amp\Sql\SqlException;
|
||||||
|
use danog\MadelineProto\SecurityException;
|
||||||
use Revolt\EventLoop;
|
use Revolt\EventLoop;
|
||||||
use TelegramApiServer\Logger;
|
use TelegramApiServer\Logger;
|
||||||
use TelegramApiServer\Migrations\EnvUpgrade;
|
use TelegramApiServer\Migrations\EnvUpgrade;
|
||||||
@ -113,3 +117,16 @@ if (!function_exists('emergency')) {
|
|||||||
Logger::getInstance()->emergency($message, $context);
|
Logger::getInstance()->emergency($message, $context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EventLoop::setErrorHandler(function (\Throwable $e) {
|
||||||
|
if ($e instanceof UnhandledFutureError) {
|
||||||
|
$e = $e->getPrevious();
|
||||||
|
}
|
||||||
|
if ($e instanceof SecurityException || $e instanceof SignalException || $e instanceof SqlException) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
if (str_starts_with($e->getMessage(), 'Could not connect to DC ')) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
emergency((string) $e);
|
||||||
|
});
|
@ -24,19 +24,22 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.1",
|
"php": "^8.2",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
"ext-mbstring": "*",
|
"ext-mbstring": "*",
|
||||||
"amphp/http-server": "^v3",
|
"amphp/http-server": "^3.3.1",
|
||||||
"amphp/http": "^v2",
|
"amphp/http": "^2.1.1",
|
||||||
"amphp/http-server-router": "^2",
|
"amphp/http-server-router": "^2",
|
||||||
"amphp/http-server-form-parser": "^v2",
|
"amphp/http-server-form-parser": "^v2",
|
||||||
"amphp/websocket-server": "^v3",
|
"amphp/websocket-server": "^3.0.1",
|
||||||
"amphp/websocket-client": "^v2",
|
"amphp/websocket-client": "^v2",
|
||||||
"vlucas/phpdotenv": "^4",
|
"vlucas/phpdotenv": "^4.3",
|
||||||
"danog/madelineproto": "dev-v8_fix_cleanup",
|
"danog/madelineproto": "dev-v8_fix_cleanup",
|
||||||
"amphp/dns": "2.x-dev"
|
"amphp/dns": "2.x-dev"
|
||||||
},
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"amphp/php-cs-fixer-config": "^2.0.1"
|
||||||
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"ext-pcntl": "Install pcintl for propper signal handling and healthcheck (enabled in .env)"
|
"ext-pcntl": "Install pcintl for propper signal handling and healthcheck (enabled in .env)"
|
||||||
},
|
},
|
||||||
@ -57,5 +60,8 @@
|
|||||||
"allow-plugins": {
|
"allow-plugins": {
|
||||||
"symfony/thanks": false
|
"symfony/thanks": false
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"cs-fix": "PHP_CS_FIXER_IGNORE_ENV=1 php -d pcre.jit=0 vendor/bin/php-cs-fixer fix -v"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2475
composer.lock
generated
2475
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -3,6 +3,8 @@
|
|||||||
use danog\MadelineProto\Logger;
|
use danog\MadelineProto\Logger;
|
||||||
use TelegramApiServer\EventObservers\LogObserver;
|
use TelegramApiServer\EventObservers\LogObserver;
|
||||||
|
|
||||||
|
use function Amp\Socket\SocketAddress\fromString;
|
||||||
|
|
||||||
$settings = [
|
$settings = [
|
||||||
'server' => [
|
'server' => [
|
||||||
'address' => (string)getenv('SERVER_ADDRESS'),
|
'address' => (string)getenv('SERVER_ADDRESS'),
|
||||||
@ -58,6 +60,10 @@ $settings = [
|
|||||||
'report_broken_media' => false,
|
'report_broken_media' => false,
|
||||||
'download_parallel_chunks' => 20,
|
'download_parallel_chunks' => 20,
|
||||||
],
|
],
|
||||||
|
'metrics' => [
|
||||||
|
'enable_prometheus_collection' => true, //(bool)getenv("PROMETHEUS_BIND_TO"),
|
||||||
|
'metrics_bind_to' => fromString("0.0.0.0:12345")
|
||||||
|
]
|
||||||
],
|
],
|
||||||
'api' => [
|
'api' => [
|
||||||
'ip_whitelist' => array_filter(
|
'ip_whitelist' => array_filter(
|
||||||
@ -79,4 +85,4 @@ if (empty($settings['telegram']['app_info']['api_id'])) {
|
|||||||
throw new InvalidArgumentException('Need to fill TELEGRAM_API_ID in .env.docker or .env');
|
throw new InvalidArgumentException('Need to fill TELEGRAM_API_ID in .env.docker or .env');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $settings;
|
return $settings;
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
opcache.jit=disable
|
||||||
|
zend_extension=xdebug.so
|
||||||
|
|
||||||
xdebug.max_nesting_level=512
|
xdebug.max_nesting_level=512
|
||||||
xdebug.mode=debug
|
xdebug.mode=debug
|
||||||
xdebug.start_with_request=yes
|
xdebug.start_with_request=yes
|
||||||
xdebug.client_host=host.docker.internal
|
xdebug.client_host=host.docker.internal
|
||||||
xdebug.client_port=9003
|
xdebug.client_port=9003
|
@ -19,7 +19,7 @@ services:
|
|||||||
max-size: "1024k"
|
max-size: "1024k"
|
||||||
max-file: "2"
|
max-file: "2"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: timeout 15 curl -f http://localhost:9503/system/healthcheck || bash -c 'kill -INT -1 && (sleep 5; kill -s 9 -1)'
|
test: timeout 15 curl -f http://localhost:9503/system/healthcheck || sh -c 'kill -INT -1 && (sleep 5; kill -s 9 -1)'
|
||||||
interval: 60s
|
interval: 60s
|
||||||
timeout: 30s
|
timeout: 30s
|
||||||
retries: 1
|
retries: 1
|
||||||
|
@ -5,6 +5,7 @@ services:
|
|||||||
service: base-api
|
service: base-api
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:9503:9503"
|
- "127.0.0.1:9503:9503"
|
||||||
|
- "12345:12345"
|
||||||
command:
|
command:
|
||||||
- "-s=session"
|
- "-s=session"
|
||||||
mysql:
|
mysql:
|
||||||
@ -15,4 +16,4 @@ services:
|
|||||||
- "127.0.0.1:9507:3306"
|
- "127.0.0.1:9507:3306"
|
||||||
networks:
|
networks:
|
||||||
default:
|
default:
|
||||||
name: telegram-api-server
|
name: telegram-api-server
|
||||||
|
@ -1 +0,0 @@
|
|||||||
zend.assertions=-1
|
|
@ -1,11 +0,0 @@
|
|||||||
; Extended PHP.ini file to enable JIT.
|
|
||||||
; ====================================
|
|
||||||
; Place this file under /usr/local/etc/php/conf.d/
|
|
||||||
zend_extension=opcache.so
|
|
||||||
opcache.enable=1
|
|
||||||
opcache.enable_cli=1
|
|
||||||
opcache.validate_timestamps=1
|
|
||||||
opcache.revalidate_freq=0
|
|
||||||
opcache.huge_code_pages=1
|
|
||||||
opcache.jit_buffer_size=100M
|
|
||||||
opcache.jit=function
|
|
@ -1,4 +1,14 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
VERSION=1.0.0
|
||||||
|
CURRENT_VERSION=$(cat /tas_version)
|
||||||
|
|
||||||
|
if [ "$VERSION" != "$CURRENT_VERSION" ]; then
|
||||||
|
echo "Wrong docker image version, expected $VERSION, got $CURRENT_VERSION, please run docker compose pull!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
composer install
|
||||||
|
|
||||||
docker-compose-wait \
|
docker-compose-wait \
|
||||||
&& nice -n 20 php server.php -e=.env.docker --docker "$@"
|
&& nice -n 20 php server.php -e=.env.docker --docker "$@"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all updates from MadelineProto EventHandler running inside TelegramApiServer via websocket
|
* Get all updates from MadelineProto EventHandler running inside TelegramApiServer via websocket.
|
||||||
* @see \TelegramApiServer\Controllers\EventsController
|
* @see \TelegramApiServer\Controllers\EventsController
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -13,7 +13,6 @@ use function Amp\Websocket\Client\connect;
|
|||||||
|
|
||||||
require 'vendor/autoload.php';
|
require 'vendor/autoload.php';
|
||||||
|
|
||||||
|
|
||||||
$shortopts = 'u::';
|
$shortopts = 'u::';
|
||||||
$longopts = [
|
$longopts = [
|
||||||
'url::',
|
'url::',
|
||||||
@ -55,4 +54,4 @@ if (defined('SIGINT')) {
|
|||||||
$signal = Amp\trapSignal([SIGINT, SIGTERM]);
|
$signal = Amp\trapSignal([SIGINT, SIGTERM]);
|
||||||
} else {
|
} else {
|
||||||
EventLoop::run();
|
EventLoop::run();
|
||||||
}
|
}
|
||||||
|
@ -83,9 +83,6 @@ foreach ($options['session'] as $session) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StartUpFixes::fix();
|
StartUpFixes::fix();
|
||||||
foreach ($sessions as $session) {
|
|
||||||
StartUpFixes::removeBrokenIpc($session);
|
|
||||||
}
|
|
||||||
|
|
||||||
new TelegramApiServer\Server\Server(
|
new TelegramApiServer\Server\Server(
|
||||||
$options,
|
$options,
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
<?php
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace TelegramApiServer;
|
namespace TelegramApiServer;
|
||||||
|
|
||||||
|
use Amp\Sync\LocalKeyedMutex;
|
||||||
use danog\MadelineProto\API;
|
use danog\MadelineProto\API;
|
||||||
use danog\MadelineProto\APIWrapper;
|
use danog\MadelineProto\APIWrapper;
|
||||||
use danog\MadelineProto\Settings;
|
use danog\MadelineProto\Settings;
|
||||||
@ -11,10 +12,9 @@ use InvalidArgumentException;
|
|||||||
use Psr\Log\LogLevel;
|
use Psr\Log\LogLevel;
|
||||||
use ReflectionProperty;
|
use ReflectionProperty;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use TelegramApiServer\EventObservers\EventHandler;
|
|
||||||
use TelegramApiServer\EventObservers\EventObserver;
|
use TelegramApiServer\EventObservers\EventObserver;
|
||||||
|
|
||||||
class Client
|
final class Client
|
||||||
{
|
{
|
||||||
public static Client $self;
|
public static Client $self;
|
||||||
/** @var API[] */
|
/** @var API[] */
|
||||||
@ -22,10 +22,10 @@ class Client
|
|||||||
|
|
||||||
public static function getInstance(): Client
|
public static function getInstance(): Client
|
||||||
{
|
{
|
||||||
if (empty(static::$self)) {
|
if (empty(self::$self)) {
|
||||||
static::$self = new static();
|
self::$self = new static();
|
||||||
}
|
}
|
||||||
return static::$self;
|
return self::$self;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function connect(array $sessionFiles)
|
public function connect(array $sessionFiles)
|
||||||
@ -40,15 +40,19 @@ class Client
|
|||||||
|
|
||||||
$this->startNotLoggedInSessions();
|
$this->startNotLoggedInSessions();
|
||||||
|
|
||||||
$sessionsCount = count($sessionFiles);
|
$sessionsCount = \count($sessionFiles);
|
||||||
warning(
|
warning(
|
||||||
"\nTelegramApiServer ready."
|
"\nTelegramApiServer ready."
|
||||||
. "\nNumber of sessions: {$sessionsCount}."
|
. "\nNumber of sessions: {$sessionsCount}."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static LocalKeyedMutex $mutex;
|
||||||
|
|
||||||
public function addSession(string $session, array $settings = []): API
|
public function addSession(string $session, array $settings = []): API
|
||||||
{
|
{
|
||||||
|
self::$mutex ??= new LocalKeyedMutex;
|
||||||
|
$lock = self::$mutex->acquire($session);
|
||||||
if (isset($this->instances[$session])) {
|
if (isset($this->instances[$session])) {
|
||||||
throw new InvalidArgumentException('Session already exists');
|
throw new InvalidArgumentException('Session already exists');
|
||||||
}
|
}
|
||||||
@ -58,8 +62,8 @@ class Client
|
|||||||
if ($settings) {
|
if ($settings) {
|
||||||
Files::saveSessionSettings($session, $settings);
|
Files::saveSessionSettings($session, $settings);
|
||||||
}
|
}
|
||||||
$settings = array_replace_recursive(
|
$settings = \array_replace_recursive(
|
||||||
(array)Config::getInstance()->get('telegram'),
|
(array) Config::getInstance()->get('telegram'),
|
||||||
Files::getSessionSettings($session),
|
Files::getSessionSettings($session),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -87,14 +91,9 @@ class Client
|
|||||||
$instance->unsetEventHandler();
|
$instance->unsetEventHandler();
|
||||||
}
|
}
|
||||||
unset($instance);
|
unset($instance);
|
||||||
gc_collect_cycles();
|
\gc_collect_cycles();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string|null $session
|
|
||||||
*
|
|
||||||
* @return API
|
|
||||||
*/
|
|
||||||
public function getSession(?string $session = null): API
|
public function getSession(?string $session = null): API
|
||||||
{
|
{
|
||||||
if (!$this->instances) {
|
if (!$this->instances) {
|
||||||
@ -104,8 +103,8 @@ class Client
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!$session) {
|
if (!$session) {
|
||||||
if (count($this->instances) === 1) {
|
if (\count($this->instances) === 1) {
|
||||||
$session = (string)array_key_first($this->instances);
|
$session = (string) \array_key_first($this->instances);
|
||||||
} else {
|
} else {
|
||||||
throw new InvalidArgumentException(
|
throw new InvalidArgumentException(
|
||||||
'Multiple sessions detected. Specify which session to use. See README for examples.'
|
'Multiple sessions detected. Specify which session to use. See README for examples.'
|
||||||
@ -160,9 +159,10 @@ class Client
|
|||||||
return $wrapper;
|
return $wrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function getSettingsFromArray(string $session, array $settings, SettingsAbstract $settingsObject = new Settings()): SettingsAbstract {
|
private static function getSettingsFromArray(string $session, array $settings, SettingsAbstract $settingsObject = new Settings()): SettingsAbstract
|
||||||
|
{
|
||||||
foreach ($settings as $key => $value) {
|
foreach ($settings as $key => $value) {
|
||||||
if (is_array($value) && $key !== 'proxies') {
|
if (\is_array($value) && $key !== 'proxies') {
|
||||||
if ($key === 'db' && isset($value['type'])) {
|
if ($key === 'db' && isset($value['type'])) {
|
||||||
$type = match ($value['type']) {
|
$type = match ($value['type']) {
|
||||||
'memory' => new Settings\Database\Memory(),
|
'memory' => new Settings\Database\Memory(),
|
||||||
@ -180,23 +180,22 @@ class Client
|
|||||||
}
|
}
|
||||||
|
|
||||||
unset($value[$value['type']], $value['type'],);
|
unset($value[$value['type']], $value['type'],);
|
||||||
if (count($value) === 0) {
|
if (\count($value) === 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$method = 'get' . ucfirst(str_replace('_', '', ucwords($key, '_')));
|
$method = 'get' . \ucfirst(\str_replace('_', '', \ucwords($key, '_')));
|
||||||
self::getSettingsFromArray($session, $value, $settingsObject->$method());
|
self::getSettingsFromArray($session, $value, $settingsObject->$method());
|
||||||
} else {
|
} else {
|
||||||
if ($key === 'serializer' && is_string($value)) {
|
if ($key === 'serializer' && \is_string($value)) {
|
||||||
$value = SerializerType::from($value);
|
$value = SerializerType::from($value);
|
||||||
}
|
}
|
||||||
$method = 'set' . ucfirst(str_replace('_', '', ucwords($key, '_')));
|
$method = 'set' . \ucfirst(\str_replace('_', '', \ucwords($key, '_')));
|
||||||
$settingsObject->$method($value);
|
$settingsObject->$method($value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $settingsObject;
|
return $settingsObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,24 @@
|
|||||||
<?php
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
|
||||||
namespace TelegramApiServer;
|
namespace TelegramApiServer;
|
||||||
|
|
||||||
|
final class Config
|
||||||
class Config
|
|
||||||
{
|
{
|
||||||
private static ?Config $instance = null;
|
private static ?Config $instance = null;
|
||||||
private array $config;
|
private array $config;
|
||||||
|
|
||||||
public static function getInstance(): Config
|
public static function getInstance(): Config
|
||||||
{
|
{
|
||||||
if (null === static::$instance) {
|
if (null === self::$instance) {
|
||||||
static::$instance = new static();
|
self::$instance = new static();
|
||||||
}
|
}
|
||||||
|
|
||||||
return static::$instance;
|
return self::$instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* is not allowed to call from outside to prevent from creating multiple instances,
|
* is not allowed to call from outside to prevent from creating multiple instances,
|
||||||
* to use the singleton, you have to obtain the instance from Singleton::getInstance() instead
|
* to use the singleton, you have to obtain the instance from Singleton::getInstance() instead.
|
||||||
*/
|
*/
|
||||||
private function __construct()
|
private function __construct()
|
||||||
{
|
{
|
||||||
@ -39,12 +37,12 @@ class Config
|
|||||||
|
|
||||||
private function findByKey($key)
|
private function findByKey($key)
|
||||||
{
|
{
|
||||||
$key = (string)$key;
|
$key = (string) $key;
|
||||||
$path = explode('.', $key);
|
$path = \explode('.', $key);
|
||||||
|
|
||||||
$value = &$this->config;
|
$value = &$this->config;
|
||||||
foreach ($path as $pathKey) {
|
foreach ($path as $pathKey) {
|
||||||
if (!is_array($value) || !array_key_exists($pathKey, $value)) {
|
if (!\is_array($value) || !\array_key_exists($pathKey, $value)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
$value = &$value[$pathKey];
|
$value = &$value[$pathKey];
|
||||||
@ -53,4 +51,4 @@ class Config
|
|||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace TelegramApiServer\Controllers;
|
namespace TelegramApiServer\Controllers;
|
||||||
|
|
||||||
@ -18,8 +18,6 @@ use TelegramApiServer\MadelineProtoExtensions\ApiExtensions;
|
|||||||
use TelegramApiServer\MadelineProtoExtensions\SystemApiExtensions;
|
use TelegramApiServer\MadelineProtoExtensions\SystemApiExtensions;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
use UnexpectedValueException;
|
use UnexpectedValueException;
|
||||||
use function Amp\delay;
|
|
||||||
use function mb_strpos;
|
|
||||||
|
|
||||||
abstract class AbstractApiController
|
abstract class AbstractApiController
|
||||||
{
|
{
|
||||||
@ -29,7 +27,6 @@ abstract class AbstractApiController
|
|||||||
protected ?StreamedField $file = null;
|
protected ?StreamedField $file = null;
|
||||||
protected string $extensionClass;
|
protected string $extensionClass;
|
||||||
|
|
||||||
|
|
||||||
public array $page = [
|
public array $page = [
|
||||||
'headers' => self::JSON_HEADER,
|
'headers' => self::JSON_HEADER,
|
||||||
'success' => false,
|
'success' => false,
|
||||||
@ -83,19 +80,19 @@ abstract class AbstractApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Получаем параметры из GET и POST
|
* Получаем параметры из GET и POST.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
private function resolveRequest(): void
|
private function resolveRequest(): void
|
||||||
{
|
{
|
||||||
$query = $this->request->getUri()->getQuery();
|
$query = $this->request->getUri()->getQuery();
|
||||||
$contentType = (string)$this->request->getHeader('Content-Type');
|
$contentType = (string) $this->request->getHeader('Content-Type');
|
||||||
|
|
||||||
parse_str($query, $get);
|
\parse_str($query, $get);
|
||||||
|
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case $contentType === 'application/x-www-form-urlencoded':
|
case $contentType === 'application/x-www-form-urlencoded':
|
||||||
case mb_strpos($contentType, 'multipart/form-data') !== false:
|
case \str_contains($contentType, 'multipart/form-data'):
|
||||||
$form = (new StreamingFormParser())->parseForm($this->request);
|
$form = (new StreamingFormParser())->parseForm($this->request);
|
||||||
$post = [];
|
$post = [];
|
||||||
|
|
||||||
@ -107,27 +104,27 @@ abstract class AbstractApiController
|
|||||||
//We need to break loop without getting file
|
//We need to break loop without getting file
|
||||||
//All other post field will be omitted, hope we dont need them :)
|
//All other post field will be omitted, hope we dont need them :)
|
||||||
break;
|
break;
|
||||||
} else {
|
|
||||||
$post[$field->getName()] = $field->buffer();
|
|
||||||
}
|
}
|
||||||
|
$post[$field->getName()] = $field->buffer();
|
||||||
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case $contentType === 'application/json':
|
case $contentType === 'application/json':
|
||||||
$body = $this->request->getBody()->buffer();
|
$body = $this->request->getBody()->buffer();
|
||||||
$post = json_decode($body, 1);
|
$post = \json_decode($body, true);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
$body = $this->request->getBody()->buffer();
|
$body = $this->request->getBody()->buffer();
|
||||||
parse_str($body, $post);
|
\parse_str($body, $post);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->parameters = array_merge((array)$post, $get);
|
$this->parameters = \array_merge((array) $post, $get);
|
||||||
$this->parameters = array_values($this->parameters);
|
$this->parameters = \array_values($this->parameters);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Получает посты для формирования ответа
|
* Получает посты для формирования ответа.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
private function generateResponse(): void
|
private function generateResponse(): void
|
||||||
@ -159,16 +156,16 @@ abstract class AbstractApiController
|
|||||||
|
|
||||||
protected function callApiCommon(API $madelineProto)
|
protected function callApiCommon(API $madelineProto)
|
||||||
{
|
{
|
||||||
$pathCount = count($this->api);
|
$pathCount = \count($this->api);
|
||||||
if ($pathCount === 1 && method_exists($this->extensionClass, $this->api[0])) {
|
if ($pathCount === 1 && \method_exists($this->extensionClass, $this->api[0])) {
|
||||||
/** @var ApiExtensions|SystemApiExtensions $madelineProtoExtensions */
|
/** @var ApiExtensions|SystemApiExtensions $madelineProtoExtensions */
|
||||||
$madelineProtoExtensions = new $this->extensionClass($madelineProto, $this->request, $this->file);
|
$madelineProtoExtensions = new $this->extensionClass($madelineProto, $this->request, $this->file);
|
||||||
$result = $madelineProtoExtensions->{$this->api[0]}(...$this->parameters);
|
$result = $madelineProtoExtensions->{$this->api[0]}(...$this->parameters);
|
||||||
} else {
|
} else {
|
||||||
if ($this->api[0] === 'API') {
|
if ($this->api[0] === 'API') {
|
||||||
$madelineProto = Client::getWrapper($madelineProto)->getAPI();
|
$madelineProto = Client::getWrapper($madelineProto)->getAPI();
|
||||||
array_shift($this->api);
|
\array_shift($this->api);
|
||||||
$pathCount = count($this->api);
|
$pathCount = \count($this->api);
|
||||||
}
|
}
|
||||||
//Проверяем нет ли в MadilineProto такого метода.
|
//Проверяем нет ли в MadilineProto такого метода.
|
||||||
switch ($pathCount) {
|
switch ($pathCount) {
|
||||||
@ -190,9 +187,7 @@ abstract class AbstractApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Throwable $e
|
|
||||||
*
|
*
|
||||||
* @return AbstractApiController
|
|
||||||
* @throws Throwable
|
* @throws Throwable
|
||||||
*/
|
*/
|
||||||
private function setError(Throwable $e): self
|
private function setError(Throwable $e): self
|
||||||
@ -210,9 +205,8 @@ abstract class AbstractApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Кодирует ответ в нужный формат: json
|
* Кодирует ответ в нужный формат: json.
|
||||||
*
|
*
|
||||||
* @return Response|string
|
|
||||||
* @throws JsonException
|
* @throws JsonException
|
||||||
*/
|
*/
|
||||||
private function getResponse(): string|Response
|
private function getResponse(): string|Response
|
||||||
@ -230,7 +224,7 @@ abstract class AbstractApiController
|
|||||||
$data['success'] = true;
|
$data['success'] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = json_encode(
|
$result = \json_encode(
|
||||||
$data,
|
$data,
|
||||||
JSON_THROW_ON_ERROR |
|
JSON_THROW_ON_ERROR |
|
||||||
JSON_INVALID_UTF8_SUBSTITUTE |
|
JSON_INVALID_UTF8_SUBSTITUTE |
|
||||||
@ -244,15 +238,13 @@ abstract class AbstractApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Устанавливает http код ответа (200, 400, 404 и тд.)
|
* Устанавливает http код ответа (200, 400, 404 и тд.).
|
||||||
*
|
*
|
||||||
* @param int $code
|
|
||||||
*
|
*
|
||||||
* @return AbstractApiController
|
|
||||||
*/
|
*/
|
||||||
private function setPageCode(int $code): self
|
private function setPageCode(int $code): self
|
||||||
{
|
{
|
||||||
$this->page['code'] = $this->page['code'] === 200 ? $code : $this->page['code'];
|
$this->page['code'] = $this->page['code'] === 200 ? $code : $this->page['code'];
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,35 +1,25 @@
|
|||||||
<?php
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace TelegramApiServer\Controllers;
|
namespace TelegramApiServer\Controllers;
|
||||||
|
|
||||||
use Amp\Sync\LocalKeyedMutex;
|
|
||||||
use Amp\Sync\LocalMutex;
|
|
||||||
use Amp\Sync\StaticKeyMutex;
|
|
||||||
use Amp\Sync\SyncException;
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use Revolt\EventLoop;
|
|
||||||
use TelegramApiServer\Client;
|
use TelegramApiServer\Client;
|
||||||
use TelegramApiServer\Config;
|
use TelegramApiServer\Config;
|
||||||
use TelegramApiServer\Logger;
|
|
||||||
use function Amp\async;
|
|
||||||
use function Amp\delay;
|
|
||||||
use function Amp\Future\awaitAll;
|
|
||||||
|
|
||||||
class ApiController extends AbstractApiController
|
final class ApiController extends AbstractApiController
|
||||||
{
|
{
|
||||||
|
|
||||||
private ?string $session = '';
|
private ?string $session = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Получаем параметры из uri
|
* Получаем параметры из uri.
|
||||||
*
|
*
|
||||||
* @param array $path
|
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
protected function resolvePath(array $path): void
|
protected function resolvePath(array $path): void
|
||||||
{
|
{
|
||||||
$this->session = $path['session'] ?? null;
|
$this->session = $path['session'] ?? null;
|
||||||
$this->api = explode('.', $path['method'] ?? '');
|
$this->api = \explode('.', $path['method'] ?? '');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -44,37 +34,6 @@ class ApiController extends AbstractApiController
|
|||||||
return $this->callApiCommon($madelineProto);
|
return $this->callApiCommon($madelineProto);
|
||||||
}
|
}
|
||||||
|
|
||||||
//GROUP REQUESTS IN BULKS
|
return $this->callApiCommon($madelineProto);
|
||||||
static $futures = [];
|
|
||||||
|
|
||||||
$futures[] = $future = async($this->callApiCommon(...), $madelineProto);
|
|
||||||
delay($this->waitNextTick());
|
|
||||||
|
|
||||||
if ($futures) {
|
|
||||||
awaitAll($futures);
|
|
||||||
Logger::getInstance()->notice("Executed bulk requests:" . count($futures));
|
|
||||||
$futures = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $future->await();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/**
|
|
||||||
* Sync threads execution via time ticks
|
|
||||||
* Need to enable madelineProto futures bulk execution
|
|
||||||
* @param float $tick interval of execution in seconds.
|
|
||||||
*/
|
|
||||||
protected function waitNextTick(float $tick = 0.5): float {
|
|
||||||
$tickMs = $tick * 1000;
|
|
||||||
$now = (int)(microtime(true) * 1000);
|
|
||||||
$currentTick = intdiv((int)(microtime(true) * 1000), $tickMs);
|
|
||||||
$nextTick = ($currentTick + 1);
|
|
||||||
$nextTickTime = $nextTick * $tickMs;
|
|
||||||
$wait = round(($nextTickTime - $now)/1000, 3);
|
|
||||||
|
|
||||||
Logger::getInstance()->notice("Waiting $wait seconds before tick");
|
|
||||||
|
|
||||||
return $wait;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
<?php
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace TelegramApiServer\Controllers;
|
namespace TelegramApiServer\Controllers;
|
||||||
|
|
||||||
|
use Amp\Http\HttpStatus;
|
||||||
use Amp\Http\Server\Request;
|
use Amp\Http\Server\Request;
|
||||||
use Amp\Http\Server\Response;
|
use Amp\Http\Server\Response;
|
||||||
use Amp\Http\Server\Router;
|
use Amp\Http\Server\Router;
|
||||||
use Amp\Http\HttpStatus;
|
|
||||||
use Amp\Http\Server\SocketHttpServer;
|
use Amp\Http\Server\SocketHttpServer;
|
||||||
use Amp\Websocket\Server\Rfc6455Acceptor;
|
use Amp\Websocket\Server\Rfc6455Acceptor;
|
||||||
use Amp\Websocket\Server\Websocket as WebsocketServer;
|
use Amp\Websocket\Server\Websocket as WebsocketServer;
|
||||||
@ -20,7 +20,7 @@ use TelegramApiServer\EventObservers\EventObserver;
|
|||||||
use TelegramApiServer\Logger;
|
use TelegramApiServer\Logger;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
class EventsController implements WebsocketClientHandler, WebsocketAcceptor
|
final class EventsController implements WebsocketClientHandler, WebsocketAcceptor
|
||||||
{
|
{
|
||||||
private const PING_INTERVAL_MS = 10_000;
|
private const PING_INTERVAL_MS = 10_000;
|
||||||
private WebsocketClientGateway $gateway;
|
private WebsocketClientGateway $gateway;
|
||||||
@ -82,7 +82,7 @@ class EventsController implements WebsocketClientHandler, WebsocketAcceptor
|
|||||||
|
|
||||||
EventObserver::startEventHandler($requestedSession);
|
EventObserver::startEventHandler($requestedSession);
|
||||||
|
|
||||||
$pingLoop = EventLoop::repeat(self::PING_INTERVAL_MS, static fn() => $client->ping());
|
$pingLoop = EventLoop::repeat(self::PING_INTERVAL_MS, static fn () => $client->ping());
|
||||||
|
|
||||||
$client->onClose(static function () use ($clientId, $requestedSession, $pingLoop) {
|
$client->onClose(static function () use ($clientId, $requestedSession, $pingLoop) {
|
||||||
EventLoop::cancel($pingLoop);
|
EventLoop::cancel($pingLoop);
|
||||||
@ -104,7 +104,7 @@ class EventsController implements WebsocketClientHandler, WebsocketAcceptor
|
|||||||
];
|
];
|
||||||
|
|
||||||
$this->gateway->multicastText(
|
$this->gateway->multicastText(
|
||||||
json_encode(
|
\json_encode(
|
||||||
$update,
|
$update,
|
||||||
JSON_THROW_ON_ERROR |
|
JSON_THROW_ON_ERROR |
|
||||||
JSON_INVALID_UTF8_SUBSTITUTE |
|
JSON_INVALID_UTF8_SUBSTITUTE |
|
||||||
@ -115,4 +115,4 @@ class EventsController implements WebsocketClientHandler, WebsocketAcceptor
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace TelegramApiServer\Controllers;
|
namespace TelegramApiServer\Controllers;
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ use Revolt\EventLoop;
|
|||||||
use TelegramApiServer\EventObservers\LogObserver;
|
use TelegramApiServer\EventObservers\LogObserver;
|
||||||
use TelegramApiServer\Logger;
|
use TelegramApiServer\Logger;
|
||||||
|
|
||||||
class LogController implements WebsocketClientHandler, WebsocketAcceptor
|
final class LogController implements WebsocketClientHandler, WebsocketAcceptor
|
||||||
{
|
{
|
||||||
private const PING_INTERVAL_MS = 10_000;
|
private const PING_INTERVAL_MS = 10_000;
|
||||||
private WebsocketClientGateway $gateway;
|
private WebsocketClientGateway $gateway;
|
||||||
@ -68,7 +68,7 @@ class LogController implements WebsocketClientHandler, WebsocketAcceptor
|
|||||||
{
|
{
|
||||||
$clientId = $client->getId();
|
$clientId = $client->getId();
|
||||||
|
|
||||||
$pingLoop = EventLoop::repeat(self::PING_INTERVAL_MS, static fn() => $client->ping());
|
$pingLoop = EventLoop::repeat(self::PING_INTERVAL_MS, static fn () => $client->ping());
|
||||||
|
|
||||||
$client->onClose(static function () use ($clientId, $pingLoop) {
|
$client->onClose(static function () use ($clientId, $pingLoop) {
|
||||||
EventLoop::cancel($pingLoop);
|
EventLoop::cancel($pingLoop);
|
||||||
@ -90,7 +90,7 @@ class LogController implements WebsocketClientHandler, WebsocketAcceptor
|
|||||||
];
|
];
|
||||||
|
|
||||||
$this->gateway->multicastText(
|
$this->gateway->multicastText(
|
||||||
json_encode(
|
\json_encode(
|
||||||
$update,
|
$update,
|
||||||
JSON_THROW_ON_ERROR |
|
JSON_THROW_ON_ERROR |
|
||||||
JSON_INVALID_UTF8_SUBSTITUTE |
|
JSON_INVALID_UTF8_SUBSTITUTE |
|
||||||
@ -101,4 +101,4 @@ class LogController implements WebsocketClientHandler, WebsocketAcceptor
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,23 @@
|
|||||||
<?php
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace TelegramApiServer\Controllers;
|
namespace TelegramApiServer\Controllers;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use TelegramApiServer\Client;
|
use TelegramApiServer\Client;
|
||||||
|
|
||||||
class SystemController extends AbstractApiController
|
final class SystemController extends AbstractApiController
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Получаем параметры из uri
|
* Получаем параметры из uri.
|
||||||
*
|
*
|
||||||
* @param array $path
|
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
protected function resolvePath(array $path): void
|
protected function resolvePath(array $path): void
|
||||||
{
|
{
|
||||||
$this->api = explode('.', $path['method'] ?? '');
|
$this->api = \explode('.', $path['method'] ?? '');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return mixed
|
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
protected function callApi()
|
protected function callApi()
|
||||||
@ -29,4 +27,4 @@ class SystemController extends AbstractApiController
|
|||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ namespace TelegramApiServer\EventObservers;
|
|||||||
|
|
||||||
use TelegramApiServer\Files;
|
use TelegramApiServer\Files;
|
||||||
|
|
||||||
class EventHandler extends \danog\MadelineProto\EventHandler
|
final class EventHandler extends \danog\MadelineProto\EventHandler
|
||||||
{
|
{
|
||||||
public static array $instances = [];
|
public static array $instances = [];
|
||||||
private string $sessionName;
|
private string $sessionName;
|
||||||
@ -12,8 +12,8 @@ class EventHandler extends \danog\MadelineProto\EventHandler
|
|||||||
public function onStart()
|
public function onStart()
|
||||||
{
|
{
|
||||||
$this->sessionName = Files::getSessionName($this->wrapper->getSession()->getSessionPath());
|
$this->sessionName = Files::getSessionName($this->wrapper->getSession()->getSessionPath());
|
||||||
if (empty(static::$instances[$this->sessionName])) {
|
if (empty(self::$instances[$this->sessionName])) {
|
||||||
static::$instances[$this->sessionName] = true;
|
self::$instances[$this->sessionName] = true;
|
||||||
warning("Event observer CONSTRUCTED: {$this->sessionName}");
|
warning("Event observer CONSTRUCTED: {$this->sessionName}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -23,7 +23,7 @@ class EventHandler extends \danog\MadelineProto\EventHandler
|
|||||||
if (empty($this->sessionName)) {
|
if (empty($this->sessionName)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
unset(static::$instances[$this->sessionName]);
|
unset(self::$instances[$this->sessionName]);
|
||||||
warning("Event observer DESTRUCTED: {$this->sessionName}");
|
warning("Event observer DESTRUCTED: {$this->sessionName}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,4 +32,4 @@ class EventHandler extends \danog\MadelineProto\EventHandler
|
|||||||
info("Received update from session: {$this->sessionName}");
|
info("Received update from session: {$this->sessionName}");
|
||||||
EventObserver::notify($update, $this->sessionName);
|
EventObserver::notify($update, $this->sessionName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
<?php
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace TelegramApiServer\EventObservers;
|
namespace TelegramApiServer\EventObservers;
|
||||||
|
|
||||||
|
|
||||||
use danog\MadelineProto\APIWrapper;
|
use danog\MadelineProto\APIWrapper;
|
||||||
use ReflectionProperty;
|
use ReflectionProperty;
|
||||||
use TelegramApiServer\Client;
|
use TelegramApiServer\Client;
|
||||||
use TelegramApiServer\Logger;
|
use TelegramApiServer\Logger;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
class EventObserver
|
final class EventObserver
|
||||||
{
|
{
|
||||||
use ObserverTrait;
|
use ObserverTrait;
|
||||||
|
|
||||||
@ -18,7 +17,7 @@ class EventObserver
|
|||||||
|
|
||||||
public static function notify(array $update, string $sessionName)
|
public static function notify(array $update, string $sessionName)
|
||||||
{
|
{
|
||||||
foreach (static::$subscribers as $clientId => $callback) {
|
foreach (self::$subscribers as $clientId => $callback) {
|
||||||
notice("Pass update to callback. ClientId: {$clientId}");
|
notice("Pass update to callback. ClientId: {$clientId}");
|
||||||
$callback($update, $sessionName);
|
$callback($update, $sessionName);
|
||||||
}
|
}
|
||||||
@ -26,16 +25,16 @@ class EventObserver
|
|||||||
|
|
||||||
private static function addSessionClient(string $session): void
|
private static function addSessionClient(string $session): void
|
||||||
{
|
{
|
||||||
if (empty(static::$sessionClients[$session])) {
|
if (empty(self::$sessionClients[$session])) {
|
||||||
static::$sessionClients[$session] = 0;
|
self::$sessionClients[$session] = 0;
|
||||||
}
|
}
|
||||||
++static::$sessionClients[$session];
|
++self::$sessionClients[$session];
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function removeSessionClient(string $session): void
|
private static function removeSessionClient(string $session): void
|
||||||
{
|
{
|
||||||
if (!empty(static::$sessionClients[$session])) {
|
if (!empty(self::$sessionClients[$session])) {
|
||||||
--static::$sessionClients[$session];
|
--self::$sessionClients[$session];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,14 +42,14 @@ class EventObserver
|
|||||||
{
|
{
|
||||||
$sessions = [];
|
$sessions = [];
|
||||||
if ($requestedSession === null) {
|
if ($requestedSession === null) {
|
||||||
$sessions = array_keys(Client::getInstance()->instances);
|
$sessions = \array_keys(Client::getInstance()->instances);
|
||||||
} else {
|
} else {
|
||||||
$sessions[] = $requestedSession;
|
$sessions[] = $requestedSession;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($sessions as $session) {
|
foreach ($sessions as $session) {
|
||||||
static::addSessionClient($session);
|
self::addSessionClient($session);
|
||||||
if (static::$sessionClients[$session] === 1) {
|
if (self::$sessionClients[$session] === 1) {
|
||||||
warning("Start EventHandler: {$session}");
|
warning("Start EventHandler: {$session}");
|
||||||
try {
|
try {
|
||||||
$instance = Client::getInstance()->getSession($session);
|
$instance = Client::getInstance()->getSession($session);
|
||||||
@ -60,7 +59,7 @@ class EventObserver
|
|||||||
EventHandler::cachePlugins(EventHandler::class);
|
EventHandler::cachePlugins(EventHandler::class);
|
||||||
$wrapper->getAPI()->setEventHandler(EventHandler::class);
|
$wrapper->getAPI()->setEventHandler(EventHandler::class);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
static::removeSessionClient($session);
|
self::removeSessionClient($session);
|
||||||
error('Cant set EventHandler', [
|
error('Cant set EventHandler', [
|
||||||
'session' => $session,
|
'session' => $session,
|
||||||
'exception' => Logger::getExceptionAsArray($e),
|
'exception' => Logger::getExceptionAsArray($e),
|
||||||
@ -74,19 +73,19 @@ class EventObserver
|
|||||||
{
|
{
|
||||||
$sessions = [];
|
$sessions = [];
|
||||||
if ($requestedSession === null) {
|
if ($requestedSession === null) {
|
||||||
$sessions = array_keys(Client::getInstance()->instances);
|
$sessions = \array_keys(Client::getInstance()->instances);
|
||||||
} else {
|
} else {
|
||||||
$sessions[] = $requestedSession;
|
$sessions[] = $requestedSession;
|
||||||
}
|
}
|
||||||
foreach ($sessions as $session) {
|
foreach ($sessions as $session) {
|
||||||
static::removeSessionClient($session);
|
self::removeSessionClient($session);
|
||||||
if (empty(static::$sessionClients[$session]) || $force) {
|
if (empty(self::$sessionClients[$session]) || $force) {
|
||||||
warning("Stopping EventHandler: {$session}");
|
warning("Stopping EventHandler: {$session}");
|
||||||
Client::getInstance()->instances[$session]->unsetEventHandler();
|
Client::getInstance()->instances[$session]->unsetEventHandler();
|
||||||
unset(EventHandler::$instances[$session], static::$sessionClients[$session]);
|
unset(EventHandler::$instances[$session], self::$sessionClients[$session]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,39 +1,39 @@
|
|||||||
<?php
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace TelegramApiServer\EventObservers;
|
namespace TelegramApiServer\EventObservers;
|
||||||
|
|
||||||
use TelegramApiServer\Logger;
|
use TelegramApiServer\Logger;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
class LogObserver
|
final class LogObserver
|
||||||
{
|
{
|
||||||
use ObserverTrait;
|
use ObserverTrait;
|
||||||
|
|
||||||
public static function notify(string $level, string $message, array $context = []): void
|
public static function notify(string $level, string $message, array $context = []): void
|
||||||
{
|
{
|
||||||
foreach (static::$subscribers as $clientId => $callback) {
|
foreach (self::$subscribers as $clientId => $callback) {
|
||||||
$callback($level, $message, $context);
|
$callback($level, $message, $context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param mixed|array|string $message
|
* @param mixed|array|string $message
|
||||||
* @param int $level
|
|
||||||
*/
|
*/
|
||||||
public static function log($message, int $level)
|
public static function log($message, int $level)
|
||||||
{
|
{
|
||||||
if (is_scalar($message)) {
|
if (\is_scalar($message)) {
|
||||||
Logger::getInstance()->log(Logger::$madelineLevels[$level], (string)$message);
|
Logger::getInstance()->log(Logger::$madelineLevels[$level], (string) $message);
|
||||||
} else {
|
} else {
|
||||||
if ($message instanceof Throwable) {
|
if ($message instanceof Throwable) {
|
||||||
$message = Logger::getExceptionAsArray($message);
|
$message = Logger::getExceptionAsArray($message);
|
||||||
}
|
}
|
||||||
if (is_array($message)) {
|
if (\is_array($message)) {
|
||||||
Logger::getInstance()->log(Logger::$madelineLevels[$level], '', $message);
|
Logger::getInstance()->log(Logger::$madelineLevels[$level], '', $message);
|
||||||
} else {
|
} else {
|
||||||
Logger::getInstance()->log(
|
Logger::getInstance()->log(
|
||||||
Logger::$madelineLevels[$level],
|
Logger::$madelineLevels[$level],
|
||||||
json_encode($message,
|
\json_encode(
|
||||||
|
$message,
|
||||||
JSON_UNESCAPED_UNICODE |
|
JSON_UNESCAPED_UNICODE |
|
||||||
JSON_PRETTY_PRINT |
|
JSON_PRETTY_PRINT |
|
||||||
JSON_INVALID_UTF8_SUBSTITUTE |
|
JSON_INVALID_UTF8_SUBSTITUTE |
|
||||||
@ -43,4 +43,4 @@ class LogObserver
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
<?php
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace TelegramApiServer\EventObservers;
|
namespace TelegramApiServer\EventObservers;
|
||||||
|
|
||||||
|
|
||||||
trait ObserverTrait
|
trait ObserverTrait
|
||||||
{
|
{
|
||||||
/** @var callable[] */
|
/** @var callable[] */
|
||||||
@ -11,17 +10,17 @@ trait ObserverTrait
|
|||||||
public static function addSubscriber($clientId, callable $callback): void
|
public static function addSubscriber($clientId, callable $callback): void
|
||||||
{
|
{
|
||||||
notice("Add event listener. ClientId: {$clientId}");
|
notice("Add event listener. ClientId: {$clientId}");
|
||||||
static::$subscribers[$clientId] = $callback;
|
self::$subscribers[$clientId] = $callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function removeSubscriber($clientId): void
|
public static function removeSubscriber($clientId): void
|
||||||
{
|
{
|
||||||
notice("Removing listener: {$clientId}");
|
notice("Removing listener: {$clientId}");
|
||||||
unset(static::$subscribers[$clientId]);
|
unset(self::$subscribers[$clientId]);
|
||||||
$listenersCount = count(static::$subscribers);
|
$listenersCount = \count(self::$subscribers);
|
||||||
notice("Event listeners left: {$listenersCount}");
|
notice("Event listeners left: {$listenersCount}");
|
||||||
if ($listenersCount === 0) {
|
if ($listenersCount === 0) {
|
||||||
static::$subscribers = [];
|
self::$subscribers = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<?php
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace TelegramApiServer\Exceptions;
|
namespace TelegramApiServer\Exceptions;
|
||||||
|
|
||||||
class NoMediaException extends NoticeException
|
final class NoMediaException extends NoticeException
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace TelegramApiServer\Exceptions;
|
namespace TelegramApiServer\Exceptions;
|
||||||
|
|
||||||
@ -7,4 +7,4 @@ use Exception;
|
|||||||
class NoticeException extends Exception
|
class NoticeException extends Exception
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
<?php
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace TelegramApiServer;
|
namespace TelegramApiServer;
|
||||||
|
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
|
||||||
class Files
|
final class Files
|
||||||
{
|
{
|
||||||
|
|
||||||
public const SESSION_EXTENSION = '.madeline';
|
public const SESSION_EXTENSION = '.madeline';
|
||||||
@ -13,11 +13,11 @@ class Files
|
|||||||
|
|
||||||
public static function checkOrCreateSessionFolder(string $session): void
|
public static function checkOrCreateSessionFolder(string $session): void
|
||||||
{
|
{
|
||||||
$directory = dirname($session);
|
$directory = \dirname($session);
|
||||||
if ($directory && $directory !== '.' && !is_dir($directory)) {
|
if ($directory && $directory !== '.' && !\is_dir($directory)) {
|
||||||
$parentDirectoryPermissions = fileperms(ROOT_DIR);
|
$parentDirectoryPermissions = \fileperms(ROOT_DIR);
|
||||||
if (!mkdir($directory, $parentDirectoryPermissions, true) && !is_dir($directory)) {
|
if (!\mkdir($directory, $parentDirectoryPermissions, true) && !\is_dir($directory)) {
|
||||||
throw new RuntimeException(sprintf('Directory "%s" was not created', $directory));
|
throw new RuntimeException(\sprintf('Directory "%s" was not created', $directory));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -28,8 +28,8 @@ class Files
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
preg_match(
|
\preg_match(
|
||||||
'~' . static::SESSION_FOLDER . "/(?'sessionName'.*?)" . static::SESSION_EXTENSION . '~',
|
'~' . self::SESSION_FOLDER . "/(?'sessionName'.*?)" . self::SESSION_EXTENSION . '~',
|
||||||
$sessionFile,
|
$sessionFile,
|
||||||
$matches
|
$matches
|
||||||
);
|
);
|
||||||
@ -37,31 +37,24 @@ class Files
|
|||||||
return $matches['sessionName'] ?? null;
|
return $matches['sessionName'] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string|null $session
|
|
||||||
*
|
|
||||||
* @param string $extension
|
|
||||||
*
|
|
||||||
* @return string|null
|
|
||||||
*/
|
|
||||||
public static function getSessionFile(?string $session, string $extension = self::SESSION_EXTENSION): ?string
|
public static function getSessionFile(?string $session, string $extension = self::SESSION_EXTENSION): ?string
|
||||||
{
|
{
|
||||||
if (!$session) {
|
if (!$session) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
$session = trim(trim($session), '/');
|
$session = \trim(\trim($session), '/');
|
||||||
$session = static::SESSION_FOLDER . '/' . $session . $extension;
|
$session = self::SESSION_FOLDER . '/' . $session . $extension;
|
||||||
$session = str_replace('//', '/', $session);
|
$session = \str_replace('//', '/', $session);
|
||||||
return $session;
|
return $session;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getSessionSettings(string $session): array
|
public static function getSessionSettings(string $session): array
|
||||||
{
|
{
|
||||||
$settingsFile = static::getSessionFile($session, static::SETTINGS_EXTENSION);
|
$settingsFile = self::getSessionFile($session, self::SETTINGS_EXTENSION);
|
||||||
$settings = [];
|
$settings = [];
|
||||||
if (file_exists($settingsFile)) {
|
if (\file_exists($settingsFile)) {
|
||||||
$settings = json_decode(
|
$settings = \json_decode(
|
||||||
file_get_contents($settingsFile),
|
\file_get_contents($settingsFile),
|
||||||
true,
|
true,
|
||||||
10,
|
10,
|
||||||
JSON_THROW_ON_ERROR
|
JSON_THROW_ON_ERROR
|
||||||
@ -73,10 +66,10 @@ class Files
|
|||||||
|
|
||||||
public static function saveSessionSettings(string $session, array $settings = []): void
|
public static function saveSessionSettings(string $session, array $settings = []): void
|
||||||
{
|
{
|
||||||
$settingsFile = static::getSessionFile($session, static::SETTINGS_EXTENSION);
|
$settingsFile = self::getSessionFile($session, self::SETTINGS_EXTENSION);
|
||||||
file_put_contents(
|
\file_put_contents(
|
||||||
$settingsFile,
|
$settingsFile,
|
||||||
json_encode(
|
\json_encode(
|
||||||
$settings,
|
$settings,
|
||||||
JSON_THROW_ON_ERROR |
|
JSON_THROW_ON_ERROR |
|
||||||
JSON_INVALID_UTF8_SUBSTITUTE |
|
JSON_INVALID_UTF8_SUBSTITUTE |
|
||||||
@ -89,11 +82,11 @@ class Files
|
|||||||
|
|
||||||
public static function globRecursive($pattern, $flags = 0): array
|
public static function globRecursive($pattern, $flags = 0): array
|
||||||
{
|
{
|
||||||
$files = glob($pattern, $flags) ?: [];
|
$files = \glob($pattern, $flags) ?: [];
|
||||||
foreach (glob(dirname($pattern) . '/*', GLOB_ONLYDIR | GLOB_NOSORT) as $dir) {
|
foreach (\glob(\dirname($pattern) . '/*', GLOB_ONLYDIR | GLOB_NOSORT) as $dir) {
|
||||||
$files = [...$files, ...static::globRecursive($dir . '/' . basename($pattern), $flags)];
|
$files = [...$files, ...self::globRecursive($dir . '/' . \basename($pattern), $flags)];
|
||||||
}
|
}
|
||||||
return $files;
|
return $files;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
108
src/Logger.php
108
src/Logger.php
@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This file is part of the Symfony package.
|
* This file is part of the Symfony package.
|
||||||
@ -11,6 +11,8 @@
|
|||||||
|
|
||||||
namespace TelegramApiServer;
|
namespace TelegramApiServer;
|
||||||
|
|
||||||
|
use Amp\ByteStream\Pipe;
|
||||||
|
use Amp\ByteStream\WritableStream;
|
||||||
use danog\MadelineProto;
|
use danog\MadelineProto;
|
||||||
use DateTimeInterface;
|
use DateTimeInterface;
|
||||||
use Psr\Log\AbstractLogger;
|
use Psr\Log\AbstractLogger;
|
||||||
@ -18,17 +20,18 @@ use Psr\Log\InvalidArgumentException;
|
|||||||
use Psr\Log\LogLevel;
|
use Psr\Log\LogLevel;
|
||||||
use TelegramApiServer\EventObservers\LogObserver;
|
use TelegramApiServer\EventObservers\LogObserver;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
use function get_class;
|
|
||||||
use function gettype;
|
|
||||||
use function is_object;
|
|
||||||
use const PHP_EOL;
|
use const PHP_EOL;
|
||||||
|
use function Amp\async;
|
||||||
|
use function Amp\ByteStream\getStdout;
|
||||||
|
use function Amp\ByteStream\pipe;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Minimalist PSR-3 logger designed to write in stderr or any other stream.
|
* Minimalist PSR-3 logger designed to write in stderr or any other stream.
|
||||||
*
|
*
|
||||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
*/
|
*/
|
||||||
class Logger extends AbstractLogger
|
final class Logger extends AbstractLogger
|
||||||
{
|
{
|
||||||
private static ?Logger $instanse = null;
|
private static ?Logger $instanse = null;
|
||||||
|
|
||||||
@ -54,13 +57,19 @@ class Logger extends AbstractLogger
|
|||||||
|
|
||||||
private static string $dateTimeFormat = 'Y-m-d H:i:s';
|
private static string $dateTimeFormat = 'Y-m-d H:i:s';
|
||||||
public int $minLevelIndex;
|
public int $minLevelIndex;
|
||||||
private array $formatter;
|
private \Closure $formatter;
|
||||||
|
|
||||||
protected function __construct(string $minLevel = LogLevel::WARNING, callable $formatter = null)
|
private WritableStream $stdout;
|
||||||
|
/**
|
||||||
|
* @var array<int, list{WritableStream, \Amp\Future}>
|
||||||
|
*/
|
||||||
|
private static array $closePromises = [];
|
||||||
|
|
||||||
|
protected function __construct(string $minLevel = LogLevel::WARNING, ?\Closure $formatter = null)
|
||||||
{
|
{
|
||||||
if (null === $minLevel) {
|
if (null === $minLevel) {
|
||||||
if (isset($_ENV['SHELL_VERBOSITY']) || isset($_SERVER['SHELL_VERBOSITY'])) {
|
if (isset($_ENV['SHELL_VERBOSITY']) || isset($_SERVER['SHELL_VERBOSITY'])) {
|
||||||
switch ((int)(isset($_ENV['SHELL_VERBOSITY']) ? $_ENV['SHELL_VERBOSITY'] :
|
switch ((int) (isset($_ENV['SHELL_VERBOSITY']) ? $_ENV['SHELL_VERBOSITY'] :
|
||||||
$_SERVER['SHELL_VERBOSITY'])) {
|
$_SERVER['SHELL_VERBOSITY'])) {
|
||||||
case -1:
|
case -1:
|
||||||
$minLevel = LogLevel::ERROR;
|
$minLevel = LogLevel::ERROR;
|
||||||
@ -79,23 +88,34 @@ class Logger extends AbstractLogger
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isset(self::$levels[$minLevel])) {
|
if (!isset(self::$levels[$minLevel])) {
|
||||||
throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $minLevel));
|
throw new InvalidArgumentException(\sprintf('The log level "%s" does not exist.', $minLevel));
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->minLevelIndex = self::$levels[$minLevel];
|
$this->minLevelIndex = \min(self::$levels[$minLevel], self::$levels[self::$madelineLevels[MadelineProto\Logger::VERBOSE]]);
|
||||||
$this->formatter = $formatter ?: [$this, 'format'];
|
$this->formatter = $formatter ?: $this->format(...);
|
||||||
|
$pipe = new Pipe(PHP_INT_MAX);
|
||||||
|
$this->stdout = $pipe->getSink();
|
||||||
|
$source = $pipe->getSource();
|
||||||
|
$promise = async(static function () use ($source, &$promise): void {
|
||||||
|
try {
|
||||||
|
pipe($source, getStdout());
|
||||||
|
} finally {
|
||||||
|
unset(self::$closePromises[\spl_object_id($promise)]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
self::$closePromises[\spl_object_id($promise)] = [$this->stdout, $promise];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getInstance(): Logger
|
public static function getInstance(): Logger
|
||||||
{
|
{
|
||||||
if (!static::$instanse) {
|
if (!self::$instanse) {
|
||||||
$settings = Config::getInstance()->get('telegram');
|
$settings = Config::getInstance()->get('telegram');
|
||||||
|
|
||||||
$loggerLevel = static::$madelineLevels[$settings['logger']['level']];
|
$loggerLevel = self::$madelineLevels[$settings['logger']['level']];
|
||||||
static::$instanse = new static($loggerLevel);
|
self::$instanse = new static($loggerLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
return static::$instanse;
|
return self::$instanse;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -104,7 +124,7 @@ class Logger extends AbstractLogger
|
|||||||
public function log($level, $message, array $context = []): void
|
public function log($level, $message, array $context = []): void
|
||||||
{
|
{
|
||||||
if (!isset(self::$levels[$level])) {
|
if (!isset(self::$levels[$level])) {
|
||||||
throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level));
|
throw new InvalidArgumentException(\sprintf('The log level "%s" does not exist.', $level));
|
||||||
}
|
}
|
||||||
|
|
||||||
LogObserver::notify($level, $message, $context);
|
LogObserver::notify($level, $message, $context);
|
||||||
@ -114,61 +134,77 @@ class Logger extends AbstractLogger
|
|||||||
}
|
}
|
||||||
|
|
||||||
$formatter = $this->formatter;
|
$formatter = $this->formatter;
|
||||||
/** @see Logger::format */
|
$data = $formatter($level, $message, $context);
|
||||||
echo $formatter($level, $message, $context);
|
;
|
||||||
|
try {
|
||||||
|
$this->stdout->write($data);
|
||||||
|
} catch (\Throwable) {
|
||||||
|
echo $data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal Internal function used to flush the log buffer on shutdown.
|
||||||
|
*/
|
||||||
|
public static function finalize(): void
|
||||||
|
{
|
||||||
|
foreach (self::$closePromises as [$stdout, $promise]) {
|
||||||
|
$stdout->close();
|
||||||
|
$promise->await();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function format(string $level, string $message, array $context): string
|
private function format(string $level, string $message, array $context): string
|
||||||
{
|
{
|
||||||
if (false !== strpos($message, '{')) {
|
if (false !== \strpos($message, '{')) {
|
||||||
$replacements = [];
|
$replacements = [];
|
||||||
foreach ($context as $key => $val) {
|
foreach ($context as $key => $val) {
|
||||||
if ($val instanceof Throwable) {
|
if ($val instanceof Throwable) {
|
||||||
$context[$key] = self::getExceptionAsArray($val);
|
$context[$key] = self::getExceptionAsArray($val);
|
||||||
}
|
}
|
||||||
if (null === $val || is_scalar($val) || (is_object($val) && method_exists($val, '__toString'))) {
|
if (null === $val || \is_scalar($val) || (\is_object($val) && \method_exists($val, '__toString'))) {
|
||||||
$replacements["{{$key}}"] = $val;
|
$replacements["{{$key}}"] = $val;
|
||||||
} else {
|
} else {
|
||||||
if ($val instanceof DateTimeInterface) {
|
if ($val instanceof DateTimeInterface) {
|
||||||
$replacements["{{$key}}"] = $val->format(static::$dateTimeFormat);
|
$replacements["{{$key}}"] = $val->format(self::$dateTimeFormat);
|
||||||
} else {
|
} else {
|
||||||
if (is_object($val)) {
|
if (\is_object($val)) {
|
||||||
$replacements["{{$key}}"] = '[object ' . get_class($val) . ']';
|
$replacements["{{$key}}"] = '[object ' . \get_class($val) . ']';
|
||||||
} else {
|
} else {
|
||||||
$replacements["{{$key}}"] = '[' . gettype($val) . ']';
|
$replacements["{{$key}}"] = '[' . \gettype($val) . ']';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$message = strtr($message, $replacements);
|
$message = \strtr($message, $replacements);
|
||||||
}
|
}
|
||||||
|
|
||||||
return sprintf(
|
return \sprintf(
|
||||||
'[%s] [%s] %s %s',
|
'[%s] [%s] %s %s',
|
||||||
date(static::$dateTimeFormat),
|
\date(self::$dateTimeFormat),
|
||||||
$level,
|
$level,
|
||||||
$message,
|
$message,
|
||||||
$context ?
|
$context ?
|
||||||
"\n" .
|
"\n" .
|
||||||
json_encode(
|
\json_encode(
|
||||||
$context,
|
$context,
|
||||||
JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE | JSON_PRETTY_PRINT | JSON_UNESCAPED_LINE_TERMINATORS | JSON_UNESCAPED_SLASHES
|
JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE | JSON_PRETTY_PRINT | JSON_UNESCAPED_LINE_TERMINATORS | JSON_UNESCAPED_SLASHES
|
||||||
)
|
)
|
||||||
: ''
|
: ''
|
||||||
) . PHP_EOL;
|
) . PHP_EOL;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getExceptionAsArray(Throwable $exception)
|
public static function getExceptionAsArray(Throwable $exception)
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'exception' => get_class($exception),
|
'exception' => \get_class($exception),
|
||||||
'message' => $exception->getMessage(),
|
'message' => $exception->getMessage(),
|
||||||
'file' => $exception->getFile(),
|
'file' => $exception->getFile(),
|
||||||
'line' => $exception->getLine(),
|
'line' => $exception->getLine(),
|
||||||
'code' => $exception->getCode(),
|
'code' => $exception->getCode(),
|
||||||
'backtrace' => array_slice($exception->getTrace(), 0, 3),
|
'backtrace' => \array_slice($exception->getTrace(), 0, 3),
|
||||||
'previous exception' => $exception->getPrevious(),
|
'previous exception' => $exception->getPrevious(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
<?php
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
|
||||||
namespace TelegramApiServer\MadelineProtoExtensions;
|
namespace TelegramApiServer\MadelineProtoExtensions;
|
||||||
|
|
||||||
|
|
||||||
use Amp\Http\Server\FormParser\StreamedField;
|
use Amp\Http\Server\FormParser\StreamedField;
|
||||||
use Amp\Http\Server\Request;
|
use Amp\Http\Server\Request;
|
||||||
use Amp\Http\Server\Response;
|
use Amp\Http\Server\Response;
|
||||||
@ -15,7 +13,7 @@ use TelegramApiServer\EventObservers\EventHandler;
|
|||||||
use TelegramApiServer\Exceptions\NoMediaException;
|
use TelegramApiServer\Exceptions\NoMediaException;
|
||||||
use function Amp\delay;
|
use function Amp\delay;
|
||||||
|
|
||||||
class ApiExtensions
|
final class ApiExtensions
|
||||||
{
|
{
|
||||||
|
|
||||||
private MadelineProto\Api $madelineProto;
|
private MadelineProto\Api $madelineProto;
|
||||||
@ -43,12 +41,9 @@ class ApiExtensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Проверяет есть ли подходящие медиа у сообщения
|
* Проверяет есть ли подходящие медиа у сообщения.
|
||||||
*
|
*
|
||||||
* @param array $message
|
|
||||||
* @param bool $allowWebPage
|
|
||||||
*
|
*
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
private static function hasMedia(array $message = [], bool $allowWebPage = false): bool
|
private static function hasMedia(array $message = [], bool $allowWebPage = false): bool
|
||||||
{
|
{
|
||||||
@ -90,13 +85,13 @@ class ApiExtensions
|
|||||||
$text = StrTools::mbSubstr($message, $entity['offset'], $entity['length']);
|
$text = StrTools::mbSubstr($message, $entity['offset'], $entity['length']);
|
||||||
|
|
||||||
$template = $html[$entity['_']];
|
$template = $html[$entity['_']];
|
||||||
if (in_array($entity['_'], ['messageEntityTextUrl', 'messageEntityMention', 'messageEntityUrl'])) {
|
if (\in_array($entity['_'], ['messageEntityTextUrl', 'messageEntityMention', 'messageEntityUrl'])) {
|
||||||
$textFormated = sprintf($template, strip_tags($entity['url'] ?? $text), $text);
|
$textFormated = \sprintf($template, \strip_tags($entity['url'] ?? $text), $text);
|
||||||
} else {
|
} else {
|
||||||
$textFormated = sprintf($template, $text);
|
$textFormated = \sprintf($template, $text);
|
||||||
}
|
}
|
||||||
|
|
||||||
$message = static::substringReplace($message, $textFormated, $entity['offset'], $entity['length']);
|
$message = self::substringReplace($message, $textFormated, $entity['offset'], $entity['length']);
|
||||||
|
|
||||||
//Увеличим оффсеты всех следующих entity
|
//Увеличим оффсеты всех следующих entity
|
||||||
foreach ($entities as $nextKey => &$nextEntity) {
|
foreach ($entities as $nextKey => &$nextEntity) {
|
||||||
@ -105,7 +100,7 @@ class ApiExtensions
|
|||||||
}
|
}
|
||||||
if ($nextEntity['offset'] < ($entity['offset'] + $entity['length'])) {
|
if ($nextEntity['offset'] < ($entity['offset'] + $entity['length'])) {
|
||||||
$nextEntity['offset'] += StrTools::mbStrlen(
|
$nextEntity['offset'] += StrTools::mbStrlen(
|
||||||
preg_replace('~(\>).*<\/.*$~', '$1', $textFormated)
|
\preg_replace('~(\>).*<\/.*$~', '$1', $textFormated)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$nextEntity['offset'] += StrTools::mbStrlen($textFormated) - StrTools::mbStrlen($text);
|
$nextEntity['offset'] += StrTools::mbStrlen($textFormated) - StrTools::mbStrlen($text);
|
||||||
@ -115,7 +110,7 @@ class ApiExtensions
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
unset($entity);
|
unset($entity);
|
||||||
$message = nl2br($message);
|
$message = \nl2br($message);
|
||||||
return $message;
|
return $message;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,7 +122,7 @@ class ApiExtensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Пересылает сообщения без ссылки на оригинал
|
* Пересылает сообщения без ссылки на оригинал.
|
||||||
*
|
*
|
||||||
* @param array $data
|
* @param array $data
|
||||||
* <pre>
|
* <pre>
|
||||||
@ -141,7 +136,7 @@ class ApiExtensions
|
|||||||
*/
|
*/
|
||||||
public function copyMessages(array $data)
|
public function copyMessages(array $data)
|
||||||
{
|
{
|
||||||
$data = array_merge(
|
$data = \array_merge(
|
||||||
[
|
[
|
||||||
'from_peer' => '',
|
'from_peer' => '',
|
||||||
'to_peer' => '',
|
'to_peer' => '',
|
||||||
@ -157,7 +152,7 @@ class ApiExtensions
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
$result = [];
|
$result = [];
|
||||||
if (!$response || !is_array($response) || !array_key_exists('messages', $response)) {
|
if (!$response || !\is_array($response) || !\array_key_exists('messages', $response)) {
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,14 +162,14 @@ class ApiExtensions
|
|||||||
'peer' => $data['to_peer'],
|
'peer' => $data['to_peer'],
|
||||||
'entities' => $message['entities'] ?? [],
|
'entities' => $message['entities'] ?? [],
|
||||||
];
|
];
|
||||||
if (static::hasMedia($message, false)) {
|
if (self::hasMedia($message, false)) {
|
||||||
$messageData['media'] = $message; //MadelineProto сама достанет все media из сообщения.
|
$messageData['media'] = $message; //MadelineProto сама достанет все media из сообщения.
|
||||||
$result[] = $this->madelineProto->messages->sendMedia(...$messageData);
|
$result[] = $this->madelineProto->messages->sendMedia(...$messageData);
|
||||||
} else {
|
} else {
|
||||||
$result[] = $this->madelineProto->messages->sendMessage(...$messageData);
|
$result[] = $this->madelineProto->messages->sendMessage(...$messageData);
|
||||||
}
|
}
|
||||||
if ($key > 0) {
|
if ($key > 0) {
|
||||||
delay(random_int(300, 2000) / 1000);
|
delay(\random_int(300, 2000) / 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,14 +177,13 @@ class ApiExtensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Загружает медиафайл из указанного сообщения в поток
|
* Загружает медиафайл из указанного сообщения в поток.
|
||||||
*
|
*
|
||||||
* @param array $data
|
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public function getMedia(array $data): Response
|
public function getMedia(array $data): Response
|
||||||
{
|
{
|
||||||
$data = array_merge(
|
$data = \array_merge(
|
||||||
[
|
[
|
||||||
'peer' => '',
|
'peer' => '',
|
||||||
'id' => [0],
|
'id' => [0],
|
||||||
@ -203,7 +197,7 @@ class ApiExtensions
|
|||||||
throw new NoMediaException('Empty message');
|
throw new NoMediaException('Empty message');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!static::hasMedia($message, true)) {
|
if (!self::hasMedia($message, true)) {
|
||||||
throw new NoMediaException('Message has no media');
|
throw new NoMediaException('Message has no media');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,17 +216,16 @@ class ApiExtensions
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return $this->downloadToResponse($info);
|
return $this->downloadToResponse($info);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Загружает превью медиафайла из указанного сообщения в поток
|
* Загружает превью медиафайла из указанного сообщения в поток.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public function getMediaPreview(array $data): Response
|
public function getMediaPreview(array $data): Response
|
||||||
{
|
{
|
||||||
$data = array_merge(
|
$data = \array_merge(
|
||||||
[
|
[
|
||||||
'peer' => '',
|
'peer' => '',
|
||||||
'id' => [0],
|
'id' => [0],
|
||||||
@ -246,11 +239,11 @@ class ApiExtensions
|
|||||||
throw new NoMediaException('Empty message');
|
throw new NoMediaException('Empty message');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!static::hasMedia($message, true)) {
|
if (!self::hasMedia($message, true)) {
|
||||||
throw new NoMediaException('Message has no media');
|
throw new NoMediaException('Message has no media');
|
||||||
}
|
}
|
||||||
|
|
||||||
$media = $message['media'][array_key_last($message['media'])];
|
$media = $message['media'][\array_key_last($message['media'])];
|
||||||
$thumb = null;
|
$thumb = null;
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case isset($media['sizes']):
|
case isset($media['sizes']):
|
||||||
@ -308,15 +301,15 @@ class ApiExtensions
|
|||||||
public function getMessages(array $data): array
|
public function getMessages(array $data): array
|
||||||
{
|
{
|
||||||
$peerInfo = $this->madelineProto->getInfo($data['peer']);
|
$peerInfo = $this->madelineProto->getInfo($data['peer']);
|
||||||
if (in_array($peerInfo['type'], ['channel', 'supergroup'])) {
|
if (\in_array($peerInfo['type'], ['channel', 'supergroup'])) {
|
||||||
$response = $this->madelineProto->channels->getMessages(
|
$response = $this->madelineProto->channels->getMessages(
|
||||||
[
|
[
|
||||||
'channel' => $data['peer'],
|
'channel' => $data['peer'],
|
||||||
'id' => (array)$data['id'],
|
'id' => (array) $data['id'],
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$response = $this->madelineProto->messages->getMessages(['id' => (array)$data['id']]);
|
$response = $this->madelineProto->messages->getMessages(['id' => (array) $data['id']]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $response;
|
return $response;
|
||||||
@ -328,7 +321,6 @@ class ApiExtensions
|
|||||||
* @param array $info
|
* @param array $info
|
||||||
* Any downloadable array: message, media etc...
|
* Any downloadable array: message, media etc...
|
||||||
*
|
*
|
||||||
* @return Response
|
|
||||||
*/
|
*/
|
||||||
public function downloadToResponse(array $info): Response
|
public function downloadToResponse(array $info): Response
|
||||||
{
|
{
|
||||||
@ -336,7 +328,7 @@ class ApiExtensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Адаптер для стандартного метода
|
* Адаптер для стандартного метода.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public function downloadToBrowser(array $info): Response
|
public function downloadToBrowser(array $info): Response
|
||||||
@ -361,7 +353,7 @@ class ApiExtensions
|
|||||||
$this->file->getMimeType(),
|
$this->file->getMimeType(),
|
||||||
$this->file->getFilename()
|
$this->file->getFilename()
|
||||||
);
|
);
|
||||||
$inputFile['id'] = unpack('P', $inputFile['id'])['1'];
|
$inputFile['id'] = \unpack('P', $inputFile['id'])['1'];
|
||||||
return [
|
return [
|
||||||
'media' => [
|
'media' => [
|
||||||
'_' => 'inputMediaUploadedDocument',
|
'_' => 'inputMediaUploadedDocument',
|
||||||
@ -384,7 +376,6 @@ class ApiExtensions
|
|||||||
Client::getWrapper($this->madelineProto)->serialize();
|
Client::getWrapper($this->madelineProto)->serialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function getUpdates(array $params): array
|
public function getUpdates(array $params): array
|
||||||
{
|
{
|
||||||
foreach ($params as $key => $value) {
|
foreach ($params as $key => $value) {
|
||||||
@ -410,12 +401,13 @@ class ApiExtensions
|
|||||||
Client::getWrapper($this->madelineProto)->serialize();
|
Client::getWrapper($this->madelineProto)->serialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function unsubscribeFromUpdates(?string $channel = null): array {
|
public function unsubscribeFromUpdates(?string $channel = null): array
|
||||||
|
{
|
||||||
$inputChannelId = null;
|
$inputChannelId = null;
|
||||||
if ($channel) {
|
if ($channel) {
|
||||||
$id = (string) $this->madelineProto->getId($channel);
|
$id = (string) $this->madelineProto->getId($channel);
|
||||||
|
|
||||||
$inputChannelId = (int)str_replace(['-100', '-'], '', $id);
|
$inputChannelId = (int) \str_replace(['-100', '-'], '', $id);
|
||||||
if (!$inputChannelId) {
|
if (!$inputChannelId) {
|
||||||
throw new InvalidArgumentException('Invalid id');
|
throw new InvalidArgumentException('Invalid id');
|
||||||
}
|
}
|
||||||
@ -438,11 +430,10 @@ class ApiExtensions
|
|||||||
$counter++;
|
$counter++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'disabled_update_loops' => $counter,
|
'disabled_update_loops' => $counter,
|
||||||
'current_update_loops' => count(Client::getWrapper($this->madelineProto)->getAPI()->feeders),
|
'current_update_loops' => \count(Client::getWrapper($this->madelineProto)->getAPI()->feeders),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace TelegramApiServer\MadelineProtoExtensions;
|
namespace TelegramApiServer\MadelineProtoExtensions;
|
||||||
|
|
||||||
@ -13,7 +13,7 @@ use function Amp\async;
|
|||||||
use function Amp\File\deleteFile;
|
use function Amp\File\deleteFile;
|
||||||
use function Amp\Future\awaitAll;
|
use function Amp\Future\awaitAll;
|
||||||
|
|
||||||
class SystemApiExtensions
|
final class SystemApiExtensions
|
||||||
{
|
{
|
||||||
private Client $client;
|
private Client $client;
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ class SystemApiExtensions
|
|||||||
public function addSession(string $session, array $settings = []): array
|
public function addSession(string $session, array $settings = []): array
|
||||||
{
|
{
|
||||||
if (!empty($settings['app_info']['api_id'])) {
|
if (!empty($settings['app_info']['api_id'])) {
|
||||||
$settings['app_info']['api_id'] = (int)$settings['app_info']['api_id'];
|
$settings['app_info']['api_id'] = (int) $settings['app_info']['api_id'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$instance = $this->client->addSession($session, $settings);
|
$instance = $this->client->addSession($session, $settings);
|
||||||
@ -75,7 +75,7 @@ class SystemApiExtensions
|
|||||||
foreach ($this->client->instances as $session => $instance) {
|
foreach ($this->client->instances as $session => $instance) {
|
||||||
$authorized = $instance->getAuthorization();
|
$authorized = $instance->getAuthorization();
|
||||||
switch ($authorized) {
|
switch ($authorized) {
|
||||||
case API::NOT_LOGGED_IN;
|
case API::NOT_LOGGED_IN:
|
||||||
$status = 'NOT_LOGGED_IN';
|
$status = 'NOT_LOGGED_IN';
|
||||||
break;
|
break;
|
||||||
case API::WAITING_CODE:
|
case API::WAITING_CODE:
|
||||||
@ -107,7 +107,7 @@ class SystemApiExtensions
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
'sessions' => $sessions,
|
'sessions' => $sessions,
|
||||||
'memory' => $this->bytesToHuman(memory_get_usage(true)),
|
'memory' => $this->bytesToHuman(\memory_get_usage(true)),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,10 +115,10 @@ class SystemApiExtensions
|
|||||||
{
|
{
|
||||||
$file = Files::getSessionFile($session);
|
$file = Files::getSessionFile($session);
|
||||||
|
|
||||||
if (is_file($file)) {
|
if (\is_file($file)) {
|
||||||
$futures = [];
|
$futures = [];
|
||||||
foreach (glob("$file*") as $file) {
|
foreach (\glob("$file*") as $file) {
|
||||||
$futures[] = async(fn() => deleteFile($file));
|
$futures[] = async(fn () => deleteFile($file));
|
||||||
}
|
}
|
||||||
awaitAll($futures);
|
awaitAll($futures);
|
||||||
} else {
|
} else {
|
||||||
@ -140,7 +140,7 @@ class SystemApiExtensions
|
|||||||
public function unlinkSessionSettings($session): string
|
public function unlinkSessionSettings($session): string
|
||||||
{
|
{
|
||||||
$settings = Files::getSessionFile($session, Files::SETTINGS_EXTENSION);
|
$settings = Files::getSessionFile($session, Files::SETTINGS_EXTENSION);
|
||||||
if (is_file($settings)) {
|
if (\is_file($settings)) {
|
||||||
deleteFile($settings);
|
deleteFile($settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,7 +149,7 @@ class SystemApiExtensions
|
|||||||
|
|
||||||
public function exit(): string
|
public function exit(): string
|
||||||
{
|
{
|
||||||
EventLoop::defer(static fn() => exit());
|
EventLoop::defer(static fn () => exit());
|
||||||
return 'ok';
|
return 'ok';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,6 +159,6 @@ class SystemApiExtensions
|
|||||||
for ($i = 0; $bytes > 1024; $i++) {
|
for ($i = 0; $bytes > 1024; $i++) {
|
||||||
$bytes /= 1024;
|
$bytes /= 1024;
|
||||||
}
|
}
|
||||||
return round($bytes, 2) . ' ' . $units[$i];
|
return \round($bytes, 2) . ' ' . $units[$i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,14 @@
|
|||||||
<?php
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace TelegramApiServer\Migrations;
|
namespace TelegramApiServer\Migrations;
|
||||||
|
|
||||||
class StartUpFixes
|
use danog\MadelineProto\Magic;
|
||||||
|
|
||||||
|
final class StartUpFixes
|
||||||
{
|
{
|
||||||
public static function fix(): void
|
public static function fix(): void
|
||||||
{
|
{
|
||||||
define('MADELINE_WORKER_TYPE', 'madeline-ipc');
|
\define('MADELINE_WORKER_TYPE', 'madeline-ipc');
|
||||||
|
Magic::$isIpcWorker = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
public static function removeBrokenIpc(string $session): void
|
|
||||||
{
|
|
||||||
info('Removing ipc sockets from sessions to fix startup' . PHP_EOL);
|
|
||||||
foreach (glob(ROOT_DIR . "/$session/*ipc") as $file) {
|
|
||||||
info("removing: $file");
|
|
||||||
unlink($file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace TelegramApiServer\Server;
|
namespace TelegramApiServer\Server;
|
||||||
|
|
||||||
@ -9,7 +9,7 @@ use Amp\Http\Server\Response;
|
|||||||
use Psr\Log\LoggerInterface as PsrLogger;
|
use Psr\Log\LoggerInterface as PsrLogger;
|
||||||
use Psr\Log\LogLevel;
|
use Psr\Log\LogLevel;
|
||||||
|
|
||||||
class AccessLoggerMiddleware implements Middleware
|
final class AccessLoggerMiddleware implements Middleware
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly PsrLogger $logger,
|
private readonly PsrLogger $logger,
|
||||||
@ -20,7 +20,7 @@ class AccessLoggerMiddleware implements Middleware
|
|||||||
{
|
{
|
||||||
|
|
||||||
$method = $request->getMethod();
|
$method = $request->getMethod();
|
||||||
$uri = (string)$request->getUri();
|
$uri = (string) $request->getUri();
|
||||||
$protocolVersion = $request->getProtocolVersion();
|
$protocolVersion = $request->getProtocolVersion();
|
||||||
$remote = Server::getClientIp($request);
|
$remote = Server::getClientIp($request);
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ class AccessLoggerMiddleware implements Middleware
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
$level = $status < 400 ? LogLevel::INFO : LogLevel::NOTICE;
|
$level = $status < 400 ? LogLevel::DEBUG : LogLevel::INFO;
|
||||||
|
|
||||||
$this->logger->log(
|
$this->logger->log(
|
||||||
$level,
|
$level,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace TelegramApiServer\Server;
|
namespace TelegramApiServer\Server;
|
||||||
|
|
||||||
@ -9,7 +9,7 @@ use Amp\Http\Server\RequestHandler;
|
|||||||
use Amp\Http\Server\Response;
|
use Amp\Http\Server\Response;
|
||||||
use TelegramApiServer\Config;
|
use TelegramApiServer\Config;
|
||||||
|
|
||||||
class Authorization implements Middleware
|
final class Authorization implements Middleware
|
||||||
{
|
{
|
||||||
private array $ipWhitelist;
|
private array $ipWhitelist;
|
||||||
private int $selfIp;
|
private int $selfIp;
|
||||||
@ -20,8 +20,8 @@ class Authorization implements Middleware
|
|||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->selfIp = ip2long(getHostByName(php_uname('n')));
|
$this->selfIp = \ip2long(\getHostByName(\php_uname('n')));
|
||||||
$this->ipWhitelist = (array)Config::getInstance()->get('api.ip_whitelist', []);
|
$this->ipWhitelist = (array) Config::getInstance()->get('api.ip_whitelist', []);
|
||||||
$this->passwords = Config::getInstance()->get('api.passwords', []);
|
$this->passwords = Config::getInstance()->get('api.passwords', []);
|
||||||
if (!$this->ipWhitelist && !$this->passwords) {
|
if (!$this->ipWhitelist && !$this->passwords) {
|
||||||
error('API is unprotected! Please specify IP_WHITELIST or PASSWORD in .env.docker');
|
error('API is unprotected! Please specify IP_WHITELIST or PASSWORD in .env.docker');
|
||||||
@ -37,11 +37,11 @@ class Authorization implements Middleware
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($this->passwords) {
|
if ($this->passwords) {
|
||||||
$header = (string)$request->getHeader('Authorization');
|
$header = (string) $request->getHeader('Authorization');
|
||||||
if ($header) {
|
if ($header) {
|
||||||
sscanf($header, "Basic %s", $encodedPassword);
|
\sscanf($header, "Basic %s", $encodedPassword);
|
||||||
[$username, $password] = explode(':', base64_decode($encodedPassword), 2);
|
[$username, $password] = \explode(':', \base64_decode($encodedPassword), 2);
|
||||||
if (array_key_exists($username, $this->passwords) && $this->passwords[$username] === $password) {
|
if (\array_key_exists($username, $this->passwords) && $this->passwords[$username] === $password) {
|
||||||
return $requestHandler->handleRequest($request);
|
return $requestHandler->handleRequest($request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -59,25 +59,25 @@ class Authorization implements Middleware
|
|||||||
private function isIpAllowed(string $host): bool
|
private function isIpAllowed(string $host): bool
|
||||||
{
|
{
|
||||||
|
|
||||||
|
if ($this->ipWhitelist && !\in_array($host, $this->ipWhitelist, true)) {
|
||||||
if ($this->ipWhitelist && !in_array($host, $this->ipWhitelist, true)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function isLocal(string $host): bool {
|
private function isLocal(string $host): bool
|
||||||
|
{
|
||||||
if ($host === '127.0.0.1' || $host === 'localhost') {
|
if ($host === '127.0.0.1' || $host === 'localhost') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
global $options;
|
global $options;
|
||||||
if ($options['docker']) {
|
if ($options['docker']) {
|
||||||
$isSameNetwork = abs(ip2long($host) - $this->selfIp) < 256;
|
$isSameNetwork = \abs(\ip2long($host) - $this->selfIp) < 256;
|
||||||
if ($isSameNetwork) {
|
if ($isSameNetwork) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,22 @@
|
|||||||
<?php
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace TelegramApiServer\Server;
|
namespace TelegramApiServer\Server;
|
||||||
|
|
||||||
use Amp\Http\Server\Response;
|
use Amp\Http\Server\Response;
|
||||||
use TelegramApiServer\Controllers\AbstractApiController;
|
use TelegramApiServer\Controllers\AbstractApiController;
|
||||||
|
|
||||||
class ErrorResponses
|
final class ErrorResponses
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @param int $status
|
|
||||||
* @param string|array $message
|
* @param string|array $message
|
||||||
*
|
*
|
||||||
* @return Response
|
|
||||||
*/
|
*/
|
||||||
public static function get(int $status, $message): Response
|
public static function get(int $status, $message): Response
|
||||||
{
|
{
|
||||||
return new Response(
|
return new Response(
|
||||||
$status,
|
$status,
|
||||||
AbstractApiController::JSON_HEADER,
|
AbstractApiController::JSON_HEADER,
|
||||||
json_encode(
|
\json_encode(
|
||||||
[
|
[
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'errors' => [
|
'errors' => [
|
||||||
@ -33,4 +31,4 @@ class ErrorResponses
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
<?php
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace TelegramApiServer\Server;
|
namespace TelegramApiServer\Server;
|
||||||
|
|
||||||
|
use Amp\Http\HttpStatus;
|
||||||
use Amp\Http\Server\ErrorHandler;
|
use Amp\Http\Server\ErrorHandler;
|
||||||
use Amp\Http\Server\Request;
|
use Amp\Http\Server\Request;
|
||||||
use Amp\Http\Server\RequestHandler\ClosureRequestHandler;
|
use Amp\Http\Server\RequestHandler\ClosureRequestHandler;
|
||||||
use Amp\Http\Server\SocketHttpServer;
|
use Amp\Http\Server\SocketHttpServer;
|
||||||
use Amp\Http\HttpStatus;
|
|
||||||
use TelegramApiServer\Controllers\ApiController;
|
use TelegramApiServer\Controllers\ApiController;
|
||||||
use TelegramApiServer\Controllers\EventsController;
|
use TelegramApiServer\Controllers\EventsController;
|
||||||
use TelegramApiServer\Controllers\LogController;
|
use TelegramApiServer\Controllers\LogController;
|
||||||
@ -16,7 +16,7 @@ use TelegramApiServer\MadelineProtoExtensions\ApiExtensions;
|
|||||||
use TelegramApiServer\MadelineProtoExtensions\SystemApiExtensions;
|
use TelegramApiServer\MadelineProtoExtensions\SystemApiExtensions;
|
||||||
use function Amp\Http\Server\Middleware\stackMiddleware;
|
use function Amp\Http\Server\Middleware\stackMiddleware;
|
||||||
|
|
||||||
class Router
|
final class Router
|
||||||
{
|
{
|
||||||
private \Amp\Http\Server\Router $router;
|
private \Amp\Http\Server\Router $router;
|
||||||
private SocketHttpServer $server;
|
private SocketHttpServer $server;
|
||||||
@ -70,5 +70,4 @@ class Router
|
|||||||
$this->router->addRoute('GET', '/log/{level:.*?[^/]}[/]', $logHandler);
|
$this->router->addRoute('GET', '/log/{level:.*?[^/]}[/]', $logHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace TelegramApiServer\Server;
|
namespace TelegramApiServer\Server;
|
||||||
|
|
||||||
@ -14,17 +14,14 @@ use Revolt\EventLoop;
|
|||||||
use TelegramApiServer\Client;
|
use TelegramApiServer\Client;
|
||||||
use TelegramApiServer\Config;
|
use TelegramApiServer\Config;
|
||||||
use TelegramApiServer\Logger;
|
use TelegramApiServer\Logger;
|
||||||
use function sprintf;
|
|
||||||
use const SIGINT;
|
use const SIGINT;
|
||||||
use const SIGTERM;
|
use const SIGTERM;
|
||||||
|
|
||||||
class Server
|
final class Server
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Server constructor.
|
* Server constructor.
|
||||||
*
|
*
|
||||||
* @param array $options
|
|
||||||
* @param array|null $sessionFiles
|
|
||||||
*/
|
*/
|
||||||
public function __construct(array $options, ?array $sessionFiles)
|
public function __construct(array $options, ?array $sessionFiles)
|
||||||
{
|
{
|
||||||
@ -51,7 +48,6 @@ class Server
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop the server gracefully when SIGINT is received.
|
* Stop the server gracefully when SIGINT is received.
|
||||||
* This is technically optional, but it is best to call Server::stop().
|
* This is technically optional, but it is best to call Server::stop().
|
||||||
@ -60,29 +56,28 @@ class Server
|
|||||||
*/
|
*/
|
||||||
private static function registerShutdown(SocketHttpServer $server)
|
private static function registerShutdown(SocketHttpServer $server)
|
||||||
{
|
{
|
||||||
if (defined('SIGINT')) {
|
if (\defined('SIGINT')) {
|
||||||
// Await SIGINT or SIGTERM to be received.
|
// Await SIGINT or SIGTERM to be received.
|
||||||
$signal = Amp\trapSignal([SIGINT, SIGTERM]);
|
$signal = Amp\trapSignal([SIGINT, SIGTERM]);
|
||||||
info(sprintf("Received signal %d, stopping HTTP server", $signal));
|
info(\sprintf("Received signal %d, stopping HTTP server", $signal));
|
||||||
$server->stop();
|
$server->stop();
|
||||||
} else {
|
} else {
|
||||||
EventLoop::run();
|
EventLoop::run();
|
||||||
info("Stopping http server");
|
info("Stopping http server");
|
||||||
$server->stop();
|
$server->stop();
|
||||||
}
|
}
|
||||||
|
Logger::finalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Установить конфигурацию для http-сервера
|
* Установить конфигурацию для http-сервера.
|
||||||
*
|
*
|
||||||
* @param array $config
|
|
||||||
* @return array
|
|
||||||
*/
|
*/
|
||||||
private function getConfig(array $config = []): array
|
private function getConfig(array $config = []): array
|
||||||
{
|
{
|
||||||
$config = array_filter($config);
|
$config = \array_filter($config);
|
||||||
|
|
||||||
$config = array_merge(
|
$config = \array_merge(
|
||||||
Config::getInstance()->get('server', []),
|
Config::getInstance()->get('server', []),
|
||||||
$config
|
$config
|
||||||
);
|
);
|
||||||
@ -96,18 +91,18 @@ class Server
|
|||||||
if ($realIpHeader) {
|
if ($realIpHeader) {
|
||||||
$remote = $request->getHeader($realIpHeader);
|
$remote = $request->getHeader($realIpHeader);
|
||||||
if (!$remote) {
|
if (!$remote) {
|
||||||
GOTO DIRECT;
|
goto DIRECT;
|
||||||
}
|
}
|
||||||
$tmp = explode(',', $remote);
|
$tmp = \explode(',', $remote);
|
||||||
$remote = trim(end($tmp));
|
$remote = \trim(\end($tmp));
|
||||||
} else {
|
} else {
|
||||||
DIRECT:
|
DIRECT:
|
||||||
$remote = $request->getClient()->getRemoteAddress()->toString();
|
$remote = $request->getClient()->getRemoteAddress()->toString();
|
||||||
$hostArray = explode(':', $remote);
|
$hostArray = \explode(':', $remote);
|
||||||
if (count($hostArray) >= 2) {
|
if (\count($hostArray) >= 2) {
|
||||||
$port = (int)array_pop($hostArray);
|
$port = (int) \array_pop($hostArray);
|
||||||
if ($port > 0 && $port <= 65353) {
|
if ($port > 0 && $port <= 65353) {
|
||||||
$remote = implode(':', $hostArray);
|
$remote = \implode(':', $hostArray);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,4 +111,4 @@ class Server
|
|||||||
return $remote;
|
return $remote;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user