From 674d9af124f03ccc42839a522fbb56dad9d57421 Mon Sep 17 00:00:00 2001 From: "Abdulmhsen B. A. A" Date: Tue, 12 Jul 2022 19:19:15 +0300 Subject: [PATCH] Updated config:* and backend:* help doc --- src/Command.php | 18 +- src/Commands/Backend/Ignore/ListCommand.php | 16 +- src/Commands/Backend/Ignore/ManageCommand.php | 42 ++-- .../Backend/Library/IgnoreCommand.php | 30 +-- src/Commands/Backend/Library/ListCommand.php | 10 +- .../Backend/Library/MismatchCommand.php | 24 +- .../Backend/Library/UnmatchedCommand.php | 14 +- src/Commands/Backend/RestoreCommand.php | 54 +++-- src/Commands/Backend/Search/IdCommand.php | 28 ++- src/Commands/Backend/Search/QueryCommand.php | 28 ++- src/Commands/Backend/Users/ListCommand.php | 28 ++- src/Commands/Config/AddCommand.php | 23 +- src/Commands/Config/EditCommand.php | 51 +++- src/Commands/Config/ListCommand.php | 10 +- src/Commands/Config/ManageCommand.php | 227 ++++++++++-------- src/Commands/Config/UnifyCommand.php | 60 ++++- src/Commands/Config/ViewCommand.php | 35 ++- 17 files changed, 485 insertions(+), 213 deletions(-) diff --git a/src/Command.php b/src/Command.php index 5cf3f618..41290558 100644 --- a/src/Command.php +++ b/src/Command.php @@ -131,25 +131,25 @@ class Command extends BaseCommand { if (!file_exists($config) || !is_file($config)) { throw new RuntimeException( - sprintf('ERROR: Config file \'%s\' does not exists.', $config) + r('Config file [{config}] does not exists.', [ + 'config' => $config + ]) ); } if (!is_readable($config)) { throw new RuntimeException( - sprintf( - 'ERROR: Unable to read config file \'%s\'. (Check Permissions)', - $config - ) + r('Unable to read config file [{config}]. (Check Permissions)', [ + 'config' => $config + ]) ); } if (!is_writable($config)) { throw new RuntimeException( - sprintf( - 'ERROR: Unable to edit config file \'%s\'. (Check Permissions)', - $config - ) + r('Unable to edit config file [{config}]. (Check Permissions)', [ + 'config' => $config + ]) ); } diff --git a/src/Commands/Backend/Ignore/ListCommand.php b/src/Commands/Backend/Ignore/ListCommand.php index e726373f..05e1c445 100644 --- a/src/Commands/Backend/Ignore/ListCommand.php +++ b/src/Commands/Backend/Ignore/ListCommand.php @@ -48,19 +48,19 @@ final class ListCommand extends Command <<--type, --backend, --db, --id]. +using one or more of the provided options like [--type, --backend, --db, --id]. ------------- -[ Examples ] ------------- +------- +[ FAQ ] +------- -# List all ignore rules that relate to specific backend. +# List all ignore rules that relate to specific backend. -{cmd} {route} --backend my_backend +{cmd} {route} --backend BACKEND_NAME -# Appending more filters to narrow down list +# Appending more filters to narrow down list -{cmd} {route} --backend my_backend --db tvdb +{cmd} {route} --backend BACKEND_NAME --db tvdb HELP, [ diff --git a/src/Commands/Backend/Ignore/ManageCommand.php b/src/Commands/Backend/Ignore/ManageCommand.php index 6529e728..1bd9d78a 100644 --- a/src/Commands/Backend/Ignore/ManageCommand.php +++ b/src/Commands/Backend/Ignore/ManageCommand.php @@ -36,43 +36,43 @@ This command allow you to ignore specific external id from backend. This helps when there is a conflict between your media servers provided external ids. Generally this should only be used as last resort. You should try to fix the source of the problem. -The id format is: type://db:id@backend[?id=backend_id] +The id format is: type://db:id@backend[?id=backend_item_id] ------------------- -[ Expected Values ] +[ Expected Values ] ------------------- -type expects the value to be one of [{listOfTypes}] -db expects the value to be one of [{supportedGuids}] -backend expects the value to be one of [{listOfBackends}] +type expects the value to be one of [{listOfTypes}] +db expects the value to be one of [{supportedGuids}] +backend expects the value to be one of [{listOfBackends}] ------- -[ FAQ ] +[ FAQ ] ------- -# Adding external id to ignore list +# Adding external id to ignore list -To ignore tvdb id 320234 from my_backend backend you would do something like +To ignore tvdb id 320234 from my_backend you would do something like -{cmd} {route} show://tvdb:320234@my_backend +{cmd} {route} show://tvdb:320234@my_backend -If you want to limit this rule to specific item id you would add [?id=backend_id] to the rule, for example +If you want to limit this rule to specific item id you would add [?id=backend_item_id] to the rule, for example -{cmd} {route} show://tvdb:320234@my_backend?id=1212111 +{cmd} {route} show://tvdb:320234@my_backend?id=1212111 -This will ignore [tvdb://320234] id only when the context id = [1212111] +This will ignore the external id [tvdb://320234] only when the context id = [1212111] -# Removing external id from ignore list +# Removing external id from ignore list -To Remove an external id from ignore list just append [-r, --remove] to the command. For example, +To Remove an external id from ignore list just append [-r, --remove] to the command. For example, -{cmd} {route} --remove episode://tvdb:320234@my_backend +{cmd} {route} --remove episode://tvdb:320234@my_backend -The id should match what was added exactly. +The id should match what was added. -# ignore.yaml file location +# ignore.yaml file location -By default, it should be at [{ignoreListFile}] +By default, it should be at [{ignoreListFile}] HELP, [ @@ -81,16 +81,16 @@ HELP, 'ignoreListFile' => Config::get('path') . '/config/ignore.yaml', 'supportedGuids' => implode( ', ', - array_map(fn($val) => '' . after($val, 'guid_') . '', + array_map(fn($val) => '' . after($val, 'guid_') . '', array_keys(Guid::getSupported(includeVirtual: false))) ), 'listOfTypes' => implode( ', ', - array_map(fn($val) => '' . after($val, 'guid_') . '', iState::TYPES_LIST) + array_map(fn($val) => '' . after($val, 'guid_') . '', iState::TYPES_LIST) ), 'listOfBackends' => implode( ', ', - array_map(fn($val) => '' . after($val, 'guid_') . '', + array_map(fn($val) => '' . after($val, 'guid_') . '', array_keys(Config::get('servers', []))) ), ] diff --git a/src/Commands/Backend/Library/IgnoreCommand.php b/src/Commands/Backend/Library/IgnoreCommand.php index 73cdfa2b..e130e5c3 100644 --- a/src/Commands/Backend/Library/IgnoreCommand.php +++ b/src/Commands/Backend/Library/IgnoreCommand.php @@ -31,34 +31,34 @@ final class IgnoreCommand extends Command r( <<[ FAQ ] - ------- +------- +[ FAQ ] +------- - # I can't use interaction is there different way to ignore library? +# I can't use interaction is there different way to ignore library? - Yes, you can do it the manual way, First get list of your libraries ids, and to do so run the following command +Yes, First get your libraries ids by running this command: - {cmd} {library_list} -- [BACKEND_NAME] +{cmd} {library_list} BACKEND_NAME - You are mainly interested in the Id column, once you have list of your ids, you can run the following command - to update the ignorelist. the [options.ignore] key accept comma seperated list of ids. +You are mainly interested in the Id column, once you have list of your ids, you can run the following +command to update the backend ignorelist. - {cmd} {backend_edit} --key 'options.ignore' --set 'id1,id2,id3' -- [BACKEND_NAME] +{cmd} {backend_edit} --key 'options.ignore' --set 'id1,id2,id3' BACKEND_NAME - You can also directly update the [servers.yaml] file found in [{configPath}], - although it's not recommended to manually edit the file. +You can also directly update the config file at [{configPath}]. +The [options.ignore] key accept comma seperated list of ids. - HELP, +HELP, [ 'cmd' => trim(commandContext()), 'library_list' => ListCommand::ROUTE, 'backend_edit' => EditCommand::ROUTE, - 'configPath' => Config::get('path') . '/config', + 'configPath' => Config::get('path') . '/config/servers.yaml', ] ) ); diff --git a/src/Commands/Backend/Library/ListCommand.php b/src/Commands/Backend/Library/ListCommand.php index fd0ebba6..c9b0a17a 100644 --- a/src/Commands/Backend/Library/ListCommand.php +++ b/src/Commands/Backend/Library/ListCommand.php @@ -26,7 +26,15 @@ final class ListCommand extends Command ->setDescription('Get Backend libraries list.') ->addOption('include-raw-response', null, InputOption::VALUE_NONE, 'Include unfiltered raw response.') ->addOption('config', 'c', InputOption::VALUE_REQUIRED, 'Use Alternative config file.') - ->addArgument('backend', InputArgument::REQUIRED, 'Backend name.'); + ->addArgument('backend', InputArgument::REQUIRED, 'Backend name.') + ->setHelp( + <<backend library id. + +HELP + ); } protected function runCommand(InputInterface $input, OutputInterface $output): int diff --git a/src/Commands/Backend/Library/MismatchCommand.php b/src/Commands/Backend/Library/MismatchCommand.php index 08d420b8..25cb86c6 100644 --- a/src/Commands/Backend/Library/MismatchCommand.php +++ b/src/Commands/Backend/Library/MismatchCommand.php @@ -85,25 +85,25 @@ final class MismatchCommand extends Command r( <<Movies and Series in library items. +This command help find possible mis-matched Movies and Series in library items. -This command require Plex Naming Standard and assume the reported [title, year] somewhat matches the reported media path. +This command require Plex Naming Standard and assume the reported [title, year] somewhat matches the reported media path. -We remove text contained within {} and [] brackets, as well as this characters: +We remove text contained within {} and [] brackets, as well as this characters: [{removedList}] -Plex naming standard for Movies is: -/storage/movies/Movie Title (Year)/Movie Title (Year) [Tags].ext +Plex naming standard for Movies is: +/storage/movies/Movie Title (Year)/Movie Title (Year) [Tags].ext -Plex naming standard for Series is: -/storage/series/Series Title (Year) +Plex naming standard for Series is: +/storage/series/Series Title (Year) ------------------- -[ Expected Values ] +[ Expected Values ] ------------------- -percentage expects the value to be [number]. [Default: {defaultPercent}]. -method expects the value to be one of [{methodsList}]. [Default: {DefaultMethod}]. +percentage expects the value to be [number]. [Default: {defaultPercent}]. +method expects the value to be one of [{methodsList}]. [Default: {DefaultMethod}]. HELP, [ @@ -111,13 +111,13 @@ HELP, 'route' => self::ROUTE, 'methodsList' => implode( ', ', - array_map(fn($val) => '' . $val . '', $this->methods) + array_map(fn($val) => '' . $val . '', $this->methods) ), 'DefaultMethod' => $this->methods[0], 'defaultPercent' => self::DEFAULT_PERCENT, 'removedList' => implode( ', ', - array_map(fn($val) => '' . $val . '', self::REMOVED_CHARS) + array_map(fn($val) => '' . $val . '', self::REMOVED_CHARS) ) ] ) diff --git a/src/Commands/Backend/Library/UnmatchedCommand.php b/src/Commands/Backend/Library/UnmatchedCommand.php index fcd6ebf0..58ba8254 100644 --- a/src/Commands/Backend/Library/UnmatchedCommand.php +++ b/src/Commands/Backend/Library/UnmatchedCommand.php @@ -47,19 +47,19 @@ final class UnmatchedCommand extends Command This command help find unmatched items in your libraries. ------- -[ FAQ ] +[ FAQ ] ------- -# I want to check specific library id? +# I want to check specific library id? -You can do that by using [--id] flag, change the backend_library_id to the library -id you get from [{library_list}] command. +You can do that by using [--id] flag, change the backend_library_id to the library +id you get from [{library_list}] command. -{cmd} {route} --id 'backend_library_id' -- [BACKEND_NAME] +{cmd} {route} --id 'backend_library_id' BACKEND_NAME -# I want to show all items regardless of the status? +# I want to show all items regardless of the status? -{cmd} {route} --show-all -- [BACKEND_NAME] +{cmd} {route} --show-all BACKEND_NAME HELP, [ diff --git a/src/Commands/Backend/RestoreCommand.php b/src/Commands/Backend/RestoreCommand.php index b588ea95..c6087041 100644 --- a/src/Commands/Backend/RestoreCommand.php +++ b/src/Commands/Backend/RestoreCommand.php @@ -40,11 +40,7 @@ class RestoreCommand extends Command protected function configure(): void { - $cmdContext = trim(commandContext()); - $cmdRoute = self::ROUTE; - $backupDir = after(Config::get('path') . '/backup/', ROOT_PATH); - - $this->setName($cmdRoute) + $this->setName(self::ROUTE) ->setDescription('Restore backend play state from backup file.') ->addOption('execute', null, InputOption::VALUE_NONE, 'Commit the changes to backend.') ->addOption('assume-yes', null, InputOption::VALUE_NONE, 'Answer yes to understanding the risks.') @@ -53,58 +49,66 @@ class RestoreCommand extends Command ->addArgument('backend', InputArgument::REQUIRED, 'Backend name to restore.') ->addArgument('file', InputArgument::REQUIRED, 'Backup file to restore from') ->setHelp( - <<state:backup command. + r( + <<state:backup] command. This restore process only works on backends that has export enabled. -The restore process is exactly the same as the state:export with [--ignore-date, --force-full] +The restore process is exactly the same as the [state:export] with [--ignore-date, --force-full] flags enabled, the difference is instead of reading state from database we are reading it from backup file. ------------------- -[ Risk Assessment ] +[ Risk Assessment ] ------------------- If you are trying to restore a backend that has import play state enabled, the changes from restoring from backup file will propagate back to your other backends. If you don't intend for that to happen, then DISABLE import from the backend. -------------------------------- -[ Enable restore functionality ] +[ Enable restore functionality ] -------------------------------- If you understand the risks and what might happen if you do restore from a backup file, -then you can enable the command by adding [--execute] to the command. +then you can enable the command by adding [--execute] to the command. For example, -{$cmdContext} {$cmdRoute} --execute -- my_plex {$backupDir}/my_plex.json +{cmd} {route} --execute -vv -- my_plex {backupDir}/my_plex.json ------- -[ FAQ ] +[ FAQ ] ------- -# Restore operation is cancelled. +# Restore operation is cancelled. If you encounter this error, it means either you didn't answer with yes for risk assessment confirmation, -or the interaction is disabled, if you can't enable interaction, then you can add another flag [--assume-yes] -to bypass the check. This confirms that you understand the risks of restoring backend that has import enabled. +or the interaction is disabled, if you can't enable interaction, then you can add another flag [--assume-yes] +to bypass the check. This confirms that you understand the risks of restoring backend that has import enabled. -# Ignoring [backend_name] [item_title]. [Movie|Episode] Is not imported yet. +# Ignoring [backend_name] [item_title]. [Movie|Episode] Is not imported yet. -This is normal, this is likely becuase the backup is already outdated and some items in remote does not exist in backup file, +This is normal, this is likely because the backup is already outdated and some items in remote does not exist in backup file, or you are using backup from another source which likely does not have matching data. -# Where are my backups stored? +# Where are the backups stored? -By defualt we store backups at {$backupDir} +By default, it should be at [{backupDir}]. -# How to see what data will be changed? +# How to see what data will be changed? -if you do not add [--execute] flag to the comment, it will run in test mode by default, -To see what data will be changed run the command with [-v] log level. +if you do not add [--execute] to the comment, it will run in dry mode by default, +To see what data will be changed run the command with [-v] log level. -HELP +HELP, + [ + 'cmd' => trim(commandContext()), + 'route' => self::ROUTE, + 'backupDir' => after(Config::get('path') . '/backup/', ROOT_PATH), + ] + ) ); } diff --git a/src/Commands/Backend/Search/IdCommand.php b/src/Commands/Backend/Search/IdCommand.php index 745c0416..75c357f9 100644 --- a/src/Commands/Backend/Search/IdCommand.php +++ b/src/Commands/Backend/Search/IdCommand.php @@ -28,7 +28,33 @@ final class IdCommand extends Command ->addOption('no-cache', null, InputOption::VALUE_NONE, 'Request new response from backend.') ->addOption('config', 'c', InputOption::VALUE_REQUIRED, 'Use Alternative config file.') ->addArgument('backend', InputArgument::REQUIRED, 'Backend name.') - ->addArgument('id', InputArgument::REQUIRED, 'Item id.'); + ->addArgument('id', InputArgument::REQUIRED, 'Backend item id.') + ->setHelp( + r( + <<item id from backend. + +The default mode display minimal information. To get more information you have to switch the output +mode to [json or yaml] and use the [--include-raw-response] flag. For example, + +{cmd} {route} --output yaml --include-raw-response -- backend_item_id + +------- +[ FAQ ] +------- + +# Why the response is not being updated? + +We cache the responses from the API to speed up the lookups, if however this is undesirable, +You can bypass the cache by using [--no-cache] flag. + +HELP, + [ + 'cmd' => trim(commandContext()), + 'route' => self::ROUTE, + ] + ) + ); } protected function runCommand(InputInterface $input, OutputInterface $output): int diff --git a/src/Commands/Backend/Search/QueryCommand.php b/src/Commands/Backend/Search/QueryCommand.php index fcbf3644..ded71514 100644 --- a/src/Commands/Backend/Search/QueryCommand.php +++ b/src/Commands/Backend/Search/QueryCommand.php @@ -28,7 +28,23 @@ final class QueryCommand extends Command ->addOption('limit', 'l', InputOption::VALUE_REQUIRED, 'Limit returned results.', 25) ->addOption('config', 'c', InputOption::VALUE_REQUIRED, 'Use Alternative config file.') ->addArgument('backend', InputArgument::REQUIRED, 'Backend name.') - ->addArgument('query', InputArgument::REQUIRED, 'Search query.'); + ->addArgument('query', InputArgument::REQUIRED, 'Search query.')->setHelp( + r( + <<keyword in backend libraries. + +The default mode display minimal information. To get more information you have to switch the output +mode to [json or yaml] and use the [--include-raw-response] flag. For example, + +{cmd} {route} --output yaml --include-raw-response -- BACKEND_NAME 'KEYWORD' + +HELP, + [ + 'cmd' => trim(commandContext()), + 'route' => self::ROUTE, + ] + ) + ); } protected function runCommand(InputInterface $input, OutputInterface $output): int @@ -76,6 +92,16 @@ final class QueryCommand extends Command return self::FAILURE; } + if ('table' === $mode) { + foreach ($results as &$item) { + $item['title'] = preg_replace( + '#(' . preg_quote($query, '#') . ')#i', + '$1', + $item['title'] + ); + } + unset($item); + } $this->displayContent($results, $output, $mode); return self::SUCCESS; diff --git a/src/Commands/Backend/Users/ListCommand.php b/src/Commands/Backend/Users/ListCommand.php index 61767af9..c378f4f6 100644 --- a/src/Commands/Backend/Users/ListCommand.php +++ b/src/Commands/Backend/Users/ListCommand.php @@ -28,7 +28,33 @@ final class ListCommand extends Command ->addOption('with-tokens', 't', InputOption::VALUE_NONE, 'Include access tokens in response.') ->addOption('include-raw-response', null, InputOption::VALUE_NONE, 'Include unfiltered raw response.') ->addOption('config', 'c', InputOption::VALUE_REQUIRED, 'Use Alternative config file.') - ->addArgument('backend', InputArgument::REQUIRED, 'Backend name.'); + ->addArgument('backend', InputArgument::REQUIRED, 'Backend name.') + ->setHelp( + r( + <<[ FAQ ] +------- + +# How to get user tokens? + +{cmd} {route} --with-tokens -- BACKEND_NAME + +# How to see the raw response? + +{cmd} {route} --output yaml --include-raw-response -- BACKEND_NAME + + +HELP, + [ + 'cmd' => trim(commandContext()), + 'route' => self::ROUTE, + ] + ) + ); } protected function runCommand(InputInterface $input, OutputInterface $output): int diff --git a/src/Commands/Config/AddCommand.php b/src/Commands/Config/AddCommand.php index a9871139..6fe08754 100644 --- a/src/Commands/Config/AddCommand.php +++ b/src/Commands/Config/AddCommand.php @@ -22,7 +22,26 @@ final class AddCommand extends Command $this->setName(self::ROUTE) ->setDescription('Add new backend.') ->addOption('config', 'c', InputOption::VALUE_REQUIRED, 'Use Alternative config file.') - ->addArgument('backend', InputArgument::REQUIRED, 'Backend name'); + ->addArgument('backend', InputArgument::REQUIRED, 'Backend name') + ->setHelp( + r( + <<servers.yaml] config. +This command require interaction to work. + +This command is purely shortcut for running the following command: + +{cmd} {manage_route} --add -- backend_name + +HELP, + [ + 'cmd' => trim(commandContext()), + 'route' => self::ROUTE, + 'manage_route' => ManageCommand::ROUTE, + ] + ) + ); } /** @@ -38,7 +57,7 @@ final class AddCommand extends Command $opts['--config'] = $input->getOption('config'); } - $opts['backend'] = $input->getArgument('backend'); + $opts['backend'] = strtolower($input->getArgument('backend')); return $this->getApplication()?->find(ManageCommand::ROUTE)->run(new ArrayInput($opts), $output); } diff --git a/src/Commands/Config/EditCommand.php b/src/Commands/Config/EditCommand.php index 557de3e2..348197a3 100644 --- a/src/Commands/Config/EditCommand.php +++ b/src/Commands/Config/EditCommand.php @@ -31,7 +31,44 @@ final class EditCommand extends Command ->addOption('delete', 'd', InputOption::VALUE_NONE, 'Delete value.') ->addOption('regenerate-webhook-token', 'g', InputOption::VALUE_NONE, 'Re-generate backend webhook token.') ->addArgument('backend', InputArgument::REQUIRED, 'Backend name') - ->setAliases(['servers:edit']); + ->setAliases(['servers:edit']) + ->setHelp( + r( + <<edit backend config settings inline. + +The [--key] accept string value. the list of officially supported keys are: + +[{keyNames}] + +------- +[ FAQ ] +------- + +# How to edit config setting? + +{cmd} {route} --key key --set value -- backend_name + +# How to change the webhook token? + +{cmd} {route} --regenerate-webhook-token -- backend_name + +HELP, + [ + 'cmd' => trim(commandContext()), + 'route' => self::ROUTE, + 'manage_route' => ManageCommand::ROUTE, + 'keyNames' => implode( + ', ', + array_map( + fn($val) => '' . $val . '', + require __DIR__ . '/../../../config/backend.spec.php' + ) + ) + ] + ) + ); } protected function runCommand(InputInterface $input, OutputInterface $output, null|array $rerun = null): int @@ -58,9 +95,11 @@ final class EditCommand extends Command if (!isValidName($name)) { $output->writeln( - sprintf( - 'ERROR: Invalid \'%s\' name was given. Only \'A-Z, a-z, 0-9, _\' are allowed.', - $name, + r( + 'ERROR: Invalid [{name}] name was given. Only [a-z, 0-9, _] are allowed.', + [ + 'name' => $name + ], ) ); return self::FAILURE; @@ -71,6 +110,10 @@ final class EditCommand extends Command return self::FAILURE; } + if (strtolower($name) !== $name) { + $output->writeln('Non lower case backend names are deprecated and will not work in v1.'); + } + if ($input->getOption('regenerate-webhook-token')) { try { $webhookToken = bin2hex(random_bytes(Config::get('webhook.tokenLength'))); diff --git a/src/Commands/Config/ListCommand.php b/src/Commands/Config/ListCommand.php index ea678b3b..f9486aca 100644 --- a/src/Commands/Config/ListCommand.php +++ b/src/Commands/Config/ListCommand.php @@ -26,7 +26,15 @@ final class ListCommand extends Command $this->setName(self::ROUTE) ->addOption('config', 'c', InputOption::VALUE_REQUIRED, 'Use Alternative config file.') ->setDescription('List Added backends.') - ->setAliases(['servers:list']); + ->setAliases(['servers:list']) + ->setHelp( + <<addOption('add', 'a', InputOption::VALUE_NONE, 'Add Backend.') ->addOption('config', 'c', InputOption::VALUE_REQUIRED, 'Use Alternative config file.') ->addArgument('backend', InputArgument::REQUIRED, 'Backend name.') - ->setAliases(['servers:manage']); + ->setAliases(['servers:manage']) + ->setHelp( + r( + <<interaction to work. + +HELP, + ) + ); } /** @@ -49,12 +59,22 @@ final class ManageCommand extends Command } if (false === $tty || $input->getOption('no-interaction')) { - $output->writeln('ERROR: This command require interaction.'); $output->writeln( - 'If you are running this tool inside docker, you have to enable interaction using "-ti" flag' - ); - $output->writeln( - 'For example: docker exec -ti watchstate console config:manage my_server' + r( + <<ERROR: This command require interaction. For example: + +{cmd} {route} -- {backend} + +ERROR, + [ + 'cmd' => trim(commandContext()), + 'route' => self::ROUTE, + 'backend' => $input->getArgument('backend'), + ] + ) + ); return self::FAILURE; } @@ -64,11 +84,10 @@ final class ManageCommand extends Command // -- Use Custom servers.yaml file. if (($config = $input->getOption('config'))) { try { - $this->checkCustomBackendsFile($config); $custom = true; - $backends = (array)Yaml::parseFile($config); + $backends = (array)Yaml::parseFile($this->checkCustomBackendsFile($config)); } catch (\RuntimeException $e) { - $output->writeln(sprintf('%s', $e->getMessage())); + $output->writeln(r('ERROR: {error}', ['error' => $e->getMessage()])); return self::FAILURE; } } else { @@ -84,37 +103,52 @@ final class ManageCommand extends Command if (!isValidName($name)) { $output->writeln( - sprintf( - 'ERROR: Invalid \'%s\' name was given. Only \'A-Z, a-z, 0-9, _\' are allowed.', - $name, + r( + 'ERROR: Invalid [{name}] name was given. Only [a-z, 0-9, _] are allowed.', + [ + 'name' => $name + ], ) ); return self::FAILURE; } - if (false === $add && null === ag($backends, "{$name}.type", null)) { + if (true === $add) { + if (null !== ag($backends, "{$name}.type", null)) { + $output->writeln( + r( + 'ERROR: Backend with [{backend}] name already exists. Omit the [--add] flag if you want to edit the backend settings.', + [ + 'backend' => $name, + ], + ) + ); + return self::FAILURE; + } + $name = strtolower($name); + } elseif (null === ag($backends, "{$name}.type", null)) { $output->writeln( - sprintf( - 'ERROR: Backend \'%s\' not found. To add new backend append --add flag to the command.', - $name, + r( + 'ERROR: No backend named [{backend}] was found. Append [--add] to add as new backend.', + [ + 'backend' => $name, + ] ) ); return self::FAILURE; } - if (true === $add && null !== ag($backends, "{$name}.type", null)) { + if (strtolower($name) !== $name) { + // @RELEASE - remove warning and make sure to lower case name. $output->writeln( - sprintf( - 'ERROR: Backend name \'%s\' already exists in \'%s\' omit the --add flag if you want to edit the config.', - $name, - $config - ) + 'Non lower case backend names are deprecated and will not work in v1.0.' ); - return self::FAILURE; } $u = $rerun ?? ag($backends, $name, []); + $output->writeln(''); + // -- $name.type (function () use ($input, $output, &$u, $name) { $list = array_keys(Config::get('supported', [])); @@ -124,11 +158,10 @@ final class ManageCommand extends Command $choice = array_search($chosen, $list); $question = new ChoiceQuestion( - sprintf( - 'Select %s type. %s', - $name, - null !== $chosen ? "[Default: {$chosen}]" : '' - ), + r('Select [{name}] type. {default}', [ + 'name' => $name, + 'default' => null !== $chosen ? "[Default: {$chosen}]" : '', + ]), $list, false === $choice ? null : $choice ); @@ -150,11 +183,10 @@ final class ManageCommand extends Command $helper = $this->getHelper('question'); $chosen = ag($u, 'url'); $question = new Question( - sprintf( - 'Enter %s URL. %s' . PHP_EOL . '> ', - $name, - null !== $chosen ? "[Default: {$chosen}]" : '', - ), + r('Enter [{name}] URL. {default}' . PHP_EOL . '> ', [ + 'name' => $name, + 'default' => null !== $chosen ? "[Default: {$chosen}]" : '', + ]), $chosen ); @@ -175,11 +207,10 @@ final class ManageCommand extends Command $helper = $this->getHelper('question'); $chosen = ag($u, 'token'); $question = new Question( - sprintf( - 'Enter %s API token. %s' . PHP_EOL . '> ', - $name, - null !== $chosen ? "[Default: {$chosen}]" : '', - ), + r('Enter [{name}] API token. {default}' . PHP_EOL . '> ', [ + 'name' => $name, + 'default' => null !== $chosen ? "[Default: {$chosen}]" : '', + ]), $chosen ); @@ -227,10 +258,12 @@ final class ManageCommand extends Command $helper = $this->getHelper('question'); $question = new Question( - sprintf( - 'Enter %s backend unique identifier. %s' . PHP_EOL . '> ', - $name, - null !== $chosen ? "[Default: {$chosen}]" : '', + r( + 'Enter [{name}] backend unique identifier. {default}' . PHP_EOL . '> ', + [ + 'name' => $name, + 'default' => null !== $chosen ? "[Default: {$chosen}]" : '', + ] ), $chosen ); @@ -285,10 +318,9 @@ final class ManageCommand extends Command $choice = $ids[$chosen] ?? null; $question = new ChoiceQuestion( - sprintf( - 'Select which user to associate with this backend. %s', - null !== $choice ? "[Default: {$choice}]" : '' - ), + r('Select which user to associate with this backend. {default}', [ + 'default' => null !== $choice ? "[Default: {$choice}]" : '' + ]), $list, false === $choice ? null : $choice ); @@ -317,7 +349,7 @@ final class ManageCommand extends Command sprintf( 'Please enter %s user id to associate this config to %s' . PHP_EOL . '> ', ucfirst(ag($u, 'type')), - null !== $chosen ? "- [Default: {$chosen}]" : '', + null !== $chosen ? "- [Default: {$chosen}]" : '', ), $chosen ); @@ -348,12 +380,13 @@ final class ManageCommand extends Command $chosen = (bool)ag($u, 'import.enabled', true); $helper = $this->getHelper('question'); - $text = 'Enable Importing metadata and play state from this backend? %s'; $question = new ConfirmationQuestion( - sprintf( - $text . PHP_EOL . '> ', - '[Y|N] [Default: ' . ($chosen ? 'Yes' : 'No') . ']', + r( + 'Enable importing of metadata and play state from this backend? {default}' . PHP_EOL . '> ', + [ + 'default' => '[Y|N] [Default: ' . ($chosen ? 'Yes' : 'No') . ']', + ] ), $chosen ); @@ -368,12 +401,13 @@ final class ManageCommand extends Command $chosen = (bool)ag($u, 'export.enabled', true); $helper = $this->getHelper('question'); - $text = 'Enable Exporting play state to this backend? %s'; $question = new ConfirmationQuestion( - sprintf( - $text . PHP_EOL . '> ', - '[Y|N] [Default: ' . ($chosen ? 'Yes' : 'No') . ']', + r( + 'Enable exporting play state to this backend? {default}' . PHP_EOL . '> ', + [ + 'default' => '[Y|N] [Default: ' . ($chosen ? 'Yes' : 'No') . ']', + ] ), $chosen ); @@ -396,21 +430,21 @@ final class ManageCommand extends Command $chosen = (bool)ag($u, 'options.' . Options::IMPORT_METADATA_ONLY, true); $helper = $this->getHelper('question'); - $text = - <<Importing metadata ONLY from this backend? %s - ------------------ - To efficiently export to this backend we need relation map and this require - us to get metadata from the backend. You have Importing disabled, as such this option - allow us to import this backend metadata without altering your play state. - ------------------ - This option is SAFE and WILL NOT change your play state or add new items. - TEXT; $question = new ConfirmationQuestion( - sprintf( - $text . PHP_EOL . '> ', - '[Y|N] [Default: ' . ($chosen ? 'Yes' : 'No') . ']', + r( + <<Enable Importing metadata ONLY from this backend? {default} + ------------------ + To efficiently export to this backend we need relation map and this require + us to get metadata from the backend. You have Importing disabled, as such this option + allow us to import this backend metadata without altering your play state. + ------------------ + This option will not alter your play state or add new items to the database. + HELP. PHP_EOL . '> ', + [ + 'default' => '[Y|N] [Default: ' . ($chosen ? 'Yes' : 'No') . ']', + ] ), $chosen ); @@ -425,18 +459,15 @@ final class ManageCommand extends Command $chosen = (bool)ag($u, 'webhook.match.user', false); $helper = $this->getHelper('question'); - $text = - <<Limit backend webhook events to the selected user? %s - ------------------ - Helpful for Plex multi user/servers setup. - Lead sometimes to missed events for jellyfin itemAdd events. You should scope the token at jellyfin level. - TEXT; $question = new ConfirmationQuestion( - sprintf( - $text . PHP_EOL . '> ', - '[Y|N] [Default: ' . ($chosen ? 'Yes' : 'No') . ']', + r( + <<Limit backend webhook events to the selected user? {default} + HELP. PHP_EOL . '> ', + [ + 'default' => '[Y|N] [Default: ' . ($chosen ? 'Yes' : 'No') . ']', + ] ), $chosen ); @@ -451,17 +482,17 @@ final class ManageCommand extends Command $chosen = (bool)ag($u, 'webhook.match.uuid', false); $helper = $this->getHelper('question'); - $text = - <<Limit backend webhook events to the selected backend unique id? %s - ------------------ - Helpful for Plex multi user/servers setup. - TEXT; $question = new ConfirmationQuestion( - sprintf( - $text . PHP_EOL . '> ', - '[Y|N] [Default: ' . ($chosen ? 'Yes' : 'No') . ']', + r( + <<Limit this backend webhook events to the specified backend unique id? {default} + ------------------ + This option MUST be enabled for multi plex servers setup. + HELP. PHP_EOL . '> ', + [ + 'default' => '[Y|N] [Default: ' . ($chosen ? 'Yes' : 'No') . ']', + ] ), $chosen ); @@ -476,7 +507,9 @@ final class ManageCommand extends Command $u = ag_set($u, 'webhook.token', bin2hex(random_bytes(Config::get('webhook.tokenLength')))); } catch (Throwable $e) { $output->writeln( - sprintf('Generating webhook api token has failed. %s', $e->getMessage()) + r('ERROR: Generating webhook token has failed. {error}', [ + 'error' => $e->getMessage() + ]) ); return self::FAILURE; } @@ -498,7 +531,8 @@ final class ManageCommand extends Command $helper = $this->getHelper('question'); $question = new ConfirmationQuestion( - 'Is the info correct? [Y|N] [Default: Yes]' . PHP_EOL . '> ', true + 'Is the info correct? [Y|N] [Default: Yes]' . PHP_EOL . '> ', + true ); if (false === $helper->ask($input, $output, $question)) { @@ -520,13 +554,13 @@ final class ManageCommand extends Command $text = <<[Y|N] [Default: Yes] + Create database indexes now? [Y|N] [Default: Yes] ----------------- - This is necessary action to ensure speedy operations on database, - If not run now, you have to manually run the system:index command, or restart the container - which will trigger index check to make sure your database data is fully indexed. - P.S: this could take few minutes to execute depending on your disk speed. + This is necessary action to ensure speedy operations on database, + If you do not run this now, you have to manually run the system:index command, or restart the container + which will trigger index check to make sure your database data is fully indexed. ----------------- + P.S: this could take few minutes to execute. TEXT; @@ -546,10 +580,9 @@ final class ManageCommand extends Command $text = <<{type} from the backend now? [Y|N] [Default: No] - ----------------- - P.S: this could take few minutes to execute. + Would you like to import {type} from the backend now? [Y|N] [Default: No] ----------------- + P.S: this could take few minutes to execute. TEXT; diff --git a/src/Commands/Config/UnifyCommand.php b/src/Commands/Config/UnifyCommand.php index 8f907321..a525c6b3 100644 --- a/src/Commands/Config/UnifyCommand.php +++ b/src/Commands/Config/UnifyCommand.php @@ -7,6 +7,7 @@ namespace App\Commands\Config; use App\Command; use App\Libs\Config; use App\Libs\Routable; +use JsonException; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Input\InputArgument; @@ -14,6 +15,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Yaml\Yaml; +use Symfony\Contracts\HttpClient\Exception\ExceptionInterface; use Throwable; #[Routable(command: self::ROUTE), Routable(command: 'servers:unify')] @@ -24,7 +26,7 @@ final class UnifyCommand extends Command protected function configure(): void { $this->setName(self::ROUTE) - ->setDescription('Unify [backendType] webhook API key.') + ->setDescription('Unify backend type webhook tokens.') ->addOption('select-backends', 's', InputOption::VALUE_OPTIONAL, 'Select backends. comma , seperated.', '') ->addOption('config', 'c', InputOption::VALUE_REQUIRED, 'Use Alternative config file.') ->addArgument( @@ -36,9 +38,40 @@ final class UnifyCommand extends Command ), ) ->setAliases(['servers:unify']) - ->addOption('servers-filter', null, InputOption::VALUE_OPTIONAL, '[DEPRECATED] Select backends.', ''); + ->addOption('servers-filter', null, InputOption::VALUE_OPTIONAL, '[DEPRECATED] Select backends.', '') + ->setHelp( + r( + <<Plex multi server users. +You shouldn't use this command unless told by the team. + +Due to Plex webhook limitation you cannot use multiple webhook tokens for one PlexPass account. +And as workaround we have to use one webhook token for all of your Plex backends. + +This command will do the following. + +3. Update backends unique identifier (uuid). +1. Change the selected backend's webhook tokens to be replica of each other. +2. Enable limit backend webhook requests to matching unique identifier. + +To execute the command, you can do the following + +{cmd} {route} -- plex + +HELP, + [ + 'cmd' => trim(commandContext()), + 'route' => self::ROUTE, + ] + ) + ); } + /** + * @throws ExceptionInterface + * @throws JsonException + */ protected function runCommand(InputInterface $input, OutputInterface $output): int { // -- Use Custom servers.yaml file. @@ -160,10 +193,20 @@ final class UnifyCommand extends Command continue; } - $output->writeln(sprintf('ERROR %s: does not have backend unique id set.', $backendName)); - $output->writeln('Please run this command to update backend info.'); - $output->writeln(sprintf(commandContext() . 'config:manage \'%s\' ', $backendName)); - return self::FAILURE; + $client = makeBackend(Config::get($ref), $backendName); + + $uuid = $client->getServerUUID(true); + + if (empty($uuid)) { + $output->writeln( + sprintf('ERROR %s: does not have backend unique id set.', $backendName) + ); + $output->writeln('Please run this command to update backend info.'); + $output->writeln(sprintf(commandContext() . 'config:manage \'%s\' ', $backendName)); + return self::FAILURE; + } + + Config::save("{$ref}.uuid", $uuid); } try { @@ -176,6 +219,7 @@ final class UnifyCommand extends Command foreach ($list as $backend) { $ref = ag($backend, 'ref'); Config::save("{$ref}.webhook.token", $apiToken); + Config::save("{$ref}.webhook.match.uuid", true); } if (false === $custom) { @@ -184,7 +228,9 @@ final class UnifyCommand extends Command file_put_contents($config, Yaml::dump(Config::get('servers', []), 8, 2)); - $output->writeln(sprintf('Unified the API key of %d %s backends.', count($list), $type)); + $output->writeln( + sprintf('Unified the webhook token of %d %s backends.', count($list), $type) + ); $output->writeln(sprintf('%s global webhook API key is: %s', ucfirst($type), $apiToken)); return self::SUCCESS; } diff --git a/src/Commands/Config/ViewCommand.php b/src/Commands/Config/ViewCommand.php index 663d4d14..2bf9e786 100644 --- a/src/Commands/Config/ViewCommand.php +++ b/src/Commands/Config/ViewCommand.php @@ -37,7 +37,40 @@ final class ViewCommand extends Command 'Can be any key from servers.yaml, use dot notion to access sub keys, for example [webhook.token]' ) ->setAliases(['servers:view']) - ->addOption('servers-filter', null, InputOption::VALUE_OPTIONAL, '[DEPRECATED] Select backends.', ''); + ->addOption('servers-filter', null, InputOption::VALUE_OPTIONAL, '[DEPRECATED] Select backends.', '') + ->setHelp( + r( + <<[ FAQ ] +------- + +# How to show one backend information? + +The flag [-s, --select-backends] accept comma seperated list of backends name, Using the flag +in combination with [--exclude] flag will flip the logic to exclude the selected backends +rather than include them. + +{cmd} {route} --select-backends my_backend + +# How to show specific key? + +The key can be any value that already exists in the list. to access sub-keys use dot notation for example, +To see if the value of import.enabled you would run: + +{cmd} {route} import.enabled + +HELP, + [ + 'cmd' => trim(commandContext()), + 'route' => self::ROUTE, + ] + ) + ); } /**