API: torrents upload/list

This commit is contained in:
xiaomlove
2025-04-17 01:39:40 +07:00
parent 0d3a46231d
commit 2b029eba10
72 changed files with 2332 additions and 507 deletions
+6
View File
@@ -6,6 +6,7 @@ use App\Models\Setting;
use App\Models\Torrent;
use App\Models\User;
use Illuminate\Encryption\Encrypter;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class BaseRepository
@@ -20,6 +21,11 @@ class BaseRepository
return [$field, $type];
}
protected function getPerPageFromRequest(Request $request)
{
return $request->get('per_page');
}
protected function handleAnonymous($username, $user, User $authenticator, Torrent $torrent = null)
{
if (!$user) {
+2
View File
@@ -7,7 +7,9 @@ class TokenRepository extends BaseRepository
{
private static array $userTokenPermissions = [
PermissionEnum::TORRENT_LIST,
PermissionEnum::TORRENT_VIEW,
PermissionEnum::UPLOAD,
PermissionEnum::USER_VIEW,
];
public function listUserTokenPermissions(): array
+2 -1
View File
@@ -346,7 +346,8 @@ class ToolRepository extends BaseRepository
->from(new Address(Setting::get('main.SITEEMAIL'), Setting::get('basic.SITENAME')))
->to($to)
->subject($subject)
->html($body)
->text($body)
->html(nl2br($body))
;
// Send the message
+124 -66
View File
@@ -2,8 +2,10 @@
namespace App\Repositories;
use App\Auth\Permission;
use App\Exceptions\InsufficientPermissionException;
use App\Exceptions\NexusException;
use App\Http\Resources\TorrentResource;
use App\Models\AudioCodec;
use App\Models\Category;
use App\Models\Claim;
@@ -26,9 +28,12 @@ use App\Models\TorrentOperationLog;
use App\Models\TorrentSecret;
use App\Models\TorrentTag;
use App\Models\User;
use App\Utils\ApiQueryBuilder;
use Carbon\Carbon;
use Hashids\Hashids;
use Elasticsearch\Endpoints\Search;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
@@ -55,81 +60,134 @@ class TorrentRepository extends BaseRepository
/**
* fetch torrent list
*
* @param array $params
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
*/
public function getList(array $params, User $user)
public function getList(Request $request, Authenticatable $user, string $sectionName = null)
{
$query = Torrent::query();
if (!empty($params['category'])) {
$query->where('category', $params['category']);
if (empty($sectionName)) {
$sectionId = SearchBox::getBrowseMode();
$searchBox = SearchBox::query()->find($sectionId);
} else {
$searchBox = SearchBox::query()->where('name', $sectionName)->first();
}
if (!empty($params['source'])) {
$query->where('source', $params['source']);
if (empty($searchBox)) {
throw new NexusException(nexus_trans("upload.invalid_section"));
}
if (!empty($params['medium'])) {
$query->where('medium', $params['medium']);
if ($searchBox->isSectionSpecial() && !Permission::canViewSpecialSection()) {
throw new InsufficientPermissionException();
}
if (!empty($params['codec'])) {
$query->where('codec', $params['codec']);
$categoryIdList = $searchBox->categories()->pluck('id')->toArray();
//query this info default
$query = Torrent::query()->with([
'basic_category', 'basic_category.search_box',
'basic_audiocodec', 'basic_codec', 'basic_medium',
'basic_source', 'basic_processing', 'basic_standard', 'basic_team',
])
->whereIn('category', $categoryIdList)
->orderBy("pos_state", "DESC");
$allowIncludes = ['user', 'extra', 'tags'];
$allowIncludeCounts = ['thank_users', 'reward_logs', 'claims'];
$allowIncludeFields = [
'has_bookmarked', 'has_claimed', 'has_thanked', 'has_rewarded',
'description', 'download_url'
];
$allowFilters = [
'title', 'category', 'source', 'medium', 'codec', 'audiocodec', 'standard', 'processing', 'team',
'owner', 'visible', 'added', 'size', 'sp_state', 'leechers', 'seeders', 'times_completed'
];
$allowSorts = ['id', 'comments', 'size', 'seeders', 'leechers', 'times_completed'];
$apiQueryBuilder = ApiQueryBuilder::for(TorrentResource::NAME, $query, $request)
->allowIncludes($allowIncludes)
->allowIncludeCounts($allowIncludeCounts)
->allowIncludeFields($allowIncludeFields)
->allowFilters($allowFilters)
->allowSorts($allowSorts)
->registerCustomFilter('title', function (Builder $query, Request $request) {
$title = $request->input(ApiQueryBuilder::PARAM_NAME_FILTER.".title");
if ($title) {
$query->where(function (Builder $query) use ($title) {
$query->where('name', 'like', '%' . $title . '%')
->orWhere('small_descr', 'like', '%' . $title . '%');
});
}
})
;
$query = $apiQueryBuilder->build();
if (!$apiQueryBuilder->hasSort()) {
$query->orderBy("id", "DESC");
}
if (!empty($params['audio_codec'])) {
$query->where('audiocodec', $params['audio_codec']);
}
if (!empty($params['standard'])) {
$query->where('standard', $params['standard']);
}
if (!empty($params['processing'])) {
$query->where('processing', $params['processing']);
}
if (!empty($params['team'])) {
$query->where('team', $params['team']);
}
if (!empty($params['owner'])) {
$query->where('owner', $params['owner']);
}
if (!empty($params['visible'])) {
$query->where('visible', $params['visible']);
}
if (!empty($params['query'])) {
$query->where(function (Builder $query) use ($params) {
$query->where('name', 'like', "%{$params['query']}%")
->orWhere('small_descr', 'like', "%{$params['query']}%");
});
}
if (!empty($params['category_mode'])) {
$query->whereHas('basic_category', function (Builder $query) use ($params) {
$query->where('mode', $params['category_mode']);
});
}
$query = $this->handleGetListSort($query, $params);
$with = ['user', 'tags'];
$torrents = $query->with($with)->paginate();
foreach ($torrents as &$item) {
$item->download_url = $this->getDownloadUrl($item->id, $user);
}
return $torrents;
$torrents = $query->paginate($this->getPerPageFromRequest($request));
return $this->appendIncludeFields($apiQueryBuilder, $user, $torrents);
}
public function getDetail($id, User $user)
public function getDetail($id, Authenticatable $user)
{
$with = [
'user', 'basic_audio_codec', 'basic_category', 'basic_codec', 'basic_media', 'basic_source', 'basic_standard', 'basic_team',
'thanks' => function ($query) use ($user) {
$query->where('userid', $user->id);
},
'reward_logs' => function ($query) use ($user) {
$query->where('userid', $user->id);
},
//query this info default
$query = Torrent::query()->with([
'basic_category', 'basic_category.search_box',
'basic_audiocodec', 'basic_codec', 'basic_medium',
'basic_source', 'basic_processing', 'basic_standard', 'basic_team',
]);
$allowIncludes = ['user', 'extra', 'tags'];
$allowIncludeCounts = ['thank_users', 'reward_logs', 'claims'];
$allowIncludeFields = [
'has_bookmarked', 'has_claimed', 'has_thanked', 'has_rewarded',
'description', 'download_url'
];
$result = Torrent::query()->with($with)->withCount(['peers', 'thank_users', 'reward_logs'])->visible()->findOrFail($id);
$result->download_url = $this->getDownloadUrl($id, $user);
return $result;
$apiQueryBuilder = ApiQueryBuilder::for(TorrentResource::NAME, $query)
->allowIncludes($allowIncludes)
->allowIncludeCounts($allowIncludeCounts)
->allowIncludeFields($allowIncludeFields)
;
$torrent = $apiQueryBuilder->build()->findOrFail($id);
$torrentList = $this->appendIncludeFields($apiQueryBuilder, $user, [$torrent]);
return $torrentList[0];
}
private function appendIncludeFields(ApiQueryBuilder $apiQueryBuilder, Authenticatable $user, $torrentList)
{
$torrentIdArr = $bookmarkData = $claimData = $thankData = $rewardData =[];
foreach ($torrentList as $torrent) {
$torrentIdArr[] = $torrent->id;
}
unset($torrent);
if ($hasFieldHasBookmarked = $apiQueryBuilder->hasIncludeField('has_bookmarked')) {
$bookmarkData = $user->bookmarks()->whereIn('torrentid', $torrentIdArr)->get()->keyBy('torrentid');
}
if ($hasFieldHasClaimed = $apiQueryBuilder->hasIncludeField('has_claimed')) {
$claimData = $user->claims()->whereIn('torrent_id', $torrentIdArr)->get()->keyBy('torrent_id');
}
if ($hasFieldHasThanked = $apiQueryBuilder->hasIncludeField('has_thanked')) {
$thankData = $user->thank_torrent_logs()->whereIn('torrentid', $torrentIdArr)->get()->keyBy('torrentid');
}
if ($hasFieldHasRewarded = $apiQueryBuilder->hasIncludeField('has_rewarded')) {
$rewardData = $user->reward_torrent_logs()->whereIn('torrentid', $torrentIdArr)->get()->keyBy('torrentid');
}
foreach ($torrentList as $torrent) {
$id = $torrent->id;
if ($hasFieldHasBookmarked) {
$torrent->has_bookmarked = $bookmarkData->has($id);
}
if ($hasFieldHasClaimed) {
$torrent->has_claimed = $claimData->has($id);
}
if ($hasFieldHasThanked) {
$torrent->has_thanked = $thankData->has($id);
}
if ($hasFieldHasRewarded) {
$torrent->has_rewarded = $rewardData->has($id);
}
if ($apiQueryBuilder->hasIncludeField('description') && $apiQueryBuilder->hasInclude('extra')) {
$descriptionArr = format_description($torrent->extra->descr ?? '');
$torrent->description = $descriptionArr;
$torrent->images = get_image_from_description($descriptionArr);
}
if ($apiQueryBuilder->hasIncludeField("download_url")) {
$torrent->download_url = $this->getDownloadUrl($id, $user);
}
}
return $torrentList;
}
public function getDownloadUrl($id, array|User $user): string
+535 -21
View File
@@ -1,52 +1,195 @@
<?php
namespace App\Repositories;
use App\Auth\Permission;
use App\Enums\ModelEventEnum;
use App\Exceptions\NexusException;
use App\Http\Resources\SearchBoxResource;
use App\Models\BonusLogs;
use App\Models\Category;
use App\Models\File;
use App\Models\SearchBox;
use App\Models\Setting;
use App\Models\Torrent;
use App\Models\TorrentExtra;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Rhilip\Bencode\Bencode;
use Rhilip\Bencode\ParseException;
class UploadRepository extends BaseRepository
{
/**
* @throws NexusException
*/
public function upload(Request $request)
{
$user = $request->user();
if ($user->uploadpos != 'yes') {
throw new NexusException("user upload permission is disabled");
if (empty($request->name)) {
throw new NexusException(nexus_trans("upload.require_name"));
}
$rules = [
'descr' => 'required',
'type' => 'required',
'name' => 'required',
];
$request->validate($rules);
$category = Category::query()->findOrFail($request->type);
$mode = $category->mode;
$searchBox = SearchBox::query()->findOrFail($mode);
$searchBox->loadSubCategories();
$searchBox->loadTags();
if (empty($request->descr)) {
throw new NexusException(nexus_trans("upload.blank_description"));
}
if (empty($request->type)) {
throw new NexusException(nexus_trans("upload.category_unselected"));
}
$category = Category::query()->find($request->type);
if (!$category) {
throw new NexusException(nexus_trans("upload.invalid_category"));
}
$torrentFile = $this->getTorrentFile($request);
$filepath = $torrentFile->getRealPath();
try {
$dict = Bencode::load($filepath);
} catch (ParseException $e) {
do_log("Bencode load error:" . $e->getMessage(), 'error');
throw new NexusException("upload.not_bencoded_file");
}
$info = $this->checkTorrentDict($dict, 'info');
if (isset($dict['piece layers']) || isset($info['files tree']) || (isset($info['meta version']) && $info['meta version'] == 2)) {
throw new NexusException("Torrent files created with Bittorrent Protocol v2, or hybrid torrents are not supported.");
}
$this->checkTorrentDict($info, 'piece length', 'integer'); // Only Check without use
$dname = $this->checkTorrentDict($info, 'name', 'string');
$pieces = $this->checkTorrentDict($info, 'pieces', 'string');
if (strlen($pieces) % 20 != 0) {
throw new NexusException(nexus_trans("upload.invalid_pieces"));
}
$dict['info']['private'] = 1;
$dict['info']['source'] = sprintf("[%s] %s", Setting::getBaseUrl(), Setting::getSiteName());
unset ($dict['announce-list']); // remove multi-tracker capability
unset ($dict['nodes']); // remove cached peers (Bitcomet & Azareus)
$infoHash = pack("H*", sha1(Bencode::encode($dict['info'])));
$exists = Torrent::query()->where('info_hash', $infoHash)->first(['id']);
if ($exists) {
throw new NexusException(nexus_trans('upload.torrent_existed', ['id' => $exists->id]));
}
$subCategoriesAngTags = $this->getSubCategoriesAndTags($request, $category);
$fileListInfo = $this->getFileListInfo($info, $dname);
$posStateInfo = $this->getPosStateInfo($request);
$pickInfo = $this->getPickInfo($request);
$anonymous = "no";
$uploaderUsername = $user->username;
if ($request->uplver == 'yes' && Permission::canBeAnonymous()) {
if ($request->uplver == 'yes') {
if (!Permission::canBeAnonymous()) {
throw new NexusException(nexus_trans('upload.no_permission_to_be_anonymous'));
}
$anonymous = "yes";
$uploaderUsername = "Anonymous";
}
$torrentSavePath = $this->getTorrentSavePath();
$nowStr = Carbon::now()->toDateTimeString();
$torrentInsert = [
'filename' => $torrentFile->getClientOriginalName(),
'owner' => $user->id,
'visible' => 'yes',
'anonymous' => $anonymous,
'name' => $request->name,
'size' => $fileListInfo['totalLength'],
'numfiles' => count($fileListInfo['fileList']),
'type' => $fileListInfo['type'],
'url' => parse_imdb_id($request->url ?? ''),
'small_descr' => $request->small_descr ?? '',
'category' => $category->id,
'source' => $subCategoriesAngTags['subCategories']['source'],
'medium' => $subCategoriesAngTags['subCategories']['medium'],
'codec' => $subCategoriesAngTags['subCategories']['codec'],
'audiocodec' => $subCategoriesAngTags['subCategories']['audiocodec'],
'standard' => $subCategoriesAngTags['subCategories']['standard'],
'processing' => $subCategoriesAngTags['subCategories']['processing'],
'team' => $subCategoriesAngTags['subCategories']['team'],
'save_as' => $dname,
'sp_state' => $this->getSpState($fileListInfo['totalLength']),
'added' => $nowStr,
'last_action' => $nowStr,
'info_hash' => $infoHash,
'cover' => $this->getCover($request),
'pieces_hash' => sha1($info['pieces']),
'cache_stamp' => time(),
'hr' => $this->getHitAndRun($request),
'pos_state' => $posStateInfo['posState'],
'pos_state_until' => $posStateInfo['posStateUntil'],
'picktype' => $pickInfo['pickType'],
'picktime' => $pickInfo['pickTime'],
'approval_status' => $this->getApprovalStatus($request),
'price' => $this->getPrice($request),
];
$extraInsert = [
'descr' => $request->descr ?? '',
'media_info' => $request->technical_info ?? '',
'nfo' => $this->getNfoContent($request),
'created_at' => $nowStr,
];
$newTorrent = DB::transaction(function () use ($torrentInsert, $extraInsert, $fileListInfo, $subCategoriesAngTags, $dict, $torrentSavePath) {
$newTorrent = Torrent::query()->create($torrentInsert);
$id = $newTorrent->id;
$torrentFilePath = "$torrentSavePath/$id.torrent";
$saveResult = Bencode::dump($torrentFilePath, $dict);
if ($saveResult === false) {
do_log("save torrent failed: $torrentFilePath", 'error');
throw new NexusException(nexus_trans('upload.save_torrent_file_failed'));
}
$extraInsert['torrent_id'] = $id;
TorrentExtra::query()->insert($extraInsert);
$fileInsert = [];
foreach ($fileListInfo['fileList'] as $fileItem) {
$fileInsert[] = [
'torrent' => $id,
'filename' => $fileItem[0],
'size' => $fileItem[1],
];
}
File::query()->insert($fileInsert);
if (!empty($subCategoriesAngTags['tags'])) {
insert_torrent_tags($id, $subCategoriesAngTags['tags']);
}
$this->sendReward($id);
return $newTorrent;
});
$id = $newTorrent->id;
$torrentRep = new TorrentRepository();
$torrentRep->addPiecesHashCache($id, $torrentInsert['pieces_hash']);
write_log("Torrent $id ($newTorrent->name) was uploaded by $uploaderUsername");
fire_event(ModelEventEnum::TORRENT_CREATED, $newTorrent);
return $newTorrent;
}
private function getTorrentFile(Request $request): UploadedFile
{
$file = $request->file('file');
if (empty($file)) {
throw new NexusException("torrent file not found");
throw new NexusException(nexus_trans('upload.missing_torrent_file'));
}
if (!$file->isValid()) {
throw new NexusException("upload torrent file error");
}
$size = $file->getSize();
$maxAllowSize = Setting::getUploadTorrentMaxSize();
if ($size > $maxAllowSize) {
$msg = sprintf("%s%s%s",
nexus_trans("upload.torrent_file_too_big"),
number_format($maxAllowSize),
nexus_trans("upload.remake_torrent_note")
);
throw new NexusException($msg);
}
if ($size == 0) {
throw new NexusException("upload.empty_file");
}
$filename = $file->getClientOriginalName();
if (!validfilename($filename)) {
throw new NexusException("upload.invalid_filename");
}
if (!preg_match('/^(.+)\.torrent$/si', $filename, $matches)) {
throw new NexusException("upload.filename_not_torrent");
}
return $file;
}
@@ -61,16 +204,387 @@ class UploadRepository extends BaseRepository
return '';
}
if (!$file->isValid()) {
throw new NexusException("upload nfo file error");
throw new NexusException(nexus_trans("upload.nfo_upload_failed"));
}
$size = $file->getSize();
if ($size == 0) {
throw new NexusException("upload nfo file size is zero");
throw new NexusException(nexus_trans("upload.zero_byte_nfo"));
}
if ($size > 65535) {
throw new NexusException("upload nfo file size is too large");
throw new NexusException(nexus_trans("upload.nfo_too_big"));
}
return str_replace("\x0d\x0d\x0a", "\x0d\x0a", $file->getContent());
}
private function getApprovalStatus(Request $request): int
{
if (Permission::canTorrentApprovalAllowAutomatic()) {
return Torrent::APPROVAL_STATUS_ALLOW;
}
return Torrent::APPROVAL_STATUS_NONE;
}
private function getPrice(Request $request): int
{
$price = $request->price ?: 0;
if (!is_numeric($price)) {
throw new NexusException(nexus_trans('upload.invalid_price', ['price' => $price]));
}
if ($price > 0) {
if (!Permission::canSetTorrentPrice()) {
throw new NexusException(nexus_trans("upload.no_permission_to_set_torrent_price"));
}
$paidTorrentEnabled = Setting::getIsPaidTorrentEnabled();
if (!$paidTorrentEnabled) {
throw new NexusException(nexus_trans("upload.paid_torrent_not_enabled"));
}
$maxPrice = Setting::getUploadTorrentMaxPrice();
if ($maxPrice > 0 && $price > $maxPrice) {
throw new NexusException(nexus_trans('upload.price_too_much'));
}
}
return intval($price);
}
private function getHitAndRun(Request $request): int
{
$hr = $request->hr ?? 0;
if ($hr > 0 && !Permission::canSetTorrentHitAndRun()) {
throw new NexusException(nexus_trans("upload.no_permission_to_set_torrent_hr"));
}
if (!in_array($hr, [0, 1])) {
throw new NexusException(nexus_trans('upload.invalid_hr'));
}
return intval($hr);
}
private function getPosStateInfo(Request $request): array
{
$posState = $request->pos_state ?: Torrent::POS_STATE_STICKY_NONE;
$posStateUntil = $request->pos_state_until ?: null;
if ($posState !== Torrent::POS_STATE_STICKY_NONE) {
if (!Permission::canSetTorrentPosState()) {
throw new NexusException("upload.no_permission_to_set_torrent_pos_state");
}
if (!isset(Torrent::$posStates[$posState])) {
throw new NexusException(nexus_trans('upload.invalid_pos_state', ['pos_state' => $posState]));
}
}
if ($posState == Torrent::POS_STATE_STICKY_NONE) {
$posStateUntil = null;
}
if ($posStateUntil && Carbon::parse($posStateUntil)->lt(Carbon::now())) {
throw new NexusException(nexus_trans('upload.invalid_pos_state_until'));
}
return compact('posState', 'posStateUntil');
}
private function getPickInfo(Request $request): array
{
$pickType = $request->pick_type ?: Torrent::PICK_NORMAL;
$pickTime = null;
if ($pickType != Torrent::PICK_NORMAL) {
if (!isset(Torrent::$pickTypes[$pickType])) {
throw new NexusException(nexus_trans('upload.invalid_pick_type', ['pick_type' => $pickType]));
}
if (!Permission::canPickTorrent()) {
throw new NexusException("upload.no_permission_to_pick_torrent");
}
$pickTime = Carbon::now();
}
return compact('pickType', 'pickTime');
}
private function checkTorrentDict($dict, $key, $type = null)
{
if (!is_array($dict)) {
throw new NexusException(nexus_trans("upload.not_a_dictionary"));
}
if (!isset($dict[$key])) {
throw new NexusException(nexus_trans("upload.dictionary_is_missing_key"));
}
$value = $dict[$key];
if (!is_null($type)) {
$isFunction = 'is_' . $type;
if (function_exists($isFunction) && !$isFunction($value)) {
throw new NexusException(nexus_trans("upload.invalid_entry_in_dictionary"));
}
}
return $value;
}
/**
* @throws NexusException
*/
private function getFileListInfo(array $info, string $dname): array
{
$filelist = array();
$totallen = 0;
if (isset($info['length'])) {
$totallen = $info['length'];
$filelist[] = array($dname, $totallen);
$type = "single";
} else {
$flist = $this->checkTorrentDict($info, 'files', 'array');
if (!count($flist)) {
throw new NexusException(nexus_trans("upload.empty_file"));
}
foreach ($flist as $fn) {
$ll = $this->checkTorrentDict($fn, 'length', 'integer');
$path_key = isset($fn['path.utf-8']) ? 'path.utf-8' : 'path';
$ff = $this->checkTorrentDict($fn, $path_key, 'list');
$totallen += $ll;
$ffa = array();
foreach ($ff as $ffe) {
if (!is_string($ffe)) {
throw new NexusException(nexus_trans("upload.filename_errors"));
}
$ffa[] = $ffe;
}
if (!count($ffa)) {
throw new NexusException(nexus_trans("upload.filename_errors"));
}
$ffe = implode("/", $ffa);
$filelist[] = array($ffe, $ll);
}
$type = "multi";
}
return [
'type' => $type,
'totalLength' => $totallen,
'fileList' => $filelist,
];
}
private function canUploadToSection(SearchBox $section): bool
{
$user = Auth::user();
$uploadDenyApprovalDenyCount = Setting::getUploadDenyApprovalDenyCount();
$approvalDenyCount = Torrent::query()->where('owner', $user->id)
->where('approval_status', Torrent::APPROVAL_STATUS_DENY)
->count()
;
if ($uploadDenyApprovalDenyCount > 0 && $approvalDenyCount >= $uploadDenyApprovalDenyCount) {
throw new NexusException(nexus_trans("upload.approval_deny_reach_upper_limit"));
}
if ($section->isSectionBrowse()) {
$offerSkipApprovedCount = Setting::getOfferSkipApprovedCount();
if ($user->offer_allowed_count >= $offerSkipApprovedCount) {
return true;
}
if (get_if_restricted_is_open()) {
return true;
}
if (!Permission::canUploadToNormalSection()) {
throw new NexusException(nexus_trans('upload.unauthorized_upload_freely'));
}
return true;
} elseif ($section->isSectionSpecial()) {
if (!Setting::getIsSpecialSectionEnabled()) {
throw new NexusException(nexus_trans('upload.special_section_not_enabled'));
}
if (!Permission::canUploadToSpecialSection()) {
throw new NexusException(nexus_trans('upload.unauthorized_upload_freely'));
}
return true;
}
throw new NexusException(nexus_trans('upload.invalid_section'));
}
private function getSpState($torrentSize): int
{
$largeTorrentSize = Setting::getLargeTorrentSize();
if ($largeTorrentSize > 0 && $torrentSize > $largeTorrentSize * 1073741824) {
$largeTorrentSpState = Setting::getLargeTorrentSpState();
if (isset(Torrent::$promotionTypes[$largeTorrentSpState])) {
do_log("large torrent, sp state from config: $largeTorrentSpState");
return $largeTorrentSpState;
}
do_log("invalid large torrent sp state: $largeTorrentSpState", 'error');
return Torrent::PROMOTION_NORMAL;
} else {
$probabilities = [
Torrent::PROMOTION_FREE => Setting::getUploadTorrentFreeProbability(),
Torrent::PROMOTION_TWO_TIMES_UP => Setting::getUploadTorrentTwoTimesUpProbability(),
Torrent::PROMOTION_FREE_TWO_TIMES_UP => Setting::getUploadTorrentFreeTwoTimesUpProbability(),
Torrent::PROMOTION_HALF_DOWN => Setting::getUploadTorrentHalfDownProbability(),
Torrent::PROMOTION_HALF_DOWN_TWO_TIMES_UP => Setting::getUploadTorrentHalfDownTwoTimesUpProbability(),
Torrent::PROMOTION_ONE_THIRD_DOWN => Setting::getUploadTorrentOneThirdDownProbability(),
];
$sum = array_sum($probabilities);
if ($sum == 0) {
do_log("no random sp state", 'warning');
return Torrent::PROMOTION_NORMAL;
}
$random = mt_rand(1, $sum);
$currentProbability = 0;
foreach ($probabilities as $k => $v) {
$currentProbability += $v;
if ($random <= $currentProbability) {
do_log(sprintf("random sp state, probabilities: %s, get result: %s by probability: %s", json_encode($probabilities), $k, $v));
return $k;
}
}
throw new \RuntimeException();
}
}
/**
* @throws NexusException
*/
private function getSubCategoriesAndTags(Request $request, Category $category): array
{
$searchBoxRep = new SearchBoxRepository();
$sections = $searchBoxRep->listSections()->keyBy('id');
if (!$sections->has($category->mode)) {
throw new NexusException(nexus_trans('upload.invalid_section'));
}
/**
* @var $section SearchBox
*/
$section = $sections->get($category->mode);
$this->canUploadToSection($section);
$sectionResource = new SearchBoxResource($section);
$sectionData = $sectionResource->response()->getData(true);
$sectionInfo = $sectionData['data'];
$categories = array_column($sectionInfo['categories'], 'id');
if (!in_array($category->id, $categories)) {
throw new NexusException(nexus_trans('upload.invalid_category'));
}
$subCategoryInfo = array_column($sectionInfo['sub_categories'], null, 'field');
$subCategories = [];
foreach (SearchBox::$taxonomies as $name => $info) {
$value = $request->get($name, 0);
if ($value > 0) {
if (!isset($subCategoryInfo[$name])) {
throw new NexusException(nexus_trans('upload.not_supported_sub_category_field', ['field' => $name]));
}
$subCategoryValues = array_column($subCategoryInfo[$name]['data'], 'name', 'id');
if (!isset($subCategoryValues[$value])) {
throw new NexusException(nexus_trans(
'upload.invalid_sub_category_value',
['field' => $name, 'label' => $subCategoryInfo[$name]['label'], 'value' => $value]
));
}
}
$subCategories[$name] = $value;
}
$tags = $request->tags ?: [];
if (!is_array($tags)) {
$tags = explode(',', $tags);
}
$allTags = array_column($sectionInfo['tags'], 'name', 'id');
foreach ($tags as $tag) {
if (!isset($allTags[$tag])) {
throw new NexusException(nexus_trans('upload.invalid_tag', ['tag' => $tag]));
}
}
return compact('subCategories', 'tags');
}
private function getCover(Request $request):string
{
$descr = $request->descr ?? '';
if (empty($descr)) {
return '';
}
$descriptionArr = format_description($descr);
return get_image_from_description($descriptionArr, true, false);
}
private function getTorrentSavePath(): string
{
$torrentSavePath = getFullDirectory(Setting::getTorrentSaveDir());
if (!is_dir($torrentSavePath)) {
do_log(sprintf("torrentSavePath: %s not exists", $torrentSavePath), 'error');
throw new NexusException(nexus_trans('upload.torrent_save_dir_not_exists'));
}
if (!is_writable($torrentSavePath)) {
do_log(sprintf("torrentSavePath: %s not writable", $torrentSavePath), 'error');
throw new NexusException(nexus_trans('upload.torrent_save_dir_not_writable'));
}
return $torrentSavePath;
}
private function sendReward($torrentId): void
{
$user = Auth::user();
$old = $user->seedbonus;
$delta = Setting::getUploadTorrentRewardBonus();
if ($delta > 0) {
$new = $old + $delta;
$user->increment('seedbonus', $delta);
BonusLogs::add($user->id, $old, $delta, $new, "Upload torrent: $torrentId", BonusLogs::BUSINESS_TYPE_UPLOAD_TORRENT);
do_log("upload torrent: $torrentId, success send reward: $delta");
} else {
do_log("upload torrent: $torrentId, no reward");
}
}
public function sendEmailNotification(Torrent $torrent, $userId = 0): int
{
$logMsg = sprintf("torrent: %s, category: %s", $torrent->id, $torrent->category);
if (!Setting::getIsAllowUserReceiveEmailNotification() || Setting::getSmtpType() == 'none') {
do_log("$logMsg, not allow user receive email notification or smtp type is none");
return 0;
}
$page = 1;
$size = 1000;
$query = User::query()
->where("notifs", "like", "%[cat$torrent->category]%")
->where("notifs", "like","%[email]%")
->normal()
;
if ($userId > 0) {
$query->where("id", $userId);
}
$total = (clone $query)->count();
if ($total == 0) {
do_log(sprintf("%s, no user receive email notification", $logMsg));
return 0;
}
$toolRep = new ToolRepository();
$categoryName = $torrent->basic_category->name;
$torrentUploader = $torrent->user;
$successCount = 0;
while (true) {
$logPage = "$logMsg, page: $page";
$users = (clone $query)->with(['language'])->forPage($page, $size)->get(['id', 'email', 'lang']);
if ($users->isEmpty()) {
do_log(sprintf("%s, no more user", $logPage));
break;
}
foreach ($users as $user) {
$locale = $user->locale;
$logUser = "$logPage, user $user->id, locale: $locale";
$subject = nexus_trans("upload.email_notification_subject", [
'site_name' => Setting::getSiteName()
], $locale);
$body = nexus_trans("upload.email_notification_body", [
'site_name' => Setting::getSiteName(),
'name' => $torrent->name,
'size' => mksize($torrent->size),
'category' => $categoryName,
'upload_by' => $this->handleAnonymous($torrentUploader->username, $torrentUploader, $user, $torrent),
'description' => Str::limit(strip_tags(format_comment($torrent->extra->descr)), 500),
'torrent_url' => sprintf("%s/details.php?id=%s&hit=1", getBaseUrl(), $torrent->id),
], $locale);
$sendResult = $toolRep->sendMail($user->email, $subject, $body);
do_log(sprintf("%s, send result: %s", $logUser, $sendResult));
if ($sendResult) {
$successCount++;
}
}
$page++;
}
do_log("$logMsg, receive email notification user total: $total, successCount: $successCount, done!");
return $successCount;
}
}
+26 -20
View File
@@ -4,6 +4,7 @@ namespace App\Repositories;
use App\Exceptions\InsufficientPermissionException;
use App\Exceptions\NexusException;
use App\Http\Resources\ExamUserResource;
use App\Http\Resources\TorrentResource;
use App\Http\Resources\UserResource;
use App\Models\ExamUser;
use App\Models\Invite;
@@ -11,12 +12,15 @@ use App\Models\LoginLog;
use App\Models\Message;
use App\Models\Setting;
use App\Models\Snatch;
use App\Models\Torrent;
use App\Models\User;
use App\Models\UserBanLog;
use App\Models\UserMeta;
use App\Models\UserModifyLog;
use App\Models\UsernameChangeLog;
use App\Utils\ApiQueryBuilder;
use Carbon\Carbon;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
@@ -53,28 +57,30 @@ class UserRepository extends BaseRepository
return $user;
}
public function getDetail($id)
public function getDetail($id, Authenticatable $currentUser)
{
$with = [
'inviter' => function ($query) {return $query->select(User::$commonFields);},
'valid_medals'
];
$user = User::query()->with($with)->findOrFail($id);
$userResource = new UserResource($user);
$baseInfo = $userResource->response()->getData(true)['data'];
//query this info default
$query = User::query()->with([]);
$allowIncludes = ['inviter', 'valid_medals'];
$allowIncludeCounts = [];
$allowIncludeFields = [];
$apiQueryBuilder = ApiQueryBuilder::for(UserResource::NAME, $query)
->allowIncludes($allowIncludes)
->allowIncludeCounts($allowIncludeCounts)
->allowIncludeFields($allowIncludeFields)
;
$user = $apiQueryBuilder->build()->findOrFail($id);
return $this->appendIncludeFields($apiQueryBuilder, $currentUser, $user);
}
$examRep = new ExamRepository();
$examProgress = $examRep->getUserExamProgress($id, ExamUser::STATUS_NORMAL);
if ($examProgress) {
$examResource = new ExamUserResource($examProgress);
$examInfo = $examResource->response()->getData(true)['data'];
} else {
$examInfo = null;
}
return [
'base_info' => $baseInfo,
'exam_info' => $examInfo,
];
private function appendIncludeFields(ApiQueryBuilder $apiQueryBuilder, Authenticatable $currentUser, User $user): User
{
// $id = $torrent->id;
// if ($apiQueryBuilder->hasIncludeField('has_bookmarked')) {
// $torrent->has_bookmarked = (int)$user->bookmarks()->where('torrentid', $id)->exists();;
// }
return $user;
}
/**