From 740bef97b12c73ccaf5664ef1a4987bdaaf5ac0b Mon Sep 17 00:00:00 2001 From: Brock H Caldwell Date: Sun, 22 Mar 2026 23:31:03 -0500 Subject: [PATCH] WIP: workig download button on movies and episodes --- assets/bootstrap.js | 2 + assets/components/download-media-buton.js | 58 +++++++++++++++++++ .../Action/Handler/DownloadMediaHandler.php | 36 ++++++++---- .../Action/Input/DownloadMediaInput.php | 13 +++-- .../Framework/Controller/ApiController.php | 7 ++- .../Repository/DownloadRepository.php | 2 + templates/components/Filter.html.twig | 13 ++++- templates/components/TvEpisodeList.html.twig | 20 +++++-- templates/search/result.html.twig | 38 ++++++------ 9 files changed, 150 insertions(+), 39 deletions(-) create mode 100644 assets/components/download-media-buton.js diff --git a/assets/bootstrap.js b/assets/bootstrap.js index 5ca4f1a..c427025 100644 --- a/assets/bootstrap.js +++ b/assets/bootstrap.js @@ -6,6 +6,7 @@ import DownloadListRow from './components/download-list-row.js'; import MonitorListRow from './components/monitor-list-row.js'; import MovieContainer from "./components/movie-container.js"; import StatusCheckerSpan from "./components/status-checker-span.js"; +import DownloadMediaButton from "./components/download-media-buton.js"; import { startStimulusApp } from '@symfony/stimulus-bundle'; 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('monitor-list-row', MonitorListRow, {extends: 'tr'}); customElements.define('status-checker-span', StatusCheckerSpan, {extends: 'span'}); +customElements.define('download-media-button', DownloadMediaButton); diff --git a/assets/components/download-media-buton.js b/assets/components/download-media-buton.js new file mode 100644 index 0000000..47667b4 --- /dev/null +++ b/assets/components/download-media-buton.js @@ -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) + }) + } +} diff --git a/src/Download/Action/Handler/DownloadMediaHandler.php b/src/Download/Action/Handler/DownloadMediaHandler.php index c493699..6c8b891 100644 --- a/src/Download/Action/Handler/DownloadMediaHandler.php +++ b/src/Download/Action/Handler/DownloadMediaHandler.php @@ -82,6 +82,19 @@ readonly class DownloadMediaHandler implements HandlerInterface $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->setTitle($media->media->title); $download->setFileName( @@ -120,7 +133,6 @@ readonly class DownloadMediaHandler implements HandlerInterface if ($download->getStatus() !== 'Paused') { $this->downloadRepository->updateStatus($download->getId(), 'Complete'); } - } catch (\Throwable $exception) { throw new UnrecoverableMessageHandlingException($exception->getMessage(), 500); } @@ -156,38 +168,42 @@ readonly class DownloadMediaHandler implements HandlerInterface { $fileType = $option->ptn->container; return (match ($mediaType) { - MediaType::Movie => function () use ($media, $fileType) { - $template = "%s (%s) [imdbid-%s].%s"; - return sprintf( + MediaType::Movie => function () use ($media, $fileType, $option) { + $template = "%s (%s) [imdbid-%s]"; + $filename = sprintf( $template, $media->media->title, $media->media->year, $media->media->imdbId, $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"; $episodeId = EpisodeId::fromSeasonEpisodeNumbers( $media->season, $media->episode, ); - return sprintf( + $filename = sprintf( $template, $media->media->title, $episodeId, $fileType ); + if ($option->resolution !== null && $option->resolution !== '-') { + $filename .= ' - [' . $option->resolution . ']'; + } + return $filename . '.' . $fileType; } })(); } public function validateDownloadUrl(string $downloadUrl) { - $badFileSizes = [ - 2119075, // copyright infringement - ]; - $badFileLocations = [ '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.' diff --git a/src/Download/Action/Input/DownloadMediaInput.php b/src/Download/Action/Input/DownloadMediaInput.php index e2d25ec..ddb15a1 100644 --- a/src/Download/Action/Input/DownloadMediaInput.php +++ b/src/Download/Action/Input/DownloadMediaInput.php @@ -5,6 +5,7 @@ namespace App\Download\Action\Input; use App\Download\Action\Command\DownloadMediaCommand; use OneToMany\RichBundle\Attribute\PropertyIgnored; use OneToMany\RichBundle\Attribute\SourceRequest; +use OneToMany\RichBundle\Attribute\SourceRoute; use OneToMany\RichBundle\Attribute\SourceSecurity; use OneToMany\RichBundle\Contract\CommandInterface; use OneToMany\RichBundle\Contract\InputInterface; @@ -17,15 +18,19 @@ class DownloadMediaInput implements InputInterface public function __construct( #[SourceRequest('imdbId')] + #[SourceRoute('imdbId')] public string $imdbId, #[SourceRequest('mediaType')] + #[SourceRoute('mediaType')] public string $mediaType, - #[SourceRequest('season', nullify: true)] + #[SourceRequest('season')] + #[SourceRoute('season')] public int|string|null $season = null, - #[SourceRequest('episode', nullify: true)] + #[SourceRequest('episode')] + #[SourceRoute('episode')] public int|string|null $episode = null, #[SourceRequest('url', nullify: true)] @@ -64,8 +69,8 @@ class DownloadMediaInput implements InputInterface return new DownloadMediaCommand( $this->imdbId, $this->mediaType, - $this->season, - $this->episode, + (int) $this->season, + (int) $this->episode, $this->url, $this->filter, $this->downloadId, diff --git a/src/Download/Framework/Controller/ApiController.php b/src/Download/Framework/Controller/ApiController.php index 71f2aec..896bd4c 100644 --- a/src/Download/Framework/Controller/ApiController.php +++ b/src/Download/Framework/Controller/ApiController.php @@ -16,6 +16,8 @@ use App\Download\DownloadEvents; use App\Download\Framework\Entity\Download; use App\Download\Framework\Repository\DownloadRepository; 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\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; @@ -30,15 +32,18 @@ class ApiController extends AbstractController 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( DownloadMediaInput $input, DownloadMediaHandler $handler, + GetMediaInfoHandler $getMediaInfoHandler, ): Response { + $media = $getMediaInfoHandler->handle(new GetMediaInfoCommand($input->imdbId, $input->mediaType)); $download = $this->downloadRepository->insertNew( $this->getUser(), $input->imdbId, $input->mediaType, + $media->media->title, $input->season, $input->episode, ); diff --git a/src/Download/Framework/Repository/DownloadRepository.php b/src/Download/Framework/Repository/DownloadRepository.php index 7b87c85..16440b1 100644 --- a/src/Download/Framework/Repository/DownloadRepository.php +++ b/src/Download/Framework/Repository/DownloadRepository.php @@ -61,6 +61,7 @@ class DownloadRepository extends ServiceEntityRepository UserInterface $user, string $imdbId, string $mediaType, + string $title, int|null $season = null, int|null $episode = null, string $status = 'New' @@ -69,6 +70,7 @@ class DownloadRepository extends ServiceEntityRepository $download = (new Download()) ->setUser($user) ->setImdbId($imdbId) + ->setTitle($title) ->setMediaType($mediaType) ->setProgress(0) ->setStatus($status); diff --git a/templates/components/Filter.html.twig b/templates/components/Filter.html.twig index 6b11831..d6402d3 100644 --- a/templates/components/Filter.html.twig +++ b/templates/components/Filter.html.twig @@ -9,7 +9,7 @@ {% set preferences_form = form %} {{ form_start(preferences_form) }} -

Apply a filter to your results

+

What type of file do you want?

{{ form_row(preferences_form.resolution) }} {{ form_row(preferences_form.codec) }} @@ -37,6 +37,17 @@
{% endif %} + {% if results.media.mediaType == "movies" %} +
+ + download + +
+ {% endif %} {{ form_end(preferences_form) }}
diff --git a/templates/components/TvEpisodeList.html.twig b/templates/components/TvEpisodeList.html.twig index 9ee1173..d897db6 100644 --- a/templates/components/TvEpisodeList.html.twig +++ b/templates/components/TvEpisodeList.html.twig @@ -53,11 +53,21 @@
-
- -
+ + + + + +