mirror of
https://github.com/NNTmux/newznab-tmux.git
synced 2026-08-28 22:01:33 +00:00
284 lines
8.8 KiB
PHP
284 lines
8.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\AdditionalProcessing;
|
|
|
|
use App\Models\ReleaseFile;
|
|
use App\Services\AdditionalProcessing\Config\ProcessingConfiguration;
|
|
use App\Services\AdditionalProcessing\State\ReleaseProcessingContext;
|
|
use App\Services\AdditionalProcessing\Enums\DownloadKind;
|
|
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
|
|
use Illuminate\Support\Facades\File;
|
|
|
|
/**
|
|
* Shared fallback logic for extracting known files back out of downloaded archives.
|
|
*/
|
|
class ReleaseFilesArchiveFallback
|
|
{
|
|
public function __construct(
|
|
private readonly ProcessingConfiguration $config,
|
|
private readonly ArchiveExtractionService $archiveService,
|
|
private readonly MediaExtractionService $mediaService,
|
|
private readonly UsenetDownloadService $downloadService,
|
|
private readonly ReleaseFileManager $releaseManager,
|
|
private readonly ConsoleOutputService $output
|
|
) {}
|
|
|
|
/**
|
|
* Try JPG/PNG files listed inside an already-downloaded archive summary.
|
|
*
|
|
* @param array<int, array<string, mixed>> $files
|
|
*/
|
|
public function processJpgFromArchiveFileList(
|
|
string $compressedData,
|
|
array $files,
|
|
ReleaseProcessingContext $context
|
|
): void {
|
|
$imageFiles = array_values(array_filter(
|
|
$files,
|
|
static fn (array $file): bool => preg_match('/\.(jpe?g|png)$/i', (string) ($file['name'] ?? '')) === 1
|
|
));
|
|
|
|
if ($imageFiles === []) {
|
|
return;
|
|
}
|
|
|
|
usort($imageFiles, static fn (array $a, array $b): int => ((int) ($b['size'] ?? 0)) <=> ((int) ($a['size'] ?? 0)));
|
|
|
|
$this->processExtractedCandidatesFromData(
|
|
$compressedData,
|
|
array_slice($imageFiles, 0, 3),
|
|
$context,
|
|
'extracted_',
|
|
function (string $tempPath, ReleaseProcessingContext $context): bool {
|
|
if (! $this->mediaService->isValidImage($tempPath)) {
|
|
return false;
|
|
}
|
|
|
|
if (! $this->mediaService->getJPGSample($tempPath, $context->release->guid)) {
|
|
return false;
|
|
}
|
|
|
|
$context->markFound('jpgSample');
|
|
$this->output->echoJpgSaved();
|
|
|
|
return true;
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Re-download archives and extract stored JPG/PNG candidates.
|
|
*/
|
|
public function processJpgFromReleaseFiles(ReleaseProcessingContext $context): void
|
|
{
|
|
$imageFiles = ReleaseFile::query()
|
|
->where('releases_id', $context->release->id)
|
|
->where(function ($query) {
|
|
$query->where('name', 'like', '%.jpg')
|
|
->orWhere('name', 'like', '%.jpeg')
|
|
->orWhere('name', 'like', '%.png');
|
|
})
|
|
->orderByDesc('size')
|
|
->limit(3)
|
|
->get();
|
|
|
|
if ($imageFiles->isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
$this->processStoredReleaseFiles(
|
|
$context,
|
|
$imageFiles,
|
|
'release_file_',
|
|
static fn (ReleaseProcessingContext $context): bool => $context->foundJPGSample,
|
|
function (string $tempPath, ReleaseProcessingContext $context): bool {
|
|
if (! $this->mediaService->isValidImage($tempPath)) {
|
|
return false;
|
|
}
|
|
|
|
if (! $this->mediaService->getJPGSample($tempPath, $context->release->guid)) {
|
|
return false;
|
|
}
|
|
|
|
$context->markFound('jpgSample');
|
|
$this->output->echoJpgSaved();
|
|
|
|
return true;
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Re-download archives and extract stored NFO-like candidates.
|
|
*/
|
|
public function processNfoFromReleaseFiles(ReleaseProcessingContext $context): void
|
|
{
|
|
$nfoFiles = ReleaseFile::query()
|
|
->where('releases_id', $context->release->id)
|
|
->nfoFiles()
|
|
->limit(3)
|
|
->get();
|
|
|
|
if ($nfoFiles->isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
$nntp = $this->downloadService->getNNTP();
|
|
|
|
$this->processStoredReleaseFiles(
|
|
$context,
|
|
$nfoFiles,
|
|
'release_file_',
|
|
static fn (ReleaseProcessingContext $context): bool => ! $context->releaseHasNoNFO,
|
|
function (string $tempPath, ReleaseProcessingContext $context) use ($nntp): bool {
|
|
if (! $this->releaseManager->processNfoFile($tempPath, $context, $nntp)) {
|
|
return false;
|
|
}
|
|
|
|
$this->output->echoNfoFound();
|
|
|
|
return true;
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param EloquentCollection<int, ReleaseFile> $candidates
|
|
* @param callable(ReleaseProcessingContext): bool $shouldStop
|
|
* @param callable(string, ReleaseProcessingContext): bool $handler
|
|
*/
|
|
private function processStoredReleaseFiles(
|
|
ReleaseProcessingContext $context,
|
|
EloquentCollection $candidates,
|
|
string $tempPrefix,
|
|
callable $shouldStop,
|
|
callable $handler
|
|
): void {
|
|
if ($candidates->isEmpty() || $context->nzbContents === []) {
|
|
return;
|
|
}
|
|
|
|
foreach ($context->nzbContents as $nzbFile) {
|
|
if ($shouldStop($context)) {
|
|
break;
|
|
}
|
|
|
|
$title = (string) ($nzbFile['title'] ?? '');
|
|
if (preg_match(
|
|
'/(\.(part0*1|rar|zip))(\s*\.rar)*($|[ ")]|-])|"[a-f0-9]{32}\.[1-9]\d{1,2}".*\(\d+\/\d{2,}\)$/i',
|
|
$title
|
|
) !== 1) {
|
|
continue;
|
|
}
|
|
|
|
$messageIDs = $this->extractInitialSegments($nzbFile['segments'] ?? []);
|
|
if ($messageIDs === []) {
|
|
continue;
|
|
}
|
|
|
|
$result = $this->downloadService->download(
|
|
DownloadKind::Compressed,
|
|
$messageIDs,
|
|
$context->releaseGroupName,
|
|
$context->release->id,
|
|
$title
|
|
);
|
|
|
|
if ($result['groupUnavailable']) {
|
|
$context->groupUnavailable = true;
|
|
$this->output->echoGroupUnavailable();
|
|
|
|
return;
|
|
}
|
|
|
|
if (! $result['success'] || empty($result['data'])) {
|
|
continue;
|
|
}
|
|
|
|
foreach ($candidates as $candidate) {
|
|
$fileData = $this->archiveService->extractSpecificFile(
|
|
$result['data'],
|
|
$candidate->name,
|
|
$context->tmpPath
|
|
);
|
|
|
|
if ($fileData === null || $fileData === '') {
|
|
continue;
|
|
}
|
|
|
|
if ($this->withExtractedTempFile($candidate->name, $fileData, $context, $tempPrefix, $handler)) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param array<int, array<string, mixed>> $candidates
|
|
* @param callable(string, ReleaseProcessingContext): bool $handler
|
|
*/
|
|
private function processExtractedCandidatesFromData(
|
|
string $compressedData,
|
|
array $candidates,
|
|
ReleaseProcessingContext $context,
|
|
string $tempPrefix,
|
|
callable $handler
|
|
): void {
|
|
foreach ($candidates as $candidate) {
|
|
$filename = (string) ($candidate['name'] ?? '');
|
|
if ($filename === '') {
|
|
continue;
|
|
}
|
|
|
|
$fileData = $this->archiveService->extractSpecificFile($compressedData, $filename, $context->tmpPath);
|
|
if ($fileData === null || $fileData === '') {
|
|
continue;
|
|
}
|
|
|
|
if ($this->withExtractedTempFile($filename, $fileData, $context, $tempPrefix, $handler)) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param callable(string, ReleaseProcessingContext): bool $handler
|
|
*/
|
|
private function withExtractedTempFile(
|
|
string $filename,
|
|
string $fileData,
|
|
ReleaseProcessingContext $context,
|
|
string $tempPrefix,
|
|
callable $handler
|
|
): bool {
|
|
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
|
|
$suffix = $extension === '' ? '' : '.'.$extension;
|
|
$tempPath = $context->tmpPath.$tempPrefix.uniqid('', true).$suffix;
|
|
File::put($tempPath, $fileData);
|
|
|
|
try {
|
|
return $handler($tempPath, $context);
|
|
} finally {
|
|
File::delete($tempPath);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param array<int, mixed> $segments
|
|
* @return list<string>
|
|
*/
|
|
private function extractInitialSegments(array $segments): array
|
|
{
|
|
$messageIDs = [];
|
|
$segmentCount = min(count($segments), $this->config->maximumRarSegments);
|
|
|
|
for ($index = 0; $index < $segmentCount; $index++) {
|
|
$messageIDs[] = (string) $segments[$index];
|
|
}
|
|
|
|
return $messageIDs;
|
|
}
|
|
}
|