mirror of
https://github.com/NNTmux/newznab-tmux.git
synced 2026-08-28 22:01:33 +00:00
488 lines
15 KiB
PHP
488 lines
15 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Services\NNTP\NNTPService;
|
|
use App\Services\Nzb\NzbService;
|
|
use App\Services\ReleaseImageService;
|
|
use App\Services\Releases\ReleaseManagementService;
|
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
|
use Illuminate\Database\Eloquent\Collection;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
/**
|
|
* App\Models\Group.
|
|
*
|
|
* @property int $id
|
|
* @property string $name
|
|
* @property int $backfill_target
|
|
* @property int $first_record
|
|
* @property string|null $first_record_postdate
|
|
* @property int $last_record
|
|
* @property string|null $last_record_postdate
|
|
* @property string|null $last_updated
|
|
* @property int|null $minfilestoformrelease
|
|
* @property int|null $minsizetoformrelease
|
|
* @property bool $active
|
|
* @property bool $backfill
|
|
* @property string|null $description
|
|
* @property-read Collection|Release[] $release
|
|
*
|
|
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\UsenetGroup whereActive($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\UsenetGroup whereBackfill($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\UsenetGroup whereBackfillTarget($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\UsenetGroup whereDescription($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\UsenetGroup whereFirstRecord($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\UsenetGroup whereFirstRecordPostdate($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\UsenetGroup whereId($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\UsenetGroup whereLastRecord($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\UsenetGroup whereLastRecordPostdate($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\UsenetGroup whereLastUpdated($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\UsenetGroup whereMinfilestoformrelease($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\UsenetGroup whereMinsizetoformrelease($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\UsenetGroup whereName($value)
|
|
*
|
|
* @mixin \Eloquent
|
|
*
|
|
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\UsenetGroup newModelQuery()
|
|
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\UsenetGroup newQuery()
|
|
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\UsenetGroup query()
|
|
*/
|
|
class UsenetGroup extends Model
|
|
{
|
|
protected $dateFormat = false;
|
|
|
|
/**
|
|
* @var bool
|
|
*/
|
|
public $timestamps = false;
|
|
|
|
/**
|
|
* @var array<string>
|
|
*/
|
|
protected $guarded = [];
|
|
|
|
/**
|
|
* @var array<string, mixed>
|
|
*/
|
|
protected static $cbpm = ['collections', 'binaries', 'parts', 'missed_parts']; // @phpstan-ignore property.defaultValue
|
|
|
|
/**
|
|
* @var array<string, mixed>
|
|
*/
|
|
protected static $cbppTableNames;
|
|
|
|
/**
|
|
* @var bool
|
|
*/
|
|
protected $allasmgr;
|
|
|
|
/**
|
|
* Group constructor.
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
public function __construct()
|
|
{
|
|
parent::__construct();
|
|
}
|
|
|
|
/**
|
|
* @return HasMany<Release, $this>
|
|
*/
|
|
public function release(): HasMany
|
|
{
|
|
return $this->hasMany(Release::class, 'groups_id');
|
|
}
|
|
|
|
/**
|
|
* Returns an associative array of groups for list selection.
|
|
*
|
|
* @return array<string, mixed>
|
|
*/
|
|
public static function getGroupsForSelect(): array
|
|
{
|
|
$groups = self::getActive();
|
|
|
|
$temp_array = [];
|
|
$temp_array[-1] = '--Please Select--';
|
|
|
|
foreach ($groups as $group) {
|
|
$temp_array[$group['name']] = $group['name'];
|
|
}
|
|
|
|
return $temp_array;
|
|
}
|
|
|
|
/**
|
|
* Get all properties of a single group by its ID.
|
|
*
|
|
*
|
|
* @return Model|null|static
|
|
*/
|
|
public static function getGroupByID(mixed $id)
|
|
{
|
|
return self::query()->where('id', $id)->first();
|
|
}
|
|
|
|
public static function getActive(): mixed
|
|
{
|
|
return self::query()->where('active', '=', 1)->orderBy('name')->get();
|
|
}
|
|
|
|
/**
|
|
* Get active backfill groups ordered by name ascending.
|
|
*/
|
|
public static function getActiveBackfill(mixed $order): mixed
|
|
{
|
|
return match ($order) {
|
|
'', 'normal' => self::query()->where('backfill', '=', 1)->where('last_record', '<>', 0)->orderBy('name')->get(),
|
|
'date' => self::query()->where('backfill', '=', 1)->where('last_record', '<>', 0)->orderByDesc('first_record_postdate')->get(),
|
|
default => [],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get all active group IDs.
|
|
*/
|
|
public static function getActiveIDs(): mixed
|
|
{
|
|
return self::query()->where('active', '=', 1)->orderBy('name')->get(['id']);
|
|
}
|
|
|
|
/**
|
|
* Get all group columns by Name.
|
|
*
|
|
*
|
|
* @return Model|null|static
|
|
*/
|
|
public static function getByName(mixed $grp)
|
|
{
|
|
return self::query()->where('name', $grp)->first();
|
|
}
|
|
|
|
/**
|
|
* Get a group name using its ID.
|
|
*
|
|
* @param int|string $id The group ID.
|
|
* @return string Empty string on failure, groupName on success.
|
|
*/
|
|
public static function getNameByID($id): string
|
|
{
|
|
$res = self::query()->where('id', $id)->first(['name']);
|
|
|
|
return $res !== null ? $res->name : '';
|
|
}
|
|
|
|
/**
|
|
* Get a group ID using its name.
|
|
*
|
|
* @param string $name The group name.
|
|
* @return false|int false on failure, groups_id on success.
|
|
*/
|
|
public static function getIDByName(string $name)
|
|
{
|
|
$res = self::query()->where('name', $name)->first(['id']);
|
|
|
|
return $res === null ? false : $res->id;
|
|
}
|
|
|
|
/**
|
|
* Gets a count of all groups in the table limited by parameters.
|
|
*
|
|
* @param string $groupname Constrain query to specific group name
|
|
* @param int $active Constrain query to active status
|
|
* @return mixed
|
|
*/
|
|
public static function getGroupsCount(string $groupname = '', int $active = -1)
|
|
{
|
|
$res = self::query();
|
|
|
|
if ($groupname !== '') {
|
|
$res->where('name', 'like', '%'.$groupname.'%');
|
|
}
|
|
|
|
if ($active > -1) {
|
|
$res->where('active', $active);
|
|
}
|
|
|
|
return $res === null ? 0 : $res->count('id');
|
|
}
|
|
|
|
public static function getGroupsRange(string $groupname = '', mixed $active = null): LengthAwarePaginator // @phpstan-ignore missingType.generics
|
|
{
|
|
$groups = self::query()->groupBy('id')->orderBy('name');
|
|
|
|
if ($groupname !== '') {
|
|
$groups->where('name', 'like', '%'.$groupname.'%');
|
|
}
|
|
|
|
if ($active === true) {
|
|
$groups->where('active', '=', 1);
|
|
} elseif ($active === false) {
|
|
$groups->where('active', '=', 0);
|
|
}
|
|
|
|
return $groups->paginate(config('nntmux.items_per_page'));
|
|
}
|
|
|
|
/**
|
|
* Update an existing group.
|
|
*/
|
|
public static function updateGroup(mixed $group): int
|
|
{
|
|
return self::query()->where('id', $group['id'])->update(
|
|
[
|
|
'name' => trim($group['name']),
|
|
'description' => trim($group['description']),
|
|
'backfill_target' => $group['backfill_target'],
|
|
'first_record' => $group['first_record'],
|
|
'last_record' => $group['last_record'],
|
|
'last_updated' => now(),
|
|
'active' => $group['active'],
|
|
'backfill' => $group['backfill'],
|
|
'minsizetoformrelease' => $group['minsizetoformrelease'] === '' ? null : $group['minsizetoformrelease'],
|
|
'minfilestoformrelease' => $group['minfilestoformrelease'] === '' ? null : $group['minfilestoformrelease'],
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Checks group name is standard and replaces any shorthand prefixes.
|
|
*
|
|
* @param string $groupName The full name of the usenet group being evaluated
|
|
* @return string|bool The name of the group replacing shorthand prefix or false if groupname was malformed
|
|
*/
|
|
public static function isValidGroup(string $groupName)
|
|
{
|
|
if (preg_match('/^([\w\-]+\.)+[\w\-]+$/i', $groupName)) {
|
|
return preg_replace('/^a\.b\./i', 'alt.binaries.', $groupName, 1);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Add a new group.
|
|
*
|
|
*
|
|
* @return int|mixed
|
|
*/
|
|
public static function addGroup(mixed $group)
|
|
{
|
|
$checkOld = UsenetGroup::query()->where('name', trim($group['name']))->first();
|
|
if (empty($checkOld)) {
|
|
return self::query()->insertGetId([
|
|
'name' => trim($group['name']),
|
|
'description' => isset($group['description']) ? trim($group['description']) : '',
|
|
'backfill_target' => $group['backfill_target'] ?? 1,
|
|
'first_record' => $group['first_record'] ?? 0,
|
|
'last_record' => $group['last_record'] ?? 0,
|
|
'active' => $group['active'] ?? 0,
|
|
'backfill' => $group['backfill'] ?? 0,
|
|
'minsizetoformrelease' => $group['minsizetoformrelease'] ?? null,
|
|
'minfilestoformrelease' => $group['minfilestoformrelease'] ?? null,
|
|
]);
|
|
}
|
|
|
|
return $checkOld->id;
|
|
}
|
|
|
|
/**
|
|
* Delete a group.
|
|
*
|
|
* @param int|string $id ID of the group.
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
public static function deleteGroup($id): bool
|
|
{
|
|
self::purge($id);
|
|
|
|
return self::query()->where('id', $id)->delete();
|
|
}
|
|
|
|
/**
|
|
* Reset a group.
|
|
*
|
|
* @param string|int $id The group ID.
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
public static function reset($id): int
|
|
{
|
|
// Remove rows from part repair.
|
|
MissedPart::query()->where('groups_id', $id)->delete();
|
|
|
|
// Reset the group stats.
|
|
return self::query()->where('id', $id)->update(
|
|
[
|
|
'backfill_target' => 1,
|
|
'first_record' => 0,
|
|
'first_record_postdate' => null,
|
|
'last_record' => 0,
|
|
'last_record_postdate' => null,
|
|
'last_updated' => null,
|
|
'active' => 0,
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Reset all groups.
|
|
*/
|
|
public static function resetall(): int
|
|
{
|
|
// Disable foreign key checks to allow truncating tables with foreign key constraints
|
|
DB::statement('SET FOREIGN_KEY_CHECKS=0');
|
|
|
|
try {
|
|
// Truncate tables in reverse order to respect foreign key relationships
|
|
// (child tables first: missed_parts, parts, binaries, then parent: collections)
|
|
foreach (array_reverse(self::$cbpm) as $tablePrefix) {
|
|
DB::statement("TRUNCATE TABLE {$tablePrefix}");
|
|
}
|
|
} finally {
|
|
// Always re-enable foreign key checks, even if truncate fails
|
|
DB::statement('SET FOREIGN_KEY_CHECKS=1');
|
|
}
|
|
|
|
// Reset the group stats.
|
|
|
|
return self::query()->update(
|
|
[
|
|
'backfill_target' => 1,
|
|
'first_record' => 0,
|
|
'first_record_postdate' => null,
|
|
'last_record' => 0,
|
|
'last_record_postdate' => null,
|
|
'last_updated' => null,
|
|
'active' => 0,
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Purge a single group or all groups.
|
|
*
|
|
* @param int|string|bool $id The group ID. If false, purge all groups.
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
public static function purge($id = false): void
|
|
{
|
|
if ($id === false) {
|
|
self::resetall();
|
|
} else {
|
|
self::reset($id);
|
|
}
|
|
|
|
$res = Release::query()->select(['id', 'guid']);
|
|
|
|
if ($id !== false) {
|
|
$res->where('groups_id', $id);
|
|
}
|
|
|
|
$releases = $res->get();
|
|
|
|
$releaseManagement = app(ReleaseManagementService::class);
|
|
$nzb = app(NzbService::class);
|
|
$releaseImage = new ReleaseImageService;
|
|
foreach ($releases as $row) {
|
|
$releaseManagement->deleteSingleWithService(
|
|
[
|
|
'g' => $row->guid,
|
|
'i' => $row->id,
|
|
],
|
|
$nzb,
|
|
$releaseImage
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds new newsgroups based on a regular expression match against USP available.
|
|
*
|
|
* @return array<string, mixed>|string
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
public static function addBulk(string $groupList, int $active = 1, int $backfill = 1)
|
|
{
|
|
if (preg_match('/^\s*$/m', $groupList)) {
|
|
$ret = 'No group list provided.';
|
|
} else {
|
|
$nntp = new NNTPService;
|
|
if ($nntp->doConnect() !== true) {
|
|
return 'Problem connecting to usenet.';
|
|
}
|
|
$groups = $nntp->getGroups();
|
|
$nntp->doQuit();
|
|
|
|
if ($nntp->isError($groups)) {
|
|
return 'Problem fetching usenet_groups from usenet.';
|
|
}
|
|
|
|
$regFilter = '/'.$groupList.'/i';
|
|
|
|
$ret = [];
|
|
|
|
foreach ($groups as $group) {
|
|
if (preg_match($regFilter, $group['group']) > 0) {
|
|
$res = self::getIDByName($group['group']);
|
|
if ($res === false) {
|
|
self::addGroup(
|
|
[
|
|
'name' => $group['group'],
|
|
'active' => $active,
|
|
'backfill' => $backfill,
|
|
'description' => 'Added by bulkAdd',
|
|
]
|
|
);
|
|
$ret[] = ['group' => $group['group'], 'msg' => 'Created'];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (\count($ret) === 0) {
|
|
$ret[] = ['group' => '', 'msg' => 'No groups found with your regex or groups already exist in database, try again!'];
|
|
}
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Updates the group active/backfill status.
|
|
*
|
|
* @param int $id Which group ID
|
|
* @param string $column Which column active/backfill
|
|
* @param int $status Which status we are setting
|
|
*/
|
|
public static function updateGroupStatus(int $id, string $column, int $status = 0): string
|
|
{
|
|
self::query()->where('id', $id)->update(
|
|
[
|
|
$column => $status,
|
|
]
|
|
);
|
|
|
|
return "Group {$id} has been ".(($status === 0) ? 'deactivated' : 'activated').'.';
|
|
}
|
|
|
|
/**
|
|
* Disable group that does not exist on USP server.
|
|
*
|
|
* @param int $id The Group ID to disable
|
|
*/
|
|
public static function disableIfNotExist(int $id): void
|
|
{
|
|
self::updateGroupStatus($id, 'active');
|
|
cli()->error('Group does not exist on server, disabling');
|
|
}
|
|
}
|