wip: filters movie results, adds options to filter input
This commit is contained in:
@@ -52,6 +52,7 @@ export default class extends Controller {
|
|||||||
"resolution": option.querySelector('#resolution').textContent.trim(),
|
"resolution": option.querySelector('#resolution').textContent.trim(),
|
||||||
"codec": option.querySelector('#codec').textContent.trim(),
|
"codec": option.querySelector('#codec').textContent.trim(),
|
||||||
"provider": option.querySelector('#provider').textContent.trim(),
|
"provider": option.querySelector('#provider').textContent.trim(),
|
||||||
|
"quality": option.dataset['quality'],
|
||||||
"languages": JSON.parse(option.dataset['languages']),
|
"languages": JSON.parse(option.dataset['languages']),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export default class extends Controller {
|
|||||||
|
|
||||||
languages = []
|
languages = []
|
||||||
providers = []
|
providers = []
|
||||||
|
qualities = []
|
||||||
seasons = []
|
seasons = []
|
||||||
|
|
||||||
activeFilter = {
|
activeFilter = {
|
||||||
@@ -18,10 +19,11 @@ export default class extends Controller {
|
|||||||
"codec": "",
|
"codec": "",
|
||||||
"language": "",
|
"language": "",
|
||||||
"provider": "",
|
"provider": "",
|
||||||
|
"quality": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
static outlets = ['movie-results', 'tv-results', 'tv-episode-list']
|
static outlets = ['movie-results', 'tv-results', 'tv-episode-list']
|
||||||
static targets = ['resolution', 'codec', 'language', 'provider', 'season', 'selectAll', 'downloadSelected']
|
static targets = ['resolution', 'codec', 'language', 'provider', 'season', 'quality', 'selectAll', 'downloadSelected']
|
||||||
static values = {
|
static values = {
|
||||||
'media-type': String,
|
'media-type': String,
|
||||||
'episodes': Array,
|
'episodes': Array,
|
||||||
@@ -49,6 +51,7 @@ export default class extends Controller {
|
|||||||
outlet.options.forEach((option) => {
|
outlet.options.forEach((option) => {
|
||||||
this.addLanguages(option, option.dataset);
|
this.addLanguages(option, option.dataset);
|
||||||
this.addProviders(option, option.dataset);
|
this.addProviders(option, option.dataset);
|
||||||
|
this.addQualities(option, option.dataset);
|
||||||
})
|
})
|
||||||
await this.filter();
|
await this.filter();
|
||||||
}
|
}
|
||||||
@@ -105,6 +108,30 @@ export default class extends Controller {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addQualities(option, props) {
|
||||||
|
if (!this.qualities.includes(props['quality'])) {
|
||||||
|
this.qualities.push(props['quality']);
|
||||||
|
}
|
||||||
|
|
||||||
|
const preferred = this.qualityTarget.dataset.preferred;
|
||||||
|
if (preferred) {
|
||||||
|
this.qualityTarget.innerHTML = '<option value="'+preferred+'" selected>'+preferred+'</option>';
|
||||||
|
this.qualityTarget.innerHTML += '<option value="">n/a</option>';
|
||||||
|
} else {
|
||||||
|
this.qualityTarget.innerHTML = '<option value="">n/a</option>';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.qualityTarget.innerHTML += this.qualities.sort()
|
||||||
|
.map((quality) => {
|
||||||
|
const preferred = this.qualityTarget.dataset.preferred;
|
||||||
|
if (preferred === quality) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return '<option value="' + quality + '">' + quality + '</option>'
|
||||||
|
})
|
||||||
|
.join();
|
||||||
|
}
|
||||||
|
|
||||||
async filter() {
|
async filter() {
|
||||||
const currentSeason = this.activeFilter['season'];
|
const currentSeason = this.activeFilter['season'];
|
||||||
|
|
||||||
@@ -114,6 +141,7 @@ export default class extends Controller {
|
|||||||
"codec": this.codecTarget.value,
|
"codec": this.codecTarget.value,
|
||||||
"language": this.languageTarget.value,
|
"language": this.languageTarget.value,
|
||||||
"provider": this.providerTarget.value,
|
"provider": this.providerTarget.value,
|
||||||
|
"quality": this.qualityTarget.value,
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("movies" === this.mediaTypeValue) {
|
if ("movies" === this.mediaTypeValue) {
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
|
|
||||||
namespace App\Command;
|
namespace App\Command;
|
||||||
|
|
||||||
|
use App\User\Framework\Entity\UserPreference;
|
||||||
use App\User\Framework\Repository\PreferenceOptionRepository;
|
use App\User\Framework\Repository\PreferenceOptionRepository;
|
||||||
use App\User\Framework\Repository\PreferencesRepository;
|
use App\User\Framework\Repository\PreferencesRepository;
|
||||||
|
use App\User\Framework\Repository\UserRepository;
|
||||||
use Symfony\Component\Console\Attribute\AsCommand;
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
@@ -18,14 +20,17 @@ class SeedDatabaseCommand extends Command
|
|||||||
{
|
{
|
||||||
private PreferencesRepository $preferenceRepository;
|
private PreferencesRepository $preferenceRepository;
|
||||||
private PreferenceOptionRepository $preferenceOptionRepository;
|
private PreferenceOptionRepository $preferenceOptionRepository;
|
||||||
|
private UserRepository $userRepository;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
PreferencesRepository $preferenceRepository,
|
PreferencesRepository $preferenceRepository,
|
||||||
PreferenceOptionRepository $preferenceOptionRepository,
|
PreferenceOptionRepository $preferenceOptionRepository,
|
||||||
|
UserRepository $userRepository,
|
||||||
) {
|
) {
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
$this->preferenceRepository = $preferenceRepository;
|
$this->preferenceRepository = $preferenceRepository;
|
||||||
$this->preferenceOptionRepository = $preferenceOptionRepository;
|
$this->preferenceOptionRepository = $preferenceOptionRepository;
|
||||||
|
$this->userRepository = $userRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
@@ -34,6 +39,7 @@ class SeedDatabaseCommand extends Command
|
|||||||
|
|
||||||
$this->seedPreferences($io);
|
$this->seedPreferences($io);
|
||||||
$this->seedPreferenceOptions($io);
|
$this->seedPreferenceOptions($io);
|
||||||
|
$this->updateUserPreferences($io);
|
||||||
|
|
||||||
return Command::SUCCESS;
|
return Command::SUCCESS;
|
||||||
}
|
}
|
||||||
@@ -60,6 +66,26 @@ class SeedDatabaseCommand extends Command
|
|||||||
$this->preferenceRepository->getEntityManager()->flush();
|
$this->preferenceRepository->getEntityManager()->flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function updateUserPreferences(SymfonyStyle $io)
|
||||||
|
{
|
||||||
|
$io->info('[SeedDatabaseCommand] > Updating user preferences...');
|
||||||
|
$users = $this->userRepository->findAll();
|
||||||
|
$preferences = $this->preferenceRepository->findAll();
|
||||||
|
foreach ($users as $user) {
|
||||||
|
foreach ($preferences as $preference) {
|
||||||
|
if (false === $user->hasUserPreference($preference->getId())) {
|
||||||
|
$user->addUserPreference(
|
||||||
|
new UserPreference()
|
||||||
|
->setPreference($preference)
|
||||||
|
->setUser($user)
|
||||||
|
->setPreferenceValue(null)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->userRepository->getEntityManager()->flush();
|
||||||
|
}
|
||||||
|
|
||||||
private function getPreferences(): array
|
private function getPreferences(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@@ -91,6 +117,13 @@ class SeedDatabaseCommand extends Command
|
|||||||
'enabled' => true,
|
'enabled' => true,
|
||||||
'type' => 'media',
|
'type' => 'media',
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'id' => 'quality',
|
||||||
|
'name' => 'Quality',
|
||||||
|
'description' => null,
|
||||||
|
'enabled' => true,
|
||||||
|
'type' => 'media',
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'id' => 'movie_folder',
|
'id' => 'movie_folder',
|
||||||
'name' => 'Create new folder for Movies',
|
'name' => 'Create new folder for Movies',
|
||||||
|
|||||||
@@ -20,9 +20,6 @@ use Symfony\Contracts\Cache\ItemInterface;
|
|||||||
final class ApiController extends AbstractController
|
final class ApiController extends AbstractController
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly GetMovieOptionsHandler $getMovieOptionsHandler,
|
|
||||||
private readonly GetTvShowOptionsHandler $getTvShowOptionsHandler,
|
|
||||||
private readonly Broadcaster $broadcaster,
|
|
||||||
private readonly Torrentio $torrentio,
|
private readonly Torrentio $torrentio,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@@ -38,79 +35,4 @@ final class ApiController extends AbstractController
|
|||||||
$this->torrentio->search($imdbId, 'movies', false),
|
$this->torrentio->search($imdbId, 'movies', false),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/torrentio/movies/{tmdbId}/{imdbId}', name: 'app_torrentio_movies')]
|
|
||||||
public function movieOptions(GetMovieOptionsInput $input, CacheInterface $cache): Response
|
|
||||||
{
|
|
||||||
$cacheId = sprintf(
|
|
||||||
"page.torrentio.movies.%s.%s",
|
|
||||||
$input->tmdbId,
|
|
||||||
$input->imdbId
|
|
||||||
);
|
|
||||||
|
|
||||||
return $cache->get($cacheId, function (ItemInterface $item) use ($input) {
|
|
||||||
$item->expiresAt(Carbon::now()->addHour()->setMinute(0)->setSecond(0));
|
|
||||||
$results = $this->getMovieOptionsHandler->handle($input->toCommand());
|
|
||||||
return $this->render('torrentio/movies.html.twig', [
|
|
||||||
'results' => $results,
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Route('/torrentio/tvshows/{tmdbId}/{imdbId}/{season?}/{episode?}', name: 'app_torrentio_tvshows')]
|
|
||||||
public function tvShowOptions(GetTvShowOptionsInput $input, CacheInterface $cache): Response
|
|
||||||
{
|
|
||||||
$cacheId = sprintf(
|
|
||||||
"page.torrentio.tvshows.%s.%s.%s.%s",
|
|
||||||
$input->tmdbId,
|
|
||||||
$input->imdbId,
|
|
||||||
$input->season,
|
|
||||||
$input->episode,
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// return $cache->get($cacheId, function (ItemInterface $item) use ($input) {
|
|
||||||
// $item->expiresAt(Carbon::now()->addHour()->setMinute(0)->setSecond(0));
|
|
||||||
$results = $this->getTvShowOptionsHandler->handle($input->toCommand());
|
|
||||||
return $this->render('torrentio/tvshows.html.twig', [
|
|
||||||
'results' => $results,
|
|
||||||
]);
|
|
||||||
// });
|
|
||||||
} catch (TorrentioRateLimitException $exception) {
|
|
||||||
$this->broadcaster->alert('Warning', 'Torrentio has rate limited your requests. Please wait a few minutes before trying again.', 'warning');
|
|
||||||
return $this->render('bare.html.twig',
|
|
||||||
[],
|
|
||||||
new Response('Too many requests',
|
|
||||||
Response::HTTP_TOO_MANY_REQUESTS,
|
|
||||||
['Retry-After' => 4000]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Route('/torrentio/tvshows/clear/{tmdbId}/{imdbId}/{season?}/{episode?}', name: 'app_clear_torrentio_tvshows')]
|
|
||||||
public function clearTvShowOptions(GetTvShowOptionsInput $input, CacheInterface $cache, Request $request): Response
|
|
||||||
{
|
|
||||||
$cacheId = sprintf(
|
|
||||||
"page.torrentio.tvshows.%s.%s.%s.%s",
|
|
||||||
$input->tmdbId,
|
|
||||||
$input->imdbId,
|
|
||||||
$input->season,
|
|
||||||
$input->episode,
|
|
||||||
);
|
|
||||||
$cache->delete($cacheId);
|
|
||||||
|
|
||||||
$this->broadcaster->alert(
|
|
||||||
title: 'Success',
|
|
||||||
message: 'Torrentio cache Cleared.'
|
|
||||||
);
|
|
||||||
|
|
||||||
return $cache->get($cacheId, function (ItemInterface $item) use ($input) {
|
|
||||||
$item->expiresAt(Carbon::now()->addHour()->setMinute(0)->setSecond(0));
|
|
||||||
$results = $this->getTvShowOptionsHandler->handle($input->toCommand());
|
|
||||||
return $this->render('torrentio/tvshows.html.twig', [
|
|
||||||
'results' => $results,
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class ResultFactory
|
|||||||
string $bingeGroup = "-"
|
string $bingeGroup = "-"
|
||||||
) {
|
) {
|
||||||
$ptn = (object) (new PTN())->parse($title);
|
$ptn = (object) (new PTN())->parse($title);
|
||||||
|
// dump($ptn);
|
||||||
return new TorrentioResult(
|
return new TorrentioResult(
|
||||||
self::trimTitle($title),
|
self::trimTitle($title),
|
||||||
urldecode($url),
|
urldecode($url),
|
||||||
@@ -34,6 +35,7 @@ class ResultFactory
|
|||||||
$bingeGroup,
|
$bingeGroup,
|
||||||
$ptn->resolution ?? "-",
|
$ptn->resolution ?? "-",
|
||||||
self::setCodec($ptn->codec ?? "-"),
|
self::setCodec($ptn->codec ?? "-"),
|
||||||
|
$ptn->quality ?? "-",
|
||||||
$ptn,
|
$ptn,
|
||||||
substr(base64_encode($url), strlen($url) - 10),
|
substr(base64_encode($url), strlen($url) - 10),
|
||||||
$ptn->episode ?? "-",
|
$ptn->episode ?? "-",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ class TorrentioResult
|
|||||||
public ?string $bingeGroup = "-",
|
public ?string $bingeGroup = "-",
|
||||||
public ?string $resolution = "-",
|
public ?string $resolution = "-",
|
||||||
public ?string $codec = "-",
|
public ?string $codec = "-",
|
||||||
|
public ?string $quality = "-",
|
||||||
public object|array $ptn = [],
|
public object|array $ptn = [],
|
||||||
public ?string $key = "-",
|
public ?string $key = "-",
|
||||||
public ?string $episodeNumber = "-",
|
public ?string $episodeNumber = "-",
|
||||||
|
|||||||
@@ -55,6 +55,17 @@
|
|||||||
>
|
>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
<label for="quality">
|
||||||
|
Quality
|
||||||
|
<select id="quality"
|
||||||
|
data-result-filter-target="quality"
|
||||||
|
class="px-1 py-0.5 bg-stone-100 text-gray-800 rounded-md"
|
||||||
|
{% if this.userPreferences['quality'] != null %}
|
||||||
|
data-preferred="{{ this.userPreferences['quality'] }}"
|
||||||
|
{% endif %}
|
||||||
|
>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
{% if results.media.mediaType == "tvshows" %}
|
{% if results.media.mediaType == "tvshows" %}
|
||||||
<label for="season">
|
<label for="season">
|
||||||
Season
|
Season
|
||||||
|
|||||||
@@ -8,6 +8,10 @@
|
|||||||
class="px-4 py-4 leading-[20px] font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
class="px-4 py-4 leading-[20px] font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||||
Size
|
Size
|
||||||
</th>
|
</th>
|
||||||
|
<th scope="col"
|
||||||
|
class="px-4 py-4 leading-[20px] font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||||
|
Quality
|
||||||
|
</th>
|
||||||
<th scope="col"
|
<th scope="col"
|
||||||
class="px-4 py-4 leading-[20px] font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
class="px-4 py-4 leading-[20px] font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||||
Resolution
|
Resolution
|
||||||
@@ -37,10 +41,13 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody class="flex-1 sm:flex-none">
|
<tbody class="flex-1 sm:flex-none">
|
||||||
{% for result in results.results %}
|
{% for result in results.results %}
|
||||||
<tr class="bg-white dark:bg-slate-700 flex flex-col flex-no wrap r-tablerow border-b border-gray-500" data-provider="{{ result.provider }}" data-languages="{{ result.languages|json_encode }}" {% if "tvshows" == results.media.mediaType %} data-season="{{ results.season }}"{% endif %}>
|
<tr class="bg-white dark:bg-slate-700 flex flex-col flex-no wrap r-tablerow border-b border-gray-500" data-provider="{{ result.provider }}" data-quality="{{ result.quality }}" data-languages="{{ result.languages|json_encode }}" {% if "tvshows" == results.media.mediaType %} data-season="{{ results.season }}"{% endif %}>
|
||||||
<td id="size" class="px-4 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50">
|
<td id="size" class="px-4 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50">
|
||||||
{{ result.size }}
|
{{ result.size }}
|
||||||
</td>
|
</td>
|
||||||
|
<td id="quality" class="px-4 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50">
|
||||||
|
{{ result.quality }}
|
||||||
|
</td>
|
||||||
<td id="resolution" class="px-4 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50">
|
<td id="resolution" class="px-4 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50">
|
||||||
{{ result.resolution }}
|
{{ result.resolution }}
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
Reference in New Issue
Block a user