Compare commits
7 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
5bff732814 | ||
![]() |
c52d2f3601 | ||
![]() |
f9741c250d | ||
![]() |
07ff3c4d2b | ||
![]() |
c4fdd71bdc | ||
![]() |
3c692424f7 | ||
![]() |
8408430ef8 |
8 changed files with 118 additions and 134 deletions
19
CHANGELOG.md
19
CHANGELOG.md
|
@ -4,13 +4,30 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [8.0.1] - Ongoing
|
||||
## [8.0.2] - 2024-12-09
|
||||
|
||||
This update introduces some quality of life improvements.
|
||||
|
||||
### Added
|
||||
* Added `containsMedia` method to `Message` object to check if the message contains media.
|
||||
* Add support for handling callback query events
|
||||
|
||||
|
||||
## [8.0.1] - 2024-12-04
|
||||
|
||||
This update introduces bug fixes and improvements
|
||||
|
||||
### Changed
|
||||
* Refactor PollingBot to use PsyncLib for updates
|
||||
* Refactor optional parameters in method signatures
|
||||
* Improve command check with method existence validation
|
||||
|
||||
### Fixed
|
||||
* Reposition debug log for event handler execution.
|
||||
* Improve command check with method existence validation
|
||||
* Allow nullable type for MessageEntity type
|
||||
* Encode arrays as JSON in SendAudio parameters
|
||||
* Refactor fromArray method for null data handling
|
||||
|
||||
|
||||
## [8.0.0] - 2024-11-29
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
"package": "net.nosial.tgbotlib",
|
||||
"description": "TgBotLib is a library for interacting with the Telegram Bot API",
|
||||
"company": "Nosial",
|
||||
"version": "8.0.1",
|
||||
"version": "8.0.2",
|
||||
"uuid": "b409e036-ab04-11ed-b32e-9d3f57a644ae"
|
||||
},
|
||||
"build": {
|
||||
|
@ -36,6 +36,12 @@
|
|||
"version": "latest",
|
||||
"source_type": "remote",
|
||||
"source": "nosial/libs.opts=latest@n64"
|
||||
},
|
||||
{
|
||||
"name": "net.nosial.psynclib",
|
||||
"version": "latest",
|
||||
"source_type": "remote",
|
||||
"source": "nosial/libs.psync=latest@n64"
|
||||
}
|
||||
],
|
||||
"configurations": [
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
use TgBotLib\Enums\Types\ChatActionType;
|
||||
use TgBotLib\Enums\Types\ParseMode;
|
||||
use TgBotLib\Enums\Types\StickerFormat;
|
||||
use TgBotLib\Events\CallbackQueryEvent;
|
||||
use TgBotLib\Events\CommandEvent;
|
||||
use TgBotLib\Events\UpdateEvent;
|
||||
use TgBotLib\Exceptions\TelegramException;
|
||||
|
@ -375,6 +376,26 @@
|
|||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* @method array getEventHandlersByCallbackQuery(string $callbackData) Retrieves an array of event handlers whose callback data matches the given parameter.
|
||||
* @param string $callbackData The callback data to match against the registered event handlers.
|
||||
* @return array An array of matching event handler instances.
|
||||
*/
|
||||
public function getEventHandlersByCallbackQuery(string $callbackData): array
|
||||
{
|
||||
$results = [];
|
||||
/** @var UpdateEvent $eventHandler */
|
||||
foreach($this->eventHandlers as $eventHandler)
|
||||
{
|
||||
if(method_exists($eventHandler, 'getCallbackData') && $eventHandler::getCallbackData() === $callbackData)
|
||||
{
|
||||
$results[] = $eventHandler;
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all event handlers.
|
||||
*
|
||||
|
@ -429,7 +450,6 @@
|
|||
$command = $update?->getAnyMessage()?->getCommand();
|
||||
if($command !== null)
|
||||
{
|
||||
$commandExecuted = false;
|
||||
Logger::getLogger()->debug(sprintf('Executing command %s for update %s', $command, $update->getUpdateId()));
|
||||
|
||||
/** @var CommandEvent $eventHandler */
|
||||
|
@ -438,7 +458,6 @@
|
|||
try
|
||||
{
|
||||
(new $eventHandler($update))->handle($this);
|
||||
$commandExecuted = true;
|
||||
}
|
||||
catch(TelegramException $e)
|
||||
{
|
||||
|
@ -449,10 +468,28 @@
|
|||
Logger::getLogger()->error(sprintf('Exception occurred: %s', $e->getMessage()), $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($commandExecuted)
|
||||
$callbackData = $update?->getCallbackQuery()?->getData();
|
||||
if($callbackData !== null)
|
||||
{
|
||||
Logger::getLogger()->debug(sprintf('Executing callback query %s for update %s', $callbackData, $update->getUpdateId()));
|
||||
|
||||
/** @var CallbackQueryEvent $eventHandler */
|
||||
foreach($this->getEventHandlersByCallbackQuery($callbackData) as $eventHandler)
|
||||
{
|
||||
return;
|
||||
try
|
||||
{
|
||||
(new $eventHandler($update))->handle($this);
|
||||
}
|
||||
catch(TelegramException $e)
|
||||
{
|
||||
Logger::getLogger()->error(sprintf('Telegram exception occurred: %s', $e->getMessage()), $e);
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Logger::getLogger()->error(sprintf('Exception occurred: %s', $e->getMessage()), $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -504,6 +541,20 @@
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes an array of updates.
|
||||
*
|
||||
* @param array $updates An array containing update objects to be handled. Each update will be individually processed by calling the handleUpdate method.
|
||||
* @return void This method does not return a value.
|
||||
*/
|
||||
public function handleUpdates(array $updates): void
|
||||
{
|
||||
foreach($updates as $update)
|
||||
{
|
||||
$this->handleUpdate($update);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request by executing the specified method with the given parameters.
|
||||
*
|
||||
|
|
|
@ -115,11 +115,6 @@
|
|||
return EventType::SHIPPING_QUERY;
|
||||
}
|
||||
|
||||
if($update->getCallbackQuery() !== null)
|
||||
{
|
||||
return EventType::CALLBACK_QUERY;
|
||||
}
|
||||
|
||||
if($update->getChosenInlineResult() !== null)
|
||||
{
|
||||
return EventType::CHOSEN_INLINE_RESULT;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace TgBotLib\Events;
|
||||
|
||||
use LogicException;
|
||||
use TgBotLib\Enums\EventType;
|
||||
use TgBotLib\Objects\CallbackQuery;
|
||||
|
||||
|
@ -15,6 +16,13 @@
|
|||
return EventType::CALLBACK_QUERY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves data associated with the callback.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public abstract static function getCallbackData(): string;
|
||||
|
||||
/**
|
||||
* New incoming callback query
|
||||
*
|
||||
|
|
|
@ -1061,6 +1061,16 @@
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if any media content is present in the message.
|
||||
*
|
||||
* @return bool True if media content is present, false otherwise.
|
||||
*/
|
||||
public function containsMedia(): bool
|
||||
{
|
||||
return $this->getPhoto() !== null || $this->getAnimation() !== null || $this->getAudio() !== null || $this->getDocument() !== null || $this->getSticker() !== null || $this->getVideo() !== null || $this->getVideoNote() !== null || $this->getVoice() !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
class MessageEntity implements ObjectTypeInterface
|
||||
{
|
||||
private MessageEntityType $type;
|
||||
private ?MessageEntityType $type;
|
||||
private int $offset;
|
||||
private int $length;
|
||||
private ?string $url;
|
||||
|
@ -23,9 +23,9 @@
|
|||
* (monowidth block), “text_link” (for clickable text URLs), “text_mention” (for users without usernames),
|
||||
* “custom_emoji” (for inline custom emoji stickers)
|
||||
*
|
||||
* @return MessageEntityType
|
||||
* @return ?MessageEntityType
|
||||
*/
|
||||
public function getType(): MessageEntityType
|
||||
public function getType(): ?MessageEntityType
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
@ -33,10 +33,10 @@
|
|||
/**
|
||||
* Sets the type of the entity
|
||||
*
|
||||
* @param MessageEntityType $type
|
||||
* @param ?MessageEntityType $type
|
||||
* @return MessageEntity
|
||||
*/
|
||||
public function setType(MessageEntityType $type): MessageEntity
|
||||
public function setType(?MessageEntityType $type): MessageEntity
|
||||
{
|
||||
$this->type = $type;
|
||||
return $this;
|
||||
|
@ -223,7 +223,7 @@
|
|||
}
|
||||
|
||||
$object = new self();
|
||||
$object->type = MessageEntityType::tryFrom($data['type']);
|
||||
$object->type = is_null($data['type']) ? null : MessageEntityType::tryFrom($data['type']);
|
||||
$object->offset = $data['offset'] ?? null;
|
||||
$object->length = $data['length'] ?? null;
|
||||
$object->url = $data['url'] ?? null;
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
namespace TgBotLib;
|
||||
|
||||
use RuntimeException;
|
||||
use function RuntimeException;
|
||||
use PsyncLib\Psync;
|
||||
|
||||
/**
|
||||
* PollingBot class that extends Bot for handling updates using polling.
|
||||
|
@ -14,8 +13,6 @@
|
|||
private int $limit;
|
||||
private int $timeout;
|
||||
private array $allowedUpdates;
|
||||
private bool $fork;
|
||||
private array $childPids;
|
||||
|
||||
/**
|
||||
* Constructor for the class, initializing with a Bot instance.
|
||||
|
@ -30,8 +27,6 @@
|
|||
$this->limit = 100;
|
||||
$this->timeout = 0;
|
||||
$this->allowedUpdates = [];
|
||||
$this->fork = false;
|
||||
$this->childPids = [];
|
||||
|
||||
// Register signal handler for child processes
|
||||
if (function_exists('pcntl_signal'))
|
||||
|
@ -115,6 +110,11 @@
|
|||
$this->allowedUpdates = $allowedUpdates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the list of allowed updates.
|
||||
*
|
||||
* @return array Returns an array containing the allowed updates.
|
||||
*/
|
||||
public function getAllowedUpdates(): array
|
||||
{
|
||||
return $this->allowedUpdates;
|
||||
|
@ -137,55 +137,14 @@
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets the fork value.
|
||||
* Polls for updates and processes them. Installs a signal handler if
|
||||
* needed, fetches updates, tracks the highest update ID, updates the
|
||||
* polling offset, and handles the updates using the Psync mechanism.
|
||||
*
|
||||
* @param bool $fork The fork value to set.
|
||||
* @return void
|
||||
* @return void This method does not return any value.
|
||||
*/
|
||||
public function setFork(bool $fork): void
|
||||
public function poll(): void
|
||||
{
|
||||
$this->fork = $fork;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current fork setting.
|
||||
*
|
||||
* @return bool The configured fork value.
|
||||
*/
|
||||
public function getFork(): bool
|
||||
{
|
||||
return $this->fork;
|
||||
}
|
||||
|
||||
private function signalHandler(int $signal): void
|
||||
{
|
||||
if ($signal === SIGCHLD)
|
||||
{
|
||||
$i = -1;
|
||||
while (($pid = pcntl_wait($i, WNOHANG)) > 0)
|
||||
{
|
||||
$key = array_search($pid, $this->childPids);
|
||||
if ($key !== false)
|
||||
{
|
||||
unset($this->childPids[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles incoming updates by fetching and processing them with appropriate event handlers.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handleUpdates(): void
|
||||
{
|
||||
// Install signal handler
|
||||
if ($this->fork && function_exists('pcntl_signal_dispatch'))
|
||||
{
|
||||
pcntl_signal_dispatch();
|
||||
}
|
||||
|
||||
$updates = $this->getUpdates(offset: ($this->offset ?: 0), limit: $this->limit, timeout: $this->timeout, allowed_updates: $this->retrieveAllowedUpdates());
|
||||
|
||||
if (empty($updates))
|
||||
|
@ -195,7 +154,6 @@
|
|||
|
||||
// Track the highest update ID we've seen
|
||||
$maxUpdateId = null;
|
||||
|
||||
foreach ($updates as $update)
|
||||
{
|
||||
// Update the maximum update ID as we go
|
||||
|
@ -203,56 +161,6 @@
|
|||
{
|
||||
$maxUpdateId = $update->getUpdateId();
|
||||
}
|
||||
|
||||
if ($this->fork)
|
||||
{
|
||||
// Clean up any finished processes
|
||||
if (function_exists('pcntl_signal_dispatch'))
|
||||
{
|
||||
pcntl_signal_dispatch();
|
||||
}
|
||||
|
||||
$pid = pcntl_fork();
|
||||
|
||||
if ($pid == -1)
|
||||
{
|
||||
// Fork failed
|
||||
throw new RuntimeException('Failed to fork process for update handling');
|
||||
}
|
||||
elseif ($pid)
|
||||
{
|
||||
// Parent process
|
||||
$this->childPids[] = $pid;
|
||||
|
||||
// If we have too many child processes, wait for some to finish
|
||||
$maxChildren = 32; // Adjust this value based on your system's capabilities
|
||||
while (count($this->childPids) >= $maxChildren)
|
||||
{
|
||||
if (function_exists('pcntl_signal_dispatch'))
|
||||
{
|
||||
pcntl_signal_dispatch();
|
||||
}
|
||||
|
||||
usleep(10000); // Sleep for 10ms to prevent CPU hogging
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Child process
|
||||
try
|
||||
{
|
||||
$this->handleUpdate($update);
|
||||
}
|
||||
finally
|
||||
{
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->handleUpdate($update);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the offset based on the highest update ID we've seen
|
||||
|
@ -261,20 +169,9 @@
|
|||
$this->offset = $maxUpdateId + 1;
|
||||
}
|
||||
|
||||
// If forking is enabled, ensure we clean up any remaining child processes
|
||||
if ($this->fork)
|
||||
{
|
||||
// Wait for remaining child processes to finish
|
||||
while (!empty($this->childPids))
|
||||
{
|
||||
if (function_exists('pcntl_signal_dispatch'))
|
||||
{
|
||||
pcntl_signal_dispatch();
|
||||
}
|
||||
|
||||
usleep(10000); // Sleep for 10ms to prevent CPU hogging
|
||||
}
|
||||
}
|
||||
// Pass the method name as a string and the object
|
||||
Psync::do([$this, 'handleUpdates'], [$updates]);
|
||||
Psync::clean();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Reference in a new issue