diff --git a/app/Http/Controllers/Admin/AdminReportController.php b/app/Http/Controllers/Admin/AdminReportController.php index 2ad2bab8a..8695a5003 100644 --- a/app/Http/Controllers/Admin/AdminReportController.php +++ b/app/Http/Controllers/Admin/AdminReportController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\Admin; use App\AccountInterstitial; use App\Http\Resources\AdminReport; +use App\Http\Resources\AdminRemoteReport; use App\Http\Resources\AdminSpamReport; use App\Jobs\DeletePipeline\DeleteAccountPipeline; use App\Jobs\DeletePipeline\DeleteRemoteProfilePipeline; @@ -13,6 +14,7 @@ use App\Jobs\StoryPipeline\StoryDelete; use App\Notification; use App\Profile; use App\Report; +use App\Models\RemoteReport; use App\Services\AccountService; use App\Services\ModLogService; use App\Services\NetworkTimelineService; @@ -23,6 +25,7 @@ use App\Status; use App\Story; use App\User; use Cache; +use Storage; use Carbon\Carbon; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; @@ -640,6 +643,7 @@ trait AdminReportController 'autospam' => AccountInterstitial::whereType('post.autospam')->count(), 'autospam_open' => AccountInterstitial::whereType('post.autospam')->whereNull(['appeal_handled_at'])->count(), 'appeals' => AccountInterstitial::whereNotNull('appeal_requested_at')->whereNull('appeal_handled_at')->count(), + 'remote_open' => RemoteReport::whereNull('action_taken_at')->count(), 'email_verification_requests' => Redis::scard('email:manual'), ]; @@ -665,6 +669,24 @@ trait AdminReportController return $reports; } + public function reportsApiRemote(Request $request) + { + $filter = $request->input('filter') == 'closed' ? 'closed' : 'open'; + + $reports = AdminRemoteReport::collection( + RemoteReport::orderBy('id', 'desc') + ->when($filter, function ($q, $filter) { + return $filter == 'open' ? + $q->whereNull('action_taken_at') : + $q->whereNotNull('action_taken_at'); + }) + ->cursorPaginate(6) + ->withQueryString() + ); + + return $reports; + } + public function reportsApiGet(Request $request, $id) { $report = Report::findOrFail($id); @@ -1327,4 +1349,173 @@ trait AdminReportController return new AdminSpamReport($report); } + + public function reportsApiRemoteHandle(Request $request) + { + $this->validate($request, [ + 'id' => 'required|exists:remote_reports,id', + 'action' => 'required|in:mark-read,cw-posts,unlist-posts,delete-posts,private-posts,mark-all-read-by-domain,mark-all-read-by-username,cw-all-posts,private-all-posts,unlist-all-posts' + ]); + + $report = RemoteReport::findOrFail($request->input('id')); + $user = User::whereProfileId($report->account_id)->first(); + $ogPublicStatuses = []; + $ogUnlistedStatuses = []; + $ogNonCwStatuses = []; + + switch ($request->input('action')) { + case 'mark-read': + $report->action_taken_at = now(); + $report->save(); + break; + case 'mark-all-read-by-domain': + RemoteReport::whereInstanceId($report->instance_id)->update(['action_taken_at' => now()]); + break; + case 'cw-posts': + $statuses = Status::find($report->status_ids); + foreach($statuses as $status) { + if($report->account_id != $status->profile_id) { + continue; + } + if(!$status->is_nsfw) { + $ogNonCwStatuses[] = $status->id; + } + $status->is_nsfw = true; + $status->saveQuietly(); + StatusService::del($status->id); + } + $report->action_taken_at = now(); + $report->save(); + break; + case 'cw-all-posts': + foreach(Status::whereProfileId($report->account_id)->lazyById(50, 'id') as $status) { + if($status->is_nsfw || $status->reblog_of_id) { + continue; + } + if(!$status->is_nsfw) { + $ogNonCwStatuses[] = $status->id; + } + $status->is_nsfw = true; + $status->saveQuietly(); + StatusService::del($status->id); + } + break; + case 'unlist-posts': + $statuses = Status::find($report->status_ids); + foreach($statuses as $status) { + if($report->account_id != $status->profile_id) { + continue; + } + if($status->scope === 'public') { + $ogPublicStatuses[] = $status->id; + $status->scope = 'unlisted'; + $status->visibility = 'unlisted'; + $status->saveQuietly(); + StatusService::del($status->id); + } + } + $report->action_taken_at = now(); + $report->save(); + break; + case 'unlist-all-posts': + foreach(Status::whereProfileId($report->account_id)->lazyById(50, 'id') as $status) { + if($status->visibility !== 'public' || $status->reblog_of_id) { + continue; + } + $ogPublicStatuses[] = $status->id; + $status->visibility = 'unlisted'; + $status->scope = 'unlisted'; + $status->saveQuietly(); + StatusService::del($status->id); + } + break; + case 'private-posts': + $statuses = Status::find($report->status_ids); + foreach($statuses as $status) { + if($report->account_id != $status->profile_id) { + continue; + } + if(in_array($status->scope, ['public', 'unlisted', 'private'])) { + if($status->scope === 'public') { + $ogPublicStatuses[] = $status->id; + } + $status->scope = 'private'; + $status->visibility = 'private'; + $status->saveQuietly(); + StatusService::del($status->id); + } + } + $report->action_taken_at = now(); + $report->save(); + break; + case 'private-all-posts': + foreach(Status::whereProfileId($report->account_id)->lazyById(50, 'id') as $status) { + if(!in_array($status->visibility, ['public', 'unlisted']) || $status->reblog_of_id) { + continue; + } + if($status->visibility === 'public') { + $ogPublicStatuses[] = $status->id; + } else if($status->visibility === 'unlisted') { + $ogUnlistedStatuses[] = $status->id; + } + $status->visibility = 'private'; + $status->scope = 'private'; + $status->saveQuietly(); + StatusService::del($status->id); + } + break; + case 'delete-posts': + $statuses = Status::find($report->status_ids); + foreach($statuses as $status) { + if($report->account_id != $status->profile_id) { + continue; + } + StatusDelete::dispatch($status); + } + $report->action_taken_at = now(); + $report->save(); + break; + case 'mark-all-read-by-username': + RemoteReport::whereNull('action_taken_at')->whereAccountId($report->account_id)->update(['action_taken_at' => now()]); + break; + + default: + abort(404); + break; + } + + if($ogPublicStatuses && count($ogPublicStatuses)) { + Storage::disk('local')->put('mod-log-cache/' . $report->account_id . '/' . now()->format('Y-m-d') . '-og-public-statuses.json', json_encode($ogPublicStatuses, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES)); + } + + if($ogNonCwStatuses && count($ogNonCwStatuses)) { + Storage::disk('local')->put('mod-log-cache/' . $report->account_id . '/' . now()->format('Y-m-d') . '-og-noncw-statuses.json', json_encode($ogNonCwStatuses, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES)); + } + + if($ogUnlistedStatuses && count($ogUnlistedStatuses)) { + Storage::disk('local')->put('mod-log-cache/' . $report->account_id . '/' . now()->format('Y-m-d') . '-og-unlisted-statuses.json', json_encode($ogUnlistedStatuses, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES)); + } + + ModLogService::boot() + ->user(request()->user()) + ->objectUid($user ? $user->id : null) + ->objectId($report->id) + ->objectType('App\Report::class') + ->action('admin.report.moderate') + ->metadata([ + 'action' => $request->input('action'), + 'duration_active' => now()->parse($report->created_at)->diffForHumans() + ]) + ->accessLevel('admin') + ->save(); + + if($report->status_ids) { + foreach($report->status_ids as $sid) { + RemoteReport::whereNull('action_taken_at') + ->whereJsonContains('status_ids', [$sid]) + ->update(['action_taken_at' => now()]); + } + } + return [200]; + } } diff --git a/app/Http/Resources/AdminRemoteReport.php b/app/Http/Resources/AdminRemoteReport.php new file mode 100644 index 000000000..a726e25cc --- /dev/null +++ b/app/Http/Resources/AdminRemoteReport.php @@ -0,0 +1,49 @@ + + */ + public function toArray(Request $request): array + { + $instance = parse_url($this->uri, PHP_URL_HOST); + $statuses = []; + if($this->status_ids && count($this->status_ids)) { + foreach($this->status_ids as $sid) { + $s = StatusService::get($sid, false); + if($s && $s['in_reply_to_id'] != null) { + $parent = StatusService::get($s['in_reply_to_id'], false); + if($parent) { + $s['parent'] = $parent; + } + } + if($s) { + $statuses[] = $s; + } + } + } + $res = [ + 'id' => $this->id, + 'instance' => $instance, + 'reported' => AccountService::get($this->account_id, true), + 'status_ids' => $this->status_ids, + 'statuses' => $statuses, + 'message' => $this->comment, + 'report_meta' => $this->report_meta, + 'created_at' => optional($this->created_at)->format('c'), + 'action_taken_at' => optional($this->action_taken_at)->format('c'), + ]; + return $res; + } +} diff --git a/app/Util/ActivityPub/Inbox.php b/app/Util/ActivityPub/Inbox.php index 0ef7d6a7c..ed6b964e8 100644 --- a/app/Util/ActivityPub/Inbox.php +++ b/app/Util/ActivityPub/Inbox.php @@ -1243,7 +1243,14 @@ class Inbox return; } - $content = isset($this->payload['content']) ? Purify::clean($this->payload['content']) : null; + $content = null; + if(isset($this->payload['content'])) { + if(strlen($this->payload['content']) > 5000) { + $content = Purify::clean(substr($this->payload['content'], 0, 5000) . ' ... (truncated message due to exceeding max length)'); + } else { + $content = Purify::clean($this->payload['content']); + } + } $object = $this->payload['object']; if(empty($object) || (!is_array($object) && !is_string($object))) { @@ -1259,7 +1266,7 @@ class Inbox foreach($object as $objectUrl) { if(!Helpers::validateLocalUrl($objectUrl)) { - continue; + return; } if(str_contains($objectUrl, '/users/')) { @@ -1280,6 +1287,23 @@ class Inbox return; } + if($objects->count()) { + $obc = $objects->count(); + if($obc > 25) { + if($obc > 30) { + return; + } else { + $objLimit = $objects->take(20); + $objects = collect($objLimit->all()); + $obc = $objects->count(); + } + } + $count = Status::whereProfileId($accountId)->find($objects)->count(); + if($obc !== $count) { + return; + } + } + $instanceHost = parse_url($id, PHP_URL_HOST); $instance = Instance::updateOrCreate([ diff --git a/resources/assets/components/admin/AdminReports.vue b/resources/assets/components/admin/AdminReports.vue index 7bfcd10a9..e43f8af62 100644 --- a/resources/assets/components/admin/AdminReports.vue +++ b/resources/assets/components/admin/AdminReports.vue @@ -103,6 +103,21 @@ +