fix-feat: ajax download call
This commit is contained in:
37
assets/controllers/download_button_controller.js
Normal file
37
assets/controllers/download_button_controller.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
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 = {
|
||||||
|
url: String,
|
||||||
|
title: String,
|
||||||
|
filename: String,
|
||||||
|
mediaType: String,
|
||||||
|
imdbId: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
download() {
|
||||||
|
fetch('/download', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
url: this.urlValue,
|
||||||
|
title: this.titleValue,
|
||||||
|
filename: this.filenameValue,
|
||||||
|
mediaType: this.mediaTypeValue,
|
||||||
|
imdbId: this.imdbIdValue
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(json => {
|
||||||
|
console.log(json)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import { Controller } from '@hotwired/stimulus';
|
|||||||
/* stimulusFetch: 'lazy' */
|
/* stimulusFetch: 'lazy' */
|
||||||
export default class extends Controller {
|
export default class extends Controller {
|
||||||
static values = {
|
static values = {
|
||||||
|
tmdbId: String,
|
||||||
imdbId: String
|
imdbId: String
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ export default class extends Controller {
|
|||||||
|
|
||||||
async setOptions() {
|
async setOptions() {
|
||||||
if (this.options.length === 0) {
|
if (this.options.length === 0) {
|
||||||
await fetch(`/torrentio/movies/${this.imdbIdValue}`)
|
await fetch(`/torrentio/movies/${this.tmdbIdValue}/${this.imdbIdValue}`)
|
||||||
.then(res => res.text())
|
.then(res => res.text())
|
||||||
.then(response => {
|
.then(response => {
|
||||||
this.element.innerHTML = response;
|
this.element.innerHTML = response;
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"php-tmdb/api": "^4.1",
|
"php-tmdb/api": "^4.1",
|
||||||
"symfony/asset": "7.2.*",
|
"symfony/asset": "7.2.*",
|
||||||
"symfony/console": "7.2.*",
|
"symfony/console": "7.2.*",
|
||||||
|
"symfony/doctrine-messenger": "7.2.*",
|
||||||
"symfony/dotenv": "7.2.*",
|
"symfony/dotenv": "7.2.*",
|
||||||
"symfony/flex": "^2",
|
"symfony/flex": "^2",
|
||||||
"symfony/form": "7.2.*",
|
"symfony/form": "7.2.*",
|
||||||
|
|||||||
74
composer.lock
generated
74
composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "eeaaf3d88479bdcd00dcb637222408f4",
|
"content-hash": "0448ecb537f5d169d81a56ba2b3c2cc6",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "1tomany/data-uri",
|
"name": "1tomany/data-uri",
|
||||||
@@ -3469,6 +3469,78 @@
|
|||||||
],
|
],
|
||||||
"time": "2025-03-25T15:54:33+00:00"
|
"time": "2025-03-25T15:54:33+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/doctrine-messenger",
|
||||||
|
"version": "v7.2.5",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/doctrine-messenger.git",
|
||||||
|
"reference": "c353e6ee6b41748d8ea6faa2d0b84ac501e3ec0c"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/doctrine-messenger/zipball/c353e6ee6b41748d8ea6faa2d0b84ac501e3ec0c",
|
||||||
|
"reference": "c353e6ee6b41748d8ea6faa2d0b84ac501e3ec0c",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"doctrine/dbal": "^3.6|^4",
|
||||||
|
"php": ">=8.2",
|
||||||
|
"symfony/messenger": "^6.4|^7.0",
|
||||||
|
"symfony/service-contracts": "^2.5|^3"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"doctrine/persistence": "<1.3"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"doctrine/persistence": "^1.3|^2|^3",
|
||||||
|
"symfony/property-access": "^6.4|^7.0",
|
||||||
|
"symfony/serializer": "^6.4|^7.0"
|
||||||
|
},
|
||||||
|
"type": "symfony-messenger-bridge",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Component\\Messenger\\Bridge\\Doctrine\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Symfony Doctrine Messenger Bridge",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/doctrine-messenger/tree/v7.2.5"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-03-25T15:54:33+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/dotenv",
|
"name": "symfony/dotenv",
|
||||||
"version": "v7.2.0",
|
"version": "v7.2.0",
|
||||||
|
|||||||
@@ -5,14 +5,7 @@ framework:
|
|||||||
|
|
||||||
transports:
|
transports:
|
||||||
# https://symfony.com/doc/current/messenger.html#transport-configuration
|
# https://symfony.com/doc/current/messenger.html#transport-configuration
|
||||||
async:
|
async: '%env(MESSENGER_TRANSPORT_DSN)%'
|
||||||
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
|
|
||||||
options:
|
|
||||||
use_notify: true
|
|
||||||
check_delayed_interval: 60000
|
|
||||||
retry_strategy:
|
|
||||||
max_retries: 1
|
|
||||||
multiplier: 1
|
|
||||||
failed: 'doctrine://default?queue_name=failed'
|
failed: 'doctrine://default?queue_name=failed'
|
||||||
|
|
||||||
default_bus: messenger.bus.default
|
default_bus: messenger.bus.default
|
||||||
|
|||||||
@@ -18,7 +18,11 @@ class DownloadController extends AbstractController
|
|||||||
public function download(
|
public function download(
|
||||||
DownloadMediaInput $input,
|
DownloadMediaInput $input,
|
||||||
): Response {
|
): Response {
|
||||||
$this->bus->dispatch($input->toCommand());
|
try {
|
||||||
|
$this->bus->dispatch($input->toCommand());
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
return $this->json(['error' => $exception->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->json(['status' => 200, 'message' => 'Added to Queue']);
|
return $this->json(['status' => 200, 'message' => 'Added to Queue']);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ final class TorrentioController extends AbstractController
|
|||||||
private readonly GetTvShowOptionsHandler $getTvShowOptionsHandler,
|
private readonly GetTvShowOptionsHandler $getTvShowOptionsHandler,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
#[Route('/torrentio/movies/{imdbId}', name: 'app_torrentio_movies')]
|
#[Route('/torrentio/movies/{tmdbId}/{imdbId}', name: 'app_torrentio_movies')]
|
||||||
public function movieOptions(GetMovieOptionsInput $input): Response
|
public function movieOptions(GetMovieOptionsInput $input): Response
|
||||||
{
|
{
|
||||||
$results = $this->getMovieOptionsHandler->handle($input->toCommand());
|
$results = $this->getMovieOptionsHandler->handle($input->toCommand());
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use OneToMany\RichBundle\Contract\CommandInterface;
|
|||||||
class GetMovieOptionsCommand implements CommandInterface
|
class GetMovieOptionsCommand implements CommandInterface
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
public string $tmdbId,
|
||||||
public string $imdbId,
|
public string $imdbId,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Torrentio\Action\Handler;
|
namespace App\Torrentio\Action\Handler;
|
||||||
|
|
||||||
|
use App\Tmdb\Tmdb;
|
||||||
use App\Torrentio\Action\Result\GetMovieOptionsResult;
|
use App\Torrentio\Action\Result\GetMovieOptionsResult;
|
||||||
use App\Torrentio\Client\Torrentio;
|
use App\Torrentio\Client\Torrentio;
|
||||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||||
@@ -11,13 +12,15 @@ use OneToMany\RichBundle\Contract\ResultInterface;
|
|||||||
class GetMovieOptionsHandler implements HandlerInterface
|
class GetMovieOptionsHandler implements HandlerInterface
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
private readonly Tmdb $tmdb,
|
||||||
private readonly Torrentio $torrentio,
|
private readonly Torrentio $torrentio,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function handle(CommandInterface $command): ResultInterface
|
public function handle(CommandInterface $command): ResultInterface
|
||||||
{
|
{
|
||||||
return new GetMovieOptionsResult(
|
return new GetMovieOptionsResult(
|
||||||
results: $this->torrentio->search($command->imdbId, 'movies')
|
media: $this->tmdb->mediaDetails($command->tmdbId, 'movies'),
|
||||||
|
results: $this->torrentio->search($command->imdbId, 'movies'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,12 +10,15 @@ use OneToMany\RichBundle\Contract\InputInterface;
|
|||||||
class GetMovieOptionsInput implements InputInterface
|
class GetMovieOptionsInput implements InputInterface
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
#[SourceRoute('tmdbId')]
|
||||||
|
public string $tmdbId,
|
||||||
|
|
||||||
#[SourceRoute('imdbId')]
|
#[SourceRoute('imdbId')]
|
||||||
public string $imdbId,
|
public string $imdbId,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function toCommand(): CommandInterface
|
public function toCommand(): CommandInterface
|
||||||
{
|
{
|
||||||
return new GetMovieOptionsCommand($this->imdbId);
|
return new GetMovieOptionsCommand($this->tmdbId, $this->imdbId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,13 @@
|
|||||||
|
|
||||||
namespace App\Torrentio\Action\Result;
|
namespace App\Torrentio\Action\Result;
|
||||||
|
|
||||||
|
use App\Tmdb\TmdbResult;
|
||||||
use OneToMany\RichBundle\Contract\ResultInterface;
|
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||||
|
|
||||||
class GetMovieOptionsResult implements ResultInterface
|
class GetMovieOptionsResult implements ResultInterface
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
public TmdbResult $media,
|
||||||
public array $results
|
public array $results
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ class ResultFactory
|
|||||||
$ptn = (object) (new PTN())->parse($title);
|
$ptn = (object) (new PTN())->parse($title);
|
||||||
return new TorrentioResult(
|
return new TorrentioResult(
|
||||||
self::trimTitle($title),
|
self::trimTitle($title),
|
||||||
$url,
|
urldecode($url),
|
||||||
|
self::setFilename($url),
|
||||||
self::setSize($title),
|
self::setSize($title),
|
||||||
self::setSeeders($title),
|
self::setSeeders($title),
|
||||||
self::setProvider($title),
|
self::setProvider($title),
|
||||||
@@ -33,6 +34,12 @@ class ResultFactory
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function setFilename(string $url)
|
||||||
|
{
|
||||||
|
$file = explode("/", urldecode($url));
|
||||||
|
return end($file);
|
||||||
|
}
|
||||||
|
|
||||||
public static function setSize(string $title): string
|
public static function setSize(string $title): string
|
||||||
{
|
{
|
||||||
$sizeMatch = [];
|
$sizeMatch = [];
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ class TorrentioResult
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
public ?string $title = "-",
|
public ?string $title = "-",
|
||||||
public ?string $url = "-",
|
public ?string $url = "-",
|
||||||
|
public ?string $filename = "-",
|
||||||
public ?string $size = "-",
|
public ?string $size = "-",
|
||||||
public ?string $seeders = "-",
|
public ?string $seeders = "-",
|
||||||
public ?string $provider = "-",
|
public ?string $provider = "-",
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
{{ include('search/partial/filter.html.twig') }}
|
{{ include('search/partial/filter.html.twig') }}
|
||||||
|
|
||||||
{% if "movies" == results.media.mediaType %}
|
{% if "movies" == results.media.mediaType %}
|
||||||
<div class="results" {{ stimulus_controller('movie_results', {imdbId: results.media.imdbId}) }}>
|
<div class="results" {{ stimulus_controller('movie_results', {tmdbId: results.media.tmdbId, imdbId: results.media.imdbId}) }}>
|
||||||
</div>
|
</div>
|
||||||
{% elseif "tvshows" == results.media.mediaType %}
|
{% elseif "tvshows" == results.media.mediaType %}
|
||||||
{% for season, episodes in results.media.episodes %}
|
{% for season, episodes in results.media.episodes %}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400 hidden"
|
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400 {{ results.media.mediaType == "tvshows" ? "hidden" }}"
|
||||||
{{ stimulus_target(controller, "list") }}
|
{{ stimulus_target(controller, "list") }}
|
||||||
>
|
>
|
||||||
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
|
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for result in results.results %}
|
{% for result in results.results %}
|
||||||
<tr class="bg-white border-b dark:bg-slate-700 dark:border-gray-600 border-gray-200" data-languages="{{ result.languages|json_encode }}" data-season="{{ results.season }}">
|
<tr class="bg-white border-b dark:bg-slate-700 dark:border-gray-600 border-gray-200" data-languages="{{ result.languages|json_encode }}" {% if "tvshows" == results.media.mediaType %} data-season="{{ results.season }} {% endif %}">
|
||||||
<td id="size" class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50">
|
<td id="size" class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50">
|
||||||
{{ result.size }}
|
{{ result.size }}
|
||||||
</td>
|
</td>
|
||||||
@@ -54,7 +54,16 @@
|
|||||||
{{ result.languageFlags|raw }}
|
{{ result.languageFlags|raw }}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-end text-gray-800 dark:text-gray-50 flex flex-row gap-2 items-center justify-end">
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-end text-gray-800 dark:text-gray-50 flex flex-row gap-2 items-center justify-end">
|
||||||
<button class="p-1.5 bg-green-600 rounded-md text-gray-50">
|
<button class="p-1.5 bg-green-600 rounded-md text-gray-50"
|
||||||
|
{{ stimulus_controller('download_button', {
|
||||||
|
url: result.url,
|
||||||
|
title: result.title,
|
||||||
|
filename: result.filename,
|
||||||
|
mediaType: results.media.mediaType,
|
||||||
|
imdbId: results.media.imdbId
|
||||||
|
}) }}
|
||||||
|
{{ stimulus_action('download_button', 'download', 'click') }}
|
||||||
|
>
|
||||||
Download
|
Download
|
||||||
</button>
|
</button>
|
||||||
<label for="select">
|
<label for="select">
|
||||||
|
|||||||
Reference in New Issue
Block a user