feat: media result page
This commit is contained in:
@@ -8,7 +8,9 @@
|
||||
"ext-ctype": "*",
|
||||
"ext-iconv": "*",
|
||||
"1tomany/rich-bundle": "^1.8",
|
||||
"nihilarr/parse-torrent-name": "^0.0.1",
|
||||
"nyholm/psr7": "*",
|
||||
"p3k/emoji-detector": "^1.2",
|
||||
"php-tmdb/api": "^4.1",
|
||||
"symfony/asset": "7.2.*",
|
||||
"symfony/console": "7.2.*",
|
||||
|
||||
93
composer.lock
generated
93
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "52ea99b6f072a3dba10316c24060b886",
|
||||
"content-hash": "e8c968ee6b83d42fa44746ec5e4d303d",
|
||||
"packages": [
|
||||
{
|
||||
"name": "1tomany/data-uri",
|
||||
@@ -251,6 +251,50 @@
|
||||
},
|
||||
"time": "2025-04-07T20:06:18+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nihilarr/parse-torrent-name",
|
||||
"version": "v0.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://gitlab.com/nihilarr/parse-torrent-name.git",
|
||||
"reference": "0d8ddb6c91b33845d2e26677304ea91f69c90319"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://gitlab.com/api/v4/projects/nihilarr%2Fparse-torrent-name/repository/archive.zip?sha=0d8ddb6c91b33845d2e26677304ea91f69c90319",
|
||||
"reference": "0d8ddb6c91b33845d2e26677304ea91f69c90319",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.24"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Nihilarr\\": "PTN/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Drew Smith",
|
||||
"homepage": "https://www.nihilarr.com"
|
||||
}
|
||||
],
|
||||
"description": "Extract media information from torrent-like filename",
|
||||
"homepage": "https://gitlab.com/nihilarr/parse-torrent-name",
|
||||
"keywords": [
|
||||
"library",
|
||||
"parse",
|
||||
"parser",
|
||||
"ptn",
|
||||
"torrent"
|
||||
],
|
||||
"time": "2018-04-23T01:48:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nyholm/psr7",
|
||||
"version": "1.8.2",
|
||||
@@ -519,6 +563,53 @@
|
||||
},
|
||||
"time": "2021-05-22T15:57:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "p3k/emoji-detector",
|
||||
"version": "1.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/aaronpk/emoji-detector-php.git",
|
||||
"reference": "dce4638e215622181d272f08145a3f97b735b1c7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/aaronpk/emoji-detector-php/zipball/dce4638e215622181d272f08145a3f97b735b1c7",
|
||||
"reference": "dce4638e215622181d272f08145a3f97b735b1c7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-intl": "*",
|
||||
"ext-mbstring": "*",
|
||||
"php": ">=7.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^8.5"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/Emoji.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Aaron Parecki",
|
||||
"email": "aaron@parecki.com",
|
||||
"homepage": "https://aaronparecki.com/"
|
||||
}
|
||||
],
|
||||
"description": "Detect and return all emoji found in a string",
|
||||
"homepage": "https://github.com/aaronpk/emoji-detector-php",
|
||||
"support": {
|
||||
"issues": "https://github.com/aaronpk/emoji-detector-php/issues",
|
||||
"source": "https://github.com/aaronpk/emoji-detector-php/tree/1.2.0"
|
||||
},
|
||||
"time": "2024-02-19T18:29:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "php-http/discovery",
|
||||
"version": "1.20.0",
|
||||
|
||||
@@ -6,11 +6,3 @@ controllersIndex:
|
||||
defaults:
|
||||
schemes: [ https ]
|
||||
|
||||
controllersSearch:
|
||||
resource:
|
||||
path: ../src/Search/Framework/Controller
|
||||
namespace: App\Search\Framework\Controller
|
||||
type: attribute
|
||||
defaults:
|
||||
schemes: [ https ]
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Search\Action\Handler\GetMediaInfoHandler;
|
||||
use App\Search\Action\Handler\SearchHandler;
|
||||
use App\Search\Action\Input\GetMediaInfoInput;
|
||||
use App\Search\Action\Input\SearchInput;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
@@ -12,17 +14,30 @@ final class SearchController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private SearchHandler $searchHandler,
|
||||
private GetMediaInfoHandler $getMediaInfoHandler,
|
||||
) {}
|
||||
|
||||
#[Route('/search', name: 'app_search', methods: ['GET'])]
|
||||
public function index(
|
||||
public function search(
|
||||
SearchInput $searchInput,
|
||||
): Response
|
||||
{
|
||||
): Response {
|
||||
$results = $this->searchHandler->handle($searchInput->toCommand());
|
||||
|
||||
return $this->render('search/results.html.twig', [
|
||||
'results' => $results,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/result/{mediaType}/{tmdbId}', name: 'app_search_result')]
|
||||
public function result(
|
||||
GetMediaInfoInput $getDownloadOptionsInput,
|
||||
): Response {
|
||||
$result = $this->getMediaInfoHandler->handle(
|
||||
$getDownloadOptionsInput->toCommand()
|
||||
);
|
||||
|
||||
return $this->render('search/result.html.twig', [
|
||||
'result' => $result,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
14
src/Download/Action/Command/GetDownloadOptionsCommand.php
Normal file
14
src/Download/Action/Command/GetDownloadOptionsCommand.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Download\Action\Command;
|
||||
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
|
||||
class GetDownloadOptionsCommand implements CommandInterface
|
||||
{
|
||||
/** @implements CommandInterface<GetDownloadOptionsCommand> */
|
||||
public function __construct(
|
||||
public string $tmdbId,
|
||||
public string $mediaType,
|
||||
) {}
|
||||
}
|
||||
22
src/Download/Action/Handler/GetDownloadOptionsHandler.php
Normal file
22
src/Download/Action/Handler/GetDownloadOptionsHandler.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Download\Action\Handler;
|
||||
|
||||
use App\Tmdb\Tmdb;
|
||||
use App\Torrentio\Torrentio;
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
use OneToMany\RichBundle\Contract\HandlerInterface;
|
||||
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||
|
||||
class GetDownloadOptionsHandler implements HandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Tmdb $tmdb,
|
||||
private readonly Torrentio $torrentio,
|
||||
) {}
|
||||
|
||||
public function handle(CommandInterface $command): ResultInterface
|
||||
{
|
||||
$media = $this->tmdb->mediaDetails($command->tmdbId, $command->mediaType);
|
||||
}
|
||||
}
|
||||
24
src/Download/Action/Input/GetDownloadOptionsInput.php
Normal file
24
src/Download/Action/Input/GetDownloadOptionsInput.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Download\Action\Input;
|
||||
|
||||
use App\Download\Action\Command\GetDownloadOptionsCommand;
|
||||
use OneToMany\RichBundle\Attribute\SourceRoute;
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
use OneToMany\RichBundle\Contract\InputInterface;
|
||||
|
||||
class GetDownloadOptionsInput implements InputInterface
|
||||
{
|
||||
public function __construct(
|
||||
#[SourceRoute('tmdbId')]
|
||||
public string $tmdbId,
|
||||
|
||||
#[SourceRoute('mediaType')]
|
||||
public string $mediaType,
|
||||
) {}
|
||||
|
||||
public function toCommand(): CommandInterface
|
||||
{
|
||||
return new GetDownloadOptionsCommand($this->tmdbId, $this->mediaType);
|
||||
}
|
||||
}
|
||||
12
src/Download/Action/Result/GetDownloadOptionsResult.php
Normal file
12
src/Download/Action/Result/GetDownloadOptionsResult.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Download\Action\Result;
|
||||
|
||||
use App\Tmdb\TmdbResult;
|
||||
|
||||
class GetDownloadOptionsResult
|
||||
{
|
||||
public function __construct(
|
||||
public TmdbResult $media,
|
||||
) {}
|
||||
}
|
||||
14
src/Search/Action/Command/GetMediaInfoCommand.php
Normal file
14
src/Search/Action/Command/GetMediaInfoCommand.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Search\Action\Command;
|
||||
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
|
||||
class GetMediaInfoCommand implements CommandInterface
|
||||
{
|
||||
/** @implements CommandInterface<GetMediaInfoCommand> */
|
||||
public function __construct(
|
||||
public string $tmdbId,
|
||||
public string $mediaType,
|
||||
) {}
|
||||
}
|
||||
23
src/Search/Action/Handler/GetMediaInfoHandler.php
Normal file
23
src/Search/Action/Handler/GetMediaInfoHandler.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Search\Action\Handler;
|
||||
|
||||
use App\Search\Action\Result\GetMediaInfoResult;
|
||||
use App\Tmdb\Tmdb;
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
use OneToMany\RichBundle\Contract\HandlerInterface;
|
||||
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||
|
||||
class GetMediaInfoHandler implements HandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Tmdb $tmdb,
|
||||
) {}
|
||||
|
||||
public function handle(CommandInterface $command): ResultInterface
|
||||
{
|
||||
$media = $this->tmdb->mediaDetails($command->tmdbId, $command->mediaType);
|
||||
|
||||
return new GetMediaInfoResult($media);
|
||||
}
|
||||
}
|
||||
25
src/Search/Action/Input/GetMediaInfoInput.php
Normal file
25
src/Search/Action/Input/GetMediaInfoInput.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Search\Action\Input;
|
||||
|
||||
use App\Download\Action\Command\GetDownloadOptionsCommand;
|
||||
use App\Search\Action\Command\GetMediaInfoCommand;
|
||||
use OneToMany\RichBundle\Attribute\SourceRoute;
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
use OneToMany\RichBundle\Contract\InputInterface;
|
||||
|
||||
class GetMediaInfoInput implements InputInterface
|
||||
{
|
||||
public function __construct(
|
||||
#[SourceRoute('tmdbId')]
|
||||
public string $tmdbId,
|
||||
|
||||
#[SourceRoute('mediaType')]
|
||||
public string $mediaType,
|
||||
) {}
|
||||
|
||||
public function toCommand(): CommandInterface
|
||||
{
|
||||
return new GetMediaInfoCommand($this->tmdbId, $this->mediaType);
|
||||
}
|
||||
}
|
||||
14
src/Search/Action/Result/GetMediaInfoResult.php
Normal file
14
src/Search/Action/Result/GetMediaInfoResult.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Search\Action\Result;
|
||||
|
||||
use App\Tmdb\TmdbResult;
|
||||
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||
|
||||
class GetMediaInfoResult implements ResultInterface
|
||||
{
|
||||
/** @implements ResultInterface<GetMediaInfoResult> */
|
||||
public function __construct(
|
||||
public TmdbResult $media,
|
||||
) {}
|
||||
}
|
||||
@@ -182,10 +182,10 @@ class Tmdb
|
||||
function parseTvShow(array $data, string $posterBasePath): TmdbResult {
|
||||
return new TmdbResult(
|
||||
imdbId: $data['external_ids']['imdb_id'],
|
||||
tvdbId: $data['id'],
|
||||
tmdbId: $data['id'],
|
||||
title: $data['name'],
|
||||
poster: $posterBasePath . $data['poster_path'],
|
||||
excerpt: $data['overview'],
|
||||
description: $data['overview'],
|
||||
year: (new \DateTime($data['first_air_date']))->format('Y'),
|
||||
mediaType: "tvshows",
|
||||
episodes: $data['episodes'],
|
||||
@@ -195,10 +195,10 @@ class Tmdb
|
||||
function parseMovie(array $data, string $posterBasePath): TmdbResult {
|
||||
return new TmdbResult(
|
||||
imdbId: $data['external_ids']['imdb_id'],
|
||||
tvdbId: $data['id'],
|
||||
tmdbId: $data['id'],
|
||||
title: $data['title'],
|
||||
poster: $posterBasePath . $data['poster_path'],
|
||||
excerpt: $data['overview'],
|
||||
description: $data['overview'],
|
||||
year: (new \DateTime($data['release_date']))->format('Y'),
|
||||
mediaType: "movies",
|
||||
);
|
||||
@@ -215,6 +215,7 @@ class Tmdb
|
||||
$mediaType = $result instanceof Movie ? MediaType::Movie->value : MediaType::TvShow->value;
|
||||
$tmdbResult = new TmdbResult();
|
||||
$tmdbResult->mediaType = $mediaType;
|
||||
$tmdbResult->tmdbId = $result->getId();
|
||||
$tmdbResult->imdbId = $this->getImdbId($result->getId(), $mediaType);
|
||||
$tmdbResult->title = $this->getTitle($result, $mediaType);
|
||||
$tmdbResult->poster = self::POSTER_IMG_PATH . $result->getPosterImage();
|
||||
|
||||
@@ -6,7 +6,7 @@ class TmdbResult
|
||||
{
|
||||
public function __construct(
|
||||
public ?string $imdbId = "",
|
||||
public ?string $tvdbId = "",
|
||||
public ?string $tmdbId = "",
|
||||
public ?string $title = "",
|
||||
public ?string $poster = "",
|
||||
public ?string $description = "",
|
||||
|
||||
107
src/Torrentio/Result/ResultFactory.php
Normal file
107
src/Torrentio/Result/ResultFactory.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace App\Torrentio\Result;
|
||||
|
||||
use App\Util\CountryCodes;
|
||||
use Nihilarr\PTN;
|
||||
|
||||
class ResultFactory
|
||||
{
|
||||
public static function map(
|
||||
string $url,
|
||||
string $title,
|
||||
string $bingeGroup = "-"
|
||||
) {
|
||||
$ptn = (object) (new PTN())->parse($title);
|
||||
return new TorrentioResult(
|
||||
self::trimTitle($title),
|
||||
$url,
|
||||
self::setSize($title),
|
||||
self::setSeeders($title),
|
||||
self::setProvider($title),
|
||||
self::setEpisode($title),
|
||||
$ptn->season ?? "-",
|
||||
$bingeGroup,
|
||||
$ptn->resolution ?? "-",
|
||||
$ptn->codec ?? "-",
|
||||
$ptn,
|
||||
substr(base64_encode($url), strlen($url) - 10),
|
||||
$ptn->episode ?? "-",
|
||||
self::setLanguages($title),
|
||||
self::setLanguageFlags($title),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
public static function setSize(string $title): string
|
||||
{
|
||||
$sizeMatch = [];
|
||||
preg_match('/(\d+\.?\d+ )(GB|MB)/', $title, $sizeMatch);
|
||||
return $sizeMatch[0] ?? "-";
|
||||
}
|
||||
|
||||
private static function setSeeders(string $title): string
|
||||
{
|
||||
$emoji = \Emoji\detect_emoji($title);
|
||||
return intval(
|
||||
grapheme_substr($title, $emoji[0]['grapheme_offset'] + 1, $emoji[1]['grapheme_offset'] - $emoji[0]['grapheme_offset'])
|
||||
);
|
||||
}
|
||||
|
||||
private static function setProvider(string $title): string
|
||||
{
|
||||
$emoji = \Emoji\detect_emoji($title);
|
||||
$provider = trim(
|
||||
grapheme_substr($title, $emoji[2]['grapheme_offset'] + 1, strlen($title) - $emoji[1]['grapheme_offset'])
|
||||
);
|
||||
$providerParts = explode("\n", $provider);
|
||||
return $providerParts[0];
|
||||
}
|
||||
|
||||
private static function setLanguageFlags(string $title): string
|
||||
{
|
||||
$emoji = \Emoji\detect_emoji($title);
|
||||
$provider = trim(
|
||||
grapheme_substr($title, $emoji[2]['grapheme_offset'] + 1, strlen($title) - $emoji[1]['grapheme_offset'])
|
||||
);
|
||||
|
||||
$providerParts = explode("\n", $provider);
|
||||
|
||||
if (array_key_exists(1, $providerParts)) {
|
||||
return $providerParts[1];
|
||||
} else {
|
||||
return "🇺🇸";
|
||||
}
|
||||
}
|
||||
|
||||
public static function setLanguages(string $title): array
|
||||
{
|
||||
$emoji = \Emoji\detect_emoji($title);
|
||||
$flags = array_filter($emoji, function ($emoji) {
|
||||
return str_starts_with($emoji['short_name'], 'flag-');
|
||||
});
|
||||
|
||||
$languages = array_map(function ($flag) {
|
||||
return CountryCodes::convertFromAbbr(strtoupper(substr($flag['short_name'], strlen('flag-'))));
|
||||
},
|
||||
$flags);
|
||||
if (count($languages) > 0) {
|
||||
return array_values($languages);
|
||||
} else {
|
||||
return ["English"];
|
||||
}
|
||||
}
|
||||
|
||||
private static function setEpisode(string $title)
|
||||
{
|
||||
$value = [];
|
||||
preg_match('/[sS]\d\d[eE]\d\d/', $title, $value);
|
||||
return array_key_exists(0, $value) ? strtoupper($value[0]) : "n/a";
|
||||
}
|
||||
|
||||
private static function trimTitle(string $title)
|
||||
{
|
||||
$emoji = \Emoji\detect_emoji($title);
|
||||
return trim(grapheme_substr($title, 0, $emoji[0]['grapheme_offset']));
|
||||
}
|
||||
}
|
||||
25
src/Torrentio/Result/TorrentioResult.php
Normal file
25
src/Torrentio/Result/TorrentioResult.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Torrentio\Result;
|
||||
|
||||
class TorrentioResult
|
||||
{
|
||||
public function __construct(
|
||||
public ?string $title = "-",
|
||||
public ?string $url = "-",
|
||||
public ?string $size = "-",
|
||||
public ?string $seeders = "-",
|
||||
public ?string $provider = "-",
|
||||
public ?string $episode = "-",
|
||||
public ?string $season = "-",
|
||||
public ?string $bingeGroup = "-",
|
||||
public ?string $resolution = "-",
|
||||
public ?string $codec = "-",
|
||||
public object|array $ptn = [],
|
||||
public ?string $key = "-",
|
||||
public ?string $episodeNumber = "-",
|
||||
public ?array $languages = [],
|
||||
public ?string $languageFlags = "-",
|
||||
public ?bool $selected = false,
|
||||
) {}
|
||||
}
|
||||
18
src/Torrentio/Rule/DownloadOptionFilter/Resolution.php
Normal file
18
src/Torrentio/Rule/DownloadOptionFilter/Resolution.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Torrentio\Rule\DownloadOptionFilter;
|
||||
|
||||
use App\Torrentio\Result\ResultFactory;
|
||||
|
||||
class Resolution
|
||||
{
|
||||
|
||||
public function __construct(
|
||||
public string $expectedValue
|
||||
) {}
|
||||
|
||||
public function __invoke(ResultFactory $result): bool
|
||||
{
|
||||
return $result->resolution === $this->expectedValue;
|
||||
}
|
||||
}
|
||||
34
src/Torrentio/Rule/RuleEngine.php
Normal file
34
src/Torrentio/Rule/RuleEngine.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Torrentio\Rule;
|
||||
|
||||
class RuleEngine
|
||||
{
|
||||
private array $rules = [];
|
||||
|
||||
public function addRule(callable $rule): void
|
||||
{
|
||||
$this->rules[] = $rule;
|
||||
}
|
||||
|
||||
public function validateAny($fact): bool
|
||||
{
|
||||
foreach ($this->rules as $rule) {
|
||||
if ($rule($fact)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function validateAll($fact): bool
|
||||
{
|
||||
foreach ($this->rules as $rule) {
|
||||
if (!$rule($fact)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
117
src/Torrentio/Torrentio.php
Normal file
117
src/Torrentio/Torrentio.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace App\Torrentio;
|
||||
|
||||
use App\Torrentio\Rule\DownloadOptionFilter\Resolution;
|
||||
use App\Torrentio\Rule\RuleEngine;
|
||||
use App\Torrentio\Result\ResultFactory;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Contracts\Cache\CacheInterface;
|
||||
use Symfony\Contracts\Cache\ItemInterface;
|
||||
|
||||
/**
|
||||
* ToDo: Fix
|
||||
*/
|
||||
class Torrentio
|
||||
{
|
||||
private string $baseUrl = 'https://torrentio.strem.fun/providers%253Dyts%252Ceztv%252Crarbg%252C1337x%252Cthepiratebay%252Ckickasstorrents%252Ctorrentgalaxy%252Cmagnetdl%252Chorriblesubs%252Cnyaasi%7Csort%253Dqualitysize%7Cqualityfilter%253D480p%252Cscr%252Ccam%7Crealdebrid={realDebridKey}/stream/movie/{imdbCode}.json';
|
||||
// private string $baseUrl = 'https://torrentio.strem.fun/providers=yts,eztv,rarbg,1337x,thepiratebay,kickasstorrents,torrentgalaxy,magnetdl,horriblesubs|sort=qualitysize|qualityfilter=480p,cam,unknown|debridoptions=nodownloadlinks|realdebrid=QYYBR7OSQ4VEFKWASDEZ2B4VO67KHUJY6IWOT7HHA7ATXO7QCYDQ/stream/{imdbCode}.json';
|
||||
|
||||
private string $searchUrl;
|
||||
|
||||
public function __construct(
|
||||
#[Autowire(env: 'REAL_DEBRID_KEY')] private string $realDebridKey,
|
||||
private CacheInterface $cache,
|
||||
) {
|
||||
$this->searchUrl = str_replace('{realDebridKey}', $this->realDebridKey, $this->baseUrl);
|
||||
}
|
||||
|
||||
public function search(string $imdbCode, string $type, array $filter = []): array
|
||||
{
|
||||
$cacheKey = "torrentio.{$imdbCode}";
|
||||
|
||||
$results = $this->cache->get($cacheKey, function (ItemInterface $item) use ($imdbCode) {
|
||||
$item->expiresAt(new \DateTimeImmutable("today 11:59 pm"));
|
||||
$response = file_get_contents(str_replace('{imdbCode}', $imdbCode, $this->searchUrl));
|
||||
return json_decode(
|
||||
$response,
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
return $this->parse($results, $filter);
|
||||
}
|
||||
|
||||
public function searchBySeriesSeason(MediaResult $series): MediaResult
|
||||
{
|
||||
$imdbCode = $series->imdbId;
|
||||
// foreach ($series->episodes as $season => $episodes) {
|
||||
// foreach ($episodes as $key => $episode) {
|
||||
// $cacheKey = "torrentio.$series->imdbId.$season.{$episode['episode_number']}";
|
||||
// $downloadOptions = $this->cache->get($cacheKey, function (ItemInterface $item) use ($imdbCode, $season, $episode) {
|
||||
// $item->expiresAt(new \DateTimeImmutable("today 11:59 pm"));
|
||||
// $response = file_get_contents(str_replace('{imdbCode}', "$imdbCode:$season:{$episode['episode_number']}", $this->searchUrl));
|
||||
// return json_decode(
|
||||
// $response,
|
||||
// true
|
||||
// );
|
||||
// });
|
||||
// $series->episodes[$season][$key]['download_options'] = $this->parse($downloadOptions, []);
|
||||
// }
|
||||
// }
|
||||
return $series;
|
||||
}
|
||||
|
||||
public function fetchEpisodeResults(string $imdbId, int $season, int $episode): array
|
||||
{
|
||||
$cacheKey = "torrentio.$imdbId.$season.$episode";
|
||||
$results = $this->cache->get($cacheKey, function (ItemInterface $item) use ($imdbId, $season, $episode) {
|
||||
$item->expiresAt(new \DateTimeImmutable("today 11:59 pm"));
|
||||
$response = file_get_contents(str_replace('{imdbCode}', "$imdbId:$season:$episode", $this->searchUrl));
|
||||
return json_decode(
|
||||
$response,
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
return $this->parse($results, []);
|
||||
}
|
||||
|
||||
public function parse(array $data, array $filter): array
|
||||
{
|
||||
$ruleEngine = new RuleEngine();
|
||||
foreach ($filter as $rule => $value) {
|
||||
if ('resolution' === $rule) {
|
||||
$ruleEngine->addRule(new Resolution($value));
|
||||
}
|
||||
}
|
||||
|
||||
$results = [];
|
||||
foreach ($data['streams'] as $stream) {
|
||||
if (!str_starts_with($stream['url'], "https")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
array_key_exists('behaviorHints', $stream) &&
|
||||
array_key_exists('bingeGroup', $stream['behaviorHints'])
|
||||
) {
|
||||
$bingeGroup = $stream['behaviorHints']['bingeGroup'];
|
||||
} else {
|
||||
$bingeGroup = '-';
|
||||
}
|
||||
|
||||
$result = ResultFactory::map(
|
||||
$stream['url'],
|
||||
$stream['title'],
|
||||
$bingeGroup
|
||||
);
|
||||
|
||||
if ($ruleEngine->validateAll($result)) {
|
||||
$results[] = $result;
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
260
src/Util/CountryCodes.php
Normal file
260
src/Util/CountryCodes.php
Normal file
@@ -0,0 +1,260 @@
|
||||
<?php
|
||||
|
||||
namespace App\Util;
|
||||
|
||||
class CountryCodes
|
||||
{
|
||||
static $countries = [
|
||||
'AF' => 'Afghanistan',
|
||||
'AX' => 'Aland Islands',
|
||||
'AL' => 'Albania',
|
||||
'DZ' => 'Algeria',
|
||||
'AS' => 'American Samoa',
|
||||
'AD' => 'Andorra',
|
||||
'AO' => 'Angola',
|
||||
'AI' => 'Anguilla',
|
||||
'AQ' => 'Antarctica',
|
||||
'AG' => 'Antigua and Barbuda',
|
||||
'AR' => 'Argentina',
|
||||
'AM' => 'Armenia',
|
||||
'AW' => 'Aruba',
|
||||
'AU' => 'Australia',
|
||||
'AT' => 'Austria',
|
||||
'AZ' => 'Azerbaijan',
|
||||
'BS' => 'Bahamas the',
|
||||
'BH' => 'Bahrain',
|
||||
'BD' => 'Bangladesh',
|
||||
'BB' => 'Barbados',
|
||||
'BY' => 'Belarus',
|
||||
'BE' => 'Belgium',
|
||||
'BZ' => 'Belize',
|
||||
'BJ' => 'Benin',
|
||||
'BM' => 'Bermuda',
|
||||
'BT' => 'Bhutan',
|
||||
'BO' => 'Bolivia',
|
||||
'BA' => 'Bosnia and Herzegovina',
|
||||
'BW' => 'Botswana',
|
||||
'BV' => 'Bouvet Island (Bouvetoya)',
|
||||
'BR' => 'Brazil',
|
||||
'IO' => 'British Indian Ocean Territory (Chagos Archipelago)',
|
||||
'VG' => 'British Virgin Islands',
|
||||
'BN' => 'Brunei Darussalam',
|
||||
'BG' => 'Bulgaria',
|
||||
'BF' => 'Burkina Faso',
|
||||
'BI' => 'Burundi',
|
||||
'KH' => 'Cambodia',
|
||||
'CM' => 'Cameroon',
|
||||
'CA' => 'Canada',
|
||||
'CV' => 'Cape Verde',
|
||||
'KY' => 'Cayman Islands',
|
||||
'CF' => 'Central African Republic',
|
||||
'TD' => 'Chad',
|
||||
'CL' => 'Chile',
|
||||
'CN' => 'China',
|
||||
'CX' => 'Christmas Island',
|
||||
'CC' => 'Cocos (Keeling) Islands',
|
||||
'CO' => 'Colombia',
|
||||
'KM' => 'Comoros the',
|
||||
'CD' => 'Congo',
|
||||
'CG' => 'Congo the',
|
||||
'CK' => 'Cook Islands',
|
||||
'CR' => 'Costa Rica',
|
||||
'CI' => 'Cote d\'Ivoire',
|
||||
'HR' => 'Croatia',
|
||||
'CU' => 'Cuba',
|
||||
'CY' => 'Cyprus',
|
||||
'CZ' => 'Czech Republic',
|
||||
'DK' => 'Denmark',
|
||||
'DJ' => 'Djibouti',
|
||||
'DM' => 'Dominica',
|
||||
'DO' => 'Dominican Republic',
|
||||
'EC' => 'Ecuador',
|
||||
'EG' => 'Egypt',
|
||||
'SV' => 'El Salvador',
|
||||
'GQ' => 'Equatorial Guinea',
|
||||
'ER' => 'Eritrea',
|
||||
'EE' => 'Estonia',
|
||||
'ET' => 'Ethiopia',
|
||||
'FO' => 'Faroe Islands',
|
||||
'FK' => 'Falkland Islands (Malvinas)',
|
||||
'FJ' => 'Fiji the Fiji Islands',
|
||||
'FI' => 'Finland',
|
||||
'FR' => 'France, French Republic',
|
||||
'GF' => 'French Guiana',
|
||||
'PF' => 'French Polynesia',
|
||||
'TF' => 'French Southern Territories',
|
||||
'GA' => 'Gabon',
|
||||
'GM' => 'Gambia the',
|
||||
'GE' => 'Georgia',
|
||||
'DE' => 'Germany',
|
||||
'GH' => 'Ghana',
|
||||
'GI' => 'Gibraltar',
|
||||
'GR' => 'Greece',
|
||||
'GL' => 'Greenland',
|
||||
'GD' => 'Grenada',
|
||||
'GP' => 'Guadeloupe',
|
||||
'GU' => 'Guam',
|
||||
'GT' => 'Guatemala',
|
||||
'GG' => 'Guernsey',
|
||||
'GN' => 'Guinea',
|
||||
'GW' => 'Guinea-Bissau',
|
||||
'GY' => 'Guyana',
|
||||
'HT' => 'Haiti',
|
||||
'HM' => 'Heard Island and McDonald Islands',
|
||||
'VA' => 'Holy See (Vatican City State)',
|
||||
'HN' => 'Honduras',
|
||||
'HK' => 'Hong Kong',
|
||||
'HU' => 'Hungary',
|
||||
'IS' => 'Iceland',
|
||||
'IN' => 'India',
|
||||
'ID' => 'Indonesia',
|
||||
'IR' => 'Iran',
|
||||
'IQ' => 'Iraq',
|
||||
'IE' => 'Ireland',
|
||||
'IM' => 'Isle of Man',
|
||||
'IL' => 'Israel',
|
||||
'IT' => 'Italy',
|
||||
'JM' => 'Jamaica',
|
||||
'JP' => 'Japan',
|
||||
'JE' => 'Jersey',
|
||||
'JO' => 'Jordan',
|
||||
'KZ' => 'Kazakhstan',
|
||||
'KE' => 'Kenya',
|
||||
'KI' => 'Kiribati',
|
||||
'KP' => 'Korea',
|
||||
'KR' => 'Korea',
|
||||
'KW' => 'Kuwait',
|
||||
'KG' => 'Kyrgyz Republic',
|
||||
'LA' => 'Lao',
|
||||
'LV' => 'Latvia',
|
||||
'LB' => 'Lebanon',
|
||||
'LS' => 'Lesotho',
|
||||
'LR' => 'Liberia',
|
||||
'LY' => 'Libyan Arab Jamahiriya',
|
||||
'LI' => 'Liechtenstein',
|
||||
'LT' => 'Lithuania',
|
||||
'LU' => 'Luxembourg',
|
||||
'MO' => 'Macao',
|
||||
'MK' => 'Macedonia',
|
||||
'MG' => 'Madagascar',
|
||||
'MW' => 'Malawi',
|
||||
'MY' => 'Malaysia',
|
||||
'MV' => 'Maldives',
|
||||
'ML' => 'Mali',
|
||||
'MT' => 'Malta',
|
||||
'MH' => 'Marshall Islands',
|
||||
'MQ' => 'Martinique',
|
||||
'MR' => 'Mauritania',
|
||||
'MU' => 'Mauritius',
|
||||
'YT' => 'Mayotte',
|
||||
'MX' => 'Mexico',
|
||||
'FM' => 'Micronesia',
|
||||
'MD' => 'Moldova',
|
||||
'MC' => 'Monaco',
|
||||
'MN' => 'Mongolia',
|
||||
'ME' => 'Montenegro',
|
||||
'MS' => 'Montserrat',
|
||||
'MA' => 'Morocco',
|
||||
'MZ' => 'Mozambique',
|
||||
'MM' => 'Myanmar',
|
||||
'NA' => 'Namibia',
|
||||
'NR' => 'Nauru',
|
||||
'NP' => 'Nepal',
|
||||
'AN' => 'Netherlands Antilles',
|
||||
'NL' => 'Netherlands the',
|
||||
'NC' => 'New Caledonia',
|
||||
'NZ' => 'New Zealand',
|
||||
'NI' => 'Nicaragua',
|
||||
'NE' => 'Niger',
|
||||
'NG' => 'Nigeria',
|
||||
'NU' => 'Niue',
|
||||
'NF' => 'Norfolk Island',
|
||||
'MP' => 'Northern Mariana Islands',
|
||||
'NO' => 'Norway',
|
||||
'OM' => 'Oman',
|
||||
'PK' => 'Pakistan',
|
||||
'PW' => 'Palau',
|
||||
'PS' => 'Palestinian Territory',
|
||||
'PA' => 'Panama',
|
||||
'PG' => 'Papua New Guinea',
|
||||
'PY' => 'Paraguay',
|
||||
'PE' => 'Peru',
|
||||
'PH' => 'Philippines',
|
||||
'PN' => 'Pitcairn Islands',
|
||||
'PL' => 'Poland',
|
||||
'PT' => 'Portugal, Portuguese Republic',
|
||||
'PR' => 'Puerto Rico',
|
||||
'QA' => 'Qatar',
|
||||
'RE' => 'Reunion',
|
||||
'RO' => 'Romania',
|
||||
'RU' => 'Russian Federation',
|
||||
'RW' => 'Rwanda',
|
||||
'BL' => 'Saint Barthelemy',
|
||||
'SH' => 'Saint Helena',
|
||||
'KN' => 'Saint Kitts and Nevis',
|
||||
'LC' => 'Saint Lucia',
|
||||
'MF' => 'Saint Martin',
|
||||
'PM' => 'Saint Pierre and Miquelon',
|
||||
'VC' => 'Saint Vincent and the Grenadines',
|
||||
'WS' => 'Samoa',
|
||||
'SM' => 'San Marino',
|
||||
'ST' => 'Sao Tome and Principe',
|
||||
'SA' => 'Saudi Arabia',
|
||||
'SN' => 'Senegal',
|
||||
'RS' => 'Serbia',
|
||||
'SC' => 'Seychelles',
|
||||
'SL' => 'Sierra Leone',
|
||||
'SG' => 'Singapore',
|
||||
'SK' => 'Slovakia (Slovak Republic)',
|
||||
'SI' => 'Slovenia',
|
||||
'SB' => 'Solomon Islands',
|
||||
'SO' => 'Somalia, Somali Republic',
|
||||
'ZA' => 'South Africa',
|
||||
'GS' => 'South Georgia and the South Sandwich Islands',
|
||||
'ES' => 'Spain',
|
||||
'LK' => 'Sri Lanka',
|
||||
'SD' => 'Sudan',
|
||||
'SR' => 'Suriname',
|
||||
'SJ' => 'Svalbard & Jan Mayen Islands',
|
||||
'SZ' => 'Swaziland',
|
||||
'SE' => 'Sweden',
|
||||
'CH' => 'Switzerland, Swiss Confederation',
|
||||
'SY' => 'Syrian Arab Republic',
|
||||
'TW' => 'Taiwan',
|
||||
'TJ' => 'Tajikistan',
|
||||
'TZ' => 'Tanzania',
|
||||
'TH' => 'Thailand',
|
||||
'TL' => 'Timor-Leste',
|
||||
'TG' => 'Togo',
|
||||
'TK' => 'Tokelau',
|
||||
'TO' => 'Tonga',
|
||||
'TT' => 'Trinidad and Tobago',
|
||||
'TN' => 'Tunisia',
|
||||
'TR' => 'Turkey',
|
||||
'TM' => 'Turkmenistan',
|
||||
'TC' => 'Turks and Caicos Islands',
|
||||
'TV' => 'Tuvalu',
|
||||
'UG' => 'Uganda',
|
||||
'UA' => 'Ukraine',
|
||||
'AE' => 'United Arab Emirates',
|
||||
'GB' => 'United Kingdom',
|
||||
'US' => 'United States of America',
|
||||
'UM' => 'United States Minor Outlying Islands',
|
||||
'VI' => 'United States Virgin Islands',
|
||||
'UY' => 'Uruguay, Eastern Republic of',
|
||||
'UZ' => 'Uzbekistan',
|
||||
'VU' => 'Vanuatu',
|
||||
'VE' => 'Venezuela',
|
||||
'VN' => 'Vietnam',
|
||||
'WF' => 'Wallis and Futuna',
|
||||
'EH' => 'Western Sahara',
|
||||
'YE' => 'Yemen',
|
||||
'ZM' => 'Zambia',
|
||||
'ZW' => 'Zimbabwe'
|
||||
];
|
||||
|
||||
public static function convertFromAbbr(string $abbr): ?string
|
||||
{
|
||||
return self::$countries[$abbr] ?? null;
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
<body class="bg-neutral-700 flex flex-col">
|
||||
<twig:Header />
|
||||
|
||||
<div class="flex flex-row w-full">
|
||||
<div class="flex flex-row mx-w-lg">
|
||||
<twig:NavBar />
|
||||
{% block body %}{% endblock %}
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<a class="h-9 rounded-md py-1 px-2 bg-green-600 text-gray-50"
|
||||
href="/results/{{ mediaType }}/{{ imdbId }}/"
|
||||
href="{{ url('app_search_result', {mediaType: mediaType, tmdbId: tmdbId}) }}"
|
||||
>choose</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
14
templates/search/result.html.twig
Normal file
14
templates/search/result.html.twig
Normal file
@@ -0,0 +1,14 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Search Results &mdash - Torsearch{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="p-4 flex flex-col grow gap-4">
|
||||
<h2 class="mb-2 text-3xl font-bold text-gray-50">Search Results</h2>
|
||||
<div class="flex flex-row w-full gap-2">
|
||||
<twig:Card title="{{ result.media.title }}" contentClass="flex flex-col gap-4 justify-between w-full">
|
||||
|
||||
</twig:Card>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -17,6 +17,7 @@
|
||||
description="{{ result.description }}"
|
||||
poster="{{ result.poster }}"
|
||||
imdbId="{{ result.imdbId }}"
|
||||
tmdbId="{{ result.tmdbId }}"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
Reference in New Issue
Block a user