From d60fae24d17578d5c93e1b919cc8a73892b6bdb4 Mon Sep 17 00:00:00 2001 From: Brock H Caldwell Date: Mon, 21 Apr 2025 22:39:22 -0500 Subject: [PATCH] feat: tv episode results --- assets/controllers/tv_results_controller.js | 26 +++++++ src/Controller/TorrentioController.php | 15 +++- src/Tmdb/Tmdb.php | 32 +++++++- .../Command/GetTvShowOptionsCommand.php | 15 ++++ .../Handler/GetTvShowOptionsHandler.php | 32 ++++++++ .../Action/Input/GetTvShowOptionsInput.php | 32 ++++++++ .../Action/Result/GetTvShowOptionsResult.php | 16 ++++ templates/search/result.html.twig | 16 ++++ templates/torrentio/tvshows.html.twig | 76 ++++++++++++++++++- 9 files changed, 253 insertions(+), 7 deletions(-) create mode 100644 assets/controllers/tv_results_controller.js create mode 100644 src/Torrentio/Action/Command/GetTvShowOptionsCommand.php create mode 100644 src/Torrentio/Action/Handler/GetTvShowOptionsHandler.php create mode 100644 src/Torrentio/Action/Input/GetTvShowOptionsInput.php create mode 100644 src/Torrentio/Action/Result/GetTvShowOptionsResult.php diff --git a/assets/controllers/tv_results_controller.js b/assets/controllers/tv_results_controller.js new file mode 100644 index 0000000..e39a604 --- /dev/null +++ b/assets/controllers/tv_results_controller.js @@ -0,0 +1,26 @@ +import { Controller } from '@hotwired/stimulus'; + +/* +* The following line makes this controller "lazy": it won't be downloaded until needed +* See https://github.com/symfony/stimulus-bridge#lazy-controllers +*/ +/* stimulusFetch: 'lazy' */ +export default class extends Controller { + static values = { + tmdbId: String, + imdbId: String, + season: String, + episode: String, + active: Boolean, + }; + + connect() { + if (true === this.activeValue) { + fetch(`/torrentio/tvshows/${this.tmdbIdValue}/${this.imdbIdValue}/${this.seasonValue}/${this.episodeValue}`) + .then(res => res.text()) + .then(response => { + this.element.innerHTML = response; + }); + } + } +} diff --git a/src/Controller/TorrentioController.php b/src/Controller/TorrentioController.php index 65d8883..52c4d9b 100644 --- a/src/Controller/TorrentioController.php +++ b/src/Controller/TorrentioController.php @@ -3,7 +3,9 @@ namespace App\Controller; use App\Torrentio\Action\Handler\GetMovieOptionsHandler; +use App\Torrentio\Action\Handler\GetTvShowOptionsHandler; use App\Torrentio\Action\Input\GetMovieOptionsInput; +use App\Torrentio\Action\Input\GetTvShowOptionsInput; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; @@ -12,9 +14,10 @@ final class TorrentioController extends AbstractController { public function __construct( private readonly GetMovieOptionsHandler $getMovieOptionsHandler, + private readonly GetTvShowOptionsHandler $getTvShowOptionsHandler, ) {} - #[Route('/torrentio/movies/{imdbId}', name: 'app_torrentio')] + #[Route('/torrentio/movies/{imdbId}', name: 'app_torrentio_movies')] public function movieOptions(GetMovieOptionsInput $input): Response { $results = $this->getMovieOptionsHandler->handle($input->toCommand()); @@ -23,4 +26,14 @@ final class TorrentioController extends AbstractController 'results' => $results, ]); } + + #[Route('/torrentio/tvshows/{tmdbId}/{imdbId}/{season?}/{episode?}', name: 'app_torrentio_tvshows')] + public function tvShowOptions(GetTvShowOptionsInput $input): Response + { + $results = $this->getTvShowOptionsHandler->handle($input->toCommand()); + + return $this->render('torrentio/tvshows.html.twig', [ + 'results' => $results, + ]); + } } diff --git a/src/Tmdb/Tmdb.php b/src/Tmdb/Tmdb.php index a3e948c..e6232a6 100644 --- a/src/Tmdb/Tmdb.php +++ b/src/Tmdb/Tmdb.php @@ -27,6 +27,7 @@ use Tmdb\Model\Search\SearchQuery\MovieSearchQuery; use Tmdb\Model\Tv; use Tmdb\Repository\MovieRepository; use Tmdb\Repository\SearchRepository; +use Tmdb\Repository\TvEpisodeRepository; use Tmdb\Repository\TvRepository; use Tmdb\Repository\TvSeasonRepository; use Tmdb\Token\Api\ApiToken; @@ -139,7 +140,14 @@ class Tmdb $client = new TvRepository($this->client); $details = $client->getApi()->getTvshow($id, ['append_to_response' => 'external_ids,seasons']); $details = $this->getEpisodesFromSeries($details); - return $this->parseResult($details, "tv"); + return $this->parseResult($details, "tvshow"); + } + + public function episodeDetails(string $id, string $season, string $episode) + { + $client = new TvEpisodeRepository($this->client); + $result = $client->getApi()->getEpisode($id, $season, $episode, ['append_to_response' => 'external_ids']); + return $this->parseResult($result, "episode"); } public function getEpisodesFromSeries(array $series) @@ -192,6 +200,19 @@ class Tmdb ); } + function parseEpisode(array $data, string $posterBasePath): TmdbResult { + return new TmdbResult( + imdbId: $data['external_ids']['imdb_id'], + tmdbId: $data['id'], + title: $data['name'], + poster: $posterBasePath . $data['still_path'], + description: $data['overview'], + year: (new \DateTime($data['air_date']))->format('Y'), + mediaType: "tvshows", + episodes: null, + ); + } + function parseMovie(array $data, string $posterBasePath): TmdbResult { return new TmdbResult( imdbId: $data['external_ids']['imdb_id'], @@ -204,8 +225,13 @@ class Tmdb ); } - $posterBasePath = self::POSTER_IMG_PATH; - $result = ("movie" === $mediaType) ? parseMovie($data, $posterBasePath) : parseTvShow($data, $posterBasePath); + if ($mediaType === 'movie') { + $result = parseMovie($data, self::POSTER_IMG_PATH); + } elseif ($mediaType === 'tvshow') { + $result = parseTvShow($data, self::POSTER_IMG_PATH); + } elseif ($mediaType === 'episode') { + $result = parseEpisode($data, self::POSTER_IMG_PATH); + } return $result; } diff --git a/src/Torrentio/Action/Command/GetTvShowOptionsCommand.php b/src/Torrentio/Action/Command/GetTvShowOptionsCommand.php new file mode 100644 index 0000000..4b7724c --- /dev/null +++ b/src/Torrentio/Action/Command/GetTvShowOptionsCommand.php @@ -0,0 +1,15 @@ +tmdb->episodeDetails($command->tmdbId, $command->season, $command->episode), + season: $command->season, + episode: $command->episode, + results: $this->torrentio->fetchEpisodeResults( + $command->imdbId, + $command->season, + $command->episode, + ) + ); + } +} diff --git a/src/Torrentio/Action/Input/GetTvShowOptionsInput.php b/src/Torrentio/Action/Input/GetTvShowOptionsInput.php new file mode 100644 index 0000000..ec39998 --- /dev/null +++ b/src/Torrentio/Action/Input/GetTvShowOptionsInput.php @@ -0,0 +1,32 @@ +tmdbId, + $this->imdbId, + $this->season, + $this->episode + ); + } +} diff --git a/src/Torrentio/Action/Result/GetTvShowOptionsResult.php b/src/Torrentio/Action/Result/GetTvShowOptionsResult.php new file mode 100644 index 0000000..d04bb81 --- /dev/null +++ b/src/Torrentio/Action/Result/GetTvShowOptionsResult.php @@ -0,0 +1,16 @@ + + {% if "movies" == results.media.mediaType %}
+ {% elseif "tvshows" == results.media.mediaType %} + {% for season, episodes in results.media.episodes %} + {% set active = (season == '1') ? true : false %} + {% for episode in episodes %} +
+
+ {% endfor %} + {% endfor %} + {% endif %} diff --git a/templates/torrentio/tvshows.html.twig b/templates/torrentio/tvshows.html.twig index 5417f46..6e45612 100644 --- a/templates/torrentio/tvshows.html.twig +++ b/templates/torrentio/tvshows.html.twig @@ -1,3 +1,73 @@ -
-

Episodes

-
\ No newline at end of file +
+

Episode {{ results.episode }}

+
+ +
+
{{ results.media.title }}
+

{{ results.media.description }}

+
+
+ + + + + + + + + + + + + + {% for result in results.results %} + + + + + + + + + + {% endfor %} + +
+ Size + + Resolution + + Codec + + Seeders + + Provider + + Language + +
+ {{ result.size }} + + {{ result.resolution }} + + {{ result.codec }} + + {{ result.seeders }} + + {{ result.provider }} + + {{ result.languageFlags }} + + + Download + + +
+