From aa00e5bd9700df9aa73a409debe0463767a8ae07 Mon Sep 17 00:00:00 2001 From: "Abdulmhsen B. A. A" Date: Thu, 3 Mar 2022 15:30:36 +0300 Subject: [PATCH] Renamed :webhook command to :edit to better reflect the intention of the command and to expand the configurable settings via the command. --- README.md | 20 +- config/commands.php | 2 +- src/Commands/Servers/EditCommand.php | 298 ++++++++++++++++++++++++ src/Commands/Servers/ListCommand.php | 10 +- src/Commands/Servers/WebhookCommand.php | 170 -------------- src/Libs/helpers.php | 14 +- 6 files changed, 332 insertions(+), 182 deletions(-) create mode 100644 src/Commands/Servers/EditCommand.php delete mode 100644 src/Commands/Servers/WebhookCommand.php diff --git a/README.md b/README.md index a83770b8..e5dc191d 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ To start receiving webhook events from servers you need to do few more steps. Run the following commands to generate api key for each server ```sh -$ docker exec -ti watchstate console servers:webhook [SERVER_NAME] --status=enable --push==enable --generate +$ docker exec -ti watchstate console servers:edit [SERVER_NAME] --webhook-import-status=enable --webhook-push-status=enable --webhook-key-generate Server '[SERVER_NAME]' Webhook API key is: random_string ``` @@ -90,8 +90,22 @@ same API key, you still keep them separate but have the same `webhook.token` val to whitelist IPs for each server. ```bash -docker exec -ti watchstate console servers:webhook [PLEX_SERVER_1] --require-ips 'comma seperated list of ips and CIDR' -docker exec -ti watchstate console servers:webhook [PLEX_SERVER_2] --require-ips '10.0.0.0/8,172.23.0.1,etc...' +docker exec -ti watchstate console servers:edit [PLEX_SERVER_1] --webhook-require-ips 'comma seperated list of ips/CIDR' +docker exec -ti watchstate console servers:edit [PLEX_SERVER_2] --webhook-require-ips '10.0.0.0/8,172.23.0.1,etc...' +``` + +You can also use server unique ID to identify the server, however you have to get the server ID manually by visiting +server settings > General then look at the ID in the URL for example + +`https://app.plex.tv/desktop/#!/settings/server/[RANDOM_STRING]/settings/general` + +##### [RANDOM_STRING] + +will be a randomly generated string that looks like the 2nd example. + +```bash +docker exec -ti watchstate console servers:edit [PLEX_SERVER_1] --webhook-server-uuid '[RANDOM_STRING]' +docker exec -ti watchstate console servers:edit [PLEX_SERVER_2] --webhook-server-uuid '0d4d365add01145852146d0c25b3776c1effc507' ``` The reason for this is, because Plex link webhook to user account instead of server, as such for all servers you have diff --git a/config/commands.php b/config/commands.php index a4a3cd4f..a5f63bc2 100644 --- a/config/commands.php +++ b/config/commands.php @@ -15,5 +15,5 @@ return [ 'scheduler:closure' => App\Commands\Scheduler\RunClosure::class, 'webhooks:queued' => App\Commands\State\QueueCommand::class, 'servers:list' => App\Commands\Servers\ListCommand::class, - 'servers:webhook' => App\Commands\Servers\WebhookCommand::class, + 'servers:edit' => App\Commands\Servers\EditCommand::class, ]; diff --git a/src/Commands/Servers/EditCommand.php b/src/Commands/Servers/EditCommand.php new file mode 100644 index 00000000..3d59ad02 --- /dev/null +++ b/src/Commands/Servers/EditCommand.php @@ -0,0 +1,298 @@ + true, + 'enable' => true, + 'yes' => true, + 'disabled' => false, + 'disable' => false, + 'no' => false, + ]; + + protected function configure(): void + { + $values = implode('|', array_keys(self::ON_OFF_FLAGS)); + + $this->setName('servers:edit') + ->setDescription('Edit Server settings.') + ->addOption( + 'type', + null, + InputOption::VALUE_REQUIRED, + sprintf( + 'Change server type. Expected value is one of [%s]', + implode('|', array_keys(Config::get('supported', []))) + ) + ) + ->addOption( + 'url', + null, + InputOption::VALUE_REQUIRED, + 'Change server url.' + ) + ->addOption( + 'token', + null, + InputOption::VALUE_REQUIRED, + 'Change server API key.' + ) + ->addOption( + 'user', + null, + InputOption::VALUE_REQUIRED, + 'Change server User Id.' + ) + ->addOption( + 'export-status', + null, + InputOption::VALUE_REQUIRED, + sprintf('Enable/Disable manual exporting to this server. Expected value is one of [%s]', $values) + ) + ->addOption( + 'import-status', + null, + InputOption::VALUE_REQUIRED, + sprintf('Enable/Disable manual importing from this server. Expected value is one of [%s]', $values) + ) + ->addOption( + 'webhook-key-generate', + null, + InputOption::VALUE_NONE, + 'Generate API key for this server. *WILL NOT* override existing key.' + ) + ->addOption( + 'webhook-key-regenerate', + null, + InputOption::VALUE_NONE, + 'Regenerate API key, it will invalidate old keys please update related server config.' + ) + ->addOption( + 'webhook-key-length', + null, + InputOption::VALUE_OPTIONAL, + 'Change default API key random generator length.', + (int)Config::get('webhook.keyLength', 16) + ) + ->addOption( + 'webhook-import-status', + null, + InputOption::VALUE_REQUIRED, + sprintf('Enable/Disable the webhook api for this server. Expected value is one of [%s]', $values) + ) + ->addOption( + 'webhook-require-ips', + null, + InputOption::VALUE_REQUIRED, + 'Comma seperated ips/CIDR to link a server to specific ips. Useful for Multi Plex servers setup.', + ) + ->addOption( + 'webhook-server-uuid', + null, + InputOption::VALUE_REQUIRED, + 'Limit this server specific UUID. Useful for Multi Plex servers setup.', + ) + ->addOption( + 'webhook-push-status', + null, + InputOption::VALUE_REQUIRED, + sprintf('Enable/Disable pushing to this server on webhook events. Expected value are [%s]', $values) + ) + ->addOption('use-config', null, InputOption::VALUE_REQUIRED, 'Use different servers.yaml.') + ->addArgument('name', InputArgument::REQUIRED, 'Server name'); + } + + /** + * @throws Exception + */ + protected function runCommand(InputInterface $input, OutputInterface $output): int + { + // -- Use Custom servers.yaml file. + if (($config = $input->getOption('use-config'))) { + if (!is_string($config) || !is_file($config) || !is_readable($config)) { + throw new RuntimeException('Unable to read data given config.'); + } + Config::save('servers', Yaml::parseFile($config)); + } else { + $config = Config::get('path') . '/config/servers.yaml'; + } + + $name = $input->getArgument('name'); + $ref = "servers.{$name}"; + + if (null === Config::get("{$ref}.type", null)) { + throw new RuntimeException(sprintf('No server named \'%s\' was found in %s.', $name, $config)); + } + + // -- $type + if ($input->getOption('type')) { + if (!array_key_exists($input->getOption('type'), Config::get('supported', []))) { + $output->writeln( + sprintf( + 'Unexpected value for --type, was expecting one of [%s] but got \'%s\' instead.', + implode('|', array_keys(Config::get('supported', []))), + $input->getOption('type') + ) + ); + return self::INVALID; + } + + Config::save("{$ref}.type", $input->getOption('type')); + } + + // -- $ref.url + if ($input->getOption('url')) { + if (!filter_var($input->getOption('url'), FILTER_VALIDATE_URL)) { + $output->writeln(sprintf('Invalid --url value \'%s\' was given.', $input->getOption('url'))); + return self::INVALID; + } + + Config::save("{$ref}.url", $input->getOption('url')); + } + + // -- $ref.user + if ($input->getOption('user')) { + if (!is_string($input->getOption('user')) && !is_int($input->getOption('user'))) { + $output->writeln( + sprintf( + 'Expecting --user value to be string or integer. but got \'%s\' instead.', + get_debug_type($input->getOption('user')) + ) + ); + return self::INVALID; + } + + Config::save("{$ref}.user", $input->getOption('user')); + } + + // -- $ref.token + if ($input->getOption('token')) { + if (!is_string($input->getOption('token')) && !is_int($input->getOption('token'))) { + $output->writeln( + sprintf( + 'Expecting --token value to be string or integer. but got \'%s\' instead.', + get_debug_type($input->getOption('token')) + ) + ); + return self::INVALID; + } + + Config::save("{$ref}.token", $input->getOption('token')); + } + + // -- $ref.export.enabled + if ($input->getOption('export-status')) { + $statusName = strtolower($input->getOption('export-status')); + + if (!array_key_exists($statusName, self::ON_OFF_FLAGS)) { + $output->writeln( + sprintf( + 'Unexpected value for --export-status, was expecting one of [%s] but got \'%s\' instead.', + implode('|', array_keys(self::ON_OFF_FLAGS)), + $statusName + ) + ); + return self::INVALID; + } + + Config::save("{$ref}.export.enabled", (bool)self::ON_OFF_FLAGS[$statusName]); + } + + // -- $ref.import.enabled + if ($input->getOption('import-status')) { + $statusName = strtolower($input->getOption('import-status')); + + if (!array_key_exists($statusName, self::ON_OFF_FLAGS)) { + $output->writeln( + sprintf( + 'Unexpected value for --import-status, was expecting one of [%s] but got \'%s\' instead.', + implode('|', array_keys(self::ON_OFF_FLAGS)), + $statusName + ) + ); + return self::INVALID; + } + + Config::save("{$ref}.export.enabled", (bool)self::ON_OFF_FLAGS[$statusName]); + } + + // -- $ref.webhook.token + if ($input->getOption('webhook-key-generate') || $input->getOption('webhook-key-regenerate')) { + if (!Config::get("{$ref}.webhook.token") || $input->getOption('webhook-key-regenerate')) { + $apiToken = bin2hex(random_bytes($input->getOption('api-key-length'))); + + $output->writeln(sprintf('Server \'%s\' Webhook API key is: %s', $name, $apiToken)); + + Config::save("{$ref}.webhook.token", $apiToken); + } + } + + // -- $ref.webhook.enabled + if ($input->getOption('webhook-import-status')) { + $statusName = strtolower($input->getOption('webhook-import-status')); + + if (!array_key_exists($statusName, self::ON_OFF_FLAGS)) { + $output->writeln( + sprintf( + 'Unexpected value for --webhook-import-status, was expecting one of [%s] but got \'%s\' instead.', + implode('|', array_keys(self::ON_OFF_FLAGS)), + $statusName + ) + ); + return self::INVALID; + } + + $status = self::ON_OFF_FLAGS[$statusName]; + + Config::save($ref . '.webhook.import', (bool)$status); + } + + // -- $ref.webhook.push + if ($input->getOption('webhook-push-status')) { + $statusName = strtolower($input->getOption('webhook-push-status')); + + if (!array_key_exists($statusName, self::ON_OFF_FLAGS)) { + $output->writeln( + sprintf( + 'Unexpected value for --webhook-push-status, was expecting one of [%s] but got \'%s\' instead.', + implode('|', array_keys(self::ON_OFF_FLAGS)), + $statusName + ) + ); + + return self::INVALID; + } + + Config::save($ref . '.webhook.push', (bool)self::ON_OFF_FLAGS[$statusName]); + } + + // -- $ref.webhook.ips + if ($input->getOption('webhook-require-ips')) { + Config::save($ref . '.webhook.ips', explode(',', $input->getOption('webhook-require-ips'))); + } + + // -- $ref.webhook.uuid + if ($input->getOption('webhook-server-uuid')) { + Config::save($ref . '.webhook.uuid', $input->getOption('webhook-server-uuid')); + } + + file_put_contents($config, Yaml::dump(Config::get('servers', []), 8, 2)); + + return self::SUCCESS; + } +} diff --git a/src/Commands/Servers/ListCommand.php b/src/Commands/Servers/ListCommand.php index 8b3d1081..fe6001c3 100644 --- a/src/Commands/Servers/ListCommand.php +++ b/src/Commands/Servers/ListCommand.php @@ -44,9 +44,10 @@ final class ListCommand extends Command 'Name', 'Type', 'URL', - 'Webhook', - 'Last Import at', - 'Last Export at' + 'WH Import', + 'WH Push', + 'Last Manual Import at', + 'Last Manual Export at' ] ); @@ -55,7 +56,8 @@ final class ListCommand extends Command $name, ag($server, 'type'), ag($server, 'url'), - ag($server, 'webhook.token') && ag($server, 'webhook.enabled') ? 'Enabled' : 'Disabled', + ag($server, 'webhook.token') && ag($server, 'webhook.import') ? 'Enabled' : 'Disabled', + true === ag($server, 'webhook.push') ? 'Enabled' : 'Disabled', ($lastImportSync = ag($server, 'import.lastSync')) ? makeDate($lastImportSync) : 'Never', ($lastExportSync = ag($server, 'export.lastSync')) ? makeDate($lastExportSync) : 'Never', ]; diff --git a/src/Commands/Servers/WebhookCommand.php b/src/Commands/Servers/WebhookCommand.php deleted file mode 100644 index 0cf7923c..00000000 --- a/src/Commands/Servers/WebhookCommand.php +++ /dev/null @@ -1,170 +0,0 @@ - true, - 'enable' => true, - 'yes' => true, - 'disabled' => false, - 'disable' => false, - 'no' => false, - ]; - - protected function configure(): void - { - $this->setName('servers:webhook') - ->setDescription('Change Server Webhook settings.') - ->addOption( - 'api-key-generate', - 'g', - InputOption::VALUE_NONE, - 'Generate API key for this server. *WILL NOT* override existing key.' - ) - ->addOption( - 'api-key-regenerate', - 'r', - InputOption::VALUE_NONE, - 'Regenerate API key, it will invalidate old keys please update related server config.' - ) - ->addOption( - 'api-key-length', - 'l', - InputOption::VALUE_OPTIONAL, - 'Change default API key random generator length.', - (int)Config::get('webhook.keyLength', 16) - ) - ->addOption( - 'status', - null, - InputOption::VALUE_REQUIRED, - sprintf( - 'Enable/Disable the webhook api for this server. Expected value are [%s]', - implode('|', array_keys(self::WEBHOOK_STATUS_VALUES)) - ) - ) - ->addOption( - 'require-ips', - null, - InputOption::VALUE_REQUIRED, - 'Comma seperated ips/cdr to link a server to specific ips. Mainly for Plex.', - ) - ->addOption( - 'push', - null, - InputOption::VALUE_REQUIRED, - sprintf( - 'Enable/Disable pushing to this server on webhook events. Expected value are [%s]', - implode('|', array_keys(self::WEBHOOK_STATUS_VALUES)) - ) - ) - ->addOption('use-config', null, InputOption::VALUE_REQUIRED, 'Use different servers.yaml.') - ->addArgument('name', InputArgument::REQUIRED, 'Server name'); - } - - /** - * @throws Exception - */ - protected function runCommand(InputInterface $input, OutputInterface $output): int - { - // -- Use Custom servers.yaml file. - if (($config = $input->getOption('use-config'))) { - if (!is_string($config) || !is_file($config) || !is_readable($config)) { - throw new RuntimeException('Unable to read data given config.'); - } - Config::save('servers', Yaml::parseFile($config)); - } else { - $config = Config::get('path') . '/config/servers.yaml'; - } - - $name = $input->getArgument('name'); - $ref = "servers.{$name}"; - - if (null === Config::get("{$ref}.type", null)) { - throw new RuntimeException(sprintf('No server named \'%s\' was found in %s.', $name, $config)); - } - - // -- webhook.token - if ($input->getOption('api-key-generate') || $input->getOption('api-key-regenerate')) { - if (!Config::get("{$ref}.webhook.token") || $input->getOption('api-key-regenerate')) { - $apiToken = bin2hex(random_bytes($input->getOption('api-key-length'))); - - $output->writeln(sprintf('Server \'%s\' Webhook API key is: %s', $name, $apiToken)); - - Config::save("{$ref}.webhook.token", $apiToken); - } - } - - // -- webhook.enabled - if ($input->getOption('status')) { - $statusName = strtolower($input->getOption('status')); - - if (!array_key_exists($statusName, self::WEBHOOK_STATUS_VALUES)) { - throw new RuntimeException( - sprintf( - 'Invalid value was given to --status \'%s\', expected values are [%s]', - $statusName, - implode(', ', array_keys(self::WEBHOOK_STATUS_VALUES)) - ) - ); - } - - $status = self::WEBHOOK_STATUS_VALUES[$statusName]; - - if (true === $status && !Config::get($ref . '.webhook.token')) { - $output->writeln( - sprintf( - 'You must generate api key for this server \'%s\' using [-g, --generate] flag before you can start using the Webhook API.', - $name - ) - ); - - return self::INVALID; - } - - Config::save($ref . '.webhook.enabled', (bool)$status); - } - - // -- webhook.push - if ($input->getOption('push')) { - $statusName = strtolower($input->getOption('push')); - - if (!array_key_exists($statusName, self::WEBHOOK_STATUS_VALUES)) { - throw new RuntimeException( - sprintf( - 'Invalid value was given to --push \'%s\', expected values are [%s]', - $statusName, - implode(', ', array_keys(self::WEBHOOK_STATUS_VALUES)) - ) - ); - } - - $status = self::WEBHOOK_STATUS_VALUES[$statusName]; - - Config::save($ref . '.webhook.push', (bool)$status); - } - - // -- webhook.ips - if ($input->getOption('require-ips')) { - Config::save($ref . '.webhook.ips', explode(',', $input->getOption('require-ips'))); - } - - file_put_contents($config, Yaml::dump(Config::get('servers', []), 8, 2)); - - return self::SUCCESS; - } -} diff --git a/src/Libs/helpers.php b/src/Libs/helpers.php index c1f173cc..4a00ab64 100644 --- a/src/Libs/helpers.php +++ b/src/Libs/helpers.php @@ -294,7 +294,6 @@ if (!function_exists('preServeHttpRequest')) { if (!function_exists('serveHttpRequest')) { function serveHttpRequest(ServerRequestInterface $request): ResponseInterface { - $logger = Container::get(LoggerInterface::class); try { @@ -330,6 +329,12 @@ if (!function_exists('serveHttpRequest')) { continue; } + $uuid = ag($info, 'webhook.uuid', null); + + if (null !== $uuid && $uuid !== $request->getAttribute('SERVER_ID', null)) { + continue; + } + $server = array_replace_recursive(['name' => $name], $info); break; } @@ -338,12 +343,13 @@ if (!function_exists('serveHttpRequest')) { throw new HttpException('Invalid API key was given.', 401); } - if (true !== ag($server, 'webhook.enabled')) { + if (true !== ag($server, 'webhook.import')) { throw new HttpException( sprintf( - 'Webhook API is disabled for \'%s\' via config.', + 'Import via webhook for this server \'%s\' is disabled.', ag($server, 'name') - ), 500 + ), + 500 ); }