mirror of
https://github.com/HDInnovations/UNIT3D-Community-Edition.git
synced 2026-01-29 07:19:32 -06:00
800 lines
28 KiB
PHP
800 lines
28 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* NOTICE OF LICENSE.
|
|
*
|
|
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
|
|
* The details is bundled with this project in the file LICENSE.tx
|
|
*
|
|
* @project UNIT3D Community Edition
|
|
*
|
|
* @author HDVinnie <hdinnovations@protonmail.com>
|
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
|
|
*/
|
|
|
|
namespace App\Http\Livewire;
|
|
|
|
use App\DTO\TorrentSearchFiltersDTO;
|
|
use App\Models\Category;
|
|
use App\Models\Distributor;
|
|
use App\Models\Genre;
|
|
use App\Models\Movie;
|
|
use App\Models\Region;
|
|
use App\Models\Resolution;
|
|
use App\Models\Torrent;
|
|
use App\Models\Tv;
|
|
use App\Models\Type;
|
|
use App\Traits\CastLivewireProperties;
|
|
use App\Traits\LivewireSort;
|
|
use App\Traits\TorrentMeta;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Http\Request;
|
|
use Livewire\Attributes\Computed;
|
|
use Livewire\Attributes\Url;
|
|
use Livewire\Component;
|
|
use Livewire\WithPagination;
|
|
use Meilisearch\Endpoints\Indexes;
|
|
use Closure;
|
|
|
|
class TorrentSearch extends Component
|
|
{
|
|
use CastLivewireProperties;
|
|
use LivewireSort;
|
|
use TorrentMeta;
|
|
use WithPagination;
|
|
|
|
#TODO: Update URL attributes once Livewire 3 fixes upstream bug. See: https://github.com/livewire/livewire/discussions/7746
|
|
|
|
#[Url(history: true)]
|
|
public string $name = '';
|
|
|
|
#[Url(history: true)]
|
|
public string $description = '';
|
|
|
|
#[Url(history: true)]
|
|
public string $mediainfo = '';
|
|
|
|
#[Url(history: true)]
|
|
public string $uploader = '';
|
|
|
|
#[Url(history: true)]
|
|
public string $keywords = '';
|
|
|
|
#[Url(history: true)]
|
|
public ?int $startYear = null;
|
|
|
|
#[Url(history: true)]
|
|
public ?int $endYear = null;
|
|
|
|
#[Url(history: true)]
|
|
public ?int $minSize = null;
|
|
|
|
#[Url(history: true)]
|
|
public int $minSizeMultiplier = 1;
|
|
|
|
#[Url(history: true)]
|
|
public ?int $maxSize = null;
|
|
|
|
#[Url(history: true)]
|
|
public int $maxSizeMultiplier = 1;
|
|
|
|
#[Url(history: true)]
|
|
public ?int $episodeNumber = null;
|
|
|
|
#[Url(history: true)]
|
|
public ?int $seasonNumber = null;
|
|
|
|
/**
|
|
* @var array<int>
|
|
*/
|
|
#[Url(history: true)]
|
|
public array $categoryIds = [];
|
|
|
|
/**
|
|
* @var array<int>
|
|
*/
|
|
#[Url(history: true)]
|
|
public array $typeIds = [];
|
|
|
|
/**
|
|
* @var array<int>
|
|
*/
|
|
#[Url(history: true)]
|
|
public array $resolutionIds = [];
|
|
|
|
/**
|
|
* @var array<int>
|
|
*/
|
|
#[Url(history: true)]
|
|
public array $genreIds = [];
|
|
|
|
/**
|
|
* @var array<int>
|
|
*/
|
|
#[Url(history: true)]
|
|
public array $regionIds = [];
|
|
|
|
/**
|
|
* @var array<int>
|
|
*/
|
|
#[Url(history: true)]
|
|
public array $distributorIds = [];
|
|
|
|
#[Url(history: true)]
|
|
public string $adult = 'any';
|
|
|
|
#[Url(history: true)]
|
|
public ?int $tmdbId = null;
|
|
|
|
#[Url(history: true)]
|
|
public string $imdbId = '';
|
|
|
|
#[Url(history: true)]
|
|
public ?int $tvdbId = null;
|
|
|
|
#[Url(history: true)]
|
|
public ?int $malId = null;
|
|
|
|
#[Url(history: true)]
|
|
public ?int $playlistId = null;
|
|
|
|
#[Url(history: true)]
|
|
public ?int $collectionId = null;
|
|
|
|
#[Url(history: true)]
|
|
public ?int $networkId = null;
|
|
|
|
#[Url(history: true)]
|
|
public ?int $companyId = null;
|
|
|
|
/**
|
|
* @var string[]
|
|
*/
|
|
#[Url(history: true)]
|
|
public array $primaryLanguageNames = [];
|
|
|
|
/**
|
|
* @var string[]
|
|
*/
|
|
#[Url(history: true)]
|
|
public array $free = [];
|
|
|
|
#[Url(history: true)]
|
|
public bool $doubleup = false;
|
|
|
|
#[Url(history: true)]
|
|
public bool $featured = false;
|
|
|
|
#[Url(history: true)]
|
|
public bool $refundable = false;
|
|
|
|
#[Url(history: true)]
|
|
public bool $stream = false;
|
|
|
|
#[Url(history: true)]
|
|
public bool $sd = false;
|
|
|
|
#[Url(history: true)]
|
|
public bool $highspeed = false;
|
|
|
|
#[Url(history: true)]
|
|
public bool $bookmarked = false;
|
|
|
|
#[Url(history: true)]
|
|
public bool $wished = false;
|
|
|
|
#[Url(history: true)]
|
|
public bool $internal = false;
|
|
|
|
#[Url(history: true)]
|
|
public bool $personalRelease = false;
|
|
|
|
#[Url(history: true)]
|
|
public bool $trumpable = false;
|
|
|
|
#[Url(history: true)]
|
|
public bool $alive = false;
|
|
|
|
#[Url(history: true)]
|
|
public bool $dying = false;
|
|
|
|
#[Url(history: true)]
|
|
public bool $dead = false;
|
|
|
|
#[Url(history: true)]
|
|
public bool $graveyard = false;
|
|
|
|
#[Url(history: true)]
|
|
public bool $notDownloaded = false;
|
|
|
|
#[Url(history: true)]
|
|
public bool $downloaded = false;
|
|
|
|
#[Url(history: true)]
|
|
public bool $seeding = false;
|
|
|
|
#[Url(history: true)]
|
|
public bool $leeching = false;
|
|
|
|
#[Url(history: true)]
|
|
public bool $incomplete = false;
|
|
|
|
#[Url(history: true, except: 'meilisearch')]
|
|
public ?string $driver = 'meilisearch';
|
|
|
|
#[Url(history: true)]
|
|
public int $perPage = 25;
|
|
|
|
#[Url(except: 'bumped_at')]
|
|
public string $sortField = 'bumped_at';
|
|
|
|
#[Url(history: true)]
|
|
public string $sortDirection = 'desc';
|
|
|
|
#[Url(except: 'list')]
|
|
public string $view = 'list';
|
|
|
|
final public function mount(Request $request): void
|
|
{
|
|
if ($request->missing('sortField')) {
|
|
$this->sortField = auth()->user()->settings?->torrent_sort_field ?? 'bumped_at';
|
|
}
|
|
|
|
if ($request->missing('view')) {
|
|
$this->view = match (auth()->user()->settings?->torrent_layout) {
|
|
1 => 'card',
|
|
2 => 'group',
|
|
3 => 'poster',
|
|
default => 'list',
|
|
};
|
|
}
|
|
}
|
|
|
|
final public function updating(string $field, mixed &$value): void
|
|
{
|
|
$this->castLivewireProperties($field, $value);
|
|
}
|
|
|
|
final public function updatingName(): void
|
|
{
|
|
$this->resetPage();
|
|
}
|
|
|
|
final public function updatedView(): void
|
|
{
|
|
$this->perPage = \in_array($this->view, ['card', 'poster']) ? 24 : 25;
|
|
}
|
|
|
|
#[Computed]
|
|
final public function personalFreeleech(): bool
|
|
{
|
|
return cache()->get('personal_freeleech:'.auth()->id()) ?? false;
|
|
}
|
|
|
|
/**
|
|
* @return \Illuminate\Database\Eloquent\Collection<int, Category>
|
|
*/
|
|
#[Computed(seconds: 3600, cache: true)]
|
|
final public function categories(): \Illuminate\Database\Eloquent\Collection
|
|
{
|
|
return Category::query()->orderBy('position')->get();
|
|
}
|
|
|
|
/**
|
|
* @return \Illuminate\Database\Eloquent\Collection<int, Type>
|
|
*/
|
|
#[Computed(seconds: 3600, cache: true)]
|
|
final public function types(): \Illuminate\Database\Eloquent\Collection
|
|
{
|
|
return Type::query()->orderBy('position')->get();
|
|
}
|
|
|
|
/**
|
|
* @return \Illuminate\Database\Eloquent\Collection<int, Resolution>
|
|
*/
|
|
#[Computed(seconds: 3600, cache: true)]
|
|
final public function resolutions(): \Illuminate\Database\Eloquent\Collection
|
|
{
|
|
return Resolution::query()->orderBy('position')->get();
|
|
}
|
|
|
|
/**
|
|
* @return \Illuminate\Database\Eloquent\Collection<int, Genre>
|
|
*/
|
|
#[Computed(seconds: 3600, cache: true)]
|
|
final public function genres(): \Illuminate\Database\Eloquent\Collection
|
|
{
|
|
return Genre::query()->orderBy('name')->get();
|
|
}
|
|
|
|
/**
|
|
* @return \Illuminate\Database\Eloquent\Collection<int, Region>
|
|
*/
|
|
#[Computed(seconds: 3600, cache: true)]
|
|
final public function regions(): \Illuminate\Database\Eloquent\Collection
|
|
{
|
|
return Region::query()->orderBy('position')->get();
|
|
}
|
|
|
|
/**
|
|
* @return \Illuminate\Database\Eloquent\Collection<int, Distributor>
|
|
*/
|
|
#[Computed(seconds: 3600, cache: true)]
|
|
final public function distributors(): \Illuminate\Database\Eloquent\Collection
|
|
{
|
|
return Distributor::query()->orderBy('name')->get();
|
|
}
|
|
|
|
/**
|
|
* @return \Illuminate\Support\Collection<int, Movie>
|
|
*/
|
|
#[Computed(seconds: 3600, cache: true)]
|
|
final public function primaryLanguages(): \Illuminate\Support\Collection
|
|
{
|
|
return Movie::query()
|
|
->select('original_language')
|
|
->distinct()
|
|
->orderBy('original_language')
|
|
->pluck('original_language');
|
|
}
|
|
|
|
/**
|
|
* @return Closure(Builder<Torrent>): Builder<Torrent>
|
|
*/
|
|
final public function filters(): TorrentSearchFiltersDTO
|
|
{
|
|
return (new TorrentSearchFiltersDTO(
|
|
name: $this->name,
|
|
description: $this->description,
|
|
mediainfo: $this->mediainfo,
|
|
uploader: $this->uploader,
|
|
keywords: $this->keywords ? array_map('trim', explode(',', $this->keywords)) : [],
|
|
startYear: $this->startYear,
|
|
endYear: $this->endYear,
|
|
minSize: $this->minSize === null ? null : $this->minSize * $this->minSizeMultiplier,
|
|
maxSize: $this->maxSize === null ? null : $this->maxSize * $this->maxSizeMultiplier,
|
|
episodeNumber: $this->episodeNumber,
|
|
seasonNumber: $this->seasonNumber,
|
|
categoryIds: $this->categoryIds,
|
|
typeIds: $this->typeIds,
|
|
resolutionIds: $this->resolutionIds,
|
|
genreIds: $this->genreIds,
|
|
regionIds: $this->regionIds,
|
|
distributorIds: $this->distributorIds,
|
|
adult: match ($this->adult) {
|
|
'include' => true,
|
|
'exclude' => false,
|
|
default => null,
|
|
},
|
|
tmdbId: $this->tmdbId,
|
|
imdbId: $this->imdbId === '' ? null : ((int) (preg_match('/tt0*(?=(\d{7,}))/', $this->imdbId, $matches) ? $matches[1] : $this->imdbId)),
|
|
tvdbId: $this->tvdbId,
|
|
malId: $this->malId,
|
|
playlistId: $this->playlistId,
|
|
collectionId: $this->collectionId,
|
|
networkId: $this->networkId,
|
|
companyId: $this->companyId,
|
|
primaryLanguageNames: $this->primaryLanguageNames,
|
|
free: $this->free,
|
|
doubleup: $this->doubleup,
|
|
featured: $this->featured,
|
|
refundable: $this->refundable,
|
|
stream: $this->stream,
|
|
sd: $this->sd,
|
|
highspeed: $this->highspeed,
|
|
internal: $this->internal,
|
|
trumpable: $this->trumpable,
|
|
personalRelease: $this->personalRelease,
|
|
alive: $this->alive,
|
|
dying: $this->dying,
|
|
dead: $this->dead,
|
|
graveyard: $this->graveyard,
|
|
userBookmarked: $this->bookmarked,
|
|
userWished: $this->wished,
|
|
userDownloaded: match (true) {
|
|
$this->downloaded => true,
|
|
$this->notDownloaded => false,
|
|
default => null,
|
|
},
|
|
userSeeder: match (true) {
|
|
$this->seeding => true,
|
|
$this->leeching => false,
|
|
default => null,
|
|
},
|
|
userActive: match (true) {
|
|
$this->seeding => true,
|
|
$this->leeching => true,
|
|
default => null,
|
|
},
|
|
));
|
|
}
|
|
|
|
/**
|
|
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator<Torrent>
|
|
*/
|
|
#[Computed]
|
|
final public function torrents(): \Illuminate\Contracts\Pagination\LengthAwarePaginator
|
|
{
|
|
$user = auth()->user();
|
|
|
|
// Whitelist which columns are allowed to be ordered by
|
|
if (!\in_array($this->sortField, [
|
|
'name',
|
|
'size',
|
|
'seeders',
|
|
'leechers',
|
|
'times_completed',
|
|
'created_at',
|
|
'bumped_at'
|
|
])) {
|
|
$this->reset('sortField');
|
|
}
|
|
|
|
// Only allow sql for now to prevent user complaints of limiting page count to 1000 results (meilisearch limitation).
|
|
// However, eventually we want to switch to meilisearch only to reduce server load.
|
|
// $isSqlAllowed = $user->group->is_modo && $this->driver === 'sql';
|
|
$isSqlAllowed = $this->driver !== 'sql';
|
|
|
|
$eagerLoads = fn (Builder $query) => $query
|
|
->with(['user:id,username,group_id', 'user.group', 'category', 'type', 'resolution'])
|
|
->withCount([
|
|
'thanks',
|
|
'comments',
|
|
'seeds' => fn ($query) => $query->where('active', '=', true)->where('visible', '=', true),
|
|
'leeches' => fn ($query) => $query->where('active', '=', true)->where('visible', '=', true),
|
|
])
|
|
->withExists([
|
|
'bookmarks' => fn ($query) => $query->where('user_id', '=', $user->id),
|
|
'freeleechTokens' => fn ($query) => $query->where('user_id', '=', $user->id),
|
|
'history as seeding' => fn ($query) => $query->where('user_id', '=', $user->id)
|
|
->where('active', '=', 1)
|
|
->where('seeder', '=', 1),
|
|
'history as leeching' => fn ($query) => $query->where('user_id', '=', $user->id)
|
|
->where('active', '=', 1)
|
|
->where('seeder', '=', 0),
|
|
'history as not_completed' => fn ($query) => $query->where('user_id', '=', $user->id)
|
|
->where('active', '=', 0)
|
|
->where('seeder', '=', 0)
|
|
->whereNull('completed_at'),
|
|
'history as not_seeding' => fn ($query) => $query->where('user_id', '=', $user->id)
|
|
->where('active', '=', 0)
|
|
->where(
|
|
fn ($query) => $query
|
|
->where('seeder', '=', 1)
|
|
->orWhereNotNull('completed_at')
|
|
),
|
|
'trump',
|
|
])
|
|
->selectRaw("
|
|
CASE
|
|
WHEN category_id IN (SELECT `id` from `categories` where `movie_meta` = 1) THEN 'movie'
|
|
WHEN category_id IN (SELECT `id` from `categories` where `tv_meta` = 1) THEN 'tv'
|
|
WHEN category_id IN (SELECT `id` from `categories` where `game_meta` = 1) THEN 'game'
|
|
WHEN category_id IN (SELECT `id` from `categories` where `music_meta` = 1) THEN 'music'
|
|
WHEN category_id IN (SELECT `id` from `categories` where `no_meta` = 1) THEN 'no'
|
|
END as meta
|
|
");
|
|
|
|
if ($isSqlAllowed) {
|
|
$torrents = Torrent::query()
|
|
->where($this->filters()->toSqlQueryBuilder())
|
|
->latest('sticky')
|
|
->orderBy($this->sortField, $this->sortDirection);
|
|
|
|
$eagerLoads($torrents);
|
|
$torrents = $torrents->paginate(min($this->perPage, 100));
|
|
} else {
|
|
$torrents = Torrent::search(
|
|
$this->name,
|
|
function (Indexes $meilisearch, string $query, array $options) {
|
|
$options['sort'] = [
|
|
'sticky:desc',
|
|
$this->sortField.':'.$this->sortDirection,
|
|
];
|
|
$options['filter'] = $this->filters()->toMeilisearchFilter();
|
|
$options['matchingStrategy'] = 'all';
|
|
|
|
$results = $meilisearch->search($query, $options);
|
|
|
|
return $results;
|
|
}
|
|
)
|
|
->query($eagerLoads)
|
|
->paginate(min($this->perPage, 100));
|
|
}
|
|
|
|
// See app/Traits/TorrentMeta.php
|
|
$this->scopeMeta($torrents);
|
|
|
|
return $torrents;
|
|
}
|
|
|
|
/**
|
|
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator<Torrent>
|
|
*/
|
|
#[Computed]
|
|
final public function groupedTorrents()
|
|
{
|
|
$user = auth()->user();
|
|
|
|
// Whitelist which columns are allowed to be ordered by
|
|
if (!\in_array($this->sortField, [
|
|
'bumped_at',
|
|
'times_completed',
|
|
])) {
|
|
$this->reset('sortField');
|
|
}
|
|
|
|
$groups = Torrent::query()
|
|
->select('tmdb')
|
|
->selectRaw('MAX(sticky) as sticky')
|
|
->selectRaw('MAX(bumped_at) as bumped_at')
|
|
->selectRaw('SUM(times_completed) as times_completed')
|
|
->selectRaw("CASE WHEN category_id IN (SELECT `id` from `categories` where `movie_meta` = 1) THEN 'movie' WHEN category_id IN (SELECT `id` from `categories` where `tv_meta` = 1) THEN 'tv' END as meta")
|
|
->havingNotNull('meta')
|
|
->where('tmdb', '!=', 0)
|
|
->where($this->filters()->toSqlQueryBuilder())
|
|
->groupBy('tmdb', 'meta')
|
|
->latest('sticky')
|
|
->orderBy($this->sortField, $this->sortDirection)
|
|
->paginate(min($this->perPage, 100));
|
|
|
|
$movieIds = $groups->getCollection()->where('meta', '=', 'movie')->pluck('tmdb');
|
|
$tvIds = $groups->getCollection()->where('meta', '=', 'tv')->pluck('tmdb');
|
|
|
|
$movies = Movie::with('genres', 'directors')->whereIntegerInRaw('id', $movieIds)->get()->keyBy('id');
|
|
$tv = Tv::with('genres', 'creators')->whereIntegerInRaw('id', $tvIds)->get()->keyBy('id');
|
|
|
|
$torrents = Torrent::query()
|
|
->with(['type:id,name,position', 'resolution:id,name,position'])
|
|
->withCount([
|
|
'seeds' => fn ($query) => $query->where('active', '=', true)->where('visible', '=', true),
|
|
'leeches' => fn ($query) => $query->where('active', '=', true)->where('visible', '=', true),
|
|
])
|
|
->withExists([
|
|
'freeleechTokens' => fn ($query) => $query->where('user_id', '=', $user->id),
|
|
'bookmarks' => fn ($query) => $query->where('user_id', '=', $user->id),
|
|
'history as seeding' => fn ($query) => $query->where('user_id', '=', $user->id)
|
|
->where('active', '=', 1)
|
|
->where('seeder', '=', 1),
|
|
'history as leeching' => fn ($query) => $query->where('user_id', '=', $user->id)
|
|
->where('active', '=', 1)
|
|
->where('seeder', '=', 0),
|
|
'history as not_completed' => fn ($query) => $query->where('user_id', '=', $user->id)
|
|
->where('active', '=', 0)
|
|
->where('seeder', '=', 0)
|
|
->whereNull('completed_at'),
|
|
'history as not_seeding' => fn ($query) => $query->where('user_id', '=', $user->id)
|
|
->where('active', '=', 0)
|
|
->where(
|
|
fn ($query) => $query
|
|
->where('seeder', '=', 1)
|
|
->orWhereNotNull('completed_at')
|
|
),
|
|
])
|
|
->select([
|
|
'id',
|
|
'name',
|
|
'info_hash',
|
|
'size',
|
|
'leechers',
|
|
'seeders',
|
|
'times_completed',
|
|
'category_id',
|
|
'user_id',
|
|
'season_number',
|
|
'episode_number',
|
|
'tmdb',
|
|
'stream',
|
|
'free',
|
|
'doubleup',
|
|
'highspeed',
|
|
'featured',
|
|
'sticky',
|
|
'sd',
|
|
'internal',
|
|
'created_at',
|
|
'bumped_at',
|
|
'type_id',
|
|
'resolution_id',
|
|
'personal_release',
|
|
])
|
|
->selectRaw("CASE WHEN category_id IN (SELECT `id` from `categories` where `movie_meta` = 1) THEN 'movie' WHEN category_id IN (SELECT `id` from `categories` where `tv_meta` = 1) THEN 'tv' END as meta")
|
|
->where(
|
|
fn ($query) => $query
|
|
->where(
|
|
fn ($query) => $query
|
|
->whereRelation('category', 'movie_meta', '=', true)
|
|
->whereIntegerInRaw('tmdb', $movieIds)
|
|
)
|
|
->orWhere(
|
|
fn ($query) => $query
|
|
->whereRelation('category', 'tv_meta', '=', true)
|
|
->whereIntegerInRaw('tmdb', $tvIds)
|
|
)
|
|
)
|
|
->where($this->filters()->toSqlQueryBuilder())
|
|
->get()
|
|
->groupBy('meta')
|
|
->map(fn ($movieOrTv, $key) => match ($key) {
|
|
'movie' => $movieOrTv
|
|
->groupBy('tmdb')
|
|
->map(
|
|
function ($movie) {
|
|
$category_id = $movie->first()->category_id;
|
|
$movie = $this->groupByTypeAndSort($movie);
|
|
$movie->put('category_id', $category_id);
|
|
|
|
return $movie;
|
|
}
|
|
),
|
|
'tv' => $movieOrTv
|
|
->groupBy([
|
|
fn ($torrent) => $torrent->tmdb,
|
|
])
|
|
->map(
|
|
function ($tv) {
|
|
$category_id = $tv->first()->category_id;
|
|
$tv = $tv
|
|
->groupBy(fn ($torrent) => $torrent->season_number === 0 ? ($torrent->episode_number === 0 ? 'Complete Pack' : 'Specials') : 'Seasons')
|
|
->map(fn ($packOrSpecialOrSeasons, $key) => match ($key) {
|
|
'Complete Pack' => $this->groupByTypeAndSort($packOrSpecialOrSeasons),
|
|
'Specials' => $packOrSpecialOrSeasons
|
|
->groupBy(fn ($torrent) => 'Special '.$torrent->episode_number)
|
|
->sortKeysDesc(SORT_NATURAL)
|
|
->map(fn ($episode) => $this->groupByTypeAndSort($episode)),
|
|
'Seasons' => $packOrSpecialOrSeasons
|
|
->groupBy(fn ($torrent) => 'Season '.$torrent->season_number)
|
|
->sortKeysDesc(SORT_NATURAL)
|
|
->map(
|
|
fn ($season) => $season
|
|
->groupBy(fn ($torrent) => $torrent->episode_number === 0 ? 'Season Pack' : 'Episodes')
|
|
->map(fn ($packOrEpisodes, $key) => match ($key) {
|
|
'Season Pack' => $this->groupByTypeAndSort($packOrEpisodes),
|
|
'Episodes' => $packOrEpisodes
|
|
->groupBy(fn ($torrent) => 'Episode '.$torrent->episode_number)
|
|
->sortKeysDesc(SORT_NATURAL)
|
|
->map(fn ($episode) => $this->groupByTypeAndSort($episode)),
|
|
default => abort(500, 'Group found that isn\'t one of: Season Pack, Episodes.'),
|
|
})
|
|
),
|
|
default => abort(500, 'Group found that isn\'t one of: Complete Pack, Specials, Seasons'),
|
|
});
|
|
$tv->put('category_id', $category_id);
|
|
|
|
return $tv;
|
|
}
|
|
),
|
|
default => abort(500, 'Group found that isn\'t one of: movie, tv'),
|
|
});
|
|
|
|
$medias = $groups->through(function ($group) use ($torrents, $movies, $tv) {
|
|
switch ($group->meta) {
|
|
case 'movie':
|
|
if ($movies->has($group->tmdb)) {
|
|
$media = $movies[$group->tmdb];
|
|
$media->setAttribute('meta', 'movie');
|
|
$media->setRelation('torrents', $torrents['movie'][$group->tmdb] ?? collect());
|
|
$media->setAttribute('category_id', $media->torrents->pop());
|
|
} else {
|
|
$media = null;
|
|
}
|
|
|
|
break;
|
|
case 'tv':
|
|
if ($tv->has($group->tmdb)) {
|
|
$media = $tv[$group->tmdb];
|
|
$media->setAttribute('meta', 'tv');
|
|
$media->setRelation('torrents', $torrents['tv'][$group->tmdb] ?? collect());
|
|
$media->setAttribute('category_id', $media->torrents->pop());
|
|
} else {
|
|
$media = null;
|
|
}
|
|
|
|
break;
|
|
default:
|
|
$media = null;
|
|
}
|
|
|
|
return $media;
|
|
});
|
|
|
|
return $medias;
|
|
}
|
|
|
|
/**
|
|
* @param \Illuminate\Support\Collection<int, \App\Models\Torrent> $torrents
|
|
* @return \Illuminate\Support\Collection<string, \Illuminate\Support\Collection<int, \App\Models\Torrent>>
|
|
*/
|
|
private function groupByTypeAndSort($torrents): \Illuminate\Support\Collection
|
|
{
|
|
return $torrents
|
|
->sortBy('type.position')
|
|
->values()
|
|
->groupBy(fn ($torrent) => $torrent->type->name)
|
|
->map(
|
|
fn ($torrentsBytype) => $torrentsBytype
|
|
->sortBy([
|
|
['resolution.position', 'asc'],
|
|
['name', 'asc'],
|
|
])
|
|
->values()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator<Torrent>
|
|
*/
|
|
#[Computed]
|
|
final public function groupedPosters()
|
|
{
|
|
// Whitelist which columns are allowed to be ordered by
|
|
if (!\in_array($this->sortField, [
|
|
'bumped_at',
|
|
'times_completed',
|
|
])) {
|
|
$this->reset('sortField');
|
|
}
|
|
|
|
$groups = Torrent::query()
|
|
->select('tmdb')
|
|
->selectRaw('MAX(sticky) as sticky')
|
|
->selectRaw('MAX(bumped_at) as bumped_at')
|
|
->selectRaw('SUM(times_completed) as times_completed')
|
|
->selectRaw('MIN(category_id) as category_id')
|
|
->selectRaw("CASE WHEN category_id IN (SELECT `id` from `categories` where `movie_meta` = 1) THEN 'movie' WHEN category_id IN (SELECT `id` from `categories` where `tv_meta` = 1) THEN 'tv' END as meta")
|
|
->havingNotNull('meta')
|
|
->where('tmdb', '!=', 0)
|
|
->where($this->filters()->toSqlQueryBuilder())
|
|
->groupBy('tmdb', 'meta')
|
|
->latest('sticky')
|
|
->orderBy($this->sortField, $this->sortDirection)
|
|
->paginate(min($this->perPage, 100));
|
|
|
|
$movieIds = $groups->getCollection()->where('meta', '=', 'movie')->pluck('tmdb');
|
|
$tvIds = $groups->getCollection()->where('meta', '=', 'tv')->pluck('tmdb');
|
|
|
|
$movies = Movie::with('genres', 'directors')->whereIntegerInRaw('id', $movieIds)->get()->keyBy('id');
|
|
$tv = Tv::with('genres', 'creators')->whereIntegerInRaw('id', $tvIds)->get()->keyBy('id');
|
|
|
|
$groups = $groups->through(function ($group) use ($movies, $tv) {
|
|
switch ($group->meta) {
|
|
case 'movie':
|
|
$group->movie = $movies[$group->tmdb] ?? null;
|
|
|
|
break;
|
|
case 'tv':
|
|
$group->tv = $tv[$group->tmdb] ?? null;
|
|
|
|
break;
|
|
}
|
|
|
|
return $group;
|
|
});
|
|
|
|
return $groups;
|
|
}
|
|
|
|
final public function render(): \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Contracts\Foundation\Application
|
|
{
|
|
return view('livewire.torrent-search', [
|
|
'categories' => $this->categories,
|
|
'types' => $this->types,
|
|
'resolutions' => $this->resolutions,
|
|
'genres' => $this->genres,
|
|
'primaryLanguages' => $this->primaryLanguages,
|
|
'regions' => $this->regions,
|
|
'distributors' => $this->distributors,
|
|
'user' => auth()->user()->load('group'),
|
|
'personalFreeleech' => $this->personalFreeleech,
|
|
'torrents' => match ($this->view) {
|
|
'group' => $this->groupedTorrents,
|
|
'poster' => $this->groupedPosters,
|
|
default => $this->torrents,
|
|
},
|
|
]);
|
|
}
|
|
}
|