1
0
mirror of https://github.com/danog/MadelineProto.git synced 2024-11-26 17:04:39 +01:00

Refactor public API

This commit is contained in:
Daniil Gentili 2023-01-27 14:20:47 +01:00
parent 9ea2b38ca1
commit f5c94fcae3
257 changed files with 7214 additions and 8167 deletions

2
.gitignore vendored
View File

@ -101,7 +101,7 @@ madeline.php
composer.lock
b.php
telegram-cli*
src/danog/MadelineProto/Fuzzer.php
src/Fuzzer.php
fuzzer.php
tests/500mb
*.save

View File

@ -86,7 +86,7 @@
],
"autoload": {
"psr-4": {
"danog\\MadelineProto\\": "src/danog/MadelineProto"
"danog\\MadelineProto\\": "src"
},
"files": [
"src/polyfill.php",

View File

@ -113,7 +113,8 @@ class MyEventHandler extends EventHandler
// Chat id
$id = $this->getId($update);
// You can also use the built-in MadelineProto MySQL async driver!
// In this example code, send the "This userbot is powered by MadelineProto!" message only once per chat.
// Ignore all further messages coming from this chat.
if (!isset($this->notifiedChats[$id])) {
$this->notifiedChats[$id] = true;
@ -139,17 +140,19 @@ class MyEventHandler extends EventHandler
}
}
// Test MadelineProto's built-in database driver, which automatically maps to MySQL/PostgreSQL/Redis
// properties mentioned in the MyEventHandler::$dbProperties property!
// Can be anything serializable: an array, an int, an object, ...
$myData = [];
if (isset($this->dataStoredOnDb['yourKey'])) {
// Always when fetching data
$myData = $this->dataStoredOnDb['yourKey'];
if (isset($this->dataStoredOnDb['k1'])) {
$myData = $this->dataStoredOnDb['k1'];
}
$this->dataStoredOnDb['yourKey'] = $myData + ['moreStuff' => 'yay'];
$this->dataStoredOnDb['k1'] = $myData + ['moreStuff' => 'yay'];
$this->dataStoredOnDb['otherKey'] = 0;
unset($this->dataStoredOnDb['otherKey']);
$this->dataStoredOnDb['k2'] = 0;
unset($this->dataStoredOnDb['k2']);
$this->logger("Count: ".count($this->dataStoredOnDb));

View File

@ -308,7 +308,7 @@ class SecretHandler extends EventHandler
while ($i < 10) {
$this->logger("SENDING MESSAGE $i TO ".$update['message']['chat_id']);
// You can also use the sendEncrypted parameter for more options in secret chats
$this->messages->sendMessage(['peer' => $update, 'message' => (string) ($i++)]);
$this->messages->sendMessage(peer: $update, message: (string) ($i++));
}
$this->sent[$update['message']['chat_id']] = true;
}

View File

@ -1,22 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="dev-master@">
<file src="src/danog/MadelineProto/Conversion.php">
<file src="src/Conversion.php">
<UndefinedDocblockClass occurrences="1">
<code>$MadelineProto-&gt;help-&gt;getConfig()</code>
</UndefinedDocblockClass>
</file>
<file src="src/danog/MadelineProto/Db/DriverArray.php">
<file src="src/Db/DriverArray.php">
<UndefinedMethod occurrences="1">
<code>getName</code>
</UndefinedMethod>
</file>
<file src="src/danog/MadelineProto/Db/MemoryArray.php">
<file src="src/Db/MemoryArray.php">
<MethodSignatureMismatch occurrences="2">
<code>MemoryArray</code>
<code>MemoryArray</code>
</MethodSignatureMismatch>
</file>
<file src="src/danog/MadelineProto/Stream/Common/UdpBufferedStream.php">
<file src="src/Stream/Common/UdpBufferedStream.php">
<InvalidPropertyAssignmentValue occurrences="1">
<code>$ctx-&gt;getStream($header)</code>
</InvalidPropertyAssignmentValue>

View File

@ -11,7 +11,6 @@
<directory name="src" />
<ignoreFiles>
<directory name="vendor" />
<file name="src/danog/MadelineProto/InternalDoc.php" />
</ignoreFiles>
</projectFiles>
<issueHandlers>

View File

@ -45,7 +45,7 @@ use function Amp\Future\await;
/**
* Main API wrapper for MadelineProto.
*/
final class API extends InternalDoc
final class API extends AbstractAPI
{
/**
* Release version.
@ -176,7 +176,7 @@ final class API extends InternalDoc
/**
* Reconnect to full instance.
*/
protected function reconnectFull()
protected function reconnectFull(): bool
{
if ($this->wrapper->getAPI() instanceof Client) {
$this->wrapper->logger('Restarting to full instance...');

View File

@ -22,40 +22,10 @@ namespace danog\MadelineProto;
use Amp\Future\UnhandledFutureError;
use Amp\SignalException;
use InvalidArgumentException;
use Revolt\EventLoop;
abstract class AbstractAPIFactory
abstract class AbstractAPI extends InternalDoc
{
/**
* Namespace.
*
* @internal
*/
private string $namespace = '';
/**
* API wrapper (to avoid circular references).
*/
protected APIWrapper $wrapper;
/**
* Export APIFactory instance with the specified namespace.
*/
protected function exportNamespaces(): void
{
$class = \array_reverse(\array_values(\class_parents(static::class)))[1];
foreach (\get_class_vars(APIFactory::class) as $key => $var) {
if (\in_array($key, ['namespace', 'methods', 'wrapper'])) {
continue;
}
$instance = new $class;
$instance->namespace = $key.'.';
$instance->wrapper = $this->wrapper;
$this->{$key} = $instance;
}
}
/**
* Enable or disable async.
*
@ -64,29 +34,6 @@ abstract class AbstractAPIFactory
public function async(bool $async): void
{
}
/**
* Call async wrapper function.
*
* @param string $name Method name
* @param array $arguments Arguments
* @internal
*/
public function __call(string $name, array $arguments)
{
if ($arguments && !isset($arguments[0])) {
$arguments = [$arguments];
}
$name = $this->namespace.$name;
$aargs = isset($arguments[1]) && \is_array($arguments[1]) ? $arguments[1] : [];
$aargs['apifactory'] = true;
$args = isset($arguments[0]) && \is_array($arguments[0]) ? $arguments[0] : [];
if (isset($args[0]) && !isset($args['multiple'])) {
throw new InvalidArgumentException('Parameter names must be provided!');
}
return $this->wrapper->getAPI()->methodCallAsyncRead($name, $args, $aargs);
}
/**
* Start MadelineProto and the event handler (enables async).
*
@ -130,6 +77,9 @@ abstract class AbstractAPIFactory
}
}
}
abstract protected function reconnectFull(): bool;
private function startAndLoopLogic(string $eventHandler, bool &$started): void
{
$this->start();

View File

@ -22,24 +22,20 @@ namespace danog\MadelineProto;
use danog\MadelineProto\Settings\TLSchema;
use danog\MadelineProto\TL\TL;
use danog\MadelineProto\TL\TLCallback;
use phpDocumentor\Reflection\DocBlockFactory;
use ReflectionClass;
use ReflectionMethod;
use ReflectionNamedType;
use ReflectionType;
use ReflectionUnionType;
use function Amp\File\read;
final class AnnotationsBuilder
{
/**
* Reflection classes.
*/
private array $reflectionClasses = [];
/**
* Logger.
*/
private Logger $logger;
/**
* Namespace.
*/
@ -48,56 +44,71 @@ final class AnnotationsBuilder
* TL instance.
*/
private TL $TL;
/**
* Settings.
*/
private array $settings;
/**
* Output file.
*/
private string $output;
public function __construct(Logger $logger, array $settings, string $output, array $reflectionClasses, string $namespace)
private array $blacklist;
private array $blacklistHard;
public function __construct(Logger $logger, array $settings, array $reflectionClasses, string $namespace)
{
$this->reflectionClasses = $reflectionClasses;
$this->logger = $logger;
$this->namespace = $namespace;
/** @psalm-suppress InvalidArgument */
$this->TL = new TL();
$tlSchema = new TLSchema;
$tlSchema->mergeArray($settings);
$this->TL->init($tlSchema);
$this->settings = $settings;
$this->output = $output;
$this->blacklist = json_decode(read(__DIR__.'/../../../docs/template/disallow.json'), true);
$this->blacklistHard = $this->blacklist;
unset($this->blacklistHard['messages.getHistory']);
unset($this->blacklistHard['channels.getMessages']);
unset($this->blacklistHard['updates.getDifference']);
unset($this->blacklistHard['updates.getChannelDifference']);
unset($this->blacklistHard['updates.getState']);
}
public function mkAnnotations(): void
{
Logger::log('Generating annotations...', Logger::NOTICE);
$this->setProperties();
$this->createInternalClasses();
}
/**
* Open file of class APIFactory
* Insert properties
* save the file with new content.
*/
private function setProperties(): void
private function prepareTLType(string $type): string
{
Logger::log('Generating properties...', Logger::NOTICE);
$fixture = DocBlockFactory::createInstance();
$class = new ReflectionClass($this->reflectionClasses['APIFactory']);
$content = \file_get_contents($filename = $class->getFileName());
foreach ($class->getProperties() as $property) {
if ($raw_docblock = $property->getDocComment()) {
$docblock = $fixture->create($raw_docblock);
if ($docblock->hasTag('internal')) {
$content = \str_replace("\n ".$raw_docblock."\n public \$".$property->getName().';', '', $content);
}
}
}
foreach ($this->TL->getMethodNamespaces() as $namespace) {
$content = \preg_replace('/(class( \\w+[,]?){0,}\\n{\\n)/', '${1}'." /**\n"." * @internal this is a internal property generated by build_docs.php, don't change manually\n"." *\n"." * @var {$namespace}\n"." */\n"." public \${$namespace};\n", $content);
}
\file_put_contents($filename, $content);
return match ($type) {
'string' => 'string',
'bytes' => 'string',
'int' => 'int',
'long' => 'int',
'double' => 'float',
'float' => 'float',
'Bool' => 'bool',
'bool' => 'bool',
default => 'array'
};
}
private function prepareTLDefault(string $type): string
{
return match ($type) {
'string' => "''",
'bytes' => "''",
'int' => '0',
'long' => '0',
'double' => '0.0',
'float' => '0.0',
'Bool' => 'false',
'bool' => 'false',
default => '[]'
};
}
private function prepareTLTypeDescription(string $type): string
{
return match ($type) {
'string' => '',
'bytes' => '',
'int' => '',
'long' => '',
'double' => '',
'float' => '',
'Bool' => '',
'bool' => '',
default => " @see https://docs.madelineproto.xyz/API_docs/types/$type.html"
};
}
/**
* Create internalDoc.
@ -105,21 +116,6 @@ final class AnnotationsBuilder
private function createInternalClasses(): void
{
Logger::log('Creating internal classes...', Logger::NOTICE);
$handle = \fopen($this->output, 'w');
\fwrite($handle, "<?php namespace {$this->namespace}; class InternalDoc extends APIFactory {}");
$class = new ReflectionClass($this->reflectionClasses['API']);
$methods = $class->getMethods(ReflectionMethod::IS_STATIC | ReflectionMethod::IS_PUBLIC);
$ignoreMethods = ['fetchserializableobject'];
foreach ($methods as $method) {
$ignoreMethods[$method->getName()] = $method->getName();
}
$class = new ReflectionClass(TLCallback::class);
$methods = $class->getMethods(ReflectionMethod::IS_STATIC | ReflectionMethod::IS_PUBLIC);
foreach ($methods as $method) {
$ignoreMethods[$method->getName()] = $method->getName();
}
\fclose($handle);
$handle = \fopen($this->output, 'w');
$internalDoc = [];
foreach ($this->TL->getMethods()->by_id as $id => $data) {
if (!\strpos($data['method'], '.')) {
@ -129,6 +125,9 @@ final class AnnotationsBuilder
if (!\in_array($namespace, $this->TL->getMethodNamespaces())) {
continue;
}
if (isset($this->blacklist[$data['method']])) {
continue;
}
$internalDoc[$namespace][$method]['title'] = \str_replace(['](../', '.md'], ['](https://docs.madelineproto.xyz/API_docs/', '.html'], Lang::$lang['en']["method_{$data['method']}"] ?? '');
$type = \str_ireplace(['vector<', '>'], [' of ', '[]'], $data['type']);
foreach ($data['params'] as $param) {
@ -142,11 +141,14 @@ final class AnnotationsBuilder
if ($param['name'] === 'chat_id' && $data['method'] !== 'messages.discardEncryption') {
$param['type'] = 'InputPeer';
}
if ($param['name'] === 'hash' && $param['type'] === 'int') {
if ($param['name'] === 'hash' && $param['type'] === 'long') {
$param['pow'] = 'hi';
$param['type'] = 'Vector t';
$param['subtype'] = 'int';
}
if ($param['type'] === 'bool') {
$param['pow'] = 'hi';
}
$stype = 'type';
if (isset($param['subtype'])) {
$stype = 'subtype';
@ -158,8 +160,7 @@ final class AnnotationsBuilder
$ptype = 'boolean';
}
$ptype = $stype === 'type' ? $ptype : "[{$ptype}]";
$opt = $param['pow'] ?? false ? 'Optional: ' : '';
$internalDoc[$namespace][$method]['attr'][$param['name']] = ['type' => $ptype, 'description' => \str_replace(['](../', '.md'], ['](https://docs.madelineproto.xyz/API_docs/', '.html'], $opt.(Lang::$lang['en']["method_{$data['method']}_param_{$param['name']}_type_{$param['type']}"] ?? ''))];
$internalDoc[$namespace][$method]['attr'][$param['name']] = ['optional' => isset($param['pow']), 'type' => $ptype, 'description' => \str_replace(['](../', '.md'], ['](https://docs.madelineproto.xyz/API_docs/', '.html'], Lang::$lang['en']["method_{$data['method']}_param_{$param['name']}_type_{$param['type']}"] ?? '')];
}
if ($type === 'Bool') {
$type = \strtolower($type);
@ -252,7 +253,6 @@ final class AnnotationsBuilder
}
$doc .= ', ';
if ($param->isVariadic()) {
$hasVariadic = true;
$paramList .= '...';
}
$paramList .= '$'.$param->getName().', ';
@ -274,7 +274,10 @@ final class AnnotationsBuilder
if ($method->getDeclaringClass()->getName() == StrTools::class) {
$async = false;
}
$ret = $type && $type instanceof ReflectionNamedType && \in_array($type->getName(), ['self', 'void']) ? '' : 'return';
if ($method->getDeclaringClass()->getName() == AsyncTools::class) {
$async = false;
}
$ret = $type && $type instanceof ReflectionNamedType && $type->getName() === 'void' ? '' : 'return';
$doc .= "\n{\n";
if ($async) {
$doc .= " {$ret} \$this->wrapper->getAPI()->{__FUNCTION__}({$paramList});\n";
@ -283,9 +286,6 @@ final class AnnotationsBuilder
} else {
$doc .= " {$ret} \\".$method->getDeclaringClass()->getName().'::'.$name."({$paramList});\n";
}
if (!$ret && $type->getName() === 'self') {
$doc .= " return \$this;\n";
}
$doc .= "}\n";
if (!$method->getDocComment()) {
Logger::log("{$name} has no PHPDOC!", Logger::FATAL_ERROR);
@ -325,17 +325,46 @@ final class AnnotationsBuilder
$internalDoc['InternalDoc'][$name]['method'] = $phpdoc;
$internalDoc['InternalDoc'][$name]['method'] .= "\n ".\implode("\n ", \explode("\n", $doc));
}
\fwrite($handle, "<?php\n");
\fwrite($handle, "/**\n");
\fwrite($handle, " * This file is automatic generated by build_docs.php file\n");
\fwrite($handle, " * and is used only for autocomplete in multiple IDE\n");
\fwrite($handle, " * don't modify manually.\n");
\fwrite($handle, " */\n\n");
\fwrite($handle, "namespace {$this->namespace};\n");
foreach ($internalDoc as $namespace => $methods) {
if ($namespace === 'InternalDoc') {
\fwrite($handle, "\nclass {$namespace} extends APIFactory\n{\n");
$handle = \fopen(__DIR__.'/InternalDoc.php', 'w');
\fwrite($handle, "<?php\n");
\fwrite($handle, "/**\n");
\fwrite($handle, " * This file is automatically generated by the build_docs.php file\n");
\fwrite($handle, " * and is used only for autocompletion in multiple IDEs\n");
\fwrite($handle, " * don't modify it manually.\n");
\fwrite($handle, " */\n\n");
\fwrite($handle, "namespace {$this->namespace};\n");
\fwrite($handle, "\nabstract class {$namespace}\n{\nprotected APIWrapper \$wrapper;\n");
foreach ($this->TL->getMethodNamespaces() as $namespace) {
$namespaceInterface = '\\danog\\MadelineProto\\Namespace\\'.\ucfirst($namespace);
\fwrite($handle, '/** @var \\danog\\MadelineProto\\Namespace\\AbstractAPI&'.$namespaceInterface.' $'.$namespace." */\n");
\fwrite($handle, 'public readonly \\danog\\MadelineProto\\Namespace\\AbstractAPI $'.$namespace.";\n");
}
\fwrite($handle, '
/**
* Export APIFactory instance with the specified namespace.
*/
protected function exportNamespaces(): void
{
');
foreach ($this->TL->getMethodNamespaces() as $namespace) {
\fwrite($handle, "\$this->$namespace ??= new \\danog\\MadelineProto\\Namespace\\AbstractAPI('$namespace');\n");
\fwrite($handle, "\$this->{$namespace}->setWrapper(\$this->wrapper);\n");
}
\fwrite($handle, "}\n");
} else {
$namespace = \ucfirst($namespace);
$handle = \fopen(__DIR__."/Namespace/$namespace.php", 'w');
\fwrite($handle, "<?php\n");
\fwrite($handle, "/**\n");
\fwrite($handle, " * This file is automatic generated by build_docs.php file\n");
\fwrite($handle, " * and is used only for autocomplete in multiple IDE\n");
\fwrite($handle, " * don't modify manually.\n");
\fwrite($handle, " */\n\n");
\fwrite($handle, "namespace {$this->namespace}\\Namespace;\n");
\fwrite($handle, "\ninterface {$namespace}\n{");
}
foreach ($methods as $method => $properties) {
@ -347,29 +376,29 @@ final class AnnotationsBuilder
\fwrite($handle, "\n /**\n");
\fwrite($handle, " * {$title}\n");
\fwrite($handle, " *\n");
$params = [];
if (isset($properties['attr'])) {
\fwrite($handle, " * Parameters: \n");
$longest = [0, 0, 0];
\uasort($properties['attr'], fn (array $arr1, array $arr2) => $arr1['optional'] <=> $arr2['optional']);
foreach ($properties['attr'] as $name => $param) {
$longest[0] = \max($longest[0], \strlen($param['type']));
$longest[1] = \max($longest[1], \strlen($name));
$longest[2] = \max($longest[2], \strlen($param['description']));
}
foreach ($properties['attr'] as $name => $param) {
$param['type'] = \str_pad('`'.$param['type'].'`', $longest[0] + 2);
$name = \str_pad('**'.$name.'**', $longest[1] + 4);
$param['description'] = \str_pad($param['description'], $longest[2]);
\fwrite($handle, " * * {$param['type']} {$name} - {$param['description']}\n");
$param['type'] = $this->prepareTLType($param['type']);
$param_var = $param['type'].' $'.$name;
if ($param['optional']) {
$param_var .= ' = '.$this->prepareTLDefault($param['type']);
}
$params []= $param_var;
$param['description'] .= $this->prepareTLTypeDescription($param['type']);
\fwrite($handle, " * @param {$param['type']} \${$name} {$param['description']}\n");
}
\fwrite($handle, " * \n");
\fwrite($handle, " * @param array \$params Parameters\n");
\fwrite($handle, " *\n");
}
\fwrite($handle, " * @return {$properties['return']}\n");
$properties['return'] = $this->prepareTLType($properties['return']);
$properties['return'] .= $this->prepareTLTypeDescription($properties['return']);
\fwrite($handle, " * @return array\n");
\fwrite($handle, " */\n");
\fwrite($handle, " public function {$method}(");
if (isset($properties['attr'])) {
\fwrite($handle, '$params');
\fwrite($handle, \implode(', ', $params));
}
\fwrite($handle, ");\n");
}

View File

@ -29,7 +29,7 @@ use Generator;
/**
* Event handler.
*/
abstract class EventHandler extends InternalDoc
abstract class EventHandler extends AbstractAPI
{
use DbPropertiesTrait {
DbPropertiesTrait::initDb as private internalInitDb;
@ -76,6 +76,10 @@ abstract class EventHandler extends InternalDoc
$API->botLogin($token);
$API->startAndLoopInternal(static::class);
}
protected function reconnectFull(): bool
{
return true;
}
/**
* Internal constructor.
*

1864
src/InternalDoc.php Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1607,7 +1607,7 @@ final class MTProto implements TLCallback, LoggerGetter
$this->logger->logger($e->getMessage());
return false;
}
return $this->authorization['user'];
return $this->getSelf();
}
/**
* Get authorization info.
@ -1752,14 +1752,23 @@ final class MTProto implements TLCallback, LoggerGetter
}
return \array_merge($methods, \get_class_methods(InternalDoc::class));
}
/**
* @internal
*/
public function getMethodAfterResponseDeserializationCallbacks(): array
{
return [];
}
/**
* @internal
*/
public function getMethodBeforeResponseDeserializationCallbacks(): array
{
return [];
}
/**
* @internal
*/
public function getConstructorAfterDeserializationCallbacks(): array
{
return \array_merge(
@ -1770,14 +1779,23 @@ final class MTProto implements TLCallback, LoggerGetter
['config' => [$this->addConfig(...)]],
);
}
/**
* @internal
*/
public function getConstructorBeforeDeserializationCallbacks(): array
{
return [];
}
/**
* @internal
*/
public function getConstructorBeforeSerializationCallbacks(): array
{
return [];
}
/**
* @internal
*/
public function getTypeMismatchCallbacks(): array
{
return \array_merge(
@ -1818,6 +1836,9 @@ final class MTProto implements TLCallback, LoggerGetter
),
);
}
/**
* @internal
*/
public function areDeserializationCallbacksMutuallyExclusive(): bool
{
return false;

Some files were not shown because too many files have changed in this diff Show More