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 559 additions and 454 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) public function discoverCategories(Request $request)
{ {
$categories = DiscoverCategory::whereActive(true)->orderBy('order')->take(10)->get(); $categories = DiscoverCategory::whereActive(true)->orderBy('order')->take(10)->get();
$res = $categories->map(function($item) { $res = $categories->map(function($item) {
return [ return [
'name' => $item->name, 'name' => $item->name,
'url' => $item->url(), 'url' => $item->url(),
'thumb' => $item->thumb() 'thumb' => $item->thumb()
]; ];
}); });
return response()->json($res); return response()->json($res);
} }
public function modAction(Request $request) public function modAction(Request $request)
{ {
abort_unless(Auth::user()->is_admin, 400); abort_unless(Auth::user()->is_admin, 400);
$this->validate($request, [ $this->validate($request, [
'action' => [ 'action' => [
'required', 'required',
'string', 'string',
Rule::in([ Rule::in([
'addcw', 'addcw',
'remcw', 'remcw',
'unlist' 'unlist',
'spammer'
])
],
'item_id' => 'required|integer|min:1',
'item_type' => [
'required',
'string',
Rule::in(['profile', 'status'])
]
]);
]) $action = $request->input('action');
], $item_id = $request->input('item_id');
'item_id' => 'required|integer|min:1', $item_type = $request->input('item_type');
'item_type' => [
'required',
'string',
Rule::in(['profile', 'status'])
]
]);
$action = $request->input('action'); switch($action) {
$item_id = $request->input('item_id'); case 'addcw':
$item_type = $request->input('item_type'); $status = Status::findOrFail($item_id);
$status->is_nsfw = true;
switch($action) { $status->save();
case 'addcw': ModLogService::boot()
$status = Status::findOrFail($item_id); ->user(Auth::user())
$status->is_nsfw = true; ->objectUid($status->profile->user_id)
$status->save(); ->objectId($status->id)
ModLogService::boot() ->objectType('App\Status::class')
->user(Auth::user()) ->action('admin.status.moderate')
->objectUid($status->profile->user_id) ->metadata([
->objectId($status->id) 'action' => 'cw',
->objectType('App\Status::class') 'message' => 'Success!'
->action('admin.status.moderate') ])
->metadata([ ->accessLevel('admin')
'action' => 'cw', ->save();
'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) { $limit = $request->limit ?? 9;
if(!Auth::check()) { $max_id = $request->max_id;
return response()->json([]); $min_id = $request->min_id;
} $scope = $request->only_media == true ?
$pid = Auth::user()->profile->id; ['photo', 'photo:album', 'video', 'video:album'] :
$following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) { ['photo', 'photo:album', 'video', 'video:album', 'share', 'reply'];
$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 ? '>' : '<'; if($profile->is_private) {
$id = $min_id ?? $max_id; if(!Auth::check()) {
$timeline = Status::select( return response()->json([]);
'id', }
'uri', $pid = Auth::user()->profile->id;
'caption', $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) {
'rendered', $following = Follower::whereProfileId($pid)->pluck('following_id');
'profile_id', return $following->push($pid)->toArray();
'type', });
'in_reply_to_id', $visibility = true == in_array($profile->id, $following) ? ['public', 'unlisted', 'private'] : [];
'reblog_of_id', } else {
'is_nsfw', if(Auth::check()) {
'likes_count', $pid = Auth::user()->profile->id;
'reblogs_count', $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) {
'scope', $following = Follower::whereProfileId($pid)->pluck('following_id');
'local', return $following->push($pid)->toArray();
'created_at', });
'updated_at' $visibility = true == in_array($profile->id, $following) ? ['public', 'unlisted', 'private'] : ['public', 'unlisted'];
)->whereProfileId($profile->id) } else {
->whereIn('type', $scope) $visibility = ['public', 'unlisted'];
->where('id', $dir, $id) }
->whereIn('visibility', $visibility) }
->latest()
->limit($limit)
->get();
$resource = new Fractal\Resource\Collection($timeline, new StatusTransformer()); $dir = $min_id ? '>' : '<';
$res = $this->fractal->createData($resource)->toArray(); $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) return response()->json($res);
{ }
$profile = Profile::whereNull('status')
->whereNotNull('domain')
->findOrFail($id);
$user = Auth::user();
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) return view('profile.remote', compact('profile', 'user'));
{ }
$user = Profile::whereNull('status')
->whereNotNull('domain')
->findOrFail($profileId);
$status = Status::whereProfileId($user->id) public function remoteStatus(Request $request, $profileId, $statusId)
->whereNull('reblog_of_id') {
->whereIn('visibility', ['public', 'unlisted']) $user = Profile::whereNull('status')
->findOrFail($statusId); ->whereNotNull('domain')
$template = $status->in_reply_to_id ? 'status.reply' : 'status.remote'; ->findOrFail($profileId);
return view($template, compact('user', 'status'));
} $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;
}
}

BIN
public/js/timeline.js vendored

Binary file not shown.

Binary file not shown.

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;
} }
}, },