InternalApiController.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. <?php
  2. namespace App\Http\Controllers;
  3. use Illuminate\Http\Request;
  4. use App\{
  5. DirectMessage,
  6. DiscoverCategory,
  7. Hashtag,
  8. Follower,
  9. Like,
  10. Media,
  11. Notification,
  12. Profile,
  13. StatusHashtag,
  14. Status,
  15. UserFilter,
  16. };
  17. use Auth,Cache;
  18. use Carbon\Carbon;
  19. use League\Fractal;
  20. use App\Transformer\Api\{
  21. AccountTransformer,
  22. StatusTransformer,
  23. };
  24. use App\Util\Media\Filter;
  25. use App\Jobs\StatusPipeline\NewStatusPipeline;
  26. use League\Fractal\Serializer\ArraySerializer;
  27. use League\Fractal\Pagination\IlluminatePaginatorAdapter;
  28. use Illuminate\Validation\Rule;
  29. class InternalApiController extends Controller
  30. {
  31. protected $fractal;
  32. public function __construct()
  33. {
  34. $this->middleware('auth');
  35. $this->fractal = new Fractal\Manager();
  36. $this->fractal->setSerializer(new ArraySerializer());
  37. }
  38. // deprecated v2 compose api
  39. public function compose(Request $request)
  40. {
  41. return redirect('/');
  42. }
  43. // deprecated
  44. public function discover(Request $request)
  45. {
  46. $profile = Auth::user()->profile;
  47. $pid = $profile->id;
  48. $following = Cache::remember('feature:discover:following:'.$pid, now()->addMinutes(60), function() use ($pid) {
  49. return Follower::whereProfileId($pid)->pluck('following_id')->toArray();
  50. });
  51. $filters = Cache::remember("user:filter:list:$pid", now()->addMinutes(60), function() use($pid) {
  52. return UserFilter::whereUserId($pid)
  53. ->whereFilterableType('App\Profile')
  54. ->whereIn('filter_type', ['mute', 'block'])
  55. ->pluck('filterable_id')->toArray();
  56. });
  57. $following = array_merge($following, $filters);
  58. $people = Profile::select('id', 'name', 'username')
  59. ->with('avatar')
  60. ->whereNull('status')
  61. ->orderByRaw('rand()')
  62. ->whereHas('statuses')
  63. ->whereNull('domain')
  64. ->whereNotIn('id', $following)
  65. ->whereIsPrivate(false)
  66. ->take(3)
  67. ->get();
  68. $posts = Status::select('id', 'caption', 'profile_id')
  69. ->whereHas('media')
  70. ->whereIsNsfw(false)
  71. ->whereVisibility('public')
  72. ->whereNotIn('profile_id', $following)
  73. ->with('media')
  74. ->orderBy('created_at', 'desc')
  75. ->take(21)
  76. ->get();
  77. $res = [
  78. 'people' => $people->map(function($profile) {
  79. return [
  80. 'id' => $profile->id,
  81. 'avatar' => $profile->avatarUrl(),
  82. 'name' => $profile->name,
  83. 'username' => $profile->username,
  84. 'url' => $profile->url(),
  85. ];
  86. }),
  87. 'posts' => $posts->map(function($post) {
  88. return [
  89. 'url' => $post->url(),
  90. 'thumb' => $post->thumb(),
  91. ];
  92. })
  93. ];
  94. return response()->json($res, 200, [], JSON_PRETTY_PRINT);
  95. }
  96. public function discoverPeople(Request $request)
  97. {
  98. $profile = Auth::user()->profile;
  99. $pid = $profile->id;
  100. $following = Cache::remember('feature:discover:following:'.$pid, now()->addMinutes(60), function() use ($pid) {
  101. return Follower::whereProfileId($pid)->pluck('following_id')->toArray();
  102. });
  103. $filters = Cache::remember("user:filter:list:$pid", now()->addMinutes(60), function() use($pid) {
  104. return UserFilter::whereUserId($pid)
  105. ->whereFilterableType('App\Profile')
  106. ->whereIn('filter_type', ['mute', 'block'])
  107. ->pluck('filterable_id')->toArray();
  108. });
  109. $following = array_merge($following, $filters);
  110. $people = Profile::select('id', 'name', 'username')
  111. ->with('avatar')
  112. ->orderByRaw('rand()')
  113. ->whereHas('statuses')
  114. ->whereNull('domain')
  115. ->whereNotIn('id', $following)
  116. ->whereIsPrivate(false)
  117. ->take(3)
  118. ->get();
  119. $res = [
  120. 'people' => $people->map(function($profile) {
  121. return [
  122. 'id' => $profile->id,
  123. 'avatar' => $profile->avatarUrl(),
  124. 'name' => $profile->name,
  125. 'username' => $profile->username,
  126. 'url' => $profile->url(),
  127. ];
  128. })
  129. ];
  130. return response()->json($res, 200, [], JSON_PRETTY_PRINT);
  131. }
  132. public function discoverPosts(Request $request)
  133. {
  134. $profile = Auth::user()->profile;
  135. $pid = $profile->id;
  136. $following = Cache::remember('feature:discover:following:'.$pid, now()->addMinutes(15), function() use ($pid) {
  137. return Follower::whereProfileId($pid)->pluck('following_id')->toArray();
  138. });
  139. $filters = Cache::remember("user:filter:list:$pid", now()->addMinutes(15), function() use($pid) {
  140. $private = Profile::whereIsPrivate(true)
  141. ->orWhere('unlisted', true)
  142. ->orWhere('status', '!=', null)
  143. ->pluck('id')
  144. ->toArray();
  145. $filters = UserFilter::whereUserId($pid)
  146. ->whereFilterableType('App\Profile')
  147. ->whereIn('filter_type', ['mute', 'block'])
  148. ->pluck('filterable_id')
  149. ->toArray();
  150. return array_merge($private, $filters);
  151. });
  152. $following = array_merge($following, $filters);
  153. $posts = Status::select('id', 'caption', 'profile_id')
  154. ->whereNull('uri')
  155. ->whereHas('media')
  156. ->whereHas('profile', function($q) {
  157. return $q->whereNull('status');
  158. })
  159. ->whereIsNsfw(false)
  160. ->whereVisibility('public')
  161. ->whereNotIn('profile_id', $following)
  162. ->with('media')
  163. ->orderBy('created_at', 'desc')
  164. ->take(21)
  165. ->get();
  166. $res = [
  167. 'posts' => $posts->map(function($post) {
  168. return [
  169. 'url' => $post->url(),
  170. 'thumb' => $post->thumb(),
  171. ];
  172. })
  173. ];
  174. return response()->json($res);
  175. }
  176. public function directMessage(Request $request, $profileId, $threadId)
  177. {
  178. $profile = Auth::user()->profile;
  179. if($profileId != $profile->id) {
  180. abort(403);
  181. }
  182. $msg = DirectMessage::whereToId($profile->id)
  183. ->orWhere('from_id',$profile->id)
  184. ->findOrFail($threadId);
  185. $thread = DirectMessage::with('status')->whereIn('to_id', [$profile->id, $msg->from_id])
  186. ->whereIn('from_id', [$profile->id,$msg->from_id])
  187. ->orderBy('created_at', 'asc')
  188. ->paginate(30);
  189. return response()->json(compact('msg', 'profile', 'thread'), 200, [], JSON_PRETTY_PRINT);
  190. }
  191. public function notificationMarkAllRead(Request $request)
  192. {
  193. $profile = Auth::user()->profile;
  194. $notifications = Notification::whereProfileId($profile->id)->get();
  195. foreach($notifications as $n) {
  196. $n->read_at = Carbon::now();
  197. $n->save();
  198. }
  199. return;
  200. }
  201. public function statusReplies(Request $request, int $id)
  202. {
  203. $parent = Status::findOrFail($id);
  204. $children = Status::whereInReplyToId($parent->id)
  205. ->orderBy('created_at', 'desc')
  206. ->take(3)
  207. ->get();
  208. $resource = new Fractal\Resource\Collection($children, new StatusTransformer());
  209. $res = $this->fractal->createData($resource)->toArray();
  210. return response()->json($res);
  211. }
  212. public function stories(Request $request)
  213. {
  214. }
  215. public function discoverCategories(Request $request)
  216. {
  217. $categories = DiscoverCategory::whereActive(true)->orderBy('order')->take(10)->get();
  218. $res = $categories->map(function($item) {
  219. return [
  220. 'name' => $item->name,
  221. 'url' => $item->url(),
  222. 'thumb' => $item->thumb()
  223. ];
  224. });
  225. return response()->json($res);
  226. }
  227. public function modAction(Request $request)
  228. {
  229. abort_unless(Auth::user()->is_admin, 403);
  230. $this->validate($request, [
  231. 'action' => [
  232. 'required',
  233. 'string',
  234. Rule::in([
  235. 'autocw',
  236. 'noautolink',
  237. 'unlisted',
  238. 'disable',
  239. 'suspend'
  240. ])
  241. ],
  242. 'item_id' => 'required|integer|min:1',
  243. 'item_type' => [
  244. 'required',
  245. 'string',
  246. Rule::in(['status'])
  247. ]
  248. ]);
  249. $action = $request->input('action');
  250. $item_id = $request->input('item_id');
  251. $item_type = $request->input('item_type');
  252. switch($action) {
  253. case 'autocw':
  254. $profile = $item_type == 'status' ? Status::findOrFail($item_id)->profile : null;
  255. $profile->cw = true;
  256. $profile->save();
  257. break;
  258. case 'noautolink':
  259. $profile = $item_type == 'status' ? Status::findOrFail($item_id)->profile : null;
  260. $profile->no_autolink = true;
  261. $profile->save();
  262. break;
  263. case 'unlisted':
  264. $profile = $item_type == 'status' ? Status::findOrFail($item_id)->profile : null;
  265. $profile->unlisted = true;
  266. $profile->save();
  267. break;
  268. case 'disable':
  269. $profile = $item_type == 'status' ? Status::findOrFail($item_id)->profile : null;
  270. $user = $profile->user;
  271. $profile->status = 'disabled';
  272. $user->status = 'disabled';
  273. $profile->save();
  274. $user->save();
  275. break;
  276. case 'suspend':
  277. $profile = $item_type == 'status' ? Status::findOrFail($item_id)->profile : null;
  278. $user = $profile->user;
  279. $profile->status = 'suspended';
  280. $user->status = 'suspended';
  281. $profile->save();
  282. $user->save();
  283. break;
  284. default:
  285. # code...
  286. break;
  287. }
  288. return ['msg' => 200];
  289. }
  290. public function composePost(Request $request)
  291. {
  292. $this->validate($request, [
  293. 'caption' => 'nullable|string',
  294. 'media.*' => 'required',
  295. 'media.*.id' => 'required|integer|min:1',
  296. 'media.*.filter_class' => 'nullable|alpha_dash|max:30',
  297. 'media.*.license' => 'nullable|string|max:80',
  298. 'cw' => 'nullable|boolean',
  299. 'visibility' => 'required|string|in:public,private,unlisted|min:2|max:10'
  300. ]);
  301. $profile = Auth::user()->profile;
  302. $visibility = $request->input('visibility');
  303. $medias = $request->input('media');
  304. $attachments = [];
  305. $status = new Status;
  306. $mimes = [];
  307. $cw = $request->input('cw');
  308. foreach($medias as $k => $media) {
  309. if($k + 1 > config('pixelfed.max_album_length')) {
  310. continue;
  311. }
  312. $m = Media::findOrFail($media['id']);
  313. if($m->profile_id !== $profile->id || $m->status_id) {
  314. abort(403, 'Invalid media id');
  315. }
  316. $m->filter_class = in_array($media['filter_class'], Filter::classes()) ? $media['filter_class'] : null;
  317. $m->license = $media['license'];
  318. $m->caption = isset($media['alt']) ? strip_tags($media['alt']) : null;
  319. $m->order = isset($media['cursor']) && is_int($media['cursor']) ? (int) $media['cursor'] : $k;
  320. if($cw == true || $profile->cw == true) {
  321. $m->is_nsfw = $cw;
  322. $status->is_nsfw = $cw;
  323. }
  324. $m->save();
  325. $attachments[] = $m;
  326. array_push($mimes, $m->mime);
  327. }
  328. $status->caption = strip_tags($request->caption);
  329. $status->scope = 'draft';
  330. $status->profile_id = $profile->id;
  331. $status->save();
  332. foreach($attachments as $media) {
  333. $media->status_id = $status->id;
  334. $media->save();
  335. }
  336. $visibility = $profile->unlisted == true && $visibility == 'public' ? 'unlisted' : $visibility;
  337. $cw = $profile->cw == true ? true : $cw;
  338. $status->is_nsfw = $cw;
  339. $status->visibility = $visibility;
  340. $status->scope = $visibility;
  341. $status->type = StatusController::mimeTypeCheck($mimes);
  342. $status->save();
  343. NewStatusPipeline::dispatch($status);
  344. return $status->url();
  345. }
  346. }