Skip to content

Commit

Permalink
add: temporary conversations
Browse files Browse the repository at this point in the history
  • Loading branch information
Mateodioev committed Apr 5, 2024
1 parent 9d89e80 commit 36c81f8
Show file tree
Hide file tree
Showing 13 changed files with 177 additions and 28 deletions.
9 changes: 8 additions & 1 deletion Changelog.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@

n.n.n / 2024-04-04
n.n.n / 2024-04-05
==================



v5.3.1 / 2024-04-05
===================

* feat: :warning: Delete method handle
* add: changelog file
* add: more examples
* feat: use gc
* fix: version
Expand Down
62 changes: 62 additions & 0 deletions examples/TemporaryConversation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

declare(strict_types=1);

use Mateodioev\TgHandler\Commands\MessageCommand;
use Mateodioev\TgHandler\Conversations\{MessageConversation, customTTl};

class TriggerConversationCommand extends MessageCommand
{
protected string $name = 'temp';
protected string $description = 'Trigger a temporary conversation';

public function execute(array $args = [])
{
$this->api()->sendMessage(
$this->ctx()->message()->chat()->id(),
'Send me a message. You only have 1 hour to do it.'
);

$actualTime = time();

// Expire the conversation after 1 hour
return TemporaryConversation::fromContext($this->ctx())
->withCustomTTL(60 * 60)
->setCreatedAt($actualTime);
}

}

class TemporaryConversation extends MessageConversation
{
use customTTl;

private int $createdAt;

public function setCreatedAt(int $time): static
{
$this->createdAt = $time;
return $this;
}

public function execute(array $args = [])
{
$this->api()->sendMessage(
$this->ctx()->message()->chat()->id(),
'You sent: ' . $this->ctx()->message()->text() . "\n\nSend me another message (expire in " . date('Y-m-d H:i:s', $this->createdAt) . ').'
);

/// With this, the conversation will expire after 1 hour from the creation time

$diff = time() - $this->createdAt;

return TemporaryConversation::fromContext($this->ctx())
->withCustomTTL(60 * 60 - $diff)
->setCreatedAt($this->createdAt);
}

public function onExpired(): void
{
$this->api()->sendMessage($this->chatId, 'The conversation has expired.');
}
}
5 changes: 3 additions & 2 deletions examples/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
declare(strict_types=1);

use Mateodioev\TgHandler\BotConfig;
use Mateodioev\TgHandler\Log\{BulkStream, Logger, TerminalStream};
use Mateodioev\TgHandler\Log\{BulkStream, Logger, PhpNativeStream, TerminalStream};

require __DIR__ . '/../vendor/autoload.php';
require __DIR__ . '/All.php';
require __DIR__ . '/StickerListener.php';
require __DIR__ . '/TemporaryConversation.php';
require __DIR__ . '/Start.php';
require __DIR__ . '/Params.php';
require __DIR__ . '/Me.php';
Expand All @@ -21,7 +22,7 @@
// Log php error and print in terminal
$streamCollection = new BulkStream(
new TerminalStream(),
// (new PhpNativeStream())->activate(__DIR__)
(new PhpNativeStream())->activate(__DIR__)
);
$logger = new Logger($streamCollection);

Expand Down
1 change: 1 addition & 0 deletions examples/bot.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
$bot->registerCommand(Start::get())
->add(Params::get())
->add(Name::get())
->add(TriggerConversationCommand::get())
->add(Me::get())
->add(GetUsage::get())
->withDefaultFallbackCommand(); // use this to register the fallback command
Expand Down
20 changes: 16 additions & 4 deletions src/Bot.php
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,9 @@ private function registerEvent(EventInterface $eventInterface): int

/**
* Register a conversation with ttl. If ttl is Conversation::UNDEFINED_TTL, the conversation will not be registered.
* @internal
*/
private function registerConversation(Conversation $conversation): void
public function registerConversation(Conversation $conversation): void
{
$ttl = $conversation->ttl();
$conversationId = $this->registerEvent($conversation);
Expand All @@ -224,21 +225,28 @@ private function registerConversation(Conversation $conversation): void
'ttl' => $ttl
]);

EventLoop::delay($ttl, function () use ($conversationId, $conversation) {
$id = EventLoop::delay($ttl, function () use ($conversationId, $conversation) {
$deleted = $this->eventStorage->deleteById($conversationId);

if ($deleted) {
$this->getLogger()->debug(
message: 'Conversation with id {id} removed because exceded the ttl ({ttl})',
context: ['id' => $conversationId, 'ttl' => $conversation->ttl()]
);
EventLoop::queue($conversation->onExpired(...));
}
});
}

/**
* @throws BotException If command type is invalid
*/
public function registerCommand(Command $command): GenericCommand
{
$generic = $this->genericCommands[$command->type()->value()];
$type = $command->type();
$generic = $this->genericCommands[$type->value()]
?? throw new BotException('Invalid command type: ' . $type->prettyName());

$generic->add($command);

return $generic;
Expand Down Expand Up @@ -293,7 +301,11 @@ public function executeCommand(EventInterface $event, Context $ctx): void
}
// Register next conversation
if ($nextEvent instanceof Conversation) {
$this->registerConversation($nextEvent);
$this->getLogger()->debug('Register next conversation {name}', ['name' => $nextEvent::class]);
$this->registerConversation(
$nextEvent->setVars($api, $ctx)
->setDb($this->getDb())
);
}
} catch (Throwable $e) {
if ($this->handleException($e, $this, $ctx)) {
Expand Down
14 changes: 11 additions & 3 deletions src/Commands/Generics/GenericCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,18 +103,26 @@ private function runCommand(Command $cmd): bool
}
}

$return = $cmd->setLogger($this->logger())->execute(
$nextEvent = $cmd->setLogger($this->logger())->execute(
$this->bot->handleMiddlewares($cmd, $this->ctx())
);

$this->getLogger()->debug('Command {name} ({eventType}) executed', [
'name' => $cmd::class,
'eventType' => $cmd->type()->prettyName(),
]);

// Delete temporary event
if ($cmd instanceof TemporaryEvent) {
$this->bot->deleteEvent($cmd);
}

// Register next conversation
if ($return instanceof Conversation) {
$this->bot->onEvent($return);
if ($nextEvent instanceof Conversation) {
$this->bot->registerConversation(
$nextEvent->setVars($this->botApi, $this->botContext)
->setDb($this->db())
);
}

return true;
Expand Down
15 changes: 15 additions & 0 deletions src/Conversations/Conversation.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,21 @@

interface Conversation extends TemporaryEvent
{
public const UNDEFINED_TTL = null;

/**
* Get the time to live (TTL) value in seconds.
* If can run for unlimited time, return null
*/
public function ttl(): ?int;

/**
* Called when the conversation is expired.
* In this method you only have access to the context where the conversation was created.
* Additional the bot api and the logger are available.
*/
public function onExpired(): void;

/**
* Message format to validate the user answer
*/
Expand Down
24 changes: 17 additions & 7 deletions src/Conversations/ConversationHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

use Mateodioev\StringVars\Matcher;
use Mateodioev\TgHandler\Events\{EventType, abstractEvent};
use Mateodioev\TgHandler\{Bot, RunState};

abstract class ConversationHandler extends abstractEvent implements Conversation
{
Expand All @@ -15,9 +14,11 @@ abstract class ConversationHandler extends abstractEvent implements Conversation
private ?Matcher $pattern = null;
private array $params = [];

protected ?int $ttl = Conversation::UNDEFINED_TTL;

protected function __construct(
private readonly int $chatId,
private readonly int $userId,
protected readonly int $chatId,
protected readonly int $userId,
) {
}

Expand All @@ -26,9 +27,10 @@ protected function __construct(
*/
protected static function create(EventType $type, int $chatId, int $userId): static
{
if (Bot::$state === RunState::webhook) {
/// You can use in webhook mode if you are using servers like amphp or swoole
/* if (Bot::$state === RunState::webhook) {
throw new ConversationException('Can\'t use Conversation handlers while bot is running in webhook mode');
}
} */

return (new static($chatId, $userId))
->setType($type);
Expand Down Expand Up @@ -71,15 +73,23 @@ protected function setType(EventType $type): static
return $this;
}

public function ttl(): ?int
{
return $this->ttl;
}

public function onExpired(): void
{
}

private function getPattern(): Matcher
{
if ($this->pattern instanceof Matcher) {
return $this->pattern;
}

/** @var Matcher $this->pattern */
$this->pattern = new Matcher($this->format());
return $this->pattern;
}

abstract public function execute(array $args = []);
}
4 changes: 2 additions & 2 deletions src/Conversations/MessageConversation.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ abstract class MessageConversation extends ConversationHandler
{
public static function new(int $chatId, int $userId): static
{
return self::create(EventType::message, $chatId, $userId);
return static::create(EventType::message, $chatId, $userId);
}

/**
Expand All @@ -21,6 +21,6 @@ public static function new(int $chatId, int $userId): static
*/
public static function fromContext(Context $ctx): static
{
return self::new($ctx->getChatId(), $ctx->getUserId());
return static::new($ctx->getChatId(), $ctx->getUserId());
}
}
21 changes: 21 additions & 0 deletions src/Conversations/customTTl.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace Mateodioev\TgHandler\Conversations;

trait customTTl
{
protected ?int $customTtl = null;

public function withCustomTTL(int $ttl): static
{
$this->customTtl = $ttl;
return $this;
}

public function ttl(): ?int
{
return $this->customTtl;
}
}
2 changes: 1 addition & 1 deletion src/Events/TemporaryEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace Mateodioev\TgHandler\Events;

/**
* This interface represents a temporary event
* Temporary event is an event that will be removed from the event list after it's processed.
*/
interface TemporaryEvent extends EventInterface
{
Expand Down
19 changes: 19 additions & 0 deletions src/Events/abstractEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,30 @@ public function api(): Api
return $this->botApi;
}

/**
* @internal
*/
public function setApi(Api $api): static
{
$this->botApi = $api;
return $this;
}


public function ctx(): Context
{
return $this->botContext;
}

/**
* @internal
*/
public function setCtx(Context $ctx): static
{
$this->botContext = $ctx;
return $this;
}

public function description(): string
{
return $this->description;
Expand Down
9 changes: 1 addition & 8 deletions src/Log/PhpNativeStream.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@

use function date;
use function error_reporting;
use function fclose;
use function fopen;
use function ini_set;
use function is_dir;
use function restore_error_handler;
Expand Down Expand Up @@ -66,10 +64,6 @@ public function setFile(string $path): PhpNativeStream
{
$this->fileLog = $path;

if (!Files::isFile($path)) {
fclose(fopen($path, 'a')); // create file if not exists
}

return $this;
}

Expand Down Expand Up @@ -114,8 +108,7 @@ public function push(string $message, ?string $level = null): void
protected function write(string $path, string $content): bool
{
try {
$fileContent = File\read($path) . $content;
File\write($path, $fileContent);
File\openFile($path, 'a')->write($content); // Create file if not exists
return true;
} catch (FilesystemException) {
return false;
Expand Down

0 comments on commit 36c81f8

Please sign in to comment.