From 7b9e0eefd31a869a6ce71181bad42e601a1fc672 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 29 Jun 2021 01:46:04 -0600 Subject: [PATCH 1/4] Update InternalApiController formatting --- .../Controllers/InternalApiController.php | 743 +++++++++--------- 1 file changed, 371 insertions(+), 372 deletions(-) diff --git a/app/Http/Controllers/InternalApiController.php b/app/Http/Controllers/InternalApiController.php index 2df78dee3..90c195a28 100644 --- a/app/Http/Controllers/InternalApiController.php +++ b/app/Http/Controllers/InternalApiController.php @@ -4,27 +4,27 @@ namespace App\Http\Controllers; use Illuminate\Http\Request; use App\{ - AccountInterstitial, - DirectMessage, - DiscoverCategory, - Hashtag, - Follower, - Like, - Media, - MediaTag, - Notification, - Profile, - StatusHashtag, - Status, - UserFilter, + AccountInterstitial, + DirectMessage, + DiscoverCategory, + Hashtag, + Follower, + Like, + Media, + MediaTag, + Notification, + Profile, + StatusHashtag, + Status, + UserFilter, }; use Auth,Cache; use Carbon\Carbon; use League\Fractal; use App\Transformer\Api\{ - AccountTransformer, - StatusTransformer, - // StatusMediaContainerTransformer, + AccountTransformer, + StatusTransformer, + // StatusMediaContainerTransformer, }; use App\Util\Media\Filter; use App\Jobs\StatusPipeline\NewStatusPipeline; @@ -40,401 +40,400 @@ use App\Services\StatusService; class InternalApiController extends Controller { - protected $fractal; + protected $fractal; - public function __construct() - { - $this->middleware('auth'); - $this->fractal = new Fractal\Manager(); - $this->fractal->setSerializer(new ArraySerializer()); - } + public function __construct() + { + $this->middleware('auth'); + $this->fractal = new Fractal\Manager(); + $this->fractal->setSerializer(new ArraySerializer()); + } - // deprecated v2 compose api - public function compose(Request $request) - { - return redirect('/'); - } + // deprecated v2 compose api + public function compose(Request $request) + { + return redirect('/'); + } - // deprecated - public function discover(Request $request) - { - return; - } + // deprecated + public function discover(Request $request) + { + return; + } - public function discoverPosts(Request $request) - { - $profile = Auth::user()->profile; - $pid = $profile->id; - $following = Cache::remember('feature:discover:following:'.$pid, now()->addMinutes(15), function() use ($pid) { - return Follower::whereProfileId($pid)->pluck('following_id')->toArray(); - }); - $filters = Cache::remember("user:filter:list:$pid", now()->addMinutes(15), function() use($pid) { - $private = Profile::whereIsPrivate(true) - ->orWhere('unlisted', true) - ->orWhere('status', '!=', null) - ->pluck('id') - ->toArray(); - $filters = UserFilter::whereUserId($pid) - ->whereFilterableType('App\Profile') - ->whereIn('filter_type', ['mute', 'block']) - ->pluck('filterable_id') - ->toArray(); - return array_merge($private, $filters); - }); - $following = array_merge($following, $filters); + public function discoverPosts(Request $request) + { + $profile = Auth::user()->profile; + $pid = $profile->id; + $following = Cache::remember('feature:discover:following:'.$pid, now()->addMinutes(15), function() use ($pid) { + return Follower::whereProfileId($pid)->pluck('following_id')->toArray(); + }); + $filters = Cache::remember("user:filter:list:$pid", now()->addMinutes(15), function() use($pid) { + $private = Profile::whereIsPrivate(true) + ->orWhere('unlisted', true) + ->orWhere('status', '!=', null) + ->pluck('id') + ->toArray(); + $filters = UserFilter::whereUserId($pid) + ->whereFilterableType('App\Profile') + ->whereIn('filter_type', ['mute', 'block']) + ->pluck('filterable_id') + ->toArray(); + return array_merge($private, $filters); + }); + $following = array_merge($following, $filters); - $sql = config('database.default') !== 'pgsql'; - $min_id = SnowflakeService::byDate(now()->subMonths(3)); - $posts = Status::select( - 'id', - 'is_nsfw', - 'profile_id', - 'type', - 'uri', - ) - ->whereNull('uri') - ->whereIn('type', ['photo','photo:album', 'video']) - ->whereIsNsfw(false) - ->whereVisibility('public') - ->whereNotIn('profile_id', $following) - ->where('id', '>', $min_id) - ->inRandomOrder() - ->take(39) - ->pluck('id'); + $sql = config('database.default') !== 'pgsql'; + $min_id = SnowflakeService::byDate(now()->subMonths(3)); + $posts = Status::select( + 'id', + 'is_nsfw', + 'profile_id', + 'type', + 'uri', + ) + ->whereNull('uri') + ->whereIn('type', ['photo','photo:album', 'video']) + ->whereIsNsfw(false) + ->whereVisibility('public') + ->whereNotIn('profile_id', $following) + ->where('id', '>', $min_id) + ->inRandomOrder() + ->take(39) + ->pluck('id'); - $res = [ - 'posts' => $posts->map(function($post) { - return StatusService::get($post); - }) - ]; - return response()->json($res); - } + $res = [ + 'posts' => $posts->map(function($post) { + return StatusService::get($post); + }) + ]; + return response()->json($res); + } - public function directMessage(Request $request, $profileId, $threadId) - { - $profile = Auth::user()->profile; + public function directMessage(Request $request, $profileId, $threadId) + { + $profile = Auth::user()->profile; - if($profileId != $profile->id) { - abort(403); - } + if($profileId != $profile->id) { + abort(403); + } - $msg = DirectMessage::whereToId($profile->id) - ->orWhere('from_id',$profile->id) - ->findOrFail($threadId); + $msg = DirectMessage::whereToId($profile->id) + ->orWhere('from_id',$profile->id) + ->findOrFail($threadId); - $thread = DirectMessage::with('status')->whereIn('to_id', [$profile->id, $msg->from_id]) - ->whereIn('from_id', [$profile->id,$msg->from_id]) - ->orderBy('created_at', 'asc') - ->paginate(30); + $thread = DirectMessage::with('status')->whereIn('to_id', [$profile->id, $msg->from_id]) + ->whereIn('from_id', [$profile->id,$msg->from_id]) + ->orderBy('created_at', 'asc') + ->paginate(30); - return response()->json(compact('msg', 'profile', 'thread'), 200, [], JSON_PRETTY_PRINT); - } + return response()->json(compact('msg', 'profile', 'thread'), 200, [], JSON_PRETTY_PRINT); + } - public function statusReplies(Request $request, int $id) - { - $this->validate($request, [ - 'limit' => 'nullable|int|min:1|max:6' - ]); - $parent = Status::whereScope('public')->findOrFail($id); - $limit = $request->input('limit') ?? 3; - $children = Status::whereInReplyToId($parent->id) - ->orderBy('created_at', 'desc') - ->take($limit) - ->get(); - $resource = new Fractal\Resource\Collection($children, new StatusTransformer()); - $res = $this->fractal->createData($resource)->toArray(); + public function statusReplies(Request $request, int $id) + { + $this->validate($request, [ + 'limit' => 'nullable|int|min:1|max:6' + ]); + $parent = Status::whereScope('public')->findOrFail($id); + $limit = $request->input('limit') ?? 3; + $children = Status::whereInReplyToId($parent->id) + ->orderBy('created_at', 'desc') + ->take($limit) + ->get(); + $resource = new Fractal\Resource\Collection($children, new StatusTransformer()); + $res = $this->fractal->createData($resource)->toArray(); - return response()->json($res); - } + return response()->json($res); + } - public function stories(Request $request) - { - - } + public function stories(Request $request) + { - public function discoverCategories(Request $request) - { - $categories = DiscoverCategory::whereActive(true)->orderBy('order')->take(10)->get(); - $res = $categories->map(function($item) { - return [ - 'name' => $item->name, - 'url' => $item->url(), - 'thumb' => $item->thumb() - ]; - }); - return response()->json($res); - } + } - public function modAction(Request $request) - { - abort_unless(Auth::user()->is_admin, 400); - $this->validate($request, [ - 'action' => [ - 'required', - 'string', - Rule::in([ - 'addcw', - 'remcw', - 'unlist' - - ]) - ], - 'item_id' => 'required|integer|min:1', - 'item_type' => [ - 'required', - 'string', - Rule::in(['profile', 'status']) - ] - ]); + public function discoverCategories(Request $request) + { + $categories = DiscoverCategory::whereActive(true)->orderBy('order')->take(10)->get(); + $res = $categories->map(function($item) { + return [ + 'name' => $item->name, + 'url' => $item->url(), + 'thumb' => $item->thumb() + ]; + }); + return response()->json($res); + } - $action = $request->input('action'); - $item_id = $request->input('item_id'); - $item_type = $request->input('item_type'); + public function modAction(Request $request) + { + abort_unless(Auth::user()->is_admin, 400); + $this->validate($request, [ + 'action' => [ + 'required', + 'string', + Rule::in([ + 'addcw', + 'remcw', + 'unlist' + ]) + ], + 'item_id' => 'required|integer|min:1', + 'item_type' => [ + 'required', + 'string', + Rule::in(['profile', 'status']) + ] + ]); - switch($action) { - case 'addcw': - $status = Status::findOrFail($item_id); - $status->is_nsfw = true; - $status->save(); - ModLogService::boot() - ->user(Auth::user()) - ->objectUid($status->profile->user_id) - ->objectId($status->id) - ->objectType('App\Status::class') - ->action('admin.status.moderate') - ->metadata([ - 'action' => 'cw', - 'message' => 'Success!' - ]) - ->accessLevel('admin') - ->save(); + $action = $request->input('action'); + $item_id = $request->input('item_id'); + $item_type = $request->input('item_type'); + + switch($action) { + case 'addcw': + $status = Status::findOrFail($item_id); + $status->is_nsfw = true; + $status->save(); + ModLogService::boot() + ->user(Auth::user()) + ->objectUid($status->profile->user_id) + ->objectId($status->id) + ->objectType('App\Status::class') + ->action('admin.status.moderate') + ->metadata([ + 'action' => 'cw', + 'message' => 'Success!' + ]) + ->accessLevel('admin') + ->save(); - if($status->uri == null) { - $media = $status->media; - $ai = new AccountInterstitial; - $ai->user_id = $status->profile->user_id; - $ai->type = 'post.cw'; - $ai->view = 'account.moderation.post.cw'; - $ai->item_type = 'App\Status'; - $ai->item_id = $status->id; - $ai->has_media = (bool) $media->count(); - $ai->blurhash = $media->count() ? $media->first()->blurhash : null; - $ai->meta = json_encode([ - 'caption' => $status->caption, - 'created_at' => $status->created_at, - 'type' => $status->type, - 'url' => $status->url(), - 'is_nsfw' => $status->is_nsfw, - 'scope' => $status->scope, - 'reblog' => $status->reblog_of_id, - 'likes_count' => $status->likes_count, - 'reblogs_count' => $status->reblogs_count, - ]); - $ai->save(); + if($status->uri == null) { + $media = $status->media; + $ai = new AccountInterstitial; + $ai->user_id = $status->profile->user_id; + $ai->type = 'post.cw'; + $ai->view = 'account.moderation.post.cw'; + $ai->item_type = 'App\Status'; + $ai->item_id = $status->id; + $ai->has_media = (bool) $media->count(); + $ai->blurhash = $media->count() ? $media->first()->blurhash : null; + $ai->meta = json_encode([ + 'caption' => $status->caption, + 'created_at' => $status->created_at, + 'type' => $status->type, + 'url' => $status->url(), + 'is_nsfw' => $status->is_nsfw, + 'scope' => $status->scope, + 'reblog' => $status->reblog_of_id, + 'likes_count' => $status->likes_count, + 'reblogs_count' => $status->reblogs_count, + ]); + $ai->save(); - $u = $status->profile->user; - $u->has_interstitial = true; - $u->save(); - } - break; + $u = $status->profile->user; + $u->has_interstitial = true; + $u->save(); + } + break; - case 'remcw': - $status = Status::findOrFail($item_id); - $status->is_nsfw = false; - $status->save(); - ModLogService::boot() - ->user(Auth::user()) - ->objectUid($status->profile->user_id) - ->objectId($status->id) - ->objectType('App\Status::class') - ->action('admin.status.moderate') - ->metadata([ - 'action' => 'remove_cw', - 'message' => 'Success!' - ]) - ->accessLevel('admin') - ->save(); - if($status->uri == null) { - $ai = AccountInterstitial::whereUserId($status->profile->user_id) - ->whereType('post.cw') - ->whereItemId($status->id) - ->whereItemType('App\Status') - ->first(); - $ai->delete(); - } - break; + case 'remcw': + $status = Status::findOrFail($item_id); + $status->is_nsfw = false; + $status->save(); + ModLogService::boot() + ->user(Auth::user()) + ->objectUid($status->profile->user_id) + ->objectId($status->id) + ->objectType('App\Status::class') + ->action('admin.status.moderate') + ->metadata([ + 'action' => 'remove_cw', + 'message' => 'Success!' + ]) + ->accessLevel('admin') + ->save(); + if($status->uri == null) { + $ai = AccountInterstitial::whereUserId($status->profile->user_id) + ->whereType('post.cw') + ->whereItemId($status->id) + ->whereItemType('App\Status') + ->first(); + $ai->delete(); + } + break; - case 'unlist': - $status = Status::whereScope('public')->findOrFail($item_id); - $status->scope = $status->visibility = 'unlisted'; - $status->save(); - PublicTimelineService::del($status->id); - ModLogService::boot() - ->user(Auth::user()) - ->objectUid($status->profile->user_id) - ->objectId($status->id) - ->objectType('App\Status::class') - ->action('admin.status.moderate') - ->metadata([ - 'action' => 'unlist', - 'message' => 'Success!' - ]) - ->accessLevel('admin') - ->save(); + case 'unlist': + $status = Status::whereScope('public')->findOrFail($item_id); + $status->scope = $status->visibility = 'unlisted'; + $status->save(); + PublicTimelineService::del($status->id); + ModLogService::boot() + ->user(Auth::user()) + ->objectUid($status->profile->user_id) + ->objectId($status->id) + ->objectType('App\Status::class') + ->action('admin.status.moderate') + ->metadata([ + 'action' => 'unlist', + 'message' => 'Success!' + ]) + ->accessLevel('admin') + ->save(); - if($status->uri == null) { - $media = $status->media; - $ai = new AccountInterstitial; - $ai->user_id = $status->profile->user_id; - $ai->type = 'post.unlist'; - $ai->view = 'account.moderation.post.unlist'; - $ai->item_type = 'App\Status'; - $ai->item_id = $status->id; - $ai->has_media = (bool) $media->count(); - $ai->blurhash = $media->count() ? $media->first()->blurhash : null; - $ai->meta = json_encode([ - 'caption' => $status->caption, - 'created_at' => $status->created_at, - 'type' => $status->type, - 'url' => $status->url(), - 'is_nsfw' => $status->is_nsfw, - 'scope' => $status->scope, - 'reblog' => $status->reblog_of_id, - 'likes_count' => $status->likes_count, - 'reblogs_count' => $status->reblogs_count, - ]); - $ai->save(); + if($status->uri == null) { + $media = $status->media; + $ai = new AccountInterstitial; + $ai->user_id = $status->profile->user_id; + $ai->type = 'post.unlist'; + $ai->view = 'account.moderation.post.unlist'; + $ai->item_type = 'App\Status'; + $ai->item_id = $status->id; + $ai->has_media = (bool) $media->count(); + $ai->blurhash = $media->count() ? $media->first()->blurhash : null; + $ai->meta = json_encode([ + 'caption' => $status->caption, + 'created_at' => $status->created_at, + 'type' => $status->type, + 'url' => $status->url(), + 'is_nsfw' => $status->is_nsfw, + 'scope' => $status->scope, + 'reblog' => $status->reblog_of_id, + 'likes_count' => $status->likes_count, + 'reblogs_count' => $status->reblogs_count, + ]); + $ai->save(); - $u = $status->profile->user; - $u->has_interstitial = true; - $u->save(); - } - break; - } + $u = $status->profile->user; + $u->has_interstitial = true; + $u->save(); + } + break; + } - Cache::forget('_api:statuses:recent_9:' . $status->profile_id); - Cache::forget('profile:embed:' . $status->profile_id); + Cache::forget('_api:statuses:recent_9:' . $status->profile_id); + Cache::forget('profile:embed:' . $status->profile_id); - return ['msg' => 200]; - } + return ['msg' => 200]; + } - public function composePost(Request $request) - { - abort(400, 'Endpoint deprecated'); - } + public function composePost(Request $request) + { + abort(400, 'Endpoint deprecated'); + } - public function bookmarks(Request $request) - { - $statuses = Auth::user()->profile - ->bookmarks() - ->withCount(['likes','comments']) - ->orderBy('created_at', 'desc') - ->simplePaginate(10); + public function bookmarks(Request $request) + { + $statuses = Auth::user()->profile + ->bookmarks() + ->withCount(['likes','comments']) + ->orderBy('created_at', 'desc') + ->simplePaginate(10); - $resource = new Fractal\Resource\Collection($statuses, new StatusTransformer()); - $res = $this->fractal->createData($resource)->toArray(); + $resource = new Fractal\Resource\Collection($statuses, new StatusTransformer()); + $res = $this->fractal->createData($resource)->toArray(); - return response()->json($res); - } + return response()->json($res); + } - public function accountStatuses(Request $request, $id) - { - $this->validate($request, [ - 'only_media' => 'nullable', - 'pinned' => 'nullable', - 'exclude_replies' => 'nullable', - 'max_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX, - 'since_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX, - 'min_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX, - 'limit' => 'nullable|integer|min:1|max:24' - ]); + public function accountStatuses(Request $request, $id) + { + $this->validate($request, [ + 'only_media' => 'nullable', + 'pinned' => 'nullable', + 'exclude_replies' => 'nullable', + 'max_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX, + 'since_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX, + 'min_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX, + 'limit' => 'nullable|integer|min:1|max:24' + ]); - $profile = Profile::whereNull('status')->findOrFail($id); + $profile = Profile::whereNull('status')->findOrFail($id); - $limit = $request->limit ?? 9; - $max_id = $request->max_id; - $min_id = $request->min_id; - $scope = $request->only_media == true ? - ['photo', 'photo:album', 'video', 'video:album'] : - ['photo', 'photo:album', 'video', 'video:album', 'share', 'reply']; - - if($profile->is_private) { - if(!Auth::check()) { - return response()->json([]); - } - $pid = Auth::user()->profile->id; - $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) { - $following = Follower::whereProfileId($pid)->pluck('following_id'); - return $following->push($pid)->toArray(); - }); - $visibility = true == in_array($profile->id, $following) ? ['public', 'unlisted', 'private'] : []; - } else { - if(Auth::check()) { - $pid = Auth::user()->profile->id; - $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) { - $following = Follower::whereProfileId($pid)->pluck('following_id'); - return $following->push($pid)->toArray(); - }); - $visibility = true == in_array($profile->id, $following) ? ['public', 'unlisted', 'private'] : ['public', 'unlisted']; - } else { - $visibility = ['public', 'unlisted']; - } - } + $limit = $request->limit ?? 9; + $max_id = $request->max_id; + $min_id = $request->min_id; + $scope = $request->only_media == true ? + ['photo', 'photo:album', 'video', 'video:album'] : + ['photo', 'photo:album', 'video', 'video:album', 'share', 'reply']; - $dir = $min_id ? '>' : '<'; - $id = $min_id ?? $max_id; - $timeline = Status::select( - 'id', - 'uri', - 'caption', - 'rendered', - 'profile_id', - 'type', - 'in_reply_to_id', - 'reblog_of_id', - 'is_nsfw', - 'likes_count', - 'reblogs_count', - 'scope', - 'local', - 'created_at', - 'updated_at' - )->whereProfileId($profile->id) - ->whereIn('type', $scope) - ->where('id', $dir, $id) - ->whereIn('visibility', $visibility) - ->latest() - ->limit($limit) - ->get(); + if($profile->is_private) { + if(!Auth::check()) { + return response()->json([]); + } + $pid = Auth::user()->profile->id; + $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) { + $following = Follower::whereProfileId($pid)->pluck('following_id'); + return $following->push($pid)->toArray(); + }); + $visibility = true == in_array($profile->id, $following) ? ['public', 'unlisted', 'private'] : []; + } else { + if(Auth::check()) { + $pid = Auth::user()->profile->id; + $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) { + $following = Follower::whereProfileId($pid)->pluck('following_id'); + return $following->push($pid)->toArray(); + }); + $visibility = true == in_array($profile->id, $following) ? ['public', 'unlisted', 'private'] : ['public', 'unlisted']; + } else { + $visibility = ['public', 'unlisted']; + } + } - $resource = new Fractal\Resource\Collection($timeline, new StatusTransformer()); - $res = $this->fractal->createData($resource)->toArray(); + $dir = $min_id ? '>' : '<'; + $id = $min_id ?? $max_id; + $timeline = Status::select( + 'id', + 'uri', + 'caption', + 'rendered', + 'profile_id', + 'type', + 'in_reply_to_id', + 'reblog_of_id', + 'is_nsfw', + 'likes_count', + 'reblogs_count', + 'scope', + 'local', + 'created_at', + 'updated_at' + )->whereProfileId($profile->id) + ->whereIn('type', $scope) + ->where('id', $dir, $id) + ->whereIn('visibility', $visibility) + ->latest() + ->limit($limit) + ->get(); - return response()->json($res); - } + $resource = new Fractal\Resource\Collection($timeline, new StatusTransformer()); + $res = $this->fractal->createData($resource)->toArray(); - public function remoteProfile(Request $request, $id) - { - $profile = Profile::whereNull('status') - ->whereNotNull('domain') - ->findOrFail($id); - $user = Auth::user(); + return response()->json($res); + } - return view('profile.remote', compact('profile', 'user')); - } + public function remoteProfile(Request $request, $id) + { + $profile = Profile::whereNull('status') + ->whereNotNull('domain') + ->findOrFail($id); + $user = Auth::user(); - public function remoteStatus(Request $request, $profileId, $statusId) - { - $user = Profile::whereNull('status') - ->whereNotNull('domain') - ->findOrFail($profileId); + return view('profile.remote', compact('profile', 'user')); + } - $status = Status::whereProfileId($user->id) - ->whereNull('reblog_of_id') - ->whereIn('visibility', ['public', 'unlisted']) - ->findOrFail($statusId); - $template = $status->in_reply_to_id ? 'status.reply' : 'status.remote'; - return view($template, compact('user', 'status')); - } + public function remoteStatus(Request $request, $profileId, $statusId) + { + $user = Profile::whereNull('status') + ->whereNotNull('domain') + ->findOrFail($profileId); + + $status = Status::whereProfileId($user->id) + ->whereNull('reblog_of_id') + ->whereIn('visibility', ['public', 'unlisted']) + ->findOrFail($statusId); + $template = $status->in_reply_to_id ? 'status.reply' : 'status.remote'; + return view($template, compact('user', 'status')); + } } From 6d956a86f4ab6f043c27fafc8c2659981e938dfc Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 29 Jun 2021 02:14:22 -0600 Subject: [PATCH 2/4] Add mark as spammer mod tool, unlists and applies content warning to existing and future posts --- .../Controllers/InternalApiController.php | 21 +- .../ModPipeline/HandleSpammerPipeline.php | 52 +++++ .../js/components/partials/ContextMenu.vue | 198 ++++++++++-------- 3 files changed, 188 insertions(+), 83 deletions(-) create mode 100644 app/Jobs/ModPipeline/HandleSpammerPipeline.php diff --git a/app/Http/Controllers/InternalApiController.php b/app/Http/Controllers/InternalApiController.php index 90c195a28..ad34f92ae 100644 --- a/app/Http/Controllers/InternalApiController.php +++ b/app/Http/Controllers/InternalApiController.php @@ -28,6 +28,7 @@ use App\Transformer\Api\{ }; use App\Util\Media\Filter; use App\Jobs\StatusPipeline\NewStatusPipeline; +use App\Jobs\ModPipeline\HandleSpammerPipeline; use League\Fractal\Serializer\ArraySerializer; use League\Fractal\Pagination\IlluminatePaginatorAdapter; use Illuminate\Validation\Rule; @@ -175,7 +176,8 @@ class InternalApiController extends Controller Rule::in([ 'addcw', 'remcw', - 'unlist' + 'unlist', + 'spammer' ]) ], 'item_id' => 'required|integer|min:1', @@ -310,6 +312,23 @@ class InternalApiController extends Controller $u->save(); } break; + + case 'spammer': + $status = Status::findOrFail($item_id); + HandleSpammerPipeline::dispatch($status->profile); + ModLogService::boot() + ->user(Auth::user()) + ->objectUid($status->profile->user_id) + ->objectId($status->id) + ->objectType('App\User::class') + ->action('admin.status.moderate') + ->metadata([ + 'action' => 'spammer', + 'message' => 'Success!' + ]) + ->accessLevel('admin') + ->save(); + break; } Cache::forget('_api:statuses:recent_9:' . $status->profile_id); diff --git a/app/Jobs/ModPipeline/HandleSpammerPipeline.php b/app/Jobs/ModPipeline/HandleSpammerPipeline.php new file mode 100644 index 000000000..0e5b4042d --- /dev/null +++ b/app/Jobs/ModPipeline/HandleSpammerPipeline.php @@ -0,0 +1,52 @@ +profile = $profile; + } + + public function handle() + { + $profile = $this->profile; + + $profile->unlisted = true; + $profile->cw = true; + $profile->no_autolink = true; + $profile->save(); + + Status::whereProfileId($profile->id) + ->chunk(50, function($statuses) { + foreach($statuses as $status) { + $status->is_nsfw = true; + $status->scope = $status->scope === 'public' ? 'unlisted' : $status->scope; + $status->visibility = $status->scope; + $status->save(); + StatusService::del($status->id); + } + }); + + Cache::forget('_api:statuses:recent_9:'.$profile->id); + + return 1; + } +} diff --git a/resources/assets/js/components/partials/ContextMenu.vue b/resources/assets/js/components/partials/ContextMenu.vue index 5a398ba74..35402d819 100644 --- a/resources/assets/js/components/partials/ContextMenu.vue +++ b/resources/assets/js/components/partials/ContextMenu.vue @@ -37,6 +37,10 @@
Unlist from Timelines
Remove Content Warning
Add Content Warning
+
+ Mark as Spammer
+ Unlist + CW existing and future posts +
Cancel
@@ -465,99 +469,129 @@ moderatePost(status, action, $event) { let username = status.account.username; + let pid = status.id; let msg = ''; let self = this; switch(action) { case 'addcw': - msg = 'Are you sure you want to add a content warning to this post?'; - swal({ - title: 'Confirm', - text: msg, - icon: 'warning', - buttons: true, - dangerMode: true - }).then(res => { - if(res) { - axios.post('/api/v2/moderator/action', { - action: action, - item_id: status.id, - item_type: 'status' - }).then(res => { - swal('Success', 'Successfully added content warning', 'success'); - status.sensitive = true; - self.ctxModMenuClose(); - }).catch(err => { - swal( - 'Error', - 'Something went wrong, please try again later.', - 'error' - ); - self.ctxModMenuClose(); - }); - } - }); + msg = 'Are you sure you want to add a content warning to this post?'; + swal({ + title: 'Confirm', + text: msg, + icon: 'warning', + buttons: true, + dangerMode: true + }).then(res => { + if(res) { + axios.post('/api/v2/moderator/action', { + action: action, + item_id: status.id, + item_type: 'status' + }).then(res => { + swal('Success', 'Successfully added content warning', 'success'); + status.sensitive = true; + self.ctxModMenuClose(); + }).catch(err => { + swal( + 'Error', + 'Something went wrong, please try again later.', + 'error' + ); + self.ctxModMenuClose(); + }); + } + }); break; case 'remcw': - msg = 'Are you sure you want to remove the content warning on this post?'; - swal({ - title: 'Confirm', - text: msg, - icon: 'warning', - buttons: true, - dangerMode: true - }).then(res => { - if(res) { - axios.post('/api/v2/moderator/action', { - action: action, - item_id: status.id, - item_type: 'status' - }).then(res => { - swal('Success', 'Successfully added content warning', 'success'); - status.sensitive = false; - self.ctxModMenuClose(); - }).catch(err => { - swal( - 'Error', - 'Something went wrong, please try again later.', - 'error' - ); - self.ctxModMenuClose(); - }); - } - }); + msg = 'Are you sure you want to remove the content warning on this post?'; + swal({ + title: 'Confirm', + text: msg, + icon: 'warning', + buttons: true, + dangerMode: true + }).then(res => { + if(res) { + axios.post('/api/v2/moderator/action', { + action: action, + item_id: status.id, + item_type: 'status' + }).then(res => { + swal('Success', 'Successfully added content warning', 'success'); + status.sensitive = false; + self.ctxModMenuClose(); + }).catch(err => { + swal( + 'Error', + 'Something went wrong, please try again later.', + 'error' + ); + self.ctxModMenuClose(); + }); + } + }); break; case 'unlist': - msg = 'Are you sure you want to unlist this post?'; - swal({ - title: 'Confirm', - text: msg, - icon: 'warning', - buttons: true, - dangerMode: true - }).then(res => { - if(res) { - axios.post('/api/v2/moderator/action', { - action: action, - item_id: status.id, - item_type: 'status' - }).then(res => { - this.feed = this.feed.filter(f => { - return f.id != status.id; + msg = 'Are you sure you want to unlist this post?'; + swal({ + title: 'Confirm', + text: msg, + icon: 'warning', + buttons: true, + dangerMode: true + }).then(res => { + if(res) { + axios.post('/api/v2/moderator/action', { + action: action, + item_id: status.id, + item_type: 'status' + }).then(res => { + this.feed = this.feed.filter(f => { + return f.id != status.id; + }); + swal('Success', 'Successfully unlisted post', 'success'); + self.ctxModMenuClose(); + }).catch(err => { + self.ctxModMenuClose(); + swal( + 'Error', + 'Something went wrong, please try again later.', + 'error' + ); }); - swal('Success', 'Successfully unlisted post', 'success'); - self.ctxModMenuClose(); - }).catch(err => { - self.ctxModMenuClose(); - swal( - 'Error', - 'Something went wrong, please try again later.', - 'error' - ); - }); - } - }); + } + }); + break; + + case 'spammer': + msg = 'Are you sure you want to mark this user as a spammer? All existing and future posts will be unlisted on timelines and a content warning will be applied.'; + swal({ + title: 'Confirm', + text: msg, + icon: 'warning', + buttons: true, + dangerMode: true + }).then(res => { + if(res) { + axios.post('/api/v2/moderator/action', { + action: action, + item_id: status.id, + item_type: 'status' + }).then(res => { + swal('Success', 'Successfully marked account as spammer', 'success'); + self.ctxModMenuClose(); + }).catch(err => { + self.ctxModMenuClose(); + swal( + 'Error', + 'Something went wrong, please try again later.', + 'error' + ); + }); + } + }); break; } }, From bae8075ef138fc1d47fb8b26422178cb10de9ca8 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 29 Jun 2021 02:15:15 -0600 Subject: [PATCH 3/4] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7747a7fc..37b92672d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Added - WebP Support ([069a0e4a](https://github.com/pixelfed/pixelfed/commit/069a0e4a)) - Auto Following support for admins ([68aa2540](https://github.com/pixelfed/pixelfed/commit/68aa2540)) +- Mark as spammer mod tool, unlists and applies content warning to existing and future post ([6d956a86](https://github.com/pixelfed/pixelfed/commit/6d956a86)) ### Updated - Updated PrettyNumber, fix deprecated warning. ([20ec870b](https://github.com/pixelfed/pixelfed/commit/20ec870b)) From c0a2f88b49d727063e84f52552c31648c2764063 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 29 Jun 2021 02:15:30 -0600 Subject: [PATCH 4/4] Update compiled assets --- public/js/timeline.js | Bin 202153 -> 203006 bytes public/mix-manifest.json | Bin 2125 -> 2125 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/public/js/timeline.js b/public/js/timeline.js index 98a5609707d005a0c23c4d3114eb2bad284435d2..3148454862ffcdff3fef08f8da0422e99e187cbc 100644 GIT binary patch delta 410 zcmZ3vn&;m}o(+PLrs1|_`I#vS2DY}gPBk@7I)%1*dc_5a$*IAqIjPAd#lDFp$r(D4 zP*sYywuNeHg`3?X^%>d1bxJZaizmVic%A^t&*<>saqO+L6mE;=`{C|d!fNujhjwMZecSRqjXDsQjgn3JQBT9H{?l9`vTkeHXE zkXBk!T9m3#kY8LQn4-jj zf}G6M6g?%K=>mtC+$MYfE}w3b&&WBM>5sT1$PqyOiOI?NrFkVFM?jsh`Tf?r+>-^4 zD^8a`%OuVP6;;x#oj!3fqlknr$nikc!4MBAY3k%@C?ypsO&9cFRGzG`pH*L}Rwued qFTPAeDHQBV1#JcAaNK^H+;CE4@`f9-%^ME3Z#c-feZxVfpTPhNLYNN# delta 111 zcmeyjk!R&a;bxJZaizm;hrpT->^C8XnW96 M#_d5znbrgY0MutH4gdfE diff --git a/public/mix-manifest.json b/public/mix-manifest.json index 1bbf0344302a04449673d8c71401446e06d54702..6c26b076523ca5f54a53702cda3ef8d92256d583 100644 GIT binary patch delta 32 ncmX>ra8_W0DTjoad9sO7Vxp0GN@B8sX`*SWrD3uXS1lI+q|gZ| delta 32 ncmX>ra8_W0DTjo)Nn(nnaiUSOsky0zp=oMzvPF^-S1lI+qD=`2