소스 검색

Add admin report email notifications

Daniel Supernault 2 년 전
부모
커밋
4e1d0ed596

+ 8 - 3
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,

+ 5 - 0
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 {

+ 52 - 0
app/Jobs/ReportPipeline/AutospamNotifyAdminViaEmail.php

@@ -0,0 +1,52 @@
+<?php
+
+namespace App\Jobs\ReportPipeline;
+
+use Illuminate\Bus\Queueable;
+use Illuminate\Contracts\Queue\ShouldBeUnique;
+use Illuminate\Contracts\Queue\ShouldQueue;
+use Illuminate\Foundation\Bus\Dispatchable;
+use Illuminate\Queue\InteractsWithQueue;
+use Illuminate\Queue\SerializesModels;
+use App\Mail\AdminNewAutospam;
+use Illuminate\Support\Facades\Mail;
+use Illuminate\Support\Str;
+
+class AutospamNotifyAdminViaEmail implements ShouldQueue
+{
+    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
+
+    public $report;
+
+    /**
+     * Create a new job instance.
+     *
+     * @return void
+     */
+    public function __construct($report)
+    {
+        $this->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));
+    }
+}

+ 52 - 0
app/Jobs/ReportPipeline/ReportNotifyAdminViaEmail.php

@@ -0,0 +1,52 @@
+<?php
+
+namespace App\Jobs\ReportPipeline;
+
+use Illuminate\Bus\Queueable;
+use Illuminate\Contracts\Queue\ShouldBeUnique;
+use Illuminate\Contracts\Queue\ShouldQueue;
+use Illuminate\Foundation\Bus\Dispatchable;
+use Illuminate\Queue\InteractsWithQueue;
+use Illuminate\Queue\SerializesModels;
+use App\Mail\AdminNewReport;
+use Illuminate\Support\Facades\Mail;
+use Illuminate\Support\Str;
+
+class ReportNotifyAdminViaEmail implements ShouldQueue
+{
+    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
+
+    public $report;
+
+    /**
+     * Create a new job instance.
+     *
+     * @return void
+     */
+    public function __construct($report)
+    {
+        $this->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));
+    }
+}

+ 79 - 0
app/Mail/AdminNewAutospam.php

@@ -0,0 +1,79 @@
+<?php
+
+namespace App\Mail;
+
+use Illuminate\Bus\Queueable;
+use Illuminate\Contracts\Queue\ShouldQueue;
+use Illuminate\Mail\Mailable;
+use Illuminate\Mail\Mailables\Content;
+use Illuminate\Mail\Mailables\Envelope;
+use Illuminate\Queue\SerializesModels;
+use App\Services\AccountService;
+use App\Services\StatusService;
+
+class AdminNewAutospam extends Mailable
+{
+    use Queueable, SerializesModels;
+
+    public $report;
+
+    /**
+     * Create a new message instance.
+     *
+     * @return void
+     */
+    public function __construct($report)
+    {
+        $this->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 [];
+    }
+}

+ 100 - 0
app/Mail/AdminNewReport.php

@@ -0,0 +1,100 @@
+<?php
+
+namespace App\Mail;
+
+use Illuminate\Bus\Queueable;
+use Illuminate\Contracts\Queue\ShouldQueue;
+use Illuminate\Mail\Mailable;
+use Illuminate\Mail\Mailables\Content;
+use Illuminate\Mail\Mailables\Envelope;
+use Illuminate\Queue\SerializesModels;
+use App\Services\AccountService;
+use App\Services\StatusService;
+
+class AdminNewReport extends Mailable
+{
+    use Queueable, SerializesModels;
+
+    public $report;
+
+    /**
+     * Create a new message instance.
+     *
+     * @return void
+     */
+    public function __construct($report)
+    {
+        $this->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'] = '<a href="' .  url('/i/web/profile/' . $reporter['id']) . '">@' .
+	    			$reporter['acct'] . '</a> reported a post by <a href="' . url('/i/web/profile/' . $reported['id']) .
+	    			'">@' . $reported['acct'] . '</a> as ' . $report->type . '.';
+    		}
+    	}
+
+    	if($object_type === 'Profile') {
+    		if($reporter && $reported) {
+    		$data['message'] = '<a href="' .  url('/i/web/profile/' . $reporter['id']) . '">@' .
+    			$reporter['acct'] . '</a> reported <a href="' . url('/i/web/profile/' . $reported['id']) .
+    			'">@' . $reported['acct'] . '</a>\'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 [];
+    }
+}

+ 3 - 0
app/Util/Lexer/RestrictedNames.php

@@ -139,6 +139,9 @@ class RestrictedNames
 		'css',
 		'd',
 		'dashboard',
+		'delete',
+		'deleted',
+		'deleting',
 		'dmca',
 		'db',
 		'deck',

+ 5 - 0
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();

+ 8 - 0
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)
+		]
 	]
 ];

+ 87 - 0
resources/views/emails/admin/new_autospam.blade.php

@@ -0,0 +1,87 @@
+<x-mail::message>
+# 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**. <br />
+Please review this report and handle accordingly.
+
+<x-mail::button :url="$url">
+Review Autospam Report
+</x-mail::button>
+
+@if($reported_status)
+<x-mail::panel>
+<p style="font-size: 13px; color: #cccccc; text-align: center; font-weight: bold;margin-bottom: 10px;">Reported Status</p>
+<div style="display: flex; align-items: center;gap: 10px;">
+@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'])
+)
+<img
+	src="{{$reported_status['media_attachments'][0]['url']}}"
+	width="100"
+	height="100"
+	alt="Media preview"
+	style="object-fit: cover; border: 1px solid #cccccc; border-radius: 10px; margin-bottom: 10px;"
+	onerror="this.src='{{url('/storage/no-preview.png')}}';this.onerror=null;" />
+@endif
+@if(isset($reported_status['content']))
+<div style="font-size: 12px !important;">{{ strip_tags(str_replace(["\n", "\r", "\r\n"], ' ', $reported_status['content'])) }}</div>
+@endif
+</div>
+<div style="display: flex; align-items: center; justify-content: space-between;margin-top:10px;">
+<a style="font-size: 11px !important;font-weight: bold;text-decoration: none;" href="{{ url('/i/web/post/' . $reported_status['id'])}}">
+	View status
+</a>
+<p style="font-size: 11px !important;font-weight: bold;">
+	Posted {{ now()->parse($reported_status['created_at'])->diffForHumans() }}
+</p>
+</div>
+</x-mail::panel>
+@endif
+
+@if($reported_account && isset($reported_account['id']))
+<x-mail::panel>
+<p style="font-size: 13px; color: #cccccc; text-align: center; font-weight: bold;margin-bottom: 10px;">Reported Account</p>
+
+<div style="display: flex; align-items: flex-start;gap: 10px;">
+
+<img
+	src="{{$reported_account['avatar']}}"
+	width="50"
+	height="50"
+	alt="Avatar"
+	style="border-radius: 10px;min-width: 50px;flex-grow: 1;"
+	onerror="this.src='{{url('/storage/avatars/default.jpg')}}';this.onerror=null;" />
+<div>
+
+<p style="margin-bottom: 0;">
+<a href="{{ url('/i/web/profile/' . $reported_account['id']) }}" style="text-decoration: none;font-weight: bold">{{ $reported_account['username'] }}</a>
+</p>
+
+<p style="margin-bottom: 5px;font-size: 10px;opacity: 0.5;">
+{{ strip_tags(str_replace(["\n", "\r", "\r\n"], ' ', $reported_account['note'])) }}
+</p>
+
+<div style="display: flex; align-items: center; gap: 5px;">
+<p style="font-size: 10px;margin-bottom: 0;">{{ $reported_account['statuses_count'] }} posts</p>
+<p style="font-size: 10px;margin-bottom: 0;">·</p>
+<p style="font-size: 10px;margin-bottom: 0;">{{ $reported_account['followers_count'] }} followers</p>
+<p style="font-size: 10px;margin-bottom: 0;">·</p>
+<p style="font-size: 10px;margin-bottom: 0;">{{ $reported_account['following_count'] }} following</p>
+<p style="font-size: 10px;margin-bottom: 0;">·</p>
+<p style="font-size: 10px;margin-bottom: 0;">Joined {{ now()->parse($reported_account['created_at'])->diffForHumans()}}</p>
+</div>
+
+</div>
+</div>
+</x-mail::panel>
+@endif
+
+<p style="font-size: 12px;color: #cccccc;text-align: center;">
+This is an automated email that is intended for administrators of {{ config('pixelfed.domain.app')}}.<br />
+If you received this email by mistake, kindly disregard and delete this email.
+</p>
+</x-mail::message>

+ 122 - 0
resources/views/emails/admin/new_report.blade.php

@@ -0,0 +1,122 @@
+<x-mail::message>
+# {{ $title }}
+
+## {!! $message !!}
+
+<x-mail::button :url="$url">
+View Report
+</x-mail::button>
+
+@if($object_type === 'Status' && $reported_status)
+<x-mail::panel>
+<p style="font-size: 13px; color: #cccccc; text-align: center; font-weight: bold;margin-bottom: 10px;">Reported Status</p>
+<div style="display: flex; align-items: center;gap: 10px;">
+@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'])
+)
+<img
+	src="{{$reported_status['media_attachments'][0]['url']}}"
+	width="100"
+	height="100"
+	alt="Media preview"
+	style="object-fit: cover; border: 1px solid #cccccc; border-radius: 10px; margin-bottom: 10px;"
+	onerror="this.src='{{url('/storage/no-preview.png')}}';this.onerror=null;" />
+@endif
+@if(isset($reported_status['content']))
+<div style="font-size: 12px !important;">{{ strip_tags(str_replace(["\n", "\r", "\r\n"], ' ', $reported_status['content'])) }}</div>
+@endif
+</div>
+<div style="display: flex; align-items: center; justify-content: space-between;margin-top:10px;">
+<a style="font-size: 11px !important;font-weight: bold;text-decoration: none;" href="{{ url('/i/web/post/' . $reported_status['id'])}}">
+	View status
+</a>
+<p style="font-size: 11px !important;font-weight: bold;">
+	Posted {{ now()->parse($reported_status['created_at'])->diffForHumans() }}
+</p>
+</div>
+</x-mail::panel>
+@endif
+
+@if($reported && isset($reported['id']))
+<x-mail::panel>
+<p style="font-size: 13px; color: #cccccc; text-align: center; font-weight: bold;margin-bottom: 10px;">Reported Account</p>
+
+<div style="display: flex; align-items: flex-start;gap: 10px;">
+
+<img
+	src="{{$reported['avatar']}}"
+	width="50"
+	height="50"
+	alt="Avatar"
+	style="border-radius: 10px;min-width: 50px;flex-grow: 1;"
+	onerror="this.src='{{url('/storage/avatars/default.jpg')}}';this.onerror=null;" />
+<div>
+
+<p style="margin-bottom: 0;">
+<a href="{{ url('/i/web/profile/' . $reported['id']) }}" style="text-decoration: none;font-weight: bold">{{ $reported['username'] }}</a>
+</p>
+
+<p style="margin-bottom: 5px;font-size: 10px;opacity: 0.5;">
+{{ strip_tags(str_replace(["\n", "\r", "\r\n"], ' ', $reported['note'])) }}
+</p>
+
+<div style="display: flex; align-items: center; gap: 5px;">
+<p style="font-size: 10px;margin-bottom: 0;">{{ $reported['statuses_count'] }} posts</p>
+<p style="font-size: 10px;margin-bottom: 0;">·</p>
+<p style="font-size: 10px;margin-bottom: 0;">{{ $reported['followers_count'] }} followers</p>
+<p style="font-size: 10px;margin-bottom: 0;">·</p>
+<p style="font-size: 10px;margin-bottom: 0;">{{ $reported['following_count'] }} following</p>
+<p style="font-size: 10px;margin-bottom: 0;">·</p>
+<p style="font-size: 10px;margin-bottom: 0;">Joined {{ now()->parse($reported['created_at'])->diffForHumans()}}</p>
+</div>
+
+</div>
+</div>
+</x-mail::panel>
+@endif
+
+@if($reporter && isset($reporter['id']))
+<x-mail::panel>
+<p style="font-size: 13px; color: #cccccc; text-align: center; font-weight: bold;margin-bottom: 10px;">Reported By</p>
+
+<div style="display: flex; align-items: flex-start;gap: 10px;">
+
+<img
+	src="{{$reporter['avatar']}}"
+	width="50"
+	height="50"
+	alt="Avatar"
+	style="border-radius: 10px;min-width: 50px;flex-grow: 1;"
+	onerror="this.src='{{url('/storage/avatars/default.jpg')}}';this.onerror=null;" />
+<div>
+
+<p style="margin-bottom: 0;"><a href="{{ url('/i/web/profile/' . $reporter['id']) }}" style="text-decoration: none;font-weight: bold">{{ $reporter['username'] }}</a>
+</p>
+
+<p style="margin-bottom: 5px;font-size: 10px;opacity: 0.5;">
+{{ strip_tags(str_replace(["\n", "\r", "\r\n"], ' ', $reporter['note'])) }}
+</p>
+
+<div style="display: flex; align-items: center; gap: 5px;">
+<p style="font-size: 10px;margin-bottom: 0;">{{ $reporter['statuses_count'] }} posts</p>
+<p style="font-size: 10px;margin-bottom: 0;">·</p>
+<p style="font-size: 10px;margin-bottom: 0;">{{ $reporter['followers_count'] }} followers</p>
+<p style="font-size: 10px;margin-bottom: 0;">·</p>
+<p style="font-size: 10px;margin-bottom: 0;">{{ $reporter['following_count'] }} following</p>
+<p style="font-size: 10px;margin-bottom: 0;">·</p>
+<p style="font-size: 10px;margin-bottom: 0;">Joined {{ now()->parse($reporter['created_at'])->diffForHumans()}}</p>
+</div>
+</div>
+</div>
+</x-mail::panel>
+@endif
+
+<p style="font-size: 12px;color: #cccccc;text-align: center;">
+This is an automated email that is intended for administrators of {{ config('pixelfed.domain.app')}}.<br />
+If you received this email by mistake, kindly disregard and delete this email.
+</p>
+
+</x-mail::message>