Skip to content

Commit

Permalink
Implement PSR-4 autoloading for plugin paths
Browse files Browse the repository at this point in the history
  • Loading branch information
danog committed Jul 10, 2023
1 parent 7d47e1f commit 8f66319
Show file tree
Hide file tree
Showing 17 changed files with 324 additions and 77 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Features:
- Added support for `pay`, `login_url`, `web_app` and `tg://user?id=` buttons in bot API syntax!
- Added a `getAdmin` function that returns the ID of the admin of the bot (which is equal to the first peer returned by getReportPeers in the event handler).
- getPlugin can now be used from IPC clients!
- `getReply`, `sendMessage`, `reply`

Fixes:
- Fixed file uploads with ext-uv!
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ Want to add your own open-source project to this list? [Click here!](https://doc
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#getdhconfig-array" name="getDhConfig">Get diffie-hellman configuration: getDhConfig</a>
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#getdownloadinfo-mixed-messagemedia-array-ext-string-name-string-mime-string-size-int-inputfilelocation-array" name="getDownloadInfo">Get download info of file: getDownloadInfo</a>
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#getpropicinfo-mixed-data-array" name="getPropicInfo">Get download info of the propic of a user: getPropicInfo</a>
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#geteventhandler-danog-madelineproto-eventhandler-__php_incomplete_class-null" name="getEventHandler">Get event handler: getEventHandler</a>
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#geteventhandler-danog-madelineproto-eventhandler-danog-madelineproto-ipc-eventhandlerproxy-__php_incomplete_class-null" name="getEventHandler">Get event handler: getEventHandler</a>
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#getextensionfromlocation-mixed-location-string-default-string" name="getExtensionFromLocation">Get extension from file location: getExtensionFromLocation</a>
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#getextensionfrommime-string-mime-string" name="getExtensionFromMime">Get extension from mime type: getExtensionFromMime</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages.getFavedStickers.html" name="messages.getFavedStickers">Get faved stickers: messages.getFavedStickers</a>
Expand Down Expand Up @@ -628,7 +628,7 @@ Want to add your own open-source project to this list? [Click here!](https://doc
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages.readMessageContents.html" name="messages.readMessageContents">Notifies the sender about the recipient having listened a voice message or watched a video: messages.readMessageContents</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages.sendScreenshotNotification.html" name="messages.sendScreenshotNotification">Notify the other user in a private chat that a screenshot of the chat was taken: messages.sendScreenshotNotification</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/users.setSecureValueErrors.html" name="users.setSecureValueErrors">Notify the user that the sent passport data contains some errors The user will not be able to re-submit their Passport data to you until the errors are fixed (the contents of the field for which you returned the error must change): users.setSecureValueErrors</a>
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#getplugin-class-string-t-class-danog-madelineproto-plugineventhandler-danog-madelineproto-ipc-plugineventhandlerproxy-null" name="getPlugin">Obtain a certain event handler plugin instance: getPlugin</a>
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#getplugin-class-string-t-class-danog-madelineproto-plugineventhandler-danog-madelineproto-ipc-eventhandlerproxy-null" name="getPlugin">Obtain a certain event handler plugin instance: getPlugin</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/bots.getBotCommands.html" name="bots.getBotCommands">Obtain a list of bot commands for the specified bot scope and language code: bots.getBotCommands</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages.getEmojiKeywordsLanguages.html" name="messages.getEmojiKeywordsLanguages">Obtain a list of related languages that must be used when fetching emoji keyword lists »: messages.getEmojiKeywordsLanguages</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages.getAvailableReactions.html" name="messages.getAvailableReactions">Obtain available message reactions »: messages.getAvailableReactions</a>
Expand Down Expand Up @@ -755,6 +755,7 @@ Want to add your own open-source project to this list? [Click here!](https://doc
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#broadcastmessages-array-messages-danog-madelineproto-broadcast-filter-filter-bool-pin-int" name="broadcastMessages">Sends a list of messages to all peers (users, chats, channels) of the bot: broadcastMessages</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages.sendMessage.html" name="messages.sendMessage">Sends a message to a chat: messages.sendMessage</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages.sendEncryptedFile.html" name="messages.sendEncryptedFile">Sends a message with a file attachment to a secret chat: messages.sendEncryptedFile</a>
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#sendmessage-int-string-peer-string-message-html-markdown-null-parsemode-int-null-replytomsgid-int-null-topmsgid-array-null-replymarkup-int-null-sendas-int-null-scheduledate-bool-silent-bool-noforwards-bool-background-bool-cleardraft-bool-nowebpage-bool-updatestickersetsorder-message" name="sendMessage">Sends a message: sendMessage</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages.sendEncryptedService.html" name="messages.sendEncryptedService">Sends a service message to a secret chat: messages.sendEncryptedService</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages.sendEncrypted.html" name="messages.sendEncrypted">Sends a text message to a secret chat: messages.sendEncrypted</a>
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#sendcustomevent-mixed-payload-void" name="sendCustomEvent">Sends an updateCustomEvent update to the event handler: sendCustomEvent</a>
Expand Down Expand Up @@ -827,7 +828,7 @@ Want to add your own open-source project to this list? [Click here!](https://doc
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#uploadfromcallable-mixed-callable-int-size-string-mime-string-filename-callable-cb-bool-seekable-bool-encrypted-mixed" name="uploadFromCallable">Upload file from callable: uploadFromCallable</a>
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#uploadfromstream-mixed-stream-int-size-string-mime-string-filename-callable-cb-bool-encrypted-mixed" name="uploadFromStream">Upload file from stream: uploadFromStream</a>
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#uploadencrypted-filecallbackinterface-string-array-file-string-filename-callable-cb-mixed" name="uploadEncrypted">Upload file to secret chat: uploadEncrypted</a>
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#upload-filecallbackinterface-string-array-resource-file-string-filename-callable-cb-bool-encrypted-mixed" name="upload">Upload file: upload</a>
* <a href="https://docs.madelineproto.xyz/PHP/danog/MadelineProto/API.html#upload-filecallbackinterface-localfile-string-array-resource-file-string-filename-callable-cb-bool-encrypted-mixed" name="upload">Upload file: upload</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account.uploadRingtone.html" name="account.uploadRingtone">Upload notification sound, use account.saveRingtone to convert it and add it to the list of saved notification sounds: account.uploadRingtone</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account.uploadTheme.html" name="account.uploadTheme">Upload theme: account.uploadTheme</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages.setGameScore.html" name="messages.setGameScore">Use this method to set the score of the specified user in a game sent as a normal message (bots only): messages.setGameScore</a>
Expand Down
71 changes: 50 additions & 21 deletions src/EventHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,6 @@ final public function internalStart(APIWrapper $MadelineProto, array $pluginsPre
$this->setReportPeers(Tools::call($this->getReportPeers())->await());
}

$old = true;
$constructors = $this->getTL()->getConstructors();
$methods = [];
$handlers = [];
Expand All @@ -182,6 +181,9 @@ function (array $update) use ($basic_handler, $closure): void {
];
continue;
}
if (!$this instanceof SimpleEventHandler) {
continue;
}
if ($periodic = $methodRefl->getAttributes(Cron::class)) {
$periodic = $periodic[0]->newInstance();
$this->periodicLoops[$method] = new PeriodicLoop(
Expand All @@ -192,7 +194,6 @@ function (PeriodicLoop $loop) use ($closure): bool {
$periodic->period
);
$this->periodicLoops[$method]->start();
$old = false;
continue;
}
$filter = $methodRefl->getAttributes(
Expand All @@ -217,9 +218,8 @@ function (PeriodicLoop $loop) use ($closure): bool {
EventLoop::queue($closure, $update);
}
};
$old = false;
}
if (!$old) {
if ($this instanceof SimpleEventHandler) {
self::validateEventHandler(static::class);
}
if ($has_any) {
Expand All @@ -236,6 +236,9 @@ function (PeriodicLoop $loop) use ($closure): bool {
$plugin = $pluginsPrev[$class] ?? $pluginsNew[$class] ?? new $class;
$pluginsNew[$class] = $plugin;
[$newMethods, $newHandlers] = $plugin->internalStart($MadelineProto, $pluginsPrev, $pluginsNew, false) ?? [];
if (!$plugin->isPluginEnabled()) {
continue;
}
foreach ($newMethods as $update => $method) {
$methods[$update] ??= [];
$methods[$update][] = $method;
Expand Down Expand Up @@ -303,22 +306,47 @@ public function getPlugins(): array
private static array $includedPaths = [];
/**
* Obtain a list of plugin event handlers.
*
* @return list<class-string<PluginEventHandler>>
*/
private function internalGetPlugins(): array
{
$plugins = $this->getPlugins();
$this->internalGetDirectoryPlugins($plugins);
$plugins = \array_values(\array_unique($plugins, SORT_REGULAR));

foreach ($plugins as $plugin) {
Assert::classExists($plugin);
Assert::true(\is_subclass_of($plugin, PluginEventHandler::class), "$plugin must extend ".PluginEventHandler::class);
Assert::notEq($plugin, PluginEventHandler::class);
Assert::true(\str_contains(\ltrim($plugin, '\\'), '\\'), "$plugin must be in a namespace!");
self::validateEventHandler($plugin);
}

return $plugins;
}

private function internalGetDirectoryPlugins(array &$plugins): void
{
if ($this instanceof PluginEventHandler) {
return;
}

$paths = $this->getPluginPaths();
if (\is_string($paths)) {
$paths = [$paths];
} elseif ($paths === null) {
$paths = [];
}
if (!$paths) {
return;
}
$paths = \array_map(realpath(...), $paths);

$plugins = \array_values($this->getPlugins());

$recurse = static function (string $path) use (&$recurse, &$plugins): void {
$recurse = static function (string $path, string $namespace = 'MadelinePlugin') use (&$recurse, &$plugins): void {
foreach (listFiles($path) as $file) {
if (isDirectory($file)) {
$recurse($file);
$recurse($file, $namespace.'\\'.\basename($file));
} elseif (isFile($file) && \str_ends_with($file, "Plugin.php")) {
$file = \realpath($file);
if (isset(self::$includedPaths[$file])) {
Expand All @@ -328,8 +356,7 @@ private function internalGetPlugins(): array
try {
require $file;
} catch (PluginRegistration $e) {
$name = \substr($e->plugin, \strrpos($e->plugin, '\\')+1);
Assert::eq($name, \basename($file, '.php'));
Assert::eq($e->plugin, $namespace.'\\'.\basename($file, '.php'));
$plugins []= $e->plugin;
continue;
}
Expand All @@ -345,17 +372,19 @@ private function internalGetPlugins(): array
self::$includingPlugins = false;
}

$plugins = \array_values(\array_unique($plugins, SORT_REGULAR));

foreach ($plugins as $plugin) {
Assert::classExists($plugin);
Assert::true(\is_subclass_of($plugin, PluginEventHandler::class), "$plugin must extend ".PluginEventHandler::class);
Assert::notEq($plugin, PluginEventHandler::class);
Assert::true(\str_contains(\ltrim($plugin, '\\'), '\\'), "$plugin must be in a namespace!");
self::validateEventHandler($plugin);
}

return $plugins;
\spl_autoload_register(function (string $class) use ($paths): void {
if (!\str_starts_with($class, 'MadelinePlugin\\')) {
return;
}
// Has leading /
$file = \str_replace('\\', DIRECTORY_SEPARATOR, \substr($class, 14)).'.php';
foreach ($paths as $path) {
if (\file_exists($path.$file)) {
require $path.$file;
Assert::classExists($class);
}
}
});
}

private const BANNED_FUNCTIONS = [
Expand Down
46 changes: 46 additions & 0 deletions src/EventHandler/AbstractMessage.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,50 @@ public function getReply(): ?self
]
)['messages'][0]);
}

/**
* Replies to the message.
*
* @param string $message Message to send
* @param "html"|"markdown"|null $parseMode Parse mode
* @param array|null $replyMarkup Keyboard information.
* @param integer|null $sendAs Peer to send the message as.
* @param integer|null $scheduleDate Schedule date.
* @param boolean $silent Whether to send the message silently, without triggering notifications.
* @param boolean $background Send this message as background message
* @param boolean $clearDraft Clears the draft field
* @param boolean $noWebpage Set this flag to disable generation of the webpage preview
* @param boolean $updateStickersetsOrder Whether to move used stickersets to top
*
*/
public function reply(
string $message,
?string $parseMode = null,
?array $replyMarkup = null,
int|string|null $sendAs = null,
?int $scheduleDate = null,
bool $silent = false,
bool $noForwards = false,
bool $background = false,
bool $clearDraft = false,
bool $noWebpage = false,
bool $updateStickersetsOrder = false,
): Message {
return $this->API->sendMessage(
peer: $this->chatId,
message: $message,
parseMode: $parseMode,
replyToMsgId: $this->id,
topMsgId: $this->topicId === 1 ? null : $this->topicId,
replyMarkup: $replyMarkup,
sendAs: $sendAs,
scheduleDate: $scheduleDate,
silent: $silent,
noForwards: $noForwards,
background: $background,
clearDraft: $clearDraft,
noWebpage: $noWebpage,
updateStickersetsOrder: $updateStickersetsOrder
);
}
}
37 changes: 37 additions & 0 deletions src/EventHandler/Filter/FilterFromSenders.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php declare(strict_types=1);

namespace danog\MadelineProto\EventHandler\Filter;

use Attribute;
use danog\MadelineProto\EventHandler;
use danog\MadelineProto\EventHandler\Message\GroupMessage;
use danog\MadelineProto\EventHandler\Update;

/**
* Allow incoming or outgoing group messages made by a certain list of senders.
*/
#[Attribute(Attribute::TARGET_METHOD)]
final class FilterFromSenders extends Filter
{
/** @var array<string|int> */
private readonly array $peers;
/** @var list<string|int> */
private readonly array $peersResolved;
public function __construct(string|int ...$idOrUsername)
{
$this->peers = \array_unique($idOrUsername);
}
public function initialize(EventHandler $API): ?Filter
{
$res = [];
foreach ($this->peers as $peer) {
$res []= $API->getId($peer);
}
$this->peersResolved = $res;
return null;
}
public function apply(Update $update): bool
{
return $update instanceof GroupMessage && \in_array($update->senderId, $this->peersResolved, true);
}
}
Loading

0 comments on commit 8f66319

Please sign in to comment.