1
0
mirror of https://github.com/danog/MadelineProto.git synced 2024-12-02 23:07:45 +01:00

Merge branch 'v8' into v8

This commit is contained in:
Mahdi 2023-09-07 18:47:24 +00:00 committed by GitHub
commit 0e27befbf8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 363 additions and 147 deletions

View File

@ -248,7 +248,7 @@ Want to add your own open-source project to this list? [Click here!](https://doc
* [FAQ](https://docs.madelineproto.xyz/docs/FAQ.html) - Here's a list of common MadelineProto questions and answers.
* [Upgrading from MadelineProto v7 to v8](https://docs.madelineproto.xyz/docs/UPGRADING.html) - MadelineProto v8 is a major MadelineProto update, that removes a large number of long-deprecated APIs: I've created this upgrade checklist, to simplify the upgrade process.
* [Using methods](https://docs.madelineproto.xyz/docs/USING_METHODS.html) - There are simplifications for many, if not all of, these methods.
* [Named arguments (PHP 8+)](https://docs.madelineproto.xyz/docs/USING_METHODS.html#named-arguments)
* [Named arguments](https://docs.madelineproto.xyz/docs/USING_METHODS.html#named-arguments)
* [Peers](https://docs.madelineproto.xyz/docs/USING_METHODS.html#peers)
* [Files](https://docs.madelineproto.xyz/docs/FILES.html)
* [Secret chats](https://docs.madelineproto.xyz/docs/USING_METHODS.html#secret-chats)

2
docs

@ -1 +1 @@
Subproject commit 61cdff1c445fd53456b63d33a6feac12e4023514
Subproject commit dbfd462edb6a96c6bb1214b5def5e350a669100e

View File

@ -47,7 +47,7 @@ class MyEventHandler extends SimpleEventHandler
public function convertCmd((Incoming&Message&HasAudio)|(Incoming&Message&HasDocument) $message): void
{
$reply = $message->reply("Conversion in progress...");
try {
async(function () use ($message, $reply): void {
$pipe = self::getStreamPipe();
$sink = $pipe->getSink();
async(
@ -62,9 +62,7 @@ class MyEventHandler extends SimpleEventHandler
fileName: $message->media->fileName.".ogg",
replyToMsgId: $message->id
);
} finally {
$reply->delete();
}
})->finally($reply->delete(...));
}
}

View File

@ -908,6 +908,10 @@
<code>copy</code>
<code>unwrap</code>
</MissingReturnType>
<PossiblyUndefinedArrayOffset>
<code>$payload[0]</code>
<code>$payload[0]</code>
</PossiblyUndefinedArrayOffset>
<PropertyNotSetInConstructor>
<code>$data</code>
<code>Wrapper</code>
@ -1508,6 +1512,9 @@
<code>seek</code>
<code>seek</code>
</PossiblyUndefinedMethod>
<PossiblyUndefinedVariable>
<code>$l</code>
</PossiblyUndefinedVariable>
</file>
<file src="src/MTProtoTools/MinDatabase.php">
<MissingReturnType>

View File

@ -51,7 +51,7 @@ final class API extends AbstractAPI
*
* @var string
*/
public const RELEASE = '8.0.0-beta145';
public const RELEASE = '8.0.0-beta147';
/**
* Secret chat was not found.
*

View File

@ -24,6 +24,7 @@ use Amp\CancelledException;
use Amp\DeferredCancellation;
use danog\MadelineProto\Logger;
use danog\MadelineProto\MTProto;
use Revolt\EventLoop;
use Throwable;
use Webmozart\Assert\Assert;
@ -109,7 +110,7 @@ final class InternalState
}
private function notifyProgress(): void
{
$this->API->saveUpdate(['_' => 'updateBroadcastProgress', 'progress' => $this->getProgress()]);
EventLoop::queue($this->API->saveUpdate(...), ['_' => 'updateBroadcastProgress', 'progress' => $this->getProgress()]);
}
private function gatherPeers(): void
{

View File

@ -21,6 +21,8 @@ declare(strict_types=1);
namespace danog\MadelineProto;
use Amp\ByteStream\ClosedException;
use Amp\ByteStream\ReadableBuffer;
use Amp\ByteStream\ReadableStream;
use Amp\DeferredFuture;
use Amp\Sync\LocalMutex;
use AssertionError;
@ -35,10 +37,12 @@ use danog\MadelineProto\MTProtoSession\Session;
use danog\MadelineProto\Stream\BufferedStreamInterface;
use danog\MadelineProto\Stream\ConnectionContext;
use danog\MadelineProto\Stream\MTProtoBufferInterface;
use danog\MadelineProto\TL\Conversion\Extension;
use danog\MadelineProto\TL\Exception as TLException;
use Revolt\EventLoop;
use Webmozart\Assert\Assert;
use function Amp\ByteStream\buffer;
/**
* Connection class.
*
@ -105,6 +109,10 @@ final class Connection
* Whether we're currently reading an MTProto packet.
*/
private bool $reading = false;
/**
* Whether we're currently writing an MTProto packet.
*/
private bool $writing = false;
/**
* Logger instance.
*
@ -160,6 +168,7 @@ final class Connection
*/
public function writing(bool $writing): void
{
$this->writing = $writing;
$this->shared->writing($writing, $this->id);
}
/**
@ -177,6 +186,13 @@ final class Connection
{
return $this->reading;
}
/**
* Whether we're currently writing an MTProto packet.
*/
public function isWriting(): bool
{
return $this->writing;
}
/**
* Indicate a received HTTP response.
*/
@ -310,7 +326,7 @@ final class Connection
}
throw new AssertionError("Could not connect to DC {$this->datacenterId}!");
} finally {
$lock->release();
EventLoop::queue($lock->release(...));
}
}
/**
@ -362,16 +378,24 @@ final class Connection
}
}
if (\is_array($arguments['media']) && isset($arguments['media']['_'])) {
if ($arguments['media']['_'] === 'inputMediaPhotoExternal') {
$arguments['media']['_'] = 'inputMediaUploadedPhoto';
$arguments['media']['file'] = new RemoteUrl($arguments['media']['url']);
} elseif ($arguments['media']['_'] === 'inputMediaDocumentExternal') {
$arguments['media']['_'] = 'inputMediaUploadedDocument';
$arguments['media']['file'] = new RemoteUrl($arguments['media']['url']);
$arguments['media']['mime_type'] = Extension::getMimeFromExtension(
\pathinfo($arguments['media']['url'], PATHINFO_EXTENSION),
'application/octet-stream'
);
$this->API->processMedia($arguments['media']);
if ($arguments['media']['_'] === 'inputMediaUploadedPhoto'
&& (
$arguments['media']['file'] instanceof ReadableStream
|| (
$arguments['media']['file'] instanceof FileCallback
&& $arguments['media']['file']->file instanceof ReadableStream
)
)
) {
if ($arguments['media']['file'] instanceof FileCallback) {
$arguments['media']['file'] = new FileCallback(
new ReadableBuffer(buffer($arguments['media']['file']->file)),
$arguments['media']['file']->callback
);
} else {
$arguments['media']['file'] = new ReadableBuffer(buffer($arguments['media']['file']));
}
}
}
} elseif ($method === 'messages.sendMultiMedia') {

View File

@ -41,6 +41,7 @@ use danog\MadelineProto\Stream\MTProtoTransport\ObfuscatedStream;
use danog\MadelineProto\Stream\Transport\DefaultStream;
use danog\MadelineProto\Stream\Transport\WssStream;
use danog\MadelineProto\Stream\Transport\WsStream;
use Revolt\EventLoop;
/**
* @psalm-type TDcOption=array{
@ -387,7 +388,7 @@ final class DataCenter
$this->sockets[$dc]->setExtra($this->API, $dc, $ctxs);
$this->sockets[$dc]->connect();
} finally {
$lock->release();
EventLoop::queue($lock->release(...));
}
}
return $this->sockets[$dc];

View File

@ -197,7 +197,7 @@ final class DataCenterConnection implements JsonSerializable
$this->syncAuthorization();
}
} finally {
$lock->release();
EventLoop::queue($lock->release(...));
}
if ($this->hasTempAuthKey()) {
$connection->pingHttpWaiter();

View File

@ -157,7 +157,7 @@ final class CacheContainer
$this->ttl = $newTtl;
$this->cache = $newValues;
} finally {
$lock->release();
EventLoop::queue($lock->release(...));
}
}
}

View File

@ -24,6 +24,7 @@ use danog\MadelineProto\Logger;
use danog\MadelineProto\Settings\Database\Mysql as DatabaseMysql;
use PDO;
use PDOException;
use Revolt\EventLoop;
use Throwable;
/**
@ -79,7 +80,7 @@ final class Mysql
];
}
} finally {
$lock->release();
EventLoop::queue($lock->release(...));
}
return self::$connections[$dbKey];

View File

@ -21,6 +21,7 @@ use Amp\Postgres\PostgresConnectionPool;
use Amp\Sync\LocalKeyedMutex;
use danog\MadelineProto\Logger;
use danog\MadelineProto\Settings\Database\Postgres as DatabasePostgres;
use Revolt\EventLoop;
use Throwable;
/**
@ -51,7 +52,7 @@ final class Postgres
self::$connections[$dbKey] = new PostgresConnectionPool($config, $settings->getMaxConnections(), $settings->getIdleTimeout());
}
} finally {
$lock->release();
EventLoop::queue($lock->release(...));
}
return self::$connections[$dbKey];

View File

@ -21,6 +21,7 @@ use Amp\Redis\RedisClient;
use Amp\Redis\RedisConfig;
use Amp\Sync\LocalKeyedMutex;
use danog\MadelineProto\Settings\Database\Redis as DatabaseRedis;
use Revolt\EventLoop;
use function Amp\Redis\createRedisConnector;
@ -51,7 +52,7 @@ final class Redis
self::$connections[$dbKey]->ping();
}
} finally {
$lock->release();
EventLoop::queue($lock->release(...));
}
return self::$connections[$dbKey];

View File

@ -263,7 +263,7 @@ abstract class EventHandler extends AbstractAPI
} finally {
$this->startDeferred = null;
$startDeferred->complete();
$lock->release();
EventLoop::queue($lock->release(...));
}
}
/**

View File

@ -16,6 +16,7 @@
namespace danog\MadelineProto\EventHandler;
use danog\MadelineProto\EventHandler\Action\Cancel;
use danog\MadelineProto\EventHandler\Action\ChooseContact;
use danog\MadelineProto\EventHandler\Action\ChooseSticker;
use danog\MadelineProto\EventHandler\Action\EmojiSeen;
@ -56,7 +57,8 @@ abstract class Action implements JsonSerializable
}
return match ($type) {
'sendMessageTypingAction' => new Typing,
'sendMessageCancelAction' => new GamePlay,
'sendMessageCancelAction' => new Cancel,
'sendMessageGamePlayAction' => new GamePlay,
'sendMessageGeoLocationAction' => new GeoLocation,
'sendMessageChooseContactAction' => new ChooseContact,
'sendMessageChooseStickerAction' => new ChooseSticker,
@ -79,7 +81,8 @@ abstract class Action implements JsonSerializable
{
return match (true) {
$this instanceof Typing => [ '_' => 'sendMessageTypingAction' ],
$this instanceof GamePlay => [ '_' => 'sendMessageCancelAction' ],
$this instanceof Cancel => [ '_' => 'sendMessageCancelAction' ],
$this instanceof GamePlay => [ '_' => 'sendMessageGamePlayAction' ],
$this instanceof GeoLocation => [ '_' => 'sendMessageGeoLocationAction' ],
$this instanceof ChooseContact => [ '_' => 'sendMessageChooseContactAction' ],
$this instanceof ChooseSticker => [ '_' => 'sendMessageChooseStickerAction' ],

View File

@ -237,26 +237,19 @@ final class Client extends ClientAbstract
*/
public function methodCallAsyncRead(string $method, array $args = [], array $aargs = [])
{
if (($method === 'messages.editInlineBotMessage' ||
if ((
$method === 'messages.editInlineBotMessage' ||
$method === 'messages.uploadMedia' ||
$method === 'messages.sendMedia' ||
$method === 'messages.editMessage') &&
isset($args['media']['file']) &&
$args['media']['file'] instanceof FileCallbackInterface
) {
$params = [$method, &$args, $aargs];
$wrapper = Wrapper::create($params, $this->session, $this->logger);
$wrapper->wrap($args['media']['file'], true);
return $this->__call('methodCallAsyncRead', $wrapper);
$method === 'messages.editMessage'
) && isset($args['media']) && \is_array($args['media'])) {
$this->processMedia($args['media'], true);
} elseif ($method === 'messages.sendMultiMedia' && isset($args['multi_media'])) {
$params = [$method, &$args, $aargs];
$wrapper = Wrapper::create($params, $this->session, $this->logger);
foreach ($args['multi_media'] as &$media) {
if (isset($media['media']['file']) && $media['media']['file'] instanceof FileCallbackInterface) {
$wrapper->wrap($media['media']['file'], true);
if (\is_array($media['media'])) {
$this->processMedia($media['media'], true);
}
}
return $this->__call('methodCallAsyncRead', $wrapper);
}
return $this->__call('methodCallAsyncRead', [$method, $args, $aargs]);
}

View File

@ -214,14 +214,17 @@ class Server extends Loop
} catch (Throwable $e) {
Logger::log("Exception in IPC connection: $e");
} finally {
try {
$socket->disconnect();
} catch (Throwable $e) {
}
if ($payload === self::SHUTDOWN) {
Shutdown::removeCallback('restarter');
$this->stop();
}
EventLoop::queue(function () use ($socket, $payload): void {
try {
$socket->disconnect();
} catch (Throwable $e) {
Logger::log("Exception during shutdown in IPC connection: $e");
}
if ($payload === self::SHUTDOWN) {
Shutdown::removeCallback('restarter');
$this->stop();
}
});
}
}
/**
@ -243,10 +246,13 @@ class Server extends Loop
$result = new ExitFailure($e);
} finally {
if (isset($wrapper)) {
try {
$wrapper->disconnect();
} catch (Throwable $e) {
}
EventLoop::queue(function () use ($wrapper): void {
try {
$wrapper->disconnect();
} catch (Throwable $e) {
Logger::log("Exception during shutdown in IPC connection: $e");
}
});
}
}
try {

View File

@ -20,6 +20,7 @@ use Amp\ByteStream\ReadableStream as ByteStreamReadableStream;
use Amp\ByteStream\WritableStream as ByteStreamWritableStream;
use Amp\Cancellation;
use Amp\Ipc\Sync\ChannelledSocket;
use danog\MadelineProto\FileCallback as MadelineProtoFileCallback;
use danog\MadelineProto\FileCallbackInterface;
use danog\MadelineProto\Ipc\Wrapper\Cancellation as WrapperCancellation;
use danog\MadelineProto\Ipc\Wrapper\FileCallback;
@ -107,6 +108,13 @@ final class Wrapper extends ClientAbstract
public function wrap(mixed &$callback, bool $wrapObjects = true): void
{
if (\is_object($callback) && $wrapObjects) {
if ($callback instanceof FileCallbackInterface) {
$file = $callback->getFile();
if ($file instanceof ByteStreamReadableStream) {
$this->wrap($file, true);
$callback = new MadelineProtoFileCallback($file, $callback);
}
}
$ids = [];
foreach (\get_class_methods($callback) as $method) {
$id = $this->id++;
@ -115,9 +123,9 @@ final class Wrapper extends ClientAbstract
}
$class = null;
if ($callback instanceof ByteStreamReadableStream) {
$class = \method_exists($callback, 'seek') ? ReadableStream::class : SeekableReadableStream::class;
$class = \method_exists($callback, 'seek') ? SeekableReadableStream::class : ReadableStream::class;
} elseif ($callback instanceof ByteStreamWritableStream) {
$class = \method_exists($callback, 'seek') ? WritableStream::class : SeekableWritableStream::class;
$class = \method_exists($callback, 'seek') ? SeekableWritableStream::class : WritableStream::class;
} elseif ($callback instanceof FileCallbackInterface) {
$class = FileCallback::class;
} elseif ($callback instanceof Cancellation) {
@ -154,7 +162,7 @@ final class Wrapper extends ClientAbstract
EventLoop::queue($this->clientRequest(...), $id++, $payload);
}
} finally {
$this->server->disconnect();
EventLoop::queue($this->server->disconnect(...));
}
}
@ -175,11 +183,11 @@ final class Wrapper extends ClientAbstract
try {
$this->server->send([$id, $result]);
} catch (Throwable $e) {
$this->logger->logger("Got error while trying to send result of reverse method: $e", Logger::ERROR);
$this->logger->logger("Got error while trying to send result of reverse method {$payload[0]}: $e", Logger::ERROR);
try {
$this->server->send([$id, new ExitFailure($e)]);
} catch (Throwable $e) {
$this->logger->logger("Got error while trying to send error of error of reverse method: $e", Logger::ERROR);
$this->logger->logger("Got error while trying to send error of error of reverse method {$payload[0]}: $e", Logger::ERROR);
}
}
}

View File

@ -62,6 +62,6 @@ trait ClosableTrait
final public function __destruct()
{
$this->close();
EventLoop::queue($this->close(...));
}
}

View File

@ -132,9 +132,9 @@ final class CheckLoop extends Loop
}
}
$this->connection->flush();
} catch (CancelledException) {
$this->logger->logger("We did not receive a response for {$this->timeout} seconds: reconnecting and exiting check loop on DC {$this->datacenter}");
EventLoop::queue($this->connection->reconnect(...));
//} catch (CancelledException) {
//$this->logger->logger("We did not receive a response for {$this->timeout} seconds: reconnecting and exiting check loop on DC {$this->datacenter}");
//EventLoop::queue($this->connection->reconnect(...));
} catch (\Throwable $e) {
$this->logger->logger("Got exception in check loop for DC {$this->datacenter}");
$this->logger->logger((string) $e);

View File

@ -64,6 +64,7 @@ final class ReadLoop extends Loop
if ($e instanceof NothingInTheSocketException
&& !$this->connection->hasPendingCalls()
&& $this->connection->isMedia()
&& !$this->connection->isWriting()
) {
$this->logger->logger("Got NothingInTheSocketException in DC {$this->datacenter}, disconnecting because we have nothing to do...", Logger::ERROR);
$this->connection->disconnect(true);

View File

@ -211,7 +211,7 @@ final class DjLoop extends VoIPLoop
Ogg::convert($f, $pipe->getSink(), $cancellation);
} catch (CancelledException) {
} finally {
$pipe->getSink()->close();
EventLoop::queue($pipe->getSink()->close(...));
}
});
$it = new Ogg($pipe->getSource());

View File

@ -1670,7 +1670,7 @@ final class MTProto implements TLCallback, LoggerGetter
$this->logger->logger('Reported!');
}
} finally {
$lock->release();
EventLoop::queue($lock->release(...));
}
}
/**

View File

@ -130,7 +130,7 @@ trait CallHandler
if (isset($args['message']) && \is_string($args['message']) && \mb_strlen($args['message'], 'UTF-8') > ($this->API->getConfig())['message_length_max'] && \mb_strlen($this->API->parseMode($args)['message'], 'UTF-8') > ($this->API->getConfig())['message_length_max']) {
$args = $this->API->splitToChunks($args);
$promises = [];
$aargs['queue'] = $method;
$aargs['queue'] = $method.' '.\time();
$aargs['multiple'] = true;
}
if (isset($aargs['multiple'])) {

View File

@ -312,6 +312,17 @@ trait ResponseHandler
}
EventLoop::queue(closure: $this->methodRecall(...), message_id: $request->getMsgId(), datacenter: $datacenter);
return null;
case 400:
if ($request->hasQueue() &&
(
$response['error_message'] === 'MSG_WAIT_FAILED'
|| $response['error_message'] === 'MSG_WAIT_TIMEOUT'
)
) {
EventLoop::queue(closure: $this->methodRecall(...), message_id: $request->getMsgId());
return null;
}
return fn () => new RPCErrorException($response['error_message'], $response['error_code'], $request->getConstructor());
case 401:
switch ($response['error_message']) {
case 'USER_DEACTIVATED':

View File

@ -35,6 +35,7 @@ use danog\MadelineProto\Settings;
use danog\MadelineProto\Settings\AppInfo;
use danog\MadelineProto\Tools;
use Exception;
use Revolt\EventLoop;
use Throwable;
use function Amp\File\exists;
@ -216,7 +217,7 @@ trait FileServer
$this->checkDownloadScript($f);
return self::$checkedAutoload[$autoloadPath] = $f;
} finally {
$lock->release();
EventLoop::queue($lock->release(...));
}
}
@ -249,7 +250,7 @@ trait FileServer
self::$checkedScripts[$scriptUrl] = true;
} finally {
$lock->release();
EventLoop::queue($lock->release(...));
}
}
}

View File

@ -23,7 +23,21 @@ namespace danog\MadelineProto\MTProtoTools;
use Amp\DeferredFuture;
use Amp\Future;
use Amp\Http\Client\Request;
use AssertionError;
use danog\MadelineProto\EventHandler\Media;
use danog\MadelineProto\EventHandler\Media\AnimatedSticker;
use danog\MadelineProto\EventHandler\Media\Audio;
use danog\MadelineProto\EventHandler\Media\CustomEmoji;
use danog\MadelineProto\EventHandler\Media\Document;
use danog\MadelineProto\EventHandler\Media\DocumentPhoto;
use danog\MadelineProto\EventHandler\Media\Gif;
use danog\MadelineProto\EventHandler\Media\MaskSticker;
use danog\MadelineProto\EventHandler\Media\Photo;
use danog\MadelineProto\EventHandler\Media\RoundVideo;
use danog\MadelineProto\EventHandler\Media\StaticSticker;
use danog\MadelineProto\EventHandler\Media\Video;
use danog\MadelineProto\EventHandler\Media\VideoSticker;
use danog\MadelineProto\EventHandler\Media\Voice;
use danog\MadelineProto\EventHandler\Message;
use danog\MadelineProto\Exception;
use danog\MadelineProto\FileCallbackInterface;
@ -57,8 +71,88 @@ use function Amp\Future\awaitFirst;
trait Files
{
use FilesLogic;
use FilesAbstraction;
use FileServer;
/**
* Wrap a media constructor into an abstract Media object.
*/
public function wrapMedia(array $media, bool $protected = false): ?Media
{
if ($media['_'] === 'messageMediaPhoto') {
if (!isset($media['photo'])) {
return null;
}
return new Photo($this, $media, $protected);
}
if ($media['_'] !== 'messageMediaDocument') {
return null;
}
if (!isset($media['document'])) {
return null;
}
$has_video = null;
$has_document_photo = null;
$has_animated = false;
foreach ($media['document']['attributes'] as $attr) {
$t = $attr['_'];
if ($t === 'documentAttributeImageSize') {
$has_document_photo = $attr;
continue;
}
if ($t === 'documentAttributeAnimated') {
$has_animated = true;
continue;
}
if ($t === 'documentAttributeSticker') {
if ($has_video) {
return new VideoSticker($this, $media, $attr, $has_video, $protected);
}
if ($has_document_photo === null) {
throw new AssertionError("has_document_photo === null: ".\json_encode($media['document']));
}
if ($attr['mask']) {
return new MaskSticker($this, $media, $attr, $has_document_photo, $protected);
}
if ($media['document']['mime_type'] === 'application/x-tgsticker') {
return new AnimatedSticker($this, $media, $attr, $has_document_photo, $protected);
}
return new StaticSticker($this, $media, $attr, $has_document_photo, $protected);
}
if ($t === 'documentAttributeVideo') {
$has_video = $attr;
continue;
}
if ($t === 'documentAttributeAudio') {
return $attr['voice']
? new Voice($this, $media, $attr, $protected)
: new Audio($this, $media, $attr, $protected);
}
if ($t === 'documentAttributeCustomEmoji') {
if ($has_document_photo === null) {
throw new AssertionError("has_document_photo === null: ".\json_encode($media['document']));
}
return new CustomEmoji($this, $media, $attr, $has_document_photo, $protected);
}
}
if ($has_animated) {
if ($has_video === null) {
throw new AssertionError("has_video === null: ".\json_encode($media['document']));
}
return new Gif($this, $media, $has_video, $protected);
}
if ($has_video) {
return $has_video['round_message']
? new RoundVideo($this, $media, $has_video, $protected)
: new Video($this, $media, $has_video, $protected);
}
if ($has_document_photo) {
return new DocumentPhoto($this, $media, $has_document_photo, $protected);
}
return new Document($this, $media, $protected);
}
/**
* Upload file from URL.
*
@ -859,16 +953,14 @@ trait Files
$this->logger->logger('Waiting for lock of file to download...');
$unlock = Tools::flock("$file.lock", LOCK_EX);
$this->logger->logger('Got lock of file to download');
try {
$this->downloadToStream($messageMedia, $stream, $cb, $size, -1);
} finally {
async($this->downloadToStream(...), $messageMedia, $stream, $cb, $size, -1)->finally(function () use ($stream, $unlock, $file): void {
$stream->close();
$unlock();
try {
deleteFile("$file.lock");
} catch (Throwable) {
} catch (\Throwable) {
}
$stream->close();
}
})->await();
return $file;
}
/**

View File

@ -24,19 +24,8 @@ use Amp\ByteStream\ReadableStream;
use AssertionError;
use danog\MadelineProto\BotApiFileId;
use danog\MadelineProto\EventHandler\Media;
use danog\MadelineProto\EventHandler\Media\AnimatedSticker;
use danog\MadelineProto\EventHandler\Media\Audio;
use danog\MadelineProto\EventHandler\Media\CustomEmoji;
use danog\MadelineProto\EventHandler\Media\Document;
use danog\MadelineProto\EventHandler\Media\DocumentPhoto;
use danog\MadelineProto\EventHandler\Media\Gif;
use danog\MadelineProto\EventHandler\Media\MaskSticker;
use danog\MadelineProto\EventHandler\Media\Photo;
use danog\MadelineProto\EventHandler\Media\RoundVideo;
use danog\MadelineProto\EventHandler\Media\StaticSticker;
use danog\MadelineProto\EventHandler\Media\Video;
use danog\MadelineProto\EventHandler\Media\VideoSticker;
use danog\MadelineProto\EventHandler\Media\Voice;
use danog\MadelineProto\EventHandler\Message;
use danog\MadelineProto\FileCallback;
use danog\MadelineProto\LocalFile;

View File

@ -17,6 +17,7 @@
namespace danog\MadelineProto\MTProtoTools;
use Amp\ByteStream\Pipe;
use Amp\ByteStream\ReadableBuffer;
use Amp\ByteStream\ReadableResourceStream;
use Amp\ByteStream\ReadableStream;
use Amp\ByteStream\StreamException;
@ -34,6 +35,7 @@ use danog\MadelineProto\BotApiFileId;
use danog\MadelineProto\EventHandler\Media;
use danog\MadelineProto\EventHandler\Message;
use danog\MadelineProto\Exception;
use danog\MadelineProto\FileCallback;
use danog\MadelineProto\FileCallbackInterface;
use danog\MadelineProto\Lang;
use danog\MadelineProto\LocalFile;
@ -55,6 +57,7 @@ use Webmozart\Assert\Assert;
use const FILTER_VALIDATE_URL;
use function Amp\async;
use function Amp\ByteStream\buffer;
use function Amp\File\exists;
use function Amp\File\getSize;
@ -67,6 +70,7 @@ use function Amp\File\openFile;
*/
trait FilesLogic
{
use FilesAbstraction;
/**
* Download file to browser.
*
@ -139,13 +143,7 @@ trait FilesLogic
{
$pipe = new Pipe(1024*1024);
$sink = $pipe->getSink();
EventLoop::queue(function () use ($messageMedia, $sink, $cb, $offset, $end): void {
try {
$this->downloadToStream($messageMedia, $sink, $cb, $offset, $end);
} finally {
$sink->close();
}
});
async($this->downloadToStream(...), $messageMedia, $sink, $cb, $offset, $end)->finally($sink->close(...));
return $pipe->getSource();
}
/**
@ -191,7 +189,7 @@ trait FilesLogic
}
$stream->write($payload);
} finally {
$l->release();
EventLoop::queue($l->release(...));
}
return \strlen($payload);
};
@ -260,6 +258,42 @@ trait FilesLogic
return $this->upload($file, $fileName, $cb, true);
}
/**
* @internal
*/
public function processMedia(array &$media, bool $upload = false): void
{
if ($media['_'] === 'inputMediaPhotoExternal') {
$media['_'] = 'inputMediaUploadedPhoto';
if ($media['url'] instanceof FileCallbackInterface) {
$media['file'] = new FileCallback(
new RemoteUrl($media['url']->getFile()),
$media['url']
);
} else {
$media['file'] = new RemoteUrl($media['url']);
}
unset($media['url']);
} elseif ($media['_'] === 'inputMediaDocumentExternal') {
$media['_'] = 'inputMediaUploadedDocument';
if ($media['url'] instanceof FileCallbackInterface) {
$media['file'] = new FileCallback(
new RemoteUrl($url = $media['url']->getFile()),
$media['url']
);
} else {
$media['file'] = new RemoteUrl($url = $media['url']);
}
unset($media['url']);
$media['mime_type'] = Extension::getMimeFromExtension(
\pathinfo($url, PATHINFO_EXTENSION),
'application/octet-stream'
);
}
if ($upload) {
$media['file'] = $this->upload($media['file']);
}
}
/**
* Upload file.
*
@ -316,11 +350,7 @@ trait FilesLogic
}
$stream = openFile($file, 'rb');
$mime = Extension::getMimeFromFile($file);
try {
return $this->uploadFromStream($stream, $size, $mime, $fileName, $cb, $encrypted);
} finally {
$stream->close();
}
return async($this->uploadFromStream(...), $stream, $size, $mime, $fileName, $cb, $encrypted)->finally($stream->close(...))->await();
}
/**
@ -356,6 +386,17 @@ trait FilesLogic
}
}
$created = false;
if (!$size) {
if ($seekable && \method_exists($stream, 'tell')) {
$stream->seek(0, Whence::End);
$size = $stream->tell();
$stream->seek(0);
} elseif ($stream instanceof ReadableBuffer) {
$stream = buffer($stream);
$size = \strlen($stream);
$stream = new ReadableBuffer($stream);
}
}
if ($stream instanceof File) {
$lock = new LocalMutex;
$callable = static function (int $offset, int $size) use ($stream, $seekable, $lock) {
@ -369,7 +410,7 @@ trait FilesLogic
}
return $stream->read(null, $size);
} finally {
$l->release();
EventLoop::queue($l->release(...));
}
};
} else {
@ -392,11 +433,6 @@ trait FilesLogic
};
$seekable = false;
}
if (!$size && $seekable && \method_exists($stream, 'tell')) {
$stream->seek(0, Whence::End);
$size = $stream->tell();
$stream->seek(0);
}
$res = $this->uploadFromCallable($callable, $size, $mime, $fileName, $cb, $seekable, $encrypted);
if ($created) {
/** @var StreamInterface $stream */

View File

@ -220,7 +220,7 @@ final class MinDatabase implements TLCallback
}
} finally {
unset($this->pendingDb[$id]);
$lock->release();
EventLoop::queue($lock->release(...));
}
}
public function populateFrom(array $object)

View File

@ -289,7 +289,7 @@ final class PeerDatabase implements TLCallback
$this->usernames[$username] = $id;
}
} finally {
$lock->release();
EventLoop::queue($lock->release(...));
}
}
@ -450,7 +450,7 @@ final class PeerDatabase implements TLCallback
if (isset($o) && $this->pendingDb[$id] === $o) {
unset($this->pendingDb[$id]);
}
$lock->release();
EventLoop::queue($lock->release(...));
}
}
public function addChatBlocking(int $chat): void
@ -584,7 +584,7 @@ final class PeerDatabase implements TLCallback
if (isset($o) && $this->pendingDb[$id] === $o) {
unset($this->pendingDb[$id]);
}
$lock->release();
EventLoop::queue($lock->release(...));
}
}
/**

View File

@ -148,7 +148,7 @@ final class ReferenceDatabase implements TLCallback
$this->db[$location] = $locationValue;
} finally {
unset($this->pendingDb[$location]);
$lock->release();
EventLoop::queue($lock->release(...));
}
}
public function getMethodAfterResponseDeserializationCallbacks(): array

View File

@ -308,6 +308,9 @@ final class Magic
throw Exception::extension($extension);
}
}
if (\extension_loaded('psr')) {
throw new Exception("Please uninstall the psr extension to use MadelineProto!");
}
self::$BIG_ENDIAN = \pack('L', 1) === \pack('N', 1);
self::$hasOpenssl = \extension_loaded('openssl');
self::$emojis = \json_decode(self::JSON_EMOJIS);

View File

@ -981,12 +981,6 @@ final class TL implements TLInterface
case 'true':
$x[$arg['name']] = ($x[$arg['flag']] & $arg['pow']) !== 0;
continue 2;
case 'Bool':
if (($x[$arg['flag']] & $arg['pow']) === 0) {
$x[$arg['name']] = false;
continue 2;
}
// no break
default:
if (($x[$arg['flag']] & $arg['pow']) === 0) {
continue 2;

View File

@ -705,6 +705,9 @@ abstract class Tools extends AsyncTools
*/
public static function validateEventHandlerClass(string $class): array
{
if (!\extension_loaded('tokenizer')) {
throw \danog\MadelineProto\Exception::extension('tokenizer');
}
$plugin = \is_subclass_of($class, PluginEventHandler::class);
$file = (new ReflectionClass($class))->getFileName();
$code = read($file);
@ -754,6 +757,14 @@ abstract class Tools extends AsyncTools
$name = $call->name->toLowerString();
if (isset(self::BLOCKING_FUNCTIONS[$name])) {
if ($name === 'fopen' &&
isset($call->args[0]) &&
$call->args[0] instanceof Arg &&
$call->args[0]->value instanceof String_ &&
\str_starts_with($call->args[0]->value->value, 'php://memory')
) {
continue;
}
$explanation = self::BLOCKING_FUNCTIONS[$name];
$issues []= new EventHandlerIssue(
message: \sprintf(Lang::$current_lang['do_not_use_blocking_function'], $name, $explanation),

View File

@ -25,6 +25,7 @@ use danog\MadelineProto\API;
use danog\MadelineProto\Exception;
use danog\MadelineProto\Settings;
use danog\MadelineProto\Tools;
use Revolt\EventLoop;
use Throwable;
use Webmozart\Assert\Assert;
@ -93,7 +94,7 @@ trait DialogHandler
}
$this->cachedAllBotUsers = true;
} finally {
$lock->release();
EventLoop::queue($lock->release(...));
}
}
private function searchRightPts(): void
@ -188,7 +189,7 @@ trait DialogHandler
try {
$this->getFullDialogsInternal(false);
} finally {
$lock->release();
EventLoop::queue($lock->release(...));
}
return true;
}

View File

@ -15,7 +15,9 @@ If not, see <http://www.gnu.org/licenses/>.
* Various ways to load MadelineProto.
*/
use Amp\ByteStream\ReadableBuffer;
use danog\MadelineProto\API;
use danog\MadelineProto\FileCallback;
use danog\MadelineProto\Logger;
use danog\MadelineProto\Settings;
use danog\MadelineProto\VoIP;
@ -84,16 +86,6 @@ $MadelineProto = new API(__DIR__.'/../testing.madeline', $settings);
$MadelineProto->start();
$MadelineProto->fileGetContents('https://google.com');
try {
$MadelineProto->getSelf();
} catch (\danog\MadelineProto\Exception $e) {
if ($e->getMessage() === 'TOS action required, check the logs') {
$MadelineProto->acceptTos();
}
}
//var_dump(count($MadelineProto->getPwrChat('@madelineproto')['participants']));
/*
* Test logging
*/
@ -260,7 +252,7 @@ $media = [];
$media['photo'] = ['_' => 'inputMediaUploadedPhoto', 'file' => __DIR__.'/faust.jpg'];
// Image by URL
$media['photo'] = ['_' => 'inputMediaPhotoExternal', 'url' => 'https://github.com/danog/MadelineProto/raw/v8/tests/faust.jpg'];
$media['photo_url'] = ['_' => 'inputMediaPhotoExternal', 'url' => 'https://github.com/danog/MadelineProto/raw/v8/tests/faust.jpg'];
// Sticker
$media['sticker'] = ['_' => 'inputMediaUploadedDocument', 'file' => __DIR__.'/lel.webp', 'attributes' => [['_' => 'documentAttributeSticker', 'alt' => 'LEL']]];
@ -278,7 +270,7 @@ $media['voice'] = ['_' => 'inputMediaUploadedDocument', 'file' => __DIR__.'/mosc
$media['document'] = ['_' => 'inputMediaUploadedDocument', 'file' => __DIR__.'/60', 'mime_type' => 'magic/magic', 'attributes' => [['_' => 'documentAttributeFilename', 'file_name' => 'magic.magic']]];
// Document by URL
$media['document'] = ['_' => 'inputMediaDocumentExternal', 'url' => 'https://github.com/danog/MadelineProto/raw/v8/tests/60'];
$media['document_url'] = ['_' => 'inputMediaDocumentExternal', 'url' => 'https://github.com/danog/MadelineProto/raw/v8/tests/60'];
$message = 'yay '.PHP_VERSION_ID;
$mention = $MadelineProto->getInfo(getenv('TEST_USERNAME')); // Returns an array with all of the constructors that can be extracted from a username or an id
@ -288,6 +280,60 @@ $peers = json_decode(getenv('TEST_DESTINATION_GROUPS'), true);
if (!$peers) {
die("No TEST_DESTINATION_GROUPS array was provided!");
}
foreach ($media as &$inputMedia) {
$inputMedia['content'] = isset($inputMedia['file'])
? read($inputMedia['file'])
: $MadelineProto->fileGetContents($inputMedia['url']);
}
function eq(string $file, string $contents, string $type): void
{
if ($type !== 'photo' && $type !== 'photo_url') {
Assert::eq(read($file), $contents, "Not equal $type!");
}
}
function sendMedia(API $MadelineProto, array $media, string $message, string $mention, mixed $peer, string $type): void
{
$medias = [
'base' => $media
];
/*if (isset($media['file']) && is_string($media['file'])) {
$MadelineProto->sendDocument(
peer: $peer,
file: new ReadableBuffer(read($media['file'])),
callback: fn ($v) => $MadelineProto->logger($v),
fileName: basename($media['file'])
);
$medias['callback'] = array_merge(
$media,
['file' => new FileCallback($media['file'], fn ($v) => $MadelineProto->logger(...))]
);
$medias['stream'] = array_merge(
$media,
['file' => new ReadableBuffer(read($media['file']))]
);
$medias['callback stream'] = array_merge(
$media,
['file' => new FileCallback(new ReadableBuffer(read($media['file'])), fn ($v) => $MadelineProto->logger(...))]
);
} elseif (isset($media['url'])) {
$medias['callback'] = array_merge(
$media,
['url' => new FileCallback($media['url'], fn ($v) => $MadelineProto->logger(...))]
);
}*/
foreach ($medias as $subtype => $m) {
$MadelineProto->logger("Sending $type $subtype");
$dl = $MadelineProto->extractMessage($MadelineProto->messages->sendMedia(['peer' => $peer, 'media' => $m, 'message' => '['.$message.'](mention:'.$mention.')', 'parse_mode' => 'markdown']));
$MadelineProto->logger("Downloading $type $subtype");
$file = $MadelineProto->downloadToDir($dl, '/tmp');
eq($file, $m['content'], $type);
}
}
foreach ($peers as $peer) {
$sentMessage = $MadelineProto->messages->sendMessage(['peer' => $peer, 'message' => $message, 'entities' => [['_' => 'inputMessageEntityMentionName', 'offset' => 0, 'length' => mb_strlen($message), 'user_id' => $mention]]]);
$MadelineProto->logger($sentMessage, Logger::NOTICE);
@ -303,26 +349,15 @@ foreach ($peers as $peer) {
['_' => 'inputSingleMedia', 'media' => $inputMedia, 'message' => '['.$message.'](mention:'.$mention.')', 'parse_mode' => 'markdown'],
]]);
}
$fileOrig = isset($inputMedia['file'])
? read($inputMedia['file'])
: $MadelineProto->fileGetContents($inputMedia['url']);
$MadelineProto->logger("Sending $type");
$dl = $MadelineProto->extractMessage($MadelineProto->messages->sendMedia(['peer' => $peer, 'media' => $inputMedia, 'message' => '['.$message.'](mention:'.$mention.')', 'parse_mode' => 'markdown']));
$MadelineProto->logger("Downloading $type");
$file = $MadelineProto->downloadToDir($dl, '/tmp');
if ($type !== 'photo') {
Assert::eq(read($file), $fileOrig, "Not equal!");
}
sendMedia($MadelineProto, $inputMedia, $message, $mention, $peer, $type);
$MadelineProto->logger("Uploading $type");
$media = $MadelineProto->messages->uploadMedia(['peer' => '@me', 'media' => $inputMedia]);
$MadelineProto->logger("Downloading $type");
$file = $MadelineProto->downloadToDir($media, '/tmp');
if ($type !== 'photo') {
Assert::eq(read($file), $fileOrig, "Not equal!");
}
eq($file, $inputMedia['content'], $type);
$MadelineProto->logger("Re-sending $type");
$inputMedia['file'] = $media;
@ -331,9 +366,7 @@ foreach ($peers as $peer) {
$MadelineProto->logger("Re-downloading $type");
$file = $MadelineProto->downloadToDir($dl, '/tmp');
if ($type !== 'photo') {
Assert::eq(read($file), $fileOrig, "Not equal!");
}
eq($file, $inputMedia['content'], $type);
}
}