diff --git a/app/Http/Controllers/Api/ApiV1Dot1Controller.php b/app/Http/Controllers/Api/ApiV1Dot1Controller.php index cf90684ef..b7ff35a41 100644 --- a/app/Http/Controllers/Api/ApiV1Dot1Controller.php +++ b/app/Http/Controllers/Api/ApiV1Dot1Controller.php @@ -32,6 +32,7 @@ use App\Mail\PasswordChange; use App\Mail\ConfirmAppEmail; use App\Http\Resources\StatusStateless; use App\Jobs\StatusPipeline\StatusDelete; +use App\Jobs\ReportPipeline\ReportNotifyAdminViaEmail; class ApiV1Dot1Controller extends Controller { @@ -144,6 +145,10 @@ class ApiV1Dot1Controller extends Controller $report->type = $report_type; $report->save(); + if(config('instance.reports.email.enabled')) { + ReportNotifyAdminViaEmail::dispatch($report)->onQueue('default'); + } + $res = [ "msg" => "Successfully sent report", "code" => 200 @@ -399,10 +404,10 @@ class ApiV1Dot1Controller extends Controller abort_if(!$user, 403); abort_if($user->status != null, 403); - $res = $user->tokens->sortByDesc('created_at')->take(10)->map(function($token, $key) { + $res = $user->tokens->sortByDesc('created_at')->take(10)->map(function($token, $key) use($request) { return [ - 'id' => $key + 1, - 'did' => encrypt($token->id), + 'id' => $token->id, + 'current_session' => $request->user()->token()->id == $token->id, 'name' => $token->client->name, 'scopes' => $token->scopes, 'revoked' => $token->revoked, diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index 8791856ec..e9b7415c8 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -8,6 +8,7 @@ use App\Status; use App\User; use Auth; use Illuminate\Http\Request; +use App\Jobs\ReportPipeline\ReportNotifyAdminViaEmail; class ReportController extends Controller { @@ -165,6 +166,10 @@ class ReportController extends Controller $report->message = e($request->input('msg')); $report->save(); + if(config('instance.reports.email.enabled')) { + ReportNotifyAdminViaEmail::dispatch($report)->onQueue('default'); + } + if($request->wantsJson()) { return response()->json(200); } else { diff --git a/app/Jobs/ReportPipeline/AutospamNotifyAdminViaEmail.php b/app/Jobs/ReportPipeline/AutospamNotifyAdminViaEmail.php new file mode 100644 index 000000000..a793e9fea --- /dev/null +++ b/app/Jobs/ReportPipeline/AutospamNotifyAdminViaEmail.php @@ -0,0 +1,52 @@ +report = $report; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + $addresses = config('instance.reports.email.to'); + + if(config('instance.reports.email.enabled') == false || empty($addresses) || !config('instance.reports.email.autospam')) { + return; + } + + if(strpos($addresses, ',')) { + $to = explode(',', $addresses); + } else { + $to = $addresses; + } + + Mail::to($to)->send(new AdminNewAutospam($this->report)); + } +} diff --git a/app/Jobs/ReportPipeline/ReportNotifyAdminViaEmail.php b/app/Jobs/ReportPipeline/ReportNotifyAdminViaEmail.php new file mode 100644 index 000000000..96149ebfa --- /dev/null +++ b/app/Jobs/ReportPipeline/ReportNotifyAdminViaEmail.php @@ -0,0 +1,52 @@ +report = $report; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + $addresses = config('instance.reports.email.to'); + + if(config('instance.reports.email.enabled') == false || empty($addresses)) { + return; + } + + if(strpos($addresses, ',')) { + $to = explode(',', $addresses); + } else { + $to = $addresses; + } + + Mail::to($to)->send(new AdminNewReport($this->report)); + } +} diff --git a/app/Mail/AdminNewAutospam.php b/app/Mail/AdminNewAutospam.php new file mode 100644 index 000000000..c4f3246bf --- /dev/null +++ b/app/Mail/AdminNewAutospam.php @@ -0,0 +1,79 @@ +report = $report; + } + + /** + * Get the message envelope. + * + * @return \Illuminate\Mail\Mailables\Envelope + */ + public function envelope() + { + return new Envelope( + subject: '[' . config('pixelfed.domain.app') . '] Spam Post Detected', + ); + } + + /** + * Get the message content definition. + * + * @return \Illuminate\Mail\Mailables\Content + */ + public function content() + { + $data = $this->report->toArray(); + $reported_status = null; + $reported_account = null; + $url = url('/i/admin/reports/autospam/' . $this->report->id); + + if($data['item_type'] === 'App\Status') { + $reported_status = StatusService::get($this->report->item_id, false); + $reported_account = AccountService::get($reported_status['account']['id'], true); + } + + return new Content( + markdown: 'emails.admin.new_autospam', + with: [ + 'report' => $data, + 'url' => $url, + 'reported_status' => $reported_status, + 'reported_account' => $reported_account + ] + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments() + { + return []; + } +} diff --git a/app/Mail/AdminNewReport.php b/app/Mail/AdminNewReport.php new file mode 100644 index 000000000..f57c00d1f --- /dev/null +++ b/app/Mail/AdminNewReport.php @@ -0,0 +1,100 @@ +report = $report; + } + + /** + * Get the message envelope. + * + * @return \Illuminate\Mail\Mailables\Envelope + */ + public function envelope() + { + $type = $this->report->type; + $id = $this->report->id; + $object_type = last(explode("\\", $this->report->object_type)); + return new Envelope( + subject: '[' . config('pixelfed.domain.app') . '] ' . $object_type . ' Report (#' . $id . '-' . $type . ')', + ); + } + + /** + * Get the message content definition. + * + * @return \Illuminate\Mail\Mailables\Content + */ + public function content() + { + $report = $this->report; + $object_type = last(explode("\\", $this->report->object_type)); + $reporter = AccountService::get($report->profile_id, true); + $reported = AccountService::get($report->reported_profile_id, true); + $title = 'New ' . $object_type . ' Report (#' . $report->id . ')'; + $reportUrl = url('/i/admin/reports/show/' . $report->id . '?ref=email'); + $data = [ + 'report' => $report, + 'object_type' => $object_type, + 'title' => $title, + 'reporter' => $reporter, + 'reported' => $reported, + 'url' => $reportUrl, + 'message' => 'You have a new moderation report.' + ]; + + if($object_type === 'Status') { + $data['reported_status'] = StatusService::get($report['object_id'], false); + if($reporter && $reported) { + $data['message'] = '@' . + $reporter['acct'] . ' reported a post by @' . $reported['acct'] . ' as ' . $report->type . '.'; + } + } + + if($object_type === 'Profile') { + if($reporter && $reported) { + $data['message'] = '@' . + $reporter['acct'] . ' reported @' . $reported['acct'] . '\'s profile as ' . $report->type . '.'; + } + } + + return new Content( + markdown: 'emails.admin.new_report', + with: $data + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments() + { + return []; + } +} diff --git a/app/Util/Lexer/RestrictedNames.php b/app/Util/Lexer/RestrictedNames.php index d8a43b001..4224ae96c 100644 --- a/app/Util/Lexer/RestrictedNames.php +++ b/app/Util/Lexer/RestrictedNames.php @@ -139,6 +139,9 @@ class RestrictedNames 'css', 'd', 'dashboard', + 'delete', + 'deleted', + 'deleting', 'dmca', 'db', 'deck', diff --git a/app/Util/Sentiment/Bouncer.php b/app/Util/Sentiment/Bouncer.php index 948cef412..c0a151a9b 100644 --- a/app/Util/Sentiment/Bouncer.php +++ b/app/Util/Sentiment/Bouncer.php @@ -7,6 +7,7 @@ use App\Status; use Cache; use Illuminate\Support\Str; use App\Services\StatusService; +use App\Jobs\ReportPipeline\AutospamNotifyAdminViaEmail; class Bouncer { @@ -126,6 +127,10 @@ class Bouncer { ]); $ai->save(); + if(config('instance.reports.email.enabled') && config('instance.reports.email.autospam')) { + AutospamNotifyAdminViaEmail::dispatch($ai); + } + $u = $status->profile->user; $u->has_interstitial = true; $u->save(); diff --git a/config/instance.php b/config/instance.php index 3e550a809..327573c5d 100644 --- a/config/instance.php +++ b/config/instance.php @@ -111,5 +111,13 @@ return [ 'user_filters' => [ 'max_user_blocks' => env('PF_MAX_USER_BLOCKS', 50), 'max_user_mutes' => env('PF_MAX_USER_MUTES', 50) + ], + + 'reports' => [ + 'email' => [ + 'enabled' => env('INSTANCE_REPORTS_EMAIL_ENABLED', false), + 'to' => env('INSTANCE_REPORTS_EMAIL_ADDRESSES'), + 'autospam' => env('INSTANCE_REPORTS_EMAIL_AUTOSPAM', false) + ] ] ]; diff --git a/resources/views/emails/admin/new_autospam.blade.php b/resources/views/emails/admin/new_autospam.blade.php new file mode 100644 index 000000000..99c226ad6 --- /dev/null +++ b/resources/views/emails/admin/new_autospam.blade.php @@ -0,0 +1,87 @@ + +# Autospam Detection (#{{ $report['id'] }}) + +We have detected a potential spam post. The post has been unlisted from public feeds. +**Action is required to restore post visibility**.
+Please review this report and handle accordingly. + + +Review Autospam Report + + +@if($reported_status) + +

Reported Status

+
+@if( + isset($reported_status['media_attachments']) && + isset($reported_status['pf_type']) && + count($reported_status['media_attachments']) && + in_array($reported_status['pf_type'], ['photo', 'photo:album']) +) +Media preview +@endif +@if(isset($reported_status['content'])) +
{{ strip_tags(str_replace(["\n", "\r", "\r\n"], ' ', $reported_status['content'])) }}
+@endif +
+
+ + View status + +

+ Posted {{ now()->parse($reported_status['created_at'])->diffForHumans() }} +

+
+
+@endif + +@if($reported_account && isset($reported_account['id'])) + +

Reported Account

+ +
+ +Avatar +
+ +

+{{ $reported_account['username'] }} +

+ +

+{{ strip_tags(str_replace(["\n", "\r", "\r\n"], ' ', $reported_account['note'])) }} +

+ +
+

{{ $reported_account['statuses_count'] }} posts

+

·

+

{{ $reported_account['followers_count'] }} followers

+

·

+

{{ $reported_account['following_count'] }} following

+

·

+

Joined {{ now()->parse($reported_account['created_at'])->diffForHumans()}}

+
+ +
+
+
+@endif + +

+This is an automated email that is intended for administrators of {{ config('pixelfed.domain.app')}}.
+If you received this email by mistake, kindly disregard and delete this email. +

+
diff --git a/resources/views/emails/admin/new_report.blade.php b/resources/views/emails/admin/new_report.blade.php new file mode 100644 index 000000000..3d4d0da5c --- /dev/null +++ b/resources/views/emails/admin/new_report.blade.php @@ -0,0 +1,122 @@ + +# {{ $title }} + +## {!! $message !!} + + +View Report + + +@if($object_type === 'Status' && $reported_status) + +

Reported Status

+
+@if( + isset($reported_status['media_attachments']) && + isset($reported_status['pf_type']) && + count($reported_status['media_attachments']) && + in_array($reported_status['pf_type'], ['photo', 'photo:album']) +) +Media preview +@endif +@if(isset($reported_status['content'])) +
{{ strip_tags(str_replace(["\n", "\r", "\r\n"], ' ', $reported_status['content'])) }}
+@endif +
+
+ + View status + +

+ Posted {{ now()->parse($reported_status['created_at'])->diffForHumans() }} +

+
+
+@endif + +@if($reported && isset($reported['id'])) + +

Reported Account

+ +
+ +Avatar +
+ +

+{{ $reported['username'] }} +

+ +

+{{ strip_tags(str_replace(["\n", "\r", "\r\n"], ' ', $reported['note'])) }} +

+ +
+

{{ $reported['statuses_count'] }} posts

+

·

+

{{ $reported['followers_count'] }} followers

+

·

+

{{ $reported['following_count'] }} following

+

·

+

Joined {{ now()->parse($reported['created_at'])->diffForHumans()}}

+
+ +
+
+
+@endif + +@if($reporter && isset($reporter['id'])) + +

Reported By

+ +
+ +Avatar +
+ +

{{ $reporter['username'] }} +

+ +

+{{ strip_tags(str_replace(["\n", "\r", "\r\n"], ' ', $reporter['note'])) }} +

+ +
+

{{ $reporter['statuses_count'] }} posts

+

·

+

{{ $reporter['followers_count'] }} followers

+

·

+

{{ $reporter['following_count'] }} following

+

·

+

Joined {{ now()->parse($reporter['created_at'])->diffForHumans()}}

+
+
+
+
+@endif + +

+This is an automated email that is intended for administrators of {{ config('pixelfed.domain.app')}}.
+If you received this email by mistake, kindly disregard and delete this email. +

+ +