1
0
mirror of https://github.com/danog/MadelineProto.git synced 2024-12-02 13:37:47 +01:00
This commit is contained in:
Daniil Gentili 2023-07-14 20:15:04 +02:00
parent a2270bae9e
commit 60a7afb6a8
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
19 changed files with 51 additions and 34 deletions

View File

@ -2,6 +2,8 @@ Introducing MadelineProto's biggest update yet, 8.0.0-beta100!
This version introduces [plugins](https://docs.madelineproto.xyz/docs/PLUGINS.html), [bound methods](https://docs.madelineproto.xyz/docs/UPDATES.html#bound-methods), [filters](https://docs.madelineproto.xyz/docs/FILTERS.html), [a built-in cron system](https://docs.madelineproto.xyz/docs/UPDATES.html#cron), [IPC support for the event handler](https://docs.madelineproto.xyz/docs/UPDATES.html#persisting-data-and-ipc) and automatic static analysis for event handler code. This version introduces [plugins](https://docs.madelineproto.xyz/docs/PLUGINS.html), [bound methods](https://docs.madelineproto.xyz/docs/UPDATES.html#bound-methods), [filters](https://docs.madelineproto.xyz/docs/FILTERS.html), [a built-in cron system](https://docs.madelineproto.xyz/docs/UPDATES.html#cron), [IPC support for the event handler](https://docs.madelineproto.xyz/docs/UPDATES.html#persisting-data-and-ipc) and automatic static analysis for event handler code.
- [Plugins](https://docs.madelineproto.xyz/docs/PLUGINS.html)
To create a plugin, simply create an event handler that extends PluginEventHandler. To create a plugin, simply create an event handler that extends PluginEventHandler.
For example, create a `plugins/Danogentili/PingPlugin.php` file: For example, create a `plugins/Danogentili/PingPlugin.php` file:
@ -44,10 +46,16 @@ And use a [plugin base](https://raw.githubusercontent.com/danog/MadelineProto/v8
See the [documentation](https://docs.madelineproto.xyz/docs/PLUGINS.html) for more info on how to create MadelineProto plugins! See the [documentation](https://docs.madelineproto.xyz/docs/PLUGINS.html) for more info on how to create MadelineProto plugins!
- [Message](https://docs.madelineproto.xyz/PHP/danog/MadelineProto/EventHandler/Message.html) objects with bound methods
Both plugins and normal bots can make use of [bound update methods](https://docs.madelineproto.xyz/docs/UPDATES.html#bound-methods) like `reply()`, `delete()`, `getReply()`, `getHTML()` and simplified properties like `chatId`, `senderId`, `command`, `commandArgs` and many more, see the [documentation](https://docs.madelineproto.xyz/docs/UPDATES.html#bound-methods) for more info! Both plugins and normal bots can make use of [bound update methods](https://docs.madelineproto.xyz/docs/UPDATES.html#bound-methods) like `reply()`, `delete()`, `getReply()`, `getHTML()` and simplified properties like `chatId`, `senderId`, `command`, `commandArgs` and many more, see the [documentation](https://docs.madelineproto.xyz/docs/UPDATES.html#bound-methods) for more info!
- [Filters](https://docs.madelineproto.xyz/docs/FILTERS.html)
Plugins and bots can now use three different filtering systems, to easily receive only updates satisfying certain conditions (incoming/outgoing, from group, channel, private, from an admin or a specific peer, with an audio/sticker/..., satisfying a certain regex or a certain /command, and much more!), [see the documentation](https://docs.madelineproto.xyz/docs/FILTERS.html) for more info! Plugins and bots can now use three different filtering systems, to easily receive only updates satisfying certain conditions (incoming/outgoing, from group, channel, private, from an admin or a specific peer, with an audio/sticker/..., satisfying a certain regex or a certain /command, and much more!), [see the documentation](https://docs.madelineproto.xyz/docs/FILTERS.html) for more info!
- [Built-in cron system](https://docs.madelineproto.xyz/docs/UPDATES.html#cron)
All event handler methods marked by the `Cron` attribute are now automatically invoked by MadelineProto every `period` seconds: All event handler methods marked by the `Cron` attribute are now automatically invoked by MadelineProto every `period` seconds:
``` ```
@ -68,10 +76,17 @@ class MyEventHandler extends SimpleEventHandler
See the [documentation](https://docs.madelineproto.xyz/docs/UPDATES.html#cron) for more info! See the [documentation](https://docs.madelineproto.xyz/docs/UPDATES.html#cron) for more info!
- [IPC support for the event handler](https://docs.madelineproto.xyz/docs/UPDATES.html#persisting-data-and-ipc)
You can now call event handler and plugin methods from outside of the event handler, using `getEventHandler()` on an `API` instance, see [the docs for more info](https://docs.madelineproto.xyz/docs/PLUGINS.html#limitations)!
- Automatic static analysis of event handler code
Finally, all new bots and plugins will be automatically analyzed by MadelineProto, blocking execution if performance or security issues are detected! Finally, all new bots and plugins will be automatically analyzed by MadelineProto, blocking execution if performance or security issues are detected!
Other features: Other features:
- Thanks to the many translation contributors @ https://weblate.madelineproto.xyz/, MadelineProto is now localized in Hebrew, Persian, Kurdish, Uzbek, Russian, French and Italian! - Thanks to the many translation contributors @ https://weblate.madelineproto.xyz/, MadelineProto is now localized in Hebrew, Persian, Kurdish, Uzbek, Russian, French and Italian!
- Added simplified `sendMessage`, `sendDocument`, `sendPhoto` methods that return abstract [Message](https://docs.madelineproto.xyz/PHP/danog/MadelineProto/EventHandler/Message.html) objects with simplified properties and bound methods!
- You can now use `Tools::callFork` to fork a new green thread! - You can now use `Tools::callFork` to fork a new green thread!
- You can now automatically pin messages broadcasted using `broadcastMessages`, `broadcastForwardMessages` by using the new `pin: true` parameter! - You can now automatically pin messages broadcasted using `broadcastMessages`, `broadcastForwardMessages` by using the new `pin: true` parameter!
- You can now use `sendMessageToAdmins` to send messages to the bot's admin (the peers returned by `getReportPeers`). - You can now use `sendMessageToAdmins` to send messages to the bot's admin (the peers returned by `getReportPeers`).
@ -84,10 +99,6 @@ Other features:
- You can now use `reportMemoryProfile()` to generate and send a `pprof` memory profile to all report peers to debug the causes of high memory usage. - You can now use `reportMemoryProfile()` to generate and send a `pprof` memory profile to all report peers to debug the causes of high memory usage.
- Added support for `pay`, `login_url`, `web_app` and `tg://user?id=` buttons in bot API syntax! - Added support for `pay`, `login_url`, `web_app` and `tg://user?id=` buttons in bot API syntax!
- Added a `getAdminIds` function that returns the IDs of the admin of the bot (equal to the peers returned by getReportPeers in the event handler). - Added a `getAdminIds` function that returns the IDs of the admin of the bot (equal to the peers returned by getReportPeers in the event handler).
- getEventHandler can now be used from IPC clients!
- Added `Cron`
- Added plugins, filters, simple filters
- `getReply`, `sendMessage`, `sendDocument`, `sendPhoto`, `reply`, `delete`
Fixes: Fixes:
- Fixed file uploads with ext-uv! - Fixed file uploads with ext-uv!

View File

@ -111,6 +111,7 @@ Want to add your own open-source project to this list? [Click here!](https://doc
* [Filters](https://docs.madelineproto.xyz/docs/FILTERS.html) * [Filters](https://docs.madelineproto.xyz/docs/FILTERS.html)
* [Simple filters](https://docs.madelineproto.xyz/docs/FILTERS.html#simple-filters) * [Simple filters](https://docs.madelineproto.xyz/docs/FILTERS.html#simple-filters)
* [Attribute filters](https://docs.madelineproto.xyz/docs/FILTERS.html#attribute-filters) * [Attribute filters](https://docs.madelineproto.xyz/docs/FILTERS.html#attribute-filters)
* [Creating custom attribute filters](https://docs.madelineproto.xyz/docs/FILTERS.html#creating-custom-attribute-filters)
* [MTProto filters](https://docs.madelineproto.xyz/docs/FILTERS.html#mtproto-filters) * [MTProto filters](https://docs.madelineproto.xyz/docs/FILTERS.html#mtproto-filters)
* [Plugins](https://docs.madelineproto.xyz/docs/PLUGINS.html) * [Plugins](https://docs.madelineproto.xyz/docs/PLUGINS.html)
* [Installing plugins](https://docs.madelineproto.xyz/docs/PLUGINS.html#installing-plugins) * [Installing plugins](https://docs.madelineproto.xyz/docs/PLUGINS.html#installing-plugins)

2
docs

@ -1 +1 @@
Subproject commit d3eb81be0cad837e7fd3f7f6d6e2f7b518b78b55 Subproject commit 64bf195ee68d92bde6f49955ef735f58e9d6083c

View File

@ -45,6 +45,7 @@ final class Progress implements JsonSerializable
) { ) {
$this->percent = $pendingCount ? (int) (($successCount+$failCount)*100/$pendingCount) : 0; $this->percent = $pendingCount ? (int) (($successCount+$failCount)*100/$pendingCount) : 0;
} }
/** @internal */
public function jsonSerialize(): mixed public function jsonSerialize(): mixed
{ {
return \get_object_vars($this); return \get_object_vars($this);

View File

@ -9,6 +9,7 @@ enum CommandType: string implements JsonSerializable
case SLASH = '/'; case SLASH = '/';
case DOT = '.'; case DOT = '.';
case BANG = '!'; case BANG = '!';
/** @internal */
public function jsonSerialize(): mixed public function jsonSerialize(): mixed
{ {
return $this->value; return $this->value;

View File

@ -20,11 +20,14 @@ final class FilterNot extends Filter
{ {
$filter = $this->filter->initialize($API); $filter = $this->filter->initialize($API);
if ($filter === null) { if ($filter === null) {
// The nested filter didn't replace itself
return $this; return $this;
} }
if ($filter instanceof self) { if ($filter instanceof self) {
// The nested filter is a FilterNot, optimize !!A => A
return $filter->filter; return $filter->filter;
} }
// The nested filter replaced itself, re-wrap it
return new self($filter); return new self($filter);
} }

View File

@ -75,6 +75,7 @@ abstract class Media extends IpcCapable implements JsonSerializable
$this->spoiler = $rawMedia['spoiler'] ?? false; $this->spoiler = $rawMedia['spoiler'] ?? false;
} }
/** @internal */
public function jsonSerialize(): mixed public function jsonSerialize(): mixed
{ {
$v = \get_object_vars($this); $v = \get_object_vars($this);

View File

@ -12,6 +12,7 @@ enum MaskPosition: int implements JsonSerializable
case Mouth = 2; case Mouth = 2;
case Chin = 3; case Chin = 3;
/** @internal */
public function jsonSerialize(): mixed public function jsonSerialize(): mixed
{ {
return $this->value; return $this->value;

View File

@ -47,10 +47,8 @@ abstract class Message extends AbstractMessage
/** /**
* Attached media. * Attached media.
*
* @var Audio|Document|DocumentPhoto|Gif|MaskSticker|Photo|RoundVideo|Sticker|Video|Voice|null
*/ */
public readonly ?Media $media; public readonly Audio|Document|DocumentPhoto|Gif|MaskSticker|Photo|RoundVideo|Sticker|Video|Voice|null $media;
/** Whether this message is a sent scheduled message */ /** Whether this message is a sent scheduled message */
public readonly bool $fromScheduled; public readonly bool $fromScheduled;

View File

@ -10,6 +10,7 @@ use JsonSerializable;
*/ */
abstract class Update extends IpcCapable implements JsonSerializable abstract class Update extends IpcCapable implements JsonSerializable
{ {
/** @internal */
public function jsonSerialize(): mixed public function jsonSerialize(): mixed
{ {
$v = \get_object_vars($this); $v = \get_object_vars($this);

View File

@ -28,14 +28,14 @@ final class FileCallback implements FileCallbackInterface
/** /**
* Callback. * Callback.
* *
* @var callable(float, float, float) * @var callable(float, float, int)
*/ */
public readonly mixed $callback; public readonly mixed $callback;
/** /**
* Construct file callback. * Construct file callback.
* *
* @param mixed $file File to download/upload * @param mixed $file File to download/upload
* @param callable(float, float, float) $callback Callback * @param callable(float, float, int) $callback Callback
*/ */
public function __construct(public readonly mixed $file, callable $callback) public function __construct(public readonly mixed $file, callable $callback)
{ {

View File

@ -767,7 +767,7 @@ abstract class InternalDoc
/** /**
* Get event handler (or plugin instance). * Get event handler (or plugin instance).
* *
* @param ?class-string<PluginEventHandler> * @param ?class-string<PluginEventHandler> $class
*/ */
public function getEventHandler(?string $class = null): \danog\MadelineProto\EventHandler|\danog\MadelineProto\Ipc\EventHandlerProxy|\__PHP_Incomplete_Class|null public function getEventHandler(?string $class = null): \danog\MadelineProto\EventHandler|\danog\MadelineProto\Ipc\EventHandlerProxy|\__PHP_Incomplete_Class|null
{ {
@ -1497,7 +1497,7 @@ abstract class InternalDoc
* @param Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream $file File to upload: can be a message to reuse media present in a message. * @param Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream $file File to upload: can be a message to reuse media present in a message.
* @param Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream|null $thumb Optional: Thumbnail to upload * @param Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream|null $thumb Optional: Thumbnail to upload
* @param string $caption Caption of document * @param string $caption Caption of document
* @param ?callable(float, float, float) $callback Upload callback (percent, speed in mpbs, time elapsed) * @param ?callable(float, float, int) $callback Upload callback (percent, speed in mpbs, time elapsed)
* @param ?string $fileName Optional file name, if absent will be extracted from the passed $file. * @param ?string $fileName Optional file name, if absent will be extracted from the passed $file.
* @param "html"|"markdown"|null $parseMode Parse mode * @param "html"|"markdown"|null $parseMode Parse mode
* @param integer|null $replyToMsgId ID of message to reply to. * @param integer|null $replyToMsgId ID of message to reply to.
@ -1549,7 +1549,7 @@ abstract class InternalDoc
* @param boolean $clearDraft Clears the draft field * @param boolean $clearDraft Clears the draft field
* @param boolean $noWebpage Set this flag to disable generation of the webpage preview * @param boolean $noWebpage Set this flag to disable generation of the webpage preview
* *
* @return list<Message> * @return list<\danog\Madelineproto\EventHandler\Message>
*/ */
public function sendMessageToAdmins(string $message, ?string $parseMode = null, ?array $replyMarkup = null, ?int $scheduleDate = null, bool $silent = false, bool $noForwards = false, bool $background = false, bool $clearDraft = false, bool $noWebpage = false): array public function sendMessageToAdmins(string $message, ?string $parseMode = null, ?array $replyMarkup = null, ?int $scheduleDate = null, bool $silent = false, bool $noForwards = false, bool $background = false, bool $clearDraft = false, bool $noWebpage = false): array
{ {
@ -1563,7 +1563,7 @@ abstract class InternalDoc
* @param integer|string $peer Destination peer or username. * @param integer|string $peer Destination peer or username.
* @param Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream $file File to upload: can be a message to reuse media present in a message. * @param Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream $file File to upload: can be a message to reuse media present in a message.
* @param string $caption Caption of document * @param string $caption Caption of document
* @param ?callable(float, float, float) $callback Upload callback (percent, speed in mpbs, time elapsed) * @param ?callable(float, float, int) $callback Upload callback (percent, speed in mpbs, time elapsed)
* @param ?string $fileName Optional file name, if absent will be extracted from the passed $file. * @param ?string $fileName Optional file name, if absent will be extracted from the passed $file.
* @param "html"|"markdown"|null $parseMode Parse mode * @param "html"|"markdown"|null $parseMode Parse mode
* @param integer|null $replyToMsgId ID of message to reply to. * @param integer|null $replyToMsgId ID of message to reply to.
@ -1958,7 +1958,7 @@ abstract class InternalDoc
/** /**
* Wrap a media constructor into an abstract Media object. * Wrap a media constructor into an abstract Media object.
*/ */
public function wrapMedia(array $media, bool $protected): ?\danog\MadelineProto\EventHandler\Media public function wrapMedia(array $media, bool $protected = false): ?\danog\MadelineProto\EventHandler\Media
{ {
return $this->wrapper->getAPI()->wrapMedia($media, $protected); return $this->wrapper->getAPI()->wrapMedia($media, $protected);
} }

View File

@ -1677,7 +1677,7 @@ final class MTProto implements TLCallback, LoggerGetter
* @param boolean $clearDraft Clears the draft field * @param boolean $clearDraft Clears the draft field
* @param boolean $noWebpage Set this flag to disable generation of the webpage preview * @param boolean $noWebpage Set this flag to disable generation of the webpage preview
* *
* @return list<Message> * @return list<\danog\Madelineproto\EventHandler\Message>
*/ */
public function sendMessageToAdmins( public function sendMessageToAdmins(
string $message, string $message,

View File

@ -899,7 +899,7 @@ trait Files
$seekable = false; $seekable = false;
} }
if ($offset === $end) { if ($offset === $end) {
$cb(100, 0, 0); $cb(100.0, 0.0, 0);
return true; return true;
} }
$params = []; $params = [];
@ -923,11 +923,11 @@ trait Files
return true; return true;
} }
$count = \count($params); $count = \count($params);
$time = 0; $time = 0.0;
$speed = 0; $speed = 0.0;
$origCb = $cb; $origCb = $cb;
$cb = static function () use ($cb, $count, &$time, &$speed): void { $cb = static function () use ($cb, $count, &$time, &$speed): void {
static $cur = 0; static $cur = 0.0;
$cur++; $cur++;
$cb($cur * 100 / $count, $time, $speed); $cb($cur * 100 / $count, $time, $speed);
}; };
@ -981,7 +981,7 @@ trait Files
$this->clearCdnHashes($messageMedia['file_token']); $this->clearCdnHashes($messageMedia['file_token']);
} }
if (!isset($messageMedia['size'])) { if (!isset($messageMedia['size'])) {
$origCb(100, $time, $speed); $origCb(100.0, $time, $speed);
} }
return true; return true;
} }

View File

@ -54,7 +54,7 @@ trait FilesAbstraction
/** /**
* Wrap a media constructor into an abstract Media object. * Wrap a media constructor into an abstract Media object.
*/ */
public function wrapMedia(array $media, bool $protected): ?Media public function wrapMedia(array $media, bool $protected = false): ?Media
{ {
if ($media['_'] === 'messageMediaPhoto') { if ($media['_'] === 'messageMediaPhoto') {
if (!isset($media['photo'])) { if (!isset($media['photo'])) {
@ -117,7 +117,7 @@ trait FilesAbstraction
* @param Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream $file File to upload: can be a message to reuse media present in a message. * @param Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream $file File to upload: can be a message to reuse media present in a message.
* @param Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream|null $thumb Optional: Thumbnail to upload * @param Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream|null $thumb Optional: Thumbnail to upload
* @param string $caption Caption of document * @param string $caption Caption of document
* @param ?callable(float, float, float) $callback Upload callback (percent, speed in mpbs, time elapsed) * @param ?callable(float, float, int) $callback Upload callback (percent, speed in mpbs, time elapsed)
* @param ?string $fileName Optional file name, if absent will be extracted from the passed $file. * @param ?string $fileName Optional file name, if absent will be extracted from the passed $file.
* @param "html"|"markdown"|null $parseMode Parse mode * @param "html"|"markdown"|null $parseMode Parse mode
* @param integer|null $replyToMsgId ID of message to reply to. * @param integer|null $replyToMsgId ID of message to reply to.
@ -209,7 +209,7 @@ trait FilesAbstraction
* @param integer|string $peer Destination peer or username. * @param integer|string $peer Destination peer or username.
* @param Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream $file File to upload: can be a message to reuse media present in a message. * @param Message|Media|LocalFile|RemoteUrl|BotApiFileId|ReadableStream $file File to upload: can be a message to reuse media present in a message.
* @param string $caption Caption of document * @param string $caption Caption of document
* @param ?callable(float, float, float) $callback Upload callback (percent, speed in mpbs, time elapsed) * @param ?callable(float, float, int) $callback Upload callback (percent, speed in mpbs, time elapsed)
* @param ?string $fileName Optional file name, if absent will be extracted from the passed $file. * @param ?string $fileName Optional file name, if absent will be extracted from the passed $file.
* @param "html"|"markdown"|null $parseMode Parse mode * @param "html"|"markdown"|null $parseMode Parse mode
* @param integer|null $replyToMsgId ID of message to reply to. * @param integer|null $replyToMsgId ID of message to reply to.

View File

@ -21,6 +21,7 @@ declare(strict_types=1);
namespace danog\MadelineProto\TL\Types; namespace danog\MadelineProto\TL\Types;
use ArrayAccess; use ArrayAccess;
use AssertionError;
use JsonSerializable; use JsonSerializable;
/** /**
@ -69,11 +70,7 @@ final class Bytes implements JsonSerializable, ArrayAccess
*/ */
public function offsetSet(mixed $offset, mixed $value): void public function offsetSet(mixed $offset, mixed $value): void
{ {
if ($offset === null) { throw new AssertionError("Cannot modify nested bytes!");
$this->bytes .= $value;
} else {
$this->bytes[$offset] = $value;
}
} }
/** /**
* Get char at offset. * Get char at offset.
@ -92,7 +89,7 @@ final class Bytes implements JsonSerializable, ArrayAccess
*/ */
public function offsetUnset(mixed $offset): void public function offsetUnset(mixed $offset): void
{ {
unset($this->bytes[$offset]); throw new AssertionError("Cannot modify nested bytes!");
} }
/** /**
* Check if char at offset exists. * Check if char at offset exists.

View File

@ -28,6 +28,7 @@ use Countable;
use Exception; use Exception;
use Fiber; use Fiber;
use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\Include_;
use PhpParser\Node\Expr\New_; use PhpParser\Node\Expr\New_;
use PhpParser\Node\Name; use PhpParser\Node\Name;
use PhpParser\Node\Scalar\LNumber; use PhpParser\Node\Scalar\LNumber;
@ -629,7 +630,8 @@ abstract class Tools extends AsyncTools
{ {
$code = (new ParserFactory)->create(ParserFactory::ONLY_PHP7)->parse($code); $code = (new ParserFactory)->create(ParserFactory::ONLY_PHP7)->parse($code);
Assert::notNull($code); Assert::notNull($code);
$traverser = new NodeTraverser([new NameResolver()]); $traverser = new NodeTraverser;
$traverser->addVisitor(new NameResolver());
$code = $traverser->traverse($code); $code = $traverser->traverse($code);
$finder = new NodeFinder; $finder = new NodeFinder;
@ -640,7 +642,7 @@ abstract class Tools extends AsyncTools
} }
$class = $class[0]->name->toString(); $class = $class[0]->name->toString();
/** @var DeclareDeclare|null $call */ /** @var DeclareDeclare|null $declare */
$declare = $finder->findFirstInstanceOf($code, DeclareDeclare::class); $declare = $finder->findFirstInstanceOf($code, DeclareDeclare::class);
if ($declare === null if ($declare === null
|| $declare->key->name !== 'strict_types' || $declare->key->name !== 'strict_types'

View File

@ -141,7 +141,7 @@ trait Events
/** /**
* Get event handler (or plugin instance). * Get event handler (or plugin instance).
* *
* @param ?class-string<PluginEventHandler> * @param ?class-string<PluginEventHandler> $class
*/ */
public function getEventHandler(?string $class = null): EventHandler|EventHandlerProxy|__PHP_Incomplete_Class|null public function getEventHandler(?string $class = null): EventHandler|EventHandlerProxy|__PHP_Incomplete_Class|null
{ {

View File

@ -166,7 +166,7 @@ function printTypes(array $types, string $type): string
foreach ($orderedfiles as $key => $filename) { foreach ($orderedfiles as $key => $filename) {
$lines = file_get_contents($filename); $lines = file_get_contents($filename);
$lines = preg_replace_callback('/\<\!-- cut_here (\S+) -->.*\<\!-- cut_here_end \1 --\>/sim', function ($matches) { $lines = preg_replace_callback('/\<\!--\s+cut_here\s+(\S+)\s+-->.*\<\!--\s+cut_here_end\s+\1\s+--\>/sim', function ($matches) {
[, $match] = $matches; [, $match] = $matches;
if ($match === "concretefilters") { if ($match === "concretefilters") {
$result = [Update::class, AbstractMessage::class, Message::class, ServiceMessage::class]; $result = [Update::class, AbstractMessage::class, Message::class, ServiceMessage::class];