Added support for using emby and jellyfin oauth tokens via config:add/manage.

This commit is contained in:
abdulmohsen
2024-07-06 18:34:13 +03:00
parent cc2dffad46
commit 90373e7ce2
4 changed files with 74 additions and 105 deletions

View File

@@ -60,10 +60,13 @@ class JellyfinManage implements ManageInterface
$chosen = ag($backend, 'token');
$question = new Question(
r('<question>Enter [<value>{name}</value>] API token</question>. {default}' . PHP_EOL . '> ', [
'name' => ag($backend, 'name'),
'default' => null !== $chosen ? "<value>[Default: {$chosen}]</value>" : '',
]),
r(
'<question>Enter [<value>{name}</value>] API key or "username:password" for oauth token generation</question>. {default}' . PHP_EOL . '> ',
[
'name' => ag($backend, 'name'),
'default' => null !== $chosen ? "<value>[Default: {$chosen}]</value>" : '',
]
),
$chosen
);
@@ -86,7 +89,47 @@ class JellyfinManage implements ManageInterface
});
$token = $this->questionHelper->ask($this->input, $this->output, $question);
$backend = ag_set($backend, 'token', $token);
if (true === str_contains($token, ':')) {
[$username, $password] = explode(':', $token, 2);
if (empty($username) || empty($password)) {
$this->output->writeln('<error>Invalid username or password was given.</error>');
goto re_goto_token;
}
// we are probably dealing with a username:password, try to generate oAuth token.
$this->output->writeln(
'<info>Attempting to generate oAuth token from username and password. Please wait...</info>'
);
$backend = ag_set($backend, 'token', 'oauth_token');
try {
$accessToken = makeBackend($backend)->generateAccessToken($username, $password);
} catch (Throwable $e) {
$this->output->writeln('<error>Failed to generate oAuth token from username and password.</error>');
$this->output->writeln(
sprintf(
'<error>ERROR - %s: %s.</error>' . PHP_EOL,
afterLast(get_class($e), '\\'),
$e->getMessage()
)
);
$backend = ag_set($backend, 'token', null);
goto re_goto_token;
}
$backend = ag_set($backend, 'token', ag($accessToken, 'accesstoken'));
$backend = ag_set($backend, 'user', ag($accessToken, 'user'));
$backend = ag_set($backend, 'uuid', ag($accessToken, 'identifier'));
$backend = ag_set($backend, 'options.' . Options::IS_LIMITED_TOKEN, true);
} else {
$backend = ag_set($backend, 'token', $token);
}
if (true === (bool)ag($backend, 'options.' . Options::IS_LIMITED_TOKEN, false)) {
return;
}
$this->output->writeln('');
@@ -124,6 +167,8 @@ class JellyfinManage implements ManageInterface
]
)
);
$backend = ag_set($backend, 'uuid', $chosen);
return;
} catch (Throwable $e) {
$this->output->writeln('<error>Failed to get the backend unique identifier.</error>');
$this->output->writeln(r('<error>ERROR - {kind}: {message}.</error>' . PHP_EOL, [
@@ -135,66 +180,6 @@ class JellyfinManage implements ManageInterface
goto re_goto_token;
}
$question = new Question(
r(
<<<HELP
<question>Enter [<value>{name}</value>] Unique identifier</question>. {default}
------------------
The Server Unique identifier is randomly generated string on server setup.
------------------
<notice>If you select invalid or give incorrect server unique identifier, Some checks will
fail And you may not be able to sync your backend.</notice>
------------------
<error>DO NOT CHANGE the default value unless you know what you are doing, or was told by devs.</error>
HELP. PHP_EOL . '> ',
[
'name' => ag($backend, 'name'),
'default' => "<value>[Default: {$chosen}]</value>",
]
),
$chosen
);
$question->setValidator(function ($answer) use ($custom) {
if (empty($answer)) {
throw new RuntimeException('Backend unique identifier cannot be empty.');
}
if (!is_string($answer) && !is_int($answer)) {
throw new RuntimeException(
r(
"Backend unique identifier is invalid. Expecting string or integer, but got '{type}' instead.",
[
'type' => get_debug_type($answer)
]
)
);
}
$backendUUid = makeBackend($custom, ag($custom, 'name'))->getIdentifier(true);
if ('auto' === $answer && !empty($backendUUid)) {
return $backendUUid;
}
if ($answer !== $backendUUid) {
throw new RuntimeException(
r(
'Invalid backend unique identifier was given. Expecting "{uuid}", but got "{answer}" instead.',
[
'uuid' => $backendUUid,
'answer' => $answer
]
)
);
}
return $answer;
});
$uuid = $this->questionHelper->ask($this->input, $this->output, $question);
$backend = ag_set($backend, 'uuid', $uuid);
})();
$this->output->writeln('');

View File

@@ -23,7 +23,7 @@ use Symfony\Component\Console\Question\Question;
#[Cli(command: self::ROUTE)]
final class AddCommand extends Command
{
public const ROUTE = 'config:add';
public const string ROUTE = 'config:add';
/**
* Configures the command.

View File

@@ -5,11 +5,11 @@ declare(strict_types=1);
namespace App\Commands\Config;
use App\Command;
use App\Commands\State\ImportCommand;
use App\Commands\System\IndexCommand;
use App\Libs\Attributes\Route\Cli;
use App\Libs\Config;
use App\Libs\ConfigFile;
use App\Libs\Exceptions\Backends\InvalidContextException;
use App\Libs\Options;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Exception\ExceptionInterface;
@@ -30,7 +30,7 @@ use Throwable;
#[Cli(command: self::ROUTE)]
final class ManageCommand extends Command
{
public const ROUTE = 'config:manage';
public const string ROUTE = 'config:manage';
public function __construct(private LoggerInterface $logger)
{
@@ -117,6 +117,8 @@ final class ManageCommand extends Command
return self::FAILURE;
}
go_start:
if (true === $add) {
if (null !== $configFile->get("{$name}.type", null)) {
$output->writeln(
@@ -384,7 +386,24 @@ final class ManageCommand extends Command
);
if (false === $helper->ask($input, $output, $question)) {
return $this->runCommand($input, $output, $u);
goto go_start;
}
try {
$output->writeln('<info>Validating backend context. Please wait...</info>');
$client = makeBackend($u, $name);
$client->setLogger($this->logger);
if (false === $client->validateContext($client->getContext())) {
$output->writeln('<error>ERROR:</error> Backend context is invalid.');
goto go_start;
}
} catch (InvalidContextException $e) {
$output->writeln(
r('<error>ERROR:</error> Backend context validation has failed. {error}', [
'error' => $e->getMessage()
])
);
goto go_start;
}
$configFile->set($name, $u)->persist();
@@ -411,41 +430,6 @@ final class ManageCommand extends Command
if (true === $helper->ask($input, $output, $question)) {
$this->getApplication()?->find(IndexCommand::ROUTE)->run(new ArrayInput([]), $output);
}
$importEnabled = (bool)ag($u, 'import.enabled');
$metaEnabled = (bool)ag($u, 'options.' . Options::IMPORT_METADATA_ONLY);
if (true === $importEnabled || true === $metaEnabled) {
$importType = $importEnabled ? 'play state & metadata' : 'metadata only.';
$helper = $this->getHelper('question');
$text =
<<<TEXT
<question>Would you like to import <flag>{type}</flag> from the backend now</question>? [<value>Y|N</value>] [<value>Default: No</value>]
-----------------
<value>P.S: this could take few minutes to execute.</value>
TEXT;
$text = r($text, ['type' => $importType]);
$question = new ConfirmationQuestion($text . PHP_EOL . '> ', false);
if (true === $helper->ask($input, $output, $question)) {
$output->writeln(
r('<info>Importing {type} from {name}</info>', [
'name' => $name,
'type' => $importType
])
);
$cmd = $this->getApplication()?->find(ImportCommand::ROUTE);
$cmd->run(new ArrayInput(['--quiet', '--select-backend' => [$name]]), $output);
}
$output->writeln('<info>Import complete</info>');
}
}
return self::SUCCESS;

View File

@@ -144,7 +144,7 @@ final class TestCommand extends Command
foreach ($backends as $backendName => $backend) {
$backend = $this->getBackend($backendName);
$context = $backend->getContext();
$output->writeln(r("Running '{client}' functional tests on '{backend}'.", [
$output->writeln(r("Running '{client}' client functional tests on '{backend}'.", [
'client' => $context->clientName,
'backend' => $context->backendName,
]));