WIP: workig download button on movies and episodes
This commit is contained in:
2
assets/bootstrap.js
vendored
2
assets/bootstrap.js
vendored
@@ -6,6 +6,7 @@ import DownloadListRow from './components/download-list-row.js';
|
|||||||
import MonitorListRow from './components/monitor-list-row.js';
|
import MonitorListRow from './components/monitor-list-row.js';
|
||||||
import MovieContainer from "./components/movie-container.js";
|
import MovieContainer from "./components/movie-container.js";
|
||||||
import StatusCheckerSpan from "./components/status-checker-span.js";
|
import StatusCheckerSpan from "./components/status-checker-span.js";
|
||||||
|
import DownloadMediaButton from "./components/download-media-buton.js";
|
||||||
|
|
||||||
import { startStimulusApp } from '@symfony/stimulus-bundle';
|
import { startStimulusApp } from '@symfony/stimulus-bundle';
|
||||||
import Popover from '@stimulus-components/popover';
|
import Popover from '@stimulus-components/popover';
|
||||||
@@ -26,3 +27,4 @@ customElements.define('dl-tr', DownloadOptionTr, {extends: 'tr'});
|
|||||||
customElements.define('download-list-row', DownloadListRow, {extends: 'tr'});
|
customElements.define('download-list-row', DownloadListRow, {extends: 'tr'});
|
||||||
customElements.define('monitor-list-row', MonitorListRow, {extends: 'tr'});
|
customElements.define('monitor-list-row', MonitorListRow, {extends: 'tr'});
|
||||||
customElements.define('status-checker-span', StatusCheckerSpan, {extends: 'span'});
|
customElements.define('status-checker-span', StatusCheckerSpan, {extends: 'span'});
|
||||||
|
customElements.define('download-media-button', DownloadMediaButton);
|
||||||
|
|||||||
58
assets/components/download-media-buton.js
Normal file
58
assets/components/download-media-buton.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
export default class DownloadMediaButon extends HTMLElement
|
||||||
|
{
|
||||||
|
#filterEl;
|
||||||
|
#mediaType;
|
||||||
|
#imdbId;
|
||||||
|
#season = null;
|
||||||
|
#episode = null;
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
this.#filterEl = this.querySelector('#filter');
|
||||||
|
this.#mediaType = this.getAttribute('media-type');
|
||||||
|
this.#imdbId = this.getAttribute('imdb-id');
|
||||||
|
|
||||||
|
if (this.getAttribute('season') !== null && this.getAttribute('episode') !== null) {
|
||||||
|
this.#season = this.getAttribute('season');
|
||||||
|
this.#episode = this.getAttribute('episode');
|
||||||
|
} else if (this.#mediaType === 'tvshows') {
|
||||||
|
console.warn('Season and Episode are not set for \'tvshows\' media type');
|
||||||
|
}
|
||||||
|
this.addEventListener('click', this.download.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
this.removeEventListener('click', this.download.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedMoveCallback() {}
|
||||||
|
|
||||||
|
download() {
|
||||||
|
const preferencesForm = document.querySelector('[name="user_media_preferences_form"]');
|
||||||
|
const preferences = {
|
||||||
|
resolution: preferencesForm.querySelector('[id="user_media_preferences_form_resolution"]').value,
|
||||||
|
codec: preferencesForm.querySelector('[id="user_media_preferences_form_codec"]').value,
|
||||||
|
language: preferencesForm.querySelector('[id="user_media_preferences_form_language"]').value,
|
||||||
|
quality: preferencesForm.querySelector('[id="user_media_preferences_form_quality"]').value,
|
||||||
|
provider: preferencesForm.querySelector('[id="user_media_preferences_form_provider"]').value,
|
||||||
|
}
|
||||||
|
console.log(preferences);
|
||||||
|
fetch('/api/download', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
mediaType: this.#mediaType,
|
||||||
|
imdbId: this.#imdbId,
|
||||||
|
season: this.#season,
|
||||||
|
episode: this.#episode,
|
||||||
|
filter: preferences,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(json => {
|
||||||
|
console.log(json)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -82,6 +82,19 @@ readonly class DownloadMediaHandler implements HandlerInterface
|
|||||||
|
|
||||||
$matchingOption = $this->downloadOptionEvaluator->evaluateOptions($downloadOptions->results, $filter);
|
$matchingOption = $this->downloadOptionEvaluator->evaluateOptions($downloadOptions->results, $filter);
|
||||||
|
|
||||||
|
if ($matchingOption === null) {
|
||||||
|
$download->setProgress(100);
|
||||||
|
$download->setStatus('Failed');
|
||||||
|
$this->downloadRepository->getEntityManager()->flush();
|
||||||
|
$this->broadcaster->alert(
|
||||||
|
title:'Uh oh',
|
||||||
|
message: 'No matching download options found.',
|
||||||
|
type: 'warning',
|
||||||
|
mercureAlertTopic: $command->mercureAlertTopic
|
||||||
|
);
|
||||||
|
throw new UnrecoverableMessageHandlingException('No matching download options found.', 404);
|
||||||
|
}
|
||||||
|
|
||||||
$download->setUrl($matchingOption->url);
|
$download->setUrl($matchingOption->url);
|
||||||
$download->setTitle($media->media->title);
|
$download->setTitle($media->media->title);
|
||||||
$download->setFileName(
|
$download->setFileName(
|
||||||
@@ -120,7 +133,6 @@ readonly class DownloadMediaHandler implements HandlerInterface
|
|||||||
if ($download->getStatus() !== 'Paused') {
|
if ($download->getStatus() !== 'Paused') {
|
||||||
$this->downloadRepository->updateStatus($download->getId(), 'Complete');
|
$this->downloadRepository->updateStatus($download->getId(), 'Complete');
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (\Throwable $exception) {
|
} catch (\Throwable $exception) {
|
||||||
throw new UnrecoverableMessageHandlingException($exception->getMessage(), 500);
|
throw new UnrecoverableMessageHandlingException($exception->getMessage(), 500);
|
||||||
}
|
}
|
||||||
@@ -156,38 +168,42 @@ readonly class DownloadMediaHandler implements HandlerInterface
|
|||||||
{
|
{
|
||||||
$fileType = $option->ptn->container;
|
$fileType = $option->ptn->container;
|
||||||
return (match ($mediaType) {
|
return (match ($mediaType) {
|
||||||
MediaType::Movie => function () use ($media, $fileType) {
|
MediaType::Movie => function () use ($media, $fileType, $option) {
|
||||||
$template = "%s (%s) [imdbid-%s].%s";
|
$template = "%s (%s) [imdbid-%s]";
|
||||||
return sprintf(
|
$filename = sprintf(
|
||||||
$template,
|
$template,
|
||||||
$media->media->title,
|
$media->media->title,
|
||||||
$media->media->year,
|
$media->media->year,
|
||||||
$media->media->imdbId,
|
$media->media->imdbId,
|
||||||
$fileType
|
$fileType
|
||||||
);
|
);
|
||||||
|
if ($option->resolution !== null && $option->resolution !== '-') {
|
||||||
|
$filename .= ' - [' . $option->resolution . ']';
|
||||||
|
}
|
||||||
|
return $filename . '.' . $fileType;
|
||||||
},
|
},
|
||||||
MediaType::TvShow => function () use ($media, $fileType) {
|
MediaType::TvShow => function () use ($media, $fileType, $option) {
|
||||||
$template = "%s %s.%s";
|
$template = "%s %s.%s";
|
||||||
$episodeId = EpisodeId::fromSeasonEpisodeNumbers(
|
$episodeId = EpisodeId::fromSeasonEpisodeNumbers(
|
||||||
$media->season,
|
$media->season,
|
||||||
$media->episode,
|
$media->episode,
|
||||||
);
|
);
|
||||||
return sprintf(
|
$filename = sprintf(
|
||||||
$template,
|
$template,
|
||||||
$media->media->title,
|
$media->media->title,
|
||||||
$episodeId,
|
$episodeId,
|
||||||
$fileType
|
$fileType
|
||||||
);
|
);
|
||||||
|
if ($option->resolution !== null && $option->resolution !== '-') {
|
||||||
|
$filename .= ' - [' . $option->resolution . ']';
|
||||||
|
}
|
||||||
|
return $filename . '.' . $fileType;
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function validateDownloadUrl(string $downloadUrl)
|
public function validateDownloadUrl(string $downloadUrl)
|
||||||
{
|
{
|
||||||
$badFileSizes = [
|
|
||||||
2119075, // copyright infringement
|
|
||||||
];
|
|
||||||
|
|
||||||
$badFileLocations = [
|
$badFileLocations = [
|
||||||
'https://torrentio.strem.fun/videos/failed_infringement_v2.mp4' => 'Removed for Copyright Infringement.',
|
'https://torrentio.strem.fun/videos/failed_infringement_v2.mp4' => 'Removed for Copyright Infringement.',
|
||||||
'https://torrentio.strem.fun/videos/downloading_v2.mp4' => 'Your torrent is downloading to your debrid provider.'
|
'https://torrentio.strem.fun/videos/downloading_v2.mp4' => 'Your torrent is downloading to your debrid provider.'
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace App\Download\Action\Input;
|
|||||||
use App\Download\Action\Command\DownloadMediaCommand;
|
use App\Download\Action\Command\DownloadMediaCommand;
|
||||||
use OneToMany\RichBundle\Attribute\PropertyIgnored;
|
use OneToMany\RichBundle\Attribute\PropertyIgnored;
|
||||||
use OneToMany\RichBundle\Attribute\SourceRequest;
|
use OneToMany\RichBundle\Attribute\SourceRequest;
|
||||||
|
use OneToMany\RichBundle\Attribute\SourceRoute;
|
||||||
use OneToMany\RichBundle\Attribute\SourceSecurity;
|
use OneToMany\RichBundle\Attribute\SourceSecurity;
|
||||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||||
use OneToMany\RichBundle\Contract\InputInterface;
|
use OneToMany\RichBundle\Contract\InputInterface;
|
||||||
@@ -17,15 +18,19 @@ class DownloadMediaInput implements InputInterface
|
|||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
#[SourceRequest('imdbId')]
|
#[SourceRequest('imdbId')]
|
||||||
|
#[SourceRoute('imdbId')]
|
||||||
public string $imdbId,
|
public string $imdbId,
|
||||||
|
|
||||||
#[SourceRequest('mediaType')]
|
#[SourceRequest('mediaType')]
|
||||||
|
#[SourceRoute('mediaType')]
|
||||||
public string $mediaType,
|
public string $mediaType,
|
||||||
|
|
||||||
#[SourceRequest('season', nullify: true)]
|
#[SourceRequest('season')]
|
||||||
|
#[SourceRoute('season')]
|
||||||
public int|string|null $season = null,
|
public int|string|null $season = null,
|
||||||
|
|
||||||
#[SourceRequest('episode', nullify: true)]
|
#[SourceRequest('episode')]
|
||||||
|
#[SourceRoute('episode')]
|
||||||
public int|string|null $episode = null,
|
public int|string|null $episode = null,
|
||||||
|
|
||||||
#[SourceRequest('url', nullify: true)]
|
#[SourceRequest('url', nullify: true)]
|
||||||
@@ -64,8 +69,8 @@ class DownloadMediaInput implements InputInterface
|
|||||||
return new DownloadMediaCommand(
|
return new DownloadMediaCommand(
|
||||||
$this->imdbId,
|
$this->imdbId,
|
||||||
$this->mediaType,
|
$this->mediaType,
|
||||||
$this->season,
|
(int) $this->season,
|
||||||
$this->episode,
|
(int) $this->episode,
|
||||||
$this->url,
|
$this->url,
|
||||||
$this->filter,
|
$this->filter,
|
||||||
$this->downloadId,
|
$this->downloadId,
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ use App\Download\DownloadEvents;
|
|||||||
use App\Download\Framework\Entity\Download;
|
use App\Download\Framework\Entity\Download;
|
||||||
use App\Download\Framework\Repository\DownloadRepository;
|
use App\Download\Framework\Repository\DownloadRepository;
|
||||||
use App\EventLog\Action\Command\AddEventLogCommand;
|
use App\EventLog\Action\Command\AddEventLogCommand;
|
||||||
|
use App\Search\Action\Command\GetMediaInfoCommand;
|
||||||
|
use App\Search\Action\Handler\GetMediaInfoHandler;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\RequestStack;
|
use Symfony\Component\HttpFoundation\RequestStack;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
@@ -30,15 +32,18 @@ class ApiController extends AbstractController
|
|||||||
private readonly Broadcaster $broadcaster, private readonly RequestStack $requestStack,
|
private readonly Broadcaster $broadcaster, private readonly RequestStack $requestStack,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
#[Route('/api/download', name: 'api_download', methods: ['POST'])]
|
#[Route('/api/download/{mediaType?}/{imdbId?}/{season?}/{episode?}', name: 'api_download', methods: ['GET', 'POST'])]
|
||||||
public function download(
|
public function download(
|
||||||
DownloadMediaInput $input,
|
DownloadMediaInput $input,
|
||||||
DownloadMediaHandler $handler,
|
DownloadMediaHandler $handler,
|
||||||
|
GetMediaInfoHandler $getMediaInfoHandler,
|
||||||
): Response {
|
): Response {
|
||||||
|
$media = $getMediaInfoHandler->handle(new GetMediaInfoCommand($input->imdbId, $input->mediaType));
|
||||||
$download = $this->downloadRepository->insertNew(
|
$download = $this->downloadRepository->insertNew(
|
||||||
$this->getUser(),
|
$this->getUser(),
|
||||||
$input->imdbId,
|
$input->imdbId,
|
||||||
$input->mediaType,
|
$input->mediaType,
|
||||||
|
$media->media->title,
|
||||||
$input->season,
|
$input->season,
|
||||||
$input->episode,
|
$input->episode,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ class DownloadRepository extends ServiceEntityRepository
|
|||||||
UserInterface $user,
|
UserInterface $user,
|
||||||
string $imdbId,
|
string $imdbId,
|
||||||
string $mediaType,
|
string $mediaType,
|
||||||
|
string $title,
|
||||||
int|null $season = null,
|
int|null $season = null,
|
||||||
int|null $episode = null,
|
int|null $episode = null,
|
||||||
string $status = 'New'
|
string $status = 'New'
|
||||||
@@ -69,6 +70,7 @@ class DownloadRepository extends ServiceEntityRepository
|
|||||||
$download = (new Download())
|
$download = (new Download())
|
||||||
->setUser($user)
|
->setUser($user)
|
||||||
->setImdbId($imdbId)
|
->setImdbId($imdbId)
|
||||||
|
->setTitle($title)
|
||||||
->setMediaType($mediaType)
|
->setMediaType($mediaType)
|
||||||
->setProgress(0)
|
->setProgress(0)
|
||||||
->setStatus($status);
|
->setStatus($status);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
{% set preferences_form = form %}
|
{% set preferences_form = form %}
|
||||||
{{ form_start(preferences_form) }}
|
{{ form_start(preferences_form) }}
|
||||||
<h3 class="font-bold text-lg mb-2 md:mb-4">Apply a filter to your results</h3>
|
<h3 class="font-bold text-lg mb-2 md:mb-4">What type of file do you want?</h3>
|
||||||
<div class="flex flex-col md:flex-row gap-2 justify-between">
|
<div class="flex flex-col md:flex-row gap-2 justify-between">
|
||||||
{{ form_row(preferences_form.resolution) }}
|
{{ form_row(preferences_form.resolution) }}
|
||||||
{{ form_row(preferences_form.codec) }}
|
{{ form_row(preferences_form.codec) }}
|
||||||
@@ -37,6 +37,17 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
{% if results.media.mediaType == "movies" %}
|
||||||
|
<div class="w-full flex-col md:flex-row justify-between gap-2">
|
||||||
|
<download-media-button
|
||||||
|
class="px-2 py-1 bg-green-500 bg-opacity-60 font-medium rounded-lg text-sm cursor-pointer self-end"
|
||||||
|
title="Download {{ results.media.title }}"
|
||||||
|
media-type="{{ results.media.mediaType }}"
|
||||||
|
imdb-id="{{ results.media.imdbId }}">
|
||||||
|
download
|
||||||
|
</download-media-button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{{ form_end(preferences_form) }}
|
{{ form_end(preferences_form) }}
|
||||||
|
|
||||||
<div class="flex flex-col-reverse md:flex-row justify-between">
|
<div class="flex flex-col-reverse md:flex-row justify-between">
|
||||||
|
|||||||
@@ -53,11 +53,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-4 justify-between">
|
<div class="flex flex-col gap-4 justify-between">
|
||||||
<div class="flex flex-col items-center">
|
<download-media-button
|
||||||
<input class="episode-selector" type="checkbox"
|
class="px-2 py-2 bg-green-500 bg-opacity-80 font-medium text-center rounded-lg text-sm cursor-pointer items-center self-end"
|
||||||
{{ stimulus_target('tv-results', 'episodeSelector') }}
|
title="Download season {{ episode.seasonNumber }} episode {{ episode.episodeNumber }} of {{ this.title }}"
|
||||||
/>
|
media-type="tvshows"
|
||||||
</div>
|
imdb-id="{{ this.imdbId }}"
|
||||||
|
season="{{ episode.seasonNumber }}"
|
||||||
|
episode="{{ episode.episodeNumber }}"
|
||||||
|
>
|
||||||
|
<twig:ux:icon name="bi:download" width="20" />
|
||||||
|
</download-media-button>
|
||||||
|
|
||||||
|
<input class="episode-selector" type="checkbox"
|
||||||
|
{{ stimulus_target('tv-results', 'episodeSelector') }}
|
||||||
|
/>
|
||||||
|
|
||||||
<button class="dropdown-button flex flex-col items-end transition-transform duration-300 ease-in-out rotate-90" title="Click to expand the results table for season {{ episode.seasonNumber }} episode {{ episode.episodeNumber }}.">
|
<button class="dropdown-button flex flex-col items-end transition-transform duration-300 ease-in-out rotate-90" title="Click to expand the results table for season {{ episode.seasonNumber }} episode {{ episode.episodeNumber }}.">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32">
|
||||||
<path
|
<path
|
||||||
|
|||||||
@@ -19,9 +19,11 @@
|
|||||||
|
|
||||||
<div class="w-full flex flex-col">
|
<div class="w-full flex flex-col">
|
||||||
<div class="mb-4 flex flex-row gap-2 justify-between">
|
<div class="mb-4 flex flex-row gap-2 justify-between">
|
||||||
<h3 class="text-xl font-medium leading-tight font-bold text-gray-50">
|
<div class="flex flex-row justify-between items-center gap-2 w-full">
|
||||||
{{ results.media.title }} ({{ results.media.year }})
|
<h3 class="text-xl font-medium leading-tight font-bold text-gray-50">
|
||||||
</h3>
|
{{ results.media.title }} ({{ results.media.year }})
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% if results.media.mediaType == "tvshows" %}
|
{% if results.media.mediaType == "tvshows" %}
|
||||||
<div {{ stimulus_controller('monitor_button', {
|
<div {{ stimulus_controller('monitor_button', {
|
||||||
@@ -96,9 +98,9 @@
|
|||||||
|
|
||||||
{% if "movies" == results.media.mediaType %}
|
{% if "movies" == results.media.mediaType %}
|
||||||
<div class="flex flex-row justify-start items-end grow text-xs">
|
<div class="flex flex-row justify-start items-end grow text-xs">
|
||||||
<span class="results-count-badge py-1 px-1.5 mr-1 grow-0 font-bold text-xs bg-green-600 rounded-lg hover:cursor-pointer hover:bg-green-700 text-white">
|
{# <span class="results-count-badge py-1 px-1.5 mr-1 grow-0 font-bold text-xs bg-green-600 rounded-lg hover:cursor-pointer hover:bg-green-700 text-white">#}
|
||||||
<span class="results-count-number" id="movie_results_count">-</span> results
|
{# <span class="results-count-number" id="movie_results_count">-</span> results#}
|
||||||
</span>
|
{# </span>#}
|
||||||
|
|
||||||
<twig:Turbo:Frame id="meb_{{ results.media.imdbId }}" src="{{ path('api.library.search', {
|
<twig:Turbo:Frame id="meb_{{ results.media.imdbId }}" src="{{ path('api.library.search', {
|
||||||
title: results.media.title,
|
title: results.media.title,
|
||||||
@@ -133,18 +135,18 @@
|
|||||||
<twig:Filter results="{{ results }}" filter="{{ filter }}" />
|
<twig:Filter results="{{ results }}" filter="{{ filter }}" />
|
||||||
|
|
||||||
{% if "movies" == results.media.mediaType %}
|
{% if "movies" == results.media.mediaType %}
|
||||||
<movie-container class="results"
|
{# <movie-container class="results"#}
|
||||||
{{ stimulus_controller('movie_results', {title: results.media.title, tmdbId: results.media.tmdbId, imdbId: results.media.imdbId}) }}
|
{# {{ stimulus_controller('movie_results', {title: results.media.title, tmdbId: results.media.tmdbId, imdbId: results.media.imdbId}) }}#}
|
||||||
>
|
{# >#}
|
||||||
<twig:Turbo:Frame id="movie_results_frame" src="{{ path('app_torrentio_movies', {
|
{# <twig:Turbo:Frame id="movie_results_frame" src="{{ path('app_torrentio_movies', {#}
|
||||||
tmdbId: results.media.tmdbId,
|
{# tmdbId: results.media.tmdbId,#}
|
||||||
imdbId: results.media.imdbId,
|
{# imdbId: results.media.imdbId,#}
|
||||||
target: 'movie_results_frame',
|
{# target: 'movie_results_frame',#}
|
||||||
block: 'movie_results'
|
{# block: 'movie_results'#}
|
||||||
}) }}">
|
{# }) }}">#}
|
||||||
<twig:ux:icon name="codex:loader" height="20" width="20" data-loading-icon-target="icon" class="text-end" title="Loading download options for {{ results.media.title }}" />
|
{# <twig:ux:icon name="codex:loader" height="20" width="20" data-loading-icon-target="icon" class="text-end" title="Loading download options for {{ results.media.title }}" />#}
|
||||||
</twig:Turbo:Frame>
|
{# </twig:Turbo:Frame>#}
|
||||||
</movie-container>
|
{# </movie-container>#}
|
||||||
{% elseif "tvshows" == results.media.mediaType %}
|
{% elseif "tvshows" == results.media.mediaType %}
|
||||||
<twig:TvEpisodeList
|
<twig:TvEpisodeList
|
||||||
results="results"
|
results="results"
|
||||||
|
|||||||
Reference in New Issue
Block a user