tryResponse(context: $context, fn: fn() => $this->getUsers($context, $opts)); } /** * Get Users list. * * @throws ExceptionInterface * @throws JsonException */ private function getUsers(Context $context, array $opts = []): Response { $url = Container::getNew(UriInterface::class)->withPort(443)->withScheme('https')->withHost('plex.tv') ->withPath('/api/v2/home/users/'); $response = $this->http->request('GET', (string)$url, [ 'headers' => [ 'Accept' => 'application/json', 'X-Plex-Token' => $context->backendToken, 'X-Plex-Client-Identifier' => $context->backendId, ], ]); $this->logger->debug('Requesting [%(backend)] Users list.', [ 'backend' => $context->backendName, 'url' => (string)$url, ]); if (200 !== $response->getStatusCode()) { return new Response( status: false, error: new Error( message: 'Request for [%(backend)] users list returned with unexpected [%(status_code)] status code.', context: [ 'backend' => $context->backendName, 'status_code' => $response->getStatusCode(), ], level: Levels::ERROR ), ); } $json = json_decode( json: $response->getContent(), associative: true, flags: JSON_THROW_ON_ERROR | JSON_INVALID_UTF8_IGNORE ); if ($context->trace) { $this->logger->debug('Parsing [%(backend)] user list payload.', [ 'backend' => $context->backendName, 'url' => (string)$url, 'trace' => $json, ]); } $list = []; $adminsCount = 0; $users = ag($json, 'users', []); foreach ($users as $user) { if (true === (bool)ag($user, 'admin')) { $adminsCount++; } } foreach ($users as $user) { $data = [ 'id' => ag($user, 'admin') && $adminsCount <= 1 ? 1 : ag($user, 'id'), 'name' => ag($user, ['friendlyName', 'username', 'title', 'email'], '??'), 'admin' => (bool)ag($user, 'admin'), 'guest' => (bool)ag($user, 'guest'), 'restricted' => (bool)ag($user, 'restricted'), 'updatedAt' => isset($user['updatedAt']) ? makeDate($user['updatedAt']) : 'Never', ]; if (true === (bool)ag($opts, 'tokens')) { $tokenRequest = $this->getUserToken( context: $context, userId: ag($user, 'uuid'), username: ag($data, 'name'), ); if ($tokenRequest->hasError()) { $this->logger->log( $tokenRequest->error->level(), $tokenRequest->error->message, $tokenRequest->error->context ); } $data['token'] = $tokenRequest->isSuccessful() ? $tokenRequest->response : null; } if (true === (bool)ag($opts, Options::RAW_RESPONSE)) { $data['raw'] = $user; } $list[] = $data; } return new Response(status: true, response: $list); } /** * Request tokens from plex.tv api. * * @param Context $context * @param int|string $userId * @param string $username * * @return Response */ private function getUserToken(Context $context, int|string $userId, string $username): Response { try { $url = Container::getNew(UriInterface::class) ->withPort(443)->withScheme('https')->withHost('plex.tv') ->withPath(sprintf('/api/v2/home/users/%s/switch', $userId)); $this->logger->debug('Requesting temporary access token for [%(backend)] user [%(username)].', [ 'backend' => $context->backendName, 'username' => $username, 'user_id' => $userId, 'url' => (string)$url, ]); $response = $this->http->request('POST', (string)$url, [ 'headers' => [ 'Accept' => 'application/json', 'X-Plex-Token' => $context->backendToken, 'X-Plex-Client-Identifier' => $context->backendId, ], ]); if (201 !== $response->getStatusCode()) { return new Response( status: false, error: new Error( message: 'Request for [%(backend)] user [%(username)] temporary access token responded with unexpected [%(status_code)] status code.', context: [ 'backend' => $context->backendName, 'username' => $username, 'user_id' => $userId, 'status_code' => $response->getStatusCode(), ], level: Levels::ERROR ), ); } $json = json_decode( json: $response->getContent(), associative: true, flags: JSON_THROW_ON_ERROR | JSON_INVALID_UTF8_IGNORE ); if ($context->trace) { $this->logger->debug('Parsing temporary access token for [%(backend)] user [%(username)] payload.', [ 'backend' => $context->backendName, 'username' => $username, 'user_id' => $userId, 'url' => (string)$url, 'trace' => $json, ]); } $tempToken = ag($json, 'authToken', null); $url = Container::getNew(UriInterface::class)->withPort(443)->withScheme('https')->withHost('plex.tv') ->withPath('/api/v2/resources')->withQuery( http_build_query( [ 'includeIPv6' => 1, 'includeHttps' => 1, 'includeRelay' => 1 ] ) ); $this->logger->debug('Requesting permanent access token for [%(backend)] user [%(username)].', [ 'backend' => $context->backendName, 'username' => $username, 'user_id' => $userId, 'url' => (string)$url, ]); $response = $this->http->request('GET', (string)$url, [ 'headers' => [ 'Accept' => 'application/json', 'X-Plex-Token' => $tempToken, 'X-Plex-Client-Identifier' => $context->backendId, ], ]); $json = json_decode( json: $response->getContent(), associative: true, flags: JSON_THROW_ON_ERROR | JSON_INVALID_UTF8_IGNORE ); if ($context->trace) { $this->logger->debug('Parsing permanent access token for [%(backend)] user [%(username)] payload.', [ 'backend' => $context->backendName, 'username' => $username, 'user_id' => $userId, 'url' => (string)$url, 'trace' => $json, ]); } foreach ($json ?? [] as $server) { if (ag($server, 'clientIdentifier') !== $context->backendId) { continue; } return new Response(status: true, response: ag($server, 'accessToken')); } return new Response( status: false, error: new Error( message: 'No permanent access token found in [%(backend)] user [%(username)] response.', context: [ 'backend' => $context->backendName, 'username' => $username, 'user_id' => $userId, ], level: Levels::ERROR ), ); } catch (Throwable $e) { return new Response( status: false, error: new Error( message: 'Unhandled exception was thrown during request for [%(backend)] [%(username)] access token.', context: [ 'backend' => $context->backendName, 'username' => $username, 'user_id' => $userId, 'exception' => [ 'file' => after($e->getFile(), ROOT_PATH), 'line' => $e->getLine(), 'kind' => get_class($e), 'message' => $e->getMessage(), 'trace' => $context->trace ? $e->getTrace() : [], ], ], level: Levels::ERROR ), ); } } }