SearchApiV2Service.php 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. <?php
  2. namespace App\Services;
  3. use Cache;
  4. use Illuminate\Support\Facades\Redis;
  5. use App\{Hashtag, Profile, Status};
  6. use App\Transformer\Api\AccountTransformer;
  7. use App\Transformer\Api\StatusTransformer;
  8. use League\Fractal;
  9. use League\Fractal\Serializer\ArraySerializer;
  10. use League\Fractal\Pagination\IlluminatePaginatorAdapter;
  11. use App\Util\ActivityPub\Helpers;
  12. use Illuminate\Support\Str;
  13. use App\Services\AccountService;
  14. use App\Services\HashtagService;
  15. use App\Services\StatusService;
  16. class SearchApiV2Service
  17. {
  18. private $query;
  19. public static function query($query)
  20. {
  21. return (new self)->run($query);
  22. }
  23. protected function run($query)
  24. {
  25. $this->query = $query;
  26. if($query->has('resolve') &&
  27. $query->resolve == true &&
  28. Helpers::validateUrl(urldecode($query->input('q')))
  29. ) {
  30. return $this->resolve();
  31. }
  32. if($query->has('type')) {
  33. switch ($query->input('type')) {
  34. case 'accounts':
  35. return [
  36. 'accounts' => $this->accounts(),
  37. 'hashtags' => [],
  38. 'statuses' => []
  39. ];
  40. break;
  41. case 'hashtags':
  42. return [
  43. 'accounts' => [],
  44. 'hashtags' => $this->hashtags(),
  45. 'statuses' => []
  46. ];
  47. break;
  48. case 'statuses':
  49. return [
  50. 'accounts' => [],
  51. 'hashtags' => [],
  52. 'statuses' => $this->statuses()
  53. ];
  54. break;
  55. }
  56. }
  57. if($query->has('account_id')) {
  58. return [
  59. 'accounts' => [],
  60. 'hashtags' => [],
  61. 'statuses' => $this->statusesById()
  62. ];
  63. }
  64. return [
  65. 'accounts' => $this->accounts(),
  66. 'hashtags' => $this->hashtags(),
  67. 'statuses' => $this->statuses()
  68. ];
  69. }
  70. protected function resolve()
  71. {
  72. $query = urldecode($this->query->input('q'));
  73. if(Str::startsWith($query, '@') == true) {
  74. return WebfingerService::lookup($this->query->input('q'));
  75. } else if (Str::startsWith($query, 'https://') == true) {
  76. return $this->resolveQuery();
  77. }
  78. }
  79. protected function accounts()
  80. {
  81. $user = request()->user();
  82. $limit = $this->query->input('limit') ?? 20;
  83. $offset = $this->query->input('offset') ?? 0;
  84. $query = '%' . $this->query->input('q') . '%';
  85. $results = Profile::select('profiles.*', 'followers.profile_id', 'followers.created_at')
  86. ->whereNull('status')
  87. ->leftJoin('followers', function($join) use($user) {
  88. return $join->on('profiles.id', '=', 'followers.following_id')
  89. ->where('followers.profile_id', $user->profile_id);
  90. })
  91. ->where('username', 'like', $query)
  92. ->orderByDesc('profiles.followers_count')
  93. ->orderByDesc('followers.created_at')
  94. ->offset($offset)
  95. ->limit($limit)
  96. ->get()
  97. ->map(function($res) {
  98. return AccountService::get($res['id']);
  99. });
  100. return $results;
  101. }
  102. protected function hashtags()
  103. {
  104. $limit = $this->query->input('limit') ?? 20;
  105. $offset = $this->query->input('offset') ?? 0;
  106. $query = '%' . $this->query->input('q') . '%';
  107. return Hashtag::whereIsBanned(false)
  108. ->where('name', 'like', $query)
  109. ->offset($offset)
  110. ->limit($limit)
  111. ->get()
  112. ->map(function($tag) {
  113. return [
  114. 'name' => $tag->name,
  115. 'url' => $tag->url(),
  116. 'count' => HashtagService::count($tag->id),
  117. 'history' => []
  118. ];
  119. });
  120. }
  121. protected function statuses()
  122. {
  123. // Removed until we provide more relevent sorting/results
  124. return [];
  125. }
  126. protected function statusesById()
  127. {
  128. $accountId = $this->query->input('account_id');
  129. $limit = $this->query->input('limit', 20);
  130. $query = '%' . $this->query->input('q') . '%';
  131. $results = Status::where('caption', 'like', $query)
  132. ->whereProfileId($accountId)
  133. ->limit($limit)
  134. ->get()
  135. ->map(function($status) {
  136. return StatusService::get($status->id);
  137. });
  138. return $results;
  139. }
  140. protected function resolveQuery()
  141. {
  142. $query = urldecode($this->query->input('q'));
  143. if(Helpers::validateLocalUrl($query)) {
  144. if(Str::contains($query, '/p/')) {
  145. return $this->resolveLocalStatus();
  146. } else {
  147. return $this->resolveLocalProfile();
  148. }
  149. } else {
  150. return [
  151. 'accounts' => [],
  152. 'hashtags' => [],
  153. 'statuses' => []
  154. ];
  155. }
  156. }
  157. protected function resolveLocalStatus()
  158. {
  159. $query = urldecode($this->query->input('q'));
  160. $query = last(explode('/', $query));
  161. $status = Status::whereNull('uri')
  162. ->whereScope('public')
  163. ->find($query);
  164. if(!$status) {
  165. return [
  166. 'accounts' => [],
  167. 'hashtags' => [],
  168. 'statuses' => []
  169. ];
  170. }
  171. $fractal = new Fractal\Manager();
  172. $fractal->setSerializer(new ArraySerializer());
  173. $resource = new Fractal\Resource\Item($status, new StatusTransformer());
  174. return [
  175. 'accounts' => [],
  176. 'hashtags' => [],
  177. 'statuses' => $fractal->createData($resource)->toArray()
  178. ];
  179. }
  180. protected function resolveLocalProfile()
  181. {
  182. $query = urldecode($this->query->input('q'));
  183. $query = last(explode('/', $query));
  184. $profile = Profile::whereNull('status')
  185. ->whereNull('domain')
  186. ->whereUsername($query)
  187. ->first();
  188. if(!$profile) {
  189. return [
  190. 'accounts' => [],
  191. 'hashtags' => [],
  192. 'statuses' => []
  193. ];
  194. }
  195. $fractal = new Fractal\Manager();
  196. $fractal->setSerializer(new ArraySerializer());
  197. $resource = new Fractal\Resource\Item($profile, new AccountTransformer());
  198. return [
  199. 'accounts' => $fractal->createData($resource)->toArray(),
  200. 'hashtags' => [],
  201. 'statuses' => []
  202. ];
  203. }
  204. }