Ver código fonte

Add AdminShadowFilter feature

Daniel Supernault 1 ano atrás
pai
commit
33ed7a8c91

+ 122 - 0
app/Http/Controllers/AdminShadowFilterController.php

@@ -0,0 +1,122 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use Illuminate\Http\Request;
+use App\Models\AdminShadowFilter;
+use App\Profile;
+use App\Services\AccountService;
+use App\Services\AdminShadowFilterService;
+
+class AdminShadowFilterController extends Controller
+{
+    public function __construct()
+    {
+        $this->middleware(['auth','admin']);
+    }
+
+    public function home(Request $request)
+    {
+        $filter = $request->input('filter');
+        $searchQuery = $request->input('q');
+        $filters = AdminShadowFilter::when($filter, function($q, $filter) {
+            if($filter == 'all') {
+                return $q;
+            } else if($filter == 'inactive') {
+                return $q->whereActive(false);
+            } else {
+                return $q;
+            }
+        }, function($q, $filter) {
+            return $q->whereActive(true);
+        })
+        ->when($searchQuery, function($q, $searchQuery) {
+            $ids = Profile::where('username', 'like', '%' . $searchQuery . '%')
+                ->limit(100)
+                ->pluck('id')
+                ->toArray();
+            return $q->where('item_type', 'App\Profile')->whereIn('item_id', $ids);
+        })
+        ->latest()
+        ->paginate(10)
+        ->withQueryString();
+
+        return view('admin.asf.home', compact('filters'));
+    }
+
+    public function create(Request $request)
+    {
+        return view('admin.asf.create');
+    }
+
+    public function edit(Request $request, $id)
+    {
+        $filter = AdminShadowFilter::findOrFail($id);
+        $profile = AccountService::get($filter->item_id);
+        return view('admin.asf.edit', compact('filter', 'profile'));
+    }
+
+    public function store(Request $request)
+    {
+        $this->validate($request, [
+            'username' => 'required',
+            'active' => 'sometimes',
+            'note' => 'sometimes',
+            'hide_from_public_feeds' => 'sometimes'
+        ]);
+
+        $profile = Profile::whereUsername($request->input('username'))->first();
+
+        if(!$profile) {
+            return back()->withErrors(['Invalid account']);
+        }
+
+        if($profile->user && $profile->user->is_admin) {
+            return back()->withErrors(['Cannot filter an admin account']);
+        }
+
+        $active = $request->has('active') && $request->has('hide_from_public_feeds');
+
+        AdminShadowFilter::updateOrCreate([
+            'item_id' => $profile->id,
+            'item_type' => get_class($profile)
+        ], [
+            'is_local' => $profile->domain === null,
+            'note' => $request->input('note'),
+            'hide_from_public_feeds' => $request->has('hide_from_public_feeds'),
+            'admin_id' => $request->user()->profile_id,
+            'active' => $active
+        ]);
+
+        AdminShadowFilterService::refresh();
+
+        return redirect('/i/admin/asf/home');
+    }
+
+    public function storeEdit(Request $request, $id)
+    {
+        $this->validate($request, [
+            'active' => 'sometimes',
+            'note' => 'sometimes',
+            'hide_from_public_feeds' => 'sometimes'
+        ]);
+
+        $filter = AdminShadowFilter::findOrFail($id);
+
+        $profile = Profile::findOrFail($filter->item_id);
+
+        if($profile->user && $profile->user->is_admin) {
+            return back()->withErrors(['Cannot filter an admin account']);
+        }
+
+        $active = $request->has('active');
+        $filter->active = $active;
+        $filter->hide_from_public_feeds = $request->has('hide_from_public_feeds');
+        $filter->note = $request->input('note');
+        $filter->save();
+
+        AdminShadowFilterService::refresh();
+
+        return redirect('/i/admin/asf/home');
+    }
+}

+ 4 - 1
app/Jobs/StatusPipeline/StatusEntityLexer.php

@@ -20,6 +20,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
 use Illuminate\Queue\InteractsWithQueue;
 use Illuminate\Queue\InteractsWithQueue;
 use Illuminate\Queue\SerializesModels;
 use Illuminate\Queue\SerializesModels;
 use App\Services\UserFilterService;
 use App\Services\UserFilterService;
+use App\Services\AdminShadowFilterService;
 
 
 class StatusEntityLexer implements ShouldQueue
 class StatusEntityLexer implements ShouldQueue
 {
 {
@@ -176,7 +177,9 @@ class StatusEntityLexer implements ShouldQueue
 			$status->reblog_of_id === null &&
 			$status->reblog_of_id === null &&
 			($hideNsfw ? $status->is_nsfw == false : true)
 			($hideNsfw ? $status->is_nsfw == false : true)
 		) {
 		) {
-			PublicTimelineService::add($status->id);
+            if(AdminShadowFilterService::canAddToPublicFeedByProfileId($status->profile_id)) {
+    			PublicTimelineService::add($status->id);
+            }
 		}
 		}
 
 
 		if(config_cache('federation.activitypub.enabled') == true && config('app.env') == 'production') {
 		if(config_cache('federation.activitypub.enabled') == true && config('app.env') == 'production') {

+ 51 - 0
app/Services/AdminShadowFilterService.php

@@ -0,0 +1,51 @@
+<?php
+
+namespace App\Services;
+
+use App\Models\AdminShadowFilter;
+use Cache;
+
+class AdminShadowFilterService
+{
+    const CACHE_KEY = 'pf:services:asfs:';
+
+    public static function queryFilter($name = 'hide_from_public_feeds')
+    {
+        return AdminShadowFilter::whereItemType('App\Profile')
+            ->whereActive(1)
+            ->where('hide_from_public_feeds', true)
+            ->pluck('item_id')
+            ->toArray();
+    }
+
+    public static function getHideFromPublicFeedsList($refresh = false)
+    {
+        $key = self::CACHE_KEY . 'list:hide_from_public_feeds';
+        if($refresh) {
+            Cache::forget($key);
+        }
+        return Cache::remember($key, 86400, function() {
+            return AdminShadowFilter::whereItemType('App\Profile')
+                ->whereActive(1)
+                ->where('hide_from_public_feeds', true)
+                ->pluck('item_id')
+                ->toArray();
+        });
+    }
+
+    public static function canAddToPublicFeedByProfileId($profileId)
+    {
+        return !in_array($profileId, self::getHideFromPublicFeedsList());
+    }
+
+    public static function refresh()
+    {
+        $keys = [
+            self::CACHE_KEY . 'list:hide_from_public_feeds'
+        ];
+
+        foreach($keys as $key) {
+            Cache::forget($key);
+        }
+    }
+}

+ 6 - 4
app/Services/PublicTimelineService.php

@@ -95,7 +95,7 @@ class PublicTimelineService {
 		if(self::count() == 0 || $force == true) {
 		if(self::count() == 0 || $force == true) {
 			$hideNsfw = config('instance.hide_nsfw_on_public_feeds');
 			$hideNsfw = config('instance.hide_nsfw_on_public_feeds');
 			Redis::del(self::CACHE_KEY);
 			Redis::del(self::CACHE_KEY);
-			$minId = SnowflakeService::byDate(now()->subDays(14));
+			$minId = SnowflakeService::byDate(now()->subDays(90));
 			$ids = Status::where('id', '>', $minId)
 			$ids = Status::where('id', '>', $minId)
 				->whereNull(['uri', 'in_reply_to_id', 'reblog_of_id'])
 				->whereNull(['uri', 'in_reply_to_id', 'reblog_of_id'])
 				->when($hideNsfw, function($q, $hideNsfw) {
 				->when($hideNsfw, function($q, $hideNsfw) {
@@ -105,9 +105,11 @@ class PublicTimelineService {
 				->whereScope('public')
 				->whereScope('public')
 				->orderByDesc('id')
 				->orderByDesc('id')
 				->limit($limit)
 				->limit($limit)
-				->pluck('id');
-			foreach($ids as $id) {
-				self::add($id);
+				->pluck('id', 'profile_id');
+			foreach($ids as $k => $id) {
+                if(AdminShadowFilterService::canAddToPublicFeedByProfileId($k)) {
+			         self::add($id);
+                }
 			}
 			}
 			return 1;
 			return 1;
 		}
 		}

+ 64 - 0
resources/views/admin/asf/create.blade.php

@@ -0,0 +1,64 @@
+@extends('admin.partial.template-full')
+
+@section('section')
+</div><div class="header bg-primary pb-3 mt-n4">
+    <div class="container-fluid">
+        <div class="header-body">
+            <div class="row align-items-center py-4">
+                <div class="col-lg-6 col-7">
+                    <p class="display-1 text-white d-inline-block mb-0">New Shadow Filters</p>
+                    <p class="text-white mb-0">Creating a new admin shadow filter</p>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+    <div class="m-n2 m-lg-4">
+        <div class="container-fluid mt-4">
+            <div class="row justify-content-center">
+                <div class="col-12 col-md-6">
+                    @if ($errors->any())
+                    <div class="alert alert-danger">
+                        <ul class="mb-0">
+                            @foreach ($errors->all() as $error)
+                            <li class="mb-0 font-weight-bold">{{ $error }}</li>
+                            @endforeach
+                        </ul>
+                    </div>
+                    @endif
+                    <div class="card card-body">
+                        <form method="post">
+                            @csrf
+                            <div class="form-group">
+                                <label class="font-weight-bold">Username</label>
+                                <input class="form-control" name="username" placeholder="Enter username here" />
+                            </div>
+
+                            <p class="mb-0 font-weight-bold small">Filters</p>
+                            <div class="list-group mb-3">
+                                <div class="list-group-item">
+                                    <div class="custom-control custom-checkbox">
+                                        <input type="checkbox" class="custom-control-input" id="hide_from_public_feeds" name="hide_from_public_feeds">
+                                        <label class="custom-control-label" for="hide_from_public_feeds">Hide public posts from public feed</label>
+                                    </div>
+                                </div>
+                                {{-- <div class="list-group-item"></div> --}}
+                            </div>
+
+                            <div class="form-group">
+                                <label class="font-weight-bold">Note</label>
+                                <textarea class="form-control" name="note" placeholder="Add an optional note, only visible to admins"></textarea>
+                            </div>
+                            <div class="custom-control custom-checkbox">
+                                <input type="checkbox" class="custom-control-input" id="active" name="active" checked>
+                                <label class="custom-control-label font-weight-bold" for="active">Mark as Active</label>
+                            </div>
+                            <hr>
+                            <button type="submit" class="btn btn-success">Save</button>
+                        </form>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+@endsection

+ 64 - 0
resources/views/admin/asf/edit.blade.php

@@ -0,0 +1,64 @@
+@extends('admin.partial.template-full')
+
+@section('section')
+</div><div class="header bg-primary pb-3 mt-n4">
+    <div class="container-fluid">
+        <div class="header-body">
+            <div class="row align-items-center py-4">
+                <div class="col-lg-6 col-7">
+                    <p class="display-1 text-white d-inline-block mb-0">Edit Shadow Filters</p>
+                    <p class="text-white mb-0">Editing shadow filters</p>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+    <div class="m-n2 m-lg-4">
+        <div class="container-fluid mt-4">
+            <div class="row justify-content-center">
+                <div class="col-12 col-md-6">
+                    @if ($errors->any())
+                    <div class="alert alert-danger">
+                        <ul class="mb-0">
+                            @foreach ($errors->all() as $error)
+                            <li class="mb-0 font-weight-bold">{{ $error }}</li>
+                            @endforeach
+                        </ul>
+                    </div>
+                    @endif
+                    <div class="card card-body">
+                        <form method="post">
+                            @csrf
+                            <div class="form-group">
+                                <label class="font-weight-bold">Username</label>
+                                <input class="form-control" name="username" placeholder="Enter username here" value="{{ $profile['username'] }}" disabled="disabled" />
+                            </div>
+
+                            <p class="mb-0 font-weight-bold small">Filters</p>
+                            <div class="list-group mb-3">
+                                <div class="list-group-item">
+                                    <div class="custom-control custom-checkbox">
+                                        <input type="checkbox" class="custom-control-input" id="hide_from_public_feeds" name="hide_from_public_feeds" {!! $filter->hide_from_public_feeds ? 'checked=""' : '' !!}>
+                                        <label class="custom-control-label" for="hide_from_public_feeds">Hide public posts from public feed</label>
+                                    </div>
+                                </div>
+                                {{-- <div class="list-group-item"></div> --}}
+                            </div>
+
+                            <div class="form-group">
+                                <label class="font-weight-bold">Note</label>
+                                <textarea class="form-control" name="note" placeholder="Add an optional note, only visible to admins">{{ $filter->note }}</textarea>
+                            </div>
+                            <div class="custom-control custom-checkbox">
+                                <input type="checkbox" class="custom-control-input" id="active" name="active" {{ $filter->active ? 'checked=""' : ''}}>
+                                <label class="custom-control-label font-weight-bold" for="active">Mark as Active</label>
+                            </div>
+                            <hr>
+                            <button type="submit" class="btn btn-success">Save</button>
+                        </form>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+@endsection

+ 81 - 0
resources/views/admin/asf/home.blade.php

@@ -0,0 +1,81 @@
+@extends('admin.partial.template-full')
+
+@section('section')
+</div><div class="header bg-primary pb-3 mt-n4">
+    <div class="container-fluid">
+        <div class="header-body">
+            <div class="row align-items-center py-4">
+                <div class="col-lg-6 col-7">
+                    <p class="display-1 text-white d-inline-block mb-0">Admin Shadow Filters</p>
+                    <p class="text-white mb-0">Manage shadow filters across Accounts, Hashtags, Feeds and Stories</p>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+    <div class="m-n2 m-lg-4">
+        <div class="container-fluid mt-4">
+            <div class="row mb-3 justify-content-between">
+                <div class="col-12 col-md-8">
+                    <ul class="nav nav-pills">
+                        <li class="nav-item">
+                            <a class="nav-link {{request()->has('filter') ? '':'active'}}" href="/i/admin/asf/home">Active Filters</a>
+                        </li>
+                        <li class="nav-item">
+                            <a class="nav-link {{request()->has('filter') && request()->filter == 'all' ? 'active':''}}" href="/i/admin/asf/home?filter=all">All</a>
+                        </li>
+                        <li class="nav-item">
+                            <a class="nav-link {{request()->has('filter') && request()->filter == 'inactive' ? 'active':''}}" href="/i/admin/asf/home?filter=inactive">Inactive</a>
+                        </li>
+                        <li class="nav-item">
+                            <a class="nav-link {{request()->has('new') ? 'active':''}}" href="/i/admin/asf/create">New</a>
+                        </li>
+                    </ul>
+                </div>
+
+                <div class="col-12 col-md-4">
+                    <form method="get">
+                        <input class="form-control" placeholder="Search by username" name="q" value="{{request()->has('q') ? request()->query('q') : ''}}" />
+                    </form>
+                </div>
+            </div>
+
+            <div class="table-responsive rounded">
+                <table class="table table-dark">
+                    <thead class="thead-dark">
+                        <tr>
+                            <th scope="col" class="cursor-pointer">ID</th>
+                            <th scope="col" class="cursor-pointer">Username</th>
+                            <th scope="col" class="cursor-pointer">Hide Feeds</th>
+                            <th scope="col" class="cursor-pointer">Active</th>
+                            <th scope="col" class="cursor-pointer">Created</th>
+                        </tr>
+                    </thead>
+                    <tbody>
+                        @foreach($filters as $filter)
+                        <tr>
+                            <td><a href="/i/admin/asf/edit/{{$filter->id}}">{{ $filter->id }}</a></td>
+                            <td>
+                                <div class="d-flex align-items-center" style="gap: 1rem;">
+
+                                <img src="{{ $filter->account()['avatar'] }}" class="rounded-circle" width="30" height="30" onerror="this.src='/storage/avatars/default.jpg';this.onerror=null;" />
+                                <p class="font-weight-bold mb-0">
+                                    &commat;{{ $filter->account()['acct'] }}
+                                </p>
+                                </div>
+                            </td>
+                            <td>{{ $filter->hide_from_public_feeds ? '✅' : ''}}</td>
+                            <td>{{ $filter->active ? '✅' : ''}}</td>
+                            <td>{{ $filter->created_at->diffForHumans() }}</td>
+                        </tr>
+                        @endforeach
+                    </tbody>
+                </table>
+
+                <div class="d-flex mt-3">
+                    {{ $filters->links() }}
+                </div>
+            </div>
+        </div>
+    </div>
+@endsection

+ 7 - 0
routes/web.php

@@ -96,6 +96,13 @@ Route::domain(config('pixelfed.domain.admin'))->prefix('i/admin')->group(functio
 
 
 	Route::get('autospam/home', 'AdminController@autospamHome')->name('admin.autospam');
 	Route::get('autospam/home', 'AdminController@autospamHome')->name('admin.autospam');
 
 
+    Route::redirect('asf/', 'asf/home');
+    Route::get('asf/home', 'AdminShadowFilterController@home');
+    Route::get('asf/create', 'AdminShadowFilterController@create');
+    Route::get('asf/edit/{id}', 'AdminShadowFilterController@edit');
+    Route::post('asf/edit/{id}', 'AdminShadowFilterController@storeEdit');
+    Route::post('asf/create', 'AdminShadowFilterController@store');
+
 	Route::prefix('api')->group(function() {
 	Route::prefix('api')->group(function() {
 		Route::get('stats', 'AdminController@getStats');
 		Route::get('stats', 'AdminController@getStats');
 		Route::get('accounts', 'AdminController@getAccounts');
 		Route::get('accounts', 'AdminController@getAccounts');