diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index 064afd86f..be506ea4e 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -15,9 +15,15 @@ use App\Transformer\Api\{ HashtagTransformer, StatusTransformer, }; +use App\Services\WebfingerService; class SearchController extends Controller { + public $tokens = []; + public $term = ''; + public $hash = ''; + public $cacheKey = 'api:search:tag:'; + public function __construct() { $this->middleware('auth'); @@ -28,50 +34,98 @@ class SearchController extends Controller $this->validate($request, [ 'q' => 'required|string|min:3|max:120', 'src' => 'required|string|in:metro', - 'v' => 'required|integer|in:1' + 'v' => 'required|integer|in:1', + 'scope' => 'required|in:all,hashtag,profile,remote,webfinger' ]); - $tag = $request->input('q'); - $tag = e(urldecode($tag)); + $scope = $request->input('scope') ?? 'all'; + $this->term = e(urldecode($request->input('q'))); + $this->hash = hash('sha256', $this->term); + + switch ($scope) { + case 'all': + $this->getHashtags(); + $this->getPosts(); + $this->getProfiles(); + break; + + case 'hashtag': + $this->getHashtags(); + break; + + case 'profile': + $this->getProfiles(); + break; + + case 'webfinger': + $this->webfingerSearch(); + break; + + default: + break; + } + + return response()->json($this->tokens, 200, [], JSON_PRETTY_PRINT); + } + + protected function getPosts() + { + $tag = $this->term; $hash = hash('sha256', $tag); - $tokens = Cache::remember('api:search:tag:'.$hash, now()->addMinutes(5), function () use ($tag) { - $tokens = []; - if(Helpers::validateUrl($tag) != false && config('federation.activitypub.enabled') == true && config('federation.activitypub.remoteFollow') == true) { - abort_if(Helpers::validateLocalUrl($tag), 404); - $remote = Helpers::fetchFromUrl($tag); - if(isset($remote['type']) && in_array($remote['type'], ['Note', 'Person']) == true) { - $type = $remote['type']; - if($type == 'Person') { - $item = Helpers::profileFirstOrNew($tag); - $tokens['profiles'] = [[ - '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 - ] - ]]; - } else if ($type == 'Note') { - $item = Helpers::statusFetch($tag); - $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(), - ]]; - } - } + if( Helpers::validateUrl($tag) != false && + Helpers::validateLocalUrl($tag) != true && + config('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(); + + 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; + } + } + } + + 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.'%') @@ -89,72 +143,93 @@ class SearchController extends Controller 'name' => null, ]; }); - $tokens['hashtags'] = $tags; + return $tags; } - return $tokens; }); - $users = Profile::select('domain', 'username', 'name', 'id') - ->whereNull('status') - ->whereNull('domain') - ->where('id', '!=', Auth::user()->profile->id) - ->where('username', 'like', '%'.$tag.'%') - //->orWhere('remote_url', $tag) - ->limit(20) - ->get(); + $this->tokens['hashtags'] = $tokens; + } - if($users->count() > 0) { - $profiles = $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' => $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 - ] - ]; - }); - if(isset($tokens['profiles'])) { - array_push($tokens['profiles'], $profiles); - } else { - $tokens['profiles'] = $profiles; + 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('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 + ] + ]]; + return $tokens; + }); } - } - $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) + } + // elseif( Str::containsAll($tag, ['@', '.']) + // config('federation.activitypub.enabled') == true && + // config('federation.activitypub.remoteFollow') == true + // ) { + // if(substr_count($tag, '@') == 2) { + // $domain = last(explode('@', sub_str($u, 1))); + // } else { + // $domain = last(explode('@', $u)); + // } + + // } + else { + $this->tokens['profiles'] = Cache::remember($key, $ttl, function() use($tag) { + $users = Profile::select('domain', 'username', 'name', 'id') + ->whereNull('status') + ->where('id', '!=', Auth::user()->profile->id) + ->where('username', 'like', '%'.$tag.'%') + ->limit(20) ->get(); - 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 - ]; + 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() + ] + ]; + }); + } }); - $tokens['posts'] = $posts; } - - return response()->json($tokens); } public function results(Request $request) @@ -166,4 +241,31 @@ class SearchController extends Controller return view('search.results'); } -} + protected function webfingerSearch() + { + $wfs = WebfingerService::lookup($this->term); + + if(empty($wfs)) { + return; + } + + $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; + } +} \ No newline at end of file