mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-11-22 06:21:27 +00:00
commit
eebed73a5e
42 changed files with 1669 additions and 354 deletions
|
@ -12,6 +12,7 @@
|
||||||
- Added `app:hashtag-cached-count-update` command to update cached_count of hashtags and add to scheduler to run every 25 minutes past the hour ([1e31fee6](https://github.com/pixelfed/pixelfed/commit/1e31fee6))
|
- Added `app:hashtag-cached-count-update` command to update cached_count of hashtags and add to scheduler to run every 25 minutes past the hour ([1e31fee6](https://github.com/pixelfed/pixelfed/commit/1e31fee6))
|
||||||
- Added `app:hashtag-related-generate` command to generate related hashtags ([176b4ed7](https://github.com/pixelfed/pixelfed/commit/176b4ed7))
|
- Added `app:hashtag-related-generate` command to generate related hashtags ([176b4ed7](https://github.com/pixelfed/pixelfed/commit/176b4ed7))
|
||||||
- Added Mutual Followers API endpoint ([33dbbe46](https://github.com/pixelfed/pixelfed/commit/33dbbe46))
|
- Added Mutual Followers API endpoint ([33dbbe46](https://github.com/pixelfed/pixelfed/commit/33dbbe46))
|
||||||
|
- Added User Domain Blocks ([#4834](https://github.com/pixelfed/pixelfed/pull/4834)) ([fa0380ac](https://github.com/pixelfed/pixelfed/commit/fa0380ac))
|
||||||
|
|
||||||
### Federation
|
### Federation
|
||||||
- Update Privacy Settings, add support for Mastodon `indexable` search flag ([fc24630e](https://github.com/pixelfed/pixelfed/commit/fc24630e))
|
- Update Privacy Settings, add support for Mastodon `indexable` search flag ([fc24630e](https://github.com/pixelfed/pixelfed/commit/fc24630e))
|
||||||
|
@ -80,6 +81,8 @@
|
||||||
- Update Inbox, improve tombstone query efficiency ([759a4393](https://github.com/pixelfed/pixelfed/commit/759a4393))
|
- Update Inbox, improve tombstone query efficiency ([759a4393](https://github.com/pixelfed/pixelfed/commit/759a4393))
|
||||||
- Update AccountService, add setLastActive method ([ebbd98e7](https://github.com/pixelfed/pixelfed/commit/ebbd98e7))
|
- Update AccountService, add setLastActive method ([ebbd98e7](https://github.com/pixelfed/pixelfed/commit/ebbd98e7))
|
||||||
- Update ApiV1Controller, set last_active_at ([b6419545](https://github.com/pixelfed/pixelfed/commit/b6419545))
|
- Update ApiV1Controller, set last_active_at ([b6419545](https://github.com/pixelfed/pixelfed/commit/b6419545))
|
||||||
|
- Update AdminShadowFilter, fix deleted profile bug ([a492a95a](https://github.com/pixelfed/pixelfed/commit/a492a95a))
|
||||||
|
- Update FollowerService, add $silent param to remove method to more efficently purge relationships ([1664a5bc](https://github.com/pixelfed/pixelfed/commit/1664a5bc))
|
||||||
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
||||||
|
|
||||||
## [v0.11.9 (2023-08-21)](https://github.com/pixelfed/pixelfed/compare/v0.11.8...v0.11.9)
|
## [v0.11.9 (2023-08-21)](https://github.com/pixelfed/pixelfed/compare/v0.11.8...v0.11.9)
|
||||||
|
|
106
app/Console/Commands/AddUserDomainBlock.php
Normal file
106
app/Console/Commands/AddUserDomainBlock.php
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use App\User;
|
||||||
|
use App\Models\DefaultDomainBlock;
|
||||||
|
use App\Models\UserDomainBlock;
|
||||||
|
use function Laravel\Prompts\text;
|
||||||
|
use function Laravel\Prompts\confirm;
|
||||||
|
use function Laravel\Prompts\progress;
|
||||||
|
|
||||||
|
class AddUserDomainBlock extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'app:add-user-domain-block';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Apply a domain block to all users';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$domain = text('Enter domain you want to block');
|
||||||
|
$domain = strtolower($domain);
|
||||||
|
$domain = $this->validateDomain($domain);
|
||||||
|
if(!$domain || empty($domain)) {
|
||||||
|
$this->error('Invalid domain');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->processBlocks($domain);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function validateDomain($domain)
|
||||||
|
{
|
||||||
|
if(!strpos($domain, '.')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(str_starts_with($domain, 'https://')) {
|
||||||
|
$domain = str_replace('https://', '', $domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(str_starts_with($domain, 'http://')) {
|
||||||
|
$domain = str_replace('http://', '', $domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
$domain = strtolower(parse_url('https://' . $domain, PHP_URL_HOST));
|
||||||
|
|
||||||
|
$valid = filter_var($domain, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME|FILTER_NULL_ON_FAILURE);
|
||||||
|
if(!$valid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($domain === config('pixelfed.domain.app')) {
|
||||||
|
$this->error('Invalid domain');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$confirmed = confirm('Are you sure you want to block ' . $domain . '?');
|
||||||
|
if(!$confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function processBlocks($domain)
|
||||||
|
{
|
||||||
|
DefaultDomainBlock::updateOrCreate([
|
||||||
|
'domain' => $domain
|
||||||
|
]);
|
||||||
|
progress(
|
||||||
|
label: 'Updating user domain blocks...',
|
||||||
|
steps: User::lazyById(500),
|
||||||
|
callback: fn ($user) => $this->performTask($user, $domain),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function performTask($user, $domain)
|
||||||
|
{
|
||||||
|
if(!$user->profile_id || $user->delete_after) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($user->status != null && $user->status != 'disabled') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UserDomainBlock::updateOrCreate([
|
||||||
|
'profile_id' => $user->profile_id,
|
||||||
|
'domain' => $domain
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
96
app/Console/Commands/DeleteUserDomainBlock.php
Normal file
96
app/Console/Commands/DeleteUserDomainBlock.php
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use App\User;
|
||||||
|
use App\Models\DefaultDomainBlock;
|
||||||
|
use App\Models\UserDomainBlock;
|
||||||
|
use function Laravel\Prompts\text;
|
||||||
|
use function Laravel\Prompts\confirm;
|
||||||
|
use function Laravel\Prompts\progress;
|
||||||
|
|
||||||
|
class DeleteUserDomainBlock extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'app:delete-user-domain-block';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Remove a domain block for all users';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$domain = text('Enter domain you want to unblock');
|
||||||
|
$domain = strtolower($domain);
|
||||||
|
$domain = $this->validateDomain($domain);
|
||||||
|
if(!$domain || empty($domain)) {
|
||||||
|
$this->error('Invalid domain');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->processUnblocks($domain);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function validateDomain($domain)
|
||||||
|
{
|
||||||
|
if(!strpos($domain, '.')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(str_starts_with($domain, 'https://')) {
|
||||||
|
$domain = str_replace('https://', '', $domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(str_starts_with($domain, 'http://')) {
|
||||||
|
$domain = str_replace('http://', '', $domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
$domain = strtolower(parse_url('https://' . $domain, PHP_URL_HOST));
|
||||||
|
|
||||||
|
$valid = filter_var($domain, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME|FILTER_NULL_ON_FAILURE);
|
||||||
|
if(!$valid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($domain === config('pixelfed.domain.app')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$confirmed = confirm('Are you sure you want to unblock ' . $domain . '?');
|
||||||
|
if(!$confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function processUnblocks($domain)
|
||||||
|
{
|
||||||
|
DefaultDomainBlock::whereDomain($domain)->delete();
|
||||||
|
if(!UserDomainBlock::whereDomain($domain)->count()) {
|
||||||
|
$this->info('No results found!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
progress(
|
||||||
|
label: 'Updating user domain blocks...',
|
||||||
|
steps: UserDomainBlock::whereDomain($domain)->lazyById(500),
|
||||||
|
callback: fn ($domainBlock) => $this->performTask($domainBlock),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function performTask($domainBlock)
|
||||||
|
{
|
||||||
|
$domainBlock->deleteQuietly();
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,7 +19,8 @@ class AdminShadowFilterController extends Controller
|
||||||
{
|
{
|
||||||
$filter = $request->input('filter');
|
$filter = $request->input('filter');
|
||||||
$searchQuery = $request->input('q');
|
$searchQuery = $request->input('q');
|
||||||
$filters = AdminShadowFilter::when($filter, function($q, $filter) {
|
$filters = AdminShadowFilter::whereHas('profile')
|
||||||
|
->when($filter, function($q, $filter) {
|
||||||
if($filter == 'all') {
|
if($filter == 'all') {
|
||||||
return $q;
|
return $q;
|
||||||
} else if($filter == 'inactive') {
|
} else if($filter == 'inactive') {
|
||||||
|
|
|
@ -31,6 +31,7 @@ use App\{
|
||||||
UserSetting,
|
UserSetting,
|
||||||
UserFilter,
|
UserFilter,
|
||||||
};
|
};
|
||||||
|
use App\Models\UserDomainBlock;
|
||||||
use League\Fractal;
|
use League\Fractal;
|
||||||
use App\Transformer\Api\Mastodon\v1\{
|
use App\Transformer\Api\Mastodon\v1\{
|
||||||
AccountTransformer,
|
AccountTransformer,
|
||||||
|
@ -2422,6 +2423,7 @@ class ApiV1Controller extends Controller
|
||||||
$local = $request->has('local');
|
$local = $request->has('local');
|
||||||
$filtered = $user ? UserFilterService::filters($user->profile_id) : [];
|
$filtered = $user ? UserFilterService::filters($user->profile_id) : [];
|
||||||
AccountService::setLastActive($user->id);
|
AccountService::setLastActive($user->id);
|
||||||
|
$domainBlocks = UserFilterService::domainBlocks($user->profile_id);
|
||||||
|
|
||||||
if($remote && config('instance.timeline.network.cached')) {
|
if($remote && config('instance.timeline.network.cached')) {
|
||||||
Cache::remember('api:v1:timelines:network:cache_check', 10368000, function() {
|
Cache::remember('api:v1:timelines:network:cache_check', 10368000, function() {
|
||||||
|
@ -2496,6 +2498,13 @@ class ApiV1Controller extends Controller
|
||||||
->filter(function($s) use($filtered) {
|
->filter(function($s) use($filtered) {
|
||||||
return $s && isset($s['account']) && in_array($s['account']['id'], $filtered) == false;
|
return $s && isset($s['account']) && in_array($s['account']['id'], $filtered) == false;
|
||||||
})
|
})
|
||||||
|
->filter(function($s) use($domainBlocks) {
|
||||||
|
if(!$domainBlocks || !count($domainBlocks)) {
|
||||||
|
return $s;
|
||||||
|
}
|
||||||
|
$domain = strtolower(parse_url($s['url'], PHP_URL_HOST));
|
||||||
|
return !in_array($domain, $domainBlocks);
|
||||||
|
})
|
||||||
->take($limit)
|
->take($limit)
|
||||||
->values();
|
->values();
|
||||||
|
|
||||||
|
@ -3276,6 +3285,7 @@ class ApiV1Controller extends Controller
|
||||||
$limit = $request->input('limit', 20);
|
$limit = $request->input('limit', 20);
|
||||||
$onlyMedia = $request->input('only_media', true);
|
$onlyMedia = $request->input('only_media', true);
|
||||||
$pe = $request->has(self::PF_API_ENTITY_KEY);
|
$pe = $request->has(self::PF_API_ENTITY_KEY);
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
|
||||||
if($min || $max) {
|
if($min || $max) {
|
||||||
$minMax = SnowflakeService::byDate(now()->subMonths(6));
|
$minMax = SnowflakeService::byDate(now()->subMonths(6));
|
||||||
|
@ -3287,7 +3297,8 @@ class ApiV1Controller extends Controller
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$filters = UserFilterService::filters($request->user()->profile_id);
|
$filters = UserFilterService::filters($pid);
|
||||||
|
$domainBlocks = UserFilterService::domainBlocks($pid);
|
||||||
|
|
||||||
if(!$min && !$max) {
|
if(!$min && !$max) {
|
||||||
$id = 1;
|
$id = 1;
|
||||||
|
@ -3313,10 +3324,11 @@ class ApiV1Controller extends Controller
|
||||||
if($onlyMedia && !isset($i['media_attachments']) || !count($i['media_attachments'])) {
|
if($onlyMedia && !isset($i['media_attachments']) || !count($i['media_attachments'])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return $i && isset($i['account']);
|
return $i && isset($i['account'], $i['url']);
|
||||||
})
|
})
|
||||||
->filter(function($i) use($filters) {
|
->filter(function($i) use($filters, $domainBlocks) {
|
||||||
return !in_array($i['account']['id'], $filters);
|
$domain = strtolower(parse_url($i['url'], PHP_URL_HOST));
|
||||||
|
return !in_array($i['account']['id'], $filters) && !in_array($domain, $domainBlocks);
|
||||||
})
|
})
|
||||||
->values()
|
->values()
|
||||||
->toArray();
|
->toArray();
|
||||||
|
@ -3619,25 +3631,31 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
$pid = $request->user()->profile_id;
|
$pid = $request->user()->profile_id;
|
||||||
|
|
||||||
$ids = Cache::remember('api:v1.1:discover:accounts:popular', 86400, function() {
|
$ids = Cache::remember('api:v1.1:discover:accounts:popular', 3600, function() {
|
||||||
return DB::table('profiles')
|
return DB::table('profiles')
|
||||||
->where('is_private', false)
|
->where('is_private', false)
|
||||||
->whereNull('status')
|
->whereNull('status')
|
||||||
->orderByDesc('profiles.followers_count')
|
->orderByDesc('profiles.followers_count')
|
||||||
->limit(20)
|
->limit(30)
|
||||||
->get();
|
->get();
|
||||||
});
|
});
|
||||||
|
$filters = UserFilterService::filters($pid);
|
||||||
$ids = $ids->map(function($profile) {
|
$ids = $ids->map(function($profile) {
|
||||||
return AccountService::get($profile->id, true);
|
return AccountService::get($profile->id, true);
|
||||||
})
|
})
|
||||||
->filter(function($profile) use($pid) {
|
->filter(function($profile) use($pid) {
|
||||||
return $profile && isset($profile['id']);
|
return $profile && isset($profile['id'], $profile['locked']) && !$profile['locked'];
|
||||||
})
|
})
|
||||||
->filter(function($profile) use($pid) {
|
->filter(function($profile) use($pid) {
|
||||||
return $profile['id'] != $pid;
|
return $profile['id'] != $pid;
|
||||||
})
|
})
|
||||||
->take(6)
|
->filter(function($profile) use($pid) {
|
||||||
|
return !FollowerService::follows($pid, $profile['id'], true);
|
||||||
|
})
|
||||||
|
->filter(function($profile) use($filters) {
|
||||||
|
return !in_array($profile['id'], $filters);
|
||||||
|
})
|
||||||
|
->take(16)
|
||||||
->values();
|
->values();
|
||||||
|
|
||||||
return $this->json($ids);
|
return $this->json($ids);
|
||||||
|
|
118
app/Http/Controllers/Api/V1/DomainBlockController.php
Normal file
118
app/Http/Controllers/Api/V1/DomainBlockController.php
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api\V1;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\UserDomainBlock;
|
||||||
|
use App\Util\ActivityPub\Helpers;
|
||||||
|
use App\Services\UserFilterService;
|
||||||
|
use Illuminate\Bus\Batch;
|
||||||
|
use Illuminate\Support\Facades\Bus;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use App\Jobs\HomeFeedPipeline\FeedRemoveDomainPipeline;
|
||||||
|
use App\Jobs\ProfilePipeline\ProfilePurgeNotificationsByDomain;
|
||||||
|
use App\Jobs\ProfilePipeline\ProfilePurgeFollowersByDomain;
|
||||||
|
|
||||||
|
class DomainBlockController extends Controller
|
||||||
|
{
|
||||||
|
public function json($res, $code = 200, $headers = [])
|
||||||
|
{
|
||||||
|
return response()->json($res, $code, $headers, JSON_UNESCAPED_SLASHES);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
abort_unless($request->user(), 403);
|
||||||
|
$this->validate($request, [
|
||||||
|
'limit' => 'sometimes|integer|min:1|max:200'
|
||||||
|
]);
|
||||||
|
$limit = $request->input('limit', 100);
|
||||||
|
$id = $request->user()->profile_id;
|
||||||
|
$filters = UserDomainBlock::whereProfileId($id)->orderByDesc('id')->cursorPaginate($limit);
|
||||||
|
$links = null;
|
||||||
|
$headers = [];
|
||||||
|
|
||||||
|
if($filters->nextCursor()) {
|
||||||
|
$links .= '<'.$filters->nextPageUrl().'&limit='.$limit.'>; rel="next"';
|
||||||
|
}
|
||||||
|
|
||||||
|
if($filters->previousCursor()) {
|
||||||
|
if($links != null) {
|
||||||
|
$links .= ', ';
|
||||||
|
}
|
||||||
|
$links .= '<'.$filters->previousPageUrl().'&limit='.$limit.'>; rel="prev"';
|
||||||
|
}
|
||||||
|
|
||||||
|
if($links) {
|
||||||
|
$headers = ['Link' => $links];
|
||||||
|
}
|
||||||
|
return $this->json($filters->pluck('domain'), 200, $headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
abort_unless($request->user(), 403);
|
||||||
|
|
||||||
|
$this->validate($request, [
|
||||||
|
'domain' => 'required|active_url|min:1|max:120'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
|
||||||
|
$domain = trim($request->input('domain'));
|
||||||
|
|
||||||
|
if(Helpers::validateUrl($domain) == false) {
|
||||||
|
return abort(500, 'Invalid domain or already blocked by server admins');
|
||||||
|
}
|
||||||
|
|
||||||
|
$domain = strtolower(parse_url($domain, PHP_URL_HOST));
|
||||||
|
|
||||||
|
abort_if(config_cache('pixelfed.domain.app') == $domain, 400, 'Cannot ban your own server');
|
||||||
|
|
||||||
|
$existingCount = UserDomainBlock::whereProfileId($pid)->count();
|
||||||
|
$maxLimit = config('instance.user_filters.max_domain_blocks');
|
||||||
|
$errorMsg = __('profile.block.domain.max', ['max' => $maxLimit]);
|
||||||
|
|
||||||
|
abort_if($existingCount >= $maxLimit, 400, $errorMsg);
|
||||||
|
|
||||||
|
$block = UserDomainBlock::updateOrCreate([
|
||||||
|
'profile_id' => $pid,
|
||||||
|
'domain' => $domain
|
||||||
|
]);
|
||||||
|
|
||||||
|
if($block->wasRecentlyCreated) {
|
||||||
|
Bus::batch([
|
||||||
|
[
|
||||||
|
new FeedRemoveDomainPipeline($pid, $domain),
|
||||||
|
new ProfilePurgeNotificationsByDomain($pid, $domain),
|
||||||
|
new ProfilePurgeFollowersByDomain($pid, $domain)
|
||||||
|
]
|
||||||
|
])->allowFailures()->onQueue('feed')->dispatch();
|
||||||
|
|
||||||
|
Cache::forget('profile:following:' . $pid);
|
||||||
|
UserFilterService::domainBlocks($pid, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->json([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(Request $request)
|
||||||
|
{
|
||||||
|
abort_unless($request->user(), 403);
|
||||||
|
|
||||||
|
$this->validate($request, [
|
||||||
|
'domain' => 'required|min:1|max:120'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
|
||||||
|
$domain = strtolower(trim($request->input('domain')));
|
||||||
|
|
||||||
|
$filters = UserDomainBlock::whereProfileId($pid)->whereDomain($domain)->delete();
|
||||||
|
|
||||||
|
UserFilterService::domainBlocks($pid, true);
|
||||||
|
|
||||||
|
return $this->json([]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ use App\Util\Lexer\PrettyNumber;
|
||||||
use App\Util\ActivityPub\Helpers;
|
use App\Util\ActivityPub\Helpers;
|
||||||
use Auth, Cache, DB;
|
use Auth, Cache, DB;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use App\Models\UserDomainBlock;
|
||||||
|
|
||||||
trait PrivacySettings
|
trait PrivacySettings
|
||||||
{
|
{
|
||||||
|
@ -149,47 +150,25 @@ trait PrivacySettings
|
||||||
|
|
||||||
public function blockedInstances()
|
public function blockedInstances()
|
||||||
{
|
{
|
||||||
$pid = Auth::user()->profile->id;
|
// deprecated
|
||||||
$filters = UserFilter::whereUserId($pid)
|
abort(404);
|
||||||
->whereFilterableType('App\Instance')
|
}
|
||||||
->whereFilterType('block')
|
|
||||||
->orderByDesc('id')
|
public function domainBlocks()
|
||||||
->paginate(10);
|
{
|
||||||
return view('settings.privacy.blocked-instances', compact('filters'));
|
return view('settings.privacy.domain-blocks');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function blockedInstanceStore(Request $request)
|
public function blockedInstanceStore(Request $request)
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
// deprecated
|
||||||
'domain' => 'required|url|min:1|max:120'
|
abort(404);
|
||||||
]);
|
|
||||||
$domain = $request->input('domain');
|
|
||||||
if(Helpers::validateUrl($domain) == false) {
|
|
||||||
return abort(400, 'Invalid domain');
|
|
||||||
}
|
|
||||||
$domain = parse_url($domain, PHP_URL_HOST);
|
|
||||||
$instance = Instance::firstOrCreate(['domain' => $domain]);
|
|
||||||
$filter = new UserFilter;
|
|
||||||
$filter->user_id = Auth::user()->profile->id;
|
|
||||||
$filter->filterable_id = $instance->id;
|
|
||||||
$filter->filterable_type = 'App\Instance';
|
|
||||||
$filter->filter_type = 'block';
|
|
||||||
$filter->save();
|
|
||||||
return response()->json(['msg' => 200]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function blockedInstanceUnblock(Request $request)
|
public function blockedInstanceUnblock(Request $request)
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
// deprecated
|
||||||
'id' => 'required|integer|min:1'
|
abort(404);
|
||||||
]);
|
|
||||||
$pid = Auth::user()->profile->id;
|
|
||||||
|
|
||||||
$filter = UserFilter::whereFilterableType('App\Instance')
|
|
||||||
->whereUserId($pid)
|
|
||||||
->findOrFail($request->input('id'));
|
|
||||||
$filter->delete();
|
|
||||||
return redirect(route('settings.privacy.blocked-instances'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function blockedKeywords()
|
public function blockedKeywords()
|
||||||
|
|
|
@ -76,10 +76,7 @@ class DeleteRemoteStatusPipeline implements ShouldQueue
|
||||||
});
|
});
|
||||||
Mention::whereStatusId($status->id)->forceDelete();
|
Mention::whereStatusId($status->id)->forceDelete();
|
||||||
Report::whereObjectType('App\Status')->whereObjectId($status->id)->delete();
|
Report::whereObjectType('App\Status')->whereObjectId($status->id)->delete();
|
||||||
$statusHashtags = StatusHashtag::whereStatusId($status->id)->get();
|
StatusHashtag::whereStatusId($status->id)->deleteQuietly();
|
||||||
foreach($statusHashtags as $stag) {
|
|
||||||
$stag->delete();
|
|
||||||
}
|
|
||||||
StatusView::whereStatusId($status->id)->delete();
|
StatusView::whereStatusId($status->id)->delete();
|
||||||
Status::whereReblogOfId($status->id)->forceDelete();
|
Status::whereReblogOfId($status->id)->forceDelete();
|
||||||
$status->forceDelete();
|
$status->forceDelete();
|
||||||
|
|
|
@ -11,6 +11,7 @@ use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
|
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
|
||||||
use App\UserFilter;
|
use App\UserFilter;
|
||||||
|
use App\Models\UserDomainBlock;
|
||||||
use App\Services\FollowerService;
|
use App\Services\FollowerService;
|
||||||
use App\Services\HomeTimelineService;
|
use App\Services\HomeTimelineService;
|
||||||
use App\Services\StatusService;
|
use App\Services\StatusService;
|
||||||
|
@ -69,7 +70,7 @@ class FeedInsertPipeline implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
||||||
$sid = $this->sid;
|
$sid = $this->sid;
|
||||||
$status = StatusService::get($sid, false);
|
$status = StatusService::get($sid, false);
|
||||||
|
|
||||||
if(!$status || !isset($status['account']) || !isset($status['account']['id'])) {
|
if(!$status || !isset($status['account']) || !isset($status['account']['id'], $status['url'])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,7 +86,24 @@ class FeedInsertPipeline implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$skipIds = UserFilter::whereFilterableType('App\Profile')->whereFilterableId($status['account']['id'])->whereIn('filter_type', ['mute', 'block'])->pluck('user_id')->toArray();
|
$domain = strtolower(parse_url($status['url'], PHP_URL_HOST));
|
||||||
|
$skipIds = [];
|
||||||
|
|
||||||
|
if(strtolower(config('pixelfed.domain.app')) !== $domain) {
|
||||||
|
$skipIds = UserDomainBlock::where('domain', $domain)->pluck('profile_id')->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
$filters = UserFilter::whereFilterableType('App\Profile')
|
||||||
|
->whereFilterableId($status['account']['id'])
|
||||||
|
->whereIn('filter_type', ['mute', 'block'])
|
||||||
|
->pluck('user_id')
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
if($filters && count($filters)) {
|
||||||
|
$skipIds = array_merge($skipIds, $filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
$skipIds = array_unique(array_values($skipIds));
|
||||||
|
|
||||||
foreach($ids as $id) {
|
foreach($ids as $id) {
|
||||||
if(!in_array($id, $skipIds)) {
|
if(!in_array($id, $skipIds)) {
|
||||||
|
|
|
@ -11,6 +11,7 @@ use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
|
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
|
||||||
use App\UserFilter;
|
use App\UserFilter;
|
||||||
|
use App\Models\UserDomainBlock;
|
||||||
use App\Services\FollowerService;
|
use App\Services\FollowerService;
|
||||||
use App\Services\HomeTimelineService;
|
use App\Services\HomeTimelineService;
|
||||||
use App\Services\StatusService;
|
use App\Services\StatusService;
|
||||||
|
@ -69,7 +70,7 @@ class FeedInsertRemotePipeline implements ShouldQueue, ShouldBeUniqueUntilProces
|
||||||
$sid = $this->sid;
|
$sid = $this->sid;
|
||||||
$status = StatusService::get($sid, false);
|
$status = StatusService::get($sid, false);
|
||||||
|
|
||||||
if(!$status || !isset($status['account']) || !isset($status['account']['id'])) {
|
if(!$status || !isset($status['account']) || !isset($status['account']['id'], $status['url'])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +84,24 @@ class FeedInsertRemotePipeline implements ShouldQueue, ShouldBeUniqueUntilProces
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$skipIds = UserFilter::whereFilterableType('App\Profile')->whereFilterableId($status['account']['id'])->whereIn('filter_type', ['mute', 'block'])->pluck('user_id')->toArray();
|
$domain = strtolower(parse_url($status['url'], PHP_URL_HOST));
|
||||||
|
$skipIds = [];
|
||||||
|
|
||||||
|
if(strtolower(config('pixelfed.domain.app')) !== $domain) {
|
||||||
|
$skipIds = UserDomainBlock::where('domain', $domain)->pluck('profile_id')->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
$filters = UserFilter::whereFilterableType('App\Profile')
|
||||||
|
->whereFilterableId($status['account']['id'])
|
||||||
|
->whereIn('filter_type', ['mute', 'block'])
|
||||||
|
->pluck('user_id')
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
if($filters && count($filters)) {
|
||||||
|
$skipIds = array_merge($skipIds, $filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
$skipIds = array_unique(array_values($skipIds));
|
||||||
|
|
||||||
foreach($ids as $id) {
|
foreach($ids as $id) {
|
||||||
if(!in_array($id, $skipIds)) {
|
if(!in_array($id, $skipIds)) {
|
||||||
|
|
98
app/Jobs/HomeFeedPipeline/FeedRemoveDomainPipeline.php
Normal file
98
app/Jobs/HomeFeedPipeline/FeedRemoveDomainPipeline.php
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\HomeFeedPipeline;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Batchable;
|
||||||
|
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 Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
|
||||||
|
use App\Services\StatusService;
|
||||||
|
use App\Services\HomeTimelineService;
|
||||||
|
|
||||||
|
class FeedRemoveDomainPipeline implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
||||||
|
{
|
||||||
|
use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected $pid;
|
||||||
|
protected $domain;
|
||||||
|
|
||||||
|
public $timeout = 900;
|
||||||
|
public $tries = 3;
|
||||||
|
public $maxExceptions = 1;
|
||||||
|
public $failOnTimeout = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of seconds after which the job's unique lock will be released.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $uniqueFor = 3600;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the unique ID for the job.
|
||||||
|
*/
|
||||||
|
public function uniqueId(): string
|
||||||
|
{
|
||||||
|
return 'hts:feed:remove:domain:' . $this->pid . ':d-' . $this->domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the middleware the job should pass through.
|
||||||
|
*
|
||||||
|
* @return array<int, object>
|
||||||
|
*/
|
||||||
|
public function middleware(): array
|
||||||
|
{
|
||||||
|
return [(new WithoutOverlapping("hts:feed:remove:domain:{$this->pid}:d-{$this->domain}"))->shared()->dontRelease()];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*/
|
||||||
|
public function __construct($pid, $domain)
|
||||||
|
{
|
||||||
|
$this->pid = $pid;
|
||||||
|
$this->domain = $domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
if(!config('exp.cached_home_timeline')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->batch()->cancelled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$this->pid || !$this->domain) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$domain = strtolower($this->domain);
|
||||||
|
$pid = $this->pid;
|
||||||
|
$posts = HomeTimelineService::get($pid, '0', '-1');
|
||||||
|
|
||||||
|
foreach($posts as $post) {
|
||||||
|
$status = StatusService::get($post, false);
|
||||||
|
if(!$status || !isset($status['url'])) {
|
||||||
|
HomeTimelineService::rem($pid, $post);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$host = strtolower(parse_url($status['url'], PHP_URL_HOST));
|
||||||
|
if($host === strtolower(config('pixelfed.domain.app')) || !$host) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if($host === $domain) {
|
||||||
|
HomeTimelineService::rem($pid, $status['id']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ use Illuminate\Queue\SerializesModels;
|
||||||
use App\Hashtag;
|
use App\Hashtag;
|
||||||
use App\StatusHashtag;
|
use App\StatusHashtag;
|
||||||
use App\UserFilter;
|
use App\UserFilter;
|
||||||
|
use App\Models\UserDomainBlock;
|
||||||
use App\Services\HashtagFollowService;
|
use App\Services\HashtagFollowService;
|
||||||
use App\Services\HomeTimelineService;
|
use App\Services\HomeTimelineService;
|
||||||
use App\Services\StatusService;
|
use App\Services\StatusService;
|
||||||
|
@ -77,7 +78,7 @@ class HashtagInsertFanoutPipeline implements ShouldQueue, ShouldBeUniqueUntilPro
|
||||||
$sid = $hashtag->status_id;
|
$sid = $hashtag->status_id;
|
||||||
$status = StatusService::get($sid, false);
|
$status = StatusService::get($sid, false);
|
||||||
|
|
||||||
if(!$status || !isset($status['account']) || !isset($status['account']['id'])) {
|
if(!$status || !isset($status['account']) || !isset($status['account']['id'], $status['url'])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,7 +86,20 @@ class HashtagInsertFanoutPipeline implements ShouldQueue, ShouldBeUniqueUntilPro
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$skipIds = UserFilter::whereFilterableType('App\Profile')->whereFilterableId($status['account']['id'])->whereIn('filter_type', ['mute', 'block'])->pluck('user_id')->toArray();
|
$domain = strtolower(parse_url($status['url'], PHP_URL_HOST));
|
||||||
|
$skipIds = [];
|
||||||
|
|
||||||
|
if(strtolower(config('pixelfed.domain.app')) !== $domain) {
|
||||||
|
$skipIds = UserDomainBlock::where('domain', $domain)->pluck('profile_id')->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
$filters = UserFilter::whereFilterableType('App\Profile')->whereFilterableId($status['account']['id'])->whereIn('filter_type', ['mute', 'block'])->pluck('user_id')->toArray();
|
||||||
|
|
||||||
|
if($filters && count($filters)) {
|
||||||
|
$skipIds = array_merge($skipIds, $filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
$skipIds = array_unique(array_values($skipIds));
|
||||||
|
|
||||||
$ids = HashtagFollowService::getPidByHid($hashtag->hashtag_id);
|
$ids = HashtagFollowService::getPidByHid($hashtag->hashtag_id);
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Log;
|
||||||
|
|
||||||
class ImageResize implements ShouldQueue
|
class ImageResize implements ShouldQueue
|
||||||
{
|
{
|
||||||
|
@ -46,6 +47,7 @@ class ImageResize implements ShouldQueue
|
||||||
}
|
}
|
||||||
$path = storage_path('app/'.$media->media_path);
|
$path = storage_path('app/'.$media->media_path);
|
||||||
if (!is_file($path) || $media->skip_optimize) {
|
if (!is_file($path) || $media->skip_optimize) {
|
||||||
|
Log::info('Tried to optimize media that does not exist or is not readable. ' . $path);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,6 +59,7 @@ class ImageResize implements ShouldQueue
|
||||||
$img = new Image();
|
$img = new Image();
|
||||||
$img->resizeImage($media);
|
$img->resizeImage($media);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
|
Log::error($e);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageThumbnail::dispatch($media)->onQueue('mmo');
|
ImageThumbnail::dispatch($media)->onQueue('mmo');
|
||||||
|
|
119
app/Jobs/ProfilePipeline/ProfilePurgeFollowersByDomain.php
Normal file
119
app/Jobs/ProfilePipeline/ProfilePurgeFollowersByDomain.php
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\ProfilePipeline;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Batchable;
|
||||||
|
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 Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
|
||||||
|
use App\Follower;
|
||||||
|
use App\Profile;
|
||||||
|
use App\Notification;
|
||||||
|
use DB;
|
||||||
|
use App\Services\AccountService;
|
||||||
|
use App\Services\FollowerService;
|
||||||
|
use App\Services\NotificationService;
|
||||||
|
|
||||||
|
class ProfilePurgeFollowersByDomain implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
||||||
|
{
|
||||||
|
use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected $pid;
|
||||||
|
protected $domain;
|
||||||
|
|
||||||
|
public $timeout = 900;
|
||||||
|
public $tries = 3;
|
||||||
|
public $maxExceptions = 1;
|
||||||
|
public $failOnTimeout = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of seconds after which the job's unique lock will be released.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $uniqueFor = 3600;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the unique ID for the job.
|
||||||
|
*/
|
||||||
|
public function uniqueId(): string
|
||||||
|
{
|
||||||
|
return 'followers:v1:purge-by-domain:' . $this->pid . ':d-' . $this->domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the middleware the job should pass through.
|
||||||
|
*
|
||||||
|
* @return array<int, object>
|
||||||
|
*/
|
||||||
|
public function middleware(): array
|
||||||
|
{
|
||||||
|
return [(new WithoutOverlapping("followers:v1:purge-by-domain:{$this->pid}:d-{$this->domain}"))->shared()->dontRelease()];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*/
|
||||||
|
public function __construct($pid, $domain)
|
||||||
|
{
|
||||||
|
$this->pid = $pid;
|
||||||
|
$this->domain = $domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
if ($this->batch()->cancelled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pid = $this->pid;
|
||||||
|
$domain = $this->domain;
|
||||||
|
|
||||||
|
$query = 'SELECT f.*
|
||||||
|
FROM followers f
|
||||||
|
JOIN profiles p ON p.id = f.profile_id OR p.id = f.following_id
|
||||||
|
WHERE (f.profile_id = ? OR f.following_id = ?)
|
||||||
|
AND p.domain = ?;';
|
||||||
|
$params = [$pid, $pid, $domain];
|
||||||
|
|
||||||
|
foreach(DB::cursor($query, $params) as $n) {
|
||||||
|
if(!$n || !$n->id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$follower = Follower::find($n->id);
|
||||||
|
if($follower->following_id == $pid && $follower->profile_id) {
|
||||||
|
FollowerService::remove($follower->profile_id, $pid, true);
|
||||||
|
$follower->delete();
|
||||||
|
} else if ($follower->profile_id == $pid && $follower->following_id) {
|
||||||
|
FollowerService::remove($follower->following_id, $pid, true);
|
||||||
|
$follower->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$profile = Profile::find($pid);
|
||||||
|
|
||||||
|
$followerCount = DB::table('profiles')
|
||||||
|
->join('followers', 'profiles.id', '=', 'followers.following_id')
|
||||||
|
->where('followers.following_id', $pid)
|
||||||
|
->count();
|
||||||
|
|
||||||
|
$followingCount = DB::table('profiles')
|
||||||
|
->join('followers', 'profiles.id', '=', 'followers.following_id')
|
||||||
|
->where('followers.profile_id', $pid)
|
||||||
|
->count();
|
||||||
|
|
||||||
|
$profile->followers_count = $followerCount;
|
||||||
|
$profile->following_count = $followingCount;
|
||||||
|
$profile->save();
|
||||||
|
|
||||||
|
AccountService::del($profile->id);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\ProfilePipeline;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Batchable;
|
||||||
|
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 Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
|
||||||
|
use App\Notification;
|
||||||
|
use DB;
|
||||||
|
use App\Services\NotificationService;
|
||||||
|
|
||||||
|
class ProfilePurgeNotificationsByDomain implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
||||||
|
{
|
||||||
|
use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected $pid;
|
||||||
|
protected $domain;
|
||||||
|
|
||||||
|
public $timeout = 900;
|
||||||
|
public $tries = 3;
|
||||||
|
public $maxExceptions = 1;
|
||||||
|
public $failOnTimeout = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of seconds after which the job's unique lock will be released.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $uniqueFor = 3600;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the unique ID for the job.
|
||||||
|
*/
|
||||||
|
public function uniqueId(): string
|
||||||
|
{
|
||||||
|
return 'notify:v1:purge-by-domain:' . $this->pid . ':d-' . $this->domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the middleware the job should pass through.
|
||||||
|
*
|
||||||
|
* @return array<int, object>
|
||||||
|
*/
|
||||||
|
public function middleware(): array
|
||||||
|
{
|
||||||
|
return [(new WithoutOverlapping("notify:v1:purge-by-domain:{$this->pid}:d-{$this->domain}"))->shared()->dontRelease()];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*/
|
||||||
|
public function __construct($pid, $domain)
|
||||||
|
{
|
||||||
|
$this->pid = $pid;
|
||||||
|
$this->domain = $domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
if ($this->batch()->cancelled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pid = $this->pid;
|
||||||
|
$domain = $this->domain;
|
||||||
|
|
||||||
|
$query = 'SELECT notifications.*
|
||||||
|
FROM profiles
|
||||||
|
JOIN notifications on profiles.id = notifications.actor_id
|
||||||
|
WHERE notifications.profile_id = ?
|
||||||
|
AND profiles.domain = ?';
|
||||||
|
$params = [$pid, $domain];
|
||||||
|
|
||||||
|
foreach(DB::cursor($query, $params) as $n) {
|
||||||
|
if(!$n || !$n->id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Notification::where('id', $n->id)->delete();
|
||||||
|
NotificationService::del($pid, $n->id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -174,10 +174,7 @@ class RemoteStatusDelete implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
||||||
->whereObjectId($status->id)
|
->whereObjectId($status->id)
|
||||||
->delete();
|
->delete();
|
||||||
StatusArchived::whereStatusId($status->id)->delete();
|
StatusArchived::whereStatusId($status->id)->delete();
|
||||||
$statusHashtags = StatusHashtag::whereStatusId($status->id)->get();
|
StatusHashtag::whereStatusId($status->id)->deleteQuietly();
|
||||||
foreach($statusHashtags as $stag) {
|
|
||||||
$stag->delete();
|
|
||||||
}
|
|
||||||
StatusView::whereStatusId($status->id)->delete();
|
StatusView::whereStatusId($status->id)->delete();
|
||||||
Status::whereInReplyToId($status->id)->update(['in_reply_to_id' => null]);
|
Status::whereInReplyToId($status->id)->update(['in_reply_to_id' => null]);
|
||||||
|
|
||||||
|
|
|
@ -151,10 +151,7 @@ class StatusDelete implements ShouldQueue
|
||||||
->delete();
|
->delete();
|
||||||
|
|
||||||
StatusArchived::whereStatusId($status->id)->delete();
|
StatusArchived::whereStatusId($status->id)->delete();
|
||||||
$statusHashtags = StatusHashtag::whereStatusId($status->id)->get();
|
StatusHashtag::whereStatusId($status->id)->deleteQuietly();
|
||||||
foreach($statusHashtags as $stag) {
|
|
||||||
$stag->delete();
|
|
||||||
}
|
|
||||||
StatusView::whereStatusId($status->id)->delete();
|
StatusView::whereStatusId($status->id)->delete();
|
||||||
Status::whereInReplyToId($status->id)->update(['in_reply_to_id' => null]);
|
Status::whereInReplyToId($status->id)->update(['in_reply_to_id' => null]);
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ namespace App\Models;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use App\Services\AccountService;
|
use App\Services\AccountService;
|
||||||
|
use App\Profile;
|
||||||
|
|
||||||
class AdminShadowFilter extends Model
|
class AdminShadowFilter extends Model
|
||||||
{
|
{
|
||||||
|
@ -24,4 +25,9 @@ class AdminShadowFilter extends Model
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function profile()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Profile::class, 'item_id');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
13
app/Models/DefaultDomainBlock.php
Normal file
13
app/Models/DefaultDomainBlock.php
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class DefaultDomainBlock extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $guarded = [];
|
||||||
|
}
|
21
app/Models/UserDomainBlock.php
Normal file
21
app/Models/UserDomainBlock.php
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use App\Profile;
|
||||||
|
|
||||||
|
class UserDomainBlock extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $guarded = [];
|
||||||
|
|
||||||
|
public $timestamps = false;
|
||||||
|
|
||||||
|
public function profile()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Profile::class, 'profile_id');
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,90 +7,52 @@ use App\Follower;
|
||||||
use App\Profile;
|
use App\Profile;
|
||||||
use App\User;
|
use App\User;
|
||||||
use App\UserSetting;
|
use App\UserSetting;
|
||||||
|
use App\Services\UserFilterService;
|
||||||
|
use App\Models\DefaultDomainBlock;
|
||||||
|
use App\Models\UserDomainBlock;
|
||||||
use App\Jobs\FollowPipeline\FollowPipeline;
|
use App\Jobs\FollowPipeline\FollowPipeline;
|
||||||
use DB;
|
use DB;
|
||||||
use App\Services\FollowerService;
|
use App\Services\FollowerService;
|
||||||
|
|
||||||
class UserObserver
|
class UserObserver
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Listen to the User created event.
|
* Handle the notification "created" event.
|
||||||
*
|
*
|
||||||
* @param \App\User $user
|
* @param \App\User $user
|
||||||
*
|
* @return void
|
||||||
* @return void
|
*/
|
||||||
*/
|
public function created(User $user): void
|
||||||
public function saved(User $user)
|
{
|
||||||
{
|
$this->handleUser($user);
|
||||||
if($user->status == 'deleted') {
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(Profile::whereUsername($user->username)->exists()) {
|
/**
|
||||||
return;
|
* Listen to the User saved event.
|
||||||
|
*
|
||||||
|
* @param \App\User $user
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function saved(User $user)
|
||||||
|
{
|
||||||
|
$this->handleUser($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen to the User updated event.
|
||||||
|
*
|
||||||
|
* @param \App\User $user
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function updated(User $user): void
|
||||||
|
{
|
||||||
|
$this->handleUser($user);
|
||||||
|
if($user->profile) {
|
||||||
|
$this->applyDefaultDomainBlocks($user);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (empty($user->profile)) {
|
|
||||||
$profile = DB::transaction(function() use($user) {
|
|
||||||
$profile = new Profile();
|
|
||||||
$profile->user_id = $user->id;
|
|
||||||
$profile->username = $user->username;
|
|
||||||
$profile->name = $user->name;
|
|
||||||
$pkiConfig = [
|
|
||||||
'digest_alg' => 'sha512',
|
|
||||||
'private_key_bits' => 2048,
|
|
||||||
'private_key_type' => OPENSSL_KEYTYPE_RSA,
|
|
||||||
];
|
|
||||||
$pki = openssl_pkey_new($pkiConfig);
|
|
||||||
openssl_pkey_export($pki, $pki_private);
|
|
||||||
$pki_public = openssl_pkey_get_details($pki);
|
|
||||||
$pki_public = $pki_public['key'];
|
|
||||||
|
|
||||||
$profile->private_key = $pki_private;
|
|
||||||
$profile->public_key = $pki_public;
|
|
||||||
$profile->save();
|
|
||||||
return $profile;
|
|
||||||
});
|
|
||||||
|
|
||||||
DB::transaction(function() use($user, $profile) {
|
|
||||||
$user = User::findOrFail($user->id);
|
|
||||||
$user->profile_id = $profile->id;
|
|
||||||
$user->save();
|
|
||||||
|
|
||||||
CreateAvatar::dispatch($profile);
|
|
||||||
});
|
|
||||||
|
|
||||||
if(config_cache('account.autofollow') == true) {
|
|
||||||
$names = config_cache('account.autofollow_usernames');
|
|
||||||
$names = explode(',', $names);
|
|
||||||
|
|
||||||
if(!$names || !last($names)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$profiles = Profile::whereIn('username', $names)->get();
|
|
||||||
|
|
||||||
if($profiles) {
|
|
||||||
foreach($profiles as $p) {
|
|
||||||
$follower = new Follower;
|
|
||||||
$follower->profile_id = $profile->id;
|
|
||||||
$follower->following_id = $p->id;
|
|
||||||
$follower->save();
|
|
||||||
|
|
||||||
FollowPipeline::dispatch($follower);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($user->settings)) {
|
|
||||||
DB::transaction(function() use($user) {
|
|
||||||
UserSetting::firstOrCreate([
|
|
||||||
'user_id' => $user->id
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle the user "deleted" event.
|
* Handle the user "deleted" event.
|
||||||
|
@ -102,4 +64,97 @@ class UserObserver
|
||||||
{
|
{
|
||||||
FollowerService::delCache($user->profile_id);
|
FollowerService::delCache($user->profile_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function handleUser($user)
|
||||||
|
{
|
||||||
|
if(in_array($user->status, ['deleted', 'delete'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Profile::whereUsername($user->username)->exists()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($user->profile)) {
|
||||||
|
$profile = DB::transaction(function() use($user) {
|
||||||
|
$profile = new Profile();
|
||||||
|
$profile->user_id = $user->id;
|
||||||
|
$profile->username = $user->username;
|
||||||
|
$profile->name = $user->name;
|
||||||
|
$pkiConfig = [
|
||||||
|
'digest_alg' => 'sha512',
|
||||||
|
'private_key_bits' => 2048,
|
||||||
|
'private_key_type' => OPENSSL_KEYTYPE_RSA,
|
||||||
|
];
|
||||||
|
$pki = openssl_pkey_new($pkiConfig);
|
||||||
|
openssl_pkey_export($pki, $pki_private);
|
||||||
|
$pki_public = openssl_pkey_get_details($pki);
|
||||||
|
$pki_public = $pki_public['key'];
|
||||||
|
|
||||||
|
$profile->private_key = $pki_private;
|
||||||
|
$profile->public_key = $pki_public;
|
||||||
|
$profile->save();
|
||||||
|
$this->applyDefaultDomainBlocks($user);
|
||||||
|
return $profile;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
DB::transaction(function() use($user, $profile) {
|
||||||
|
$user = User::findOrFail($user->id);
|
||||||
|
$user->profile_id = $profile->id;
|
||||||
|
$user->save();
|
||||||
|
|
||||||
|
CreateAvatar::dispatch($profile);
|
||||||
|
});
|
||||||
|
|
||||||
|
if(config_cache('account.autofollow') == true) {
|
||||||
|
$names = config_cache('account.autofollow_usernames');
|
||||||
|
$names = explode(',', $names);
|
||||||
|
|
||||||
|
if(!$names || !last($names)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$profiles = Profile::whereIn('username', $names)->get();
|
||||||
|
|
||||||
|
if($profiles) {
|
||||||
|
foreach($profiles as $p) {
|
||||||
|
$follower = new Follower;
|
||||||
|
$follower->profile_id = $profile->id;
|
||||||
|
$follower->following_id = $p->id;
|
||||||
|
$follower->save();
|
||||||
|
|
||||||
|
FollowPipeline::dispatch($follower);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($user->settings)) {
|
||||||
|
DB::transaction(function() use($user) {
|
||||||
|
UserSetting::firstOrCreate([
|
||||||
|
'user_id' => $user->id
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function applyDefaultDomainBlocks($user)
|
||||||
|
{
|
||||||
|
if($user->profile_id == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$defaultDomainBlocks = DefaultDomainBlock::pluck('domain')->toArray();
|
||||||
|
|
||||||
|
if(!$defaultDomainBlocks || !count($defaultDomainBlocks)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach($defaultDomainBlocks as $domain) {
|
||||||
|
UserDomainBlock::updateOrCreate([
|
||||||
|
'profile_id' => $user->profile_id,
|
||||||
|
'domain' => strtolower(trim($domain))
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ use App\Profile;
|
||||||
use App\Status;
|
use App\Status;
|
||||||
use App\User;
|
use App\User;
|
||||||
use App\UserSetting;
|
use App\UserSetting;
|
||||||
|
use App\Models\UserDomainBlock;
|
||||||
use App\Transformer\Api\AccountTransformer;
|
use App\Transformer\Api\AccountTransformer;
|
||||||
use League\Fractal;
|
use League\Fractal;
|
||||||
use League\Fractal\Serializer\ArraySerializer;
|
use League\Fractal\Serializer\ArraySerializer;
|
||||||
|
@ -234,4 +235,13 @@ class AccountService
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function blocksDomain($pid, $domain = false)
|
||||||
|
{
|
||||||
|
if(!$domain) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return UserDomainBlock::whereProfileId($pid)->whereDomain($domain)->exists();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,16 +35,18 @@ class FollowerService
|
||||||
Cache::forget('profile:following:' . $actor);
|
Cache::forget('profile:following:' . $actor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function remove($actor, $target)
|
public static function remove($actor, $target, $silent = false)
|
||||||
{
|
{
|
||||||
Redis::zrem(self::FOLLOWING_KEY . $actor, $target);
|
Redis::zrem(self::FOLLOWING_KEY . $actor, $target);
|
||||||
Redis::zrem(self::FOLLOWERS_KEY . $target, $actor);
|
Redis::zrem(self::FOLLOWERS_KEY . $target, $actor);
|
||||||
Cache::forget('pf:services:follower:audience:' . $actor);
|
if($silent !== true) {
|
||||||
Cache::forget('pf:services:follower:audience:' . $target);
|
AccountService::del($actor);
|
||||||
AccountService::del($actor);
|
AccountService::del($target);
|
||||||
AccountService::del($target);
|
RelationshipService::refresh($actor, $target);
|
||||||
RelationshipService::refresh($actor, $target);
|
Cache::forget('profile:following:' . $actor);
|
||||||
Cache::forget('profile:following:' . $actor);
|
} else {
|
||||||
|
RelationshipService::forget($actor, $target);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function followers($id, $start = 0, $stop = 10)
|
public static function followers($id, $start = 0, $stop = 10)
|
||||||
|
@ -89,12 +91,16 @@ class FollowerService
|
||||||
return Redis::zCard(self::FOLLOWING_KEY . $id);
|
return Redis::zCard(self::FOLLOWING_KEY . $id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function follows(string $actor, string $target)
|
public static function follows(string $actor, string $target, $quickCheck = false)
|
||||||
{
|
{
|
||||||
if($actor == $target) {
|
if($actor == $target) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if($quickCheck) {
|
||||||
|
return (bool) Redis::zScore(self::FOLLOWERS_KEY . $target, $actor);
|
||||||
|
}
|
||||||
|
|
||||||
if(self::followerCount($target, false) && self::followingCount($actor, false)) {
|
if(self::followerCount($target, false) && self::followingCount($actor, false)) {
|
||||||
self::cacheSyncCheck($target, 'followers');
|
self::cacheSyncCheck($target, 'followers');
|
||||||
return (bool) Redis::zScore(self::FOLLOWERS_KEY . $target, $actor);
|
return (bool) Redis::zScore(self::FOLLOWERS_KEY . $target, $actor);
|
||||||
|
|
|
@ -6,6 +6,7 @@ use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Facades\Redis;
|
use Illuminate\Support\Facades\Redis;
|
||||||
use App\Follower;
|
use App\Follower;
|
||||||
use App\Status;
|
use App\Status;
|
||||||
|
use App\Models\UserDomainBlock;
|
||||||
|
|
||||||
class HomeTimelineService
|
class HomeTimelineService
|
||||||
{
|
{
|
||||||
|
@ -81,6 +82,8 @@ class HomeTimelineService
|
||||||
$following = array_diff($following, $filters);
|
$following = array_diff($following, $filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$domainBlocks = UserDomainBlock::whereProfileId($id)->pluck('domain')->toArray();
|
||||||
|
|
||||||
$ids = Status::where('id', '>', $minId)
|
$ids = Status::where('id', '>', $minId)
|
||||||
->whereIn('profile_id', $following)
|
->whereIn('profile_id', $following)
|
||||||
->whereNull(['in_reply_to_id', 'reblog_of_id'])
|
->whereNull(['in_reply_to_id', 'reblog_of_id'])
|
||||||
|
@ -91,6 +94,16 @@ class HomeTimelineService
|
||||||
->pluck('id');
|
->pluck('id');
|
||||||
|
|
||||||
foreach($ids as $pid) {
|
foreach($ids as $pid) {
|
||||||
|
$status = StatusService::get($pid, false);
|
||||||
|
if(!$status || !isset($status['account'], $status['url'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if($domainBlocks && count($domainBlocks)) {
|
||||||
|
$domain = strtolower(parse_url($status['url'], PHP_URL_HOST));
|
||||||
|
if(in_array($domain, $domainBlocks)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
self::add($id, $pid);
|
self::add($id, $pid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ class MarkerService
|
||||||
return Cache::get(self::CACHE_KEY . $timeline . ':' . $profileId);
|
return Cache::get(self::CACHE_KEY . $timeline . ':' . $profileId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function set($profileId, $timeline = 'home', $entityId)
|
public static function set($profileId, $timeline = 'home', $entityId = false)
|
||||||
{
|
{
|
||||||
$existing = self::get($profileId, $timeline);
|
$existing = self::get($profileId, $timeline);
|
||||||
$key = self::CACHE_KEY . $timeline . ':' . $profileId;
|
$key = self::CACHE_KEY . $timeline . ':' . $profileId;
|
||||||
|
|
|
@ -95,7 +95,15 @@ class SearchApiV2Service
|
||||||
if(substr($webfingerQuery, 0, 1) !== '@') {
|
if(substr($webfingerQuery, 0, 1) !== '@') {
|
||||||
$webfingerQuery = '@' . $webfingerQuery;
|
$webfingerQuery = '@' . $webfingerQuery;
|
||||||
}
|
}
|
||||||
$banned = InstanceService::getBannedDomains();
|
$banned = InstanceService::getBannedDomains() ?? [];
|
||||||
|
$domainBlocks = UserFilterService::domainBlocks($user->profile_id);
|
||||||
|
if($domainBlocks && count($domainBlocks)) {
|
||||||
|
$banned = array_unique(
|
||||||
|
array_values(
|
||||||
|
array_merge($banned, $domainBlocks)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
$operator = config('database.default') === 'pgsql' ? 'ilike' : 'like';
|
$operator = config('database.default') === 'pgsql' ? 'ilike' : 'like';
|
||||||
$results = Profile::select('username', 'id', 'followers_count', 'domain')
|
$results = Profile::select('username', 'id', 'followers_count', 'domain')
|
||||||
->where('username', $operator, $query)
|
->where('username', $operator, $query)
|
||||||
|
@ -172,8 +180,18 @@ class SearchApiV2Service
|
||||||
'hashtags' => [],
|
'hashtags' => [],
|
||||||
'statuses' => [],
|
'statuses' => [],
|
||||||
];
|
];
|
||||||
|
$user = request()->user();
|
||||||
$mastodonMode = self::$mastodonMode;
|
$mastodonMode = self::$mastodonMode;
|
||||||
$query = urldecode($this->query->input('q'));
|
$query = urldecode($this->query->input('q'));
|
||||||
|
$banned = InstanceService::getBannedDomains();
|
||||||
|
$domainBlocks = UserFilterService::domainBlocks($user->profile_id);
|
||||||
|
if($domainBlocks && count($domainBlocks)) {
|
||||||
|
$banned = array_unique(
|
||||||
|
array_values(
|
||||||
|
array_merge($banned, $domainBlocks)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
if(substr($query, 0, 1) === '@' && !Str::contains($query, '.')) {
|
if(substr($query, 0, 1) === '@' && !Str::contains($query, '.')) {
|
||||||
$default['accounts'] = $this->accounts(substr($query, 1));
|
$default['accounts'] = $this->accounts(substr($query, 1));
|
||||||
return $default;
|
return $default;
|
||||||
|
@ -197,7 +215,11 @@ class SearchApiV2Service
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return $default;
|
return $default;
|
||||||
}
|
}
|
||||||
if($res && isset($res['id'])) {
|
if($res && isset($res['id'], $res['url'])) {
|
||||||
|
$domain = strtolower(parse_url($res['url'], PHP_URL_HOST));
|
||||||
|
if(in_array($domain, $banned)) {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
$default['accounts'][] = $res;
|
$default['accounts'][] = $res;
|
||||||
return $default;
|
return $default;
|
||||||
} else {
|
} else {
|
||||||
|
@ -212,6 +234,10 @@ class SearchApiV2Service
|
||||||
return $default;
|
return $default;
|
||||||
}
|
}
|
||||||
if($res && isset($res['id'])) {
|
if($res && isset($res['id'])) {
|
||||||
|
$domain = strtolower(parse_url($res['url'], PHP_URL_HOST));
|
||||||
|
if(in_array($domain, $banned)) {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
$default['accounts'][] = $res;
|
$default['accounts'][] = $res;
|
||||||
return $default;
|
return $default;
|
||||||
} else {
|
} else {
|
||||||
|
@ -221,6 +247,9 @@ class SearchApiV2Service
|
||||||
|
|
||||||
if($sid = Status::whereUri($query)->first()) {
|
if($sid = Status::whereUri($query)->first()) {
|
||||||
$s = StatusService::get($sid->id, false);
|
$s = StatusService::get($sid->id, false);
|
||||||
|
if(!$s) {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
if(in_array($s['visibility'], ['public', 'unlisted'])) {
|
if(in_array($s['visibility'], ['public', 'unlisted'])) {
|
||||||
$default['statuses'][] = $s;
|
$default['statuses'][] = $s;
|
||||||
return $default;
|
return $default;
|
||||||
|
@ -229,7 +258,7 @@ class SearchApiV2Service
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$res = ActivityPubFetchService::get($query);
|
$res = ActivityPubFetchService::get($query);
|
||||||
$banned = InstanceService::getBannedDomains();
|
|
||||||
if($res) {
|
if($res) {
|
||||||
$json = json_decode($res, true);
|
$json = json_decode($res, true);
|
||||||
|
|
||||||
|
|
|
@ -4,145 +4,160 @@ namespace App\Services;
|
||||||
|
|
||||||
use Cache;
|
use Cache;
|
||||||
use App\UserFilter;
|
use App\UserFilter;
|
||||||
|
use App\Models\UserDomainBlock;
|
||||||
use Illuminate\Support\Facades\Redis;
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
|
||||||
class UserFilterService
|
class UserFilterService
|
||||||
{
|
{
|
||||||
const USER_MUTES_KEY = 'pf:services:mutes:ids:';
|
const USER_MUTES_KEY = 'pf:services:mutes:ids:';
|
||||||
const USER_BLOCKS_KEY = 'pf:services:blocks:ids:';
|
const USER_BLOCKS_KEY = 'pf:services:blocks:ids:';
|
||||||
|
const USER_DOMAIN_KEY = 'pf:services:domain-blocks:ids:';
|
||||||
|
|
||||||
public static function mutes(int $profile_id)
|
public static function mutes(int $profile_id)
|
||||||
{
|
{
|
||||||
$key = self::USER_MUTES_KEY . $profile_id;
|
$key = self::USER_MUTES_KEY . $profile_id;
|
||||||
$warm = Cache::has($key . ':cached-v0');
|
$warm = Cache::has($key . ':cached-v0');
|
||||||
if($warm) {
|
if($warm) {
|
||||||
return Redis::zrevrange($key, 0, -1) ?? [];
|
return Redis::zrevrange($key, 0, -1) ?? [];
|
||||||
} else {
|
} else {
|
||||||
if(Redis::zrevrange($key, 0, -1)) {
|
if(Redis::zrevrange($key, 0, -1)) {
|
||||||
return Redis::zrevrange($key, 0, -1);
|
return Redis::zrevrange($key, 0, -1);
|
||||||
}
|
}
|
||||||
$ids = UserFilter::whereFilterType('mute')
|
$ids = UserFilter::whereFilterType('mute')
|
||||||
->whereUserId($profile_id)
|
->whereUserId($profile_id)
|
||||||
->pluck('filterable_id')
|
->pluck('filterable_id')
|
||||||
->map(function($id) {
|
->map(function($id) {
|
||||||
$acct = AccountService::get($id, true);
|
$acct = AccountService::get($id, true);
|
||||||
if(!$acct) {
|
if(!$acct) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return $acct['id'];
|
return $acct['id'];
|
||||||
})
|
})
|
||||||
->filter(function($res) {
|
->filter(function($res) {
|
||||||
return $res;
|
return $res;
|
||||||
})
|
})
|
||||||
->values()
|
->values()
|
||||||
->toArray();
|
->toArray();
|
||||||
foreach ($ids as $muted_id) {
|
foreach ($ids as $muted_id) {
|
||||||
Redis::zadd($key, (int) $muted_id, (int) $muted_id);
|
Redis::zadd($key, (int) $muted_id, (int) $muted_id);
|
||||||
}
|
}
|
||||||
Cache::set($key . ':cached-v0', 1, 7776000);
|
Cache::set($key . ':cached-v0', 1, 7776000);
|
||||||
return $ids;
|
return $ids;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function blocks(int $profile_id)
|
public static function blocks(int $profile_id)
|
||||||
{
|
{
|
||||||
$key = self::USER_BLOCKS_KEY . $profile_id;
|
$key = self::USER_BLOCKS_KEY . $profile_id;
|
||||||
$warm = Cache::has($key . ':cached-v0');
|
$warm = Cache::has($key . ':cached-v0');
|
||||||
if($warm) {
|
if($warm) {
|
||||||
return Redis::zrevrange($key, 0, -1) ?? [];
|
return Redis::zrevrange($key, 0, -1) ?? [];
|
||||||
} else {
|
} else {
|
||||||
if(Redis::zrevrange($key, 0, -1)) {
|
if(Redis::zrevrange($key, 0, -1)) {
|
||||||
return Redis::zrevrange($key, 0, -1);
|
return Redis::zrevrange($key, 0, -1);
|
||||||
}
|
}
|
||||||
$ids = UserFilter::whereFilterType('block')
|
$ids = UserFilter::whereFilterType('block')
|
||||||
->whereUserId($profile_id)
|
->whereUserId($profile_id)
|
||||||
->pluck('filterable_id')
|
->pluck('filterable_id')
|
||||||
->map(function($id) {
|
->map(function($id) {
|
||||||
$acct = AccountService::get($id, true);
|
$acct = AccountService::get($id, true);
|
||||||
if(!$acct) {
|
if(!$acct) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return $acct['id'];
|
return $acct['id'];
|
||||||
})
|
})
|
||||||
->filter(function($res) {
|
->filter(function($res) {
|
||||||
return $res;
|
return $res;
|
||||||
})
|
})
|
||||||
->values()
|
->values()
|
||||||
->toArray();
|
->toArray();
|
||||||
foreach ($ids as $blocked_id) {
|
foreach ($ids as $blocked_id) {
|
||||||
Redis::zadd($key, (int) $blocked_id, (int) $blocked_id);
|
Redis::zadd($key, (int) $blocked_id, (int) $blocked_id);
|
||||||
}
|
}
|
||||||
Cache::set($key . ':cached-v0', 1, 7776000);
|
Cache::set($key . ':cached-v0', 1, 7776000);
|
||||||
return $ids;
|
return $ids;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function filters(int $profile_id)
|
public static function filters(int $profile_id)
|
||||||
{
|
{
|
||||||
return array_unique(array_merge(self::mutes($profile_id), self::blocks($profile_id)));
|
return array_unique(array_merge(self::mutes($profile_id), self::blocks($profile_id)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function mute(int $profile_id, int $muted_id)
|
public static function mute(int $profile_id, int $muted_id)
|
||||||
{
|
{
|
||||||
if($profile_id == $muted_id) {
|
if($profile_id == $muted_id) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$key = self::USER_MUTES_KEY . $profile_id;
|
$key = self::USER_MUTES_KEY . $profile_id;
|
||||||
$mutes = self::mutes($profile_id);
|
$mutes = self::mutes($profile_id);
|
||||||
$exists = in_array($muted_id, $mutes);
|
$exists = in_array($muted_id, $mutes);
|
||||||
if(!$exists) {
|
if(!$exists) {
|
||||||
Redis::zadd($key, $muted_id, $muted_id);
|
Redis::zadd($key, $muted_id, $muted_id);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function unmute(int $profile_id, string $muted_id)
|
public static function unmute(int $profile_id, string $muted_id)
|
||||||
{
|
{
|
||||||
if($profile_id == $muted_id) {
|
if($profile_id == $muted_id) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$key = self::USER_MUTES_KEY . $profile_id;
|
$key = self::USER_MUTES_KEY . $profile_id;
|
||||||
$mutes = self::mutes($profile_id);
|
$mutes = self::mutes($profile_id);
|
||||||
$exists = in_array($muted_id, $mutes);
|
$exists = in_array($muted_id, $mutes);
|
||||||
if($exists) {
|
if($exists) {
|
||||||
Redis::zrem($key, $muted_id);
|
Redis::zrem($key, $muted_id);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function block(int $profile_id, int $blocked_id)
|
public static function block(int $profile_id, int $blocked_id)
|
||||||
{
|
{
|
||||||
if($profile_id == $blocked_id) {
|
if($profile_id == $blocked_id) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$key = self::USER_BLOCKS_KEY . $profile_id;
|
$key = self::USER_BLOCKS_KEY . $profile_id;
|
||||||
$exists = in_array($blocked_id, self::blocks($profile_id));
|
$exists = in_array($blocked_id, self::blocks($profile_id));
|
||||||
if(!$exists) {
|
if(!$exists) {
|
||||||
Redis::zadd($key, $blocked_id, $blocked_id);
|
Redis::zadd($key, $blocked_id, $blocked_id);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function unblock(int $profile_id, string $blocked_id)
|
public static function unblock(int $profile_id, string $blocked_id)
|
||||||
{
|
{
|
||||||
if($profile_id == $blocked_id) {
|
if($profile_id == $blocked_id) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$key = self::USER_BLOCKS_KEY . $profile_id;
|
$key = self::USER_BLOCKS_KEY . $profile_id;
|
||||||
$exists = in_array($blocked_id, self::blocks($profile_id));
|
$exists = in_array($blocked_id, self::blocks($profile_id));
|
||||||
if($exists) {
|
if($exists) {
|
||||||
Redis::zrem($key, $blocked_id);
|
Redis::zrem($key, $blocked_id);
|
||||||
}
|
}
|
||||||
return $exists;
|
return $exists;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function blockCount(int $profile_id)
|
public static function blockCount(int $profile_id)
|
||||||
{
|
{
|
||||||
return Redis::zcard(self::USER_BLOCKS_KEY . $profile_id);
|
return Redis::zcard(self::USER_BLOCKS_KEY . $profile_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function muteCount(int $profile_id)
|
public static function muteCount(int $profile_id)
|
||||||
{
|
{
|
||||||
return Redis::zcard(self::USER_MUTES_KEY . $profile_id);
|
return Redis::zcard(self::USER_MUTES_KEY . $profile_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function domainBlocks($pid, $purge = false)
|
||||||
|
{
|
||||||
|
if($purge) {
|
||||||
|
Cache::forget(self::USER_DOMAIN_KEY . $pid);
|
||||||
|
}
|
||||||
|
return Cache::remember(
|
||||||
|
self::USER_DOMAIN_KEY . $pid,
|
||||||
|
21600,
|
||||||
|
function() use($pid) {
|
||||||
|
return UserDomainBlock::whereProfileId($pid)->pluck('domain')->toArray();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,4 +33,9 @@ class UserFilter extends Model
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Instance::class, 'filterable_id');
|
return $this->belongsTo(Instance::class, 'filterable_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function user()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Profile::class, 'user_id');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ use App\Util\ActivityPub\Validator\Like as LikeValidator;
|
||||||
use App\Util\ActivityPub\Validator\UndoFollow as UndoFollowValidator;
|
use App\Util\ActivityPub\Validator\UndoFollow as UndoFollowValidator;
|
||||||
use App\Util\ActivityPub\Validator\UpdatePersonValidator;
|
use App\Util\ActivityPub\Validator\UpdatePersonValidator;
|
||||||
|
|
||||||
|
use App\Services\AccountService;
|
||||||
use App\Services\PollService;
|
use App\Services\PollService;
|
||||||
use App\Services\FollowerService;
|
use App\Services\FollowerService;
|
||||||
use App\Services\ReblogService;
|
use App\Services\ReblogService;
|
||||||
|
@ -372,7 +373,11 @@ class Inbox
|
||||||
->whereUsername(array_last(explode('/', $activity['to'][0])))
|
->whereUsername(array_last(explode('/', $activity['to'][0])))
|
||||||
->firstOrFail();
|
->firstOrFail();
|
||||||
|
|
||||||
if(in_array($actor->id, $profile->blockedIds()->toArray())) {
|
if(!$actor || in_array($actor->id, $profile->blockedIds()->toArray())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(AccountService::blocksDomain($profile->id, $actor->domain) == true) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -510,6 +515,10 @@ class Inbox
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(AccountService::blocksDomain($target->id, $actor->domain) == true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(
|
if(
|
||||||
Follower::whereProfileId($actor->id)
|
Follower::whereProfileId($actor->id)
|
||||||
->whereFollowingId($target->id)
|
->whereFollowingId($target->id)
|
||||||
|
@ -581,6 +590,10 @@ class Inbox
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(AccountService::blocksDomain($parent->profile_id, $actor->domain) == true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$blocks = UserFilterService::blocks($parent->profile_id);
|
$blocks = UserFilterService::blocks($parent->profile_id);
|
||||||
if($blocks && in_array($actor->id, $blocks)) {
|
if($blocks && in_array($actor->id, $blocks)) {
|
||||||
return;
|
return;
|
||||||
|
@ -634,6 +647,10 @@ class Inbox
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(AccountService::blocksDomain($target->id, $actor->domain) == true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$request = FollowRequest::whereFollowerId($actor->id)
|
$request = FollowRequest::whereFollowerId($actor->id)
|
||||||
->whereFollowingId($target->id)
|
->whereFollowingId($target->id)
|
||||||
->whereIsRejected(false)
|
->whereIsRejected(false)
|
||||||
|
@ -759,6 +776,10 @@ class Inbox
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(AccountService::blocksDomain($status->profile_id, $profile->domain) == true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$blocks = UserFilterService::blocks($status->profile_id);
|
$blocks = UserFilterService::blocks($status->profile_id);
|
||||||
if($blocks && in_array($profile->id, $blocks)) {
|
if($blocks && in_array($profile->id, $blocks)) {
|
||||||
return;
|
return;
|
||||||
|
@ -816,6 +837,9 @@ class Inbox
|
||||||
if(!$status) {
|
if(!$status) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if(AccountService::blocksDomain($status->profile_id, $profile->domain) == true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
FeedRemoveRemotePipeline::dispatch($status->id, $status->profile_id)->onQueue('feed');
|
FeedRemoveRemotePipeline::dispatch($status->id, $status->profile_id)->onQueue('feed');
|
||||||
Status::whereProfileId($profile->id)
|
Status::whereProfileId($profile->id)
|
||||||
->whereReblogOfId($status->id)
|
->whereReblogOfId($status->id)
|
||||||
|
@ -837,6 +861,9 @@ class Inbox
|
||||||
if(!$following) {
|
if(!$following) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if(AccountService::blocksDomain($following->id, $profile->domain) == true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Follower::whereProfileId($profile->id)
|
Follower::whereProfileId($profile->id)
|
||||||
->whereFollowingId($following->id)
|
->whereFollowingId($following->id)
|
||||||
->delete();
|
->delete();
|
||||||
|
@ -862,6 +889,9 @@ class Inbox
|
||||||
if(!$status) {
|
if(!$status) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if(AccountService::blocksDomain($status->profile_id, $profile->domain) == true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Like::whereProfileId($profile->id)
|
Like::whereProfileId($profile->id)
|
||||||
->whereStatusId($status->id)
|
->whereStatusId($status->id)
|
||||||
->forceDelete();
|
->forceDelete();
|
||||||
|
@ -915,6 +945,10 @@ class Inbox
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(AccountService::blocksDomain($story->profile_id, $profile->domain) == true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(!FollowerService::follows($profile->id, $story->profile_id)) {
|
if(!FollowerService::follows($profile->id, $story->profile_id)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -985,6 +1019,10 @@ class Inbox
|
||||||
|
|
||||||
$actorProfile = Helpers::profileFetch($actor);
|
$actorProfile = Helpers::profileFetch($actor);
|
||||||
|
|
||||||
|
if(AccountService::blocksDomain($targetProfile->id, $actorProfile->domain) == true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(!FollowerService::follows($actorProfile->id, $targetProfile->id)) {
|
if(!FollowerService::follows($actorProfile->id, $targetProfile->id)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1103,6 +1141,11 @@ class Inbox
|
||||||
|
|
||||||
$actorProfile = Helpers::profileFetch($actor);
|
$actorProfile = Helpers::profileFetch($actor);
|
||||||
|
|
||||||
|
|
||||||
|
if(AccountService::blocksDomain($targetProfile->id, $actorProfile->domain) == true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(!FollowerService::follows($actorProfile->id, $targetProfile->id)) {
|
if(!FollowerService::follows($actorProfile->id, $targetProfile->id)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,7 +110,8 @@ return [
|
||||||
|
|
||||||
'user_filters' => [
|
'user_filters' => [
|
||||||
'max_user_blocks' => env('PF_MAX_USER_BLOCKS', 50),
|
'max_user_blocks' => env('PF_MAX_USER_BLOCKS', 50),
|
||||||
'max_user_mutes' => env('PF_MAX_USER_MUTES', 50)
|
'max_user_mutes' => env('PF_MAX_USER_MUTES', 50),
|
||||||
|
'max_domain_blocks' => env('PF_MAX_DOMAIN_BLOCKS', 50),
|
||||||
],
|
],
|
||||||
|
|
||||||
'reports' => [
|
'reports' => [
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('user_domain_blocks', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->unsignedBigInteger('profile_id')->index();
|
||||||
|
$table->string('domain')->index();
|
||||||
|
$table->unique(['profile_id', 'domain'], 'user_domain_blocks_by_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('user_domain_blocks');
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('job_batches', function (Blueprint $table) {
|
||||||
|
$table->string('id')->primary();
|
||||||
|
$table->string('name');
|
||||||
|
$table->integer('total_jobs');
|
||||||
|
$table->integer('pending_jobs');
|
||||||
|
$table->integer('failed_jobs');
|
||||||
|
$table->longText('failed_job_ids');
|
||||||
|
$table->mediumText('options')->nullable();
|
||||||
|
$table->integer('cancelled_at')->nullable();
|
||||||
|
$table->integer('created_at');
|
||||||
|
$table->integer('finished_at')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('job_batches');
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use App\StatusHashtag;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
StatusHashtag::doesntHave('status')->lazyById(200)->each->deleteQuietly();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('default_domain_blocks', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('domain')->unique()->index();
|
||||||
|
$table->text('note')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('default_domain_blocks');
|
||||||
|
}
|
||||||
|
};
|
|
@ -12,4 +12,10 @@ return [
|
||||||
|
|
||||||
'status.disabled.header' => 'Profile Unavailable',
|
'status.disabled.header' => 'Profile Unavailable',
|
||||||
'status.disabled.body' => 'Sorry, this profile is not available at the moment. Please try again shortly.',
|
'status.disabled.body' => 'Sorry, this profile is not available at the moment. Please try again shortly.',
|
||||||
|
|
||||||
|
'block.domain.max' => 'Max limit of domain blocks reached! You can only block :max domains at a time. Ask your admin to adjust this limit.',
|
||||||
|
|
||||||
|
'mutedAccounts' => 'Muted Accounts',
|
||||||
|
'blockedAccounts' => 'Blocked Accounts',
|
||||||
|
'blockedDomains' => 'Blocked Domains',
|
||||||
];
|
];
|
||||||
|
|
|
@ -8,8 +8,9 @@
|
||||||
<hr>
|
<hr>
|
||||||
<div class="form-group pb-1">
|
<div class="form-group pb-1">
|
||||||
<p>
|
<p>
|
||||||
<a class="btn btn-outline-secondary py-0 font-weight-bold" href="{{route('settings.privacy.muted-users')}}">Muted Users</a>
|
<a class="btn btn-link py-0 font-weight-bold" href="{{route('settings.privacy.muted-users')}}">{{ __('profile.mutedAccounts') }}</a>
|
||||||
<a class="btn btn-outline-secondary py-0 font-weight-bold" href="{{route('settings.privacy.blocked-users')}}">Blocked Users</a>
|
<a class="btn btn-link py-0 font-weight-bold" href="{{route('settings.privacy.blocked-users')}}">{{ __('profile.blockedAccounts') }}</a>
|
||||||
|
<a class="btn btn-link py-0 font-weight-bold" href="{{route('settings.privacy.domain-blocks')}}">{{ __('profile.blockedDomains') }}</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<form method="post">
|
<form method="post">
|
||||||
|
|
|
@ -2,40 +2,36 @@
|
||||||
|
|
||||||
@section('section')
|
@section('section')
|
||||||
|
|
||||||
<div class="title">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<h3 class="font-weight-bold">Blocked Users</h3>
|
<div class="title d-flex align-items-center" style="gap: 1rem;">
|
||||||
</div>
|
<p class="mb-0"><a href="/settings/privacy"><i class="far fa-chevron-left fa-lg"></i></a></p>
|
||||||
<hr>
|
<h3 class="font-weight-bold mb-0">Blocked Accounts</h3>
|
||||||
<div class="form-group pb-1">
|
</div>
|
||||||
<p>
|
</div>
|
||||||
<a class="btn btn-outline-secondary py-0 font-weight-bold" href="{{route('settings.privacy.muted-users')}}">Muted Users</a>
|
<hr />
|
||||||
<a class="btn btn-outline-primary py-0 font-weight-bold" href="{{route('settings.privacy.blocked-users')}}">Blocked Users</a>
|
|
||||||
{{-- <a class="btn btn-outline-secondary py-0 font-weight-bold" href="{{route('settings.privacy.blocked-keywords')}}">Blocked keywords</a>
|
|
||||||
<a class="btn btn-outline-secondary py-0 font-weight-bold" href="{{route('settings.privacy.blocked-instances')}}">Blocked instances</a> --}}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
@if($users->count() > 0)
|
|
||||||
<ul class="list-group list-group-flush">
|
|
||||||
@foreach($users as $user)
|
|
||||||
<li class="list-group-item">
|
|
||||||
<div class="d-flex justify-content-between align-items-center font-weight-bold">
|
|
||||||
<span><a href="{{$user->url()}}" class="text-decoration-none text-dark"><img class="rounded-circle mr-3" src="{{$user->avatarUrl()}}" width="32px">{{$user->username}}</a></span>
|
|
||||||
<span class="btn-group">
|
|
||||||
<form method="post">
|
|
||||||
@csrf
|
|
||||||
<input type="hidden" name="profile_id" value="{{$user->id}}">
|
|
||||||
<button type="submit" class="btn btn-outline-secondary btn-sm px-3 font-weight-bold">Unblock</button>
|
|
||||||
</form>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
@endforeach
|
|
||||||
</ul>
|
|
||||||
<div class="d-flex justify-content-center mt-3 font-weight-bold">
|
|
||||||
{{$users->links()}}
|
|
||||||
</div>
|
|
||||||
@else
|
|
||||||
<p class="lead">You are not blocking any accounts.</p>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
@endsection
|
@if($users->count() > 0)
|
||||||
|
<div class="list-group">
|
||||||
|
@foreach($users as $user)
|
||||||
|
<div class="list-group-item">
|
||||||
|
<div class="d-flex justify-content-between align-items-center font-weight-bold">
|
||||||
|
<span><a href="{{$user->url()}}" class="text-decoration-none text-dark"><img class="rounded-circle mr-3" src="{{$user->avatarUrl()}}" width="32px" onerror="this.onerror=null;this.src='/storage/avatars/default.jpg?v=0';">{{$user->username}}</a></span>
|
||||||
|
<span class="btn-group">
|
||||||
|
<form method="post">
|
||||||
|
@csrf
|
||||||
|
<input type="hidden" name="profile_id" value="{{$user->id}}">
|
||||||
|
<button type="submit" class="btn btn-link btn-sm px-3 font-weight-bold">Unblock</button>
|
||||||
|
</form>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-center mt-3 font-weight-bold">
|
||||||
|
{{$users->links()}}
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<p class="lead text-center font-weight-bold">You are not blocking any accounts.</p>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@endsection
|
||||||
|
|
272
resources/views/settings/privacy/domain-blocks.blade.php
Normal file
272
resources/views/settings/privacy/domain-blocks.blade.php
Normal file
|
@ -0,0 +1,272 @@
|
||||||
|
@extends('settings.template-vue')
|
||||||
|
|
||||||
|
@section('section')
|
||||||
|
<div>
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div class="title d-flex align-items-center" style="gap: 1rem;">
|
||||||
|
<p class="mb-0"><a href="/settings/privacy"><i class="far fa-chevron-left fa-lg"></i></a></p>
|
||||||
|
<h3 class="font-weight-bold mb-0">Domain Blocks</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="mt-3 mb-n2 small">You can block entire domains, this prevents users on that instance from interacting with your content and from you seeing content from that domain on public feeds.</p>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<div v-if="!loaded" class="d-flex justify-content-center align-items-center flex-grow-1">
|
||||||
|
<b-spinner />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<div class="mb-3 d-flex flex-column flex-md-row justify-content-between align-items-center" style="gap: 2rem;">
|
||||||
|
<div style="width: 60%;">
|
||||||
|
<div class="input-group align-items-center">
|
||||||
|
<input class="form-control form-control-sm rounded-lg" v-model="q" placeholder="Search by domain..." style="padding-right: 60px;" :disabled="!blocks || !blocks.length">
|
||||||
|
<div style="margin-left: -60px;width: 60px;z-index:3">
|
||||||
|
<button class="btn btn-link" type="button" style="font-size: 12px;text-decoration: none;" v-html="q && q.length ? 'Clear': ' '" @click="searchAction()"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" class="btn btn-outline-primary btn-sm font-weight-bold px-3 flex-grow" @click="openModal">
|
||||||
|
<i class="fas fa-plus mr-1"></i> New Block
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div v-if="blocks && blocks.length" class="list-group">
|
||||||
|
<div
|
||||||
|
v-for="(item, idx) in chunks[index]"
|
||||||
|
class="list-group-item">
|
||||||
|
<div class="d-flex justify-content-between align-items-center font-weight-bold">
|
||||||
|
<span>
|
||||||
|
<span v-text="item"></span>
|
||||||
|
</span>
|
||||||
|
<span class="btn-group">
|
||||||
|
<button type="button" class="btn btn-link btn-sm px-3 font-weight-bold" @click="handleUnblock(item)">Unblock</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav v-if="blocks && blocks.length && chunks && chunks.length > 1" class="mt-3" aria-label="Domain block pagination">
|
||||||
|
<ul class="pagination justify-content-center" style="gap: 1rem">
|
||||||
|
<li
|
||||||
|
class="page-item"
|
||||||
|
:class="[ !index ? 'disabled' : 'font-weight-bold' ]"
|
||||||
|
:disabled="!index"
|
||||||
|
@click="paginate('prev')">
|
||||||
|
<span class="page-link px-5 rounded-lg">Previous</span>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="page-item"
|
||||||
|
:class="[ index + 1 === chunks.length ? 'disabled' : 'font-weight-bold' ]"
|
||||||
|
@click="paginate('next')">
|
||||||
|
<span class="page-link px-5 rounded-lg" href="#">Next</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div v-if="!blocks || !blocks.length">
|
||||||
|
<hr />
|
||||||
|
<p class="lead text-center font-weight-bold">You are not blocking any domains.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script type="text/javascript">
|
||||||
|
let app = new Vue({
|
||||||
|
el: '#content',
|
||||||
|
|
||||||
|
data: {
|
||||||
|
loaded: false,
|
||||||
|
q: undefined,
|
||||||
|
blocks: [],
|
||||||
|
filteredBlocks: [],
|
||||||
|
chunks: [],
|
||||||
|
index: 0,
|
||||||
|
pagination: [],
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
q: function(newVal, oldVal) {
|
||||||
|
this.filterResults(newVal)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.fetchBlocks()
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
fetchBlocks() {
|
||||||
|
axios.get('/api/v1/domain_blocks', { params: { 'limit': 200 }})
|
||||||
|
.then(res => {
|
||||||
|
let pages = false
|
||||||
|
if(res.headers?.link) {
|
||||||
|
pages = this.parseLinkHeader(res.headers['link'])
|
||||||
|
}
|
||||||
|
this.blocks = res.data
|
||||||
|
if(!pages || !pages.hasOwnProperty('next')) {
|
||||||
|
this.buildList()
|
||||||
|
} else {
|
||||||
|
this.handlePagination(pages)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err.response)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
handlePagination(pages) {
|
||||||
|
if(!pages || !pages.hasOwnProperty('next')) {
|
||||||
|
this.buildList()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.pagination = pages
|
||||||
|
this.fetchPagination()
|
||||||
|
},
|
||||||
|
|
||||||
|
buildList() {
|
||||||
|
this.index = 0
|
||||||
|
this.chunks = this.chunkify(this.blocks)
|
||||||
|
this.loaded = true
|
||||||
|
},
|
||||||
|
|
||||||
|
buildSearchList() {
|
||||||
|
this.index = 0
|
||||||
|
this.chunks = this.chunkify(this.filteredBlocks)
|
||||||
|
this.loaded = true
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchPagination() {
|
||||||
|
axios.get(this.pagination.next)
|
||||||
|
.then(res => {
|
||||||
|
let pages = false
|
||||||
|
if(res.headers?.link) {
|
||||||
|
pages = this.parseLinkHeader(res.headers['link'])
|
||||||
|
}
|
||||||
|
this.blocks.push(...res.data)
|
||||||
|
if(!pages || !pages.hasOwnProperty('next')) {
|
||||||
|
this.buildList()
|
||||||
|
} else {
|
||||||
|
this.handlePagination(pages)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
this.buildList()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
handleUnblock(domain) {
|
||||||
|
this.loaded = false
|
||||||
|
axios.delete('/api/v1/domain_blocks', {
|
||||||
|
params: {
|
||||||
|
domain: domain
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
this.blocks = this.blocks.filter(d => d != domain)
|
||||||
|
this.buildList()
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
this.buildList()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
filterResults(query) {
|
||||||
|
this.loaded = false
|
||||||
|
let formattedQuery = query.trim().toLowerCase()
|
||||||
|
this.filteredBlocks = this.blocks.filter(domain => domain.toLowerCase().startsWith(formattedQuery))
|
||||||
|
this.buildSearchList()
|
||||||
|
},
|
||||||
|
|
||||||
|
searchAction($event) {
|
||||||
|
event.currentTarget.blur()
|
||||||
|
this.q = ''
|
||||||
|
},
|
||||||
|
|
||||||
|
openModal() {
|
||||||
|
swal({
|
||||||
|
title: 'Domain Block',
|
||||||
|
text: 'Add domain to block, must start with https://',
|
||||||
|
content: "input",
|
||||||
|
button: {
|
||||||
|
text: "Block",
|
||||||
|
closeModal: false,
|
||||||
|
}
|
||||||
|
}).then(val => {
|
||||||
|
if (!val) {
|
||||||
|
swal.stopLoading()
|
||||||
|
swal.close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
axios.post('/api/v1/domain_blocks', { domain: val })
|
||||||
|
.then(res => {
|
||||||
|
let parsedUrl = new URL(val)
|
||||||
|
swal.stopLoading()
|
||||||
|
swal.close()
|
||||||
|
this.index = 0
|
||||||
|
this.blocks.unshift(parsedUrl.hostname)
|
||||||
|
this.buildList()
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
swal.stopLoading()
|
||||||
|
swal.close()
|
||||||
|
if(err.response?.data?.message || err.response?.data?.error) {
|
||||||
|
swal('Error', err.response?.data?.message ?? err.response?.data?.error, 'error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
chunkify(arr, len = 10) {
|
||||||
|
var chunks = [],
|
||||||
|
i = 0,
|
||||||
|
n = arr.length
|
||||||
|
|
||||||
|
while (i < n) {
|
||||||
|
chunks.push(arr.slice(i, i += len))
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunks
|
||||||
|
},
|
||||||
|
|
||||||
|
paginate(dir) {
|
||||||
|
if(dir === 'prev' && this.index > 0) {
|
||||||
|
this.index--
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if(dir === 'next' && this.index + 1 < this.chunks.length) {
|
||||||
|
this.index++
|
||||||
|
return
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
parseLinkHeader(linkHeader) {
|
||||||
|
const links = {}
|
||||||
|
|
||||||
|
if (!linkHeader) {
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
|
linkHeader.split(',').forEach(part => {
|
||||||
|
const match = part.match(/<([^>]+)>;\s*rel="([^"]+)"/)
|
||||||
|
if (match) {
|
||||||
|
const url = match[1]
|
||||||
|
const rel = match[2]
|
||||||
|
|
||||||
|
if (rel === 'prev' || rel === 'next') {
|
||||||
|
links[rel] = url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
@endpush
|
|
@ -1,41 +1,35 @@
|
||||||
@extends('settings.template')
|
@extends('settings.template')
|
||||||
|
|
||||||
@section('section')
|
@section('section')
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<div class="title">
|
<div class="title d-flex align-items-center" style="gap: 1rem;">
|
||||||
<h3 class="font-weight-bold">Muted Users</h3>
|
<p class="mb-0"><a href="/settings/privacy"><i class="far fa-chevron-left fa-lg"></i></a></p>
|
||||||
</div>
|
<h3 class="font-weight-bold mb-0">Muted Accounts</h3>
|
||||||
<hr>
|
</div>
|
||||||
<div class="form-group pb-1">
|
</div>
|
||||||
<p>
|
<hr />
|
||||||
<a class="btn btn-outline-primary py-0 font-weight-bold" href="{{route('settings.privacy.muted-users')}}">Muted Users</a>
|
@if($users->count() > 0)
|
||||||
<a class="btn btn-outline-secondary py-0 font-weight-bold" href="{{route('settings.privacy.blocked-users')}}">Blocked Users</a>
|
<div class="list-group">
|
||||||
{{-- <a class="btn btn-outline-secondary py-0 font-weight-bold" href="{{route('settings.privacy.blocked-keywords')}}">Blocked keywords</a>
|
|
||||||
<a class="btn btn-outline-secondary py-0 font-weight-bold" href="{{route('settings.privacy.blocked-instances')}}">Blocked instances</a> --}}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
@if($users->count() > 0)
|
|
||||||
<ul class="list-group list-group-flush">
|
|
||||||
@foreach($users as $user)
|
@foreach($users as $user)
|
||||||
<li class="list-group-item">
|
<div class="list-group-item">
|
||||||
<div class="d-flex justify-content-between align-items-center font-weight-bold">
|
<div class="d-flex justify-content-between align-items-center font-weight-bold">
|
||||||
<span><a href="{{$user->url()}}" class="text-decoration-none text-dark"><img class="rounded-circle mr-3" src="{{$user->avatarUrl()}}" width="32px">{{$user->username}}</a></span>
|
<span><a href="{{$user->url()}}" class="text-decoration-none text-dark"><img class="rounded-circle mr-3" src="{{$user->avatarUrl()}}" width="32px" onerror="this.onerror=null;this.src='/storage/avatars/default.jpg?v=0';">{{$user->username}}</a></span>
|
||||||
<span class="btn-group">
|
<span class="btn-group">
|
||||||
<form method="post">
|
<form method="post">
|
||||||
@csrf
|
@csrf
|
||||||
<input type="hidden" name="profile_id" value="{{$user->id}}">
|
<input type="hidden" name="profile_id" value="{{$user->id}}">
|
||||||
<button type="submit" class="btn btn-outline-secondary btn-sm px-3 font-weight-bold">Unmute</button>
|
<button type="submit" class="btn btn-link btn-sm px-3 font-weight-bold">Unmute</button>
|
||||||
</form>
|
</form>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
</ul>
|
</div>
|
||||||
<div class="d-flex justify-content-center mt-3 font-weight-bold">
|
<div class="d-flex justify-content-center mt-3 font-weight-bold">
|
||||||
{{$users->links()}}
|
{{$users->links()}}
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<p class="lead">You are not muting any accounts.</p>
|
<p class="lead text-center font-weight-bold">You are not muting any accounts.</p>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@endsection
|
@endsection
|
||||||
|
|
37
resources/views/settings/template-vue.blade.php
Normal file
37
resources/views/settings/template-vue.blade.php
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
@if (session('status'))
|
||||||
|
<div class="alert alert-primary px-3 h6 text-center">
|
||||||
|
{{ session('status') }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@if ($errors->any())
|
||||||
|
<div class="alert alert-danger px-3 h6 text-center">
|
||||||
|
@foreach($errors->all() as $error)
|
||||||
|
<p class="font-weight-bold mb-1">{{ $error }}</p>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@if (session('error'))
|
||||||
|
<div class="alert alert-danger px-3 h6 text-center">
|
||||||
|
{{ session('error') }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card shadow-none border mt-5">
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="row">
|
||||||
|
@include('settings.partial.sidebar')
|
||||||
|
<div class="col-12 col-md-9 p-5">
|
||||||
|
@yield('section')
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@endsection
|
|
@ -51,9 +51,9 @@ Route::group(['prefix' => 'api'], function() use($middleware) {
|
||||||
Route::get('blocks', 'Api\ApiV1Controller@accountBlocks')->middleware($middleware);
|
Route::get('blocks', 'Api\ApiV1Controller@accountBlocks')->middleware($middleware);
|
||||||
Route::get('conversations', 'Api\ApiV1Controller@conversations')->middleware($middleware);
|
Route::get('conversations', 'Api\ApiV1Controller@conversations')->middleware($middleware);
|
||||||
Route::get('custom_emojis', 'Api\ApiV1Controller@customEmojis');
|
Route::get('custom_emojis', 'Api\ApiV1Controller@customEmojis');
|
||||||
Route::get('domain_blocks', 'Api\ApiV1Controller@accountDomainBlocks')->middleware($middleware);
|
Route::get('domain_blocks', 'Api\V1\DomainBlockController@index')->middleware($middleware);
|
||||||
Route::post('domain_blocks', 'Api\ApiV1Controller@accountDomainBlocks')->middleware($middleware);
|
Route::post('domain_blocks', 'Api\V1\DomainBlockController@store')->middleware($middleware);
|
||||||
Route::delete('domain_blocks', 'Api\ApiV1Controller@accountDomainBlocks')->middleware($middleware);
|
Route::delete('domain_blocks', 'Api\V1\DomainBlockController@delete')->middleware($middleware);
|
||||||
Route::get('endorsements', 'Api\ApiV1Controller@accountEndorsements')->middleware($middleware);
|
Route::get('endorsements', 'Api\ApiV1Controller@accountEndorsements')->middleware($middleware);
|
||||||
Route::get('favourites', 'Api\ApiV1Controller@accountFavourites')->middleware($middleware);
|
Route::get('favourites', 'Api\ApiV1Controller@accountFavourites')->middleware($middleware);
|
||||||
Route::get('filters', 'Api\ApiV1Controller@accountFilters')->middleware($middleware);
|
Route::get('filters', 'Api\ApiV1Controller@accountFilters')->middleware($middleware);
|
||||||
|
|
|
@ -489,6 +489,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
||||||
Route::post('privacy/muted-users', 'SettingsController@mutedUsersUpdate');
|
Route::post('privacy/muted-users', 'SettingsController@mutedUsersUpdate');
|
||||||
Route::get('privacy/blocked-users', 'SettingsController@blockedUsers')->name('settings.privacy.blocked-users');
|
Route::get('privacy/blocked-users', 'SettingsController@blockedUsers')->name('settings.privacy.blocked-users');
|
||||||
Route::post('privacy/blocked-users', 'SettingsController@blockedUsersUpdate');
|
Route::post('privacy/blocked-users', 'SettingsController@blockedUsersUpdate');
|
||||||
|
Route::get('privacy/domain-blocks', 'SettingsController@domainBlocks')->name('settings.privacy.domain-blocks');
|
||||||
Route::get('privacy/blocked-instances', 'SettingsController@blockedInstances')->name('settings.privacy.blocked-instances');
|
Route::get('privacy/blocked-instances', 'SettingsController@blockedInstances')->name('settings.privacy.blocked-instances');
|
||||||
Route::post('privacy/blocked-instances', 'SettingsController@blockedInstanceStore');
|
Route::post('privacy/blocked-instances', 'SettingsController@blockedInstanceStore');
|
||||||
Route::post('privacy/blocked-instances/unblock', 'SettingsController@blockedInstanceUnblock')->name('settings.privacy.blocked-instances.unblock');
|
Route::post('privacy/blocked-instances/unblock', 'SettingsController@blockedInstanceUnblock')->name('settings.privacy.blocked-instances.unblock');
|
||||||
|
|
Loading…
Reference in a new issue