This commit is contained in:
Daniil Gentili 2024-06-30 16:07:42 +02:00
parent 2871122256
commit 0580754d62
26 changed files with 2583 additions and 266 deletions

View File

@ -37,6 +37,9 @@
"danog/madelineproto": "dev-v8_fix_cleanup",
"amphp/dns": "2.x-dev"
},
"require-dev": {
"amphp/php-cs-fixer-config": "^2.0.1"
},
"suggest": {
"ext-pcntl": "Install pcintl for propper signal handling and healthcheck (enabled in .env)"
},
@ -57,5 +60,8 @@
"allow-plugins": {
"symfony/thanks": false
}
},
"scripts": {
"cs-fix": "PHP_CS_FIXER_IGNORE_ENV=1 php -d pcre.jit=0 vendor/bin/php-cs-fixer fix -v"
}
}

2385
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -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
*/
@ -13,7 +13,6 @@ use function Amp\Websocket\Client\connect;
require 'vendor/autoload.php';
$shortopts = 'u::';
$longopts = [
'url::',

View File

@ -1,4 +1,4 @@
<?php
<?php declare(strict_types=1);
namespace TelegramApiServer;
@ -12,7 +12,6 @@ use InvalidArgumentException;
use Psr\Log\LogLevel;
use ReflectionProperty;
use RuntimeException;
use TelegramApiServer\EventObservers\EventHandler;
use TelegramApiServer\EventObservers\EventObserver;
final class Client
@ -41,7 +40,7 @@ final class Client
$this->startNotLoggedInSessions();
$sessionsCount = count($sessionFiles);
$sessionsCount = \count($sessionFiles);
warning(
"\nTelegramApiServer ready."
. "\nNumber of sessions: {$sessionsCount}."
@ -63,8 +62,8 @@ final class Client
if ($settings) {
Files::saveSessionSettings($session, $settings);
}
$settings = array_replace_recursive(
(array)Config::getInstance()->get('telegram'),
$settings = \array_replace_recursive(
(array) Config::getInstance()->get('telegram'),
Files::getSessionSettings($session),
);
@ -92,14 +91,9 @@ final class Client
$instance->unsetEventHandler();
}
unset($instance);
gc_collect_cycles();
\gc_collect_cycles();
}
/**
* @param string|null $session
*
* @return API
*/
public function getSession(?string $session = null): API
{
if (!$this->instances) {
@ -109,8 +103,8 @@ final class Client
}
if (!$session) {
if (count($this->instances) === 1) {
$session = (string)array_key_first($this->instances);
if (\count($this->instances) === 1) {
$session = (string) \array_key_first($this->instances);
} else {
throw new InvalidArgumentException(
'Multiple sessions detected. Specify which session to use. See README for examples.'
@ -165,9 +159,10 @@ final class Client
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) {
if (is_array($value) && $key !== 'proxies') {
if (\is_array($value) && $key !== 'proxies') {
if ($key === 'db' && isset($value['type'])) {
$type = match ($value['type']) {
'memory' => new Settings\Database\Memory(),
@ -185,23 +180,22 @@ final class Client
}
unset($value[$value['type']], $value['type'],);
if (count($value) === 0) {
if (\count($value) === 0) {
continue;
}
}
$method = 'get' . ucfirst(str_replace('_', '', ucwords($key, '_')));
$method = 'get' . \ucfirst(\str_replace('_', '', \ucwords($key, '_')));
self::getSettingsFromArray($session, $value, $settingsObject->$method());
} else {
if ($key === 'serializer' && is_string($value)) {
if ($key === 'serializer' && \is_string($value)) {
$value = SerializerType::from($value);
}
$method = 'set' . ucfirst(str_replace('_', '', ucwords($key, '_')));
$method = 'set' . \ucfirst(\str_replace('_', '', \ucwords($key, '_')));
$settingsObject->$method($value);
}
}
return $settingsObject;
}
}

View File

@ -1,9 +1,7 @@
<?php
<?php declare(strict_types=1);
namespace TelegramApiServer;
final class Config
{
private static ?Config $instance = null;
@ -20,7 +18,7 @@ final class Config
/**
* 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()
{
@ -39,12 +37,12 @@ final class Config
private function findByKey($key)
{
$key = (string)$key;
$path = explode('.', $key);
$key = (string) $key;
$path = \explode('.', $key);
$value = &$this->config;
foreach ($path as $pathKey) {
if (!is_array($value) || !array_key_exists($pathKey, $value)) {
if (!\is_array($value) || !\array_key_exists($pathKey, $value)) {
return null;
}
$value = &$value[$pathKey];

View File

@ -1,4 +1,4 @@
<?php
<?php declare(strict_types=1);
namespace TelegramApiServer\Controllers;
@ -18,8 +18,6 @@ use TelegramApiServer\MadelineProtoExtensions\ApiExtensions;
use TelegramApiServer\MadelineProtoExtensions\SystemApiExtensions;
use Throwable;
use UnexpectedValueException;
use function Amp\delay;
use function mb_strpos;
abstract class AbstractApiController
{
@ -29,7 +27,6 @@ abstract class AbstractApiController
protected ?StreamedField $file = null;
protected string $extensionClass;
public array $page = [
'headers' => self::JSON_HEADER,
'success' => false,
@ -83,19 +80,19 @@ abstract class AbstractApiController
}
/**
* Получаем параметры из GET и POST
* Получаем параметры из GET и POST.
*
*/
private function resolveRequest(): void
{
$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) {
case $contentType === 'application/x-www-form-urlencoded':
case mb_strpos($contentType, 'multipart/form-data') !== false:
case \mb_strpos($contentType, 'multipart/form-data') !== false:
$form = (new StreamingFormParser())->parseForm($this->request);
$post = [];
@ -107,27 +104,27 @@ abstract class AbstractApiController
//We need to break loop without getting file
//All other post field will be omitted, hope we dont need them :)
break;
} else {
$post[$field->getName()] = $field->buffer();
}
$post[$field->getName()] = $field->buffer();
}
break;
case $contentType === 'application/json':
$body = $this->request->getBody()->buffer();
$post = json_decode($body, 1);
$post = \json_decode($body, 1);
break;
default:
$body = $this->request->getBody()->buffer();
parse_str($body, $post);
\parse_str($body, $post);
}
$this->parameters = array_merge((array)$post, $get);
$this->parameters = array_values($this->parameters);
$this->parameters = \array_merge((array) $post, $get);
$this->parameters = \array_values($this->parameters);
}
/**
* Получает посты для формирования ответа
* Получает посты для формирования ответа.
*
*/
private function generateResponse(): void
@ -159,16 +156,16 @@ abstract class AbstractApiController
protected function callApiCommon(API $madelineProto)
{
$pathCount = count($this->api);
if ($pathCount === 1 && method_exists($this->extensionClass, $this->api[0])) {
$pathCount = \count($this->api);
if ($pathCount === 1 && \method_exists($this->extensionClass, $this->api[0])) {
/** @var ApiExtensions|SystemApiExtensions $madelineProtoExtensions */
$madelineProtoExtensions = new $this->extensionClass($madelineProto, $this->request, $this->file);
$result = $madelineProtoExtensions->{$this->api[0]}(...$this->parameters);
} else {
if ($this->api[0] === 'API') {
$madelineProto = Client::getWrapper($madelineProto)->getAPI();
array_shift($this->api);
$pathCount = count($this->api);
\array_shift($this->api);
$pathCount = \count($this->api);
}
//Проверяем нет ли в MadilineProto такого метода.
switch ($pathCount) {
@ -190,9 +187,7 @@ abstract class AbstractApiController
}
/**
* @param Throwable $e
*
* @return AbstractApiController
* @throws Throwable
*/
private function setError(Throwable $e): self
@ -210,9 +205,8 @@ abstract class AbstractApiController
}
/**
* Кодирует ответ в нужный формат: json
* Кодирует ответ в нужный формат: json.
*
* @return Response|string
* @throws JsonException
*/
private function getResponse(): string|Response
@ -221,7 +215,7 @@ abstract class AbstractApiController
return $this->page['response'];
}
if (!is_array($this->page['response']) && !is_scalar($this->page['response'])) {
if (!\is_array($this->page['response']) && !\is_scalar($this->page['response'])) {
$this->page['response'] = null;
}
@ -234,7 +228,7 @@ abstract class AbstractApiController
$data['success'] = true;
}
$result = json_encode(
$result = \json_encode(
$data,
JSON_THROW_ON_ERROR |
JSON_INVALID_UTF8_SUBSTITUTE |
@ -248,11 +242,9 @@ abstract class AbstractApiController
}
/**
* Устанавливает http код ответа (200, 400, 404 и тд.)
* Устанавливает http код ответа (200, 400, 404 и тд.).
*
* @param int $code
*
* @return AbstractApiController
*/
private function setPageCode(int $code): self
{

View File

@ -1,19 +1,10 @@
<?php
<?php declare(strict_types=1);
namespace TelegramApiServer\Controllers;
use Amp\Sync\LocalKeyedMutex;
use Amp\Sync\LocalMutex;
use Amp\Sync\StaticKeyMutex;
use Amp\Sync\SyncException;
use Exception;
use Revolt\EventLoop;
use TelegramApiServer\Client;
use TelegramApiServer\Config;
use TelegramApiServer\Logger;
use function Amp\async;
use function Amp\delay;
use function Amp\Future\awaitAll;
final class ApiController extends AbstractApiController
{
@ -21,15 +12,14 @@ final class ApiController extends AbstractApiController
private ?string $session = '';
/**
* Получаем параметры из uri
* Получаем параметры из uri.
*
* @param array $path
*
*/
protected function resolvePath(array $path): void
{
$this->session = $path['session'] ?? null;
$this->api = explode('.', $path['method'] ?? '');
$this->api = \explode('.', $path['method'] ?? '');
}
/**

View File

@ -1,11 +1,11 @@
<?php
<?php declare(strict_types=1);
namespace TelegramApiServer\Controllers;
use Amp\Http\HttpStatus;
use Amp\Http\Server\Request;
use Amp\Http\Server\Response;
use Amp\Http\Server\Router;
use Amp\Http\HttpStatus;
use Amp\Http\Server\SocketHttpServer;
use Amp\Websocket\Server\Rfc6455Acceptor;
use Amp\Websocket\Server\Websocket as WebsocketServer;
@ -82,7 +82,7 @@ final class EventsController implements WebsocketClientHandler, WebsocketAccepto
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) {
EventLoop::cancel($pingLoop);
@ -104,7 +104,7 @@ final class EventsController implements WebsocketClientHandler, WebsocketAccepto
];
$this->gateway->multicastText(
json_encode(
\json_encode(
$update,
JSON_THROW_ON_ERROR |
JSON_INVALID_UTF8_SUBSTITUTE |

View File

@ -1,4 +1,4 @@
<?php
<?php declare(strict_types=1);
namespace TelegramApiServer\Controllers;
@ -68,7 +68,7 @@ final class LogController implements WebsocketClientHandler, WebsocketAcceptor
{
$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) {
EventLoop::cancel($pingLoop);
@ -90,7 +90,7 @@ final class LogController implements WebsocketClientHandler, WebsocketAcceptor
];
$this->gateway->multicastText(
json_encode(
\json_encode(
$update,
JSON_THROW_ON_ERROR |
JSON_INVALID_UTF8_SUBSTITUTE |

View File

@ -1,4 +1,4 @@
<?php
<?php declare(strict_types=1);
namespace TelegramApiServer\Controllers;
@ -8,18 +8,16 @@ use TelegramApiServer\Client;
final class SystemController extends AbstractApiController
{
/**
* Получаем параметры из uri
* Получаем параметры из uri.
*
* @param array $path
*
*/
protected function resolvePath(array $path): void
{
$this->api = explode('.', $path['method'] ?? '');
$this->api = \explode('.', $path['method'] ?? '');
}
/**
* @return mixed
* @throws Exception
*/
protected function callApi()

View File

@ -1,8 +1,7 @@
<?php
<?php declare(strict_types=1);
namespace TelegramApiServer\EventObservers;
use danog\MadelineProto\APIWrapper;
use ReflectionProperty;
use TelegramApiServer\Client;
@ -43,7 +42,7 @@ final class EventObserver
{
$sessions = [];
if ($requestedSession === null) {
$sessions = array_keys(Client::getInstance()->instances);
$sessions = \array_keys(Client::getInstance()->instances);
} else {
$sessions[] = $requestedSession;
}
@ -74,7 +73,7 @@ final class EventObserver
{
$sessions = [];
if ($requestedSession === null) {
$sessions = array_keys(Client::getInstance()->instances);
$sessions = \array_keys(Client::getInstance()->instances);
} else {
$sessions[] = $requestedSession;
}

View File

@ -1,4 +1,4 @@
<?php
<?php declare(strict_types=1);
namespace TelegramApiServer\EventObservers;
@ -18,22 +18,22 @@ final class LogObserver
/**
* @param mixed|array|string $message
* @param int $level
*/
public static function log($message, int $level)
{
if (is_scalar($message)) {
Logger::getInstance()->log(Logger::$madelineLevels[$level], (string)$message);
if (\is_scalar($message)) {
Logger::getInstance()->log(Logger::$madelineLevels[$level], (string) $message);
} else {
if ($message instanceof Throwable) {
$message = Logger::getExceptionAsArray($message);
}
if (is_array($message)) {
if (\is_array($message)) {
Logger::getInstance()->log(Logger::$madelineLevels[$level], '', $message);
} else {
Logger::getInstance()->log(
Logger::$madelineLevels[$level],
json_encode($message,
\json_encode(
$message,
JSON_UNESCAPED_UNICODE |
JSON_PRETTY_PRINT |
JSON_INVALID_UTF8_SUBSTITUTE |

View File

@ -1,8 +1,7 @@
<?php
<?php declare(strict_types=1);
namespace TelegramApiServer\EventObservers;
trait ObserverTrait
{
/** @var callable[] */
@ -18,7 +17,7 @@ trait ObserverTrait
{
notice("Removing listener: {$clientId}");
unset(self::$subscribers[$clientId]);
$listenersCount = count(self::$subscribers);
$listenersCount = \count(self::$subscribers);
notice("Event listeners left: {$listenersCount}");
if ($listenersCount === 0) {
self::$subscribers = [];

View File

@ -1,4 +1,4 @@
<?php
<?php declare(strict_types=1);
namespace TelegramApiServer\Exceptions;

View File

@ -1,4 +1,4 @@
<?php
<?php declare(strict_types=1);
namespace TelegramApiServer\Exceptions;

View File

@ -1,4 +1,4 @@
<?php
<?php declare(strict_types=1);
namespace TelegramApiServer;
@ -13,11 +13,11 @@ final class Files
public static function checkOrCreateSessionFolder(string $session): void
{
$directory = dirname($session);
if ($directory && $directory !== '.' && !is_dir($directory)) {
$parentDirectoryPermissions = fileperms(ROOT_DIR);
if (!mkdir($directory, $parentDirectoryPermissions, true) && !is_dir($directory)) {
throw new RuntimeException(sprintf('Directory "%s" was not created', $directory));
$directory = \dirname($session);
if ($directory && $directory !== '.' && !\is_dir($directory)) {
$parentDirectoryPermissions = \fileperms(ROOT_DIR);
if (!\mkdir($directory, $parentDirectoryPermissions, true) && !\is_dir($directory)) {
throw new RuntimeException(\sprintf('Directory "%s" was not created', $directory));
}
}
}
@ -28,7 +28,7 @@ final class Files
return null;
}
preg_match(
\preg_match(
'~' . self::SESSION_FOLDER . "/(?'sessionName'.*?)" . self::SESSION_EXTENSION . '~',
$sessionFile,
$matches
@ -37,21 +37,14 @@ final class Files
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
{
if (!$session) {
return null;
}
$session = trim(trim($session), '/');
$session = \trim(\trim($session), '/');
$session = self::SESSION_FOLDER . '/' . $session . $extension;
$session = str_replace('//', '/', $session);
$session = \str_replace('//', '/', $session);
return $session;
}
@ -59,9 +52,9 @@ final class Files
{
$settingsFile = self::getSessionFile($session, self::SETTINGS_EXTENSION);
$settings = [];
if (file_exists($settingsFile)) {
$settings = json_decode(
file_get_contents($settingsFile),
if (\file_exists($settingsFile)) {
$settings = \json_decode(
\file_get_contents($settingsFile),
true,
10,
JSON_THROW_ON_ERROR
@ -74,9 +67,9 @@ final class Files
public static function saveSessionSettings(string $session, array $settings = []): void
{
$settingsFile = self::getSessionFile($session, self::SETTINGS_EXTENSION);
file_put_contents(
\file_put_contents(
$settingsFile,
json_encode(
\json_encode(
$settings,
JSON_THROW_ON_ERROR |
JSON_INVALID_UTF8_SUBSTITUTE |
@ -89,9 +82,9 @@ final class Files
public static function globRecursive($pattern, $flags = 0): array
{
$files = glob($pattern, $flags) ?: [];
foreach (glob(dirname($pattern) . '/*', GLOB_ONLYDIR | GLOB_NOSORT) as $dir) {
$files = [...$files, ...self::globRecursive($dir . '/' . basename($pattern), $flags)];
$files = \glob($pattern, $flags) ?: [];
foreach (\glob(\dirname($pattern) . '/*', GLOB_ONLYDIR | GLOB_NOSORT) as $dir) {
$files = [...$files, ...self::globRecursive($dir . '/' . \basename($pattern), $flags)];
}
return $files;
}

View File

@ -1,4 +1,4 @@
<?php
<?php declare(strict_types=1);
/*
* This file is part of the Symfony package.
@ -21,13 +21,10 @@ use Psr\Log\LogLevel;
use TelegramApiServer\EventObservers\LogObserver;
use Throwable;
use const PHP_EOL;
use function Amp\async;
use function Amp\ByteStream\getStdout;
use function Amp\ByteStream\pipe;
use function get_class;
use function gettype;
use function is_object;
use const PHP_EOL;
/**
* Minimalist PSR-3 logger designed to write in stderr or any other stream.
@ -68,11 +65,11 @@ final class Logger extends AbstractLogger
*/
private static array $closePromises = [];
protected function __construct(string $minLevel = LogLevel::WARNING, \Closure $formatter = null)
protected function __construct(string $minLevel = LogLevel::WARNING, ?\Closure $formatter = null)
{
if (null === $minLevel) {
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'])) {
case -1:
$minLevel = LogLevel::ERROR;
@ -91,10 +88,10 @@ final class Logger extends AbstractLogger
}
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 = min(self::$levels[$minLevel], self::$levels[self::$madelineLevels[MadelineProto\Logger::VERBOSE]]);
$this->minLevelIndex = \min(self::$levels[$minLevel], self::$levels[self::$madelineLevels[MadelineProto\Logger::VERBOSE]]);
$this->formatter = $formatter ?: $this->format(...);
$pipe = new Pipe(PHP_INT_MAX);
$this->stdout = $pipe->getSink();
@ -103,10 +100,10 @@ final class Logger extends AbstractLogger
try {
pipe($source, getStdout());
} finally {
unset(self::$closePromises[spl_object_id($promise)]);
unset(self::$closePromises[\spl_object_id($promise)]);
}
});
self::$closePromises[spl_object_id($promise)] = [$this->stdout, $promise];
self::$closePromises[\spl_object_id($promise)] = [$this->stdout, $promise];
}
public static function getInstance(): Logger
@ -127,7 +124,7 @@ final class Logger extends AbstractLogger
public function log($level, $message, array $context = []): void
{
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);
@ -137,7 +134,8 @@ final class Logger extends AbstractLogger
}
$formatter = $this->formatter;
$data = $formatter($level, $message, $context);;
$data = $formatter($level, $message, $context);
;
try {
$this->stdout->write($data);
} catch (\Throwable) {
@ -158,54 +156,54 @@ final class Logger extends AbstractLogger
private function format(string $level, string $message, array $context): string
{
if (false !== strpos($message, '{')) {
if (false !== \strpos($message, '{')) {
$replacements = [];
foreach ($context as $key => $val) {
if ($val instanceof Throwable) {
$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;
} else {
if ($val instanceof DateTimeInterface) {
$replacements["{{$key}}"] = $val->format(self::$dateTimeFormat);
} else {
if (is_object($val)) {
$replacements["{{$key}}"] = '[object ' . get_class($val) . ']';
if (\is_object($val)) {
$replacements["{{$key}}"] = '[object ' . \get_class($val) . ']';
} else {
$replacements["{{$key}}"] = '[' . gettype($val) . ']';
$replacements["{{$key}}"] = '[' . \gettype($val) . ']';
}
}
}
}
$message = strtr($message, $replacements);
$message = \strtr($message, $replacements);
}
return sprintf(
'[%s] [%s] %s %s',
date(self::$dateTimeFormat),
$level,
$message,
$context ?
return \sprintf(
'[%s] [%s] %s %s',
\date(self::$dateTimeFormat),
$level,
$message,
$context ?
"\n" .
json_encode(
\json_encode(
$context,
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)
{
return [
'exception' => get_class($exception),
'exception' => \get_class($exception),
'message' => $exception->getMessage(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'code' => $exception->getCode(),
'backtrace' => array_slice($exception->getTrace(), 0, 3),
'backtrace' => \array_slice($exception->getTrace(), 0, 3),
'previous exception' => $exception->getPrevious(),
];
}

View File

@ -1,9 +1,7 @@
<?php
<?php declare(strict_types=1);
namespace TelegramApiServer\MadelineProtoExtensions;
use Amp\Http\Server\FormParser\StreamedField;
use Amp\Http\Server\Request;
use Amp\Http\Server\Response;
@ -43,12 +41,9 @@ final class ApiExtensions
}
/**
* Проверяет есть ли подходящие медиа у сообщения
* Проверяет есть ли подходящие медиа у сообщения.
*
* @param array $message
* @param bool $allowWebPage
*
* @return bool
*/
private static function hasMedia(array $message = [], bool $allowWebPage = false): bool
{
@ -90,10 +85,10 @@ final class ApiExtensions
$text = StrTools::mbSubstr($message, $entity['offset'], $entity['length']);
$template = $html[$entity['_']];
if (in_array($entity['_'], ['messageEntityTextUrl', 'messageEntityMention', 'messageEntityUrl'])) {
$textFormated = sprintf($template, strip_tags($entity['url'] ?? $text), $text);
if (\in_array($entity['_'], ['messageEntityTextUrl', 'messageEntityMention', 'messageEntityUrl'])) {
$textFormated = \sprintf($template, \strip_tags($entity['url'] ?? $text), $text);
} else {
$textFormated = sprintf($template, $text);
$textFormated = \sprintf($template, $text);
}
$message = self::substringReplace($message, $textFormated, $entity['offset'], $entity['length']);
@ -105,7 +100,7 @@ final class ApiExtensions
}
if ($nextEntity['offset'] < ($entity['offset'] + $entity['length'])) {
$nextEntity['offset'] += StrTools::mbStrlen(
preg_replace('~(\>).*<\/.*$~', '$1', $textFormated)
\preg_replace('~(\>).*<\/.*$~', '$1', $textFormated)
);
} else {
$nextEntity['offset'] += StrTools::mbStrlen($textFormated) - StrTools::mbStrlen($text);
@ -115,7 +110,7 @@ final class ApiExtensions
}
}
unset($entity);
$message = nl2br($message);
$message = \nl2br($message);
return $message;
}
@ -127,7 +122,7 @@ final class ApiExtensions
}
/**
* Пересылает сообщения без ссылки на оригинал
* Пересылает сообщения без ссылки на оригинал.
*
* @param array $data
* <pre>
@ -141,7 +136,7 @@ final class ApiExtensions
*/
public function copyMessages(array $data)
{
$data = array_merge(
$data = \array_merge(
[
'from_peer' => '',
'to_peer' => '',
@ -157,7 +152,7 @@ final class ApiExtensions
]
);
$result = [];
if (!$response || !is_array($response) || !array_key_exists('messages', $response)) {
if (!$response || !\is_array($response) || !\array_key_exists('messages', $response)) {
return $result;
}
@ -174,7 +169,7 @@ final class ApiExtensions
$result[] = $this->madelineProto->messages->sendMessage(...$messageData);
}
if ($key > 0) {
delay(random_int(300, 2000) / 1000);
delay(\random_int(300, 2000) / 1000);
}
}
@ -182,14 +177,13 @@ final class ApiExtensions
}
/**
* Загружает медиафайл из указанного сообщения в поток
* Загружает медиафайл из указанного сообщения в поток.
*
* @param array $data
*
*/
public function getMedia(array $data): Response
{
$data = array_merge(
$data = \array_merge(
[
'peer' => '',
'id' => [0],
@ -222,17 +216,16 @@ final class ApiExtensions
}
}
return $this->downloadToResponse($info);
}
/**
* Загружает превью медиафайла из указанного сообщения в поток
* Загружает превью медиафайла из указанного сообщения в поток.
*
*/
public function getMediaPreview(array $data): Response
{
$data = array_merge(
$data = \array_merge(
[
'peer' => '',
'id' => [0],
@ -250,7 +243,7 @@ final class ApiExtensions
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;
switch (true) {
case isset($media['sizes']):
@ -308,15 +301,15 @@ final class ApiExtensions
public function getMessages(array $data): array
{
$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(
[
'channel' => $data['peer'],
'id' => (array)$data['id'],
'id' => (array) $data['id'],
]
);
} else {
$response = $this->madelineProto->messages->getMessages(['id' => (array)$data['id']]);
$response = $this->madelineProto->messages->getMessages(['id' => (array) $data['id']]);
}
return $response;
@ -328,7 +321,6 @@ final class ApiExtensions
* @param array $info
* Any downloadable array: message, media etc...
*
* @return Response
*/
public function downloadToResponse(array $info): Response
{
@ -336,7 +328,7 @@ final class ApiExtensions
}
/**
* Адаптер для стандартного метода
* Адаптер для стандартного метода.
*
*/
public function downloadToBrowser(array $info): Response
@ -361,7 +353,7 @@ final class ApiExtensions
$this->file->getMimeType(),
$this->file->getFilename()
);
$inputFile['id'] = unpack('P', $inputFile['id'])['1'];
$inputFile['id'] = \unpack('P', $inputFile['id'])['1'];
return [
'media' => [
'_' => 'inputMediaUploadedDocument',
@ -384,7 +376,6 @@ final class ApiExtensions
Client::getWrapper($this->madelineProto)->serialize();
}
public function getUpdates(array $params): array
{
foreach ($params as $key => $value) {
@ -410,12 +401,13 @@ final class ApiExtensions
Client::getWrapper($this->madelineProto)->serialize();
}
public function unsubscribeFromUpdates(?string $channel = null): array {
public function unsubscribeFromUpdates(?string $channel = null): array
{
$inputChannelId = null;
if ($channel) {
$id = (string) $this->madelineProto->getId($channel);
$inputChannelId = (int)str_replace(['-100', '-'], '', $id);
$inputChannelId = (int) \str_replace(['-100', '-'], '', $id);
if (!$inputChannelId) {
throw new InvalidArgumentException('Invalid id');
}
@ -438,10 +430,9 @@ final class ApiExtensions
$counter++;
}
return [
'disabled_update_loops' => $counter,
'current_update_loops' => count(Client::getWrapper($this->madelineProto)->getAPI()->feeders),
'current_update_loops' => \count(Client::getWrapper($this->madelineProto)->getAPI()->feeders),
];
}

View File

@ -1,4 +1,4 @@
<?php
<?php declare(strict_types=1);
namespace TelegramApiServer\MadelineProtoExtensions;
@ -41,7 +41,7 @@ final class SystemApiExtensions
public function addSession(string $session, array $settings = []): array
{
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);
@ -75,7 +75,7 @@ final class SystemApiExtensions
foreach ($this->client->instances as $session => $instance) {
$authorized = $instance->getAuthorization();
switch ($authorized) {
case API::NOT_LOGGED_IN;
case API::NOT_LOGGED_IN:
$status = 'NOT_LOGGED_IN';
break;
case API::WAITING_CODE:
@ -107,7 +107,7 @@ final class SystemApiExtensions
return [
'sessions' => $sessions,
'memory' => $this->bytesToHuman(memory_get_usage(true)),
'memory' => $this->bytesToHuman(\memory_get_usage(true)),
];
}
@ -115,10 +115,10 @@ final class SystemApiExtensions
{
$file = Files::getSessionFile($session);
if (is_file($file)) {
if (\is_file($file)) {
$futures = [];
foreach (glob("$file*") as $file) {
$futures[] = async(fn() => deleteFile($file));
foreach (\glob("$file*") as $file) {
$futures[] = async(fn () => deleteFile($file));
}
awaitAll($futures);
} else {
@ -140,7 +140,7 @@ final class SystemApiExtensions
public function unlinkSessionSettings($session): string
{
$settings = Files::getSessionFile($session, Files::SETTINGS_EXTENSION);
if (is_file($settings)) {
if (\is_file($settings)) {
deleteFile($settings);
}
@ -149,7 +149,7 @@ final class SystemApiExtensions
public function exit(): string
{
EventLoop::defer(static fn() => exit());
EventLoop::defer(static fn () => exit());
return 'ok';
}
@ -159,6 +159,6 @@ final class SystemApiExtensions
for ($i = 0; $bytes > 1024; $i++) {
$bytes /= 1024;
}
return round($bytes, 2) . ' ' . $units[$i];
return \round($bytes, 2) . ' ' . $units[$i];
}
}

View File

@ -1,4 +1,4 @@
<?php
<?php declare(strict_types=1);
namespace TelegramApiServer\Migrations;
@ -8,7 +8,7 @@ final class StartUpFixes
{
public static function fix(): void
{
define('MADELINE_WORKER_TYPE', 'madeline-ipc');
\define('MADELINE_WORKER_TYPE', 'madeline-ipc');
Magic::$isIpcWorker = true;
}
}

View File

@ -1,4 +1,4 @@
<?php
<?php declare(strict_types=1);
namespace TelegramApiServer\Server;
@ -20,7 +20,7 @@ final class AccessLoggerMiddleware implements Middleware
{
$method = $request->getMethod();
$uri = (string)$request->getUri();
$uri = (string) $request->getUri();
$protocolVersion = $request->getProtocolVersion();
$remote = Server::getClientIp($request);

View File

@ -1,4 +1,4 @@
<?php
<?php declare(strict_types=1);
namespace TelegramApiServer\Server;
@ -20,8 +20,8 @@ final class Authorization implements Middleware
public function __construct()
{
$this->selfIp = ip2long(getHostByName(php_uname('n')));
$this->ipWhitelist = (array)Config::getInstance()->get('api.ip_whitelist', []);
$this->selfIp = \ip2long(\getHostByName(\php_uname('n')));
$this->ipWhitelist = (array) Config::getInstance()->get('api.ip_whitelist', []);
$this->passwords = Config::getInstance()->get('api.passwords', []);
if (!$this->ipWhitelist && !$this->passwords) {
error('API is unprotected! Please specify IP_WHITELIST or PASSWORD in .env.docker');
@ -37,11 +37,11 @@ final class Authorization implements Middleware
}
if ($this->passwords) {
$header = (string)$request->getHeader('Authorization');
$header = (string) $request->getHeader('Authorization');
if ($header) {
sscanf($header, "Basic %s", $encodedPassword);
[$username, $password] = explode(':', base64_decode($encodedPassword), 2);
if (array_key_exists($username, $this->passwords) && $this->passwords[$username] === $password) {
\sscanf($header, "Basic %s", $encodedPassword);
[$username, $password] = \explode(':', \base64_decode($encodedPassword), 2);
if (\array_key_exists($username, $this->passwords) && $this->passwords[$username] === $password) {
return $requestHandler->handleRequest($request);
}
}
@ -59,21 +59,21 @@ final class Authorization implements Middleware
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 true;
}
private function isLocal(string $host): bool {
private function isLocal(string $host): bool
{
if ($host === '127.0.0.1' || $host === 'localhost') {
return true;
}
global $options;
if ($options['docker']) {
$isSameNetwork = abs(ip2long($host) - $this->selfIp) < 256;
$isSameNetwork = \abs(\ip2long($host) - $this->selfIp) < 256;
if ($isSameNetwork) {
return true;
}

View File

@ -1,4 +1,4 @@
<?php
<?php declare(strict_types=1);
namespace TelegramApiServer\Server;
@ -8,17 +8,15 @@ use TelegramApiServer\Controllers\AbstractApiController;
final class ErrorResponses
{
/**
* @param int $status
* @param string|array $message
*
* @return Response
*/
public static function get(int $status, $message): Response
{
return new Response(
$status,
AbstractApiController::JSON_HEADER,
json_encode(
\json_encode(
[
'success' => false,
'errors' => [

View File

@ -1,12 +1,12 @@
<?php
<?php declare(strict_types=1);
namespace TelegramApiServer\Server;
use Amp\Http\HttpStatus;
use Amp\Http\Server\ErrorHandler;
use Amp\Http\Server\Request;
use Amp\Http\Server\RequestHandler\ClosureRequestHandler;
use Amp\Http\Server\SocketHttpServer;
use Amp\Http\HttpStatus;
use TelegramApiServer\Controllers\ApiController;
use TelegramApiServer\Controllers\EventsController;
use TelegramApiServer\Controllers\LogController;
@ -70,5 +70,4 @@ final class Router
$this->router->addRoute('GET', '/log/{level:.*?[^/]}[/]', $logHandler);
}
}

View File

@ -1,4 +1,4 @@
<?php
<?php declare(strict_types=1);
namespace TelegramApiServer\Server;
@ -14,7 +14,6 @@ use Revolt\EventLoop;
use TelegramApiServer\Client;
use TelegramApiServer\Config;
use TelegramApiServer\Logger;
use function sprintf;
use const SIGINT;
use const SIGTERM;
@ -23,8 +22,6 @@ final class Server
/**
* Server constructor.
*
* @param array $options
* @param array|null $sessionFiles
*/
public function __construct(array $options, ?array $sessionFiles)
{
@ -51,7 +48,6 @@ final class Server
}
/**
* Stop the server gracefully when SIGINT is received.
* This is technically optional, but it is best to call Server::stop().
@ -60,10 +56,10 @@ final class Server
*/
private static function registerShutdown(SocketHttpServer $server)
{
if (defined('SIGINT')) {
if (\defined('SIGINT')) {
// Await SIGINT or SIGTERM to be received.
$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();
} else {
EventLoop::run();
@ -74,16 +70,14 @@ final class Server
}
/**
* Установить конфигурацию для http-сервера
* Установить конфигурацию для http-сервера.
*
* @param array $config
* @return 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
);
@ -97,18 +91,18 @@ final class Server
if ($realIpHeader) {
$remote = $request->getHeader($realIpHeader);
if (!$remote) {
GOTO DIRECT;
goto DIRECT;
}
$tmp = explode(',', $remote);
$remote = trim(end($tmp));
$tmp = \explode(',', $remote);
$remote = \trim(\end($tmp));
} else {
DIRECT:
$remote = $request->getClient()->getRemoteAddress()->toString();
$hostArray = explode(':', $remote);
if (count($hostArray) >= 2) {
$port = (int)array_pop($hostArray);
$hostArray = \explode(':', $remote);
if (\count($hostArray) >= 2) {
$port = (int) \array_pop($hostArray);
if ($port > 0 && $port <= 65353) {
$remote = implode(':', $hostArray);
$remote = \implode(':', $hostArray);
}
}