Update bootstrapping and create Yenc service and facade

This commit is contained in:
DariusIII
2025-12-29 13:53:22 +01:00
parent c8b0c4b744
commit 3f81661278
59 changed files with 1272 additions and 2722 deletions
+10 -3
View File
@@ -224,8 +224,15 @@ class TmuxMonitor extends Command
return; return;
} }
// These tasks would be implemented in TmuxTaskRunner // Run utility tasks (window 1)
// For now, we're keeping the structure similar to the original $this->taskRunner->runPaneTask('fixnames', [], $runVar);
// but using the modern service architecture $this->taskRunner->runPaneTask('removecrap', [], $runVar);
// Run post-processing tasks (window 2)
$this->taskRunner->runPaneTask('ppadditional', [], $runVar);
$this->taskRunner->runPaneTask('tv', [], $runVar);
$this->taskRunner->runPaneTask('movies', [], $runVar);
$this->taskRunner->runPaneTask('amazon', [], $runVar);
$this->taskRunner->runPaneTask('xxx', [], $runVar);
} }
} }
-232
View File
@@ -1,232 +0,0 @@
<?php
/**
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program (see LICENSE.txt in the base directory. If
* not, see:.
*
* @link <http://www.gnu.org/licenses/>.
*
* @author niel
* @copyright 2016 nZEDb
*/
namespace App\Extensions\util;
/**
* Class PhpYenc.
* Optimized yEnc encoder/decoder implementation.
*/
class PhpYenc
{
/**
* Pre-computed decode translation table for non-escaped characters.
* Maps yEnc encoded byte to decoded byte: (byte - 42) % 256
* @var array<string, string>|null
*/
private static ?array $decodeTable = null;
/**
* Pre-computed decode translation table for escaped characters.
* Maps yEnc escaped byte to decoded byte: ((byte - 64) - 42) % 256
* @var array<string, string>|null
*/
private static ?array $escapeDecodeTable = null;
/**
* Initialize the decode translation tables (lazy initialization).
*/
private static function initDecodeTables(): void
{
if (self::$decodeTable !== null) {
return;
}
self::$decodeTable = [];
self::$escapeDecodeTable = [];
for ($i = 0; $i < 256; $i++) {
$char = \chr($i);
// Standard decode: (byte - 42) % 256
self::$decodeTable[$char] = \chr(($i - 42 + 256) % 256);
// Escape decode: ((byte - 64) - 42) % 256
self::$escapeDecodeTable[$char] = \chr((($i - 64 - 42) + 512) % 256);
}
}
public static function decode(&$text, bool $ignore = false): bool|string
{
$crc = '';
// Extract the yEnc string itself.
if (preg_match(
'/=ybegin.*size=(\d+).*\r\n(.*)(?:\r\n)?=yend.*size=(\d+)(.*)/ims',
$text,
$encoded
)) {
if (preg_match('/crc32=([0-9a-fA-F]+)/i', $encoded[4], $trailer)) {
$crc = trim($trailer[1]);
}
$headerSize = (int) $encoded[1];
$trailerSize = (int) $encoded[3];
$encoded = $encoded[2];
} else {
return false;
}
// Make sure the header and trailer file sizes match up.
if ($headerSize !== $trailerSize) {
$message = 'Header and trailer file sizes do not match. This is a violation of the yEnc specification.';
throw new \RuntimeException($message);
}
// Remove line breaks from the string (use str_replace with array for speed).
$encoded = str_replace(["\r\n", "\r", "\n"], '', $encoded);
// Fast decode using optimized method
$decoded = self::fastDecode($encoded);
// Make sure the decoded file size is the same as the size specified in the header.
$decodedLength = \strlen($decoded);
if ($decodedLength !== $headerSize) {
$message = 'Header file size ('.$headerSize.') and actual file size ('.$decodedLength.') do not match. The file is probably corrupt.';
throw new \RuntimeException($message);
}
// Check the CRC value
if ($crc !== '' && (strcasecmp($crc, sprintf('%X', crc32($decoded))) !== 0)) {
$message = 'CRC32 checksums do not match. The file is probably corrupt.';
throw new \RuntimeException($message);
}
return $decoded;
}
/**
* Fast yEnc decode using strtr and optimized escape handling.
*
* @param string $encoded The encoded string (without headers/line breaks)
* @return string The decoded string
*/
private static function fastDecode(string $encoded): string
{
self::initDecodeTables();
/** @var array<string, string> $decodeTable */
$decodeTable = self::$decodeTable;
/** @var array<string, string> $escapeDecodeTable */
$escapeDecodeTable = self::$escapeDecodeTable;
// Check if there are any escape sequences
$escapePos = strpos($encoded, '=');
if ($escapePos === false) {
// No escape sequences - use fast strtr translation
return strtr($encoded, $decodeTable);
}
// Handle escape sequences
// First, process escape sequences by splitting on '='
$parts = explode('=', $encoded);
$result = strtr($parts[0], $decodeTable);
$count = \count($parts);
for ($i = 1; $i < $count; $i++) {
$part = $parts[$i];
if ($part === '') {
continue;
}
// First character after '=' is the escaped character
$result .= $escapeDecodeTable[$part[0]];
// Rest of the part is normal encoded data
if (isset($part[1])) {
$result .= strtr(substr($part, 1), $decodeTable);
}
}
return $result;
}
/**
* Decode a string of text encoded with yEnc. Ignores all errors.
*
* @param string $text The encoded text to decode.
* @return string The decoded yEnc string, or the input string, if it's not yEnc.
*/
public static function decodeIgnore(string &$text): string
{
if (preg_match('/^(=yBegin.*=yEnd[^$]*)$/ims', $text, $input)) {
// Extract the encoded data, removing headers and line breaks
$input = preg_replace('/(^=yBegin.*\r\n)/im', '', $input[1], 1);
$input = preg_replace('/(^=yPart.*\r\n)/im', '', $input, 1);
$input = preg_replace('/(^=yEnd.*)/im', '', $input, 1);
$input = str_replace(["\r\n", "\r", "\n"], '', trim($input));
// Use the fast decode method
$text = self::fastDecode($input);
}
return $text;
}
public static function enabled(): bool
{
return true;
}
public static function encode($data, $filename, int $lineLength = 128, bool $crc32 = true): string
{
// yEnc 1.3 draft doesn't allow line lengths of more than 254 bytes.
if ($lineLength > 254) {
$lineLength = 254;
}
if ($lineLength < 1) {
$message = $lineLength.' is not a valid line length.';
throw new \RuntimeException($message);
}
$encoded = '';
$stringLength = \strlen($data);
// Encode each character of the string one at a time.
foreach ($data as $i => $iValue) {
$value = ((\ord($iValue) + 42) % 256);
// Escape NULL, TAB, LF, CR, space, . and = characters.
$encoded .= match ($value) {
0, 10, 13, 61 => ('='.\chr(($value + 64) % 256)),
default => \chr($value),
};
}
$encoded =
'=ybegin line='.
$lineLength.
' size='.
$stringLength.
' name='.
trim($filename).
"\r\n".
trim(chunk_split($encoded, $lineLength)).
"\r\n=yend size=".
$stringLength;
// Add a CRC32 checksum if desired.
if ($crc32 === true) {
$encoded .= ' crc32='.strtolower(sprintf('%X', crc32($data)));
}
return $encoded;
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
/**
* Yenc Facade
*
* @method static string|false decode(string &$text, bool $ignore = false)
* @method static string decodeIgnore(string &$text)
* @method static bool enabled()
* @method static bool isYencEncoded(string $text)
* @method static string encode(string $data, string $filename, int $lineLength = 128, bool $includeCrc32 = true)
* @method static array|null extractMetadata(string $text)
*
* @see \App\Services\YencService
*/
class Yenc extends Facade
{
/**
* Get the registered name of the component.
*/
protected static function getFacadeAccessor(): string
{
return \App\Services\YencService::class;
}
}
+10 -4
View File
@@ -2,9 +2,9 @@
namespace App\Services\NNTP; namespace App\Services\NNTP;
use App\Extensions\util\PhpYenc;
use App\Models\Settings; use App\Models\Settings;
use App\Services\Tmux\Tmux; use App\Services\Tmux\Tmux;
use App\Services\YencService;
/* /*
* Response codes not defined in Net_NNTP * Response codes not defined in Net_NNTP
@@ -134,15 +134,21 @@ class NNTPService extends \Net_NNTP_Client
*/ */
protected string $_yEncTempOutput; protected string $_yEncTempOutput;
/**
* YEnc encoding/decoding service.
*/
protected YencService $_yencService;
/** /**
* Create a new NNTP service instance. * Create a new NNTP service instance.
*/ */
public function __construct(?Tmux $tmux = null) public function __construct(?Tmux $tmux = null, ?YencService $yencService = null)
{ {
parent::__construct(); parent::__construct();
$this->_echo = config('nntmux.echocli'); $this->_echo = config('nntmux.echocli');
$this->_tmux = $tmux ?? new Tmux; $this->_tmux = $tmux ?? new Tmux;
$this->_yencService = $yencService ?? app(YencService::class);
$this->_nntpRetries = Settings::settingValue('nntpretries') !== '' ? (int) Settings::settingValue('nntpretries') : 0 + 1; $this->_nntpRetries = Settings::settingValue('nntpretries') !== '' ? (int) Settings::settingValue('nntpretries') : 0 + 1;
$this->initializeConfig(); $this->initializeConfig();
@@ -773,7 +779,7 @@ class NNTPService extends \Net_NNTP_Client
if ($line === ".\r\n") { if ($line === ".\r\n") {
$body = implode('', $bodyParts); $body = implode('', $bodyParts);
return PhpYenc::decodeIgnore($body); return $this->_yencService->decodeIgnore($body);
} }
if ($line[0] === '.' && isset($line[1]) && $line[1] === '.') { if ($line[0] === '.' && isset($line[1]) && $line[1] === '.') {
$line = substr($line, 1); $line = substr($line, 1);
@@ -1086,7 +1092,7 @@ class NNTPService extends \Net_NNTP_Client
// Join all parts and attempt to yEnc decode // Join all parts and attempt to yEnc decode
$body = implode('', $bodyParts); $body = implode('', $bodyParts);
return PhpYenc::decodeIgnore($body); return $this->_yencService->decodeIgnore($body);
} }
// Check for line that starts with double period, remove one. // Check for line that starts with double period, remove one.
@@ -1,3 +1,4 @@
#!/usr/bin/env php
<?php <?php
/** /**
@@ -10,10 +11,8 @@
* New location: app/Services/Tmux/Scripts/groupfixrelnames.php * New location: app/Services/Tmux/Scripts/groupfixrelnames.php
*/ */
require_once __DIR__.'/../../../../bootstrap/autoload.php';
if (! isset($argv[1])) { if (! isset($argv[1])) {
cli()->error('This script is not intended to be run manually, it is called from Multiprocessing.'); fwrite(STDERR, "This script is not intended to be run manually, it is called from Multiprocessing.\n");
exit(1); exit(1);
} }
@@ -21,12 +20,12 @@ if (! isset($argv[1])) {
[$type, $guidChar, $maxPerRun, $thread] = explode(' ', $argv[1]); [$type, $guidChar, $maxPerRun, $thread] = explode(' ', $argv[1]);
// Build artisan command based on type // Build artisan command based on type
$artisan = base_path('artisan'); $artisan = dirname(__DIR__, 4).'/artisan';
switch ($type) { switch ($type) {
case 'standard': case 'standard':
if ($guidChar === null || $maxPerRun === null || ! is_numeric($maxPerRun)) { if ($guidChar === null || $maxPerRun === null || ! is_numeric($maxPerRun)) {
cli()->error('Invalid arguments for standard type'); fwrite(STDERR, "Invalid arguments for standard type\n");
exit(1); exit(1);
} }
@@ -35,7 +34,7 @@ switch ($type) {
case 'predbft': case 'predbft':
if (! isset($maxPerRun) || ! is_numeric($maxPerRun) || ! isset($thread) || ! is_numeric($thread)) { if (! isset($maxPerRun) || ! is_numeric($maxPerRun) || ! isset($thread) || ! is_numeric($thread)) {
cli()->error('Invalid arguments for predbft type'); fwrite(STDERR, "Invalid arguments for predbft type\n");
exit(1); exit(1);
} }
@@ -43,7 +42,7 @@ switch ($type) {
break; break;
default: default:
cli()->error("Unknown type: {$type}"); fwrite(STDERR, "Unknown type: {$type}\n");
exit(1); exit(1);
} }
+7 -77
View File
@@ -7,84 +7,14 @@
* This script runs in tmux pane 0.0 and continuously monitors the system, * This script runs in tmux pane 0.0 and continuously monitors the system,
* collecting statistics and spawning tasks in other panes. * collecting statistics and spawning tasks in other panes.
* *
* This is the modernized version that uses the new service architecture. * This is a simple wrapper that calls the tmux:monitor artisan command.
*/ */
// Bootstrap Laravel $artisan = dirname(__DIR__, 4).'/artisan';
// From: app/Services/Tmux/Scripts/monitor.php
// To: bootstrap/autoload.php (4 levels up)
require_once __DIR__.'/../../../../bootstrap/autoload.php';
use App\Models\Settings; // Pass any arguments to the artisan command
use App\Services\Tmux\TmuxMonitorService; $args = array_slice($argv, 1);
use App\Services\Tmux\TmuxTaskRunner; $argString = implode(' ', array_map('escapeshellarg', $args));
use App\Services\Tmux\TmuxOutput;
try { passthru("php {$artisan} tmux:monitor {$argString}", $exitCode);
// Get session name exit($exitCode);
$sessionName = Settings::settingValue('tmux_session') ?? 'nntmux';
// Initialize services
$monitor = new TmuxMonitorService;
$taskRunner = new TmuxTaskRunner($sessionName);
$output = new TmuxOutput;
// Initialize monitor
$runVar = $monitor->initializeMonitor();
// Spawn IRC scraper immediately
try {
$taskRunner->runIRCScraper(['constants' => $runVar['constants']]);
} catch (Exception $e) {
logger()->error('Failed to spawn IRC scraper: '.$e->getMessage());
}
cli()->header('Tmux Monitor Started');
echo "Session: {$sessionName}\n";
echo "Press Ctrl+C to stop (or set exit flag in settings)\n\n";
// Main monitoring loop
while ($monitor->shouldContinue()) {
// Collect statistics
$runVar = $monitor->collectStatistics();
// Update display
$output->updateMonitorPane($runVar);
// Run pane tasks if tmux is running
if ((int) ($runVar['settings']['is_running'] ?? 0) === 1) {
$sequential = (int) ($runVar['constants']['sequential'] ?? 0);
// Determine which panes to run based on sequential mode
$panesToRun = match ($sequential) {
0 => ['main', 'fixnames', 'removecrap', 'ppadditional', 'tv', 'movies', 'amazon', 'xxx'],
1 => ['main', 'fixnames', 'removecrap', 'ppadditional', 'tv', 'movies', 'amazon', 'xxx'],
2 => ['main', 'fixnames', 'amazon'],
default => [],
};
// Run each pane task using TmuxTaskRunner
foreach ($panesToRun as $paneName) {
try {
$taskRunner->runPaneTask($paneName, [], $runVar);
} catch (Exception $e) {
logger()->error("Failed to run pane {$paneName}: ".$e->getMessage());
}
}
}
// Increment iteration and sleep
$monitor->incrementIteration();
sleep(10);
}
cli()->info('Monitor stopped by exit flag');
} catch (Exception $e) {
cli()->error('Monitor failed: '.$e->getMessage());
logger()->error('Tmux monitor error', [
'message' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
exit(1);
}
@@ -1,3 +1,4 @@
#!/usr/bin/env php
<?php <?php
/** /**
@@ -10,10 +11,8 @@
* New location: app/Services/Tmux/Scripts/postprocess_pre.php * New location: app/Services/Tmux/Scripts/postprocess_pre.php
*/ */
require_once __DIR__.'/../../../../bootstrap/autoload.php';
$limit = isset($argv[1]) && is_numeric($argv[1]) ? $argv[1] : ''; $limit = isset($argv[1]) && is_numeric($argv[1]) ? $argv[1] : '';
$artisan = base_path('artisan'); $artisan = dirname(__DIR__, 4).'/artisan';
$command = "php {$artisan} predb:check".($limit ? " {$limit}" : ''); $command = "php {$artisan} predb:check".($limit ? " {$limit}" : '');
+3 -5
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env php
<?php <?php
/** /**
@@ -10,10 +11,7 @@
* New location: app/Services/Tmux/Scripts/update_groups.php * New location: app/Services/Tmux/Scripts/update_groups.php
*/ */
require_once __DIR__.'/../../../../bootstrap/autoload.php'; $artisan = dirname(__DIR__, 4).'/artisan';
$artisan = base_path('artisan'); passthru("php {$artisan} groups:update", $exitCode);
$command = "php {$artisan} groups:update";
passthru($command, $exitCode);
exit($exitCode); exit($exitCode);
+293
View File
@@ -0,0 +1,293 @@
<?php
namespace App\Services;
use RuntimeException;
/**
* YEnc encoding/decoding service.
*
* Provides optimized yEnc encoder/decoder implementation for Usenet article processing.
*/
class YencService
{
/**
* Pre-computed decode translation table for non-escaped characters.
* Maps yEnc encoded byte to decoded byte: (byte - 42) % 256
*
* @var array<string, string>|null
*/
private ?array $decodeTable = null;
/**
* Pre-computed decode translation table for escaped characters.
* Maps yEnc escaped byte to decoded byte: ((byte - 64) - 42) % 256
*
* @var array<string, string>|null
*/
private ?array $escapeDecodeTable = null;
/**
* Initialize the decode translation tables (lazy initialization).
*/
private function initDecodeTables(): void
{
if ($this->decodeTable !== null) {
return;
}
$this->decodeTable = [];
$this->escapeDecodeTable = [];
for ($i = 0; $i < 256; $i++) {
$char = \chr($i);
// Standard decode: (byte - 42) % 256
$this->decodeTable[$char] = \chr(($i - 42 + 256) % 256);
// Escape decode: ((byte - 64) - 42) % 256
$this->escapeDecodeTable[$char] = \chr((($i - 64 - 42) + 512) % 256);
}
}
/**
* Decode a yEnc encoded string.
*
* @param string $text The yEnc encoded text
* @param bool $ignore Whether to ignore errors (unused, kept for compatibility)
* @return string|false The decoded string, or false if not valid yEnc
*
* @throws RuntimeException If the data is corrupt or invalid
*/
public function decode(string &$text, bool $ignore = false): string|false
{
$crc = '';
// Extract the yEnc string itself.
// The pattern needs to handle binary content between =ybegin and =yend
if (preg_match(
'/=ybegin[^\r\n]*size=(\d+)[^\r\n]*\r\n([\s\S]*?)\r\n=yend[^\r\n]*size=(\d+)([^\r\n]*)/i',
$text,
$encoded
)) {
if (preg_match('/crc32=([0-9a-fA-F]+)/i', $encoded[4], $trailer)) {
$crc = trim($trailer[1]);
}
$headerSize = (int) $encoded[1];
$trailerSize = (int) $encoded[3];
$encoded = $encoded[2];
} else {
return false;
}
// Make sure the header and trailer file sizes match up.
if ($headerSize !== $trailerSize) {
throw new RuntimeException(
'Header and trailer file sizes do not match. This is a violation of the yEnc specification.'
);
}
// Remove line breaks from the string (use str_replace with array for speed).
$encoded = str_replace(["\r\n", "\r", "\n"], '', $encoded);
// Fast decode using optimized method
$decoded = $this->fastDecode($encoded);
// Make sure the decoded file size is the same as the size specified in the header.
$decodedLength = \strlen($decoded);
if ($decodedLength !== $headerSize) {
throw new RuntimeException(
"Header file size ({$headerSize}) and actual file size ({$decodedLength}) do not match. The file is probably corrupt."
);
}
// Check the CRC value
if ($crc !== '' && strcasecmp($crc, sprintf('%X', crc32($decoded))) !== 0) {
throw new RuntimeException('CRC32 checksums do not match. The file is probably corrupt.');
}
return $decoded;
}
/**
* Fast yEnc decode using strtr and optimized escape handling.
*
* @param string $encoded The encoded string (without headers/line breaks)
* @return string The decoded string
*/
private function fastDecode(string $encoded): string
{
$this->initDecodeTables();
/** @var array<string, string> $decodeTable */
$decodeTable = $this->decodeTable;
/** @var array<string, string> $escapeDecodeTable */
$escapeDecodeTable = $this->escapeDecodeTable;
// Check if there are any escape sequences
$escapePos = strpos($encoded, '=');
if ($escapePos === false) {
// No escape sequences - use fast strtr translation
return strtr($encoded, $decodeTable);
}
// Handle escape sequences
// First, process escape sequences by splitting on '='
$parts = explode('=', $encoded);
$result = strtr($parts[0], $decodeTable);
$count = \count($parts);
for ($i = 1; $i < $count; $i++) {
$part = $parts[$i];
if ($part === '') {
continue;
}
// First character after '=' is the escaped character
$result .= $escapeDecodeTable[$part[0]];
// Rest of the part is normal encoded data
if (isset($part[1])) {
$result .= strtr(substr($part, 1), $decodeTable);
}
}
return $result;
}
/**
* Decode a string of text encoded with yEnc. Ignores all errors.
*
* @param string $text The encoded text to decode.
* @return string The decoded yEnc string, or the input string if it's not yEnc.
*/
public function decodeIgnore(string &$text): string
{
if (preg_match('/^(=yBegin.*=yEnd[^$]*)$/ims', $text, $input)) {
// Extract the encoded data, removing headers and line breaks
$input = preg_replace('/(^=yBegin.*\r\n)/im', '', $input[1], 1);
$input = preg_replace('/(^=yPart.*\r\n)/im', '', $input, 1);
$input = preg_replace('/(^=yEnd.*)/im', '', $input, 1);
$input = str_replace(["\r\n", "\r", "\n"], '', trim($input));
// Use the fast decode method
$text = $this->fastDecode($input);
}
return $text;
}
/**
* Check if yEnc encoding/decoding is enabled.
*
* @return bool Always returns true (PHP implementation is always available)
*/
public function enabled(): bool
{
return true;
}
/**
* Check if the given text appears to be yEnc encoded.
*
* @param string $text The text to check
* @return bool True if the text appears to be yEnc encoded
*/
public function isYencEncoded(string $text): bool
{
return (bool) preg_match('/^=yBegin.*=yEnd/ims', $text);
}
/**
* Encode data using yEnc encoding.
*
* @param string $data The data to encode
* @param string $filename The filename to include in the header
* @param int $lineLength Maximum line length (1-254, default 128)
* @param bool $includeCrc32 Whether to include CRC32 checksum
* @return string The yEnc encoded string
*
* @throws RuntimeException If line length is invalid
*/
public function encode(string $data, string $filename, int $lineLength = 128, bool $includeCrc32 = true): string
{
// yEnc 1.3 draft doesn't allow line lengths of more than 254 bytes.
if ($lineLength > 254) {
$lineLength = 254;
}
if ($lineLength < 1) {
throw new RuntimeException("{$lineLength} is not a valid line length.");
}
$encoded = '';
$stringLength = \strlen($data);
// Encode each character of the string one at a time.
for ($i = 0; $i < $stringLength; $i++) {
$value = ((\ord($data[$i]) + 42) % 256);
// Escape NULL, TAB, LF, CR, space, . and = characters.
$encoded .= match ($value) {
0, 9, 10, 13, 32, 46, 61 => '='.\chr(($value + 64) % 256),
default => \chr($value),
};
}
$result = '=ybegin line='.$lineLength.
' size='.$stringLength.
' name='.trim($filename).
"\r\n".
trim(chunk_split($encoded, $lineLength)).
"\r\n=yend size=".$stringLength;
// Add a CRC32 checksum if desired.
if ($includeCrc32) {
$result .= ' crc32='.strtolower(sprintf('%X', crc32($data)));
}
return $result;
}
/**
* Extract metadata from a yEnc encoded string without decoding.
*
* @param string $text The yEnc encoded text
* @return array{name: string|null, size: int|null, line: int|null, crc32: string|null}|null
*/
public function extractMetadata(string $text): ?array
{
if (! preg_match('/=ybegin\s+(.+?)\r\n/i', $text, $headerMatch)) {
return null;
}
$header = $headerMatch[1];
$metadata = [
'name' => null,
'size' => null,
'line' => null,
'crc32' => null,
];
// Extract name
if (preg_match('/name=(.+?)(?:\s|$)/i', $header, $match)) {
$metadata['name'] = trim($match[1]);
}
// Extract size
if (preg_match('/size=(\d+)/i', $header, $match)) {
$metadata['size'] = (int) $match[1];
}
// Extract line length
if (preg_match('/line=(\d+)/i', $header, $match)) {
$metadata['line'] = (int) $match[1];
}
// Extract CRC32 from trailer
if (preg_match('/=yend.*crc32=([0-9a-fA-F]+)/i', $text, $match)) {
$metadata['crc32'] = strtoupper($match[1]);
}
return $metadata;
}
}
-17
View File
@@ -1,17 +0,0 @@
<?php
use Dotenv\Dotenv;
use Dotenv\Repository\RepositoryBuilder;
use Illuminate\Contracts\Console\Kernel;
require_once dirname(__DIR__).DIRECTORY_SEPARATOR.'vendor'.DIRECTORY_SEPARATOR.'autoload.php';
require __DIR__.DIRECTORY_SEPARATOR.'app.php';
require_once dirname(__DIR__).DIRECTORY_SEPARATOR.'app/Extensions/util/PhpYenc.php';
$repository = RepositoryBuilder::createWithDefaultAdapters()->make();
$dotenv = Dotenv::create($repository, dirname(__DIR__, 1), null)->load();
/** @var Kernel $kernel */
$kernel = app(Kernel::class);
$kernel->bootstrap();
+1
View File
@@ -24,6 +24,7 @@ return [
'RedisManager' => Illuminate\Support\Facades\Redis::class, 'RedisManager' => Illuminate\Support\Facades\Redis::class,
'UserVerification' => Jrean\UserVerification\Facades\UserVerification::class, 'UserVerification' => Jrean\UserVerification\Facades\UserVerification::class,
'Gravatar' => Creativeorange\Gravatar\Facades\Gravatar::class, 'Gravatar' => Creativeorange\Gravatar\Facades\Gravatar::class,
'Yenc' => App\Facades\Yenc::class,
])->toArray(), ])->toArray(),
]; ];
-51
View File
@@ -1,51 +0,0 @@
<?php
/**
* This script will convert releases table imdbid column
* from zerofilled int to varchar(15).
*/
use App\Models\MovieInfo;
use App\Models\Release;
use Illuminate\Support\Facades\DB;
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
$sql = Release::query()->whereNotNull('imdbid')->where('imdbid', '<>', '0000000')->get(['imdbid', 'id']);
DB::statement('
DROP TABLE IF EXISTS movie_temp;
CREATE TABLE movie_temp (
releases_id INT(11),
imdbid VARCHAR(15)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET utf8
COLLATE utf8_unicode_ci;
');
$count = $sql->count();
echo 'Copying '.$count.' imdbid values'.PHP_EOL;
foreach ($sql as $movie) {
DB::table('movie_temp')->insert(['releases_id' => $movie['id'], 'imdbid' => $movie['imdbid']]);
echo '.';
}
echo PHP_EOL.'Finished copying '.$sql->count().' imdbid values'.PHP_EOL;
echo 'Updating releases table with new values'.PHP_EOL;
foreach (DB::table('movie_temp')->get() as $imdbid) {
$movieinfo = MovieInfo::query()->where('imdbid', $imdbid->imdbid)->first(['id']);
Release::query()->where('id', $imdbid->releases_id)->update(['movieinfo_id' => $movieinfo['id']]);
echo '.';
}
echo 'Finished inserting new values into releases table'.PHP_EOL;
$check = Release::query()->whereNotNull('movieinfo_id')->count('id');
if ($check === $count) {
DB::statement('DROP TABLE movie_temp');
}
-52
View File
@@ -1,52 +0,0 @@
<?php
/**
* This script will convert releases table imdbid column
* from zerofilled int to varchar(15).
*/
use App\Models\Release;
use Illuminate\Support\Facades\DB;
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
$sql = Release::query()->whereNotNull('imdbid')->where('imdbid', '<>', '0000000')->get(['imdbid', 'id']);
DB::unprepared('
DROP TABLE IF EXISTS movie_temp;
CREATE TABLE movie_temp (
releases_id INT(11),
imdbid VARCHAR(15)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET utf8
COLLATE utf8_unicode_ci;
');
DB::commit();
$count = $sql->count();
echo 'Copying '.$count.' imdbid values'.PHP_EOL;
foreach ($sql as $movie) {
DB::table('movie_temp')->insert(['releases_id' => $movie['id'], 'imdbid' => str_pad($movie['imdbid'], 7, '0', STR_PAD_LEFT)]);
echo '.';
}
echo PHP_EOL.'Finished copying '.$sql->count().' imdbid values'.PHP_EOL;
DB::statement('ALTER TABLE releases DROP imdbid');
DB::statement('ALTER TABLE releases ADD imdbid VARCHAR(15) DEFAULT NULL');
echo 'Updating releases table with new values'.PHP_EOL;
foreach (DB::table('movie_temp')->get() as $imdbid) {
DB::table('releases')->where('id', $imdbid->releases_id)->update(['imdbid' => $imdbid->imdbid]);
echo '.';
}
echo 'Finished inserting new values into releases table'.PHP_EOL;
$check = Release::query()->whereNotNull('imdbid')->where('imdbid', '<>', '0000000')->count('id');
if ($check === $count) {
DB::statement('DROP TABLE movie_temp');
}
-25
View File
@@ -1,25 +0,0 @@
<?php
use App\Models\Release;
use App\Models\ReleaseFile;
use Blacklight\Releases;
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
$passFiles = ReleaseFile::query()->where('passworded', '=', 1)->select(['releases_id'])->groupBy('releases_id')->get();
$count1 = $count2 = 0;
if ($passFiles->isNotEmpty()) {
$count1 = $passFiles->count();
if ($count1 > 0) {
foreach ($passFiles as $passFile) {
$release = Release::query()->where('id', $passFile->releases_id)->where('passwordstatus', '=', 0)->update(['passwordstatus' => Releases::PASSWD_RAR]);
if ($release !== 0) {
$count2++;
}
echo '.';
}
}
}
cli()->info($count2.' releases marked as passworded');
-18
View File
@@ -1,18 +0,0 @@
<?php
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
$tmux = Illuminate\Support\Facades\DB::table('tmux')->get();
foreach ($tmux as $item) {
echo 'Inserting '.$item->setting.' into settings table'.PHP_EOL;
App\Models\Settings::insertOrIgnore(
[
'section' => 'site',
'subsection' => 'tmux',
'name' => $item->setting,
'value' => $item->value,
'setting' => $item->setting,
]
);
}
-323
View File
@@ -1,323 +0,0 @@
<?php
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use App\Models\Category;
use Illuminate\Support\Facades\DB;
$ran = false;
if (isset($argv[1]) && $argv[1] === 'all' && isset($argv[2]) && $argv[2] === 'true') {
$ran = true;
$where = '';
if (isset($argv[3]) && $argv[3] === 'truncate') {
echo 'Truncating tables';
DB::select('TRUNCATE TABLE consoleinfo');
DB::select('TRUNCATE TABLE gamesinfo');
DB::select('TRUNCATE TABLE movieinfo');
DB::select('TRUNCATE TABLE video_data');
DB::select('TRUNCATE TABLE musicinfo');
DB::select('TRUNCATE TABLE bookinfo');
DB::select('TRUNCATE TABLE release_nfos');
DB::select('TRUNCATE TABLE xxxinfo');
DB::select('TRUNCATE TABLE videos');
DB::select('TRUNCATE TABLE videos_aliases');
DB::select('TRUNCATE TABLE tv_info');
DB::select('TRUNCATE TABLE tv_episodes');
DB::select('TRUNCATE TABLE anidb_info');
}
cli()->header('Resetting all postprocessing');
$qry = DB::select('SELECT id FROM releases');
$affected = 0;
$total = \count($qry);
foreach ($qry as $releases) {
DB::update(
sprintf(
'
UPDATE releases
SET consoleinfo_id = NULL, gamesinfo_id = 0, imdbid = NULL, musicinfo_id = NULL,
bookinfo_id = NULL, videos_id = 0, tv_episodes_id = 0, xxxinfo_id = 0, passwordstatus = -1, haspreview = -1,
jpgstatus = 0, videostatus = 0, audiostatus = 0, nfostatus = -1
WHERE id = %d',
$releases->id
)
);
cli()->overWritePrimary('Resetting Releases: '.cli()->percentString(++$affected, $total));
}
}
if (isset($argv[1]) && ($argv[1] === 'consoles' || $argv[1] === 'all')) {
$ran = true;
if (isset($argv[3]) && $argv[3] === 'truncate') {
DB::select('TRUNCATE TABLE consoleinfo');
}
if (isset($argv[2]) && $argv[2] === 'true') {
cli()->header('Resetting all Console postprocessing');
$where = ' WHERE consoleinfo_id IS NOT NULL AND categories_id BETWEEN '.Category::GAME_ROOT.' AND '.Category::GAME_OTHER;
} else {
cli()->header('Resetting all failed Console postprocessing');
$where = ' WHERE consoleinfo_id IN (-2, 0) AND categories_id BETWEEN '.Category::GAME_ROOT.' AND '.Category::GAME_OTHER;
}
$qry = DB::select('SELECT id FROM releases'.$where);
if (! empty($qry)) {
$total = \count($qry);
} else {
$total = 0;
}
$concount = 0;
foreach ($qry as $releases) {
DB::update('UPDATE releases SET consoleinfo_id = NULL WHERE id = '.$releases->id);
cli()->overWritePrimary('Resetting Console Releases: '.cli()->percentString(++$concount, $total));
}
cli()->header(PHP_EOL.number_format($concount).' consoleinfoIDs reset.');
}
if (isset($argv[1]) && ($argv[1] === 'games' || $argv[1] === 'all')) {
$ran = true;
if (isset($argv[3]) && $argv[3] === 'truncate') {
DB::select('TRUNCATE TABLE gamesinfo');
}
if (isset($argv[2]) && $argv[2] === 'true') {
cli()->header('Resetting all Games postprocessing');
$where = ' WHERE gamesinfo_id != 0 AND categories_id = 4050';
} else {
cli()->header('Resetting all failed Games postprocessing');
$where = ' WHERE gamesinfo_id IN (-2, 0) AND categories_id = 4050';
}
$qry = DB::select('SELECT id FROM releases'.$where);
$total = 0;
if (! empty($qry)) {
$total = \count($qry);
}
$concount = 0;
foreach ($qry as $releases) {
DB::update('UPDATE releases SET gamesinfo_id = 0 WHERE id = '.$releases->id);
cli()->overWritePrimary('Resetting Games Releases: '.cli()->percentString(++$concount, $total));
}
cli()->header(PHP_EOL.number_format($concount).' gameinfo_IDs reset.');
}
if (isset($argv[1]) && ($argv[1] === 'movies' || $argv[1] === 'all')) {
$ran = true;
if (isset($argv[3]) && $argv[3] === 'truncate') {
DB::select('TRUNCATE TABLE movieinfo');
}
if (isset($argv[2]) && $argv[2] === 'true') {
cli()->header('Resetting all Movie postprocessing');
$where = ' WHERE imdbid IS NOT NULL AND categories_id BETWEEN '.Category::MOVIE_ROOT.' AND '.Category::MOVIE_OTHER;
} else {
cli()->header('Resetting all failed Movie postprocessing');
$where = ' WHERE imdbid IN (-2, 0) AND categories_id BETWEEN '.Category::MOVIE_ROOT.' AND '.Category::MOVIE_OTHER;
}
$qry = DB::select('SELECT id FROM releases'.$where);
if (! empty($qry)) {
$total = \count($qry);
} else {
$total = 0;
}
$concount = 0;
foreach ($qry as $releases) {
DB::update('UPDATE releases SET imdbid = NULL WHERE id = '.$releases->id);
cli()->overWritePrimary('Resetting Movie Releases: '.cli()->percentString(++$concount, $total));
}
cli()->header(PHP_EOL.number_format($concount).' imdbIDs reset.');
}
if (isset($argv[1]) && ($argv[1] === 'music' || $argv[1] === 'all')) {
$ran = true;
if (isset($argv[3]) && $argv[3] === 'truncate') {
DB::select('TRUNCATE TABLE musicinfo');
}
if (isset($argv[2]) && $argv[2] === 'true') {
cli()->header('Resetting all Music postprocessing');
$where = ' WHERE musicinfo_id IS NOT NULL AND categories_id BETWEEN '.Category::MUSIC_ROOT.' AND '.Category::MUSIC_OTHER;
} else {
cli()->header('Resetting all failed Music postprocessing');
$where = ' WHERE musicinfo_id IN (-2, 0) AND categories_id BETWEEN '.Category::MUSIC_ROOT.' AND '.Category::MUSIC_OTHER;
}
$qry = DB::select('SELECT id FROM releases'.$where);
$total = \count($qry);
$concount = 0;
foreach ($qry as $releases) {
DB::update(sprintf('UPDATE releases SET musicinfo_id = NULL WHERE id = %s ', $releases->id));
cli()->overWritePrimary('Resetting Music Releases: '.cli()->percentString(++$concount, $total));
}
cli()->header(PHP_EOL.number_format($concount).' musicinfo_ids reset.');
}
if (isset($argv[1]) && ($argv[1] === 'misc' || $argv[1] === 'all')) {
$ran = true;
if (isset($argv[2]) && $argv[2] === 'true') {
cli()->header('Resetting all Additional postprocessing');
$where = ' WHERE ((haspreview != -1 AND haspreview != 0) OR (passwordstatus != -1 AND passwordstatus != 0) OR jpgstatus != 0 OR videostatus != 0 OR audiostatus != 0)';
} else {
cli()->header('Resetting all failed Additional postprocessing');
$where = ' WHERE ((haspreview < -1 OR haspreview = 0) OR (passwordstatus < -1 OR passwordstatus = 0) OR jpgstatus < 0 OR videostatus < 0 OR audiostatus < 0)';
}
$where .= ' AND categories_id IN ('.Category::OTHER_MISC.','.Category::OTHER_HASHED.')';
cli()->primary('SELECT id FROM releases'.$where);
$qry = DB::select('SELECT id FROM releases'.$where);
if (! empty($qry)) {
$total = \count($qry);
} else {
$total = 0;
}
$concount = 0;
foreach ($qry as $releases) {
DB::update('UPDATE releases SET passwordstatus = -1, haspreview = -1, jpgstatus = 0, videostatus = 0, audiostatus = 0 WHERE id = '.$releases->id);
cli()->overWritePrimary('Resetting Releases: '.cli()->percentString(++$concount, $total));
}
cli()->header(PHP_EOL.number_format($concount).' Releases reset.');
}
if (isset($argv[1]) && ($argv[1] === 'tv' || $argv[1] === 'all')) {
$ran = true;
if (isset($argv[3]) && $argv[3] === 'truncate') {
DB::delete('DELETE v, va FROM videos v INNER JOIN videos_aliases va ON v.id = va.videos_id WHERE type = 0');
DB::select('TRUNCATE TABLE tv_info');
DB::select('TRUNCATE TABLE tv_episodes');
}
if (isset($argv[2]) && $argv[2] === 'true') {
cli()->header('Resetting all TV postprocessing');
$where = ' WHERE videos_id != 0 AND tv_episodes_id != 0 AND categories_id BETWEEN '.Category::TV_ROOT.' AND '.Category::TV_OTHER;
} else {
cli()->header('Resetting all failed TV postprocessing');
$where = ' WHERE tv_episodes_id < 0 AND categories_id BETWEEN '.Category::TV_ROOT.' AND '.Category::TV_OTHER;
}
$qry = DB::select('SELECT id FROM releases'.$where);
if (! empty($qry)) {
$total = \count($qry);
} else {
$total = 0;
}
$concount = 0;
foreach ($qry as $releases) {
DB::update('UPDATE releases SET videos_id = 0, tv_episodes_id = 0 WHERE id = '.$releases->id);
cli()->overWritePrimary('Resetting TV Releases: '.cli()->percentString(++$concount, $total));
}
cli()->header(PHP_EOL.number_format($concount).' Video IDs reset.');
}
if (isset($argv[1]) && ($argv[1] === 'anime' || $argv[1] === 'all')) {
$ran = true;
if (isset($argv[3]) && $argv[3] === 'truncate') {
DB::select('TRUNCATE TABLE anidb_info');
}
if (isset($argv[2]) && $argv[2] === 'true') {
cli()->header('Resetting all Anime postprocessing');
$where = ' WHERE categories_id = '.Category::TV_ANIME;
} else {
cli()->header('Resetting all failed Anime postprocessing');
$where = ' WHERE anidbid BETWEEN -2 AND -1 AND categories_id = '.Category::TV_ANIME;
}
$qry = DB::select('SELECT id FROM releases'.$where);
if (! empty($qry)) {
$total = \count($qry);
} else {
$total = 0;
}
$concount = 0;
foreach ($qry as $releases) {
DB::update('UPDATE releases SET anidbid = NULL WHERE id = '.$releases->id);
cli()->overWritePrimary('Resetting Anime Releases: '.cli()->percentString(++$concount, $total));
}
cli()->header(PHP_EOL.number_format($concount).' anidbIDs reset.');
}
if (isset($argv[1]) && ($argv[1] === 'books' || $argv[1] === 'all')) {
$ran = true;
if (isset($argv[3]) && $argv[3] === 'truncate') {
DB::select('TRUNCATE TABLE bookinfo');
}
if (isset($argv[2]) && $argv[2] === 'true') {
cli()->header('Resetting all Book postprocessing');
$where = ' WHERE bookinfo_id IS NOT NULL AND categories_id BETWEEN '.Category::BOOKS_ROOT.' AND '.Category::BOOKS_UNKNOWN;
} else {
cli()->header('Resetting all failed Book postprocessing');
$where = ' WHERE bookinfo_id IN (-2, 0) AND categories_id BETWEEN '.Category::BOOKS_ROOT.' AND '.Category::BOOKS_UNKNOWN;
}
$qry = DB::select('SELECT id FROM releases'.$where);
$total = \count($qry);
$concount = 0;
foreach ($qry as $releases) {
DB::update('UPDATE releases SET bookinfo_id = NULL WHERE id = '.$releases->id);
cli()->overWritePrimary('Resetting Book Releases: '.cli()->percentString(++$concount, $total));
}
cli()->header(PHP_EOL.number_format($concount).' bookinfoIDs reset.');
}
if (isset($argv[1]) && ($argv[1] === 'xxx' || $argv[1] === 'all')) {
$ran = true;
if (isset($argv[3]) && $argv[3] === 'truncate') {
DB::select('TRUNCATE TABLE xxxinfo');
}
if (isset($argv[2]) && $argv[2] === 'true') {
cli()->header('Resetting all XXX postprocessing');
$where = ' WHERE xxxinfo_id != 0 AND categories_id BETWEEN '.Category::XXX_ROOT.' AND '.Category::XXX_X264;
} else {
cli()->header('Resetting all failed XXX postprocessing');
$where = ' WHERE xxxinfo_id IN (-2, 0) AND categories_id BETWEEN '.Category::XXX_ROOT.' AND '.Category::XXX_X264;
}
$qry = DB::select('SELECT id FROM releases'.$where);
$concount = 0;
$total = \count($qry);
foreach ($qry as $releases) {
DB::update('UPDATE releases SET xxxinfo_id = 0 WHERE id = '.$releases->id);
cli()->overWritePrimary('Resetting XXX Releases: '.cli()->percentString(
++$concount,
$total
));
}
cli()->header(PHP_EOL.number_format($concount).' xxxinfo_IDs reset.');
}
if (isset($argv[1]) && ($argv[1] === 'nfos' || $argv[1] === 'all')) {
$ran = true;
if (isset($argv[3]) && $argv[3] === 'truncate') {
DB::select('TRUNCATE TABLE release_nfos');
}
if (isset($argv[2]) && $argv[2] === 'true') {
cli()->header('Resetting all NFO postprocessing');
$where = ' WHERE nfostatus != -1';
} else {
cli()->header('Resetting all failed NFO postprocessing');
$where = ' WHERE nfostatus < -1';
}
$qry = DB::select('SELECT id FROM releases'.$where);
$concount = 0;
$total = \count($qry);
foreach ($qry as $releases) {
DB::update('UPDATE releases SET nfostatus = -1 WHERE id = '.$releases->id);
cli()->overWritePrimary('Resetting NFO Releases: '.cli()->percentString(++$concount, $total));
}
cli()->header(PHP_EOL.number_format($concount).' NFOs reset.');
}
if ($ran === false) {
cli()->error(
'This script will reset postprocessing per category.'.PHP_EOL
.'It can also truncate the associated tables.'.PHP_EOL
.'To reset only those that have previously failed, those without covers, samples, previews, etc. use the second argument false.'.PHP_EOL
.'To reset even those previously post processed, use the second argument true.'.PHP_EOL
.'To truncate the associated table, use the third argument truncate.'.PHP_EOL.PHP_EOL
.'php reset_postprocessing.php consoles true ...: To reset all consoles.'.PHP_EOL
.'php reset_postprocessing.php games true ...: To reset all games.'.PHP_EOL
.'php reset_postprocessing.php movies true ...: To reset all movies.'.PHP_EOL
.'php reset_postprocessing.php music true ...: To reset all music.'.PHP_EOL
.'php reset_postprocessing.php misc true ...: To reset all misc.'.PHP_EOL
.'php reset_postprocessing.php tv true ...: To reset all tv.'.PHP_EOL
.'php reset_postprocessing.php anime true ...: To reset all anime.'.PHP_EOL
.'php reset_postprocessing.php books true ...: To reset all books.'.PHP_EOL
.'php reset_postprocessing.php xxx true ...: To reset all xxx.'.PHP_EOL
.'php reset_postprocessing.php nfos true ...: To reset all nfos.'.PHP_EOL
.'php reset_postprocessing.php all true ...: To reset everything.'.PHP_EOL
);
exit();
}
echo PHP_EOL;
-78
View File
@@ -1,78 +0,0 @@
<?php
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use App\Models\Release;
use App\Services\Nzb\NzbParserService;
use App\Services\Nzb\NzbService;
use App\Services\ReleaseImageService;
use Blacklight\Releases;
use Illuminate\Support\Facades\File;
$dir = resource_path().'/movednzbs/';
if (! isset($argv[1]) || ! in_array($argv[1], ['true', 'move'])) {
cli()->error("This script can remove all nzbs not found in the db and all releases with no nzbs found. It can also move invalid nzbs.\n\n"
."php $argv[0] true ...: For a dry run, to see how many would be moved.\n"
."php $argv[0] move ...: Move NZBs that are possibly bad or have no release. They are moved into this folder: $dir");
exit();
}
if (! File::isDirectory($dir) && ! File::makeDirectory($dir)) {
exit("ERROR: Could not create folder [$dir].".PHP_EOL);
}
$releases = new Releases;
$nzb = app(NzbService::class);
$nzbParser = app(NzbParserService::class);
$releaseImage = new ReleaseImageService;
$timestart = now()->toRfc2822String();
$checked = $moved = 0;
$couldbe = ($argv[1] === 'true') ? 'could be ' : '';
cli()->header('Getting List of nzbs to check against db.');
cli()->header("Checked / {$couldbe}moved\n");
$dirItr = new RecursiveDirectoryIterator(config('nntux_settings.path_to_nzbs'));
$itr = new RecursiveIteratorIterator($dirItr, RecursiveIteratorIterator::LEAVES_ONLY);
foreach ($itr as $filePath) {
$guid = stristr($filePath->getFilename(), '.nzb.gz', true);
if (File::isFile($filePath) && $guid) {
$nzbfile = unzipGzipFile($filePath);
$nzbContents = $nzbParser->parseNzbFileList($nzbfile, ['no-file-key' => false, 'strip-count' => true]);
if (! $nzbfile || ! @simplexml_load_string($nzbfile) || count($nzbContents) === 0) {
if ($argv[1] === 'move') {
rename($filePath, $dir.$guid.'.nzb.gz');
}
$releases->deleteSingle(['g' => $guid, 'i' => false], $nzb, $releaseImage);
$moved++;
}
$checked++;
echo "$checked / $moved\r";
}
}
cli()->header("\n".number_format($checked).' nzbs checked, '.number_format($moved).' nzbs '.$couldbe.'moved.');
cli()->header('Getting List of releases to check against nzbs.');
cli()->header("Checked / releases deleted\n");
$checked = $deleted = 0;
$res = Release::query()->select(['id', 'guid', 'nzbstatus'])->get();
foreach ($res as $row) {
$nzbpath = $nzb->getNzbPath($row->guid);
if (! File::isFile($nzbpath)) {
$deleted++;
$releases->deleteSingle(['g' => $row->guid, 'i' => $row->id], $nzb, $releaseImage);
} elseif ($row->nzbstatus !== 1) {
Release::where('id', $row->id)->update(['nzbstatus' => 1]);
}
$checked++;
echo "$checked / $deleted\r";
}
cli()->header("\n".number_format($checked).' releases checked, '.number_format($deleted).' releases deleted.');
cli()->header("Script started at [$timestart], finished at [".now()->toRfc2822String().']');
-72
View File
@@ -1,72 +0,0 @@
<?php
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use App\Models\Release;
use App\Models\UsenetGroup;
use App\Services\Search\ManticoreSearchService;
use App\Services\ReleaseCleaningService;
$message =
'Shows old searchname vs new searchname for releases in a group using the releaseCleaning class. (Good for testing new regex)'.
PHP_EOL.
'Argument 1 is the group name.'.PHP_EOL.
'Argument 2 is how many releases to limit this to, must be a number.'.PHP_EOL.
'Argument 3 (true|false) true renames the releases, false only displays what could be changed.'.PHP_EOL.
'Argument 4 is a categoryID, pass 0 to do all, 0010 to do misc, etc.'.PHP_EOL.
'php '.$argv[0].' alt.binaries.comics.dcp 1000 false'.PHP_EOL;
if ($argc < 5) {
exit($message);
}
if (! is_numeric($argv[2]) || ! is_numeric($argv[4])) {
exit($message);
}
if (! in_array($argv[3], ['true', 'false'], false)) {
exit($message);
}
$category = '';
$rename = false;
if ($argv[3] === 'true') {
$rename = true;
}
$group = UsenetGroup::query()->where('name', '=', $argv[1])->select(['id'])->first();
if ($group === null) {
exit('No group with name '.$argv[1].' found in the database.');
}
$releasesQuery = Release::query()->where('groups_id', $group->id);
if ((int) $argv[4] !== 0 && is_numeric($argv[4])) {
$releasesQuery->where('categories_id', '=', $argv[4]);
}
$releasesQuery->select(['name', 'searchname', 'fromname', 'size', 'id', 'postdate'])->orderBy('postdate')->limit($argv[2]);
$releases = $releasesQuery->get();
if (\count($releases) === 0) {
exit('No releases found in your database for group '.$argv[1].PHP_EOL);
}
$releaseCleaner = new ReleaseCleaningService;
$manticore = app(ManticoreSearchService::class);
foreach ($releases as $release) {
echo '.';
$newName = $releaseCleaner->releaseCleaner($release->name, $release->fromname, $argv[1]);
if (is_array($newName)) {
$newName = $newName['cleansubject'];
}
if ($newName !== $release['searchname']) {
echo 'Old name: '.$release->searchname.PHP_EOL;
echo 'New name: '.$newName.PHP_EOL.PHP_EOL;
if ($rename === true) {
Release::query()->where('id', '=', $release->id)->update(['searchname' => $newName]);
$manticore->updateRelease($release->id);
}
}
}
-196
View File
@@ -1,196 +0,0 @@
<?php
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use Illuminate\Support\Facades\File;
if (! isset($argv[1]) || ! isset($argv[2])) {
exit(
'Argument 1 is a input string. ie PRE name.'.PHP_EOL.
'Argument 2 is a expected hash or encoding. ie MD5 string. Passing true on Argument 3 ignores this.'.PHP_EOL.
'Argument 3 (optional) False, exit on first match. True, write all matches to text file in current path.'.PHP_EOL.
'ie: php test_hash_algorithms.php Dog.with.a.Blog.S02E16.Love.Loss.and.a.Beanbag.Toss.HDTV.x264-QCF 11506192c6d92e0c9c795b9997d9396226dbdf62'.PHP_EOL.
'ie: php test_hash_algorithms.php Anchorman.2.The.Legend.Continues.2013.UNRATED.WEBRip.x264-FLS false true'.PHP_EOL
);
}
/**
* Test various hashing/encoding/etc on a string.
* Class hash_algorithms.
*/
class HashAlgorithms
{
/**
* The input string.
*
* @var string
*/
protected $_inputString;
/**
* The string we are expecting to get.
*
* @var array
*/
protected $_expectedString;
/**
* Write results to file?
*
* @var bool
*/
protected $_writeToFile;
/**
* @param string $inputString
* @param string $expectedString
* @param bool $writeToFile
*/
public function __construct($inputString, $expectedString, $writeToFile)
{
$this->_inputString = $inputString;
$this->_expectedString = [
$expectedString,
strtolower($expectedString),
strtoupper($expectedString),
strrev($expectedString),
];
$this->_writeToFile = $writeToFile;
$this->_testStrings();
}
/**
* Test various hash algorithms on strings.
*
* @void
*/
protected function _testStrings()
{
if ($this->_writeToFile) {
File::put('hash_matches.txt', '');
}
$firstArray = $this->_hashesToArray($this->_inputString);
$secondArray = [];
foreach ($firstArray as $key => $value) {
if (! $this->_writeToFile) {
if (\in_array($value, $this->_expectedString, false)) {
exit(
'['.
$this->_inputString.
']=>['.
$key.
']=>'.
$value.
']'.
PHP_EOL
);
}
} else {
File::put('hash_matches.txt', $key."\t\t".$value.PHP_EOL, FILE_APPEND);
}
$secondArray[$key] = $this->_hashesToArray($value);
}
$thirdArray = [];
foreach ($secondArray as $key => $value) {
foreach ($value as $key2 => $value2) {
if (! $this->_writeToFile) {
if (\in_array($value2, $this->_expectedString, false)) {
exit(
'['.
$this->_inputString.
']=>['.
$key.
']=>['.
$firstArray[$key].
']=>['.
$key2.
']=>['.
$value2.
']'.
PHP_EOL
);
}
} else {
File::put('hash_matches.txt', $key.' => '.$key2."\t\t".$value2.PHP_EOL, FILE_APPEND);
}
$thirdArray[$key][$key2] = $this->_hashesToArray($value2);
}
}
foreach ($thirdArray as $key => $value) {
foreach ($value as $key2 => $value2) {
foreach ($value2 as $key3 => $value3) {
if (! $this->_writeToFile) {
if (in_array($value3, $this->_expectedString)) {
exit(
'['.
$this->_inputString.
']=>['.
$key.
']=>['.
$firstArray[$key].
']=>['.
$key2.
']=>['.
$value2.
']=>['.
$key3.
']=>['.
$value3.
']'.
PHP_EOL
);
}
} else {
File::put(
'hash_matches.txt',
$key.' => '.$key2.' => '.$key3."\t\t".$value3.PHP_EOL,
FILE_APPEND
);
}
}
}
}
}
/**
* Return various versions of a input string to hash.
*
* @param string $string
* @return array
*/
protected function _hashesToArray($string)
{
$strings = [
'input' => $string,
'lower' => strtolower($string),
'lower_reverse' => strtolower(strrev($string)),
'upper_reverse' => strtoupper(strrev($string)),
'upper' => strtoupper($string),
'reverse' => strrev($string),
'reverse_upper' => strrev(strtoupper($string)),
'reverse_lower' => strrev(strtolower($string)),
];
$hashTypes = ['md5', 'md4', 'sha1', 'sha256', 'sha512'];
$tmpArray = [];
foreach ($hashTypes as $hash) {
foreach ($strings as $key => $value) {
$tmpArray[$hash.'_'.$key] = hash($hash, $value, false);
}
}
foreach ($strings as $key => $value) {
$tmpArray['input_'.$key] = $value;
$tmpArray['base64_'.$key] = base64_encode($value);
$tmpArray['crc32_'.$key] = crc32($value);
}
return $tmpArray;
}
}
new HashAlgorithms($argv[1], $argv[2], ((isset($argv[3]) && strtolower($argv[3]) === 'true') ? true : false));
-47
View File
@@ -1,47 +0,0 @@
<?php
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use App\Models\Settings;
use App\Services\Nzb\NzbService;
if (! isset($argv[1]) || ! isset($argv[2])) {
exit("ERROR: You must supply the level you want to reorganize it to, and the source directory (You would use: 3 .../newznab/resources/nzb/ to move it to 3 levels deep)\n");
}
$nzb = app(NzbService::class);
$newLevel = $argv[1];
$sourcePath = $argv[2];
$objects = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($sourcePath));
$filestoprocess = [];
$iFilesProcessed = $iFilesCounted = 0;
$time = now()->toImmutable();
echo "\nReorganizing files to Level $newLevel from: $sourcePath This could take a while...\n";
// $consoleTools = new \ConsoleTools();
foreach ($objects as $filestoprocess => $nzbFile) {
if ($nzbFile->getExtension() != 'gz') {
continue;
}
$newFileName = $nzb->getNzbPath(
str_replace('.nzb.gz', '', $nzbFile->getBasename()),
$newLevel,
true
);
if ($newFileName != $nzbFile) {
rename($nzbFile, $newFileName);
chmod($newFileName, 0777);
}
$iFilesProcessed++;
if ($iFilesProcessed % 100 == 0) {
cli()->overWrite("Reorganized $iFilesProcessed");
}
}
Settings::query()->where(['name' => 'nzbsplitlevel'])->update(['value' => $argv[1]]);
cli()->overWrite("Processed $iFilesProcessed nzbs in ".$time->diffForHumans()."\n");
-56
View File
@@ -1,56 +0,0 @@
<?php
// --------------------------------------------------------------
// Scan for releases missing previews on disk
// --------------------------------------------------------------
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use App\Services\MovieService;
use Illuminate\Support\Facades\DB;
$movie = new MovieService();
$movie->echooutput = true;
$path2cover = storage_path('covers/movies/');
if (isset($argv[1]) && ($argv[1] === 'true' || $argv[1] === 'check')) {
$couldbe = $argv[1] === 'true' ? $couldbe = 'had ' : 'could have ';
$limit = $counterfixed = 0;
if (isset($argv[2]) && is_numeric($argv[2])) {
$limit = $argv[2];
}
cli()->header('Scanning for releases missing covers');
$res = DB::select('SELECT r.imdbid
FROM releases r
LEFT JOIN movieinfo m ON m.imdbid = r.imdbid
WHERE m.cover = 1 AND adddate > (NOW() - INTERVAL 24 HOUR) GROUP BY r.imdbid');
foreach ($res as $row) {
$nzbpath = $path2cover.$row->imdbid.'-cover.jpg';
if (! file_exists($nzbpath)) {
$counterfixed++;
cli()->warning('Missing cover '.$nzbpath);
if ($argv[1] === 'true') {
$cover = $movie->updateMovieInfo($row->imdbid);
if ($cover === false || ! file_exists($nzbpath)) {
DB::update(sprintf('UPDATE movieinfo m SET m.cover = 0 WHERE m.imdbid = %d', $row->imdbid));
}
}
}
if (($limit > 0) && ($counterfixed >= $limit)) {
break;
}
}
cli()->header('Total releases missing covers that '.$couldbe.'their covers fixed = '.number_format($counterfixed));
} else {
cli()->header("\nThis script checks if release covers actually exist on disk.\n\n"
."Releases without covers may be reset for post-processing, thus regenerating them and related meta data.\n\n"
."Useful for recovery after filesystem corruption, or as an alternative re-postprocessing tool.\n\n"
."Optional LIMIT parameter restricts number of releases to be reset.\n\n"
."php $argv[0] true ...:Sets all releases missing covers to be reset for post-processing.\n"
."php $argv[0] check ...:Checks and displays all releases missing covers.\n"
."php $argv[0] true 500 ...:Sets only 500 releases missing covers to be reset for post-processing.\n");
}
-57
View File
@@ -1,57 +0,0 @@
<?php
// --------------------------------------------------------------
// Scan for releases missing previews on disk
// --------------------------------------------------------------
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use App\Models\Release;
use App\Services\Nzb\NzbService;
use App\Services\ReleaseImageService;
use Blacklight\Releases;
use Illuminate\Support\Facades\DB;
$pdo = DB::connection()->getPdo();
$path2preview = storage_path('covers/preview');
if (isset($argv[1]) && ($argv[1] === 'true' || $argv[1] === 'check')) {
$releases = new Releases;
$nzb = app(NzbService::class);
$releaseImage = new ReleaseImageService;
$couldbe = $argv[1] === 'true' ? $couldbe = 'were ' : 'could be ';
$limit = $counterfixed = 0;
if (isset($argv[2]) && is_numeric($argv[2])) {
$limit = $argv[2];
}
cli()->header('Scanning for releases missing previews');
$res = Release::query()->select('id', 'guid')->where(['haspreview' => 1])->get()->toArray();
foreach ($res as $row) {
$nzbpath = $path2preview.$row['guid'].'_thumb.jpg';
if (! file_exists($nzbpath)) {
$counterfixed++;
cli()->warning('Missing preview '.$nzbpath);
if ($argv[1] === 'true') {
$pdo->exec(
sprintf('UPDATE releases SET consoleinfo_id = NULL, gamesinfo_id = 0, imdbid = NULL, musicinfo_id = NULL, bookinfo_id = NULL, videos_id = 0, xxxinfo_id = 0, passwordstatus = -1, haspreview = -1, jpgstatus = 0, videostatus = 0, audiostatus = 0, nfostatus = -1 WHERE id = %s', $row['id'])
);
}
}
if (($limit > 0) && ($counterfixed >= $limit)) {
break;
}
}
cli()->header('Total releases missing previews that '.$couldbe.'reset for reprocessing= '.number_format($counterfixed));
} else {
cli()->header("\nThis script checks if release previews actually exist on disk.\n\n"
."Releases without previews may be reset for post-processing, thus regenerating them and related meta data.\n\n"
."Useful for recovery after filesystem corruption, or as an alternative re-postprocessing tool.\n\n"
."Optional LIMIT parameter restricts number of releases to be reset.\n\n"
."php $argv[0] check [LIMIT] ...: Dry run, displays missing previews.\n"
."php $argv[0] true [LIMIT] ...: Re-process releases missing previews.\n");
exit();
}
-44
View File
@@ -1,44 +0,0 @@
<?php
// This script will update all records in the consoleinfo table
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use App\Models\Category;
use App\Services\ConsoleService;
use Illuminate\Support\Facades\DB;
$pdo = DB::connection()->getPdo();
$console = new ConsoleService;
$res = $pdo->query(
sprintf(
'SELECT searchname, id FROM releases WHERE consoleinfo_id IS NULL AND categories_id
BETWEEN %s AND %s ORDER BY id DESC',
Category::GAME_ROOT,
Category::GAME_OTHER
)
);
if ($res instanceof Traversable) {
cli()->header('Updating console info for '.number_format($res->rowCount()).' releases.');
foreach ($res as $arr) {
$starttime = now()->timestamp;
$gameInfo = $console->parseTitle($arr['searchname']);
if ($gameInfo !== false) {
$game = $console->updateConsoleInfo($gameInfo);
if ($game === false) {
cli()->primary($gameInfo['release'].' not found');
}
}
// amazon limits are 1 per 1 sec
$diff = floor((now()->timestamp - $starttime) * 1000000);
if (1000000 - $diff > 0) {
cli()->alternate('Sleeping');
usleep(1000000 - $diff);
}
}
}
-44
View File
@@ -1,44 +0,0 @@
<?php
// This script will update all records in the gamesinfo table
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use App\Services\GamesService;
use Illuminate\Support\Facades\DB;
$pdo = DB::connection()->getPdo();
$game = new GamesService;
$res = $pdo->query(
sprintf('SELECT id, title FROM gamesinfo WHERE cover = 0 ORDER BY id DESC LIMIT 100')
);
$total = $res->rowCount();
if ($total > 0) {
cli()->header('Updating game covers for '.number_format($total).' releases.');
foreach ($res as $arr) {
$starttime = now()->timestamp;
$gameInfo = $game->parseTitle($arr['title']);
if ($gameInfo !== false) {
cli()->primary('Looking up: '.$gameInfo['release']);
$gameData = $game->updateGamesInfo($gameInfo);
if ($gameData === false) {
cli()->primary($gameInfo['release'].' not found');
} else {
if (file_exists(storage_path('covers/games/').$gameData.'.jpg')) {
$pdo->exec(sprintf('UPDATE gamesinfo SET cover = 1 WHERE id = %d', $arr['id']));
}
}
}
// Rate limiting - 1 per second
$diff = floor((now()->timestamp - $starttime) * 1000000);
if (1000000 - $diff > 0) {
cli()->alternate('Sleeping');
usleep(1000000 - $diff);
}
}
}
-35
View File
@@ -1,35 +0,0 @@
<?php
// This script will update all records in the movieinfo table
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use App\Services\MovieService;
use Illuminate\Support\Facades\DB;
$pdo = DB::connection()->getPdo();
$movie = new MovieService();
$movie->echooutput = true;
$movies = $pdo->query('SELECT imdbid FROM movieinfo WHERE tmdbid = 0 ORDER BY id ASC');
if ($movies instanceof Traversable) {
$count = $movies->rowCount();
if ($count > 0) {
cli()->header('Updating movie info for '.number_format($count).' movies.');
foreach ($movies as $mov) {
$startTime = now()->timestamp;
$mov = $movie->updateMovieInfo($mov['imdbid']);
// tmdb limits are 30 per 10 sec, not certain for imdb
$diff = floor((now()->timestamp - $startTime) * 1000000);
if (333333 - $diff > 0) {
echo "sleeping\n";
usleep(333333 - $diff);
}
}
} else {
cli()->header('No movies to update');
}
}
-24
View File
@@ -1,24 +0,0 @@
<?php
// This script will update all records in the movieinfo table where there is no cover
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use App\Models\MovieInfo;
use App\Services\MovieService;
$movie = new MovieService();
$movie->echooutput = true;
$movies = MovieInfo::query()->where('cover', '=', 0)->orderBy('year')->orderByDesc('id')->get(['imdbid']);
$count = $movies->count();
if ($count > 0) {
cli()->primary('Updating '.number_format($count).' movie covers.');
foreach ($movies as $mov) {
$startTime = now()->timestamp;
$mov = $movie->updateMovieInfo($mov['imdbid']);
}
} else {
cli()->header('No movie covers to update');
}
-34
View File
@@ -1,34 +0,0 @@
<?php
// This script will update all records in the movieinfo table where there is no cover
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use App\Models\MovieInfo;
use App\Services\MovieService;
use App\Services\TvProcessing\Providers\TraktProvider;
$movie = new MovieService();
$movie->echooutput = true;
$movies = MovieInfo::query()->where('imdbid', '<>', 0)->where('traktid', '=', 0)->get(['imdbid']);
$count = $movies->count();
if ($count > 0) {
cli()->primary('Updating '.number_format($count).' movies for TraktTV id.');
foreach ($movies as $mov) {
$traktTv = new TraktProvider();
$traktmovie = $traktTv->client->getMovieSummary('tt'.$mov['imdbid'], 'full');
if ($traktmovie !== false) {
cli()->info('Updating IMDb id: tt'.$mov['imdbid']);
$trakt = $movie->parseTraktTv($traktmovie);
if ($trakt === true) {
cli()->info('Added traktid: '.$traktmovie['ids']['trakt']);
} else {
cli()->info('No traktid found.');
}
}
}
} else {
cli()->header('No movies to update');
}
-33
View File
@@ -1,33 +0,0 @@
<?php
// This script will update all records in the xxxinfo table where there is no cover
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use App\Services\AdultProcessing\AdultProcessingPipeline;
use Illuminate\Support\Facades\DB;
$pipeline = new AdultProcessingPipeline([], true);
$movies = DB::select('SELECT title FROM xxxinfo WHERE cover = 0');
echo cli()->primary('Updating '.number_format(\count($movies)).' XXX movie covers.');
foreach ($movies as $mov) {
$starttime = now()->timestamp;
$result = $pipeline->processMovie($mov->title);
if ($result['status'] === 'matched') {
echo cli()->primary('Updated: '.$mov->title);
} else {
echo cli()->warning('No match found for: '.$mov->title);
}
// sleep so that it's not ddos' the site
$diff = floor((now()->timestamp - $starttime) * 1000000);
if (333333 - $diff > 0) {
echo "\nsleeping\n";
usleep(333333 - $diff);
}
}
echo "\n";
-59
View File
@@ -1,59 +0,0 @@
<?php
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use App\Models\Category;
use App\Services\ReleaseImageService;
use Illuminate\Support\Facades\DB;
$pdo = DB::connection()->getPdo();
$path2cover = storage_path('covers/sample/');
if (isset($argv[1]) && ($argv[1] === 'true' || $argv[1] === 'check')) {
$releaseImage = new ReleaseImageService;
$couldbe = $argv[1] === 'true' ? $couldbe = 'had ' : 'could have ';
$limit = $counterfixed = 0;
if (isset($argv[2]) && is_numeric($argv[2])) {
$limit = $argv[2];
}
cli()->header('Scanning for XXX UHD/HD/SD releases missing sample images');
$res = $pdo->query(sprintf('SELECT r.id, r.guid AS guid, r.searchname AS searchname
FROM releases r
WHERE r.jpgstatus = 0 AND r.categories_id IN (%s, %s, %s) ORDER BY r.adddate DESC', Category::XXX_CLIPHD, Category::XXX_CLIPSD, Category::XXX_UHD));
foreach ($res as $row) {
$nzbpath = $path2cover.$row['guid'].'_thumb.jpg';
if (! file_exists($nzbpath)) {
$counterfixed++;
if ($argv[1] === 'true') {
$imgpath = 'http://pic4all.eu/images/'.$row['searchname'].'_1.jpg';
// scan pic4all.eu for sample image
if (stripos($row['searchname'], 'SDCLiP') !== false) {
$row['searchname'] = strtolower(preg_replace('/.XXX(.720p|.1080p)?.MP4-SDCLiP/i', '', $row['searchname']));
$imgpath = 'http://pic4all.eu/images/'.$row['searchname'].'.jpg';
}
$sample = $releaseImage->saveImage($row['guid'].'_thumb', $imgpath, $releaseImage->jpgSavePath, 650, 650);
if ($sample !== 0) {
cli()->info('Downloaded sample for '.$row['searchname']);
$pdo->exec(sprintf('UPDATE releases SET jpgstatus = 1 WHERE id = %d', $row['id']));
} else {
cli()->notice('Sample download failed!');
$pdo->exec(sprintf('UPDATE releases SET jpgstatus = -2 WHERE id = %d', $row['id']));
}
}
}
if (($limit > 0) && ($counterfixed >= $limit)) {
break;
}
}
cli()->header('Total releases missing samples that '.$couldbe.'their samples updated = '.number_format($counterfixed));
} else {
cli()->header("\nThis script checks if XXX release samples actually exist on disk.\n\n"
."php $argv[0] check ...: Dry run, displays missing samples.\n"
."php $argv[0] true ...: Update XXX releases missing samples.\n");
exit();
}
@@ -1,46 +0,0 @@
<?php
use App\Models\BookInfo;
use Illuminate\Support\Facades\File;
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
$covers = $updated = $deleted = 0;
if ($argc === 1 || $argv[1] !== 'true') {
cli()->error("\nThis script will check all images in covers/book and compare to db->bookinfo.\nTo run:\nphp $argv[0] true\n");
exit();
}
$path2covers = storage_path('covers/book/');
$itr = File::allFiles($path2covers);
foreach ($itr as $filePath) {
if (is_file($filePath->getPathname()) && preg_match('/\d+\.jpg$/', $filePath->getPathname())) {
preg_match('/(\d+)\.jpg$/', $filePath->getPathname(), $hit);
if (isset($hit[1])) {
$run = BookInfo::query()->where('cover', '=', 0)->where('id', $hit[1])->update(['cover' => 1]);
if ($run >= 1) {
$covers++;
} else {
$run = BookInfo::query()->where('id', $hit[1])->select(['id'])->get();
if ($run->count() === 0) {
cli()->info($filePath.' not found in db.');
}
}
}
}
}
$qry = BookInfo::query()->where('cover', '=', 1)->select(['id'])->get();
foreach ($qry as $rows) {
if (! is_file($path2covers.$rows['id'].'.jpg')) {
BookInfo::query()->where(['cover' => 1, 'id' => $rows['id']])->update(['cover' => 0]);
cli()->info($path2covers.$rows['id'].'.jpg does not exist.');
$deleted++;
}
}
cli()->header($covers.' covers set.');
cli()->header($deleted.' books unset.');
@@ -1,56 +0,0 @@
<?php
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use App\Models\ConsoleInfo;
use Illuminate\Support\Facades\File;
$covers = $updated = $deleted = 0;
if ($argc === 1 || $argv[1] !== 'true') {
cli()->error("\nThis script will check all images in covers/console and compare to db->consoleinfo.\nTo run:\nphp $argv[0] true\n");
exit();
}
$path2covers = storage_path('covers/console/');
$itr = File::allFiles($path2covers);
foreach ($itr as $filePath) {
if (is_file($filePath->getPathname()) && preg_match('/\d+\.jpg$/', $filePath->getPathname())) {
preg_match('/(\d+)\.jpg$/', $filePath->getPathname(), $hit);
if (isset($hit[1])) {
$run = ConsoleInfo::query()->where(
[
'cover' => 0,
'id' => $hit[1],
]
)->update(['cover' => 1]);
if ($run >= 1) {
$covers++;
} else {
$run = ConsoleInfo::query()->where('id', $hit[1])->value('id');
if ($run === 0) {
cli()->info($filePath.' not found in db.');
}
}
}
}
}
$qry = ConsoleInfo::query()->where('cover', '=', 1)->value('id');
foreach ($qry as $rows) {
if (! is_file($path2covers.$rows['id'].'.jpg')) {
ConsoleInfo::query()->where(
[
['cover' => 1],
['id' => $rows['id']],
]
)->update(['cover' => 0]);
cli()->info($path2covers.$rows['id'].'.jpg does not exist.');
$deleted++;
}
}
cli()->header($covers.' covers set.');
cli()->header($deleted.' consoles unset.');
@@ -1,48 +0,0 @@
<?php
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use App\Models\GamesInfo;
use Illuminate\Support\Facades\File;
$covers = $updated = $deleted = 0;
if ($argc === 1 || $argv[1] !== 'true') {
cli()->error("\nThis script will check all images in covers/games and compare to db->gamesinfo.\nTo run:\nphp $argv[0] true\n");
exit();
}
$path2covers = storage_path('covers/games/');
$itr = File::allFiles($path2covers);
foreach ($itr as $filePath) {
if (is_file($filePath->getPathname()) && preg_match('/\d+\.jpg$/', $filePath->getPathname())) {
preg_match('/(\d+)\.jpg$/', basename($filePath->getPathname()), $hit);
if (isset($hit[1])) {
$run = GamesInfo::query()->where('cover', '=', 0)->where('id', $hit[1])->update(['cover' => 1]);
if ($run !== false) {
if ($run >= 1) {
$covers++;
} else {
$run = GamesInfo::query()->where('id', $hit[1])->select(['id'])->get();
if ($run !== null && $run === 0) {
cli()->info($filePath->getPathname().' not found in db.');
}
}
}
}
}
}
$qry = GamesInfo::query()->where('cover', '=', 1)->select(['id'])->get();
foreach ($qry as $rows) {
if (! is_file($path2covers.$rows['id'].'.jpg')) {
GamesInfo::query()->where(['cover' => 1, 'id' => $rows['id']])->update(['cover' => 0]);
cli()->info($path2covers.$rows['id'].'.jpg does not exist.');
$deleted++;
}
}
cli()->header($covers.' covers set.');
cli()->header($deleted.' games unset.');
@@ -1,69 +0,0 @@
<?php
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use App\Models\MovieInfo;
use Illuminate\Support\Facades\File;
$covers = $updated = $deleted = 0;
if ($argc === 1 || $argv[1] !== 'true') {
cli()->error("\nThis script will check all images in covers/movies and compare to db->movieinfo.\nTo run:\nphp $argv[0] true\n");
exit();
}
$path2covers = storage_path('covers/movies');
$itr = File::allFiles($path2covers);
foreach ($itr as $filePath) {
if (is_file($filePath->getPathname()) && preg_match('/-cover\.jpg$/', $filePath->getPathname())) {
preg_match('/(\d+)-cover\.jpg$/', $filePath->getPathname(), $hit);
if (isset($hit[1])) {
$run = MovieInfo::query()->where('cover', '=', 0)->where('imdbid', $hit[1])->update(['cover' => 1]);
if ($run >= 1) {
$covers++;
} else {
$run = MovieInfo::query()->where('imdbid', '=', $hit[1])->select(['imdbid'])->get();
if ($run->count() === 0) {
cli()->error($filePath.' not found in db.');
}
}
}
}
if (is_file($filePath) && preg_match('/-backdrop\.jpg$/', $filePath->getPathname())) {
preg_match('/(\d+)-backdrop\.jpg$/', $filePath->getPathname(), $match1);
if (isset($match1[1])) {
$run = MovieInfo::query()->where(['backdrop' => 0, 'imdbid' => $match1[1]])->update(['backdrop' => 1]);
if ($run >= 1) {
$updated++;
} else {
$run = MovieInfo::query()->where('imdbid', $match1[1])->select(['imdbid'])->get();
if ($run->count() === 0) {
cli()->error($filePath->getPathname().' not found in db.');
}
}
}
}
}
$qry = MovieInfo::query()->where('cover', '=', 1)->select(['imdbid'])->get();
foreach ($qry as $rows) {
if (! is_file($path2covers.$rows['imdbid'].'-cover.jpg')) {
MovieInfo::query()->where('cover', '=', 1)->where('imdbid', $rows['imdbid'])->update(['cover' => 0]);
cli()->info($path2covers.$rows['imdbid'].'-cover.jpg does not exist.');
$deleted++;
}
}
$qry1 = MovieInfo::query()->where('backdrop', '=', 1)->select(['imdbid'])->get();
foreach ($qry1 as $rows) {
if (! is_file($path2covers.$rows['imdbid'].'-backdrop.jpg')) {
MovieInfo::query()->where('backdrop', '=', 1)->where('imdbid', $rows['imdbid'])->update(['backdrop' => 0]);
cli()->info($path2covers.$rows['imdbid'].'-backdrop.jpg does not exist.');
$deleted++;
}
}
cli()->info($covers.' covers set.');
cli()->info($updated.' backdrops set.');
cli()->info($deleted.' movies unset.');
@@ -1,46 +0,0 @@
<?php
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use App\Models\MusicInfo;
use Illuminate\Support\Facades\File;
$covers = $updated = $deleted = 0;
if ($argc === 1 || $argv[1] !== 'true') {
cli()->error("\nThis script will check all images in covers/music and compare to db->musicinfo.\nTo run:\nphp $argv[0] true\n");
exit();
}
$path2covers = storage_path('covers/music/');
$itr = File::allFiles($path2covers);
foreach ($itr as $filePath) {
if (is_file($filePath->getPathname()) && preg_match('/\d+\.jpg$/', $filePath->getPathname())) {
preg_match('/(\d+)\.jpg$/', $filePath->getPathname(), $hit);
if (isset($hit[1])) {
$run = MusicInfo::query()->where('cover', '=', 0)->where('id', $hit[1])->update(['cover' => 1]);
if ($run >= 1) {
$covers++;
} else {
$run = MusicInfo::query()->where('id', $hit[1])->select(['id'])->get();
if ($run->count() === 0) {
cli()->info($filePath->getPathname().' not found in db.');
}
}
}
}
}
$qry = MusicInfo::query()->where('cover', '=', 1)->select(['id'])->get();
foreach ($qry as $rows) {
if (! is_file($path2covers.$rows['id'].'.jpg')) {
MusicInfo::query()->where(['cover' => 1, 'id' => $rows['id']])->update(['cover' => 0]);
cli()->info($path2covers.$rows['id'].'.jpg does not exist.');
$deleted++;
}
}
cli()->header($covers.' covers set.');
cli()->header($deleted.' music unset.');
-69
View File
@@ -1,69 +0,0 @@
<?php
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use App\Models\XxxInfo;
use Illuminate\Support\Facades\File;
$covers = $updated = $deleted = 0;
if ($argc === 1 || $argv[1] !== 'true') {
cli()->error("\nThis script will check all images in covers/xxx and compare to db->xxxinfo.\nTo run:\nphp $argv[0] true\n");
exit();
}
$path2covers = storage_path('covers/xxx/');
$itr = File::allFiles($path2covers);
foreach ($itr as $filePath) {
if (is_file($filePath->getPathname()) && preg_match('/-cover\.jpg$/', $filePath->getPathname())) {
preg_match('/(\d+)-cover\.jpg$/', $filePath->getPathname(), $hit);
if (isset($hit[1])) {
$run = XxxInfo::query()->where('cover', '=', 0)->where('id', $hit[1])->update(['cover' => 1]);
if ($run >= 1) {
$covers++;
} else {
$run = XxxInfo::query()->where('id', $hit[1])->select(['id'])->get();
if ($run->count() === 0) {
cli()->info($filePath->getPathname().' not found in db.');
}
}
}
}
if (is_file($filePath->getPathname()) && preg_match('/-backdrop\.jpg$/', $filePath->getPathname())) {
preg_match('/(\d+)-backdrop\.jpg$/', $filePath->getPathname(), $match1);
if (isset($match1[1])) {
$run = XxxInfo::query()->where(['backdrop' => 0, 'id' => $match1[1]])->update(['backdrop' => 1]);
if ($run >= 1) {
$updated++;
} else {
$run = XxxInfo::query()->where('id', $match1[1])->select(['id'])->get();
if ($run->count() === 0) {
cli()->info($filePath->getPathname().' not found in db.');
}
}
}
}
}
$qry = XxxInfo::query()->where('cover', '=', 1)->select(['id'])->get();
foreach ($qry as $rows) {
if (! is_file($path2covers.$rows['id'].'-cover.jpg')) {
XxxInfo::query()->where(['cover' => 1, 'id' => $rows['id']])->update(['cover' => 0]);
cli()->info($path2covers.$rows['id'].'-cover.jpg does not exist.');
$deleted++;
}
}
$qry1 = XxxInfo::query()->where('backdrop', '=', 1)->select(['id'])->get();
foreach ($qry1 as $rows) {
if (! is_file($path2covers.$rows['id'].'-backdrop.jpg')) {
XxxInfo::query()->where(['backdrop' => 1, 'id' => $rows['id']])->update(['backdrop' => 0]);
cli()->info($path2covers.$rows['id'].'-backdrop.jpg does not exist.');
$deleted++;
}
}
cli()->header($covers.' covers set.');
cli()->header($updated.' backdrops set.');
cli()->header($deleted.' movies unset.');
-57
View File
@@ -1,57 +0,0 @@
<?php
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use Blacklight\ReleaseRemover;
// New line for CLI.
$n = PHP_EOL;
// Include config.php
// ColorCLI class.
// Print arguments/usage.
$totalArgs = count($argv);
if ($totalArgs < 2) {
exit(cli()->info(
$n.
'This deletes releases based on a list of criteria you pass.'.$n.
'Usage:'.$n.$n.
'List of supported criteria:'.$n.
'fromname : Look for names of people who posted releases (the poster name). (modifiers: equals, like)'.$n.
'groupname : Look in groups. (modifiers: equals, like)'.$n.
'guid : Look for a specific guid. (modifiers: equals)'.$n.
'name : Look for a name (the usenet name). (modifiers: equals, like)'.$n.
'searchname : Look for a name (the search name). (modifiers: equals, like)'.$n.
'size : Release must be (bigger than |smaller than |exactly) this size.(bytes) (modifiers: equals,bigger,smaller)'.$n.
'adddate : Look for releases added to our DB (older than|newer than) x hours. (modifiers: bigger,smaller)'.$n.
'postdate : Look for posted to usenet (older than|newer than) x hours. (modifiers: bigger,smaller)'.$n.
'completion : Look for completion (less than) (modifiers: smaller)'.$n.
'categories_id : Look for releases within specified category (modifiers: equals)'.$n.
'imdbid : Look for releases with imdbid (modifiers: equals)'.$n.
'rageid : Look for releases with rageid (modifiers: equals)'.$n.
'totalpart : Look for releases with certain number of parts (modifiers: equals,bigger,smaller)'.$n.
'nzbstatus : Look for releases with nzbstatus (modifiers: equals)'.$n.$n.
'List of Modifiers:'.$n.
'equals : Match must be exactly this. (fromname=equals="john" will only look for "john", not "johndoe")'.$n.
'like : Match can be similar to this. Separate words using spaces(ie:"cars hdtv x264").'.$n.
' (fromname=like="john" will look for any posters with john in it (ie:john@smith.com)'.$n.
'bigger : Match must be bigger than this. (postdate=bigger="3" means older than 3 hours ago)'.$n.
'smaller : Match must be smaller than this (postdate=smaller="3" means between now and 3 hours ago.'.$n.$n.
'Extra:'.$n.
'ignore : Ignore the user check. (before running we ask you if you want to run the query to delete)'.$n.$n.
'Examples:'.$n.
$_SERVER['_'].' '.$argv[0].' groupname=equals="alt.binaries.teevee" searchname=like="olympics 2014" postdate=bigger="5"'.$n.
$_SERVER['_'].' '.$argv[0].' guid=equals="8fb5956bae3de4fb94edcc69da44d6883d586fd0"'.$n.
$_SERVER['_'].' '.$argv[0].' size=smaller="104857600" size=bigger="2048" groupname=like="movies"'.$n.
$_SERVER['_'].' '.$argv[0].' fromname=like="@XviD.net" groupname=equals="alt.binaries.movies.divx" ignore'.$n.
$_SERVER['_'].' '.$argv[0].' imdbid=equals=NULL categories_id=equals=2999 nzbstatus=equals=1 adddate=bigger=2880 # Remove other movie releases with non-cleaned names added > 120 days ago'
));
}
$RR = new ReleaseRemover;
// Remove argv[0] and send the array.
$RR->removeByCriteria(array_slice($argv, 1, $totalArgs - 1));
-131
View File
@@ -1,131 +0,0 @@
<?php
/*
* This script attempts to clean release names using the NFO, file name and release name, Par2 file.
* A good way to use this script is to use it in this order: php fixReleaseNames.php 3 true other yes
* php fixReleaseNames.php 5 true other yes
* If you used the 4th argument yes, but you want to reset the status,
* there is another script called resetRelnameStatus.php
*/
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use App\Services\NameFixing\NameFixingService;
use App\Services\NNTP\NNTPService;
use Symfony\Component\Console\Output\ConsoleOutput;
$output = new ConsoleOutput;
$nameFixingService = new NameFixingService;
$nntp = new NNTPService;
if (isset($argv[1], $argv[2], $argv[3], $argv[4])) {
$update = $argv[2] === 'true';
$other = 1;
if ($argv[3] === 'all') {
$other = 2;
} elseif ($argv[3] === 'predb_id') {
$other = 3;
}
$setStatus = $argv[4] === 'yes';
$show = isset($argv[5]) && $argv[5] === 'show';
if ($argv[1] === '7' || $argv[1] === '8') {
$compressedHeaders = config('nntmux_nntp.compressed_headers');
if ((config('nntmux_nntp.use_alternate_nntp_server') === true ? $nntp->doConnect($compressedHeaders, true) : $nntp->doConnect()) !== true) {
$output->writeln('<error>Unable to connect to usenet.</error>');
return;
}
}
switch ($argv[1]) {
case '3':
$nameFixingService->fixNamesWithNfo(1, $update, $other, $setStatus, $show);
break;
case '4':
$nameFixingService->fixNamesWithNfo(2, $update, $other, $setStatus, $show);
break;
case '5':
$nameFixingService->fixNamesWithFiles(1, $update, $other, $setStatus, $show);
break;
case '6':
$nameFixingService->fixNamesWithFiles(2, $update, $other, $setStatus, $show);
break;
case '7':
$nameFixingService->fixNamesWithPar2(1, $update, $other, $setStatus, $show, $nntp);
break;
case '8':
$nameFixingService->fixNamesWithPar2(2, $update, $other, $setStatus, $show, $nntp);
break;
case '9':
$nameFixingService->fixNamesWithMedia(1, $update, $other, $setStatus, $show);
break;
case '10':
$nameFixingService->fixNamesWithMedia(2, $update, $other, $setStatus, $show);
break;
case '11':
$nameFixingService->fixXXXNamesWithFiles(1, $update, $other, $setStatus, $show);
break;
case '12':
$nameFixingService->fixXXXNamesWithFiles(2, $update, $other, $setStatus, $show);
break;
case '13':
$nameFixingService->fixNamesWithSrr(1, $update, $other, $setStatus, $show);
break;
case '14':
$nameFixingService->fixNamesWithSrr(2, $update, $other, $setStatus, $show);
break;
case '15':
$nameFixingService->fixNamesWithParHash(1, $update, $other, $setStatus, $show);
break;
case '16':
$nameFixingService->fixNamesWithParHash(2, $update, $other, $setStatus, $show);
break;
case '17':
$nameFixingService->fixNamesWithMediaMovieName(1, $update, $other, $setStatus, $show);
break;
case '18':
$nameFixingService->fixNamesWithMediaMovieName(2, $update, $other, $setStatus, $show);
break;
case '19':
$nameFixingService->fixNamesWithCrc(1, $update, $other, $setStatus, $show);
break;
case '20':
$nameFixingService->fixNamesWithCrc(2, $update, $other, $setStatus, $show);
break;
default:
$output->writeln('<error>'.PHP_EOL.'ERROR: Wrong argument, type php '.$argv[0].' to see a list of valid arguments.</error>');
exit(1);
}
} else {
$output->writeln([
'',
'<error>You must supply 4 arguments.</error>',
'<comment>The 2nd argument, false, will display the results, but not change the name, type true to have the names changed.</comment>',
'<comment>The 3rd argument, other, will only do against other categories, to do against all categories use all, or predb_id to process all not matched to predb.</comment>',
'<comment>The 4th argument, yes, will set the release as checked, so the next time you run it will not be processed, to not set as checked type no.</comment>',
'<comment>The 5th argument (optional), show, will display the release changes or only show a counter.</comment>',
'',
'<info>php '.$argv[0].' 3 false other no ...: Fix release names using NFO in the past 6 hours.</info>',
'<info>php '.$argv[0].' 4 false other no ...: Fix release names using NFO.</info>',
'<info>php '.$argv[0].' 5 false other no ...: Fix release names in misc categories using File Name in the past 6 hours.</info>',
'<info>php '.$argv[0].' 6 false other no ...: Fix release names in misc categories using File Name.</info>',
'<info>php '.$argv[0].' 7 false other no ...: Fix release names in misc categories using Par2 Files in the past 6 hours.</info>',
'<info>php '.$argv[0].' 8 false other no ...: Fix release names in misc categories using Par2 Files.</info>',
'<info>php '.$argv[0].' 9 false other no ...: Fix release names in misc categories using UID in the past 6 hours.</info>',
'<info>php '.$argv[0].' 10 false other no ...: Fix release names in misc categories using UID.</info>',
'<info>php '.$argv[0].' 11 false other no ...: Fix SDPORN XXX release names in misc categories using specific File Name in the past 6 hours.</info>',
'<info>php '.$argv[0].' 12 false other no ...: Fix SDPORN XXX release names in misc categories using specific File Name.</info>',
'<info>php '.$argv[0].' 13 false other no ...: Fix release names in misc categories using SRR files in the past 6 hours.</info>',
'<info>php '.$argv[0].' 14 false other no ...: Fix release names in misc categories using SRR files.</info>',
'<info>php '.$argv[0].' 15 false other no ...: Fix release names in misc categories using PAR2 hash_16K block in the past 6 hours.</info>',
'<info>php '.$argv[0].' 16 false other no ...: Fix release names in misc categories using PAR2 hash_16K block.</info>',
'<info>php '.$argv[0].' 17 false other no ...: Fix release names in misc categories using Mediainfo in the past 6 hours.</info>',
'<info>php '.$argv[0].' 18 false other no ...: Fix release names in misc categories using Mediainfo.</info>',
'<info>php '.$argv[0].' 19 false other no ...: Fix release names in misc categories using CRC32 in the past 6 hours.</info>',
'<info>php '.$argv[0].' 20 false other no ...: Fix release names in misc categories using CRC32.</info>',
'',
]);
exit(1);
}
-112
View File
@@ -1,112 +0,0 @@
<?php
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use App\Models\Category;
use App\Models\Release;
use App\Services\Categorization\CategorizationService;
if (! (isset($argv[1]) && ($argv[1] === 'all' || $argv[1] === 'misc' || preg_match('/\([\d, ]+\)/', $argv[1]) || is_numeric($argv[1])))) {
cli()->error(
"\nThis script will attempt to re-categorize releases and is useful if changes have been made to Category.php.\n"
."No updates will be done unless the category changes\n"
."An optional last argument, false, will display the number of category changes that would be made\n"
."but will not update the database.\n\n"
."php $argv[0] all ...: To process all releases.\n"
."php $argv[0] misc ...: To process all releases in misc categories.\n"
."php $argv[0] 155 ...: To process all releases in groupid 155.\n"
."php $argv[0] '(155, 140)' ...: To process all releases in groupids 155 and 140.\n"
);
exit();
}
reCategorize($argv);
function reCategorize($argv): void
{
if (isset($argv[1]) && (is_numeric($argv[1]) || preg_match('/\([\d, ]+\)/', $argv[1]))) {
cli()->header('Categorizing all releases in '.$argv[1].' using searchname. This can take a while, be patient.');
} elseif (isset($argv[1]) && $argv[1] === 'misc') {
cli()->header('Categorizing all releases in misc categories using searchname. This can take a while, be patient.');
} else {
cli()->header('Categorizing all releases using searchname. This can take a while, be patient.');
}
$timeStart = now();
$chgCount = categorizeRelease($argv, true);
$time = now()->diffInSeconds($timeStart, true);
if (! isset($argv[2])) {
cli()->header('Finished re-categorizing '.number_format($chgCount).' releases in '.$time.' seconds, using the searchname.').PHP_EOL;
} else {
cli()->header('Finished re-categorizing in '.$time.' seconds , using the searchname.'.PHP_EOL
.'This would have changed '.number_format($chgCount).' releases but no updates were done.').PHP_EOL;
}
}
function categorizeRelease($argv, $echoOutput = false): int
{
$update = true;
$query = Release::query()->select(['id', 'searchname', 'fromname', 'groups_id', 'categories_id']);
if (isset($argv[1]) && is_numeric($argv[1])) {
$query->where('groups_id', $argv[1]);
} elseif (isset($argv[1]) && preg_match('/\([\d, ]+\)/', $argv[1])) {
$query->whereIn('groups_id', $argv[1]);
} elseif (isset($argv[1]) && $argv[1] === 'misc') {
$query->whereIn('categories_id', Category::OTHERS_GROUP);
}
if (isset($argv[2]) && $argv[2] === 'test') {
$update = false;
}
$total = $query->count();
cli()->header('Categorizing ['.$total.'] releases. This can take a while, be patient.');
$relCount = $chgCount = 0;
if ($total > 0) {
$query->chunk('100', function ($results) use ($update, $relCount, $chgCount) {
$cat = new CategorizationService();
foreach ($results as $result) {
$catId = $cat->determineCategory($result->groups_id, $result->searchname, $result->fromname);
if ((int) $result->categories_id !== (int) $catId['categories_id']) {
if ($update === true) {
Release::query()->where('id', $result->id)->update([
'iscategorized' => 1,
'videos_id' => 0,
'tv_episodes_id' => 0,
'imdbid' => null,
'musicinfo_id' => null,
'consoleinfo_id' => null,
'gamesinfo_id' => 0,
'bookinfo_id' => 0,
'anidbid' => null,
'xxxinfo_id' => 0,
'categories_id' => $catId['categories_id'],
]);
Blacklight\NameFixer::echoChangedReleaseName([
'new_name' => $result->searchname,
'old_name' => $result->searchname,
'new_category' => $catId['categories_id'],
'old_category' => $result->categories_id,
'group' => $result->group->name,
'releases_id' => $result->id,
'method' => 'Recategorize',
]);
}
$chgCount++;
}
$relCount++;
echo '*';
}
});
if ($echoOutput) {
cli()->overWritePrimary('Re-Categorized: ['.number_format($chgCount).'] '.cli()->percentString($relCount, $total).PHP_EOL);
}
}
return $chgCount;
}
@@ -1,69 +0,0 @@
<?php
/* This script deletes releases that match certain criteria, type php removeCrapReleases.php false for details. */
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use Blacklight\ReleaseRemover;
$n = PHP_EOL;
$argCnt = count($argv);
if ($argCnt === 1) {
exit(
cli()->error(
$n.
'Run fixReleaseNames.php first to attempt to fix release names.'.$n.
'This will miss some releases if you have not set fixReleaseNames to set the release as checked.'.$n.$n.
"php $argv[0] false Display full usage of this script.".$n.
"php $argv[0] true full Run this script with all options."
)
);
}
if ($argCnt === 2) {
if ($argv[1] === 'false') {
exit(
"php $argv[0] arg1 arg2 arg3 arg4".$n.$n.
'arg1 (Required) = true/false'.$n.
' true = Run this script and delete releases.'.$n.
' false = Run this script and show what could be deleted.'.$n.$n.
'arg2 (Required) = full/number'.$n.
' full = Run without a time limit.'.$n.
' number = Run on releases up to this old.'.$n.$n.
'arg3 (Optional) = blacklist | blfiles | codec | executable | gibberish | hashed | huge | nzb | installbin | passworded | passwordurl | sample | scr | short | size | wmv_all'.$n.
' blfiles = Remove releases using the enabled blacklists in admin section of site against filenames.'.$n.
' codec = Remove releases where the release contains AVI or WMV file and is in x264 category (the spammer).'.$n.
' executable = Remove releases containing an exe file.'.$n.
' gibberish = Remove releases where the name is letters/numbers only and 15 characters or longer.'.$n.
' hashed = Remove releases where the name is letters/numbers only and 25 characters or longer.'.$n.
' nzb = Remove releases having 1 file that is an nzb file.'.$n.
' installbin = Remove releases which contain an install.bin file.'.$n.
' passworded = Remove releases which contain the word password in the title.'.$n.
' passwordurl = Remove releases which contain a password.url file.'.$n.
' sample = Remove releases that are smaller than 40MB more than 1 file and have sample in the title'.$n.
' scr = Remove releases where .scr extension is found in the files or subject.'.$n.
' short = Remove releases where the name is only numbers or letters and is 5 characters or less.'.$n.
' wmv_all = Remove releases where the release contains WMV file in any group!!.'.$n.
' size = Remove releases smaller than minimum size to create a release set in site settings.'.$n.$n.
'examples:'.$n.
"php $argv[0] true 12 blacklist = Remove releases up to 12 hours old using site blacklists.".$n.
"php $argv[0] false full = Show what releases could have been removed.".$n.
"php $argv[0] true full installbin = Remove releases which containing an install.bin file.".$n.
"php $argv[0] true full blacklist 1 = Remove releases matching blacklist id 1.".$n
);
} else {
exit(cli()->error("Wrong usage! Type php $argv[0] false"));
}
}
if ($argCnt < 3) {
exit(cli()->error("Wrong usage! Type php $argv[0] false"));
}
if (isset($argv[3]) && $argv[3] === 'blacklist' && isset($argv[4])) {
$blacklistID = $argv[4];
}
$RR = new ReleaseRemover;
$RR->removeCrap($argv[1] === 'true' ? true : false, $argv[2], $argv[3] ?? '', isset($blacklistID) ? $argv[4] : '');
-25
View File
@@ -1,25 +0,0 @@
<?php
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use App\Services\FanartTvService;
$fanart = new FanartTvService();
if (! empty($argv[1])) {
// Test if you can fetch Fanart.TV images
// Search for a movie/tv
$moviefanart = $fanart->getMovieFanArt((string) $argv[1]);
if ($moviefanart) {
dump($moviefanart);
} else {
cli()->error('Error retrieving Fanart.TV data.');
exit();
}
} else {
cli()->error('Invalid arguments. This script requires a number or string (TMDB or IMDb ID.');
exit();
}
-33
View File
@@ -1,33 +0,0 @@
<?php
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use aharen\OMDbAPI;
$omdb = new OMDbAPI(config('nntmux_api.omdb_api_key'));
if (! empty($argv[1]) && ! empty($argv[2]) && ($argv[2] !== 'series' || $argv[2] !== 'movie')) {
// Test if your OMDb API key and configuration are working
// If it works you should get a printed array of the show entered
// Search for a show
$search = $omdb->search((string) $argv[1], (string) $argv[2]);
if (is_object($search) && $search->data->Response !== 'False') {
dump($search->data->Search[0]->Title.PHP_EOL);
}
// Use the first show found (highest match) and get the requested season/episode from $argv
if (is_object($search) && $search->data->Response !== 'False') {
$search = $omdb->fetch('i', $search->data->Search[0]->imdbID);
dump($search);
} else {
cli()->error('Error retrieving OMDb API data.');
exit();
}
} else {
cli()->error('Invalid arguments. This script requires a text string (show name), and a second argument, movie or series.');
exit();
}
-75
View File
@@ -1,75 +0,0 @@
<?php
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use App\Services\TmdbClient;
if (! empty($argv[1]) && is_numeric($argv[2]) && is_numeric($argv[3])) {
// Test if your TMDB API configuration is working
// If it works you should get a var dumped array of the show/season/episode entered
$tmdbClient = app(TmdbClient::class);
if (! $tmdbClient->isConfigured()) {
exit(cli()->error('TMDB API key is not configured. Please set your API key in the configuration.'));
}
$season = (int) $argv[2];
$episode = (int) $argv[3];
// Search for a show
$series = $tmdbClient->searchTv((string) $argv[1]);
// Use the first show found (highest match) and get the requested season/episode from $argv
$totalResults = $series !== null ? TmdbClient::getInt($series, 'total_results') : 0;
$results = $series !== null ? TmdbClient::getArray($series, 'results') : [];
if (! empty($results) && $totalResults > 0) {
$showId = TmdbClient::getInt($results[0], 'id');
if ($showId === 0) {
exit(cli()->error('Invalid show ID returned from TMDB API.'));
}
// Get TV show details with networks
$tvShowDetails = $tmdbClient->getTvShow($showId);
$networks = $tvShowDetails !== null ? TmdbClient::getArray($tvShowDetails, 'networks') : [];
// Get alternative titles
$alternativeTitlesResponse = $tmdbClient->getTvAlternativeTitles($showId);
$alternativeTitles = $alternativeTitlesResponse !== null ? TmdbClient::getArray($alternativeTitlesResponse, 'results') : [];
// Get external IDs
$externalIds = $tmdbClient->getTvExternalIds($showId);
// Merge additional data into results
$results[0]['networks'] = $networks;
$results[0]['alternative_titles'] = $alternativeTitles;
$results[0]['external_ids'] = $externalIds ?? [];
if ($season > 0 && $episode > 0) {
$episodeObj = $tmdbClient->getTvEpisode($showId, $season, $episode);
if ($episodeObj !== null) {
echo "Show: ".TmdbClient::getString($results[0], 'name')."\n";
echo "Season: $season, Episode: $episode\n";
print_r($episodeObj);
} else {
exit(cli()->error('Episode not found on TMDB API.'));
}
} elseif ($season === 0 && $episode === 0) {
$tvShowFull = $tmdbClient->getTvShow($showId);
if ($tvShowFull !== null) {
print_r($tvShowFull);
}
} else {
exit(cli()->error('Invalid episode data returned from TMDB API.'));
}
} else {
exit(cli()->error('Invalid show data returned from TMDB API.'));
}
} else {
exit(cli()->error('Invalid arguments. This script requires a text string (show name) followed by a season and episode number.'));
}
-29
View File
@@ -1,29 +0,0 @@
<?php
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use App\Services\TraktService;
$trakt = new TraktService;
if (! empty($argv[1]) && is_numeric($argv[2]) && is_numeric($argv[3])) {
// Test if your Trakt API key and configuration are working
// If it works you should get a printed array of the show/season/episode entered
// Search for a show
$series = $trakt->searchShows((string) $argv[1], 'show');
// Use the first show found (highest match) and get the requested season/episode from $argv
if (is_array($series)) {
$series = $trakt->getShowSummary($series[0]['show']['ids']['trakt'], 'full');
$episode = $trakt->getEpisodeSummary($series['ids']['trakt'], (int) $argv[2], (int) $argv[3], 'full');
print_r($series);
print_r($episode);
} else {
exit(cli()->error('Error retrieving Trakt data.'));
}
} else {
exit(cli()->error('Invalid arguments. This script requires a text string (show name) followed by a season and episode number.'));
}
-68
View File
@@ -1,68 +0,0 @@
<?php
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use App\Services\TvProcessing\Providers\TvdbProvider;
$tvDB = new TvdbProvider;
if (! empty($argv[1]) && isset($argv[2], $argv[3]) && is_numeric($argv[2]) && is_numeric($argv[3])) {
// Test if your TvDB API key and configuration are working
// If it works you should get a var dumped array of the show/season/episode entered
$season = (int) $argv[2];
$episode = (int) $argv[3];
// Search for a show
$series = $tvDB->client->search()->search((string) $argv[1], ['type' => 'series']);
// Use the first show found (highest match) and get the requested season/episode from $argv
if ($series) {
print_r($series[0]);
$initialEpisodeObj = '';
if ($season > 0 && $episode > 0) {
try {
$initialEpisodeObj = $tvDB->client->series()->episodes($series[0]->tvdb_id);
} catch (InvalidArgumentException $error) {
echo 'Invalid argument(s) used'.PHP_EOL;
return false;
} catch (CanIHaveSomeCoffee\TheTVDbAPI\Exception\ParseException $error) {
if (str_starts_with($error->getMessage(), 'Could not decode JSON data') || str_starts_with($error->getMessage(), 'Incorrect data structure')) {
return false;
}
} catch (CanIHaveSomeCoffee\TheTVDbAPI\Exception\ResourceNotFoundException $error) {
return false;
} catch (CanIHaveSomeCoffee\TheTVDbAPI\Exception\UnauthorizedException $error) {
if (str_starts_with($error->getMessage(), 'Unauthorized')) {
return false;
}
}
if ($initialEpisodeObj) {
foreach ($initialEpisodeObj as $episodeBaseRecord) {
if ($episodeBaseRecord->number === $episode && $episodeBaseRecord->seasonNumber === $season) {
$episodeObj = $episodeBaseRecord;
}
}
print_r($episodeObj);
}
} elseif ($season === 0 && $episode === 0) {
$initialEpisodeObj = $tvDB->client->series()->allEpisodes($series[0]->tvdb_id);
if (is_object($initialEpisodeObj)) {
foreach ($initialEpisodeObj as $ep) {
print_r($ep);
}
}
} else {
exit(cli()->error('Invalid episode data returned from TVDB API.'));
}
} else {
exit(cli()->error('Invalid show data returned from TVDB API.'));
}
} else {
exit(cli()->error('Invalid arguments. This script requires a text string (show name) followed by a season and episode number.')
);
}
-46
View File
@@ -1,46 +0,0 @@
<?php
require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use App\Services\TvProcessing\Providers\TvMazeProvider;
$tvmaze = new TvMazeProvider;
if (! empty($argv[1]) && is_numeric($argv[2]) && is_numeric($argv[3])) {
// Test if your TVMaze API configuration is working
// If it works you should get a var dumped array of the show/season/episode entered
$season = (int) $argv[2];
$episode = (int) $argv[3];
// Search for a show
$series = $tvmaze->client->search((string) $argv[1]);
// Use the first show found (highest match) and get the requested season/episode from $argv
if ($series) {
echo PHP_EOL.cli()->info('Server Time: '.$serverTime).PHP_EOL;
print_r($series[0]);
if ($season > 0 and $episode > 0) {
$episodeObj = $tvmaze->client->getEpisodeByNumber($series[0]->id, $season, $episode);
if ($episodeObj) {
print_r($episodeObj);
}
} elseif ($season == 0 && $episode == 0) {
$episodeObj = $tvmaze->client->getEpisodesByShowID($series[0]->id);
if (is_array($episodeObj)) {
echo '*';
foreach ($episodeObj as $ep) {
print_r($ep);
}
}
} else {
exit(cli()->error('Invalid episode data returned from TVMaze API.'));
}
} else {
exit(cli()->error('Invalid show data returned from TVMaze API.'));
}
} else {
exit(cli()->error('Invalid arguments. This script requires a text string (show name) followed by a season and episode number.'));
}
@@ -1,19 +0,0 @@
<?php
use Spatie\Permission\Models\Permission;
require_once dirname(__DIR__, 2).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
// Reset cached roles and permissions
app('cache')->forget('spatie.permission.cache');
Permission::create(['name' => 'preview', 'guard_name' => 'api']);
Permission::create(['name' => 'hideads', 'guard_name' => 'api']);
Permission::create(['name' => 'edit release', 'guard_name' => 'api']);
Permission::create(['name' => 'view console', 'guard_name' => 'api']);
Permission::create(['name' => 'view movies', 'guard_name' => 'api']);
Permission::create(['name' => 'view audio', 'guard_name' => 'api']);
Permission::create(['name' => 'view pc', 'guard_name' => 'api']);
Permission::create(['name' => 'view tv', 'guard_name' => 'api']);
Permission::create(['name' => 'view adult', 'guard_name' => 'api']);
Permission::create(['name' => 'view books', 'guard_name' => 'api']);
Permission::create(['name' => 'view other', 'guard_name' => 'api']);
@@ -1,19 +0,0 @@
<?php
use Spatie\Permission\Models\Permission;
require_once dirname(__DIR__, 2).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
// Reset cached roles and permissions
app('cache')->forget('spatie.permission.cache');
Permission::create(['name' => 'preview', 'guard_name' => 'rss']);
Permission::create(['name' => 'hideads', 'guard_name' => 'rss']);
Permission::create(['name' => 'edit release', 'guard_name' => 'rss']);
Permission::create(['name' => 'view console', 'guard_name' => 'rss']);
Permission::create(['name' => 'view movies', 'guard_name' => 'rss']);
Permission::create(['name' => 'view audio', 'guard_name' => 'rss']);
Permission::create(['name' => 'view pc', 'guard_name' => 'rss']);
Permission::create(['name' => 'view tv', 'guard_name' => 'rss']);
Permission::create(['name' => 'view adult', 'guard_name' => 'rss']);
Permission::create(['name' => 'view books', 'guard_name' => 'rss']);
Permission::create(['name' => 'view other', 'guard_name' => 'rss']);
-83
View File
@@ -1,83 +0,0 @@
<?php
require_once dirname(__DIR__, 2).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use App\Services\Nzb\NzbImportService;
use Illuminate\Support\Str;
$n = PHP_EOL;
// Print usage.
if (count($argv) !== 6) {
exit(
'This will import NZB files(.nzb or .nzb.gz), into your newznab site from a folder recursively(it will go down into sub-folders).'.$n.
'Please use arg5, something sensible like 100k, if you have millions of NZB files the initial scan will be VERY slow otherwise.'.$n.$n.
'Usage: '.$n.
$_SERVER['_'].' '.__FILE__.' arg1 arg2 arg3 arg4 arg5'.$n.$n.
'arg1 : Path to folder where NZB files are stored. | a folder path'.$n.
'arg2 : Delete NZB when successfully imported.(recommended) | true/false'.$n.
'arg3 : Delete NZB when unsuccessfully imported.(not recommended) | true/false'.$n.
'arg4 : Use NZB file name as release name.(not recommended) | true/false'.$n.
'arg5 : Import this many NZB files. (RECOMMENDED 100,000) | a number'.$n.$n.
'ie: '.$_SERVER['_'].' '.__FILE__.' '.base_path('nzbToImport/ true false false 1000').$n
);
}
// Verify arguments.
if (! is_dir($argv[1])) {
exit('Error: arg1 must be a path (you might not have read access to this path)'.$n);
}
if (! in_array($argv[2], ['true', 'false'], false)) {
exit('Error: arg2 must be true or false'.$n);
}
if (! in_array($argv[3], ['true', 'false'], false)) {
exit('Error: arg3 must be true or false'.$n);
}
if (! in_array($argv[4], ['true', 'false'], false)) {
exit('Error: arg4 must be true or false'.$n);
}
if (! is_numeric($argv[5])) {
exit('Error: arg5 must be a number'.$n);
}
if ($argv[5] < 0) {
exit('Error: arg5 must be 0 or higher'.$n);
}
$path = $argv[1];
// Check if path ends with dir separator.
if (! Str::endsWith($path, '/')) {
$path .= '/';
}
$files = new RegexIterator(
new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($argv[1])
),
'/^.+\.nzb(\.gz)?$/i',
RecursiveRegexIterator::GET_MATCH
);
$i = 1;
$nzbFiles = [];
foreach ($files as $file) {
$nzbFiles[] = $file[0];
if ($i++ >= $argv[5]) {
break;
}
}
if ($i > 1) {
unset($files);
// Check these user argument values, convert them to bool.
$deleteNZB = ($argv[2] === 'true');
$deleteFailedNZB = ($argv[3] === 'true');
$useNzbName = ($argv[4] === 'true');
// Create a new instance of NzbImportService and send it the file locations.
$NZBImport = new NzbImportService;
$NZBImport->beginImport($nzbFiles, $useNzbName, $deleteNZB, $deleteFailedNZB);
} else {
echo 'Nothing found to import!'.$n;
}
-17
View File
@@ -1,17 +0,0 @@
<?php
use App\Models\User;
use Jrean\UserVerification\Facades\UserVerification;
require_once dirname(__DIR__, 2).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
if (isset($argv[1]) && is_numeric($argv[1])) {
$user = User::find($argv[1]);
UserVerification::generate($user);
UserVerification::send($user, 'User email verification required');
cli()->info('Email has been sent');
} else {
cli()->error('You need to provide user id as argument');
}
+1 -1
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" bootstrap="./bootstrap/autoload.php" colors="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.2/phpunit.xsd" cacheDirectory=".phpunit.cache" backupStaticProperties="false"> <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" bootstrap="vendor/autoload.php" colors="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.2/phpunit.xsd" cacheDirectory=".phpunit.cache" backupStaticProperties="false">
<testsuites> <testsuites>
<testsuite name="Install"> <testsuite name="Install">
<directory suffix="Test.php">tests/Install</directory> <directory suffix="Test.php">tests/Install</directory>
+1 -1
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" bootstrap="./bootstrap/autoload.php" colors="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.0/phpunit.xsd" cacheDirectory=".phpunit.cache" backupStaticProperties="false"> <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" bootstrap="vendor/autoload.php" colors="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.0/phpunit.xsd" cacheDirectory=".phpunit.cache" backupStaticProperties="false">
<coverage> <coverage>
<include> <include>
<directory suffix=".php">Blacklight</directory> <directory suffix=".php">Blacklight</directory>
+118
View File
@@ -0,0 +1,118 @@
<?php
namespace Tests\Feature;
use App\Services\FanartTvService;
use Tests\TestCase;
/**
* Tests for Fanart.tv API integration.
*
* These tests verify that the Fanart.tv API is properly configured and responding.
*/
class FanarttvApiTest extends TestCase
{
private FanartTvService $fanartService;
protected function setUp(): void
{
parent::setUp();
$this->fanartService = new FanartTvService;
}
public function test_fanarttv_service_is_configured(): void
{
if (! $this->fanartService->isConfigured()) {
$this->markTestSkipped('Fanart.tv API key is not configured.');
}
$this->assertTrue($this->fanartService->isConfigured());
}
public function test_can_get_movie_fanart_by_imdb_id(): void
{
if (! $this->fanartService->isConfigured()) {
$this->markTestSkipped('Fanart.tv API key is not configured.');
}
// The Shawshank Redemption IMDb ID
$movieData = $this->fanartService->getMovieFanArt('tt0111161');
$this->assertNotNull($movieData);
$this->assertIsArray($movieData);
}
public function test_can_get_movie_fanart_by_tmdb_id(): void
{
if (! $this->fanartService->isConfigured()) {
$this->markTestSkipped('Fanart.tv API key is not configured.');
}
// The Shawshank Redemption TMDB ID
$movieData = $this->fanartService->getMovieFanArt('278');
$this->assertNotNull($movieData);
$this->assertIsArray($movieData);
}
public function test_can_get_movie_properties(): void
{
if (! $this->fanartService->isConfigured()) {
$this->markTestSkipped('Fanart.tv API key is not configured.');
}
// The Shawshank Redemption IMDb ID
$props = $this->fanartService->getMovieProperties('tt0111161');
// May return null if no cover AND backdrop available
if ($props !== null) {
$this->assertIsArray($props);
$this->assertArrayHasKey('title', $props);
} else {
$this->assertNull($props);
}
}
public function test_can_get_tv_fanart(): void
{
if (! $this->fanartService->isConfigured()) {
$this->markTestSkipped('Fanart.tv API key is not configured.');
}
// Breaking Bad TVDB ID
$tvData = $this->fanartService->getTvFanArt('81189');
$this->assertNotNull($tvData);
$this->assertIsArray($tvData);
}
public function test_can_get_best_tv_poster(): void
{
if (! $this->fanartService->isConfigured()) {
$this->markTestSkipped('Fanart.tv API key is not configured.');
}
// Breaking Bad TVDB ID
$poster = $this->fanartService->getBestTvPoster('81189');
// May return null if no poster available
if ($poster !== null) {
$this->assertIsString($poster);
$this->assertStringStartsWith('http', $poster);
} else {
$this->assertNull($poster);
}
}
public function test_returns_null_for_invalid_id(): void
{
if (! $this->fanartService->isConfigured()) {
$this->markTestSkipped('Fanart.tv API key is not configured.');
}
$movieData = $this->fanartService->getMovieFanArt('tt9999999999');
$this->assertNull($movieData);
}
}
+107
View File
@@ -0,0 +1,107 @@
<?php
namespace Tests\Feature;
use aharen\OMDbAPI;
use Tests\TestCase;
/**
* Tests for OMDb API integration.
*
* These tests verify that the OMDb API is properly configured and responding.
*/
class OmdbApiTest extends TestCase
{
private ?OMDbAPI $omdb = null;
protected function setUp(): void
{
parent::setUp();
$apiKey = config('nntmux_api.omdb_api_key');
if (! empty($apiKey)) {
$this->omdb = new OMDbAPI($apiKey);
}
}
public function test_omdb_api_key_is_configured(): void
{
if ($this->omdb === null) {
$this->markTestSkipped('OMDb API key is not configured.');
}
$this->assertNotNull($this->omdb);
}
public function test_can_search_for_movies(): void
{
if ($this->omdb === null) {
$this->markTestSkipped('OMDb API key is not configured.');
}
$search = $this->omdb->search('The Matrix', 'movie');
$this->assertIsObject($search);
$this->assertNotEquals('False', $search->data->Response);
$this->assertNotEmpty($search->data->Search);
}
public function test_can_search_for_series(): void
{
if ($this->omdb === null) {
$this->markTestSkipped('OMDb API key is not configured.');
}
$search = $this->omdb->search('Breaking Bad', 'series');
$this->assertIsObject($search);
$this->assertNotEquals('False', $search->data->Response);
$this->assertNotEmpty($search->data->Search);
}
public function test_can_fetch_by_imdb_id(): void
{
if ($this->omdb === null) {
$this->markTestSkipped('OMDb API key is not configured.');
}
// The Matrix IMDb ID
$result = $this->omdb->fetch('i', 'tt0133093');
$this->assertIsObject($result);
$this->assertNotEquals('False', $result->data->Response);
$this->assertEquals('The Matrix', $result->data->Title);
}
public function test_returns_false_response_for_invalid_search(): void
{
if ($this->omdb === null) {
$this->markTestSkipped('OMDb API key is not configured.');
}
$search = $this->omdb->search('xyznonexistentmovie99999', 'movie');
$this->assertIsObject($search);
$this->assertEquals('False', $search->data->Response);
}
public function test_search_result_has_expected_properties(): void
{
if ($this->omdb === null) {
$this->markTestSkipped('OMDb API key is not configured.');
}
$search = $this->omdb->search('The Matrix', 'movie');
if ($search->data->Response === 'False') {
$this->markTestSkipped('OMDb search returned no results.');
}
$firstResult = $search->data->Search[0];
$this->assertObjectHasProperty('Title', $firstResult);
$this->assertObjectHasProperty('imdbID', $firstResult);
$this->assertObjectHasProperty('Year', $firstResult);
}
}
+118
View File
@@ -0,0 +1,118 @@
<?php
namespace Tests\Feature;
use App\Services\TmdbClient;
use Tests\TestCase;
/**
* Tests for TMDB API integration.
*
* These tests verify that the TMDB API is properly configured and responding.
*/
class TmdbApiTest extends TestCase
{
private TmdbClient $tmdbClient;
protected function setUp(): void
{
parent::setUp();
$this->tmdbClient = app(TmdbClient::class);
}
public function test_tmdb_client_is_configured(): void
{
// Skip if not configured
if (! $this->tmdbClient->isConfigured()) {
$this->markTestSkipped('TMDB API key is not configured.');
}
$this->assertTrue($this->tmdbClient->isConfigured());
}
public function test_can_search_tv_shows(): void
{
if (! $this->tmdbClient->isConfigured()) {
$this->markTestSkipped('TMDB API key is not configured.');
}
$results = $this->tmdbClient->searchTv('Breaking Bad');
$this->assertNotNull($results);
$totalResults = TmdbClient::getInt($results, 'total_results');
$this->assertGreaterThan(0, $totalResults);
}
public function test_can_get_tv_show_details(): void
{
if (! $this->tmdbClient->isConfigured()) {
$this->markTestSkipped('TMDB API key is not configured.');
}
// Breaking Bad TMDB ID
$showId = 1396;
$details = $this->tmdbClient->getTvShow($showId);
$this->assertNotNull($details);
$this->assertEquals('Breaking Bad', TmdbClient::getString($details, 'name'));
}
public function test_can_get_tv_episode(): void
{
if (! $this->tmdbClient->isConfigured()) {
$this->markTestSkipped('TMDB API key is not configured.');
}
// Breaking Bad TMDB ID, Season 1, Episode 1
$showId = 1396;
$episode = $this->tmdbClient->getTvEpisode($showId, 1, 1);
$this->assertNotNull($episode);
$this->assertEquals('Pilot', TmdbClient::getString($episode, 'name'));
}
public function test_can_get_external_ids(): void
{
if (! $this->tmdbClient->isConfigured()) {
$this->markTestSkipped('TMDB API key is not configured.');
}
// Breaking Bad TMDB ID
$showId = 1396;
$externalIds = $this->tmdbClient->getTvExternalIds($showId);
$this->assertNotNull($externalIds);
$this->assertArrayHasKey('imdb_id', $externalIds);
}
public function test_can_get_alternative_titles(): void
{
if (! $this->tmdbClient->isConfigured()) {
$this->markTestSkipped('TMDB API key is not configured.');
}
// Breaking Bad TMDB ID
$showId = 1396;
$alternativeTitles = $this->tmdbClient->getTvAlternativeTitles($showId);
$this->assertNotNull($alternativeTitles);
}
public function test_search_returns_null_for_invalid_query(): void
{
if (! $this->tmdbClient->isConfigured()) {
$this->markTestSkipped('TMDB API key is not configured.');
}
$results = $this->tmdbClient->searchTv('xyznonexistentshow12345');
// Should return results array but with 0 total_results
if ($results !== null) {
$totalResults = TmdbClient::getInt($results, 'total_results');
$this->assertEquals(0, $totalResults);
} else {
$this->assertNull($results);
}
}
}
+132
View File
@@ -0,0 +1,132 @@
<?php
namespace Tests\Feature;
use App\Services\TraktService;
use Tests\TestCase;
/**
* Tests for Trakt.tv API integration.
*
* These tests verify that the Trakt.tv API is properly configured and responding.
*/
class TraktApiTest extends TestCase
{
private TraktService $traktService;
protected function setUp(): void
{
parent::setUp();
$this->traktService = new TraktService;
}
public function test_trakt_service_is_configured(): void
{
if (! $this->traktService->isConfigured()) {
$this->markTestSkipped('Trakt.tv API key is not configured.');
}
$this->assertTrue($this->traktService->isConfigured());
}
public function test_can_search_for_shows(): void
{
if (! $this->traktService->isConfigured()) {
$this->markTestSkipped('Trakt.tv API key is not configured.');
}
$results = $this->traktService->searchShows('Breaking Bad', 'show');
$this->assertNotNull($results);
$this->assertIsArray($results);
$this->assertNotEmpty($results);
}
public function test_can_get_show_summary(): void
{
if (! $this->traktService->isConfigured()) {
$this->markTestSkipped('Trakt.tv API key is not configured.');
}
// First search for Breaking Bad
$results = $this->traktService->searchShows('Breaking Bad', 'show');
if (empty($results)) {
$this->markTestSkipped('Could not find Breaking Bad on Trakt.tv.');
}
$traktId = $results[0]['show']['ids']['trakt'];
$summary = $this->traktService->getShowSummary($traktId, 'full');
$this->assertNotNull($summary);
$this->assertIsArray($summary);
$this->assertEquals('Breaking Bad', $summary['title']);
}
public function test_can_get_episode_summary(): void
{
if (! $this->traktService->isConfigured()) {
$this->markTestSkipped('Trakt.tv API key is not configured.');
}
// First search for Breaking Bad
$results = $this->traktService->searchShows('Breaking Bad', 'show');
if (empty($results)) {
$this->markTestSkipped('Could not find Breaking Bad on Trakt.tv.');
}
$traktId = $results[0]['show']['ids']['trakt'];
// Get Season 1, Episode 1
$episode = $this->traktService->getEpisodeSummary($traktId, 1, 1, 'full');
$this->assertNotNull($episode);
$this->assertIsArray($episode);
$this->assertEquals('Pilot', $episode['title']);
}
public function test_can_get_trending_shows(): void
{
if (! $this->traktService->isConfigured()) {
$this->markTestSkipped('Trakt.tv API key is not configured.');
}
$trending = $this->traktService->getTrendingShows(5);
$this->assertNotNull($trending);
$this->assertIsArray($trending);
$this->assertCount(5, $trending);
}
public function test_search_result_has_expected_structure(): void
{
if (! $this->traktService->isConfigured()) {
$this->markTestSkipped('Trakt.tv API key is not configured.');
}
$results = $this->traktService->searchShows('Breaking Bad', 'show');
if (empty($results)) {
$this->markTestSkipped('Could not find Breaking Bad on Trakt.tv.');
}
$firstResult = $results[0];
$this->assertArrayHasKey('show', $firstResult);
$this->assertArrayHasKey('title', $firstResult['show']);
$this->assertArrayHasKey('ids', $firstResult['show']);
$this->assertArrayHasKey('trakt', $firstResult['show']['ids']);
}
public function test_returns_empty_for_invalid_search(): void
{
if (! $this->traktService->isConfigured()) {
$this->markTestSkipped('Trakt.tv API key is not configured.');
}
$results = $this->traktService->searchShows('xyznonexistentshow99999', 'show');
$this->assertTrue(empty($results) || $results === null);
}
}
+125
View File
@@ -0,0 +1,125 @@
<?php
namespace Tests\Feature;
use App\Services\TvProcessing\Providers\TvdbProvider;
use Tests\TestCase;
/**
* Tests for TVDB API integration.
*
* These tests verify that the TVDB API is properly configured and responding.
*/
class TvdbApiTest extends TestCase
{
private ?TvdbProvider $tvdbProvider = null;
protected function setUp(): void
{
parent::setUp();
try {
$this->tvdbProvider = new TvdbProvider;
} catch (\Exception $e) {
$this->tvdbProvider = null;
}
}
public function test_tvdb_provider_can_be_instantiated(): void
{
if ($this->tvdbProvider === null) {
$this->markTestSkipped('TVDB API is not configured or unavailable.');
}
$this->assertInstanceOf(TvdbProvider::class, $this->tvdbProvider);
}
public function test_can_search_for_series(): void
{
if ($this->tvdbProvider === null) {
$this->markTestSkipped('TVDB API is not configured or unavailable.');
}
try {
$results = $this->tvdbProvider->client->search()->search('Breaking Bad', ['type' => 'series']);
$this->assertNotEmpty($results);
$this->assertIsArray($results);
} catch (\Exception $e) {
$this->markTestSkipped('TVDB API search failed: '.$e->getMessage());
}
}
public function test_can_get_series_episodes(): void
{
if ($this->tvdbProvider === null) {
$this->markTestSkipped('TVDB API is not configured or unavailable.');
}
try {
// First search for Breaking Bad
$results = $this->tvdbProvider->client->search()->search('Breaking Bad', ['type' => 'series']);
if (empty($results)) {
$this->markTestSkipped('Could not find Breaking Bad on TVDB.');
}
$tvdbId = $results[0]->tvdb_id;
$episodes = $this->tvdbProvider->client->series()->episodes($tvdbId);
$this->assertNotEmpty($episodes);
} catch (\Exception $e) {
$this->markTestSkipped('TVDB API episode fetch failed: '.$e->getMessage());
}
}
public function test_can_find_specific_episode(): void
{
if ($this->tvdbProvider === null) {
$this->markTestSkipped('TVDB API is not configured or unavailable.');
}
try {
// Search for Breaking Bad
$results = $this->tvdbProvider->client->search()->search('Breaking Bad', ['type' => 'series']);
if (empty($results)) {
$this->markTestSkipped('Could not find Breaking Bad on TVDB.');
}
$tvdbId = $results[0]->tvdb_id;
$episodes = $this->tvdbProvider->client->series()->episodes($tvdbId);
// Find Season 1 Episode 1
$foundEpisode = null;
foreach ($episodes as $episode) {
if ($episode->seasonNumber === 1 && $episode->number === 1) {
$foundEpisode = $episode;
break;
}
}
$this->assertNotNull($foundEpisode, 'Should find S01E01 of Breaking Bad');
} catch (\Exception $e) {
$this->markTestSkipped('TVDB API episode search failed: '.$e->getMessage());
}
}
public function test_handles_invalid_search_gracefully(): void
{
if ($this->tvdbProvider === null) {
$this->markTestSkipped('TVDB API is not configured or unavailable.');
}
try {
$results = $this->tvdbProvider->client->search()->search('xyznonexistentshow99999', ['type' => 'series']);
// Should return empty or null for non-existent shows
$this->assertTrue(empty($results) || $results === null);
} catch (\Exception $e) {
// API might throw exception for no results, which is acceptable
$this->assertTrue(true);
}
}
}
+115
View File
@@ -0,0 +1,115 @@
<?php
namespace Tests\Feature;
use App\Services\TvProcessing\Providers\TvMazeProvider;
use Tests\TestCase;
/**
* Tests for TVMaze API integration.
*
* These tests verify that the TVMaze API is properly responding.
* Note: TVMaze API does not require an API key.
*/
class TvmazeApiTest extends TestCase
{
private TvMazeProvider $tvmazeProvider;
protected function setUp(): void
{
parent::setUp();
$this->tvmazeProvider = new TvMazeProvider;
}
public function test_tvmaze_provider_can_be_instantiated(): void
{
$this->assertInstanceOf(TvMazeProvider::class, $this->tvmazeProvider);
}
public function test_can_search_for_shows(): void
{
try {
$results = $this->tvmazeProvider->client->search('Breaking Bad');
$this->assertNotEmpty($results);
$this->assertIsArray($results);
} catch (\Exception $e) {
$this->markTestSkipped('TVMaze API search failed: '.$e->getMessage());
}
}
public function test_can_get_episode_by_number(): void
{
try {
// First search for Breaking Bad
$results = $this->tvmazeProvider->client->search('Breaking Bad');
if (empty($results)) {
$this->markTestSkipped('Could not find Breaking Bad on TVMaze.');
}
$showId = $results[0]->id;
// Get Season 1, Episode 1
$episode = $this->tvmazeProvider->client->getEpisodeByNumber($showId, 1, 1);
$this->assertNotNull($episode);
$this->assertEquals('Pilot', $episode->name);
} catch (\Exception $e) {
$this->markTestSkipped('TVMaze API episode fetch failed: '.$e->getMessage());
}
}
public function test_can_get_all_episodes_by_show_id(): void
{
try {
// First search for Breaking Bad
$results = $this->tvmazeProvider->client->search('Breaking Bad');
if (empty($results)) {
$this->markTestSkipped('Could not find Breaking Bad on TVMaze.');
}
$showId = $results[0]->id;
$episodes = $this->tvmazeProvider->client->getEpisodesByShowID($showId);
$this->assertNotEmpty($episodes);
$this->assertIsArray($episodes);
// Breaking Bad has 62 episodes
$this->assertGreaterThan(50, count($episodes));
} catch (\Exception $e) {
$this->markTestSkipped('TVMaze API episodes fetch failed: '.$e->getMessage());
}
}
public function test_search_returns_empty_for_nonexistent_show(): void
{
try {
$results = $this->tvmazeProvider->client->search('xyznonexistentshow99999');
$this->assertEmpty($results);
} catch (\Exception $e) {
// API might throw exception for no results
$this->assertTrue(true);
}
}
public function test_first_search_result_has_expected_properties(): void
{
try {
$results = $this->tvmazeProvider->client->search('Breaking Bad');
if (empty($results)) {
$this->markTestSkipped('Could not find Breaking Bad on TVMaze.');
}
$show = $results[0];
$this->assertObjectHasProperty('id', $show);
$this->assertObjectHasProperty('name', $show);
} catch (\Exception $e) {
$this->markTestSkipped('TVMaze API search failed: '.$e->getMessage());
}
}
}
+9 -7
View File
@@ -1,5 +1,8 @@
<?php <?php
/**
<?php
/** /**
* This file is part of NNTmux. * This file is part of NNTmux.
* *
@@ -12,21 +15,20 @@
* @version 0.0.1 * @version 0.0.1
*/ */
namespace tests; namespace Tests\Install;
require_once \dirname(__DIR__, 2).DIRECTORY_SEPARATOR.'bootstrap/autoload.php'; use Tests\TestCase;
/** /**
* Class InstallTest. * Class InstallTest.
*/ */
final class InstallTest extends \PHPUnit\Framework\TestCase final class InstallTest extends TestCase
{ {
public function test_full_install(): void public function test_full_install(): void
{ {
passthru('php '.base_path().'/artisan migrate:fresh --seed'); $this->artisan('migrate:fresh', ['--seed' => true])
->assertExitCode(0);
$message = 'NNTmux installation completed successfully'; $this->assertTrue(true, 'NNTmux installation completed successfully');
$this->assertEquals('NNTmux installation completed successfully', $message, 'Test Failed');
} }
} }
+185
View File
@@ -0,0 +1,185 @@
<?php
namespace Tests\Unit;
use App\Services\YencService;
use RuntimeException;
use Tests\TestCase;
class YencServiceTest extends TestCase
{
private YencService $yencService;
protected function setUp(): void
{
parent::setUp();
$this->yencService = new YencService;
}
public function test_enabled_returns_true(): void
{
$this->assertTrue($this->yencService->enabled());
}
public function test_encode_and_decode_round_trip(): void
{
$originalData = 'Hello, World! This is a test of yEnc encoding.';
$filename = 'test.txt';
// Encode
$encoded = $this->yencService->encode($originalData, $filename);
// Verify encoding contains headers
$this->assertStringContainsString('=ybegin', $encoded);
$this->assertStringContainsString('=yend', $encoded);
$this->assertStringContainsString('name=test.txt', $encoded);
$this->assertStringContainsString('crc32=', $encoded);
// Decode
$decoded = $this->yencService->decode($encoded);
$this->assertEquals($originalData, $decoded);
}
public function test_encode_respects_line_length(): void
{
$data = str_repeat('A', 500);
$filename = 'test.txt';
$encoded = $this->yencService->encode($data, $filename, 100);
// Check that no content line exceeds 100 characters
$lines = explode("\r\n", $encoded);
foreach ($lines as $line) {
// Skip header and trailer lines
if (str_starts_with($line, '=y')) {
continue;
}
$this->assertLessThanOrEqual(100, strlen($line));
}
}
public function test_encode_caps_line_length_at_254(): void
{
$data = 'Test data';
$filename = 'test.txt';
$encoded = $this->yencService->encode($data, $filename, 300);
// Should contain line=254 in header
$this->assertStringContainsString('line=254', $encoded);
}
public function test_encode_throws_exception_for_invalid_line_length(): void
{
$this->expectException(RuntimeException::class);
$this->yencService->encode('data', 'test.txt', 0);
}
public function test_decode_returns_false_for_non_yenc_data(): void
{
$nonYencData = 'This is just plain text.';
$result = $this->yencService->decode($nonYencData);
$this->assertFalse($result);
}
public function test_decode_ignore_returns_original_for_non_yenc_data(): void
{
$nonYencData = 'This is just plain text.';
$result = $this->yencService->decodeIgnore($nonYencData);
$this->assertEquals($nonYencData, $result);
}
public function test_decode_ignore_decodes_yenc_data(): void
{
$originalData = 'Test content for yEnc';
$encoded = $this->yencService->encode($originalData, 'test.txt');
$decoded = $this->yencService->decodeIgnore($encoded);
$this->assertEquals($originalData, $decoded);
}
public function test_is_yenc_encoded_returns_true_for_yenc(): void
{
$encoded = $this->yencService->encode('test', 'test.txt');
$this->assertTrue($this->yencService->isYencEncoded($encoded));
}
public function test_is_yenc_encoded_returns_false_for_plain_text(): void
{
$this->assertFalse($this->yencService->isYencEncoded('plain text'));
}
public function test_extract_metadata_returns_metadata(): void
{
$data = 'Test content';
$encoded = $this->yencService->encode($data, 'myfile.dat', 128, true);
$metadata = $this->yencService->extractMetadata($encoded);
$this->assertNotNull($metadata);
$this->assertEquals('myfile.dat', $metadata['name']);
$this->assertEquals(strlen($data), $metadata['size']);
$this->assertEquals(128, $metadata['line']);
$this->assertNotNull($metadata['crc32']);
}
public function test_extract_metadata_returns_null_for_non_yenc(): void
{
$metadata = $this->yencService->extractMetadata('plain text');
$this->assertNull($metadata);
}
public function test_encode_without_crc32(): void
{
$encoded = $this->yencService->encode('test', 'test.txt', 128, false);
$this->assertStringNotContainsString('crc32=', $encoded);
}
public function test_encode_with_special_characters(): void
{
// Test with characters that need escaping (NULL, TAB, LF, CR, space, ., =)
$data = "Hello\x00World\tTest\nNew\rLine . = End";
$encoded = $this->yencService->encode($data, 'special.bin');
$decoded = $this->yencService->decode($encoded);
$this->assertEquals($data, $decoded);
}
public function test_encode_with_binary_data(): void
{
// Test with all possible byte values
$data = '';
for ($i = 0; $i < 256; $i++) {
$data .= chr($i);
}
$encoded = $this->yencService->encode($data, 'binary.bin');
$decoded = $this->yencService->decode($encoded);
$this->assertEquals($data, $decoded);
}
public function test_service_can_be_resolved_from_container(): void
{
$service = app(YencService::class);
$this->assertInstanceOf(YencService::class, $service);
}
public function test_facade_works(): void
{
$this->assertTrue(\App\Facades\Yenc::enabled());
}
}