diff --git a/src/Foundation/RequestInputHandler.php b/src/Foundation/RequestInputHandler.php index 1bc73e6..dba20ab 100644 --- a/src/Foundation/RequestInputHandler.php +++ b/src/Foundation/RequestInputHandler.php @@ -2,6 +2,7 @@ namespace WeStacks\TeleBot\Foundation; +use WeStacks\TeleBot\Objects\Chat; use WeStacks\TeleBot\Objects\User; use WeStacks\TeleBot\TeleBot; @@ -16,17 +17,75 @@ private static function storage(TeleBot $bot): StorageContract public function trigger(): bool { - return static::class == static::storage($this->bot)->get($this->update->user()->id); + $storage = static::storage($this->bot); + + foreach ($this->storageKeys() as $key) { + $value = $storage->get($key); + if ($value) { + return static::class === $value; + } + } + + return false; + } + + private function storageKeys(): array + { + $chatId = $this->update->chat()->id; + $userId = $this->update->user()->id; + + if ($this->update->chat()->type === 'supergroup' && $this->update->message?->message_thread_id) { + $suffix = "message_thread_{$this->update->message->message_thread_id}"; + + return [ + "chat_{$chatId}_user_{$userId}_{$suffix}", + "chat_{$chatId}_{$suffix}", + "user_{$userId}_{$suffix}", + (string)$userId, + ]; + } + + return [ + "chat_{$chatId}_user_{$userId}", + "chat_{$chatId}", + "user_{$userId}", + (string)$userId, + ]; } - public static function request(TeleBot $bot, User $user): bool + /** + * Can be used to set the request input handler for a specific user or chat. + * + * @param User|Chat $target If `User`, awaits a message in ANY chat from SPECIFIC user. If `Chat`, awaits a message in SPECIFIC chat from ANY user. + * @param User|null $user If `$target` is `Chat`, awaits a message in SPECIFIC chat from SPECIFIC user. + * @param int|null $message_thread_id If passed, awaits a message in SPECIFIC message thread. + */ + public static function request(TeleBot $bot, User|Chat $target, ?User $user = null, ?int $message_thread_id = null): bool { - return static::storage($bot)->set($user->id, static::class); + $key = match (true) { + $target instanceof Chat && $user !== null => "chat_{$target->id}_user_{$user->id}", + $target instanceof User => "user_{$target->id}", + $target instanceof Chat => "chat_{$target->id}", + }; + + if ($message_thread_id) { + $key .= "_message_thread_{$message_thread_id}"; + } + + return static::storage($bot)->set($key, static::class); } protected function accept(): bool { - return static::storage($this->bot)->delete($this->update->user()->id); + $storage = static::storage($this->bot); + + foreach ($this->storageKeys() as $key) { + if ($storage->delete($key)) { + return true; + } + } + + return false; } public function __invoke(callable $next) diff --git a/tests/Feature/UpdateHandlingTest.php b/tests/Feature/UpdateHandlingTest.php index fbc70e0..07a5f73 100644 --- a/tests/Feature/UpdateHandlingTest.php +++ b/tests/Feature/UpdateHandlingTest.php @@ -6,7 +6,7 @@ use WeStacks\TeleBot\Objects; use WeStacks\TeleBot\TeleBot; -test('request input handler', function (TeleBot $bot) { +test('request input handler by user', function (TeleBot $bot) { $bot->handler(AskNameHandler::class) ->handler(function (TeleBot $bot, Objects\Update $update, callable $next) { if ($update->message()->text !== '/test' ?? null) { @@ -64,10 +64,28 @@ ] ]); + $wrongUserUpdate = Objects\Update::from([ + 'update_id' => 2, + 'message' => [ + 'message_id' => 5, + 'text' => 'Alan', + 'from' => [ + 'id' => 2, + 'is_bot' => false, + 'first_name' => 'Alan', + ], + 'chat' => [ + 'id' => 1, + 'type' => 'private', + ], + 'date' => 2, + ] + ]); + $correctNameUpdate = Objects\Update::from([ 'update_id' => 3, 'message' => [ - 'message_id' => 5, + 'message_id' => 6, 'text' => 'John', 'from' => [ 'id' => 1, @@ -120,6 +138,415 @@ expect($res)->toBeInstanceOf(Objects\Message::class); expect($res->text)->toBe('Sorry, I don\'t know you.'); + $res = $bot->handle($wrongUserUpdate); + expect($res)->toBeNull(); + + $res = $bot->handle($correctNameUpdate); + expect($res)->toBeInstanceOf(Objects\Message::class); + expect($res->text)->toBe('Hello, John!'); +})->with('bots'); + +test('request input handler by chat', function (TeleBot $bot) { + $bot->handler(AskNameHandler::class) + ->handler(function (TeleBot $bot, Objects\Update $update, callable $next) { + if ($update->message()->text !== '/test' ?? null) { + return $next(); + } + + AskNameHandler::request($bot, $update->chat()); + + return $bot->sendMessage( + chat_id: $update->chat()->id, + text: 'Please enter your name.', + ); + }); + + $initialUpdate = Objects\Update::from([ + 'update_id' => 1, + 'message' => [ + 'message_id' => 1, + 'text' => '/test', + 'from' => [ + 'id' => 1, + 'is_bot' => false, + 'first_name' => 'Alan', + ], + 'chat' => [ + 'id' => 1, + 'type' => 'group', + ], + 'date' => 1, + 'entities' => [ + [ + 'type' => 'bot_command', + 'offset' => 0, + 'length' => 5 + ] + ] + ], + ]); + + $wrongNameUpdate = Objects\Update::from([ + 'update_id' => 2, + 'message' => [ + 'message_id' => 3, + 'text' => 'Alan', + 'from' => [ + 'id' => 2, + 'is_bot' => false, + 'first_name' => 'Alan', + ], + 'chat' => [ + 'id' => 1, + 'type' => 'group', + ], + 'date' => 2, + ] + ]); + + $correctNameUpdate = Objects\Update::from([ + 'update_id' => 3, + 'message' => [ + 'message_id' => 5, + 'text' => 'John', + 'from' => [ + 'id' => 3, + 'is_bot' => false, + 'first_name' => 'Alan', + ], + 'chat' => [ + 'id' => 1, + 'type' => 'group', + ], + 'date' => 4, + ] + ]); + + $bot->fake([ + TeleBotResponse::make(Objects\Message::from([ + 'message_id' => 2, + 'date' => 1, + 'chat' => [ + 'id' => 1, + 'type' => 'private', + ], + 'text' => 'Please enter your name.', + ])), + TeleBotResponse::make(Objects\Message::from([ + 'message_id' => 4, + 'date' => 2, + 'chat' => [ + 'id' => 1, + 'type' => 'private', + ], + 'text' => 'Sorry, I don\'t know you.', + ])), + TeleBotResponse::make(Objects\Message::from([ + 'message_id' => 6, + 'date' => 4, + 'chat' => [ + 'id' => 1, + 'type' => 'private', + ], + 'text' => 'Hello, John!', + ])), + ]); + + $res = $bot->handle($initialUpdate); + expect($res)->toBeInstanceOf(Objects\Message::class); + expect($res->text)->toBe('Please enter your name.'); + + $res = $bot->handle($wrongNameUpdate); + expect($res)->toBeInstanceOf(Objects\Message::class); + expect($res->text)->toBe('Sorry, I don\'t know you.'); + + $res = $bot->handle($correctNameUpdate); + expect($res)->toBeInstanceOf(Objects\Message::class); + expect($res->text)->toBe('Hello, John!'); +})->with('bots'); + +test('request input handler by user and chat', function (TeleBot $bot) { + $bot->handler(AskNameHandler::class) + ->handler(function (TeleBot $bot, Objects\Update $update, callable $next) { + if ($update->message()->text !== '/test' ?? null) { + return $next(); + } + + AskNameHandler::request($bot, $update->chat(), $update->user()); + + return $bot->sendMessage( + chat_id: $update->chat()->id, + text: 'Please enter your name.', + ); + }); + + $initialUpdate = Objects\Update::from([ + 'update_id' => 1, + 'message' => [ + 'message_id' => 1, + 'text' => '/test', + 'from' => [ + 'id' => 1, + 'is_bot' => false, + 'first_name' => 'Alan', + ], + 'chat' => [ + 'id' => 1, + 'type' => 'group', + ], + 'date' => 1, + 'entities' => [ + [ + 'type' => 'bot_command', + 'offset' => 0, + 'length' => 5 + ] + ] + ], + ]); + + $wrongNameUpdate = Objects\Update::from([ + 'update_id' => 2, + 'message' => [ + 'message_id' => 3, + 'text' => 'Alan', + 'from' => [ + 'id' => 1, + 'is_bot' => false, + 'first_name' => 'Alan', + ], + 'chat' => [ + 'id' => 1, + 'type' => 'group', + ], + 'date' => 2, + ] + ]); + + $wrongChatUpdate = Objects\Update::from([ + 'update_id' => 3, + 'message' => [ + 'message_id' => 4, + 'text' => 'Alan', + 'from' => [ + 'id' => 1, + 'is_bot' => false, + 'first_name' => 'Alan', + ], + 'chat' => [ + 'id' => 2, + 'type' => 'group', + ], + 'date' => 3, + ] + ]); + + $correctNameUpdate = Objects\Update::from([ + 'update_id' => 4, + 'message' => [ + 'message_id' => 5, + 'text' => 'John', + 'from' => [ + 'id' => 1, + 'is_bot' => false, + 'first_name' => 'Alan', + ], + 'chat' => [ + 'id' => 1, + 'type' => 'group', + ], + 'date' => 4, + ] + ]); + + $bot->fake([ + TeleBotResponse::make(Objects\Message::from([ + 'message_id' => 2, + 'date' => 1, + 'chat' => [ + 'id' => 1, + 'type' => 'group', + ], + 'text' => 'Please enter your name.', + ])), + TeleBotResponse::make(Objects\Message::from([ + 'message_id' => 4, + 'date' => 2, + 'chat' => [ + 'id' => 1, + 'type' => 'group', + ], + 'text' => 'Sorry, I don\'t know you.', + ])), + TeleBotResponse::make(Objects\Message::from([ + 'message_id' => 6, + 'date' => 4, + 'chat' => [ + 'id' => 1, + 'type' => 'group', + ], + 'text' => 'Hello, John!', + ])), + ]); + + $res = $bot->handle($initialUpdate); + expect($res)->toBeInstanceOf(Objects\Message::class); + expect($res->text)->toBe('Please enter your name.'); + + $res = $bot->handle($wrongNameUpdate); + expect($res)->toBeInstanceOf(Objects\Message::class); + expect($res->text)->toBe('Sorry, I don\'t know you.'); + + $res = $bot->handle($wrongChatUpdate); + expect($res)->toBeNull(); + + $res = $bot->handle($correctNameUpdate); + expect($res)->toBeInstanceOf(Objects\Message::class); + expect($res->text)->toBe('Hello, John!'); +})->with('bots'); + +test('request input handler by message thread', function (TeleBot $bot) { + $bot->handler(AskNameHandler::class) + ->handler(function (TeleBot $bot, Objects\Update $update, callable $next) { + if ($update->message()->text !== '/test' ?? null) { + return $next(); + } + + AskNameHandler::request($bot, $update->user(), message_thread_id: $update->message->message_thread_id); + + return $bot->sendMessage( + chat_id: $update->chat()->id, + text: 'Please enter your name.', + ); + }); + + $initialUpdate = Objects\Update::from([ + 'update_id' => 1, + 'message' => [ + 'message_id' => 1, + 'message_thread_id' => 1, + 'text' => '/test', + 'from' => [ + 'id' => 1, + 'is_bot' => false, + 'first_name' => 'Alan', + ], + 'chat' => [ + 'id' => 1, + 'type' => 'supergroup', + ], + 'date' => 1, + 'entities' => [ + [ + 'type' => 'bot_command', + 'offset' => 0, + 'length' => 5 + ] + ] + ], + ]); + + $wrongNameUpdate = Objects\Update::from([ + 'update_id' => 2, + 'message' => [ + 'message_id' => 3, + 'message_thread_id' => 1, + 'text' => 'Alan', + 'from' => [ + 'id' => 1, + 'is_bot' => false, + 'first_name' => 'Alan', + ], + 'chat' => [ + 'id' => 1, + 'type' => 'supergroup', + ], + 'date' => 2, + ] + ]); + + $wrongChatUpdate = Objects\Update::from([ + 'update_id' => 3, + 'message' => [ + 'message_id' => 3, + 'message_thread_id' => 2, + 'text' => 'Alan', + 'from' => [ + 'id' => 1, + 'is_bot' => false, + 'first_name' => 'Alan', + ], + 'chat' => [ + 'id' => 1, + 'type' => 'supergroup', + ], + 'date' => 2, + ] + ]); + + $correctNameUpdate = Objects\Update::from([ + 'update_id' => 4, + 'message' => [ + 'message_id' => 5, + 'message_thread_id' => 1, + 'text' => 'John', + 'from' => [ + 'id' => 1, + 'is_bot' => false, + 'first_name' => 'Alan', + ], + 'chat' => [ + 'id' => 1, + 'type' => 'supergroup', + ], + 'date' => 4, + ] + ]); + + $bot->fake([ + TeleBotResponse::make(Objects\Message::from([ + 'message_id' => 2, + 'message_thread_id' => 1, + 'date' => 1, + 'chat' => [ + 'id' => 1, + 'type' => 'supergroup', + ], + 'text' => 'Please enter your name.', + ])), + TeleBotResponse::make(Objects\Message::from([ + 'message_id' => 4, + 'message_thread_id' => 1, + 'date' => 2, + 'chat' => [ + 'id' => 1, + 'type' => 'supergroup', + ], + 'text' => 'Sorry, I don\'t know you.', + ])), + TeleBotResponse::make(Objects\Message::from([ + 'message_id' => 6, + 'message_thread_id' => 1, + 'date' => 4, + 'chat' => [ + 'id' => 1, + 'type' => 'supergroup', + ], + 'text' => 'Hello, John!', + ])), + ]); + + $res = $bot->handle($initialUpdate); + expect($res)->toBeInstanceOf(Objects\Message::class); + expect($res->text)->toBe('Please enter your name.'); + + $res = $bot->handle($wrongNameUpdate); + expect($res)->toBeInstanceOf(Objects\Message::class); + expect($res->text)->toBe('Sorry, I don\'t know you.'); + + $res = $bot->handle($wrongChatUpdate); + expect($res)->toBeNull(); + $res = $bot->handle($correctNameUpdate); expect($res)->toBeInstanceOf(Objects\Message::class); expect($res->text)->toBe('Hello, John!');