1
0
mirror of https://github.com/danog/MadelineProto.git synced 2024-11-26 15:24:39 +01:00
This commit is contained in:
Daniil Gentili 2024-03-08 13:19:15 +01:00
parent 0df07f3a37
commit 26dfd6833d
8 changed files with 6719 additions and 5855 deletions

2
docs

@ -1 +1 @@
Subproject commit b7d25c3f578a7e1fff540a388cb34d5ff3460658
Subproject commit 5b7e3ec1f68ed3c3c1843ba35eccbbc0354fa9e0

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,5 @@
{
"baseDirLimitation": "A basedir limitation is configured: this can impact performance and cause some issues, please disable it if possible!",
"noReportPeers": "Warning: no report peers are set, please add the following method to your event handler",
"manualAdminActionRequired": "!!!!!!!!! MANUAL SYSTEM ADMIN ACTION REQUIRED !!!!!!!!!",
"mmapErrorPart1": "The maximum number of memory mapped (mmap) regions was reached (%s): please increase the vm.max_map_count kernel config to 262144 to fix.",

View File

@ -371,6 +371,7 @@ If you intentionally deleted this account, ignore this message.',
],
'en' =>
[
'baseDirLimitation' => 'A basedir limitation is configured: this can impact performance and cause some issues, please disable it if possible!',
'2fa_uncalled' => 'I\'m not waiting for the password! Please call the phoneLogin and the completePhoneLogin methods first!',
'accepting_call' => 'Accepting call from %s...',
'account_banned' => '!!!!!!! WARNING !!!!!!!

View File

@ -120,15 +120,6 @@ final class MTProto implements TLCallback, LoggerGetter, SettingsGetter
private const MAX_ENTITY_SIZE = 8110;
/** @internal */
public const PFS_DURATION = 1*24*60*60;
/**
* Internal version of MadelineProto.
*
* Increased every time the default settings array or something big changes
*
* @internal
* @var int
*/
public const V = 184;
/**
* Bad message error codes.
*
@ -251,10 +242,10 @@ final class MTProto implements TLCallback, LoggerGetter, SettingsGetter
*/
private array $msg_ids = [];
/**
* Version integer for upgrades.
* Version value for upgrades.
*
*/
private int $v = 0;
private string|int $v = 0;
/**
* Cached getdialogs params.
*
@ -545,7 +536,7 @@ final class MTProto implements TLCallback, LoggerGetter, SettingsGetter
$this->datacenter->currentDatacenter = $this->settings->getConnection()->getTestMode() ? 10002 : 2;
$this->getConfig();
$this->startUpdateSystem(true);
$this->v = self::V;
$this->v = API::RELEASE;
$this->settings->applyChanges();
}
@ -1154,7 +1145,7 @@ final class MTProto implements TLCallback, LoggerGetter, SettingsGetter
if (!isset($this->settings)) {
$this->settings = new Settings;
} else {
if ($this->v !== self::V || $this->settings->getSchema()->needsUpgrade()) {
if ($this->v !== API::RELEASE || $this->settings->getSchema()->needsUpgrade()) {
$this->logger->logger("Generic settings have changed!", Logger::WARNING);
$this->upgradeMadelineProto();
}
@ -1198,9 +1189,9 @@ final class MTProto implements TLCallback, LoggerGetter, SettingsGetter
|| $this->settings->getConnection()->hasChanged()
|| $this->settings->getSchema()->hasChanged()
|| $this->settings->getSchema()->needsUpgrade()
|| $this->v !== self::V)) {
|| $this->v !== API::RELEASE)) {
$this->logger->logger("Generic settings have changed!", Logger::WARNING);
if ($this->v !== self::V || $this->settings->getSchema()->needsUpgrade()) {
if ($this->v !== API::RELEASE || $this->settings->getSchema()->needsUpgrade()) {
$this->upgradeMadelineProto();
}
$this->initialize($this->settings);
@ -1577,6 +1568,9 @@ final class MTProto implements TLCallback, LoggerGetter, SettingsGetter
if (!\extension_loaded('uv')) {
$warning .= "<p>".htmlentities(sprintf(Lang::$current_lang['extensionRecommended'], 'uv'))."</p>";
}
if (Magic::$hasBasedirLimitation) {
$warning .= "<p>".htmlentities(Lang::$current_lang['extensionRecommended'])."</p>";
}
if (Lang::$currentPercentage !== 100) {
$warning .= "<p>".sprintf(Lang::$current_lang['translate_madelineproto_web'], Lang::$currentPercentage)."</p>";
}

View File

@ -200,6 +200,10 @@ final class Magic
*
*/
public static bool $hasOpenssl = false;
/**
* Whether there's a basedir limitation.
*/
public static bool $hasBasedirLimitation = false;
/**
* Encoded emojis.
*
@ -311,6 +315,9 @@ final class Magic
}
self::$BIG_ENDIAN = pack('L', 1) === pack('N', 1);
self::$hasOpenssl = \extension_loaded('openssl');
try {
self::$hasBasedirLimitation = (bool)@ini_get('open_basedir');
} catch (\Throwable) {}
self::$emojis = json_decode(self::JSON_EMOJIS);
self::$zero = new BigInteger(0);
self::$one = new BigInteger(1);

View File

@ -70,20 +70,20 @@ final class TL implements TLInterface
private array $tdDescriptions;
/** @var array<string, list<TBeforeMethodResponseDeserialization>> */
private array $beforeMethodResponseDeserialization;
public array $beforeMethodResponseDeserialization;
/** @var array<string, list<TAfterMethodResponseDeserialization>> */
private array $afterMethodResponseDeserialization;
public array $afterMethodResponseDeserialization;
/** @var array<string, TBeforeConstructorSerialization> */
private array $beforeConstructorSerialization;
public array $beforeConstructorSerialization;
/** @var array<string, list<TBeforeConstructorDeserialization>> */
private array $beforeConstructorDeserialization;
public array $beforeConstructorDeserialization;
/** @var array<string, list<TAfterConstructorDeserialization>> */
private array $afterConstructorDeserialization;
public array $afterConstructorDeserialization;
/** @var array<string, TTypeMismatch> */
private array $typeMismatch;
public array $typeMismatch;
/**
* API instance.

View File

@ -18,7 +18,15 @@ declare(strict_types=1);
namespace danog\MadelineProto\TL;
use AssertionError;
use Closure;
use danog\MadelineProto\MTProto;
use danog\MadelineProto\MTProtoTools\MinDatabase;
use danog\MadelineProto\MTProtoTools\PeerDatabase;
use danog\MadelineProto\MTProtoTools\ReferenceDatabase;
use danog\MadelineProto\Settings\TLSchema;
use ReflectionFunction;
use Webmozart\Assert\Assert;
/**
* @internal
@ -32,13 +40,6 @@ final class Builder
private readonly array $byType;
private readonly array $idByPredicate;
private readonly array $typeByPredicate;
private array $needConstructors = [];
private array $needVector = [];
private const RECURSIVE_TYPES = [
'InputPeer',
'RichText',
'PageBlock',
];
private $output;
public function __construct(
TLSchema $settings,
@ -66,6 +67,7 @@ final class Builder
$this->idByPredicate = $idByPredicate;
$this->typeByPredicate = $typeByPredicate;
}
private static function escapeConstructorName(array $constructor): string
{
return str_replace(['.', ' '], '___', $constructor['predicate']).(isset($constructor['layer']) ?'_'.$constructor['layer'] : '');
@ -74,44 +76,100 @@ final class Builder
{
return str_replace(['.', ' '], '___', $name);
}
private array $methodsCalled = [];
private array $methodsCreated = [];
private function methodCall(string $method): string
{
$this->methodsCalled[$method] = true;
return $this->methodsCreated[$method]
? "\$this->$method(\$stream)"
: "self::$method(\$stream)";
}
private function buildParam(array $param): string
{
['type' => $type] = $param;
if (isset($param['subtype'])) {
$this->needVector[$param['subtype']] = true;
$type = "array_of_{$param['subtype']}";
private function needFullConstructor(string $predicate): bool {
if (isset($this->TL->beforeConstructorDeserialization[$predicate])
|| isset($this->TL->afterConstructorDeserialization[$predicate]))
{
return true;
}
if (($param['name'] ?? null) === 'random_bytes') {
$type = $param['name'];
}
return match ($type) {
'#' => "unpack('V', stream_get_contents(\$stream, 4))[1]",
'int' => "unpack('l', stream_get_contents(\$stream, 4))[1]",
'long' => "unpack('q', stream_get_contents(\$stream, 8))[1]",
'double' => "unpack('d', stream_get_contents(\$stream, 8))[1]",
'Bool' => 'match (stream_get_contents($stream, 4)) {'.
$this->idByPredicate['boolTrue'].' => true,'.
$this->idByPredicate['boolFalse'].' => false, default => '.$this->methodCall('err').' }',
'strlong' => 'stream_get_contents($stream, 8)',
'int128' => 'stream_get_contents($stream, 16)',
'int256' => 'stream_get_contents($stream, 32)',
'int512' => 'stream_get_contents($stream, 64)',
'string', 'bytes', 'waveform', 'random_bytes' =>
$this->methodCall("deserialize_$type"),
default => \in_array($type, self::RECURSIVE_TYPES, true) || isset($param['subtype'])
? $this->methodCall("deserialize_type_{$this->escapeTypeName($type)}")
: $this->buildTypes($this->byType[$type], $type)
};
return $predicate === 'rpc_result';
}
private static function methodFromClosure(ReflectionFunction $closure): string {
$refl = new ReflectionFunction($closure);
return match ($refl->getClosureThis()::class) {
PeerDatabase::class => '$this->peerDatabase',
MinDatabase::class => '$this->minDatabase?',
ReferenceDatabase::class => '$this->referenceDatabase?',
MTProto::class => '$this->API',
}."->".$refl->getName();
}
private function buildTypes(array $constructors, ?string $type = null): string
{
$typeMethod = $type ? "_type_".self::escapeTypeName($type) : '';
$result = "match (stream_get_contents(\$stream, 4)) {\n";
foreach ($constructors as $id => ['predicate' => $predicate]) {
if ($predicate === 'gzip_packed') {
continue;
}
if ($predicate === 'jsonObjectValue') {
continue;
}
$result .= var_export($id, true)." => ";
$result .= $this->buildConstructor($predicate);
$result .= ",\n";
}
$result .= $this->idByPredicate['gzip_packed']." => ".$this->methodCall("deserialize$typeMethod", 'self::gzdecode($stream)').",\n";
$result .= "default => self::err(\$stream)\n";
return $result."}\n";
}
private array $createdConstructors = [];
public function buildConstructor(string $predicate): string {
$constructor = $this->TL->getConstructors()->findByPredicate($predicate);
Assert::notFalse($constructor, "Missing constructor $predicate");
[
'flags' => $flags,
'params' => $params,
] = $constructor;
if (!$flags && !$this->needFullConstructor($predicate)) {
return $this->buildConstructorShort($predicate, $params);
}
$nameEscaped = self::escapeConstructorName($constructor);
if (!isset($this->createdConstructors[$predicate])) {
$this->createdConstructors[$predicate] = true;
$this->m("deserialize_$nameEscaped", $this->buildConstructorFull($predicate, $params, $flags));
}
return $this->methodCall("deserialize_$nameEscaped");
}
private function buildConstructorFull(string $predicate, array $params, array $flags): string
{
if (!$flags && !$this->needFullConstructor($predicate)) {
return "return {$this->buildConstructorShort($predicate, $params)};";
}
$result = '';
foreach ($this->TL->beforeConstructorDeserialization as $closure) {
$result .= self::methodFromClosure($closure)."('$predicate');\n";
}
$result .= "\$tmp = ['_' => '$predicate'];\n";
$flagNames = [];
foreach ($flags as ['flag' => $flag]) {
$flagNames[$flag] = true;
}
foreach ($params as $param) {
$name = $param['name'];
if (!isset($param['pow'])) {
$code = $this->buildParam($param);
if (isset($flagNames[$name])) {
$result .= "\$$name = $code;\n";
} else {
$result .= "\$tmp['$name'] = $code;\n";
}
continue;
}
$flag = "(\${$param['flag']} & {$param['pow']}) !== 0";
if ($param['type'] === 'true') {
$result .= "\$tmp['$name'] = $flag;\n";
continue;
}
$code = $this->buildParam($param);
$result .= "if ($flag) \$tmp['$name'] = $code;\n";
}
return "$result\nreturn \$tmp;";
}
private function buildConstructorShort(string $predicate, array $params = []): string
{
if ($predicate === 'dataJSON') {
@ -157,92 +215,103 @@ final class Builder
}
return $result;
}
private function buildConstructor(string $predicate, array $params, array $flags): string
{
if (!$flags) {
return "return {$this->buildConstructorShort($predicate, $params)};";
}
$result = "\$tmp = ['_' => '$predicate'];\n";
$flagNames = [];
foreach ($flags as ['flag' => $flag]) {
$flagNames[$flag] = true;
}
foreach ($params as $param) {
$name = $param['name'];
if (!isset($param['pow'])) {
$code = $this->buildParam($param);
if (isset($flagNames[$name])) {
$result .= "\$$name = $code;\n";
} else {
$result .= "\$tmp['$name'] = $code;\n";
private function buildParam(array $param): string
{
['type' => $type] = $param;
if (($param['name'] ?? null) === 'random_bytes') {
$type = $param['name'];
}
if (isset($param['subtype'])) {
return $this->buildVector($param['subtype']);
}
return match ($type) {
'#' => "unpack('V', stream_get_contents(\$stream, 4))[1]",
'int' => "unpack('l', stream_get_contents(\$stream, 4))[1]",
'long' => "unpack('q', stream_get_contents(\$stream, 8))[1]",
'double' => "unpack('d', stream_get_contents(\$stream, 8))[1]",
'Bool' => 'match (stream_get_contents($stream, 4)) {'.
$this->idByPredicate['boolTrue'].' => true,'.
$this->idByPredicate['boolFalse'].' => false, default => '.$this->methodCall('err').' }',
'strlong' => 'stream_get_contents($stream, 8)',
'int128' => 'stream_get_contents($stream, 16)',
'int256' => 'stream_get_contents($stream, 32)',
'int512' => 'stream_get_contents($stream, 64)',
'string', 'bytes', 'waveform', 'random_bytes' =>
$this->methodCall("deserialize_$type"),
default => $this->buildType($type)
};
}
private array $createdVectors = [];
private function buildVector(string $type, ?string $payload = null): string
{
if (!isset($this->createdVectors[$type])) {
$this->createdVectors[$type] = true;
if ($type === 'JSONObjectValue') {
$payload = '$result['.$this->buildParam(['type' => 'string']).'] = '.$this->buildParam(['type' => 'JSONValue']);
} elseif (isset($this->byType[$type])) {
$payload = '$result []= '.$this->buildTypes($this->byType[$type], $type);
} elseif ($payload === null) {
if ($type === '%MTMessage') {
$type = 'MTmessage';
}
continue;
$payload = '$result []= '.$this->buildConstructor($type);
}
$flag = "(\${$param['flag']} & {$param['pow']}) !== 0";
if ($param['type'] === 'true') {
$result .= "\$tmp['$name'] = $flag;\n";
continue;
}
$code = $this->buildParam($param);
$result .= "if ($flag) \$tmp['$name'] = $code;\n";
$this->m("deserialize_type_array_of_{$this->escapeTypeName($type)}", '
$stream = match(stream_get_contents($stream, 4)) {
'.$this->idByPredicate['vector'].' => $stream,
'.$this->idByPredicate['gzip_packed'].' => self::gzdecode_vector($stream)
};
$result = [];
for ($x = unpack("V", stream_get_contents($stream, 4))[1]; $x > 0; --$x) {
'.$payload.';
}
return $result;
', 'array', static: $type === 'JSONValue');
}
return "$result\nreturn \$tmp;";
return $this->methodCall("deserialize_type_array_of_{$this->escapeTypeName($type)}");
}
private function buildTypes(array $constructors, ?string $type = null): string
{
$typeMethod = $type ? "_type_".self::escapeTypeName($type) : '';
$result = "match (stream_get_contents(\$stream, 4)) {\n";
foreach ($constructors as $id => $constructor) {
[
'predicate' => $name,
'flags' => $flags,
'params' => $params
] = $constructor;
if ($name === 'gzip_packed') {
continue;
}
if ($name === 'jsonObjectValue') {
continue;
}
$nameEscaped = self::escapeConstructorName($constructor);
if (!$flags) {
$params = $this->buildConstructorShort($name, $params);
$result .= var_export($id, true)." => $params,\n";
} else {
$this->needConstructors[$name] = true;
$result .= var_export($id, true)." => ".$this->methodCall("deserialize_$nameEscaped").",\n";
}
private array $createdTypes = ['Object' => true, 'MethodResponse'];
private function buildType(string $type): string {
if (!isset($this->createdTypes[$type])) {
$this->createdTypes[$type] = true;
$this->m(
"deserialize_type_{$this->escapeTypeName($type)}",
"return {$this->buildTypes($this->byType[$type], $type)};",
static: $type === 'JSONValue'
);
return $this->methodCall("deserialize_type_{$this->escapeTypeName($type)}");
}
$result .= $this->idByPredicate['gzip_packed']." => ".$this->methodCall("deserialize$typeMethod", 'self::gzdecode($stream)').",\n";
$result .= "default => self::err(\$stream)\n";
return $result."}\n";
return $this->methodCall("deserialize_type_{$this->escapeTypeName($type)}");
}
private function buildVector(string $type, string $body): void
private array $methodsCreated = [];
private function methodCall(string $method): string
{
$this->m("deserialize_type_array_of_{$this->escapeTypeName($type)}", '
$stream = match(stream_get_contents(\$stream, 4)) {
'.$this->idByPredicate['vector'].' => $stream,
'.$this->idByPredicate['gzip_packed'].' => self::gzdecode_vector($stream)
};
$result = [];
for ($x = unpack("V", stream_get_contents($stream, 4))[1]; $x > 0; --$x) {
$result []= '.$body.'
}
return $result;
', 'array', static: $type === 'JSONValue');
}
private function w(string $data): void
{
fwrite($this->output, $data);
return ($this->methodsCreated[$method] ?? true)
? "\$this->$method(\$stream)"
: "self::$method(\$stream)";
}
public function m(string $methodName, string $body, string $returnType = 'mixed', bool $public = false, bool $static = true): void
{
if (isset($this->methodsCreated[$methodName])) {
throw new AssertionError("Already created $methodName!");
}
$this->methodsCreated[$methodName] = $static;
$public = $public ? 'public' : 'private';
$static = $static ? 'static' : '';
$this->w(" $public $static function $methodName(mixed \$stream): $returnType {\n{$body}\n }\n");
}
private function w(string $data): void
{
fwrite($this->output, $data);
}
public function build(): void
{
$this->w("<?php namespace {$this->namespace};\n/** @internal Autogenerated using tools/TL/Builder.php */\nfinal class TLParser {\n");
@ -328,62 +397,22 @@ final class Builder
stream_get_contents($stream, $l);
', 'void');
$this->m('deserialize_type_array_of_JSONObjectValue', '
$stream = match(stream_get_contents($stream, 4)) {
'.$this->idByPredicate["vector"].' => $stream,
'.$this->idByPredicate["gzip_packed"].' => self::gzdecode_vector($stream)
};
$result = [];
for ($x = unpack("V", stream_get_contents($stream, 4))[1]; $x > 0; --$x) {
$result['.$this->buildParam(['type' => 'string']).'] = '.$this->buildParam(['type' => 'JSONValue']).';
}
return $result;
', 'array');
foreach (['int', 'long', 'double', 'strlong', 'string', 'bytes'] as $type) {
$this->buildVector($type, $this->buildParam(['type' => $type]));
}
$initial_constructors = array_filter(
$this->TL->getConstructors()->by_id,
static fn (array $arr) => $arr['type'] === 'Update'
static fn (array $arr) => ($arr['type'] === 'Update'
|| $arr['predicate'] === 'rpc_result'
|| !$arr['encrypted']
) && (
$arr['predicate'] === 'rpc_error'
)
);
$this->m("deserialize", "return {$this->buildTypes($initial_constructors)};", 'mixed', true, static: false);
$this->m("deserialize_type_Object", "return {$this->buildTypes($initial_constructors)};", 'mixed', true, static: false);
foreach ($this->TL->getConstructors()->by_id as $id => $constructor) {
['predicate' => $name, 'flags' => $flags, 'params' => $params, 'type' => $type] = $constructor;
if ($name === 'jsonObjectValue') {
continue;
}
if ($name === 'dataJSON') {
continue;
}
if ($type === 'JSONValue') {
continue;
}
if ($name === 'gzip_packed') {
continue;
}
$nameEscaped = self::escapeConstructorName($constructor);
$this->m("deserialize_$nameEscaped", $this->buildConstructor($name, $params, $flags));
}
foreach ($this->byType as $type => $constructors) {
if ($type === 'JSONObjectValue') {
continue;
}
$this->m(
"deserialize_type_{$this->escapeTypeName($type)}",
"return {$this->buildTypes($constructors, $type)};",
static: $type === 'JSONValue'
);
if (isset($this->needVector[$type])) {
$this->buildVector($type, $this->buildTypes($constructors, "array_of_$type"));
}
}
foreach (['int', 'long', 'double', 'strlong'] as $type) {
$this->buildVector($type, $this->buildParam(['type' => $type]));
}
$this->w("}\n");
}
}