Merge pull request #2829 from pixelfed/staging

Staging
This commit is contained in:
daniel 2021-06-29 02:17:06 -06:00 committed by GitHub
commit 8513b6d1e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 561 additions and 456 deletions

View File

@ -4,6 +4,7 @@
### Added ### Added
- WebP Support ([069a0e4a](https://github.com/pixelfed/pixelfed/commit/069a0e4a)) - WebP Support ([069a0e4a](https://github.com/pixelfed/pixelfed/commit/069a0e4a))
- Auto Following support for admins ([68aa2540](https://github.com/pixelfed/pixelfed/commit/68aa2540)) - 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
- Updated PrettyNumber, fix deprecated warning. ([20ec870b](https://github.com/pixelfed/pixelfed/commit/20ec870b)) - Updated PrettyNumber, fix deprecated warning. ([20ec870b](https://github.com/pixelfed/pixelfed/commit/20ec870b))

View File

@ -4,30 +4,31 @@ namespace App\Http\Controllers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\{ use App\{
AccountInterstitial, AccountInterstitial,
DirectMessage, DirectMessage,
DiscoverCategory, DiscoverCategory,
Hashtag, Hashtag,
Follower, Follower,
Like, Like,
Media, Media,
MediaTag, MediaTag,
Notification, Notification,
Profile, Profile,
StatusHashtag, StatusHashtag,
Status, Status,
UserFilter, UserFilter,
}; };
use Auth,Cache; use Auth,Cache;
use Carbon\Carbon; use Carbon\Carbon;
use League\Fractal; use League\Fractal;
use App\Transformer\Api\{ use App\Transformer\Api\{
AccountTransformer, AccountTransformer,
StatusTransformer, StatusTransformer,
// StatusMediaContainerTransformer, // StatusMediaContainerTransformer,
}; };
use App\Util\Media\Filter; use App\Util\Media\Filter;
use App\Jobs\StatusPipeline\NewStatusPipeline; use App\Jobs\StatusPipeline\NewStatusPipeline;
use App\Jobs\ModPipeline\HandleSpammerPipeline;
use League\Fractal\Serializer\ArraySerializer; use League\Fractal\Serializer\ArraySerializer;
use League\Fractal\Pagination\IlluminatePaginatorAdapter; use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
@ -40,401 +41,418 @@ use App\Services\StatusService;
class InternalApiController extends Controller class InternalApiController extends Controller
{ {
protected $fractal; protected $fractal;
public function __construct() public function __construct()
{ {
$this->middleware('auth'); $this->middleware('auth');
$this->fractal = new Fractal\Manager(); $this->fractal = new Fractal\Manager();
$this->fractal->setSerializer(new ArraySerializer()); $this->fractal->setSerializer(new ArraySerializer());
} }
// deprecated v2 compose api // deprecated v2 compose api
public function compose(Request $request) public function compose(Request $request)
{ {
return redirect('/'); return redirect('/');
} }
// deprecated // deprecated
public function discover(Request $request) public function discover(Request $request)
{ {
return; return;
} }
public function discoverPosts(Request $request) public function discoverPosts(Request $request)
{ {
$profile = Auth::user()->profile; $profile = Auth::user()->profile;
$pid = $profile->id; $pid = $profile->id;
$following = Cache::remember('feature:discover:following:'.$pid, now()->addMinutes(15), function() use ($pid) { $following = Cache::remember('feature:discover:following:'.$pid, now()->addMinutes(15), function() use ($pid) {
return Follower::whereProfileId($pid)->pluck('following_id')->toArray(); return Follower::whereProfileId($pid)->pluck('following_id')->toArray();
}); });
$filters = Cache::remember("user:filter:list:$pid", now()->addMinutes(15), function() use($pid) { $filters = Cache::remember("user:filter:list:$pid", now()->addMinutes(15), function() use($pid) {
$private = Profile::whereIsPrivate(true) $private = Profile::whereIsPrivate(true)
->orWhere('unlisted', true) ->orWhere('unlisted', true)
->orWhere('status', '!=', null) ->orWhere('status', '!=', null)
->pluck('id') ->pluck('id')
->toArray(); ->toArray();
$filters = UserFilter::whereUserId($pid) $filters = UserFilter::whereUserId($pid)
->whereFilterableType('App\Profile') ->whereFilterableType('App\Profile')
->whereIn('filter_type', ['mute', 'block']) ->whereIn('filter_type', ['mute', 'block'])
->pluck('filterable_id') ->pluck('filterable_id')
->toArray(); ->toArray();
return array_merge($private, $filters); return array_merge($private, $filters);
}); });
$following = array_merge($following, $filters); $following = array_merge($following, $filters);
$sql = config('database.default') !== 'pgsql'; $sql = config('database.default') !== 'pgsql';
$min_id = SnowflakeService::byDate(now()->subMonths(3)); $min_id = SnowflakeService::byDate(now()->subMonths(3));
$posts = Status::select( $posts = Status::select(
'id', 'id',
'is_nsfw', 'is_nsfw',
'profile_id', 'profile_id',
'type', 'type',
'uri', 'uri',
) )
->whereNull('uri') ->whereNull('uri')
->whereIn('type', ['photo','photo:album', 'video']) ->whereIn('type', ['photo','photo:album', 'video'])
->whereIsNsfw(false) ->whereIsNsfw(false)
->whereVisibility('public') ->whereVisibility('public')
->whereNotIn('profile_id', $following) ->whereNotIn('profile_id', $following)
->where('id', '>', $min_id) ->where('id', '>', $min_id)
->inRandomOrder() ->inRandomOrder()
->take(39) ->take(39)
->pluck('id'); ->pluck('id');
$res = [ $res = [
'posts' => $posts->map(function($post) { 'posts' => $posts->map(function($post) {
return StatusService::get($post); return StatusService::get($post);
}) })
]; ];
return response()->json($res); return response()->json($res);
} }
public function directMessage(Request $request, $profileId, $threadId) public function directMessage(Request $request, $profileId, $threadId)
{ {
$profile = Auth::user()->profile; $profile = Auth::user()->profile;
if($profileId != $profile->id) { if($profileId != $profile->id) {
abort(403); abort(403);
} }
$msg = DirectMessage::whereToId($profile->id) $msg = DirectMessage::whereToId($profile->id)
->orWhere('from_id',$profile->id) ->orWhere('from_id',$profile->id)
->findOrFail($threadId); ->findOrFail($threadId);
$thread = DirectMessage::with('status')->whereIn('to_id', [$profile->id, $msg->from_id]) $thread = DirectMessage::with('status')->whereIn('to_id', [$profile->id, $msg->from_id])
->whereIn('from_id', [$profile->id,$msg->from_id]) ->whereIn('from_id', [$profile->id,$msg->from_id])
->orderBy('created_at', 'asc') ->orderBy('created_at', 'asc')
->paginate(30); ->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) public function statusReplies(Request $request, int $id)
{ {
$this->validate($request, [ $this->validate($request, [
'limit' => 'nullable|int|min:1|max:6' 'limit' => 'nullable|int|min:1|max:6'
]); ]);
$parent = Status::whereScope('public')->findOrFail($id); $parent = Status::whereScope('public')->findOrFail($id);
$limit = $request->input('limit') ?? 3; $limit = $request->input('limit') ?? 3;
$children = Status::whereInReplyToId($parent->id) $children = Status::whereInReplyToId($parent->id)
->orderBy('created_at', 'desc') ->orderBy('created_at', 'desc')
->take($limit) ->take($limit)
->get(); ->get();
$resource = new Fractal\Resource\Collection($children, new StatusTransformer()); $resource = new Fractal\Resource\Collection($children, new StatusTransformer());
$res = $this->fractal->createData($resource)->toArray(); $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) public function discoverCategories(Request $request)
{ {
abort_unless(Auth::user()->is_admin, 400); $categories = DiscoverCategory::whereActive(true)->orderBy('order')->take(10)->get();
$this->validate($request, [ $res = $categories->map(function($item) {
'action' => [ return [
'required', 'name' => $item->name,
'string', 'url' => $item->url(),
Rule::in([ 'thumb' => $item->thumb()
'addcw', ];
'remcw', });
'unlist' return response()->json($res);
}
])
],
'item_id' => 'required|integer|min:1',
'item_type' => [
'required',
'string',
Rule::in(['profile', 'status'])
]
]);
$action = $request->input('action'); public function modAction(Request $request)
$item_id = $request->input('item_id'); {
$item_type = $request->input('item_type'); abort_unless(Auth::user()->is_admin, 400);
$this->validate($request, [
'action' => [
'required',
'string',
Rule::in([
'addcw',
'remcw',
'unlist',
'spammer'
])
],
'item_id' => 'required|integer|min:1',
'item_type' => [
'required',
'string',
Rule::in(['profile', 'status'])
]
]);
switch($action) { $action = $request->input('action');
case 'addcw': $item_id = $request->input('item_id');
$status = Status::findOrFail($item_id); $item_type = $request->input('item_type');
$status->is_nsfw = true;
$status->save(); switch($action) {
ModLogService::boot() case 'addcw':
->user(Auth::user()) $status = Status::findOrFail($item_id);
->objectUid($status->profile->user_id) $status->is_nsfw = true;
->objectId($status->id) $status->save();
->objectType('App\Status::class') ModLogService::boot()
->action('admin.status.moderate') ->user(Auth::user())
->metadata([ ->objectUid($status->profile->user_id)
'action' => 'cw', ->objectId($status->id)
'message' => 'Success!' ->objectType('App\Status::class')
]) ->action('admin.status.moderate')
->accessLevel('admin') ->metadata([
->save(); 'action' => 'cw',
'message' => 'Success!'
])
->accessLevel('admin')
->save();
if($status->uri == null) { if($status->uri == null) {
$media = $status->media; $media = $status->media;
$ai = new AccountInterstitial; $ai = new AccountInterstitial;
$ai->user_id = $status->profile->user_id; $ai->user_id = $status->profile->user_id;
$ai->type = 'post.cw'; $ai->type = 'post.cw';
$ai->view = 'account.moderation.post.cw'; $ai->view = 'account.moderation.post.cw';
$ai->item_type = 'App\Status'; $ai->item_type = 'App\Status';
$ai->item_id = $status->id; $ai->item_id = $status->id;
$ai->has_media = (bool) $media->count(); $ai->has_media = (bool) $media->count();
$ai->blurhash = $media->count() ? $media->first()->blurhash : null; $ai->blurhash = $media->count() ? $media->first()->blurhash : null;
$ai->meta = json_encode([ $ai->meta = json_encode([
'caption' => $status->caption, 'caption' => $status->caption,
'created_at' => $status->created_at, 'created_at' => $status->created_at,
'type' => $status->type, 'type' => $status->type,
'url' => $status->url(), 'url' => $status->url(),
'is_nsfw' => $status->is_nsfw, 'is_nsfw' => $status->is_nsfw,
'scope' => $status->scope, 'scope' => $status->scope,
'reblog' => $status->reblog_of_id, 'reblog' => $status->reblog_of_id,
'likes_count' => $status->likes_count, 'likes_count' => $status->likes_count,
'reblogs_count' => $status->reblogs_count, 'reblogs_count' => $status->reblogs_count,
]); ]);
$ai->save(); $ai->save();
$u = $status->profile->user; $u = $status->profile->user;
$u->has_interstitial = true; $u->has_interstitial = true;
$u->save(); $u->save();
} }
break; break;
case 'remcw': case 'remcw':
$status = Status::findOrFail($item_id); $status = Status::findOrFail($item_id);
$status->is_nsfw = false; $status->is_nsfw = false;
$status->save(); $status->save();
ModLogService::boot() ModLogService::boot()
->user(Auth::user()) ->user(Auth::user())
->objectUid($status->profile->user_id) ->objectUid($status->profile->user_id)
->objectId($status->id) ->objectId($status->id)
->objectType('App\Status::class') ->objectType('App\Status::class')
->action('admin.status.moderate') ->action('admin.status.moderate')
->metadata([ ->metadata([
'action' => 'remove_cw', 'action' => 'remove_cw',
'message' => 'Success!' 'message' => 'Success!'
]) ])
->accessLevel('admin') ->accessLevel('admin')
->save(); ->save();
if($status->uri == null) { if($status->uri == null) {
$ai = AccountInterstitial::whereUserId($status->profile->user_id) $ai = AccountInterstitial::whereUserId($status->profile->user_id)
->whereType('post.cw') ->whereType('post.cw')
->whereItemId($status->id) ->whereItemId($status->id)
->whereItemType('App\Status') ->whereItemType('App\Status')
->first(); ->first();
$ai->delete(); $ai->delete();
} }
break; break;
case 'unlist': case 'unlist':
$status = Status::whereScope('public')->findOrFail($item_id); $status = Status::whereScope('public')->findOrFail($item_id);
$status->scope = $status->visibility = 'unlisted'; $status->scope = $status->visibility = 'unlisted';
$status->save(); $status->save();
PublicTimelineService::del($status->id); PublicTimelineService::del($status->id);
ModLogService::boot() ModLogService::boot()
->user(Auth::user()) ->user(Auth::user())
->objectUid($status->profile->user_id) ->objectUid($status->profile->user_id)
->objectId($status->id) ->objectId($status->id)
->objectType('App\Status::class') ->objectType('App\Status::class')
->action('admin.status.moderate') ->action('admin.status.moderate')
->metadata([ ->metadata([
'action' => 'unlist', 'action' => 'unlist',
'message' => 'Success!' 'message' => 'Success!'
]) ])
->accessLevel('admin') ->accessLevel('admin')
->save(); ->save();
if($status->uri == null) { if($status->uri == null) {
$media = $status->media; $media = $status->media;
$ai = new AccountInterstitial; $ai = new AccountInterstitial;
$ai->user_id = $status->profile->user_id; $ai->user_id = $status->profile->user_id;
$ai->type = 'post.unlist'; $ai->type = 'post.unlist';
$ai->view = 'account.moderation.post.unlist'; $ai->view = 'account.moderation.post.unlist';
$ai->item_type = 'App\Status'; $ai->item_type = 'App\Status';
$ai->item_id = $status->id; $ai->item_id = $status->id;
$ai->has_media = (bool) $media->count(); $ai->has_media = (bool) $media->count();
$ai->blurhash = $media->count() ? $media->first()->blurhash : null; $ai->blurhash = $media->count() ? $media->first()->blurhash : null;
$ai->meta = json_encode([ $ai->meta = json_encode([
'caption' => $status->caption, 'caption' => $status->caption,
'created_at' => $status->created_at, 'created_at' => $status->created_at,
'type' => $status->type, 'type' => $status->type,
'url' => $status->url(), 'url' => $status->url(),
'is_nsfw' => $status->is_nsfw, 'is_nsfw' => $status->is_nsfw,
'scope' => $status->scope, 'scope' => $status->scope,
'reblog' => $status->reblog_of_id, 'reblog' => $status->reblog_of_id,
'likes_count' => $status->likes_count, 'likes_count' => $status->likes_count,
'reblogs_count' => $status->reblogs_count, 'reblogs_count' => $status->reblogs_count,
]); ]);
$ai->save(); $ai->save();
$u = $status->profile->user; $u = $status->profile->user;
$u->has_interstitial = true; $u->has_interstitial = true;
$u->save(); $u->save();
} }
break; break;
}
Cache::forget('_api:statuses:recent_9:' . $status->profile_id); case 'spammer':
Cache::forget('profile:embed:' . $status->profile_id); $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;
}
return ['msg' => 200]; Cache::forget('_api:statuses:recent_9:' . $status->profile_id);
} Cache::forget('profile:embed:' . $status->profile_id);
public function composePost(Request $request) return ['msg' => 200];
{ }
abort(400, 'Endpoint deprecated');
}
public function bookmarks(Request $request) public function composePost(Request $request)
{ {
$statuses = Auth::user()->profile abort(400, 'Endpoint deprecated');
->bookmarks() }
->withCount(['likes','comments'])
->orderBy('created_at', 'desc')
->simplePaginate(10);
$resource = new Fractal\Resource\Collection($statuses, new StatusTransformer()); public function bookmarks(Request $request)
$res = $this->fractal->createData($resource)->toArray(); {
$statuses = Auth::user()->profile
->bookmarks()
->withCount(['likes','comments'])
->orderBy('created_at', 'desc')
->simplePaginate(10);
return response()->json($res); $resource = new Fractal\Resource\Collection($statuses, new StatusTransformer());
} $res = $this->fractal->createData($resource)->toArray();
public function accountStatuses(Request $request, $id) return response()->json($res);
{ }
$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); 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'
]);
$limit = $request->limit ?? 9; $profile = Profile::whereNull('status')->findOrFail($id);
$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'];
}
}
$dir = $min_id ? '>' : '<'; $limit = $request->limit ?? 9;
$id = $min_id ?? $max_id; $max_id = $request->max_id;
$timeline = Status::select( $min_id = $request->min_id;
'id', $scope = $request->only_media == true ?
'uri', ['photo', 'photo:album', 'video', 'video:album'] :
'caption', ['photo', 'photo:album', 'video', 'video:album', 'share', 'reply'];
'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();
$resource = new Fractal\Resource\Collection($timeline, new StatusTransformer()); if($profile->is_private) {
$res = $this->fractal->createData($resource)->toArray(); 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'];
}
}
return response()->json($res); $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();
public function remoteProfile(Request $request, $id) $resource = new Fractal\Resource\Collection($timeline, new StatusTransformer());
{ $res = $this->fractal->createData($resource)->toArray();
$profile = Profile::whereNull('status')
->whereNotNull('domain')
->findOrFail($id);
$user = Auth::user();
return view('profile.remote', compact('profile', 'user')); return response()->json($res);
} }
public function remoteStatus(Request $request, $profileId, $statusId) public function remoteProfile(Request $request, $id)
{ {
$user = Profile::whereNull('status') $profile = Profile::whereNull('status')
->whereNotNull('domain') ->whereNotNull('domain')
->findOrFail($profileId); ->findOrFail($id);
$user = Auth::user();
$status = Status::whereProfileId($user->id) return view('profile.remote', compact('profile', 'user'));
->whereNull('reblog_of_id') }
->whereIn('visibility', ['public', 'unlisted'])
->findOrFail($statusId); public function remoteStatus(Request $request, $profileId, $statusId)
$template = $status->in_reply_to_id ? 'status.reply' : 'status.remote'; {
return view($template, compact('user', 'status')); $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'));
}
} }

View File

@ -0,0 +1,52 @@
<?php
namespace App\Jobs\ModPipeline;
use Cache;
use App\Profile;
use App\Status;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Services\StatusService;
class HandleSpammerPipeline implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $profile;
public $deleteWhenMissingModels = true;
public function __construct(Profile $profile)
{
$this->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;
}
}

File diff suppressed because one or more lines are too long

View File

@ -30,5 +30,5 @@
"/js/status.js": "/js/status.js?id=36da3b0777b5f9633e86", "/js/status.js": "/js/status.js?id=36da3b0777b5f9633e86",
"/js/story-compose.js": "/js/story-compose.js?id=b16bcf2adad9651735e1", "/js/story-compose.js": "/js/story-compose.js?id=b16bcf2adad9651735e1",
"/js/theme-monokai.js": "/js/theme-monokai.js?id=8842103833ba4861bcfa", "/js/theme-monokai.js": "/js/theme-monokai.js?id=8842103833ba4861bcfa",
"/js/timeline.js": "/js/timeline.js?id=74ad93a2c575815ecc8b" "/js/timeline.js": "/js/timeline.js?id=67c42aa27dac05a5e91c"
} }

View File

@ -37,6 +37,10 @@
<div class="list-group-item rounded cursor-pointer" @click="moderatePost(status, 'unlist')">Unlist from Timelines</div> <div class="list-group-item rounded cursor-pointer" @click="moderatePost(status, 'unlist')">Unlist from Timelines</div>
<div v-if="status.sensitive" class="list-group-item rounded cursor-pointer" @click="moderatePost(status, 'remcw')">Remove Content Warning</div> <div v-if="status.sensitive" class="list-group-item rounded cursor-pointer" @click="moderatePost(status, 'remcw')">Remove Content Warning</div>
<div v-else class="list-group-item rounded cursor-pointer" @click="moderatePost(status, 'addcw')">Add Content Warning</div> <div v-else class="list-group-item rounded cursor-pointer" @click="moderatePost(status, 'addcw')">Add Content Warning</div>
<div class="list-group-item rounded cursor-pointer" @click="moderatePost(status, 'spammer')">
Mark as Spammer<br />
<span class="small">Unlist + CW existing and future posts</span>
</div>
<!-- <div class="list-group-item rounded cursor-pointer" @click="ctxModOtherMenuShow()">Other</div> --> <!-- <div class="list-group-item rounded cursor-pointer" @click="ctxModOtherMenuShow()">Other</div> -->
<div class="list-group-item rounded cursor-pointer text-lighter" @click="ctxModMenuClose()">Cancel</div> <div class="list-group-item rounded cursor-pointer text-lighter" @click="ctxModMenuClose()">Cancel</div>
</div> </div>
@ -465,99 +469,129 @@
moderatePost(status, action, $event) { moderatePost(status, action, $event) {
let username = status.account.username; let username = status.account.username;
let pid = status.id;
let msg = ''; let msg = '';
let self = this; let self = this;
switch(action) { switch(action) {
case 'addcw': case 'addcw':
msg = 'Are you sure you want to add a content warning to this post?'; msg = 'Are you sure you want to add a content warning to this post?';
swal({ swal({
title: 'Confirm', title: 'Confirm',
text: msg, text: msg,
icon: 'warning', icon: 'warning',
buttons: true, buttons: true,
dangerMode: true dangerMode: true
}).then(res => { }).then(res => {
if(res) { if(res) {
axios.post('/api/v2/moderator/action', { axios.post('/api/v2/moderator/action', {
action: action, action: action,
item_id: status.id, item_id: status.id,
item_type: 'status' item_type: 'status'
}).then(res => { }).then(res => {
swal('Success', 'Successfully added content warning', 'success'); swal('Success', 'Successfully added content warning', 'success');
status.sensitive = true; status.sensitive = true;
self.ctxModMenuClose(); self.ctxModMenuClose();
}).catch(err => { }).catch(err => {
swal( swal(
'Error', 'Error',
'Something went wrong, please try again later.', 'Something went wrong, please try again later.',
'error' 'error'
); );
self.ctxModMenuClose(); self.ctxModMenuClose();
}); });
} }
}); });
break; break;
case 'remcw': case 'remcw':
msg = 'Are you sure you want to remove the content warning on this post?'; msg = 'Are you sure you want to remove the content warning on this post?';
swal({ swal({
title: 'Confirm', title: 'Confirm',
text: msg, text: msg,
icon: 'warning', icon: 'warning',
buttons: true, buttons: true,
dangerMode: true dangerMode: true
}).then(res => { }).then(res => {
if(res) { if(res) {
axios.post('/api/v2/moderator/action', { axios.post('/api/v2/moderator/action', {
action: action, action: action,
item_id: status.id, item_id: status.id,
item_type: 'status' item_type: 'status'
}).then(res => { }).then(res => {
swal('Success', 'Successfully added content warning', 'success'); swal('Success', 'Successfully added content warning', 'success');
status.sensitive = false; status.sensitive = false;
self.ctxModMenuClose(); self.ctxModMenuClose();
}).catch(err => { }).catch(err => {
swal( swal(
'Error', 'Error',
'Something went wrong, please try again later.', 'Something went wrong, please try again later.',
'error' 'error'
); );
self.ctxModMenuClose(); self.ctxModMenuClose();
}); });
} }
}); });
break; break;
case 'unlist': case 'unlist':
msg = 'Are you sure you want to unlist this post?'; msg = 'Are you sure you want to unlist this post?';
swal({ swal({
title: 'Confirm', title: 'Confirm',
text: msg, text: msg,
icon: 'warning', icon: 'warning',
buttons: true, buttons: true,
dangerMode: true dangerMode: true
}).then(res => { }).then(res => {
if(res) { if(res) {
axios.post('/api/v2/moderator/action', { axios.post('/api/v2/moderator/action', {
action: action, action: action,
item_id: status.id, item_id: status.id,
item_type: 'status' item_type: 'status'
}).then(res => { }).then(res => {
this.feed = this.feed.filter(f => { this.feed = this.feed.filter(f => {
return f.id != status.id; 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 => { break;
self.ctxModMenuClose();
swal( case 'spammer':
'Error', 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.';
'Something went wrong, please try again later.', swal({
'error' 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; break;
} }
}, },