12
composer.lock
generated
12
composer.lock
generated
@@ -3342,12 +3342,12 @@
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Roave/SecurityAdvisories.git",
|
||||
"reference": "504a245ede435b003e091df51766403ed446f5bf"
|
||||
"reference": "0e5a0abdd695cd45870e442647ef03a70f2ec1e5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/504a245ede435b003e091df51766403ed446f5bf",
|
||||
"reference": "504a245ede435b003e091df51766403ed446f5bf",
|
||||
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/0e5a0abdd695cd45870e442647ef03a70f2ec1e5",
|
||||
"reference": "0e5a0abdd695cd45870e442647ef03a70f2ec1e5",
|
||||
"shasum": ""
|
||||
},
|
||||
"conflict": {
|
||||
@@ -3440,7 +3440,7 @@
|
||||
"ezsystems/ezplatform": "<=1.13.6|>=2,<=2.5.24",
|
||||
"ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6|>=1.5,<1.5.27",
|
||||
"ezsystems/ezplatform-admin-ui-assets": ">=4,<4.2.1|>=5,<5.0.1|>=5.1,<5.1.1",
|
||||
"ezsystems/ezplatform-kernel": "<=1.2.5|>=1.3,<1.3.12",
|
||||
"ezsystems/ezplatform-kernel": "<=1.2.5|>=1.3,<1.3.17",
|
||||
"ezsystems/ezplatform-rest": ">=1.2,<=1.2.2|>=1.3,<1.3.8",
|
||||
"ezsystems/ezplatform-richtext": ">=2.3,<=2.3.7",
|
||||
"ezsystems/ezplatform-user": ">=1,<1.0.1",
|
||||
@@ -3450,7 +3450,7 @@
|
||||
"ezsystems/repository-forms": ">=2.3,<2.3.2.1",
|
||||
"ezyang/htmlpurifier": "<4.1.1",
|
||||
"facade/ignition": "<1.16.15|>=2,<2.4.2|>=2.5,<2.5.2",
|
||||
"facturascripts/facturascripts": "<2022.4",
|
||||
"facturascripts/facturascripts": "<2022.6",
|
||||
"feehi/cms": "<=2.1.1",
|
||||
"feehi/feehicms": "<=0.1.3",
|
||||
"fenom/fenom": "<=2.12.1",
|
||||
@@ -3821,7 +3821,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-04-29T16:08:59+00:00"
|
||||
"time": "2022-04-29T21:04:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/cli-parser",
|
||||
|
||||
@@ -107,7 +107,7 @@ return (function () {
|
||||
'type' => 'syslog',
|
||||
'docker' => false,
|
||||
'facility' => env('WS_LOGGER_SYSLOG_FACILITY', LOG_USER),
|
||||
'enabled' => env('WS_LOGGER_SYSLOG_ENABLED', false),
|
||||
'enabled' => env('WS_LOGGER_SYSLOG_ENABLED', !env('IN_DOCKER')),
|
||||
'level' => env('WS_LOGGER_SYSLOG_LEVEL', Logger::ERROR),
|
||||
'name' => env('WS_LOGGER_SYSLOG_NAME', ag($config, 'name')),
|
||||
],
|
||||
|
||||
@@ -198,7 +198,11 @@ final class StateEntity implements StateInterface
|
||||
}
|
||||
|
||||
if (null !== ($entity->{$key} ?? null) && $this->{$key} !== $entity->{$key}) {
|
||||
$this->{$key} = $entity->{$key};
|
||||
if ('meta' === $key) {
|
||||
$this->{$key} = array_replace_recursive($this->{$key} ?? [], $entity->{$key} ?? []);
|
||||
} else {
|
||||
$this->{$key} = $entity->{$key};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ use App\Libs\Mappers\ImportInterface;
|
||||
use Closure;
|
||||
use DateInterval;
|
||||
use DateTimeInterface;
|
||||
use Exception;
|
||||
use JsonException;
|
||||
use JsonMachine\Exception\PathNotFoundException;
|
||||
use JsonMachine\Items;
|
||||
@@ -70,10 +71,14 @@ class JellyfinServer implements ServerInterface
|
||||
protected bool $initialized = false;
|
||||
protected bool $isEmby = false;
|
||||
protected array $persist = [];
|
||||
protected string $cacheKey;
|
||||
protected string $cacheKey = '';
|
||||
protected array $cacheData = [];
|
||||
protected string|int|null $uuid = null;
|
||||
|
||||
protected array $showInfo = [];
|
||||
protected array $cacheShow = [];
|
||||
protected string $cacheShowKey = '';
|
||||
|
||||
public function __construct(
|
||||
protected HttpClientInterface $http,
|
||||
protected LoggerInterface $logger,
|
||||
@@ -110,11 +115,16 @@ class JellyfinServer implements ServerInterface
|
||||
$cloned->initialized = true;
|
||||
|
||||
$cloned->cacheKey = $options['cache_key'] ?? md5(__CLASS__ . '.' . $name . ($userId ?? $token) . $url);
|
||||
$cloned->cacheShowKey = $cloned->cacheKey . '_show';
|
||||
|
||||
if ($cloned->cache->has($cloned->cacheKey)) {
|
||||
$cloned->cacheData = $cloned->cache->get($cloned->cacheKey);
|
||||
}
|
||||
|
||||
if ($cloned->cache->has($cloned->cacheShowKey)) {
|
||||
$cloned->cacheShow = $cloned->cache->get($cloned->cacheShowKey);
|
||||
}
|
||||
|
||||
if (null !== ($options['emby'] ?? null)) {
|
||||
unset($options['emby']);
|
||||
}
|
||||
@@ -273,6 +283,8 @@ class JellyfinServer implements ServerInterface
|
||||
throw new HttpException(sprintf('%s: Not allowed event [%s]', afterLast(__CLASS__, '\\'), $event), 200);
|
||||
}
|
||||
|
||||
$isTainted = in_array($event, self::WEBHOOK_TAINTED_EVENTS);
|
||||
|
||||
$date = time();
|
||||
|
||||
$meta = match ($type) {
|
||||
@@ -313,7 +325,11 @@ class JellyfinServer implements ServerInterface
|
||||
);
|
||||
}
|
||||
|
||||
$guids = $this->getGuids($type, $guids);
|
||||
if (false === $isTainted && StateInterface::TYPE_EPISODE === $type) {
|
||||
$meta['parent'] = $this->getParentGUIDs(ag($json, 'ItemId'), ag($json, 'SeriesName'));
|
||||
}
|
||||
|
||||
$guids = $this->getGuids($guids, $type);
|
||||
|
||||
foreach (Guid::fromArray($guids)->getPointers() as $guid) {
|
||||
$this->cacheData[$guid] = ag($json, 'Item.ItemId');
|
||||
@@ -333,9 +349,100 @@ class JellyfinServer implements ServerInterface
|
||||
saveWebhookPayload($request, "{$this->name}.{$event}", $json + ['entity' => $row]);
|
||||
}
|
||||
|
||||
return Container::get(StateInterface::class)::fromArray($row)->setIsTainted(
|
||||
in_array($event, self::WEBHOOK_TAINTED_EVENTS)
|
||||
);
|
||||
return Container::get(StateInterface::class)::fromArray($row)->setIsTainted($isTainted);
|
||||
}
|
||||
|
||||
protected function getParentGUIDs(mixed $id, string|null $series): array
|
||||
{
|
||||
if (null !== $series && array_key_exists($series, $this->cacheShow)) {
|
||||
return $this->cacheShow[$series];
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $this->http->request(
|
||||
'GET',
|
||||
(string)$this->url->withPath(
|
||||
sprintf('/Users/%s/items/' . $id, $this->user)
|
||||
),
|
||||
$this->getHeaders()
|
||||
);
|
||||
|
||||
if (200 !== $response->getStatusCode()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$json = json_decode($response->getContent(), true, flags: JSON_THROW_ON_ERROR);
|
||||
|
||||
if (null === ($type = ag($json, 'Type'))) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (StateInterface::TYPE_EPISODE !== strtolower($type)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (null === ($seriesId = ag($json, 'SeriesId'))) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$response = $this->http->request(
|
||||
'GET',
|
||||
(string)$this->url->withPath(
|
||||
sprintf('/Users/%s/items/' . $seriesId, $this->user)
|
||||
)->withQuery(http_build_query(['Fields' => 'ProviderIds'])),
|
||||
$this->getHeaders()
|
||||
);
|
||||
|
||||
if (200 !== $response->getStatusCode()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$json = json_decode($response->getContent(), true, flags: JSON_THROW_ON_ERROR);
|
||||
|
||||
$series = $json['Name'] ?? $json['OriginalTitle'] ?? $json['Id'] ?? random_int(1, PHP_INT_MAX);
|
||||
|
||||
$providersId = (array)ag($json, 'ProviderIds', []);
|
||||
|
||||
if (!$this->hasSupportedIds($providersId)) {
|
||||
$this->cacheShow[$series] = [];
|
||||
return $this->cacheShow[$series];
|
||||
}
|
||||
|
||||
$guids = [];
|
||||
|
||||
foreach (Guid::fromArray($this->getGuids($providersId))->getPointers() as $guid) {
|
||||
[$type, $id] = explode('://', $guid);
|
||||
$guids[$type] = $id;
|
||||
}
|
||||
|
||||
$this->cacheShow[$series] = $guids;
|
||||
|
||||
return $this->cacheShow[$series];
|
||||
} catch (ExceptionInterface $e) {
|
||||
$this->logger->error($e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine()
|
||||
]);
|
||||
return [];
|
||||
} catch (JsonException $e) {
|
||||
$this->logger->error(
|
||||
sprintf('Unable to decode %s response. Reason: \'%s\'.', $this->name, $e->getMessage()),
|
||||
[
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine()
|
||||
]
|
||||
);
|
||||
return [];
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error(
|
||||
sprintf('ERROR: %s response. Reason: \'%s\'.', $this->name, $e->getMessage()),
|
||||
[
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine()
|
||||
]
|
||||
);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
protected function getHeaders(): array
|
||||
@@ -360,7 +467,7 @@ class JellyfinServer implements ServerInterface
|
||||
return array_replace_recursive($opts, $this->options['client'] ?? []);
|
||||
}
|
||||
|
||||
protected function getLibraries(Closure $ok, Closure $error): array
|
||||
protected function getLibraries(Closure $ok, Closure $error, bool $includeParent = false): array
|
||||
{
|
||||
$this->checkConfig(true);
|
||||
|
||||
@@ -374,8 +481,7 @@ class JellyfinServer implements ServerInterface
|
||||
http_build_query(
|
||||
[
|
||||
'Recursive' => 'false',
|
||||
'Fields' => 'ProviderIds',
|
||||
'enableUserData' => 'true',
|
||||
'enableUserData' => 'false',
|
||||
'enableImages' => 'false',
|
||||
]
|
||||
)
|
||||
@@ -432,6 +538,64 @@ class JellyfinServer implements ServerInterface
|
||||
$promises = [];
|
||||
$ignored = $unsupported = 0;
|
||||
|
||||
if (true === $includeParent) {
|
||||
foreach ($listDirs as $section) {
|
||||
$key = (string)ag($section, 'Id');
|
||||
$title = ag($section, 'Name', '???');
|
||||
|
||||
if ('tvshows' !== ag($section, 'CollectionType', 'unknown')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$cName = sprintf('(%s) - (%s:%s)', $title, 'show', $key);
|
||||
|
||||
if (null !== $ignoreIds && in_array($key, $ignoreIds, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$url = $this->url->withPath(sprintf('/Users/%s/items/', $this->user))->withQuery(
|
||||
http_build_query(
|
||||
[
|
||||
'parentId' => $key,
|
||||
'recursive' => 'false',
|
||||
'enableUserData' => 'false',
|
||||
'enableImages' => 'false',
|
||||
'Fields' => 'ProviderIds,DateCreated,OriginalTitle',
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->logger->debug(
|
||||
sprintf('Requesting %s - %s library parents content.', $this->name, $cName),
|
||||
['url' => $url]
|
||||
);
|
||||
|
||||
try {
|
||||
$promises[] = $this->http->request(
|
||||
'GET',
|
||||
(string)$url,
|
||||
array_replace_recursive($this->getHeaders(), [
|
||||
'user_data' => [
|
||||
'ok' => $ok($cName, 'show', $url),
|
||||
'error' => $error($cName, 'show', $url),
|
||||
]
|
||||
])
|
||||
);
|
||||
} catch (ExceptionInterface $e) {
|
||||
$this->logger->error(
|
||||
sprintf(
|
||||
'Request to %s library - %s parents failed. Reason: %s',
|
||||
$this->name,
|
||||
$cName,
|
||||
$e->getMessage()
|
||||
),
|
||||
['url' => $url]
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($listDirs as $section) {
|
||||
$key = (string)ag($section, 'Id');
|
||||
$title = ag($section, 'Name', '???');
|
||||
@@ -641,7 +805,7 @@ class JellyfinServer implements ServerInterface
|
||||
public function pull(ImportInterface $mapper, DateTimeInterface|null $after = null): array
|
||||
{
|
||||
return $this->getLibraries(
|
||||
function (string $cName, string $type) use ($after, $mapper) {
|
||||
ok: function (string $cName, string $type) use ($after, $mapper) {
|
||||
return function (ResponseInterface $response) use ($mapper, $cName, $type, $after) {
|
||||
try {
|
||||
if (200 !== $response->getStatusCode()) {
|
||||
@@ -734,7 +898,7 @@ class JellyfinServer implements ServerInterface
|
||||
}
|
||||
};
|
||||
},
|
||||
function (string $cName, string $type, UriInterface|string $url) {
|
||||
error: function (string $cName, string $type, UriInterface|string $url) {
|
||||
return fn(Throwable $e) => $this->logger->error(
|
||||
sprintf('Request to %s - %s - failed. Reason: \'%s\'.', $this->name, $cName, $e->getMessage()),
|
||||
[
|
||||
@@ -743,7 +907,8 @@ class JellyfinServer implements ServerInterface
|
||||
'url' => $url
|
||||
]
|
||||
);
|
||||
}
|
||||
},
|
||||
includeParent: true,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -920,7 +1085,7 @@ class JellyfinServer implements ServerInterface
|
||||
public function export(ExportInterface $mapper, DateTimeInterface|null $after = null): array
|
||||
{
|
||||
return $this->getLibraries(
|
||||
function (string $cName, string $type) use ($mapper, $after) {
|
||||
ok: function (string $cName, string $type) use ($mapper, $after) {
|
||||
return function (ResponseInterface $response) use ($mapper, $cName, $type, $after) {
|
||||
try {
|
||||
if (200 !== $response->getStatusCode()) {
|
||||
@@ -1016,7 +1181,7 @@ class JellyfinServer implements ServerInterface
|
||||
}
|
||||
};
|
||||
},
|
||||
function (string $cName, string $type, UriInterface|string $url) {
|
||||
error: function (string $cName, string $type, UriInterface|string $url) {
|
||||
return fn(Throwable $e) => $this->logger->error(
|
||||
sprintf('Request to %s - %s - failed. Reason: \'%s\'.', $this->name, $cName, $e->getMessage()),
|
||||
[
|
||||
@@ -1025,14 +1190,15 @@ class JellyfinServer implements ServerInterface
|
||||
'url' => $url
|
||||
]
|
||||
);
|
||||
}
|
||||
},
|
||||
includeParent: false,
|
||||
);
|
||||
}
|
||||
|
||||
public function cache(): array
|
||||
{
|
||||
return $this->getLibraries(
|
||||
function (string $cName, string $type) {
|
||||
ok: function (string $cName, string $type) {
|
||||
return function (ResponseInterface $response) use ($cName, $type) {
|
||||
try {
|
||||
if (200 !== $response->getStatusCode()) {
|
||||
@@ -1119,7 +1285,7 @@ class JellyfinServer implements ServerInterface
|
||||
}
|
||||
};
|
||||
},
|
||||
function (string $cName, string $type, UriInterface|string $url) {
|
||||
error: function (string $cName, string $type, UriInterface|string $url) {
|
||||
return fn(Throwable $e) => $this->logger->error(
|
||||
sprintf('Request to %s - %s - failed. Reason: \'%s\'.', $this->name, $cName, $e->getMessage()),
|
||||
[
|
||||
@@ -1128,7 +1294,8 @@ class JellyfinServer implements ServerInterface
|
||||
'url' => $url
|
||||
]
|
||||
);
|
||||
}
|
||||
},
|
||||
includeParent: false,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1173,7 +1340,7 @@ class JellyfinServer implements ServerInterface
|
||||
return;
|
||||
}
|
||||
|
||||
$guids = $this->getGuids($type, (array)($item->ProviderIds ?? []));
|
||||
$guids = $this->getGuids((array)($item->ProviderIds ?? []), $type);
|
||||
|
||||
foreach (Guid::fromArray($guids)->getPointers() as $guid) {
|
||||
$this->cacheData[$guid] = $item->Id;
|
||||
@@ -1246,6 +1413,39 @@ class JellyfinServer implements ServerInterface
|
||||
}
|
||||
}
|
||||
|
||||
protected function processShow(StdClass $item, string $library): void
|
||||
{
|
||||
$iName = sprintf(
|
||||
'%s - %s - [%s (%d)]',
|
||||
$this->name,
|
||||
$library,
|
||||
$item->Name ?? $item->OriginalTitle ?? '??',
|
||||
$item->ProductionYear ?? 0000
|
||||
);
|
||||
|
||||
$this->logger->debug(sprintf('Processing %s. For GUIDs.', $iName));
|
||||
|
||||
$providersId = (array)($item->ProviderIds ?? []);
|
||||
|
||||
if (!$this->hasSupportedIds($providersId)) {
|
||||
$message = sprintf('Ignoring %s. No valid/supported GUIDs.', $iName);
|
||||
if (empty($providersId)) {
|
||||
$message .= 'Most likely unmatched TV show.';
|
||||
}
|
||||
$this->logger->info($message, ['guids' => empty($providersId) ? 'None' : $providersId]);
|
||||
return;
|
||||
}
|
||||
|
||||
$guids = [];
|
||||
|
||||
foreach (Guid::fromArray($this->getGuids($providersId))->getPointers() as $guid) {
|
||||
[$type, $id] = explode('://', $guid);
|
||||
$guids[$type] = $id;
|
||||
}
|
||||
|
||||
$this->showInfo[$item->Id] = $guids;
|
||||
}
|
||||
|
||||
protected function processImport(
|
||||
ImportInterface $mapper,
|
||||
string $type,
|
||||
@@ -1254,6 +1454,11 @@ class JellyfinServer implements ServerInterface
|
||||
DateTimeInterface|null $after = null
|
||||
): void {
|
||||
try {
|
||||
if ('show' === $type) {
|
||||
$this->processShow($item, $library);
|
||||
return;
|
||||
}
|
||||
|
||||
Data::increment($this->name, $library . '_total');
|
||||
Data::increment($this->name, $type . '_total');
|
||||
|
||||
@@ -1293,22 +1498,20 @@ class JellyfinServer implements ServerInterface
|
||||
|
||||
$guids = (array)($item->ProviderIds ?? []);
|
||||
|
||||
$this->logger->info(
|
||||
sprintf(
|
||||
'Ignoring %s. No valid GUIDs. Possibly unmatched item?',
|
||||
$iName
|
||||
),
|
||||
[
|
||||
'guids' => empty($guids) ? 'None' : $guids
|
||||
]
|
||||
);
|
||||
$message = sprintf('Ignoring %s. No valid/supported GUIDs.', $iName);
|
||||
|
||||
if (empty($guids)) {
|
||||
$message .= 'Most likely unmatched item.';
|
||||
}
|
||||
|
||||
$this->logger->info($message, ['guids' => empty($guids) ? 'None' : $guids]);
|
||||
|
||||
Data::increment($this->name, $type . '_ignored_no_supported_guid');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$guids = $this->getGuids($type, (array)($item->ProviderIds ?? []));
|
||||
$guids = $this->getGuids((array)($item->ProviderIds ?? []), $type);
|
||||
|
||||
foreach (Guid::fromArray($guids)->getPointers() as $guid) {
|
||||
$this->cacheData[$guid] = $item->Id;
|
||||
@@ -1341,6 +1544,10 @@ class JellyfinServer implements ServerInterface
|
||||
'title' => $item->Name ?? '',
|
||||
'date' => makeDate($item->PremiereDate ?? $item->ProductionYear ?? 'now')->format('Y-m-d'),
|
||||
];
|
||||
|
||||
if (null !== ($item->SeriesId ?? null)) {
|
||||
$meta['parent'] = $this->showInfo[$item->SeriesId] ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
$row = [
|
||||
@@ -1368,7 +1575,7 @@ class JellyfinServer implements ServerInterface
|
||||
if (!$this->hasSupportedIds((array)($item->ProviderIds ?? []))) {
|
||||
return;
|
||||
}
|
||||
$guids = $this->getGuids($type, (array)($item->ProviderIds ?? []));
|
||||
$guids = $this->getGuids((array)($item->ProviderIds ?? []), $type);
|
||||
|
||||
foreach (Guid::fromArray($guids)->getPointers() as $guid) {
|
||||
$this->cacheData[$guid] = $item->Id;
|
||||
@@ -1381,7 +1588,7 @@ class JellyfinServer implements ServerInterface
|
||||
}
|
||||
}
|
||||
|
||||
protected function getGuids(string $type, array $ids): array
|
||||
protected function getGuids(array $ids, string|null $type = null): array
|
||||
{
|
||||
$guid = [];
|
||||
|
||||
@@ -1392,14 +1599,10 @@ class JellyfinServer implements ServerInterface
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($key !== 'plex') {
|
||||
if (null !== $type) {
|
||||
$value = $type . '/' . $value;
|
||||
}
|
||||
|
||||
if ('string' !== Guid::SUPPORTED[self::GUID_MAPPER[$key]]) {
|
||||
settype($value, Guid::SUPPORTED[self::GUID_MAPPER[$key]]);
|
||||
}
|
||||
|
||||
$guid[self::GUID_MAPPER[$key]] = $value;
|
||||
}
|
||||
|
||||
@@ -1427,6 +1630,10 @@ class JellyfinServer implements ServerInterface
|
||||
if (!empty($this->cacheKey)) {
|
||||
$this->cache->set($this->cacheKey, $this->cacheData, new DateInterval('P1Y'));
|
||||
}
|
||||
|
||||
if (!empty($this->cacheShowKey)) {
|
||||
$this->cache->set($this->cacheShowKey, $this->cacheShow, new DateInterval('PT30M'));
|
||||
}
|
||||
}
|
||||
|
||||
protected static function afterString(string $subject, string $search): string
|
||||
|
||||
@@ -15,6 +15,7 @@ use App\Libs\Mappers\ImportInterface;
|
||||
use Closure;
|
||||
use DateInterval;
|
||||
use DateTimeInterface;
|
||||
use Exception;
|
||||
use JsonException;
|
||||
use JsonMachine\Exception\PathNotFoundException;
|
||||
use JsonMachine\Items;
|
||||
@@ -85,6 +86,10 @@ class PlexServer implements ServerInterface
|
||||
protected string|int|null $uuid = null;
|
||||
protected string|int|null $user = null;
|
||||
|
||||
protected array $showInfo = [];
|
||||
protected array $cacheShow = [];
|
||||
protected string $cacheShowKey = '';
|
||||
|
||||
public function __construct(
|
||||
protected HttpClientInterface $http,
|
||||
protected LoggerInterface $logger,
|
||||
@@ -115,11 +120,16 @@ class PlexServer implements ServerInterface
|
||||
$cloned->options = $options;
|
||||
$cloned->persist = $persist;
|
||||
$cloned->cacheKey = $opts['cache_key'] ?? md5(__CLASS__ . '.' . $name . $url);
|
||||
$cloned->cacheShowKey = $cloned->cacheKey . '_show';
|
||||
|
||||
if ($cloned->cache->has($cloned->cacheKey)) {
|
||||
$cloned->cacheData = $cloned->cache->get($cloned->cacheKey);
|
||||
}
|
||||
|
||||
if ($cloned->cache->has($cloned->cacheShowKey)) {
|
||||
$cloned->cacheShow = $cloned->cache->get($cloned->cacheShowKey);
|
||||
}
|
||||
|
||||
$cloned->initialized = true;
|
||||
|
||||
return $cloned;
|
||||
@@ -300,6 +310,8 @@ class PlexServer implements ServerInterface
|
||||
throw new HttpException(sprintf('%s: Not allowed event [%s]', afterLast(__CLASS__, '\\'), $event), 200);
|
||||
}
|
||||
|
||||
$isTainted = in_array($event, self::WEBHOOK_TAINTED_EVENTS);
|
||||
|
||||
$ignoreIds = null;
|
||||
|
||||
if (null !== ($this->options['ignore'] ?? null)) {
|
||||
@@ -368,12 +380,18 @@ class PlexServer implements ServerInterface
|
||||
);
|
||||
}
|
||||
|
||||
$guids = $this->getGuids($type, $json['Metadata']['Guid'] ?? []);
|
||||
$guids = $this->getGuids($json['Metadata']['Guid'] ?? [], $type);
|
||||
|
||||
foreach (Guid::fromArray($guids)->getPointers() as $guid) {
|
||||
$this->cacheData[$guid] = ag($json, 'Metadata.guid');
|
||||
}
|
||||
|
||||
if (false === $isTainted && StateInterface::TYPE_EPISODE === $type) {
|
||||
$meta['parent'] = $this->getParentGUIDs(
|
||||
$json['Metadata']['grandparentRatingKey'] ?? $json['Metadata']['parentRatingKey']
|
||||
);
|
||||
}
|
||||
|
||||
$row = [
|
||||
'type' => $type,
|
||||
'updated' => $date,
|
||||
@@ -386,9 +404,85 @@ class PlexServer implements ServerInterface
|
||||
saveWebhookPayload($request, "{$this->name}.{$event}", $json + ['entity' => $row]);
|
||||
}
|
||||
|
||||
return Container::get(StateInterface::class)::fromArray($row)->setIsTainted(
|
||||
in_array($event, self::WEBHOOK_TAINTED_EVENTS)
|
||||
);
|
||||
return Container::get(StateInterface::class)::fromArray($row)->setIsTainted($isTainted);
|
||||
}
|
||||
|
||||
protected function getParentGUIDs(mixed $id): array
|
||||
{
|
||||
if (array_key_exists($id, $this->cacheShow)) {
|
||||
return $this->cacheShow[$id];
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $this->http->request(
|
||||
'GET',
|
||||
(string)$this->url->withPath('/library/metadata/' . $id),
|
||||
$this->getHeaders()
|
||||
);
|
||||
|
||||
if (200 !== $response->getStatusCode()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$json = json_decode($response->getContent(), true, flags: JSON_THROW_ON_ERROR);
|
||||
|
||||
$json = ag($json, 'MediaContainer.Metadata')[0] ?? [];
|
||||
|
||||
if (null === ($type = ag($json, 'type'))) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ('show' !== strtolower($type)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (null === ($json['Guid'] ?? null)) {
|
||||
$json['Guid'] = [['id' => $json['guid']]];
|
||||
} else {
|
||||
$json['Guid'][] = ['id' => $json['guid']];
|
||||
}
|
||||
|
||||
|
||||
if (!$this->hasSupportedIds($json['Guid'])) {
|
||||
$this->cacheShow[$id] = [];
|
||||
return $this->cacheShow[$id];
|
||||
}
|
||||
|
||||
$guids = [];
|
||||
|
||||
foreach (Guid::fromArray($this->getGuids($json['Guid']))->getPointers() as $guid) {
|
||||
[$type, $id] = explode('://', $guid);
|
||||
$guids[$type] = $id;
|
||||
}
|
||||
|
||||
$this->cacheShow[$id] = $guids;
|
||||
|
||||
return $this->cacheShow[$id];
|
||||
} catch (ExceptionInterface $e) {
|
||||
$this->logger->error($e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine()
|
||||
]);
|
||||
return [];
|
||||
} catch (JsonException $e) {
|
||||
$this->logger->error(
|
||||
sprintf('Unable to decode %s response. Reason: \'%s\'.', $this->name, $e->getMessage()),
|
||||
[
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine()
|
||||
]
|
||||
);
|
||||
return [];
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error(
|
||||
sprintf('ERROR: %s response. Reason: \'%s\'.', $this->name, $e->getMessage()),
|
||||
[
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine()
|
||||
]
|
||||
);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private function getHeaders(): array
|
||||
@@ -403,7 +497,7 @@ class PlexServer implements ServerInterface
|
||||
return array_replace_recursive($opts, $this->options['client'] ?? []);
|
||||
}
|
||||
|
||||
protected function getLibraries(Closure $ok, Closure $error): array
|
||||
protected function getLibraries(Closure $ok, Closure $error, bool $includeParent = false): array
|
||||
{
|
||||
$this->checkConfig();
|
||||
|
||||
@@ -469,6 +563,66 @@ class PlexServer implements ServerInterface
|
||||
$promises = [];
|
||||
$ignored = $unsupported = 0;
|
||||
|
||||
if (true === $includeParent) {
|
||||
foreach ($listDirs as $section) {
|
||||
$key = (int)ag($section, 'key');
|
||||
$title = ag($section, 'title', '???');
|
||||
|
||||
if ('show' !== ag($section, 'type', 'unknown')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$cName = sprintf('(%s) - (%s:%s)', $title, 'show', $key);
|
||||
|
||||
if (null !== $ignoreIds && in_array($key, $ignoreIds)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$url = $this->url->withPath(sprintf('/library/sections/%d/all', $key))->withQuery(
|
||||
http_build_query(
|
||||
[
|
||||
'type' => 2,
|
||||
'sort' => 'addedAt:asc',
|
||||
'includeGuids' => 1,
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->logger->debug(
|
||||
sprintf('Requesting %s - %s library parents content.', $this->name, $cName),
|
||||
['url' => $url]
|
||||
);
|
||||
|
||||
try {
|
||||
$promises[] = $this->http->request(
|
||||
'GET',
|
||||
(string)$url,
|
||||
array_replace_recursive($this->getHeaders(), [
|
||||
'user_data' => [
|
||||
'ok' => $ok($cName, 'show', $url),
|
||||
'error' => $error($cName, 'show', $url),
|
||||
]
|
||||
])
|
||||
);
|
||||
} catch (ExceptionInterface $e) {
|
||||
$this->logger->error(
|
||||
sprintf(
|
||||
'Request to %s library - %s parents failed. Reason: %s',
|
||||
$this->name,
|
||||
$cName,
|
||||
$e->getMessage()
|
||||
),
|
||||
[
|
||||
'url' => $url,
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
]
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($listDirs as $section) {
|
||||
$key = (int)ag($section, 'key');
|
||||
$type = ag($section, 'type', 'unknown');
|
||||
@@ -686,7 +840,7 @@ class PlexServer implements ServerInterface
|
||||
public function pull(ImportInterface $mapper, DateTimeInterface|null $after = null): array
|
||||
{
|
||||
return $this->getLibraries(
|
||||
function (string $cName, string $type) use ($after, $mapper) {
|
||||
ok: function (string $cName, string $type) use ($after, $mapper) {
|
||||
return function (ResponseInterface $response) use ($mapper, $cName, $type, $after) {
|
||||
try {
|
||||
if (200 !== $response->getStatusCode()) {
|
||||
@@ -783,7 +937,7 @@ class PlexServer implements ServerInterface
|
||||
}
|
||||
};
|
||||
},
|
||||
function (string $cName, string $type, UriInterface|string $url) {
|
||||
error: function (string $cName, string $type, UriInterface|string $url) {
|
||||
return fn(Throwable $e) => $this->logger->error(
|
||||
sprintf('Request to %s - %s - failed. Reason: \'%s\'.', $this->name, $cName, $e->getMessage()),
|
||||
[
|
||||
@@ -792,7 +946,8 @@ class PlexServer implements ServerInterface
|
||||
'line' => $e->getLine(),
|
||||
]
|
||||
);
|
||||
}
|
||||
},
|
||||
includeParent: true
|
||||
);
|
||||
}
|
||||
|
||||
@@ -963,7 +1118,7 @@ class PlexServer implements ServerInterface
|
||||
public function export(ExportInterface $mapper, DateTimeInterface|null $after = null): array
|
||||
{
|
||||
return $this->getLibraries(
|
||||
function (string $cName, string $type) use ($mapper, $after) {
|
||||
ok: function (string $cName, string $type) use ($mapper, $after) {
|
||||
return function (ResponseInterface $response) use ($mapper, $cName, $type, $after) {
|
||||
try {
|
||||
if (200 !== $response->getStatusCode()) {
|
||||
@@ -1059,7 +1214,7 @@ class PlexServer implements ServerInterface
|
||||
}
|
||||
};
|
||||
},
|
||||
function (string $cName, string $type, UriInterface|string $url) {
|
||||
error: function (string $cName, string $type, UriInterface|string $url) {
|
||||
return fn(Throwable $e) => $this->logger->error(
|
||||
sprintf('Request to %s - %s - failed. Reason: \'%s\'.', $this->name, $cName, $e->getMessage()),
|
||||
[
|
||||
@@ -1068,14 +1223,15 @@ class PlexServer implements ServerInterface
|
||||
'line' => $e->getLine(),
|
||||
]
|
||||
);
|
||||
}
|
||||
},
|
||||
includeParent: false
|
||||
);
|
||||
}
|
||||
|
||||
public function cache(): array
|
||||
{
|
||||
return $this->getLibraries(
|
||||
function (string $cName, string $type) {
|
||||
ok: function (string $cName, string $type) {
|
||||
return function (ResponseInterface $response) use ($cName, $type) {
|
||||
try {
|
||||
if (200 !== $response->getStatusCode()) {
|
||||
@@ -1164,7 +1320,7 @@ class PlexServer implements ServerInterface
|
||||
}
|
||||
};
|
||||
},
|
||||
function (string $cName, string $type, UriInterface|string $url) {
|
||||
error: function (string $cName, string $type, UriInterface|string $url) {
|
||||
return fn(Throwable $e) => $this->logger->error(
|
||||
sprintf('Request to %s - %s - failed. Reason: \'%s\'.', $this->name, $cName, $e->getMessage()),
|
||||
[
|
||||
@@ -1173,7 +1329,8 @@ class PlexServer implements ServerInterface
|
||||
'line' => $e->getLine(),
|
||||
]
|
||||
);
|
||||
}
|
||||
},
|
||||
includeParent: false
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1184,9 +1341,9 @@ class PlexServer implements ServerInterface
|
||||
StdClass $item,
|
||||
DateTimeInterface|null $after = null
|
||||
): void {
|
||||
Data::increment($this->name, $type . '_total');
|
||||
|
||||
try {
|
||||
Data::increment($this->name, $type . '_total');
|
||||
|
||||
if (StateInterface::TYPE_MOVIE === $type) {
|
||||
$iName = sprintf(
|
||||
'%s - %s - [%s (%d)]',
|
||||
@@ -1221,7 +1378,7 @@ class PlexServer implements ServerInterface
|
||||
return;
|
||||
}
|
||||
|
||||
$guids = $this->getGuids($type, $item->Guid ?? []);
|
||||
$guids = $this->getGuids($item->Guid ?? [], $type);
|
||||
|
||||
foreach (Guid::fromArray($guids)->getPointers() as $guid) {
|
||||
$this->cacheData[$guid] = $item->guid;
|
||||
@@ -1299,6 +1456,43 @@ class PlexServer implements ServerInterface
|
||||
}
|
||||
}
|
||||
|
||||
protected function processShow(StdClass $item, string $library): void
|
||||
{
|
||||
$iName = sprintf(
|
||||
'%s - %s - [%s (%d)]',
|
||||
$this->name,
|
||||
$library,
|
||||
$item->title ?? $item->originalTitle ?? '??',
|
||||
$item->year ?? 0000
|
||||
);
|
||||
|
||||
$this->logger->debug(sprintf('Processing %s. For GUIDs.', $iName));
|
||||
|
||||
if (null === ($item->Guid ?? null)) {
|
||||
$item->Guid = [['id' => $item->guid]];
|
||||
} else {
|
||||
$item->Guid[] = ['id' => $item->guid];
|
||||
}
|
||||
|
||||
if (!$this->hasSupportedIds($item->Guid)) {
|
||||
$message = sprintf('Ignoring %s. No valid/supported GUIDs.', $iName);
|
||||
if (empty($item->Guid)) {
|
||||
$message .= 'Possibly unmatched show';
|
||||
}
|
||||
$this->logger->info($message, ['guids' => empty($item->Guid) ? 'None' : $item->Guid]);
|
||||
return;
|
||||
}
|
||||
|
||||
$guids = [];
|
||||
|
||||
foreach (Guid::fromArray($this->getGuids($item->Guid))->getPointers() as $guid) {
|
||||
[$type, $id] = explode('://', $guid);
|
||||
$guids[$type] = $id;
|
||||
}
|
||||
|
||||
$this->showInfo[$item->ratingKey] = $guids;
|
||||
}
|
||||
|
||||
protected function processImport(
|
||||
ImportInterface $mapper,
|
||||
string $type,
|
||||
@@ -1307,6 +1501,11 @@ class PlexServer implements ServerInterface
|
||||
DateTimeInterface|null $after = null
|
||||
): void {
|
||||
try {
|
||||
if ('show' === $type) {
|
||||
$this->processShow($item, $library);
|
||||
return;
|
||||
}
|
||||
|
||||
Data::increment($this->name, $library . '_total');
|
||||
Data::increment($this->name, $type . '_total');
|
||||
|
||||
@@ -1352,18 +1551,19 @@ class PlexServer implements ServerInterface
|
||||
}
|
||||
}
|
||||
|
||||
$this->logger->info(
|
||||
sprintf('Ignoring %s. No valid GUIDs. Possibly unmatched item?', $iName),
|
||||
[
|
||||
'guids' => empty($item->Guid) ? 'None' : $item->Guid,
|
||||
]
|
||||
);
|
||||
$message = sprintf('Ignoring %s. No valid/supported GUIDs.', $iName);
|
||||
|
||||
if (empty($item->Guid)) {
|
||||
$message .= 'Most likely unmatched item.';
|
||||
}
|
||||
|
||||
$this->logger->info($message, ['guids' => empty($item->Guid) ? 'None' : $item->Guid]);
|
||||
|
||||
Data::increment($this->name, $type . '_ignored_no_supported_guid');
|
||||
return;
|
||||
}
|
||||
|
||||
$guids = $this->getGuids($type, $item->Guid ?? []);
|
||||
$guids = $this->getGuids($item->Guid ?? [], $type);
|
||||
|
||||
foreach (Guid::fromArray($guids)->getPointers() as $guid) {
|
||||
$this->cacheData[$guid] = $item->guid;
|
||||
@@ -1394,6 +1594,10 @@ class PlexServer implements ServerInterface
|
||||
'title' => $item->title ?? $item->originalTitle ?? '??',
|
||||
'date' => makeDate($item->originallyAvailableAt ?? 'now')->format('Y-m-d'),
|
||||
];
|
||||
|
||||
if (null !== ($item->grandparentRatingKey ?? null)) {
|
||||
$meta['parent'] = $this->showInfo[$item->grandparentRatingKey] ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
$row = [
|
||||
@@ -1428,7 +1632,7 @@ class PlexServer implements ServerInterface
|
||||
return;
|
||||
}
|
||||
|
||||
$guids = $this->getGuids($type, $item->Guid ?? []);
|
||||
$guids = $this->getGuids($item->Guid ?? [], $type);
|
||||
|
||||
foreach (Guid::fromArray($guids)->getPointers() as $guid) {
|
||||
$this->cacheData[$guid] = $item->guid;
|
||||
@@ -1441,7 +1645,7 @@ class PlexServer implements ServerInterface
|
||||
}
|
||||
}
|
||||
|
||||
protected function getGuids(string $type, array $guids): array
|
||||
protected function getGuids(array $guids, string|null $type = null): array
|
||||
{
|
||||
$guid = [];
|
||||
|
||||
@@ -1463,14 +1667,10 @@ class PlexServer implements ServerInterface
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($key !== 'plex') {
|
||||
if ('plex' !== $key && null !== $type) {
|
||||
$value = $type . '/' . $value;
|
||||
}
|
||||
|
||||
if ('string' !== Guid::SUPPORTED[self::GUID_MAPPER[$key]]) {
|
||||
settype($value, Guid::SUPPORTED[self::GUID_MAPPER[$key]]);
|
||||
}
|
||||
|
||||
$guid[self::GUID_MAPPER[$key]] = $value;
|
||||
}
|
||||
|
||||
@@ -1509,6 +1709,10 @@ class PlexServer implements ServerInterface
|
||||
if (!empty($this->cacheKey) && !empty($this->cacheData) && true === $this->initialized) {
|
||||
$this->cache->set($this->cacheKey, $this->cacheData, new DateInterval('P1Y'));
|
||||
}
|
||||
|
||||
if (!empty($this->cacheShowKey) && !empty($this->cacheShow) && true === $this->initialized) {
|
||||
$this->cache->set($this->cacheShowKey, $this->cacheShow, new DateInterval('PT30M'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user