mirror of
https://github.com/lkddi/nexusphp.git
synced 2026-04-24 12:07:23 +08:00
API: torrents upload/list
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user