Add admin report email notifications

This commit is contained in:
Daniel Supernault 2023-03-13 05:55:56 -06:00
parent 05ee7f2cff
commit 4e1d0ed596
No known key found for this signature in database
GPG key ID: 0DEF1C662C9033F7
11 changed files with 521 additions and 3 deletions

View file

@ -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,

View file

@ -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 {

View file

@ -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));
}
}

View file

@ -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));
}
}

View file

@ -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
app/Mail/AdminNewReport.php Normal file
View file

@ -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 [];
}
}

View file

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

View file

@ -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();

View file

@ -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)
]
]
];

View file

@ -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>

View file

@ -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>