From 5071aaf4086715f5a6c45b86897e4ae2d066e894 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 12 Mar 2024 02:20:37 -0600 Subject: [PATCH] Update activitpub setting, use config_cache() --- app/Http/Controllers/FederationController.php | 184 +++-- app/Http/Controllers/SearchController.php | 649 +++++++++--------- app/Http/Controllers/StatusController.php | 2 +- app/Jobs/SharePipeline/SharePipeline.php | 225 +++--- app/Jobs/SharePipeline/UndoSharePipeline.php | 203 +++--- app/Jobs/StatusPipeline/StatusDelete.php | 312 +++++---- app/Jobs/StatusPipeline/StatusEntityLexer.php | 43 +- app/Services/LandingService.php | 2 +- app/Util/ActivityPub/Outbox.php | 50 +- .../views/admin/diagnostics/home.blade.php | 2 +- 10 files changed, 833 insertions(+), 839 deletions(-) diff --git a/app/Http/Controllers/FederationController.php b/app/Http/Controllers/FederationController.php index 55c7b4393..54ad03227 100644 --- a/app/Http/Controllers/FederationController.php +++ b/app/Http/Controllers/FederationController.php @@ -2,57 +2,42 @@ namespace App\Http\Controllers; -use App\Jobs\InboxPipeline\{ - DeleteWorker, - InboxWorker, - InboxValidator -}; -use App\Jobs\RemoteFollowPipeline\RemoteFollowPipeline; -use App\{ - AccountLog, - Like, - Profile, - Status, - User -}; -use App\Util\Lexer\Nickname; -use App\Util\Webfinger\Webfinger; -use Auth; -use Cache; -use Carbon\Carbon; -use Illuminate\Http\Request; -use League\Fractal; -use App\Util\Site\Nodeinfo; -use App\Util\ActivityPub\{ - Helpers, - HttpSignature, - Outbox -}; -use Zttp\Zttp; -use App\Services\InstanceService; +use App\Jobs\InboxPipeline\DeleteWorker; +use App\Jobs\InboxPipeline\InboxValidator; +use App\Jobs\InboxPipeline\InboxWorker; +use App\Profile; use App\Services\AccountService; +use App\Services\InstanceService; +use App\Status; +use App\Util\Lexer\Nickname; +use App\Util\Site\Nodeinfo; +use App\Util\Webfinger\Webfinger; +use Cache; +use Illuminate\Http\Request; class FederationController extends Controller { public function nodeinfoWellKnown() { - abort_if(!config('federation.nodeinfo.enabled'), 404); + abort_if(! config('federation.nodeinfo.enabled'), 404); + return response()->json(Nodeinfo::wellKnown(), 200, [], JSON_UNESCAPED_SLASHES) - ->header('Access-Control-Allow-Origin','*'); + ->header('Access-Control-Allow-Origin', '*'); } public function nodeinfo() { - abort_if(!config('federation.nodeinfo.enabled'), 404); + abort_if(! config('federation.nodeinfo.enabled'), 404); + return response()->json(Nodeinfo::get(), 200, [], JSON_UNESCAPED_SLASHES) - ->header('Access-Control-Allow-Origin','*'); + ->header('Access-Control-Allow-Origin', '*'); } public function webfinger(Request $request) { - if (!config('federation.webfinger.enabled') || - !$request->has('resource') || - !$request->filled('resource') + if (! config('federation.webfinger.enabled') || + ! $request->has('resource') || + ! $request->filled('resource') ) { return response('', 400); } @@ -60,55 +45,56 @@ class FederationController extends Controller $resource = $request->input('resource'); $domain = config('pixelfed.domain.app'); - if(config('federation.activitypub.sharedInbox') && - $resource == 'acct:' . $domain . '@' . $domain) { + if (config('federation.activitypub.sharedInbox') && + $resource == 'acct:'.$domain.'@'.$domain) { $res = [ - 'subject' => 'acct:' . $domain . '@' . $domain, + 'subject' => 'acct:'.$domain.'@'.$domain, 'aliases' => [ - 'https://' . $domain . '/i/actor' + 'https://'.$domain.'/i/actor', ], 'links' => [ [ 'rel' => 'http://webfinger.net/rel/profile-page', 'type' => 'text/html', - 'href' => 'https://' . $domain . '/site/kb/instance-actor' + 'href' => 'https://'.$domain.'/site/kb/instance-actor', ], [ 'rel' => 'self', 'type' => 'application/activity+json', - 'href' => 'https://' . $domain . '/i/actor' - ] - ] + 'href' => 'https://'.$domain.'/i/actor', + ], + ], ]; + return response()->json($res, 200, [], JSON_UNESCAPED_SLASHES); } $hash = hash('sha256', $resource); - $key = 'federation:webfinger:sha256:' . $hash; - if($cached = Cache::get($key)) { + $key = 'federation:webfinger:sha256:'.$hash; + if ($cached = Cache::get($key)) { return response()->json($cached, 200, [], JSON_UNESCAPED_SLASHES); } - if(strpos($resource, $domain) == false) { + if (strpos($resource, $domain) == false) { return response('', 400); } $parsed = Nickname::normalizeProfileUrl($resource); - if(empty($parsed) || $parsed['domain'] !== $domain) { + if (empty($parsed) || $parsed['domain'] !== $domain) { return response('', 400); } $username = $parsed['username']; $profile = Profile::whereNull('domain')->whereUsername($username)->first(); - if(!$profile || $profile->status !== null) { + if (! $profile || $profile->status !== null) { return response('', 400); } $webfinger = (new Webfinger($profile))->generate(); Cache::put($key, $webfinger, 1209600); return response()->json($webfinger, 200, [], JSON_UNESCAPED_SLASHES) - ->header('Access-Control-Allow-Origin','*'); + ->header('Access-Control-Allow-Origin', '*'); } public function hostMeta(Request $request) { - abort_if(!config('federation.webfinger.enabled'), 404); + abort_if(! config('federation.webfinger.enabled'), 404); $path = route('well-known.webfinger'); $xml = ''; @@ -118,19 +104,19 @@ class FederationController extends Controller public function userOutbox(Request $request, $username) { - abort_if(!config_cache('federation.activitypub.enabled'), 404); + abort_if(! (bool) config_cache('federation.activitypub.enabled'), 404); - if(!$request->wantsJson()) { - return redirect('/' . $username); + if (! $request->wantsJson()) { + return redirect('/'.$username); } $id = AccountService::usernameToId($username); - abort_if(!$id, 404); + abort_if(! $id, 404); $account = AccountService::get($id); - abort_if(!$account || !isset($account['statuses_count']), 404); + abort_if(! $account || ! isset($account['statuses_count']), 404); $res = [ '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => 'https://' . config('pixelfed.domain.app') . '/users/' . $username . '/outbox', + 'id' => 'https://'.config('pixelfed.domain.app').'/users/'.$username.'/outbox', 'type' => 'OrderedCollection', 'totalItems' => $account['statuses_count'] ?? 0, ]; @@ -140,135 +126,145 @@ class FederationController extends Controller public function userInbox(Request $request, $username) { - abort_if(!config_cache('federation.activitypub.enabled'), 404); - abort_if(!config('federation.activitypub.inbox'), 404); + abort_if(! (bool) config_cache('federation.activitypub.enabled'), 404); + abort_if(! config('federation.activitypub.inbox'), 404); $headers = $request->headers->all(); $payload = $request->getContent(); - if(!$payload || empty($payload)) { + if (! $payload || empty($payload)) { return; } $obj = json_decode($payload, true, 8); - if(!isset($obj['id'])) { + if (! isset($obj['id'])) { return; } $domain = parse_url($obj['id'], PHP_URL_HOST); - if(in_array($domain, InstanceService::getBannedDomains())) { + if (in_array($domain, InstanceService::getBannedDomains())) { return; } - if(isset($obj['type']) && $obj['type'] === 'Delete') { - if(isset($obj['object']) && isset($obj['object']['type']) && isset($obj['object']['id'])) { - if($obj['object']['type'] === 'Person') { - if(Profile::whereRemoteUrl($obj['object']['id'])->exists()) { + if (isset($obj['type']) && $obj['type'] === 'Delete') { + if (isset($obj['object']) && isset($obj['object']['type']) && isset($obj['object']['id'])) { + if ($obj['object']['type'] === 'Person') { + if (Profile::whereRemoteUrl($obj['object']['id'])->exists()) { dispatch(new DeleteWorker($headers, $payload))->onQueue('inbox'); + return; } } - if($obj['object']['type'] === 'Tombstone') { - if(Status::whereObjectUrl($obj['object']['id'])->exists()) { + if ($obj['object']['type'] === 'Tombstone') { + if (Status::whereObjectUrl($obj['object']['id'])->exists()) { dispatch(new DeleteWorker($headers, $payload))->onQueue('delete'); + return; } } - if($obj['object']['type'] === 'Story') { + if ($obj['object']['type'] === 'Story') { dispatch(new DeleteWorker($headers, $payload))->onQueue('story'); + return; } } + return; - } else if( isset($obj['type']) && in_array($obj['type'], ['Follow', 'Accept'])) { + } elseif (isset($obj['type']) && in_array($obj['type'], ['Follow', 'Accept'])) { dispatch(new InboxValidator($username, $headers, $payload))->onQueue('follow'); } else { dispatch(new InboxValidator($username, $headers, $payload))->onQueue('high'); } - return; + } public function sharedInbox(Request $request) { - abort_if(!config_cache('federation.activitypub.enabled'), 404); - abort_if(!config('federation.activitypub.sharedInbox'), 404); + abort_if(! (bool) config_cache('federation.activitypub.enabled'), 404); + abort_if(! config('federation.activitypub.sharedInbox'), 404); $headers = $request->headers->all(); $payload = $request->getContent(); - if(!$payload || empty($payload)) { + if (! $payload || empty($payload)) { return; } $obj = json_decode($payload, true, 8); - if(!isset($obj['id'])) { + if (! isset($obj['id'])) { return; } $domain = parse_url($obj['id'], PHP_URL_HOST); - if(in_array($domain, InstanceService::getBannedDomains())) { + if (in_array($domain, InstanceService::getBannedDomains())) { return; } - if(isset($obj['type']) && $obj['type'] === 'Delete') { - if(isset($obj['object']) && isset($obj['object']['type']) && isset($obj['object']['id'])) { - if($obj['object']['type'] === 'Person') { - if(Profile::whereRemoteUrl($obj['object']['id'])->exists()) { + if (isset($obj['type']) && $obj['type'] === 'Delete') { + if (isset($obj['object']) && isset($obj['object']['type']) && isset($obj['object']['id'])) { + if ($obj['object']['type'] === 'Person') { + if (Profile::whereRemoteUrl($obj['object']['id'])->exists()) { dispatch(new DeleteWorker($headers, $payload))->onQueue('inbox'); + return; } } - if($obj['object']['type'] === 'Tombstone') { - if(Status::whereObjectUrl($obj['object']['id'])->exists()) { + if ($obj['object']['type'] === 'Tombstone') { + if (Status::whereObjectUrl($obj['object']['id'])->exists()) { dispatch(new DeleteWorker($headers, $payload))->onQueue('delete'); + return; } } - if($obj['object']['type'] === 'Story') { + if ($obj['object']['type'] === 'Story') { dispatch(new DeleteWorker($headers, $payload))->onQueue('story'); + return; } } + return; - } else if( isset($obj['type']) && in_array($obj['type'], ['Follow', 'Accept'])) { + } elseif (isset($obj['type']) && in_array($obj['type'], ['Follow', 'Accept'])) { dispatch(new InboxWorker($headers, $payload))->onQueue('follow'); } else { dispatch(new InboxWorker($headers, $payload))->onQueue('shared'); } - return; + } public function userFollowing(Request $request, $username) { - abort_if(!config_cache('federation.activitypub.enabled'), 404); + abort_if(! (bool) config_cache('federation.activitypub.enabled'), 404); $id = AccountService::usernameToId($username); - abort_if(!$id, 404); + abort_if(! $id, 404); $account = AccountService::get($id); - abort_if(!$account || !isset($account['following_count']), 404); + abort_if(! $account || ! isset($account['following_count']), 404); $obj = [ '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => $request->getUri(), - 'type' => 'OrderedCollection', + 'id' => $request->getUri(), + 'type' => 'OrderedCollection', 'totalItems' => $account['following_count'] ?? 0, ]; + return response()->json($obj)->header('Content-Type', 'application/activity+json'); } public function userFollowers(Request $request, $username) { - abort_if(!config_cache('federation.activitypub.enabled'), 404); + abort_if(! (bool) config_cache('federation.activitypub.enabled'), 404); $id = AccountService::usernameToId($username); - abort_if(!$id, 404); + abort_if(! $id, 404); $account = AccountService::get($id); - abort_if(!$account || !isset($account['followers_count']), 404); + abort_if(! $account || ! isset($account['followers_count']), 404); $obj = [ '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => $request->getUri(), - 'type' => 'OrderedCollection', + 'id' => $request->getUri(), + 'type' => 'OrderedCollection', 'totalItems' => $account['followers_count'] ?? 0, ]; + return response()->json($obj)->header('Content-Type', 'application/activity+json'); } } diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index cbf21518b..9388d3abd 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -2,368 +2,367 @@ namespace App\Http\Controllers; -use Auth; use App\Hashtag; use App\Place; use App\Profile; +use App\Services\WebfingerService; use App\Status; -use Illuminate\Http\Request; use App\Util\ActivityPub\Helpers; +use Auth; +use Illuminate\Http\Request; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Str; -use App\Transformer\Api\{ - AccountTransformer, - HashtagTransformer, - StatusTransformer, -}; -use App\Services\WebfingerService; class SearchController extends Controller { - public $tokens = []; - public $term = ''; - public $hash = ''; - public $cacheKey = 'api:search:tag:'; + public $tokens = []; - public function __construct() - { - $this->middleware('auth'); - } + public $term = ''; - public function searchAPI(Request $request) - { - $this->validate($request, [ - 'q' => 'required|string|min:3|max:120', - 'src' => 'required|string|in:metro', - 'v' => 'required|integer|in:2', - 'scope' => 'required|in:all,hashtag,profile,remote,webfinger' - ]); + public $hash = ''; - $scope = $request->input('scope') ?? 'all'; - $this->term = e(urldecode($request->input('q'))); - $this->hash = hash('sha256', $this->term); + public $cacheKey = 'api:search:tag:'; - switch ($scope) { - case 'all': - $this->getHashtags(); - $this->getPosts(); - $this->getProfiles(); - // $this->getPlaces(); - break; + public function __construct() + { + $this->middleware('auth'); + } - case 'hashtag': - $this->getHashtags(); - break; + public function searchAPI(Request $request) + { + $this->validate($request, [ + 'q' => 'required|string|min:3|max:120', + 'src' => 'required|string|in:metro', + 'v' => 'required|integer|in:2', + 'scope' => 'required|in:all,hashtag,profile,remote,webfinger', + ]); - case 'profile': - $this->getProfiles(); - break; + $scope = $request->input('scope') ?? 'all'; + $this->term = e(urldecode($request->input('q'))); + $this->hash = hash('sha256', $this->term); - case 'webfinger': - $this->webfingerSearch(); - break; + switch ($scope) { + case 'all': + $this->getHashtags(); + $this->getPosts(); + $this->getProfiles(); + // $this->getPlaces(); + break; - case 'remote': - $this->remoteLookupSearch(); - break; + case 'hashtag': + $this->getHashtags(); + break; - case 'place': - $this->getPlaces(); - break; + case 'profile': + $this->getProfiles(); + break; - default: - break; - } + case 'webfinger': + $this->webfingerSearch(); + break; - return response()->json($this->tokens, 200, [], JSON_PRETTY_PRINT); - } + case 'remote': + $this->remoteLookupSearch(); + break; - protected function getPosts() - { - $tag = $this->term; - $hash = hash('sha256', $tag); - if( Helpers::validateUrl($tag) != false && - Helpers::validateLocalUrl($tag) != true && - config_cache('federation.activitypub.enabled') == true && - config('federation.activitypub.remoteFollow') == true - ) { - $remote = Helpers::fetchFromUrl($tag); - if( isset($remote['type']) && - $remote['type'] == 'Note') { - $item = Helpers::statusFetch($tag); - $this->tokens['posts'] = [[ - 'count' => 0, - 'url' => $item->url(), - 'type' => 'status', - 'value' => "by {$item->profile->username} {$item->created_at->diffForHumans(null, true, true)}", - 'tokens' => [$item->caption], - 'name' => $item->caption, - 'thumb' => $item->thumb(), - ]]; - } - } else { - $posts = Status::select('id', 'profile_id', 'caption', 'created_at') - ->whereHas('media') - ->whereNull('in_reply_to_id') - ->whereNull('reblog_of_id') - ->whereProfileId(Auth::user()->profile_id) - ->where('caption', 'like', '%'.$tag.'%') - ->latest() - ->limit(10) - ->get(); + case 'place': + $this->getPlaces(); + break; - if($posts->count() > 0) { - $posts = $posts->map(function($item, $key) { - return [ - 'count' => 0, - 'url' => $item->url(), - 'type' => 'status', - 'value' => "by {$item->profile->username} {$item->created_at->diffForHumans(null, true, true)}", - 'tokens' => [$item->caption], - 'name' => $item->caption, - 'thumb' => $item->thumb(), - 'filter' => $item->firstMedia()->filter_class - ]; - }); - $this->tokens['posts'] = $posts; - } - } - } + default: + break; + } - protected function getHashtags() - { - $tag = $this->term; - $key = $this->cacheKey . 'hashtags:' . $this->hash; - $ttl = now()->addMinutes(1); - $tokens = Cache::remember($key, $ttl, function() use($tag) { - $htag = Str::startsWith($tag, '#') == true ? mb_substr($tag, 1) : $tag; - $hashtags = Hashtag::select('id', 'name', 'slug') - ->where('slug', 'like', '%'.$htag.'%') - ->whereHas('posts') - ->limit(20) - ->get(); - if($hashtags->count() > 0) { - $tags = $hashtags->map(function ($item, $key) { - return [ - 'count' => $item->posts()->count(), - 'url' => $item->url(), - 'type' => 'hashtag', - 'value' => $item->name, - 'tokens' => '', - 'name' => null, - ]; - }); - return $tags; - } - }); - $this->tokens['hashtags'] = $tokens; - } + return response()->json($this->tokens, 200, [], JSON_PRETTY_PRINT); + } - protected function getPlaces() - { - $tag = $this->term; - // $key = $this->cacheKey . 'places:' . $this->hash; - // $ttl = now()->addHours(12); - // $tokens = Cache::remember($key, $ttl, function() use($tag) { - $htag = Str::contains($tag, ',') == true ? explode(',', $tag) : [$tag]; - $hashtags = Place::select('id', 'name', 'slug', 'country') - ->where('name', 'like', '%'.$htag[0].'%') - ->paginate(20); - $tags = []; - if($hashtags->count() > 0) { - $tags = $hashtags->map(function ($item, $key) { - return [ - 'count' => null, - 'url' => $item->url(), - 'type' => 'place', - 'value' => $item->name . ', ' . $item->country, - 'tokens' => '', - 'name' => null, - 'city' => $item->name, - 'country' => $item->country - ]; - }); - // return $tags; - } - // }); - $this->tokens['places'] = $tags; - $this->tokens['placesPagination'] = [ - 'total' => $hashtags->total(), - 'current_page' => $hashtags->currentPage(), - 'last_page' => $hashtags->lastPage() - ]; - } + protected function getPosts() + { + $tag = $this->term; + $hash = hash('sha256', $tag); + if (Helpers::validateUrl($tag) != false && + Helpers::validateLocalUrl($tag) != true && + (bool) config_cache('federation.activitypub.enabled') == true && + config('federation.activitypub.remoteFollow') == true + ) { + $remote = Helpers::fetchFromUrl($tag); + if (isset($remote['type']) && + in_array($remote['type'], ['Note', 'Question']) + ) { + $item = Helpers::statusFetch($tag); + $this->tokens['posts'] = [[ + 'count' => 0, + 'url' => $item->url(), + 'type' => 'status', + 'value' => "by {$item->profile->username} {$item->created_at->diffForHumans(null, true, true)}", + 'tokens' => [$item->caption], + 'name' => $item->caption, + 'thumb' => $item->thumb(), + ]]; + } + } else { + $posts = Status::select('id', 'profile_id', 'caption', 'created_at') + ->whereHas('media') + ->whereNull('in_reply_to_id') + ->whereNull('reblog_of_id') + ->whereProfileId(Auth::user()->profile_id) + ->where('caption', 'like', '%'.$tag.'%') + ->latest() + ->limit(10) + ->get(); - protected function getProfiles() - { - $tag = $this->term; - $remoteKey = $this->cacheKey . 'profiles:remote:' . $this->hash; - $key = $this->cacheKey . 'profiles:' . $this->hash; - $remoteTtl = now()->addMinutes(15); - $ttl = now()->addHours(2); - if( Helpers::validateUrl($tag) != false && - Helpers::validateLocalUrl($tag) != true && - config_cache('federation.activitypub.enabled') == true && - config('federation.activitypub.remoteFollow') == true - ) { - $remote = Helpers::fetchFromUrl($tag); - if( isset($remote['type']) && - $remote['type'] == 'Person' - ) { - $this->tokens['profiles'] = Cache::remember($remoteKey, $remoteTtl, function() use($tag) { - $item = Helpers::profileFirstOrNew($tag); - $tokens = [[ - 'count' => 1, - 'url' => $item->url(), - 'type' => 'profile', - 'value' => $item->username, - 'tokens' => [$item->username], - 'name' => $item->name, - 'entity' => [ - 'id' => (string) $item->id, - 'following' => $item->followedBy(Auth::user()->profile), - 'follow_request' => $item->hasFollowRequestById(Auth::user()->profile_id), - 'thumb' => $item->avatarUrl(), - 'local' => (bool) !$item->domain, - 'post_count' => $item->statuses()->count() - ] - ]]; - return $tokens; - }); - } - } + if ($posts->count() > 0) { + $posts = $posts->map(function ($item, $key) { + return [ + 'count' => 0, + 'url' => $item->url(), + 'type' => 'status', + 'value' => "by {$item->profile->username} {$item->created_at->diffForHumans(null, true, true)}", + 'tokens' => [$item->caption], + 'name' => $item->caption, + 'thumb' => $item->thumb(), + 'filter' => $item->firstMedia()->filter_class, + ]; + }); + $this->tokens['posts'] = $posts; + } + } + } - else { - $this->tokens['profiles'] = Cache::remember($key, $ttl, function() use($tag) { - if(Str::startsWith($tag, '@')) { - $tag = substr($tag, 1); - } - $users = Profile::select('status', 'domain', 'username', 'name', 'id') - ->whereNull('status') - ->where('username', 'like', '%'.$tag.'%') - ->limit(20) - ->orderBy('domain') - ->get(); + protected function getHashtags() + { + $tag = $this->term; + $key = $this->cacheKey.'hashtags:'.$this->hash; + $ttl = now()->addMinutes(1); + $tokens = Cache::remember($key, $ttl, function () use ($tag) { + $htag = Str::startsWith($tag, '#') == true ? mb_substr($tag, 1) : $tag; + $hashtags = Hashtag::select('id', 'name', 'slug') + ->where('slug', 'like', '%'.$htag.'%') + ->whereHas('posts') + ->limit(20) + ->get(); + if ($hashtags->count() > 0) { + $tags = $hashtags->map(function ($item, $key) { + return [ + 'count' => $item->posts()->count(), + 'url' => $item->url(), + 'type' => 'hashtag', + 'value' => $item->name, + 'tokens' => '', + 'name' => null, + ]; + }); - if($users->count() > 0) { - return $users->map(function ($item, $key) { - return [ - 'count' => 0, - 'url' => $item->url(), - 'type' => 'profile', - 'value' => $item->username, - 'tokens' => [$item->username], - 'name' => $item->name, - 'avatar' => $item->avatarUrl(), - 'id' => (string) $item->id, - 'entity' => [ - 'id' => (string) $item->id, - 'following' => $item->followedBy(Auth::user()->profile), - 'follow_request' => $item->hasFollowRequestById(Auth::user()->profile_id), - 'thumb' => $item->avatarUrl(), - 'local' => (bool) !$item->domain, - 'post_count' => $item->statuses()->count() - ] - ]; - }); - } - }); - } - } + return $tags; + } + }); + $this->tokens['hashtags'] = $tokens; + } - public function results(Request $request) - { - $this->validate($request, [ - 'q' => 'required|string|min:1', - ]); + protected function getPlaces() + { + $tag = $this->term; + // $key = $this->cacheKey . 'places:' . $this->hash; + // $ttl = now()->addHours(12); + // $tokens = Cache::remember($key, $ttl, function() use($tag) { + $htag = Str::contains($tag, ',') == true ? explode(',', $tag) : [$tag]; + $hashtags = Place::select('id', 'name', 'slug', 'country') + ->where('name', 'like', '%'.$htag[0].'%') + ->paginate(20); + $tags = []; + if ($hashtags->count() > 0) { + $tags = $hashtags->map(function ($item, $key) { + return [ + 'count' => null, + 'url' => $item->url(), + 'type' => 'place', + 'value' => $item->name.', '.$item->country, + 'tokens' => '', + 'name' => null, + 'city' => $item->name, + 'country' => $item->country, + ]; + }); + // return $tags; + } + // }); + $this->tokens['places'] = $tags; + $this->tokens['placesPagination'] = [ + 'total' => $hashtags->total(), + 'current_page' => $hashtags->currentPage(), + 'last_page' => $hashtags->lastPage(), + ]; + } - return view('search.results'); - } + protected function getProfiles() + { + $tag = $this->term; + $remoteKey = $this->cacheKey.'profiles:remote:'.$this->hash; + $key = $this->cacheKey.'profiles:'.$this->hash; + $remoteTtl = now()->addMinutes(15); + $ttl = now()->addHours(2); + if (Helpers::validateUrl($tag) != false && + Helpers::validateLocalUrl($tag) != true && + (bool) config_cache('federation.activitypub.enabled') == true && + config('federation.activitypub.remoteFollow') == true + ) { + $remote = Helpers::fetchFromUrl($tag); + if (isset($remote['type']) && + $remote['type'] == 'Person' + ) { + $this->tokens['profiles'] = Cache::remember($remoteKey, $remoteTtl, function () use ($tag) { + $item = Helpers::profileFirstOrNew($tag); + $tokens = [[ + 'count' => 1, + 'url' => $item->url(), + 'type' => 'profile', + 'value' => $item->username, + 'tokens' => [$item->username], + 'name' => $item->name, + 'entity' => [ + 'id' => (string) $item->id, + 'following' => $item->followedBy(Auth::user()->profile), + 'follow_request' => $item->hasFollowRequestById(Auth::user()->profile_id), + 'thumb' => $item->avatarUrl(), + 'local' => (bool) ! $item->domain, + 'post_count' => $item->statuses()->count(), + ], + ]]; - protected function webfingerSearch() - { - $wfs = WebfingerService::lookup($this->term); + return $tokens; + }); + } + } else { + $this->tokens['profiles'] = Cache::remember($key, $ttl, function () use ($tag) { + if (Str::startsWith($tag, '@')) { + $tag = substr($tag, 1); + } + $users = Profile::select('status', 'domain', 'username', 'name', 'id') + ->whereNull('status') + ->where('username', 'like', '%'.$tag.'%') + ->limit(20) + ->orderBy('domain') + ->get(); - if(empty($wfs)) { - return; - } + if ($users->count() > 0) { + return $users->map(function ($item, $key) { + return [ + 'count' => 0, + 'url' => $item->url(), + 'type' => 'profile', + 'value' => $item->username, + 'tokens' => [$item->username], + 'name' => $item->name, + 'avatar' => $item->avatarUrl(), + 'id' => (string) $item->id, + 'entity' => [ + 'id' => (string) $item->id, + 'following' => $item->followedBy(Auth::user()->profile), + 'follow_request' => $item->hasFollowRequestById(Auth::user()->profile_id), + 'thumb' => $item->avatarUrl(), + 'local' => (bool) ! $item->domain, + 'post_count' => $item->statuses()->count(), + ], + ]; + }); + } + }); + } + } - $this->tokens['profiles'] = [ - [ - 'count' => 1, - 'url' => $wfs['url'], - 'type' => 'profile', - 'value' => $wfs['username'], - 'tokens' => [$wfs['username']], - 'name' => $wfs['display_name'], - 'entity' => [ - 'id' => (string) $wfs['id'], - 'following' => null, - 'follow_request' => null, - 'thumb' => $wfs['avatar'], - 'local' => (bool) $wfs['local'] - ] - ] - ]; - return; - } + public function results(Request $request) + { + $this->validate($request, [ + 'q' => 'required|string|min:1', + ]); - protected function remotePostLookup() - { - $tag = $this->term; - $hash = hash('sha256', $tag); - $local = Helpers::validateLocalUrl($tag); - $valid = Helpers::validateUrl($tag); + return view('search.results'); + } - if($valid == false || $local == true) { - return; - } + protected function webfingerSearch() + { + $wfs = WebfingerService::lookup($this->term); - if(Status::whereUri($tag)->whereLocal(false)->exists()) { - $item = Status::whereUri($tag)->first(); - $media = $item->firstMedia(); - $url = null; - if($media) { - $url = $media->remote_url; - } - $this->tokens['posts'] = [[ - 'count' => 0, - 'url' => "/i/web/post/_/$item->profile_id/$item->id", - 'type' => 'status', - 'username' => $item->profile->username, - 'caption' => $item->rendered ?? $item->caption, - 'thumb' => $url, - 'timestamp' => $item->created_at->diffForHumans() - ]]; - } + if (empty($wfs)) { + return; + } - $remote = Helpers::fetchFromUrl($tag); + $this->tokens['profiles'] = [ + [ + 'count' => 1, + 'url' => $wfs['url'], + 'type' => 'profile', + 'value' => $wfs['username'], + 'tokens' => [$wfs['username']], + 'name' => $wfs['display_name'], + 'entity' => [ + 'id' => (string) $wfs['id'], + 'following' => null, + 'follow_request' => null, + 'thumb' => $wfs['avatar'], + 'local' => (bool) $wfs['local'], + ], + ], + ]; - if(isset($remote['type']) && $remote['type'] == 'Note') { - $item = Helpers::statusFetch($tag); - $media = $item->firstMedia(); - $url = null; - if($media) { - $url = $media->remote_url; - } - $this->tokens['posts'] = [[ - 'count' => 0, - 'url' => "/i/web/post/_/$item->profile_id/$item->id", - 'type' => 'status', - 'username' => $item->profile->username, - 'caption' => $item->rendered ?? $item->caption, - 'thumb' => $url, - 'timestamp' => $item->created_at->diffForHumans() - ]]; - } - } + } - protected function remoteLookupSearch() - { - if(!Helpers::validateUrl($this->term)) { - return; - } - $this->getProfiles(); - $this->remotePostLookup(); - } + protected function remotePostLookup() + { + $tag = $this->term; + $hash = hash('sha256', $tag); + $local = Helpers::validateLocalUrl($tag); + $valid = Helpers::validateUrl($tag); + + if ($valid == false || $local == true) { + return; + } + + if (Status::whereUri($tag)->whereLocal(false)->exists()) { + $item = Status::whereUri($tag)->first(); + $media = $item->firstMedia(); + $url = null; + if ($media) { + $url = $media->remote_url; + } + $this->tokens['posts'] = [[ + 'count' => 0, + 'url' => "/i/web/post/_/$item->profile_id/$item->id", + 'type' => 'status', + 'username' => $item->profile->username, + 'caption' => $item->rendered ?? $item->caption, + 'thumb' => $url, + 'timestamp' => $item->created_at->diffForHumans(), + ]]; + } + + $remote = Helpers::fetchFromUrl($tag); + + if (isset($remote['type']) && $remote['type'] == 'Note') { + $item = Helpers::statusFetch($tag); + $media = $item->firstMedia(); + $url = null; + if ($media) { + $url = $media->remote_url; + } + $this->tokens['posts'] = [[ + 'count' => 0, + 'url' => "/i/web/post/_/$item->profile_id/$item->id", + 'type' => 'status', + 'username' => $item->profile->username, + 'caption' => $item->rendered ?? $item->caption, + 'thumb' => $url, + 'timestamp' => $item->created_at->diffForHumans(), + ]]; + } + } + + protected function remoteLookupSearch() + { + if (! Helpers::validateUrl($this->term)) { + return; + } + $this->getProfiles(); + $this->remotePostLookup(); + } } diff --git a/app/Http/Controllers/StatusController.php b/app/Http/Controllers/StatusController.php index 4a3b3552d..7f77f9a81 100644 --- a/app/Http/Controllers/StatusController.php +++ b/app/Http/Controllers/StatusController.php @@ -78,7 +78,7 @@ class StatusController extends Controller ]); } - if ($request->wantsJson() && config_cache('federation.activitypub.enabled')) { + if ($request->wantsJson() && (bool) config_cache('federation.activitypub.enabled')) { return $this->showActivityPub($request, $status); } diff --git a/app/Jobs/SharePipeline/SharePipeline.php b/app/Jobs/SharePipeline/SharePipeline.php index 4eca4e1ab..734c44231 100644 --- a/app/Jobs/SharePipeline/SharePipeline.php +++ b/app/Jobs/SharePipeline/SharePipeline.php @@ -2,9 +2,15 @@ namespace App\Jobs\SharePipeline; -use Cache, Log; -use Illuminate\Support\Facades\Redis; -use App\{Status, Notification}; +use App\Jobs\HomeFeedPipeline\FeedInsertPipeline; +use App\Notification; +use App\Services\ReblogService; +use App\Services\StatusService; +use App\Status; +use App\Transformer\ActivityPub\Verb\Announce; +use App\Util\ActivityPub\HttpSignature; +use GuzzleHttp\Client; +use GuzzleHttp\Pool; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; @@ -12,141 +18,136 @@ use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use League\Fractal; use League\Fractal\Serializer\ArraySerializer; -use App\Transformer\ActivityPub\Verb\Announce; -use GuzzleHttp\{Pool, Client, Promise}; -use App\Util\ActivityPub\HttpSignature; -use App\Services\ReblogService; -use App\Services\StatusService; -use App\Jobs\HomeFeedPipeline\FeedInsertPipeline; class SharePipeline implements ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - protected $status; + protected $status; - /** - * Delete the job if its models no longer exist. - * - * @var bool - */ - public $deleteWhenMissingModels = true; + /** + * Delete the job if its models no longer exist. + * + * @var bool + */ + public $deleteWhenMissingModels = true; - /** - * Create a new job instance. - * - * @return void - */ - public function __construct(Status $status) - { - $this->status = $status; - } + /** + * Create a new job instance. + * + * @return void + */ + public function __construct(Status $status) + { + $this->status = $status; + } - /** - * Execute the job. - * - * @return void - */ - public function handle() - { - $status = $this->status; - $parent = Status::find($this->status->reblog_of_id); - if(!$parent) { + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + $status = $this->status; + $parent = Status::find($this->status->reblog_of_id); + if (! $parent) { return; } - $actor = $status->profile; - $target = $parent->profile; + $actor = $status->profile; + $target = $parent->profile; - if ($status->uri !== null) { - // Ignore notifications to remote statuses - return; - } + if ($status->uri !== null) { + // Ignore notifications to remote statuses + return; + } - if($target->id === $status->profile_id) { - $this->remoteAnnounceDeliver(); - return true; - } + if ($target->id === $status->profile_id) { + $this->remoteAnnounceDeliver(); - ReblogService::addPostReblog($parent->profile_id, $status->id); + return true; + } - $parent->reblogs_count = $parent->reblogs_count + 1; - $parent->save(); - StatusService::del($parent->id); + ReblogService::addPostReblog($parent->profile_id, $status->id); - Notification::firstOrCreate( - [ - 'profile_id' => $target->id, - 'actor_id' => $actor->id, - 'action' => 'share', - 'item_type' => 'App\Status', - 'item_id' => $status->reblog_of_id ?? $status->id, - ] - ); + $parent->reblogs_count = $parent->reblogs_count + 1; + $parent->save(); + StatusService::del($parent->id); - FeedInsertPipeline::dispatch($status->id, $status->profile_id)->onQueue('feed'); + Notification::firstOrCreate( + [ + 'profile_id' => $target->id, + 'actor_id' => $actor->id, + 'action' => 'share', + 'item_type' => 'App\Status', + 'item_id' => $status->reblog_of_id ?? $status->id, + ] + ); - return $this->remoteAnnounceDeliver(); - } + FeedInsertPipeline::dispatch($status->id, $status->profile_id)->onQueue('feed'); - public function remoteAnnounceDeliver() - { - if(config('app.env') !== 'production' || config_cache('federation.activitypub.enabled') == false) { - return true; - } - $status = $this->status; - $profile = $status->profile; + return $this->remoteAnnounceDeliver(); + } - $fractal = new Fractal\Manager(); - $fractal->setSerializer(new ArraySerializer()); - $resource = new Fractal\Resource\Item($status, new Announce()); - $activity = $fractal->createData($resource)->toArray(); + public function remoteAnnounceDeliver() + { + if (config('app.env') !== 'production' || (bool) config_cache('federation.activitypub.enabled') == false) { + return true; + } + $status = $this->status; + $profile = $status->profile; - $audience = $status->profile->getAudienceInbox(); + $fractal = new Fractal\Manager(); + $fractal->setSerializer(new ArraySerializer()); + $resource = new Fractal\Resource\Item($status, new Announce()); + $activity = $fractal->createData($resource)->toArray(); - if(empty($audience) || $status->scope != 'public') { - // Return on profiles with no remote followers - return; - } + $audience = $status->profile->getAudienceInbox(); - $payload = json_encode($activity); + if (empty($audience) || $status->scope != 'public') { + // Return on profiles with no remote followers + return; + } - $client = new Client([ - 'timeout' => config('federation.activitypub.delivery.timeout') - ]); + $payload = json_encode($activity); - $version = config('pixelfed.version'); - $appUrl = config('app.url'); - $userAgent = "(Pixelfed/{$version}; +{$appUrl})"; + $client = new Client([ + 'timeout' => config('federation.activitypub.delivery.timeout'), + ]); - $requests = function($audience) use ($client, $activity, $profile, $payload, $userAgent) { - foreach($audience as $url) { - $headers = HttpSignature::sign($profile, $url, $activity, [ - 'Content-Type' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', - 'User-Agent' => $userAgent, - ]); - yield function() use ($client, $url, $headers, $payload) { - return $client->postAsync($url, [ - 'curl' => [ - CURLOPT_HTTPHEADER => $headers, - CURLOPT_POSTFIELDS => $payload, - CURLOPT_HEADER => true - ] - ]); - }; - } - }; + $version = config('pixelfed.version'); + $appUrl = config('app.url'); + $userAgent = "(Pixelfed/{$version}; +{$appUrl})"; - $pool = new Pool($client, $requests($audience), [ - 'concurrency' => config('federation.activitypub.delivery.concurrency'), - 'fulfilled' => function ($response, $index) { - }, - 'rejected' => function ($reason, $index) { - } - ]); + $requests = function ($audience) use ($client, $activity, $profile, $payload, $userAgent) { + foreach ($audience as $url) { + $headers = HttpSignature::sign($profile, $url, $activity, [ + 'Content-Type' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', + 'User-Agent' => $userAgent, + ]); + yield function () use ($client, $url, $headers, $payload) { + return $client->postAsync($url, [ + 'curl' => [ + CURLOPT_HTTPHEADER => $headers, + CURLOPT_POSTFIELDS => $payload, + CURLOPT_HEADER => true, + ], + ]); + }; + } + }; - $promise = $pool->promise(); + $pool = new Pool($client, $requests($audience), [ + 'concurrency' => config('federation.activitypub.delivery.concurrency'), + 'fulfilled' => function ($response, $index) { + }, + 'rejected' => function ($reason, $index) { + }, + ]); - $promise->wait(); + $promise = $pool->promise(); - } + $promise->wait(); + + } } diff --git a/app/Jobs/SharePipeline/UndoSharePipeline.php b/app/Jobs/SharePipeline/UndoSharePipeline.php index 1435688d9..af3239953 100644 --- a/app/Jobs/SharePipeline/UndoSharePipeline.php +++ b/app/Jobs/SharePipeline/UndoSharePipeline.php @@ -2,9 +2,15 @@ namespace App\Jobs\SharePipeline; -use Cache, Log; -use Illuminate\Support\Facades\Redis; -use App\{Status, Notification}; +use App\Jobs\HomeFeedPipeline\FeedRemovePipeline; +use App\Notification; +use App\Services\ReblogService; +use App\Services\StatusService; +use App\Status; +use App\Transformer\ActivityPub\Verb\UndoAnnounce; +use App\Util\ActivityPub\HttpSignature; +use GuzzleHttp\Client; +use GuzzleHttp\Pool; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; @@ -12,128 +18,125 @@ use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use League\Fractal; use League\Fractal\Serializer\ArraySerializer; -use App\Transformer\ActivityPub\Verb\UndoAnnounce; -use GuzzleHttp\{Pool, Client, Promise}; -use App\Util\ActivityPub\HttpSignature; -use App\Services\ReblogService; -use App\Services\StatusService; -use App\Jobs\HomeFeedPipeline\FeedRemovePipeline; class UndoSharePipeline implements ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - protected $status; - public $deleteWhenMissingModels = true; + use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public function __construct(Status $status) - { - $this->status = $status; - } + protected $status; - public function handle() - { - $status = $this->status; - $actor = $status->profile; - $parent = Status::find($status->reblog_of_id); + public $deleteWhenMissingModels = true; - FeedRemovePipeline::dispatch($status->id, $status->profile_id)->onQueue('feed'); + public function __construct(Status $status) + { + $this->status = $status; + } - if($parent) { - $target = $parent->profile_id; - ReblogService::removePostReblog($parent->profile_id, $status->id); + public function handle() + { + $status = $this->status; + $actor = $status->profile; + $parent = Status::find($status->reblog_of_id); - if($parent->reblogs_count > 0) { - $parent->reblogs_count = $parent->reblogs_count - 1; - $parent->save(); - StatusService::del($parent->id); - } + FeedRemovePipeline::dispatch($status->id, $status->profile_id)->onQueue('feed'); - $notification = Notification::whereProfileId($target) - ->whereActorId($status->profile_id) - ->whereAction('share') - ->whereItemId($status->reblog_of_id) - ->whereItemType('App\Status') - ->first(); + if ($parent) { + $target = $parent->profile_id; + ReblogService::removePostReblog($parent->profile_id, $status->id); - if($notification) { - $notification->forceDelete(); - } - } + if ($parent->reblogs_count > 0) { + $parent->reblogs_count = $parent->reblogs_count - 1; + $parent->save(); + StatusService::del($parent->id); + } - if ($status->uri != null) { - return; - } + $notification = Notification::whereProfileId($target) + ->whereActorId($status->profile_id) + ->whereAction('share') + ->whereItemId($status->reblog_of_id) + ->whereItemType('App\Status') + ->first(); - if(config('app.env') !== 'production' || config_cache('federation.activitypub.enabled') == false) { - return $status->delete(); - } else { - return $this->remoteAnnounceDeliver(); - } - } + if ($notification) { + $notification->forceDelete(); + } + } - public function remoteAnnounceDeliver() - { - if(config('app.env') !== 'production' || config_cache('federation.activitypub.enabled') == false) { + if ($status->uri != null) { + return; + } + + if (config('app.env') !== 'production' || (bool) config_cache('federation.activitypub.enabled') == false) { + return $status->delete(); + } else { + return $this->remoteAnnounceDeliver(); + } + } + + public function remoteAnnounceDeliver() + { + if (config('app.env') !== 'production' || (bool) config_cache('federation.activitypub.enabled') == false) { $status->delete(); - return 1; - } - $status = $this->status; - $profile = $status->profile; + return 1; + } - $fractal = new Fractal\Manager(); - $fractal->setSerializer(new ArraySerializer()); - $resource = new Fractal\Resource\Item($status, new UndoAnnounce()); - $activity = $fractal->createData($resource)->toArray(); + $status = $this->status; + $profile = $status->profile; - $audience = $status->profile->getAudienceInbox(); + $fractal = new Fractal\Manager(); + $fractal->setSerializer(new ArraySerializer()); + $resource = new Fractal\Resource\Item($status, new UndoAnnounce()); + $activity = $fractal->createData($resource)->toArray(); - if(empty($audience) || $status->scope != 'public') { - return 1; - } + $audience = $status->profile->getAudienceInbox(); - $payload = json_encode($activity); + if (empty($audience) || $status->scope != 'public') { + return 1; + } - $client = new Client([ - 'timeout' => config('federation.activitypub.delivery.timeout') - ]); + $payload = json_encode($activity); - $version = config('pixelfed.version'); - $appUrl = config('app.url'); - $userAgent = "(Pixelfed/{$version}; +{$appUrl})"; + $client = new Client([ + 'timeout' => config('federation.activitypub.delivery.timeout'), + ]); - $requests = function($audience) use ($client, $activity, $profile, $payload, $userAgent) { - foreach($audience as $url) { - $headers = HttpSignature::sign($profile, $url, $activity, [ - 'Content-Type' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', - 'User-Agent' => $userAgent, - ]); - yield function() use ($client, $url, $headers, $payload) { - return $client->postAsync($url, [ - 'curl' => [ - CURLOPT_HTTPHEADER => $headers, - CURLOPT_POSTFIELDS => $payload, - CURLOPT_HEADER => true - ] - ]); - }; - } - }; + $version = config('pixelfed.version'); + $appUrl = config('app.url'); + $userAgent = "(Pixelfed/{$version}; +{$appUrl})"; - $pool = new Pool($client, $requests($audience), [ - 'concurrency' => config('federation.activitypub.delivery.concurrency'), - 'fulfilled' => function ($response, $index) { - }, - 'rejected' => function ($reason, $index) { - } - ]); + $requests = function ($audience) use ($client, $activity, $profile, $payload, $userAgent) { + foreach ($audience as $url) { + $headers = HttpSignature::sign($profile, $url, $activity, [ + 'Content-Type' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', + 'User-Agent' => $userAgent, + ]); + yield function () use ($client, $url, $headers, $payload) { + return $client->postAsync($url, [ + 'curl' => [ + CURLOPT_HTTPHEADER => $headers, + CURLOPT_POSTFIELDS => $payload, + CURLOPT_HEADER => true, + ], + ]); + }; + } + }; - $promise = $pool->promise(); + $pool = new Pool($client, $requests($audience), [ + 'concurrency' => config('federation.activitypub.delivery.concurrency'), + 'fulfilled' => function ($response, $index) { + }, + 'rejected' => function ($reason, $index) { + }, + ]); - $promise->wait(); + $promise = $pool->promise(); - $status->delete(); + $promise->wait(); - return 1; - } + $status->delete(); + + return 1; + } } diff --git a/app/Jobs/StatusPipeline/StatusDelete.php b/app/Jobs/StatusPipeline/StatusDelete.php index dbbfad5ac..d85ebdc4a 100644 --- a/app/Jobs/StatusPipeline/StatusDelete.php +++ b/app/Jobs/StatusPipeline/StatusDelete.php @@ -2,126 +2,122 @@ namespace App\Jobs\StatusPipeline; -use DB, Cache, Storage; -use App\{ - AccountInterstitial, - Bookmark, - CollectionItem, - DirectMessage, - Like, - Media, - MediaTag, - Mention, - Notification, - Report, - Status, - StatusArchived, - StatusHashtag, - StatusView -}; +use App\AccountInterstitial; +use App\Bookmark; +use App\CollectionItem; +use App\DirectMessage; +use App\Jobs\MediaPipeline\MediaDeletePipeline; +use App\Like; +use App\Media; +use App\MediaTag; +use App\Mention; +use App\Notification; +use App\Report; +use App\Services\CollectionService; +use App\Services\NotificationService; +use App\Services\StatusService; +use App\Status; +use App\StatusArchived; +use App\StatusHashtag; +use App\StatusView; +use App\Transformer\ActivityPub\Verb\DeleteNote; +use App\Util\ActivityPub\HttpSignature; +use Cache; +use GuzzleHttp\Client; +use GuzzleHttp\Pool; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use League\Fractal; -use Illuminate\Support\Str; use League\Fractal\Serializer\ArraySerializer; -use App\Transformer\ActivityPub\Verb\DeleteNote; -use App\Util\ActivityPub\Helpers; -use GuzzleHttp\Pool; -use GuzzleHttp\Client; -use GuzzleHttp\Promise; -use App\Util\ActivityPub\HttpSignature; -use App\Services\CollectionService; -use App\Services\StatusService; -use App\Services\NotificationService; -use App\Jobs\MediaPipeline\MediaDeletePipeline; class StatusDelete implements ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - protected $status; + protected $status; - /** - * Delete the job if its models no longer exist. - * - * @var bool - */ - public $deleteWhenMissingModels = true; + /** + * Delete the job if its models no longer exist. + * + * @var bool + */ + public $deleteWhenMissingModels = true; public $timeout = 900; + public $tries = 2; - /** - * Create a new job instance. - * - * @return void - */ - public function __construct(Status $status) - { - $this->status = $status; - } + /** + * Create a new job instance. + * + * @return void + */ + public function __construct(Status $status) + { + $this->status = $status; + } - /** - * Execute the job. - * - * @return void - */ - public function handle() - { - $status = $this->status; - $profile = $this->status->profile; + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + $status = $this->status; + $profile = $this->status->profile; - StatusService::del($status->id, true); - if($profile) { - if(in_array($status->type, ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])) { - $profile->status_count = $profile->status_count - 1; - $profile->save(); - } - } + StatusService::del($status->id, true); + if ($profile) { + if (in_array($status->type, ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])) { + $profile->status_count = $profile->status_count - 1; + $profile->save(); + } + } - Cache::forget('pf:atom:user-feed:by-id:' . $status->profile_id); + Cache::forget('pf:atom:user-feed:by-id:'.$status->profile_id); - if(config_cache('federation.activitypub.enabled') == true) { - return $this->fanoutDelete($status); - } else { - return $this->unlinkRemoveMedia($status); - } - } + if ((bool) config_cache('federation.activitypub.enabled') == true) { + return $this->fanoutDelete($status); + } else { + return $this->unlinkRemoveMedia($status); + } + } - public function unlinkRemoveMedia($status) - { + public function unlinkRemoveMedia($status) + { Media::whereStatusId($status->id) - ->get() - ->each(function($media) { - MediaDeletePipeline::dispatch($media); - }); + ->get() + ->each(function ($media) { + MediaDeletePipeline::dispatch($media); + }); - if($status->in_reply_to_id) { - $parent = Status::findOrFail($status->in_reply_to_id); - --$parent->reply_count; - $parent->save(); - StatusService::del($parent->id); - } + if ($status->in_reply_to_id) { + $parent = Status::findOrFail($status->in_reply_to_id); + $parent->reply_count--; + $parent->save(); + StatusService::del($parent->id); + } Bookmark::whereStatusId($status->id)->delete(); CollectionItem::whereObjectType('App\Status') ->whereObjectId($status->id) ->get() - ->each(function($col) { + ->each(function ($col) { CollectionService::removeItem($col->collection_id, $col->object_id); $col->delete(); - }); + }); $dms = DirectMessage::whereStatusId($status->id)->get(); - foreach($dms as $dm) { + foreach ($dms as $dm) { $not = Notification::whereItemType('App\DirectMessage') ->whereItemId($dm->id) ->first(); - if($not) { + if ($not) { NotificationService::del($not->profile_id, $not->id); $not->forceDeleteQuietly(); } @@ -130,11 +126,11 @@ class StatusDelete implements ShouldQueue Like::whereStatusId($status->id)->delete(); $mediaTags = MediaTag::where('status_id', $status->id)->get(); - foreach($mediaTags as $mtag) { + foreach ($mediaTags as $mtag) { $not = Notification::whereItemType('App\MediaTag') ->whereItemId($mtag->id) ->first(); - if($not) { + if ($not) { NotificationService::del($not->profile_id, $not->id); $not->forceDeleteQuietly(); } @@ -142,85 +138,85 @@ class StatusDelete implements ShouldQueue } Mention::whereStatusId($status->id)->forceDelete(); - Notification::whereItemType('App\Status') - ->whereItemId($status->id) - ->forceDelete(); + Notification::whereItemType('App\Status') + ->whereItemId($status->id) + ->forceDelete(); - Report::whereObjectType('App\Status') - ->whereObjectId($status->id) - ->delete(); + Report::whereObjectType('App\Status') + ->whereObjectId($status->id) + ->delete(); StatusArchived::whereStatusId($status->id)->delete(); StatusHashtag::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]); - AccountInterstitial::where('item_type', 'App\Status') - ->where('item_id', $status->id) - ->delete(); + AccountInterstitial::where('item_type', 'App\Status') + ->where('item_id', $status->id) + ->delete(); - $status->delete(); - - return 1; - } - - public function fanoutDelete($status) - { - $profile = $status->profile; - - if(!$profile) { - return; - } - - $audience = $status->profile->getAudienceInbox(); - - $fractal = new Fractal\Manager(); - $fractal->setSerializer(new ArraySerializer()); - $resource = new Fractal\Resource\Item($status, new DeleteNote()); - $activity = $fractal->createData($resource)->toArray(); - - $this->unlinkRemoveMedia($status); - - $payload = json_encode($activity); - - $client = new Client([ - 'timeout' => config('federation.activitypub.delivery.timeout') - ]); - - $version = config('pixelfed.version'); - $appUrl = config('app.url'); - $userAgent = "(Pixelfed/{$version}; +{$appUrl})"; - - $requests = function($audience) use ($client, $activity, $profile, $payload, $userAgent) { - foreach($audience as $url) { - $headers = HttpSignature::sign($profile, $url, $activity, [ - 'Content-Type' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', - 'User-Agent' => $userAgent, - ]); - yield function() use ($client, $url, $headers, $payload) { - return $client->postAsync($url, [ - 'curl' => [ - CURLOPT_HTTPHEADER => $headers, - CURLOPT_POSTFIELDS => $payload, - CURLOPT_HEADER => true - ] - ]); - }; - } - }; - - $pool = new Pool($client, $requests($audience), [ - 'concurrency' => config('federation.activitypub.delivery.concurrency'), - 'fulfilled' => function ($response, $index) { - }, - 'rejected' => function ($reason, $index) { - } - ]); - - $promise = $pool->promise(); - - $promise->wait(); + $status->delete(); return 1; - } + } + + public function fanoutDelete($status) + { + $profile = $status->profile; + + if (! $profile) { + return; + } + + $audience = $status->profile->getAudienceInbox(); + + $fractal = new Fractal\Manager(); + $fractal->setSerializer(new ArraySerializer()); + $resource = new Fractal\Resource\Item($status, new DeleteNote()); + $activity = $fractal->createData($resource)->toArray(); + + $this->unlinkRemoveMedia($status); + + $payload = json_encode($activity); + + $client = new Client([ + 'timeout' => config('federation.activitypub.delivery.timeout'), + ]); + + $version = config('pixelfed.version'); + $appUrl = config('app.url'); + $userAgent = "(Pixelfed/{$version}; +{$appUrl})"; + + $requests = function ($audience) use ($client, $activity, $profile, $payload, $userAgent) { + foreach ($audience as $url) { + $headers = HttpSignature::sign($profile, $url, $activity, [ + 'Content-Type' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', + 'User-Agent' => $userAgent, + ]); + yield function () use ($client, $url, $headers, $payload) { + return $client->postAsync($url, [ + 'curl' => [ + CURLOPT_HTTPHEADER => $headers, + CURLOPT_POSTFIELDS => $payload, + CURLOPT_HEADER => true, + ], + ]); + }; + } + }; + + $pool = new Pool($client, $requests($audience), [ + 'concurrency' => config('federation.activitypub.delivery.concurrency'), + 'fulfilled' => function ($response, $index) { + }, + 'rejected' => function ($reason, $index) { + }, + ]); + + $promise = $pool->promise(); + + $promise->wait(); + + return 1; + } } diff --git a/app/Jobs/StatusPipeline/StatusEntityLexer.php b/app/Jobs/StatusPipeline/StatusEntityLexer.php index 872594a96..5c37838dc 100644 --- a/app/Jobs/StatusPipeline/StatusEntityLexer.php +++ b/app/Jobs/StatusPipeline/StatusEntityLexer.php @@ -3,12 +3,16 @@ namespace App\Jobs\StatusPipeline; use App\Hashtag; +use App\Jobs\HomeFeedPipeline\FeedInsertPipeline; use App\Jobs\MentionPipeline\MentionPipeline; use App\Mention; use App\Profile; +use App\Services\AdminShadowFilterService; +use App\Services\PublicTimelineService; +use App\Services\StatusService; +use App\Services\UserFilterService; use App\Status; use App\StatusHashtag; -use App\Services\PublicTimelineService; use App\Util\Lexer\Autolink; use App\Util\Lexer\Extractor; use App\Util\Sentiment\Bouncer; @@ -19,18 +23,15 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; -use App\Services\StatusService; -use App\Services\UserFilterService; -use App\Services\AdminShadowFilterService; -use App\Jobs\HomeFeedPipeline\FeedInsertPipeline; -use App\Jobs\HomeFeedPipeline\HashtagInsertFanoutPipeline; class StatusEntityLexer implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; protected $status; + protected $entities; + protected $autolink; /** @@ -60,12 +61,12 @@ class StatusEntityLexer implements ShouldQueue $profile = $this->status->profile; $status = $this->status; - if(in_array($status->type, ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])) { + if (in_array($status->type, ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])) { $profile->status_count = $profile->status_count + 1; $profile->save(); } - if($profile->no_autolink == false) { + if ($profile->no_autolink == false) { $this->parseEntities(); } } @@ -103,16 +104,16 @@ class StatusEntityLexer implements ShouldQueue $status = $this->status; foreach ($tags as $tag) { - if(mb_strlen($tag) > 124) { + if (mb_strlen($tag) > 124) { continue; } DB::transaction(function () use ($status, $tag) { $slug = str_slug($tag, '-', false); $hashtag = Hashtag::firstOrCreate([ - 'slug' => $slug + 'slug' => $slug, ], [ - 'name' => $tag + 'name' => $tag, ]); StatusHashtag::firstOrCreate( @@ -136,11 +137,11 @@ class StatusEntityLexer implements ShouldQueue foreach ($mentions as $mention) { $mentioned = Profile::whereUsername($mention)->first(); - if (empty($mentioned) || !isset($mentioned->id)) { + if (empty($mentioned) || ! isset($mentioned->id)) { continue; } $blocks = UserFilterService::blocks($mentioned->id); - if($blocks && in_array($status->profile_id, $blocks)) { + if ($blocks && in_array($status->profile_id, $blocks)) { continue; } @@ -161,8 +162,8 @@ class StatusEntityLexer implements ShouldQueue $status = $this->status; StatusService::refresh($status->id); - if(config('exp.cached_home_timeline')) { - if( $status->in_reply_to_id === null && + if (config('exp.cached_home_timeline')) { + if ($status->in_reply_to_id === null && in_array($status->scope, ['public', 'unlisted', 'private']) ) { FeedInsertPipeline::dispatch($status->id, $status->profile_id)->onQueue('feed'); @@ -179,28 +180,28 @@ class StatusEntityLexer implements ShouldQueue 'photo:album', 'video', 'video:album', - 'photo:video:album' + 'photo:video:album', ]; - if(config_cache('pixelfed.bouncer.enabled')) { + if (config_cache('pixelfed.bouncer.enabled')) { Bouncer::get($status); } - Cache::forget('pf:atom:user-feed:by-id:' . $status->profile_id); + Cache::forget('pf:atom:user-feed:by-id:'.$status->profile_id); $hideNsfw = config('instance.hide_nsfw_on_public_feeds'); - if( $status->uri == null && + if ($status->uri == null && $status->scope == 'public' && in_array($status->type, $types) && $status->in_reply_to_id === null && $status->reblog_of_id === null && ($hideNsfw ? $status->is_nsfw == false : true) ) { - if(AdminShadowFilterService::canAddToPublicFeedByProfileId($status->profile_id)) { + if (AdminShadowFilterService::canAddToPublicFeedByProfileId($status->profile_id)) { PublicTimelineService::add($status->id); } } - if(config_cache('federation.activitypub.enabled') == true && config('app.env') == 'production') { + if ((bool) config_cache('federation.activitypub.enabled') == true && config('app.env') == 'production') { StatusActivityPubDeliver::dispatch($status); } } diff --git a/app/Services/LandingService.php b/app/Services/LandingService.php index 213e63910..f51822df2 100644 --- a/app/Services/LandingService.php +++ b/app/Services/LandingService.php @@ -85,7 +85,7 @@ class LandingService 'media_types' => config_cache('pixelfed.media_types'), ], 'features' => [ - 'federation' => config_cache('federation.activitypub.enabled'), + 'federation' => (bool) config_cache('federation.activitypub.enabled'), 'timelines' => [ 'local' => true, 'network' => (bool) config_cache('federation.network_timeline'), diff --git a/app/Util/ActivityPub/Outbox.php b/app/Util/ActivityPub/Outbox.php index 43adb36e3..aba34955e 100644 --- a/app/Util/ActivityPub/Outbox.php +++ b/app/Util/ActivityPub/Outbox.php @@ -2,34 +2,32 @@ namespace App\Util\ActivityPub; -use App\Profile; -use App\Status; -use League\Fractal; use App\Http\Controllers\ProfileController; -use App\Transformer\ActivityPub\ProfileOutbox; +use App\Status; use App\Transformer\ActivityPub\Verb\CreateNote; +use League\Fractal; -class Outbox { +class Outbox +{ + public static function get($profile) + { + abort_if(! (bool) config_cache('federation.activitypub.enabled'), 404); + abort_if(! config('federation.activitypub.outbox'), 404); - public static function get($profile) - { - abort_if(!config_cache('federation.activitypub.enabled'), 404); - abort_if(!config('federation.activitypub.outbox'), 404); - - if($profile->status != null) { + if ($profile->status != null) { return ProfileController::accountCheck($profile); } - if($profile->is_private) { - return ['error'=>'403', 'msg' => 'private profile']; + if ($profile->is_private) { + return ['error' => '403', 'msg' => 'private profile']; } $timeline = $profile - ->statuses() - ->whereScope('public') - ->orderBy('created_at', 'desc') - ->take(10) - ->get(); + ->statuses() + ->whereScope('public') + ->orderBy('created_at', 'desc') + ->take(10) + ->get(); $count = Status::whereProfileId($profile->id)->count(); @@ -38,14 +36,14 @@ class Outbox { $res = $fractal->createData($resource)->toArray(); $outbox = [ - '@context' => 'https://www.w3.org/ns/activitystreams', - '_debug' => 'Outbox only supports latest 10 objects, pagination is not supported', - 'id' => $profile->permalink('/outbox'), - 'type' => 'OrderedCollection', - 'totalItems' => $count, - 'orderedItems' => $res['data'] + '@context' => 'https://www.w3.org/ns/activitystreams', + '_debug' => 'Outbox only supports latest 10 objects, pagination is not supported', + 'id' => $profile->permalink('/outbox'), + 'type' => 'OrderedCollection', + 'totalItems' => $count, + 'orderedItems' => $res['data'], ]; - return $outbox; - } + return $outbox; + } } diff --git a/resources/views/admin/diagnostics/home.blade.php b/resources/views/admin/diagnostics/home.blade.php index 0d8b21e47..2f592b1cc 100644 --- a/resources/views/admin/diagnostics/home.blade.php +++ b/resources/views/admin/diagnostics/home.blade.php @@ -298,7 +298,7 @@ FEDERATION ACTIVITY_PUB - {{config_cache('federation.activitypub.enabled') ? '✅ true' : '❌ false' }} + {{(bool) config_cache('federation.activitypub.enabled') ? '✅ true' : '❌ false' }} FEDERATION