Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
939b059872 | ||
|
|
f968e7e622 | ||
|
|
7958f50ff7 | ||
|
|
f4644d40ef | ||
|
|
37516c7f02 | ||
|
|
c7956f5f0b | ||
|
|
fdf8714033 | ||
|
|
0e667fc7aa |
3
.env
3
.env
@@ -68,3 +68,6 @@ SENTRY_JS_URL=
|
||||
# - only include media originally
|
||||
# produced in this language
|
||||
TMDB_ORIGINAL_LANGUAGE=en
|
||||
|
||||
# Cache Torrentio Results
|
||||
TORRENTIO_CACHE_RESULTS=true
|
||||
|
||||
@@ -14,5 +14,6 @@ export default class extends Controller {
|
||||
"3000"
|
||||
));
|
||||
this.element.addEventListener('mouseover', () => clearTimeout(timer));
|
||||
this.element.querySelector('.modal-close').addEventListener('click', () => this.element.remove());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,9 @@ parameters:
|
||||
sentry.dsn: '%env(SENTRY_DSN)%'
|
||||
sentry.javascript_url: '%env(SENTRY_JS_URL)%'
|
||||
|
||||
# Torrentio
|
||||
torrentio.cache_results: '%env(bool:TORRENTIO_CACHE_RESULTS)%'
|
||||
|
||||
services:
|
||||
# default configuration for services in *this* file
|
||||
_defaults:
|
||||
|
||||
@@ -1,58 +1,99 @@
|
||||
# App must be served over HTTPS (requirement of Mercure)
|
||||
# Either serve behind an SSL terminating reverse proxy
|
||||
# or pass your certificates into the 'app' container.
|
||||
# Please omit any trailing slashes. The APP_URL is
|
||||
# used to generate the Mercure URL behind the scenes.
|
||||
APP_URL="https://dev.caldwell.digital"
|
||||
###################
|
||||
# Torsearch #
|
||||
###################
|
||||
# The version of Torsearch to run. Docker will this tag.
|
||||
TAG=0.38.0
|
||||
|
||||
# The port for which the web server (app container) will
|
||||
# serve the application on the host.
|
||||
WEB_PORT=8004
|
||||
|
||||
# The host directories where your media is stored.
|
||||
# If you would like to use a docker driver for a network
|
||||
# share, update the compose.yml file to reflect that.
|
||||
LOCAL_MOVIES_DIR="<enter movies dir>"
|
||||
LOCAL_TVSHOWS_DIR="<enter tvshows dir>"
|
||||
|
||||
# Set the timezone you're in. This helps render monitored items correctly on the calendar
|
||||
TZ=America/Chicago
|
||||
|
||||
###################
|
||||
# Symfony #
|
||||
###################
|
||||
# The external URL of the application where it can be reached by a browser.
|
||||
APP_URL="<enter url>"
|
||||
# Requried by Symfony Framework. Feel free to change.
|
||||
APP_SECRET="70169beadfbc8101c393cbfbba27a313"
|
||||
# Change to 'dev' to show logs in the browser.
|
||||
APP_ENV=prod
|
||||
|
||||
|
||||
###################
|
||||
# Mercure #
|
||||
###################
|
||||
# Mercure is a Caddy module built into the webserver
|
||||
# that facilitates the usage of websockets to transmit
|
||||
# real time data (download progress, etc.)
|
||||
MERCURE_JWT_SECRET="!ChangeThisMercureHubJWTSecretKey!"
|
||||
MERCURE_PUBLISHER_JWT_KEY="!ChangeThisMercureHubJWTSecretKey!"
|
||||
MERCURE_SUBSCRIBER_JWT_KEY="!ChangeThisMercureHubJWTSecretKey!"
|
||||
|
||||
|
||||
###################
|
||||
# Database #
|
||||
###################
|
||||
# Use the DATABASE_URL below to use the MariaDB container
|
||||
# provided in the example.compose.yml file, or remove this
|
||||
# line and fill in the details of your own MySQL/MariaDB server
|
||||
DATABASE_URL="mysql://root:password@database:3306/app?serverVersion=10.6.19.2-MariaDB&charset=utf8mb4"
|
||||
MYSQL_RANDOM_ROOT_PASSWORD=true
|
||||
MYSQL_DATABASE=app
|
||||
MYSQL_USER=app
|
||||
MYSQL_PASSWORD=password
|
||||
MYSQL_HOST=database
|
||||
DATABASE_URL="mysql://${MYSQL_USER}:${MYSQL_PASSWORD}@${MYSQL_HOST}:3306/${MYSQL_DATABASE}?serverVersion=10.11.2-MariaDB&charset=utf8mb4"
|
||||
|
||||
# Fill in your MySQL/MariaDB connection details
|
||||
#DATABASE_URL="mysql://<mysql user>:<mysql pass>@<mysql host>:3306/<mysql db name>?serverVersion=10.6.19.2-MariaDB&charset=utf8mb4"
|
||||
|
||||
# Enter your Real Debrid API key
|
||||
# This key is never saved anywhere
|
||||
# else and is passed to Torrentio
|
||||
# to retrieve download options
|
||||
REAL_DEBRID_KEY=""
|
||||
###################
|
||||
# Real Debrid #
|
||||
###################
|
||||
# Enter your Real Debrid API key is passed to Torrentio to retrieve download options
|
||||
REAL_DEBRID_KEY="<enter real debrid api key>"
|
||||
|
||||
|
||||
###################
|
||||
# TMDB #
|
||||
###################
|
||||
# Enter your TMDB API key
|
||||
# This is used to provide rich search results
|
||||
# when searching for media and rendering the
|
||||
# Popular Movies and TV Shows section.
|
||||
TMDB_API=""
|
||||
# This is used to provide rich search results when searching
|
||||
# for media and rendering the Popular Movies and TV Shows section.
|
||||
TMDB_API="<enter tmdb api key>"
|
||||
|
||||
# Use your own Redis instance or use the
|
||||
# below value to use the container included
|
||||
# in the example compose.yml file.
|
||||
|
||||
###################
|
||||
# Redis #
|
||||
###################
|
||||
# Use your own Redis instance or use the below value to use the
|
||||
# container included in the example compose.yml file.
|
||||
REDIS_HOST="redis://redis"
|
||||
|
||||
### Auth ###
|
||||
# Change to "oidc" to and provide the required
|
||||
# environment variables below to use OIDC auth.
|
||||
|
||||
###################
|
||||
# Auth #
|
||||
###################
|
||||
# Options: form_login, oidc, or ldap (experimental)
|
||||
# Fill the rest of the configuration based on your choice here
|
||||
# No additional config is required for form_login
|
||||
AUTH_METHOD=form_login
|
||||
|
||||
# OIDC
|
||||
OIDC_WELL_KNOWN_URL=
|
||||
OIDC_CLIENT_ID=
|
||||
OIDC_CLIENT_SECRET=
|
||||
# Allows you to skip the login page and directly
|
||||
# rely on your IdP for auth.
|
||||
OIDC_BYPASS_FORM_LOGIN=
|
||||
### OIDC ###
|
||||
#OIDC_WELL_KNOWN_URL=
|
||||
#OIDC_CLIENT_ID=
|
||||
#OIDC_CLIENT_SECRET=
|
||||
# Allows you to skip the login page and directly rely on your IdP for auth.
|
||||
#OIDC_BYPASS_FORM_LOGIN=
|
||||
|
||||
|
||||
# LDAP Config: To use LDAP, enter the below fields
|
||||
# and run 'php bin/console config:set auth.method ldap'
|
||||
### LDAP (*** Experimental! ***) ###
|
||||
# LDAP Config: To use LDAP, enter the below fields and run 'php bin/console config:set auth.method ldap'
|
||||
# (LDAP is still in progress and not ready for use)
|
||||
#LDAP_HOST=
|
||||
#LDAP_PORT=
|
||||
|
||||
@@ -1,78 +1,55 @@
|
||||
services:
|
||||
### The app contains the application and web server ###
|
||||
app:
|
||||
image: code.caldwell.digital/home/torsearch-app:latest
|
||||
image: code.caldwell.digital/home/torsearch-app:${TAG}
|
||||
ports:
|
||||
- '8006:80'
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
- ./downloads/movies:/var/download/movies
|
||||
- ./downloads/tvshows:/var/download/tvshows
|
||||
environment:
|
||||
TZ: America/Chicago
|
||||
MERCURE_PUBLISHER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
|
||||
MERCURE_SUBSCRIBER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
|
||||
- "${WEB_PORT}:80"
|
||||
env_file: .env
|
||||
depends_on:
|
||||
database:
|
||||
condition: service_healthy
|
||||
- database
|
||||
volumes:
|
||||
- ${LOCAL_MOVIES_DIR}:/var/download/movies
|
||||
- ${LOCAL_TVSHOWS_DIR}:/var/download/tvshows
|
||||
- mercure_data:/data
|
||||
- mercure_config:/config
|
||||
|
||||
# Downloads happen in this container. Replicate this
|
||||
# container to run multiple downloads simultaneously.
|
||||
# Map your "movies" folder to /var/download/movies
|
||||
# Map your "TV shows" folder to /var/download/tvshows
|
||||
# If your folders are on another machine, use an NFS volume.
|
||||
# This container runs a Symfony worker process.
|
||||
# See: https://symfony.com/doc/current/messenger.html
|
||||
|
||||
### The worker handles downloads and async jobs ###
|
||||
worker:
|
||||
image: code.caldwell.digital/home/torsearch-worker:latest
|
||||
image: code.caldwell.digital/home/torsearch-worker:${TAG}
|
||||
volumes:
|
||||
- ./downloads/movies:/var/download/movies
|
||||
- ./downloads/tvshows:/var/download/tvshows
|
||||
environment:
|
||||
TZ: America/Chicago
|
||||
command: -vv --time-limit=3600 --limit=10
|
||||
env_file:
|
||||
- .env
|
||||
restart: always
|
||||
- ${LOCAL_MOVIES_DIR}:/var/download/movies
|
||||
- ${LOCAL_TVSHOWS_DIR}:/var/download/tvshows
|
||||
env_file: .env
|
||||
depends_on:
|
||||
app:
|
||||
condition: service_healthy
|
||||
- app
|
||||
|
||||
# This container handles the monitoring for new media. When new
|
||||
# monitors are added, jobs are periodically dispatched to this
|
||||
# container, and the desired media is searched for and downloaded.
|
||||
# This container runs a Symfony worker process.
|
||||
# See: https://symfony.com/doc/current/messenger.html
|
||||
|
||||
### The scheduler processes monitored media ###
|
||||
scheduler:
|
||||
image: code.caldwell.digital/home/torsearch-scheduler:latest
|
||||
image: code.caldwell.digital/home/torsearch-scheduler:${TAG}
|
||||
volumes:
|
||||
- ./downloads/movies:/var/download/movies
|
||||
- ./downloads/tvshows:/var/download/tvshows
|
||||
env_file:
|
||||
- .env
|
||||
command: -vv
|
||||
environment:
|
||||
TZ: America/Chicago
|
||||
restart: always
|
||||
- ${LOCAL_MOVIES_DIR}:/var/download/movies
|
||||
- ${LOCAL_TVSHOWS_DIR}:/var/download/tvshows
|
||||
env_file: .env
|
||||
depends_on:
|
||||
app:
|
||||
condition: service_healthy
|
||||
- app
|
||||
|
||||
|
||||
#!! If using your own database, this can be omitted !!#
|
||||
database:
|
||||
image: mariadb:10.11.2
|
||||
volumes:
|
||||
- mysql:/var/lib/mysql
|
||||
environment:
|
||||
MYSQL_DATABASE: app
|
||||
MYSQL_USERNAME: app
|
||||
MYSQL_PASSWORD: password
|
||||
MYSQL_ROOT_PASSWORD: password
|
||||
env_file: .env
|
||||
healthcheck:
|
||||
test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ]
|
||||
test: [ "CMD", "mysqladmin", "-u", "${MYSQL_USER}", "-p", "${MYSQL_PASSWORD}" ,"ping", "-h", "localhost" ]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
|
||||
#!! If using your own redis, this can be omitted !!#
|
||||
redis:
|
||||
image: redis:latest
|
||||
volumes:
|
||||
@@ -80,12 +57,6 @@ services:
|
||||
command: redis-server --maxmemory 512MB
|
||||
restart: unless-stopped
|
||||
|
||||
# **Optional**
|
||||
# Provides a simple method of viewing the database
|
||||
adminer:
|
||||
image: adminer
|
||||
ports:
|
||||
- "8081:8080"
|
||||
|
||||
volumes:
|
||||
mysql:
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Base;
|
||||
|
||||
use App\Base\Dto\AppVersionDto;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
|
||||
|
||||
final class ConfigResolver
|
||||
@@ -14,6 +15,7 @@ final class ConfigResolver
|
||||
|
||||
public function __construct(
|
||||
private readonly DenormalizerInterface $denormalizer,
|
||||
private readonly RequestStack $requestStack,
|
||||
|
||||
#[Autowire(param: 'app.url')]
|
||||
private readonly ?string $appUrl = null,
|
||||
@@ -62,6 +64,9 @@ final class ConfigResolver
|
||||
|
||||
#[Autowire(param: 'sentry.javascript_url')]
|
||||
private ?string $sentryJavascriptUrl = null,
|
||||
|
||||
#[Autowire(param: 'torrentio.cache_results')]
|
||||
private ?bool $torrentioCacheResults = null,
|
||||
) {}
|
||||
|
||||
public function validate(): bool
|
||||
@@ -110,6 +115,11 @@ final class ConfigResolver
|
||||
return $this->authOidcBypassFormLogin;
|
||||
}
|
||||
|
||||
public function isTorrentioCacheEnabled(): bool
|
||||
{
|
||||
return $this->torrentioCacheResults;
|
||||
}
|
||||
|
||||
public function getAppVersion(): AppVersionDto
|
||||
{
|
||||
$matches = [];
|
||||
|
||||
@@ -24,10 +24,10 @@ readonly class Broadcaster
|
||||
private LoggerInterface $logger,
|
||||
) {}
|
||||
|
||||
public function alert(string $title, string $message, string $type = "success", bool $sendPush = false): void
|
||||
public function alert(string $title, string $message, string $type = "success", bool $sendPush = false, ?string $mercureAlertTopic = null): void
|
||||
{
|
||||
try {
|
||||
$userAlertTopic = $this->requestStack->getCurrentRequest()->getSession()->get('mercure_alert_topic');
|
||||
$userAlertTopic = $mercureAlertTopic ?? $this->requestStack->getCurrentRequest()->getSession()->get('mercure_alert_topic');
|
||||
$update = new Update(
|
||||
$userAlertTopic,
|
||||
$this->renderer->render('broadcast/Alert.stream.html.twig', [
|
||||
@@ -39,7 +39,7 @@ readonly class Broadcaster
|
||||
);
|
||||
$this->hub->publish($update);
|
||||
} catch (\Throwable $exception) {
|
||||
// ToDo: look for better handling to get message to end user
|
||||
$this->logger->error('Unable to publish alert: ' . $exception->getMessage());
|
||||
}
|
||||
|
||||
if (true === $sendPush && in_array($this->notificationTransport, ['ntfy'])) {
|
||||
|
||||
@@ -17,5 +17,6 @@ class DownloadMediaCommand implements CommandInterface
|
||||
public string $imdbId,
|
||||
public int $userId,
|
||||
public ?int $downloadId = null,
|
||||
public ?string $mercureAlertTopic = null,
|
||||
) {}
|
||||
}
|
||||
@@ -2,9 +2,12 @@
|
||||
|
||||
namespace App\Download\Action\Handler;
|
||||
|
||||
use App\Base\Enum\MediaType;
|
||||
use App\Base\Service\Broadcaster;
|
||||
use App\Download\Action\Command\DownloadMediaCommand;
|
||||
use App\Download\Action\Result\DownloadMediaResult;
|
||||
use App\Download\DownloadEvents;
|
||||
use App\Download\Framework\Entity\Download;
|
||||
use App\Download\Framework\Repository\DownloadRepository;
|
||||
use App\Download\Downloader\DownloaderInterface;
|
||||
use App\EventLog\Action\Command\AddEventLogCommand;
|
||||
@@ -21,8 +24,8 @@ readonly class DownloadMediaHandler implements HandlerInterface
|
||||
public function __construct(
|
||||
private MessageBusInterface $bus,
|
||||
private DownloaderInterface $downloader,
|
||||
private DownloadRepository $downloadRepository,
|
||||
private UserRepository $userRepository,
|
||||
private DownloadRepository $downloadRepository,
|
||||
private UserRepository $userRepository, private Broadcaster $broadcaster,
|
||||
) {}
|
||||
|
||||
public function handle(CommandInterface $command): ResultInterface
|
||||
@@ -49,6 +52,16 @@ readonly class DownloadMediaHandler implements HandlerInterface
|
||||
$download = $this->downloadRepository->find($command->downloadId);
|
||||
}
|
||||
|
||||
try {
|
||||
$this->validateDownloadUrl($download->getUrl());
|
||||
} catch (\Throwable $exception) {
|
||||
$download->setProgress(100);
|
||||
$download->setStatus('Failed');
|
||||
$this->downloadRepository->getEntityManager()->flush();
|
||||
$this->sendFailedDownloadAlert($download, $command->mercureAlertTopic, $exception->getMessage());
|
||||
return new DownloadMediaResult(400, $exception->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
if ($download->getStatus() !== 'Paused') {
|
||||
$this->downloadRepository->updateStatus($download->getId(), 'In Progress');
|
||||
@@ -77,4 +90,30 @@ readonly class DownloadMediaHandler implements HandlerInterface
|
||||
));
|
||||
return new DownloadMediaResult(200, "Success.");
|
||||
}
|
||||
|
||||
public function validateDownloadUrl(string $downloadUrl)
|
||||
{
|
||||
$badFileSizes = [
|
||||
2119075, // copyright infringement
|
||||
];
|
||||
|
||||
$badFileLocations = [
|
||||
'https://torrentio.strem.fun/videos/failed_infringement_v2.mp4' => 'Removed for Copyright Infringement.',
|
||||
];
|
||||
|
||||
$headers = get_headers($downloadUrl, true);
|
||||
if (array_key_exists($headers['Location'], $badFileLocations)) {
|
||||
throw new \Exception($badFileLocations[$headers['Location']]);
|
||||
}
|
||||
}
|
||||
|
||||
private function sendFailedDownloadAlert(Download $download, string $mercureAlertTopic, ?string $message = null): void
|
||||
{
|
||||
$this->broadcaster->alert(
|
||||
title: 'Download Failed',
|
||||
message: $message ?? "{$download->getTitle()} failed to download.",
|
||||
type: 'warning',
|
||||
mercureAlertTopic: $mercureAlertTopic
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ use OneToMany\RichBundle\Contract\InputInterface;
|
||||
/** @implements InputInterface<DownloadMediaInput> */
|
||||
class DownloadMediaInput implements InputInterface
|
||||
{
|
||||
public ?string $mercureAlertTopic = null;
|
||||
|
||||
public function __construct(
|
||||
#[SourceRequest('url')]
|
||||
public string $url,
|
||||
@@ -44,6 +46,7 @@ class DownloadMediaInput implements InputInterface
|
||||
$this->imdbId,
|
||||
$this->userId,
|
||||
$this->downloadId,
|
||||
$this->mercureAlertTopic,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,10 @@ class DownloadOptionEvaluator
|
||||
return false;
|
||||
}
|
||||
|
||||
if (false === $this->validateDownloadUrl($result->url)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -79,4 +83,18 @@ class DownloadOptionEvaluator
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function validateDownloadUrl(string $downloadUrl)
|
||||
{
|
||||
$badFileLocations = [
|
||||
'https://torrentio.strem.fun/videos/failed_infringement_v2.mp4' => 'Removed for Copyright Infringement.',
|
||||
];
|
||||
|
||||
$headers = get_headers($downloadUrl, true);
|
||||
if (array_key_exists($headers['Location'], $badFileLocations)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ use App\Download\DownloadEvents;
|
||||
use App\Download\Framework\Repository\DownloadRepository;
|
||||
use App\EventLog\Action\Command\AddEventLogCommand;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
@@ -22,9 +23,9 @@ use Symfony\Component\Routing\Attribute\Route;
|
||||
class ApiController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private DownloadRepository $downloadRepository,
|
||||
private MessageBusInterface $bus,
|
||||
private readonly Broadcaster $broadcaster,
|
||||
private DownloadRepository $downloadRepository,
|
||||
private MessageBusInterface $bus,
|
||||
private readonly Broadcaster $broadcaster, private readonly RequestStack $requestStack,
|
||||
) {}
|
||||
|
||||
#[Route('/api/download', name: 'api_download', methods: ['POST'])]
|
||||
@@ -42,6 +43,7 @@ class ApiController extends AbstractController
|
||||
);
|
||||
$input->downloadId = $download->getId();
|
||||
$input->userId = $this->getUser()->getId();
|
||||
$input->mercureAlertTopic = $this->requestStack->getSession()->get('mercure_alert_topic');
|
||||
|
||||
$this->bus->dispatch(new AddEventLogCommand(
|
||||
$this->getUser(),
|
||||
|
||||
@@ -98,6 +98,14 @@ class DownloadRepository extends ServiceEntityRepository
|
||||
return $download;
|
||||
}
|
||||
|
||||
public function updateProgress(int $id, int $progress): Download
|
||||
{
|
||||
$download = $this->find($id);
|
||||
$download->setProgress($progress);
|
||||
$this->getEntityManager()->flush();
|
||||
return $download;
|
||||
}
|
||||
|
||||
public function delete(int $id)
|
||||
{
|
||||
$entity = $this->find($id);
|
||||
@@ -115,4 +123,16 @@ class DownloadRepository extends ServiceEntityRepository
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
|
||||
public function badDownloadUrls()
|
||||
{
|
||||
return $this->createQueryBuilder('d')
|
||||
->select('d.url')
|
||||
->andWhere('d.status = :status')
|
||||
->andWhere('d.progress = 100')
|
||||
->setParameter('status', 'Failed')
|
||||
->distinct()
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,25 @@
|
||||
|
||||
namespace App\Torrentio\Client;
|
||||
|
||||
use Aimeos\Map;
|
||||
use App\Download\Framework\Repository\DownloadRepository;
|
||||
use App\Torrentio\Result\ResultFactory;
|
||||
use App\Torrentio\Exception\TorrentioRateLimitException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class Torrentio
|
||||
{
|
||||
private array $badDownloadUrls = [];
|
||||
|
||||
public function __construct(
|
||||
private readonly HttpClient $client,
|
||||
) {}
|
||||
private readonly HttpClient $client,
|
||||
private readonly DownloadRepository $downloadRepository, private readonly LoggerInterface $logger,
|
||||
) {
|
||||
$badDownloadUrls = $this->downloadRepository->badDownloadUrls();
|
||||
$this->badDownloadUrls = Map::from($badDownloadUrls)
|
||||
->map(fn ($url) => $url['url'])
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function search(string $imdbCode, string $type, bool $parseResults = true): array
|
||||
{
|
||||
@@ -47,6 +58,16 @@ class Torrentio
|
||||
continue;
|
||||
}
|
||||
|
||||
$url = explode('/', $stream['url']);
|
||||
$filename = urldecode(end($url));
|
||||
$url[count($url) - 1] = $filename;
|
||||
$url = implode('/', $url);
|
||||
|
||||
if (in_array($stream['url'], $this->badDownloadUrls)) {
|
||||
$this->logger->warning($stream['url'] . ' was skipped because it was identified as a bad download url.');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
array_key_exists('behaviorHints', $stream) &&
|
||||
array_key_exists('bingeGroup', $stream['behaviorHints'])
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Torrentio\Framework\Controller;
|
||||
|
||||
use App\Base\ConfigResolver;
|
||||
use App\Base\Service\Broadcaster;
|
||||
use App\Torrentio\Action\Handler\GetMovieOptionsHandler;
|
||||
use App\Torrentio\Action\Handler\GetTvShowOptionsHandler;
|
||||
@@ -27,6 +28,7 @@ final class WebController extends AbstractController
|
||||
public function __construct(
|
||||
private readonly GetMovieOptionsHandler $getMovieOptionsHandler,
|
||||
private readonly GetTvShowOptionsHandler $getTvShowOptionsHandler,
|
||||
private readonly ConfigResolver $configResolver,
|
||||
private readonly Broadcaster $broadcaster,
|
||||
) {}
|
||||
|
||||
@@ -40,10 +42,14 @@ final class WebController extends AbstractController
|
||||
$input->imdbId
|
||||
);
|
||||
|
||||
$results = $cache->get($cacheId, function (ItemInterface $item) use ($input, $request) {
|
||||
$item->expiresAt(Carbon::now()->addHour()->setMinute(0)->setSecond(0));
|
||||
return $this->getMovieOptionsHandler->handle($input->toCommand());
|
||||
});
|
||||
if (true === $this->configResolver->isTorrentioCacheEnabled()) {
|
||||
$results = $cache->get($cacheId, function (ItemInterface $item) use ($input, $request) {
|
||||
$item->expiresAt(Carbon::now()->addHour()->setMinute(0)->setSecond(0));
|
||||
return $this->getMovieOptionsHandler->handle($input->toCommand());
|
||||
});
|
||||
} else {
|
||||
$results = $this->getMovieOptionsHandler->handle($input->toCommand());
|
||||
}
|
||||
|
||||
if ($request->headers->get('Turbo-Frame')) {
|
||||
return $this->sendFragmentResponse($results, $request);
|
||||
@@ -66,10 +72,14 @@ final class WebController extends AbstractController
|
||||
);
|
||||
|
||||
try {
|
||||
$results = $cache->get($cacheId, function (ItemInterface $item) use ($input) {
|
||||
$item->expiresAt(Carbon::now()->addHour()->setMinute(0)->setSecond(0));
|
||||
return $this->getTvShowOptionsHandler->handle($input->toCommand());
|
||||
});
|
||||
if (true === $this->configResolver->isTorrentioCacheEnabled()) {
|
||||
$results = $cache->get($cacheId, function (ItemInterface $item) use ($input) {
|
||||
$item->expiresAt(Carbon::now()->addHour()->setMinute(0)->setSecond(0));
|
||||
return $this->getTvShowOptionsHandler->handle($input->toCommand());
|
||||
});
|
||||
} else {
|
||||
$results = $this->getTvShowOptionsHandler->handle($input->toCommand());
|
||||
}
|
||||
|
||||
if ($request->headers->get('Turbo-Frame')) {
|
||||
return $this->sendFragmentResponse($results, $request);
|
||||
|
||||
@@ -2,16 +2,14 @@
|
||||
class="alert alert-{{ type|default('success') }}"
|
||||
role="alert"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<svg class="shrink-0 w-4 h-4 me-2" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z"/>
|
||||
</svg>
|
||||
|
||||
<h3 class="text-lg font-medium font-bold">{{ title|default('') }}</h3>
|
||||
|
||||
<twig:ux:icon name="ic:twotone-cancel" style="text-align:right" width="16.75px" height="16.75px" class="modal-close rounded-full align-end text-red-600 hover:text-red-700" />
|
||||
|
||||
<span class="sr-only">Info</span>
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex items-center">
|
||||
<svg class="shrink-0 w-4 h-4 me-2" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z"/>
|
||||
</svg>
|
||||
<h3 class="text-lg font-medium font-bold">{{ title|default('') }}</h3>
|
||||
</div>
|
||||
<twig:ux:icon name="ic:twotone-cancel" width="16.75px" height="16.75px" class="modal-close rounded-full align-end text-red-600 hover:text-red-700" />
|
||||
</div>
|
||||
<div class="mt-2 text-sm w-[300px] font-bold overflow-hidden text-wrap">
|
||||
{{ message }}
|
||||
|
||||
Reference in New Issue
Block a user