diff --git a/composer.json b/composer.json index a3af4fc..41b6c23 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,8 @@ "require": { "php": "^8.2", "nunomaduro/termwind": "^1.0|^2.0", - "nutgram/nutgram": "^4.17.0" + "nutgram/nutgram": "^4.17.0", + "spatie/file-system-watcher": "^1.2" }, "require-dev": { "illuminate/testing": "^9.0|^10.0|^11.0|^12.0", diff --git a/config/nutgram.php b/config/nutgram.php index 2134833..8d14735 100644 --- a/config/nutgram.php +++ b/config/nutgram.php @@ -22,4 +22,9 @@ // Set log channel 'log_channel' => env('TELEGRAM_LOG_CHANNEL', 'null'), + + // Watch paths used by the "nutgram:run --watch" command + 'watch_paths' => [ + app_path('Telegram'), + ], ]; diff --git a/src/Console/RunCommand.php b/src/Console/RunCommand.php index 1d7830e..12f2abe 100644 --- a/src/Console/RunCommand.php +++ b/src/Console/RunCommand.php @@ -3,22 +3,88 @@ namespace Nutgram\Laravel\Console; use Illuminate\Console\Command; +use Symfony\Component\Process\PhpSubprocess; +use Symfony\Component\Process\Process; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; use SergiX44\Nutgram\Nutgram; +use Spatie\Watcher\Watch; class RunCommand extends Command { - protected $signature = 'nutgram:run'; + protected $signature = 'nutgram:run {--watch : Watch for changes and restart the bot} + {--without-tty : Disable output to TTY}'; protected $description = 'Start the bot in long polling mode'; + protected ?Process $runProcess = null; + /** * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface */ - public function handle(Nutgram $bot): void + public function handle(Nutgram $bot): int + { + if (!$this->option('watch')) { + $bot->run(); + + return Command::SUCCESS; + } + + $this->info('Watching for changes...'); + + if(!$this->startAsyncRun()){ + return Command::FAILURE; + } + + $this->listenForChanges(); + + return Command::SUCCESS; + } + + protected function startAsyncRun(): bool + { + $this->runProcess = (new PhpSubprocess(['artisan', 'nutgram:run', '--ansi'])) + ->setTty(Process::isTtySupported() && !$this->option('without-tty')) + ->setTimeout(null); + + $this->runProcess->start(function (string $type, string $output) { + if(Process::isTtySupported() && !$this->option('without-tty')) { + $this->output->write($output); + } + }); + + return ! $this->runProcess->isTerminated(); + } + + protected function listenForChanges(): self + { + Watch::paths(...config('nutgram.watch_paths', [])) + ->setIntervalTime(200 * 1000) + ->onAnyChange(function (string $event, string $path) { + if ($this->isPhpFile($path)) { + $this->restartAsyncRun(); + } + }) + ->start(); + + return $this; + } + + protected function isPhpFile(string $path): bool { - $bot->run(); + return str_ends_with(strtolower($path), '.php'); + } + + protected function restartAsyncRun(): self + { + $this->components->info('Changes detected! Restarting bot...'); + + $this->runProcess->stop(); + $this->runProcess->wait(); + + $this->startAsyncRun(); + + return $this; } }