PublicApiController.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755
  1. <?php
  2. namespace App\Http\Controllers;
  3. use Illuminate\Http\Request;
  4. use App\{
  5. Hashtag,
  6. Follower,
  7. Like,
  8. Media,
  9. Notification,
  10. Profile,
  11. StatusHashtag,
  12. Status,
  13. StatusView,
  14. UserFilter
  15. };
  16. use Auth, Cache;
  17. use Carbon\Carbon;
  18. use League\Fractal;
  19. use App\Transformer\Api\{
  20. AccountTransformer,
  21. RelationshipTransformer,
  22. StatusTransformer,
  23. StatusStatelessTransformer
  24. };
  25. use App\Services\{
  26. AccountService,
  27. PublicTimelineService,
  28. StatusService,
  29. SnowflakeService,
  30. UserFilterService
  31. };
  32. use App\Jobs\StatusPipeline\NewStatusPipeline;
  33. use League\Fractal\Serializer\ArraySerializer;
  34. use League\Fractal\Pagination\IlluminatePaginatorAdapter;
  35. class PublicApiController extends Controller
  36. {
  37. protected $fractal;
  38. public function __construct()
  39. {
  40. $this->fractal = new Fractal\Manager();
  41. $this->fractal->setSerializer(new ArraySerializer());
  42. }
  43. protected function getUserData($user)
  44. {
  45. if(!$user) {
  46. return [];
  47. } else {
  48. return AccountService::get($user->profile_id);
  49. }
  50. }
  51. protected function getLikes($status)
  52. {
  53. if(false == Auth::check()) {
  54. return [];
  55. } else {
  56. $profile = Auth::user()->profile;
  57. if($profile->status) {
  58. return [];
  59. }
  60. $likes = $status->likedBy()->orderBy('created_at','desc')->paginate(10);
  61. $collection = new Fractal\Resource\Collection($likes, new AccountTransformer());
  62. return $this->fractal->createData($collection)->toArray();
  63. }
  64. }
  65. protected function getShares($status)
  66. {
  67. if(false == Auth::check()) {
  68. return [];
  69. } else {
  70. $profile = Auth::user()->profile;
  71. if($profile->status) {
  72. return [];
  73. }
  74. $shares = $status->sharedBy()->orderBy('created_at','desc')->paginate(10);
  75. $collection = new Fractal\Resource\Collection($shares, new AccountTransformer());
  76. return $this->fractal->createData($collection)->toArray();
  77. }
  78. }
  79. public function status(Request $request, $username, int $postid)
  80. {
  81. $profile = Profile::whereUsername($username)->whereNull('status')->firstOrFail();
  82. $status = Status::whereProfileId($profile->id)->findOrFail($postid);
  83. $this->scopeCheck($profile, $status);
  84. if(!Auth::check()) {
  85. $res = Cache::remember('wapi:v1:status:stateless_byid:' . $status->id, now()->addMinutes(30), function() use($status) {
  86. $item = new Fractal\Resource\Item($status, new StatusStatelessTransformer());
  87. $res = [
  88. 'status' => $this->fractal->createData($item)->toArray(),
  89. ];
  90. return $res;
  91. });
  92. return response()->json($res);
  93. }
  94. $item = new Fractal\Resource\Item($status, new StatusStatelessTransformer());
  95. $res = [
  96. 'status' => $this->fractal->createData($item)->toArray(),
  97. ];
  98. return response()->json($res);
  99. }
  100. public function statusState(Request $request, $username, int $postid)
  101. {
  102. $profile = Profile::whereUsername($username)->whereNull('status')->firstOrFail();
  103. $status = Status::whereProfileId($profile->id)->findOrFail($postid);
  104. $this->scopeCheck($profile, $status);
  105. if(!Auth::check()) {
  106. $res = [
  107. 'user' => [],
  108. 'likes' => [],
  109. 'shares' => [],
  110. 'reactions' => [
  111. 'liked' => false,
  112. 'shared' => false,
  113. 'bookmarked' => false,
  114. ],
  115. ];
  116. return response()->json($res);
  117. }
  118. $res = [
  119. 'user' => $this->getUserData($request->user()),
  120. 'likes' => [],
  121. 'shares' => [],
  122. 'reactions' => [
  123. 'liked' => (bool) $status->liked(),
  124. 'shared' => (bool) $status->shared(),
  125. 'bookmarked' => (bool) $status->bookmarked(),
  126. ],
  127. ];
  128. return response()->json($res);
  129. }
  130. public function statusComments(Request $request, $username, int $postId)
  131. {
  132. $this->validate($request, [
  133. 'min_id' => 'nullable|integer|min:1',
  134. 'max_id' => 'nullable|integer|min:1|max:'.PHP_INT_MAX,
  135. 'limit' => 'nullable|integer|min:5|max:50'
  136. ]);
  137. $limit = $request->limit ?? 10;
  138. $profile = Profile::whereNull('status')->findOrFail($username);
  139. $status = Status::whereProfileId($profile->id)->whereCommentsDisabled(false)->findOrFail($postId);
  140. $this->scopeCheck($profile, $status);
  141. if(Auth::check()) {
  142. $p = Auth::user()->profile;
  143. $filtered = UserFilter::whereUserId($p->id)
  144. ->whereFilterableType('App\Profile')
  145. ->whereIn('filter_type', ['mute', 'block'])
  146. ->pluck('filterable_id')->toArray();
  147. $scope = $p->id == $status->profile_id ? ['public', 'private', 'unlisted'] : ['public','unlisted'];
  148. } else {
  149. $filtered = [];
  150. $scope = ['public', 'unlisted'];
  151. }
  152. if($request->filled('min_id') || $request->filled('max_id')) {
  153. if($request->filled('min_id')) {
  154. $replies = $status->comments()
  155. ->whereNull('reblog_of_id')
  156. ->whereIn('scope', $scope)
  157. ->whereNotIn('profile_id', $filtered)
  158. ->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at')
  159. ->where('id', '>=', $request->min_id)
  160. ->orderBy('id', 'desc')
  161. ->paginate($limit);
  162. }
  163. if($request->filled('max_id')) {
  164. $replies = $status->comments()
  165. ->whereNull('reblog_of_id')
  166. ->whereIn('scope', $scope)
  167. ->whereNotIn('profile_id', $filtered)
  168. ->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at')
  169. ->where('id', '<=', $request->max_id)
  170. ->orderBy('id', 'desc')
  171. ->paginate($limit);
  172. }
  173. } else {
  174. $replies = $status->comments()
  175. ->whereNull('reblog_of_id')
  176. ->whereIn('scope', $scope)
  177. ->whereNotIn('profile_id', $filtered)
  178. ->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at')
  179. ->orderBy('id', 'desc')
  180. ->paginate($limit);
  181. }
  182. $resource = new Fractal\Resource\Collection($replies, new StatusTransformer(), 'data');
  183. $resource->setPaginator(new IlluminatePaginatorAdapter($replies));
  184. $res = $this->fractal->createData($resource)->toArray();
  185. return response()->json($res, 200, [], JSON_PRETTY_PRINT);
  186. }
  187. public function statusLikes(Request $request, $username, $id)
  188. {
  189. abort_if(!$request->user(), 404);
  190. $status = Status::findOrFail($id);
  191. $this->scopeCheck($status->profile, $status);
  192. $page = $request->input('page');
  193. if($page && $page >= 3 && $request->user()->profile_id != $status->profile_id) {
  194. return response()->json([
  195. 'data' => []
  196. ]);
  197. }
  198. $likes = $this->getLikes($status);
  199. return response()->json([
  200. 'data' => $likes
  201. ]);
  202. }
  203. public function statusShares(Request $request, $username, $id)
  204. {
  205. abort_if(!$request->user(), 404);
  206. $profile = Profile::whereUsername($username)->whereNull('status')->firstOrFail();
  207. $status = Status::whereProfileId($profile->id)->findOrFail($id);
  208. $this->scopeCheck($profile, $status);
  209. $page = $request->input('page');
  210. if($page && $page >= 3 && $request->user()->profile_id != $status->profile_id) {
  211. return response()->json([
  212. 'data' => []
  213. ]);
  214. }
  215. $shares = $this->getShares($status);
  216. return response()->json([
  217. 'data' => $shares
  218. ]);
  219. }
  220. protected function scopeCheck(Profile $profile, Status $status)
  221. {
  222. if($profile->is_private == true && Auth::check() == false) {
  223. abort(404);
  224. }
  225. switch ($status->scope) {
  226. case 'public':
  227. case 'unlisted':
  228. break;
  229. case 'private':
  230. $user = Auth::check() ? Auth::user() : false;
  231. if(!$user) {
  232. abort(403);
  233. } else {
  234. $follows = $profile->followedBy($user->profile);
  235. if($follows == false && $profile->id !== $user->profile->id && $user->is_admin == false) {
  236. abort(404);
  237. }
  238. }
  239. break;
  240. case 'direct':
  241. abort(404);
  242. break;
  243. case 'draft':
  244. abort(404);
  245. break;
  246. default:
  247. abort(404);
  248. break;
  249. }
  250. }
  251. public function publicTimelineApi(Request $request)
  252. {
  253. $this->validate($request,[
  254. 'page' => 'nullable|integer|max:40',
  255. 'min_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
  256. 'max_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
  257. 'limit' => 'nullable|integer|max:30'
  258. ]);
  259. if(config('instance.timeline.local.is_public') == false && !Auth::check()) {
  260. abort(403, 'Authentication required.');
  261. }
  262. $page = $request->input('page');
  263. $min = $request->input('min_id');
  264. $max = $request->input('max_id');
  265. $limit = $request->input('limit') ?? 3;
  266. $user = $request->user();
  267. $key = 'user:last_active_at:id:'.$user->id;
  268. $ttl = now()->addMinutes(5);
  269. Cache::remember($key, $ttl, function() use($user) {
  270. $user->last_active_at = now();
  271. $user->save();
  272. return;
  273. });
  274. $filtered = UserFilter::whereUserId($user->profile_id)
  275. ->whereFilterableType('App\Profile')
  276. ->whereIn('filter_type', ['mute', 'block'])
  277. ->pluck('filterable_id')->toArray();
  278. if($min || $max) {
  279. $dir = $min ? '>' : '<';
  280. $id = $min ?? $max;
  281. $timeline = Status::select(
  282. 'id',
  283. 'uri',
  284. 'caption',
  285. 'rendered',
  286. 'profile_id',
  287. 'type',
  288. 'in_reply_to_id',
  289. 'reblog_of_id',
  290. 'is_nsfw',
  291. 'scope',
  292. 'local',
  293. 'reply_count',
  294. 'comments_disabled',
  295. 'place_id',
  296. 'likes_count',
  297. 'reblogs_count',
  298. 'created_at',
  299. 'updated_at'
  300. )->where('id', $dir, $id)
  301. ->whereIn('type', ['text', 'photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
  302. ->whereNotIn('profile_id', $filtered)
  303. ->whereLocal(true)
  304. ->whereScope('public')
  305. ->where('created_at', '>', now()->subMonths(6))
  306. ->orderBy('created_at', 'desc')
  307. ->limit($limit)
  308. ->get();
  309. } else {
  310. $timeline = Status::select(
  311. 'id',
  312. 'uri',
  313. 'caption',
  314. 'rendered',
  315. 'profile_id',
  316. 'type',
  317. 'in_reply_to_id',
  318. 'reblog_of_id',
  319. 'is_nsfw',
  320. 'scope',
  321. 'local',
  322. 'reply_count',
  323. 'comments_disabled',
  324. 'created_at',
  325. 'place_id',
  326. 'likes_count',
  327. 'reblogs_count',
  328. 'updated_at'
  329. )->whereIn('type', ['text', 'photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
  330. ->whereNotIn('profile_id', $filtered)
  331. ->with('profile', 'hashtags', 'mentions')
  332. ->whereLocal(true)
  333. ->whereScope('public')
  334. ->where('created_at', '>', now()->subMonths(6))
  335. ->orderBy('created_at', 'desc')
  336. ->simplePaginate($limit);
  337. }
  338. $fractal = new Fractal\Resource\Collection($timeline, new StatusTransformer());
  339. $res = $this->fractal->createData($fractal)->toArray();
  340. return response()->json($res);
  341. }
  342. public function homeTimelineApi(Request $request)
  343. {
  344. if(!Auth::check()) {
  345. return abort(403);
  346. }
  347. $this->validate($request,[
  348. 'page' => 'nullable|integer|max:40',
  349. 'min_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
  350. 'max_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
  351. 'limit' => 'nullable|integer|max:40',
  352. 'recent_feed' => 'nullable',
  353. 'recent_min' => 'nullable|integer'
  354. ]);
  355. $recentFeed = $request->input('recent_feed') == 'true';
  356. $recentFeedMin = $request->input('recent_min');
  357. $page = $request->input('page');
  358. $min = $request->input('min_id');
  359. $max = $request->input('max_id');
  360. $limit = $request->input('limit') ?? 3;
  361. $user = $request->user();
  362. $key = 'user:last_active_at:id:'.$user->id;
  363. $ttl = now()->addMinutes(5);
  364. Cache::remember($key, $ttl, function() use($user) {
  365. $user->last_active_at = now();
  366. $user->save();
  367. return;
  368. });
  369. $pid = Auth::user()->profile_id;
  370. $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) {
  371. $following = Follower::whereProfileId($pid)->pluck('following_id');
  372. return $following->push($pid)->toArray();
  373. });
  374. if($recentFeed == true) {
  375. $key = 'profile:home-timeline-cursor:'.$user->id;
  376. $ttl = now()->addMinutes(30);
  377. $min = Cache::remember($key, $ttl, function() use($pid) {
  378. $res = StatusView::whereProfileId($pid)->orderByDesc('status_id')->first();
  379. return $res ? $res->status_id : null;
  380. });
  381. }
  382. $filtered = Auth::check() ? UserFilterService::filters(Auth::user()->profile_id) : [];
  383. if($min || $max) {
  384. $dir = $min ? '>' : '<';
  385. $id = $min ?? $max;
  386. $timeline = Status::select(
  387. 'id',
  388. 'uri',
  389. 'caption',
  390. 'rendered',
  391. 'profile_id',
  392. 'type',
  393. 'in_reply_to_id',
  394. 'reblog_of_id',
  395. 'is_nsfw',
  396. 'scope',
  397. 'local',
  398. 'reply_count',
  399. 'comments_disabled',
  400. 'place_id',
  401. 'likes_count',
  402. 'reblogs_count',
  403. 'created_at',
  404. 'updated_at'
  405. )->whereIn('type', ['text','photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
  406. ->with('profile', 'hashtags', 'mentions')
  407. ->where('id', $dir, $id)
  408. ->whereIn('profile_id', $following)
  409. ->whereNotIn('profile_id', $filtered)
  410. ->whereIn('visibility',['public', 'unlisted', 'private'])
  411. ->orderBy('created_at', 'desc')
  412. ->limit($limit)
  413. ->get();
  414. } else {
  415. $timeline = Status::select(
  416. 'id',
  417. 'uri',
  418. 'caption',
  419. 'rendered',
  420. 'profile_id',
  421. 'type',
  422. 'in_reply_to_id',
  423. 'reblog_of_id',
  424. 'is_nsfw',
  425. 'scope',
  426. 'local',
  427. 'reply_count',
  428. 'comments_disabled',
  429. 'place_id',
  430. 'likes_count',
  431. 'reblogs_count',
  432. 'created_at',
  433. 'updated_at'
  434. )->whereIn('type', ['text','photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
  435. ->with('profile', 'hashtags', 'mentions')
  436. ->whereIn('profile_id', $following)
  437. ->whereNotIn('profile_id', $filtered)
  438. ->whereIn('visibility',['public', 'unlisted', 'private'])
  439. ->orderBy('created_at', 'desc')
  440. ->simplePaginate($limit);
  441. }
  442. $fractal = new Fractal\Resource\Collection($timeline, new StatusTransformer());
  443. $res = $this->fractal->createData($fractal)->toArray();
  444. return response()->json($res);
  445. }
  446. public function networkTimelineApi(Request $request)
  447. {
  448. abort_if(!Auth::check(), 403);
  449. abort_if(config('federation.network_timeline') == false, 404);
  450. $this->validate($request,[
  451. 'page' => 'nullable|integer|max:40',
  452. 'min_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
  453. 'max_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
  454. 'limit' => 'nullable|integer|max:30'
  455. ]);
  456. $page = $request->input('page');
  457. $min = $request->input('min_id');
  458. $max = $request->input('max_id');
  459. $limit = $request->input('limit') ?? 3;
  460. $user = $request->user();
  461. $amin = SnowflakeService::byDate(now()->subDays(90));
  462. $key = 'user:last_active_at:id:'.$user->id;
  463. $ttl = now()->addMinutes(5);
  464. Cache::remember($key, $ttl, function() use($user) {
  465. $user->last_active_at = now();
  466. $user->save();
  467. return;
  468. });
  469. if($min || $max) {
  470. $dir = $min ? '>' : '<';
  471. $id = $min ?? $max;
  472. $timeline = Status::select(
  473. 'id',
  474. 'uri',
  475. 'type',
  476. 'scope',
  477. 'created_at',
  478. )->where('id', $dir, $id)
  479. ->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
  480. ->whereNotNull('uri')
  481. ->whereScope('public')
  482. ->where('id', '>', $amin)
  483. ->orderBy('created_at', 'desc')
  484. ->limit($limit)
  485. ->get()
  486. ->map(function($s) {
  487. return StatusService::get($s->id);
  488. });
  489. $res = $timeline->toArray();
  490. } else {
  491. $timeline = Status::select(
  492. 'id',
  493. 'uri',
  494. 'type',
  495. 'scope',
  496. 'created_at',
  497. )->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
  498. ->whereNotNull('uri')
  499. ->whereScope('public')
  500. ->where('id', '>', $amin)
  501. ->orderBy('created_at', 'desc')
  502. ->limit($limit)
  503. ->get()
  504. ->map(function($s) {
  505. return StatusService::get($s->id);
  506. });
  507. $res = $timeline->toArray();
  508. }
  509. return response()->json($res);
  510. }
  511. public function relationships(Request $request)
  512. {
  513. if(!Auth::check()) {
  514. return response()->json([]);
  515. }
  516. $this->validate($request, [
  517. 'id' => 'required|array|min:1|max:20',
  518. 'id.*' => 'required|integer'
  519. ]);
  520. $ids = collect($request->input('id'));
  521. $filtered = $ids->filter(function($v) {
  522. return $v != Auth::user()->profile->id;
  523. });
  524. $relations = Profile::whereNull('status')->findOrFail($filtered->all());
  525. $fractal = new Fractal\Resource\Collection($relations, new RelationshipTransformer());
  526. $res = $this->fractal->createData($fractal)->toArray();
  527. return response()->json($res);
  528. }
  529. public function account(Request $request, $id)
  530. {
  531. $res = AccountService::get($id);
  532. return response()->json($res);
  533. }
  534. public function accountFollowers(Request $request, $id)
  535. {
  536. abort_unless(Auth::check(), 403);
  537. $profile = Profile::with('user')->whereNull('status')->whereNull('domain')->findOrFail($id);
  538. if(Auth::id() != $profile->user_id && $profile->is_private || !$profile->user->settings->show_profile_followers) {
  539. return response()->json([]);
  540. }
  541. $followers = $profile->followers()->orderByDesc('followers.created_at')->paginate(10);
  542. $resource = new Fractal\Resource\Collection($followers, new AccountTransformer());
  543. $res = $this->fractal->createData($resource)->toArray();
  544. return response()->json($res);
  545. }
  546. public function accountFollowing(Request $request, $id)
  547. {
  548. abort_unless(Auth::check(), 403);
  549. $profile = Profile::with('user')
  550. ->whereNull('status')
  551. ->whereNull('domain')
  552. ->findOrFail($id);
  553. // filter by username
  554. $search = $request->input('fbu');
  555. $owner = Auth::id() == $profile->user_id;
  556. $filter = ($owner == true) && ($search != null);
  557. abort_if($owner == false && $profile->is_private == true && !$profile->followedBy(Auth::user()->profile), 404);
  558. abort_if($profile->user->settings->show_profile_following == false && $owner == false, 404);
  559. if($search) {
  560. abort_if(!$owner, 404);
  561. $following = $profile->following()
  562. ->where('profiles.username', 'like', '%'.$search.'%')
  563. ->orderByDesc('followers.created_at')
  564. ->paginate(10);
  565. } else {
  566. $following = $profile->following()
  567. ->orderByDesc('followers.created_at')
  568. ->paginate(10);
  569. }
  570. $resource = new Fractal\Resource\Collection($following, new AccountTransformer());
  571. $res = $this->fractal->createData($resource)->toArray();
  572. return response()->json($res);
  573. }
  574. public function accountStatuses(Request $request, $id)
  575. {
  576. $this->validate($request, [
  577. 'only_media' => 'nullable',
  578. 'pinned' => 'nullable',
  579. 'exclude_replies' => 'nullable',
  580. 'max_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
  581. 'since_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
  582. 'min_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
  583. 'limit' => 'nullable|integer|min:1|max:24'
  584. ]);
  585. $profile = Profile::whereNull('status')->findOrFail($id);
  586. $limit = $request->limit ?? 9;
  587. $max_id = $request->max_id;
  588. $min_id = $request->min_id;
  589. $scope = $request->only_media == true ?
  590. ['photo', 'photo:album', 'video', 'video:album'] :
  591. ['photo', 'photo:album', 'video', 'video:album', 'share', 'reply'];
  592. if($profile->is_private) {
  593. if(!Auth::check()) {
  594. return response()->json([]);
  595. }
  596. $pid = Auth::user()->profile->id;
  597. $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) {
  598. $following = Follower::whereProfileId($pid)->pluck('following_id');
  599. return $following->push($pid)->toArray();
  600. });
  601. $visibility = true == in_array($profile->id, $following) ? ['public', 'unlisted', 'private'] : [];
  602. } else {
  603. if(Auth::check()) {
  604. $pid = Auth::user()->profile->id;
  605. $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) {
  606. $following = Follower::whereProfileId($pid)->pluck('following_id');
  607. return $following->push($pid)->toArray();
  608. });
  609. $visibility = true == in_array($profile->id, $following) ? ['public', 'unlisted', 'private'] : ['public', 'unlisted'];
  610. } else {
  611. $visibility = ['public', 'unlisted'];
  612. }
  613. }
  614. $tag = in_array('private', $visibility) ? 'private' : 'public';
  615. if($min_id == 1 && $limit == 9 && $tag == 'public') {
  616. $limit = 9;
  617. $scope = ['photo', 'photo:album', 'video', 'video:album'];
  618. $key = '_api:statuses:recent_9:'.$profile->id;
  619. $res = Cache::remember($key, now()->addHours(24), function() use($profile, $scope, $visibility, $limit) {
  620. $dir = '>';
  621. $id = 1;
  622. $timeline = Status::select(
  623. 'id',
  624. 'uri',
  625. 'caption',
  626. 'rendered',
  627. 'profile_id',
  628. 'type',
  629. 'in_reply_to_id',
  630. 'reblog_of_id',
  631. 'is_nsfw',
  632. 'likes_count',
  633. 'reblogs_count',
  634. 'scope',
  635. 'visibility',
  636. 'local',
  637. 'place_id',
  638. 'comments_disabled',
  639. 'cw_summary',
  640. 'created_at',
  641. 'updated_at'
  642. )->whereProfileId($profile->id)
  643. ->whereIn('type', $scope)
  644. ->where('id', $dir, $id)
  645. ->whereIn('visibility', $visibility)
  646. ->limit($limit)
  647. ->orderByDesc('id')
  648. ->get();
  649. $resource = new Fractal\Resource\Collection($timeline, new StatusStatelessTransformer());
  650. $res = $this->fractal->createData($resource)->toArray();
  651. return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
  652. });
  653. return $res;
  654. }
  655. $dir = $min_id ? '>' : '<';
  656. $id = $min_id ?? $max_id;
  657. $timeline = Status::select(
  658. 'id',
  659. 'uri',
  660. 'caption',
  661. 'rendered',
  662. 'profile_id',
  663. 'type',
  664. 'in_reply_to_id',
  665. 'reblog_of_id',
  666. 'is_nsfw',
  667. 'likes_count',
  668. 'reblogs_count',
  669. 'scope',
  670. 'visibility',
  671. 'local',
  672. 'place_id',
  673. 'comments_disabled',
  674. 'cw_summary',
  675. 'created_at',
  676. 'updated_at'
  677. )->whereProfileId($profile->id)
  678. ->whereIn('type', $scope)
  679. ->where('id', $dir, $id)
  680. ->whereIn('visibility', $visibility)
  681. ->limit($limit)
  682. ->orderByDesc('id')
  683. ->get();
  684. $resource = new Fractal\Resource\Collection($timeline, new StatusStatelessTransformer());
  685. $res = $this->fractal->createData($resource)->toArray();
  686. return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
  687. }
  688. }