ApiV1Dot1Controller.php 49 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370
  1. <?php
  2. namespace App\Http\Controllers\Api;
  3. use App\AccountLog;
  4. use App\EmailVerification;
  5. use App\Http\Controllers\Controller;
  6. use App\Http\Controllers\StatusController;
  7. use App\Http\Resources\StatusStateless;
  8. use App\Jobs\ImageOptimizePipeline\ImageOptimize;
  9. use App\Jobs\ReportPipeline\ReportNotifyAdminViaEmail;
  10. use App\Jobs\StatusPipeline\NewStatusPipeline;
  11. use App\Jobs\StatusPipeline\RemoteStatusDelete;
  12. use App\Jobs\StatusPipeline\StatusDelete;
  13. use App\Jobs\VideoPipeline\VideoThumbnail;
  14. use App\Mail\ConfirmAppEmail;
  15. use App\Mail\PasswordChange;
  16. use App\Media;
  17. use App\Place;
  18. use App\Profile;
  19. use App\Report;
  20. use App\Rules\ExpoPushTokenRule;
  21. use App\Services\AccountService;
  22. use App\Services\BouncerService;
  23. use App\Services\EmailService;
  24. use App\Services\FollowerService;
  25. use App\Services\MediaBlocklistService;
  26. use App\Services\MediaPathService;
  27. use App\Services\NetworkTimelineService;
  28. use App\Services\NotificationAppGatewayService;
  29. use App\Services\ProfileStatusService;
  30. use App\Services\PublicTimelineService;
  31. use App\Services\PushNotificationService;
  32. use App\Services\StatusService;
  33. use App\Services\UserStorageService;
  34. use App\Status;
  35. use App\StatusArchived;
  36. use App\User;
  37. use App\UserSetting;
  38. use App\Util\Lexer\RestrictedNames;
  39. use Cache;
  40. use DB;
  41. use Illuminate\Http\Request;
  42. use Illuminate\Support\Facades\Hash;
  43. use Illuminate\Support\Facades\RateLimiter;
  44. use Illuminate\Support\Str;
  45. use Jenssegers\Agent\Agent;
  46. use League\Fractal;
  47. use League\Fractal\Serializer\ArraySerializer;
  48. use Mail;
  49. use Purify;
  50. class ApiV1Dot1Controller extends Controller
  51. {
  52. protected $fractal;
  53. public function __construct()
  54. {
  55. $this->fractal = new Fractal\Manager;
  56. $this->fractal->setSerializer(new ArraySerializer);
  57. }
  58. public function json($res, $code = 200, $headers = [])
  59. {
  60. return response()->json($res, $code, $headers, JSON_UNESCAPED_SLASHES);
  61. }
  62. public function error($msg, $code = 400, $extra = [], $headers = [])
  63. {
  64. $res = [
  65. 'msg' => $msg,
  66. 'code' => $code,
  67. ];
  68. return response()->json(array_merge($res, $extra), $code, $headers, JSON_UNESCAPED_SLASHES);
  69. }
  70. public function report(Request $request)
  71. {
  72. abort_if(! $request->user() || ! $request->user()->token(), 403);
  73. abort_unless($request->user()->tokenCan('write'), 403);
  74. $user = $request->user();
  75. abort_if($user->status != null, 403);
  76. if (config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  77. abort_if(BouncerService::checkIp($request->ip()), 404);
  78. }
  79. $report_type = $request->input('report_type');
  80. $object_id = $request->input('object_id');
  81. $object_type = $request->input('object_type');
  82. $types = [
  83. 'spam',
  84. 'sensitive',
  85. 'abusive',
  86. 'underage',
  87. 'violence',
  88. 'copyright',
  89. 'impersonation',
  90. 'scam',
  91. 'terrorism',
  92. ];
  93. if (! $report_type || ! $object_id || ! $object_type) {
  94. return $this->error('Invalid or missing parameters', 400, ['error_code' => 'ERROR_INVALID_PARAMS']);
  95. }
  96. if (! in_array($report_type, $types)) {
  97. return $this->error('Invalid report type', 400, ['error_code' => 'ERROR_TYPE_INVALID']);
  98. }
  99. if ($object_type === 'user' && $object_id == $user->profile_id) {
  100. return $this->error('Cannot self report', 400, ['error_code' => 'ERROR_NO_SELF_REPORTS']);
  101. }
  102. $rpid = null;
  103. switch ($object_type) {
  104. case 'post':
  105. $object = Status::find($object_id);
  106. if (! $object) {
  107. return $this->error('Invalid object id', 400, ['error_code' => 'ERROR_INVALID_OBJECT_ID']);
  108. }
  109. $object_type = 'App\Status';
  110. $exists = Report::whereUserId($user->id)
  111. ->whereObjectId($object->id)
  112. ->whereObjectType('App\Status')
  113. ->count();
  114. $rpid = $object->profile_id;
  115. break;
  116. case 'user':
  117. $object = Profile::find($object_id);
  118. if (! $object) {
  119. return $this->error('Invalid object id', 400, ['error_code' => 'ERROR_INVALID_OBJECT_ID']);
  120. }
  121. $object_type = 'App\Profile';
  122. $exists = Report::whereUserId($user->id)
  123. ->whereObjectId($object->id)
  124. ->whereObjectType('App\Profile')
  125. ->count();
  126. $rpid = $object->id;
  127. break;
  128. default:
  129. return $this->error('Invalid report type', 400, ['error_code' => 'ERROR_REPORT_OBJECT_TYPE_INVALID']);
  130. break;
  131. }
  132. if ($exists !== 0) {
  133. return $this->error('Duplicate report', 400, ['error_code' => 'ERROR_REPORT_DUPLICATE']);
  134. }
  135. if ($object->profile_id == $user->profile_id) {
  136. return $this->error('Cannot self report', 400, ['error_code' => 'ERROR_NO_SELF_REPORTS']);
  137. }
  138. $report = new Report;
  139. $report->profile_id = $user->profile_id;
  140. $report->user_id = $user->id;
  141. $report->object_id = $object->id;
  142. $report->object_type = $object_type;
  143. $report->reported_profile_id = $rpid;
  144. $report->type = $report_type;
  145. $report->save();
  146. if (config('instance.reports.email.enabled')) {
  147. ReportNotifyAdminViaEmail::dispatch($report)->onQueue('default');
  148. }
  149. $res = [
  150. 'msg' => 'Successfully sent report',
  151. 'code' => 200,
  152. ];
  153. return $this->json($res);
  154. }
  155. /**
  156. * DELETE /api/v1.1/accounts/avatar
  157. *
  158. * @return \App\Transformer\Api\AccountTransformer
  159. */
  160. public function deleteAvatar(Request $request)
  161. {
  162. abort_if(! $request->user() || ! $request->user()->token(), 403);
  163. abort_unless($request->user()->tokenCan('write'), 403);
  164. $user = $request->user();
  165. abort_if($user->status != null, 403);
  166. if (config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  167. abort_if(BouncerService::checkIp($request->ip()), 404);
  168. }
  169. $avatar = $user->profile->avatar;
  170. if ($avatar->media_path == 'public/avatars/default.png' ||
  171. $avatar->media_path == 'public/avatars/default.jpg'
  172. ) {
  173. return AccountService::get($user->profile_id);
  174. }
  175. if (is_file(storage_path('app/'.$avatar->media_path))) {
  176. @unlink(storage_path('app/'.$avatar->media_path));
  177. }
  178. $avatar->media_path = 'public/avatars/default.jpg';
  179. $avatar->change_count = $avatar->change_count + 1;
  180. $avatar->save();
  181. Cache::forget('avatar:'.$user->profile_id);
  182. Cache::forget("avatar:{$user->profile_id}");
  183. Cache::forget('user:account:id:'.$user->id);
  184. AccountService::del($user->profile_id);
  185. return AccountService::get($user->profile_id);
  186. }
  187. /**
  188. * GET /api/v1.1/accounts/{id}/posts
  189. *
  190. * @return \App\Transformer\Api\StatusTransformer
  191. */
  192. public function accountPosts(Request $request, $id)
  193. {
  194. abort_if(! $request->user() || ! $request->user()->token(), 403);
  195. abort_unless($request->user()->tokenCan('read'), 403);
  196. $user = $request->user();
  197. abort_if($user->status != null, 403);
  198. if (config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  199. abort_if(BouncerService::checkIp($request->ip()), 404);
  200. }
  201. $account = AccountService::get($id);
  202. if (! $account || $account['username'] !== $request->input('username')) {
  203. return $this->json([]);
  204. }
  205. $posts = ProfileStatusService::get($id);
  206. if (! $posts) {
  207. return $this->json([]);
  208. }
  209. $res = collect($posts)
  210. ->map(function ($id) {
  211. return StatusService::get($id);
  212. })
  213. ->filter(function ($post) {
  214. return $post && isset($post['account']);
  215. })
  216. ->toArray();
  217. return $this->json($res);
  218. }
  219. /**
  220. * POST /api/v1.1/accounts/change-password
  221. *
  222. * @return \App\Transformer\Api\AccountTransformer
  223. */
  224. public function accountChangePassword(Request $request)
  225. {
  226. abort_if(! $request->user() || ! $request->user()->token(), 403);
  227. abort_unless($request->user()->tokenCan('write'), 403);
  228. $user = $request->user();
  229. abort_if($user->status != null, 403);
  230. if (config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  231. abort_if(BouncerService::checkIp($request->ip()), 404);
  232. }
  233. $this->validate($request, [
  234. 'current_password' => 'bail|required|current_password',
  235. 'new_password' => 'required|min:'.config('pixelfed.min_password_length', 8),
  236. 'confirm_password' => 'required|same:new_password',
  237. ], [
  238. 'current_password' => 'The password you entered is incorrect',
  239. ]);
  240. $user->password = bcrypt($request->input('new_password'));
  241. $user->save();
  242. $log = new AccountLog;
  243. $log->user_id = $user->id;
  244. $log->item_id = $user->id;
  245. $log->item_type = 'App\User';
  246. $log->action = 'account.edit.password';
  247. $log->message = 'Password changed';
  248. $log->link = null;
  249. $log->ip_address = $request->ip();
  250. $log->user_agent = $request->userAgent();
  251. $log->save();
  252. Mail::to($request->user())->send(new PasswordChange($user));
  253. return $this->json(AccountService::get($user->profile_id));
  254. }
  255. /**
  256. * GET /api/v1.1/accounts/login-activity
  257. *
  258. * @return array
  259. */
  260. public function accountLoginActivity(Request $request)
  261. {
  262. abort_if(! $request->user() || ! $request->user()->token(), 403);
  263. abort_unless($request->user()->tokenCan('read'), 403);
  264. $user = $request->user();
  265. abort_if($user->status != null, 403);
  266. if (config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  267. abort_if(BouncerService::checkIp($request->ip()), 404);
  268. }
  269. $agent = new Agent;
  270. $currentIp = $request->ip();
  271. $activity = AccountLog::whereUserId($user->id)
  272. ->whereAction('auth.login')
  273. ->orderBy('created_at', 'desc')
  274. ->groupBy('ip_address')
  275. ->limit(10)
  276. ->get()
  277. ->map(function ($item) use ($agent, $currentIp) {
  278. $agent->setUserAgent($item->user_agent);
  279. return [
  280. 'id' => $item->id,
  281. 'action' => $item->action,
  282. 'ip' => $item->ip_address,
  283. 'ip_current' => $item->ip_address === $currentIp,
  284. 'is_mobile' => $agent->isMobile(),
  285. 'device' => $agent->device(),
  286. 'browser' => $agent->browser(),
  287. 'platform' => $agent->platform(),
  288. 'created_at' => $item->created_at->format('c'),
  289. ];
  290. });
  291. return $this->json($activity);
  292. }
  293. /**
  294. * GET /api/v1.1/accounts/two-factor
  295. *
  296. * @return array
  297. */
  298. public function accountTwoFactor(Request $request)
  299. {
  300. abort_if(! $request->user() || ! $request->user()->token(), 403);
  301. abort_unless($request->user()->tokenCan('read'), 403);
  302. $user = $request->user();
  303. abort_if($user->status != null, 403);
  304. if (config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  305. abort_if(BouncerService::checkIp($request->ip()), 404);
  306. }
  307. $res = [
  308. 'active' => (bool) $user->{'2fa_enabled'},
  309. 'setup_at' => $user->{'2fa_setup_at'},
  310. ];
  311. return $this->json($res);
  312. }
  313. /**
  314. * GET /api/v1.1/accounts/emails-from-pixelfed
  315. *
  316. * @return array
  317. */
  318. public function accountEmailsFromPixelfed(Request $request)
  319. {
  320. abort_if(! $request->user() || ! $request->user()->token(), 403);
  321. abort_unless($request->user()->tokenCan('read'), 403);
  322. $user = $request->user();
  323. abort_if($user->status != null, 403);
  324. if (config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  325. abort_if(BouncerService::checkIp($request->ip()), 404);
  326. }
  327. $from = config('mail.from.address');
  328. $emailVerifications = EmailVerification::whereUserId($user->id)
  329. ->orderByDesc('id')
  330. ->where('created_at', '>', now()->subDays(14))
  331. ->limit(10)
  332. ->get()
  333. ->map(function ($mail) use ($user, $from) {
  334. return [
  335. 'type' => 'Email Verification',
  336. 'subject' => 'Confirm Email',
  337. 'to_address' => $user->email,
  338. 'from_address' => $from,
  339. 'created_at' => str_replace('@', 'at', $mail->created_at->format('M j, Y @ g:i:s A')),
  340. ];
  341. })
  342. ->toArray();
  343. $passwordResets = DB::table('password_resets')
  344. ->whereEmail($user->email)
  345. ->where('created_at', '>', now()->subDays(14))
  346. ->orderByDesc('created_at')
  347. ->limit(10)
  348. ->get()
  349. ->map(function ($mail) use ($user, $from) {
  350. return [
  351. 'type' => 'Password Reset',
  352. 'subject' => 'Reset Password Notification',
  353. 'to_address' => $user->email,
  354. 'from_address' => $from,
  355. 'created_at' => str_replace('@', 'at', now()->parse($mail->created_at)->format('M j, Y @ g:i:s A')),
  356. ];
  357. })
  358. ->toArray();
  359. $passwordChanges = AccountLog::whereUserId($user->id)
  360. ->whereAction('account.edit.password')
  361. ->where('created_at', '>', now()->subDays(14))
  362. ->orderByDesc('created_at')
  363. ->limit(10)
  364. ->get()
  365. ->map(function ($mail) use ($user, $from) {
  366. return [
  367. 'type' => 'Password Change',
  368. 'subject' => 'Password Change',
  369. 'to_address' => $user->email,
  370. 'from_address' => $from,
  371. 'created_at' => str_replace('@', 'at', now()->parse($mail->created_at)->format('M j, Y @ g:i:s A')),
  372. ];
  373. })
  374. ->toArray();
  375. $res = collect([])
  376. ->merge($emailVerifications)
  377. ->merge($passwordResets)
  378. ->merge($passwordChanges)
  379. ->sortByDesc('created_at')
  380. ->values();
  381. return $this->json($res);
  382. }
  383. /**
  384. * GET /api/v1.1/accounts/apps-and-applications
  385. *
  386. * @return array
  387. */
  388. public function accountApps(Request $request)
  389. {
  390. abort_if(! $request->user() || ! $request->user()->token(), 403);
  391. abort_unless($request->user()->tokenCan('read'), 403);
  392. $user = $request->user();
  393. abort_if($user->status != null, 403);
  394. if (config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  395. abort_if(BouncerService::checkIp($request->ip()), 404);
  396. }
  397. $res = $user->tokens->sortByDesc('created_at')->take(10)->map(function ($token, $key) use ($request) {
  398. return [
  399. 'id' => $token->id,
  400. 'current_session' => $request->user()->token()->id == $token->id,
  401. 'name' => $token->client->name,
  402. 'scopes' => $token->scopes,
  403. 'revoked' => $token->revoked,
  404. 'created_at' => str_replace('@', 'at', now()->parse($token->created_at)->format('M j, Y @ g:i:s A')),
  405. 'expires_at' => str_replace('@', 'at', now()->parse($token->expires_at)->format('M j, Y @ g:i:s A')),
  406. ];
  407. });
  408. return $this->json($res);
  409. }
  410. public function inAppRegistrationPreFlightCheck(Request $request)
  411. {
  412. return [
  413. 'open' => (bool) config_cache('pixelfed.open_registration'),
  414. 'iara' => (bool) config_cache('pixelfed.allow_app_registration'),
  415. ];
  416. }
  417. public function inAppRegistration(Request $request)
  418. {
  419. abort_if($request->user(), 404);
  420. abort_unless((bool) config_cache('pixelfed.open_registration'), 404);
  421. abort_unless((bool) config_cache('pixelfed.allow_app_registration'), 404);
  422. abort_unless($request->hasHeader('X-PIXELFED-APP'), 403);
  423. if (config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  424. abort_if(BouncerService::checkIp($request->ip()), 404);
  425. }
  426. $rl = RateLimiter::attempt('pf:apiv1.1:iar:'.$request->ip(), config('pixelfed.app_registration_rate_limit_attempts', 3), function () {}, config('pixelfed.app_registration_rate_limit_decay', 1800));
  427. abort_if(! $rl, 400, 'Too many requests');
  428. $this->validate($request, [
  429. 'email' => [
  430. 'required',
  431. 'string',
  432. 'email',
  433. 'max:255',
  434. 'unique:users',
  435. function ($attribute, $value, $fail) {
  436. $banned = EmailService::isBanned($value);
  437. if ($banned) {
  438. return $fail('Email is invalid.');
  439. }
  440. },
  441. ],
  442. 'username' => [
  443. 'required',
  444. 'min:2',
  445. 'max:15',
  446. 'unique:users',
  447. function ($attribute, $value, $fail) {
  448. $dash = substr_count($value, '-');
  449. $underscore = substr_count($value, '_');
  450. $period = substr_count($value, '.');
  451. if (ends_with($value, ['.php', '.js', '.css'])) {
  452. return $fail('Username is invalid.');
  453. }
  454. if (($dash + $underscore + $period) > 1) {
  455. return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).');
  456. }
  457. if (! ctype_alnum($value[0])) {
  458. return $fail('Username is invalid. Must start with a letter or number.');
  459. }
  460. if (! ctype_alnum($value[strlen($value) - 1])) {
  461. return $fail('Username is invalid. Must end with a letter or number.');
  462. }
  463. $val = str_replace(['_', '.', '-'], '', $value);
  464. if (! ctype_alnum($val)) {
  465. return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).');
  466. }
  467. $restricted = RestrictedNames::get();
  468. if (in_array(strtolower($value), array_map('strtolower', $restricted))) {
  469. return $fail('Username cannot be used.');
  470. }
  471. },
  472. ],
  473. 'password' => 'required|string|min:8',
  474. ]);
  475. $email = $request->input('email');
  476. $username = $request->input('username');
  477. $password = $request->input('password');
  478. if (config('database.default') == 'pgsql') {
  479. $username = strtolower($username);
  480. $email = strtolower($email);
  481. }
  482. $user = new User;
  483. $user->name = $username;
  484. $user->username = $username;
  485. $user->email = $email;
  486. $user->password = Hash::make($password);
  487. $user->register_source = 'app';
  488. $user->app_register_ip = $request->ip();
  489. $user->app_register_token = Str::random(40);
  490. $user->save();
  491. $rtoken = Str::random(64);
  492. $verify = new EmailVerification;
  493. $verify->user_id = $user->id;
  494. $verify->email = $user->email;
  495. $verify->user_token = $user->app_register_token;
  496. $verify->random_token = $rtoken;
  497. $verify->save();
  498. $params = http_build_query([
  499. 'ut' => $user->app_register_token,
  500. 'rt' => $rtoken,
  501. 'ea' => base64_encode($user->email),
  502. ]);
  503. $appUrl = url('/api/v1.1/auth/iarer?'.$params);
  504. Mail::to($user->email)->send(new ConfirmAppEmail($verify, $appUrl));
  505. return response()->json([
  506. 'success' => true,
  507. ]);
  508. }
  509. public function inAppRegistrationEmailRedirect(Request $request)
  510. {
  511. $this->validate($request, [
  512. 'ut' => 'required',
  513. 'rt' => 'required',
  514. 'ea' => 'required',
  515. ]);
  516. $ut = $request->input('ut');
  517. $rt = $request->input('rt');
  518. $ea = $request->input('ea');
  519. $params = http_build_query([
  520. 'ut' => $ut,
  521. 'rt' => $rt,
  522. 'domain' => config('pixelfed.domain.app'),
  523. 'ea' => $ea,
  524. ]);
  525. $url = 'pixelfed://confirm-account/'.$ut.'?'.$params;
  526. return redirect()->away($url);
  527. }
  528. public function inAppRegistrationConfirm(Request $request)
  529. {
  530. abort_if($request->user(), 404);
  531. abort_unless((bool) config_cache('pixelfed.open_registration'), 404);
  532. abort_unless((bool) config_cache('pixelfed.allow_app_registration'), 404);
  533. abort_unless($request->hasHeader('X-PIXELFED-APP'), 403);
  534. if (config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  535. abort_if(BouncerService::checkIp($request->ip()), 404);
  536. }
  537. $request->validate([
  538. 'user_token' => 'required',
  539. 'random_token' => 'required',
  540. 'email' => 'required',
  541. ]);
  542. $verify = EmailVerification::whereEmail($request->input('email'))
  543. ->whereUserToken($request->input('user_token'))
  544. ->whereRandomToken($request->input('random_token'))
  545. ->first();
  546. if (! $verify) {
  547. return response()->json(['error' => 'Invalid tokens'], 403);
  548. }
  549. if ($verify->created_at->lt(now()->subHours(24))) {
  550. $verify->delete();
  551. return response()->json(['error' => 'Invalid tokens'], 403);
  552. }
  553. $user = User::findOrFail($verify->user_id);
  554. $user->email_verified_at = now();
  555. $user->last_active_at = now();
  556. $user->save();
  557. $token = $user->createToken('Pixelfed', ['read', 'write', 'follow', 'push']);
  558. return response()->json([
  559. 'access_token' => $token->accessToken,
  560. ]);
  561. }
  562. public function archive(Request $request, $id)
  563. {
  564. abort_if(! $request->user() || ! $request->user()->token(), 403);
  565. abort_unless($request->user()->tokenCan('write'), 403);
  566. if (config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  567. abort_if(BouncerService::checkIp($request->ip()), 404);
  568. }
  569. $status = Status::whereNull('in_reply_to_id')
  570. ->whereNull('reblog_of_id')
  571. ->whereProfileId($request->user()->profile_id)
  572. ->findOrFail($id);
  573. if ($status->scope === 'archived') {
  574. return [200];
  575. }
  576. $archive = new StatusArchived;
  577. $archive->status_id = $status->id;
  578. $archive->profile_id = $status->profile_id;
  579. $archive->original_scope = $status->scope;
  580. $archive->save();
  581. $status->scope = 'archived';
  582. $status->visibility = 'draft';
  583. $status->save();
  584. StatusService::del($status->id, true);
  585. AccountService::syncPostCount($status->profile_id);
  586. return [200];
  587. }
  588. public function unarchive(Request $request, $id)
  589. {
  590. abort_if(! $request->user() || ! $request->user()->token(), 403);
  591. abort_unless($request->user()->tokenCan('write'), 403);
  592. if (config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  593. abort_if(BouncerService::checkIp($request->ip()), 404);
  594. }
  595. $status = Status::whereNull('in_reply_to_id')
  596. ->whereNull('reblog_of_id')
  597. ->whereProfileId($request->user()->profile_id)
  598. ->findOrFail($id);
  599. if ($status->scope !== 'archived') {
  600. return [200];
  601. }
  602. $archive = StatusArchived::whereStatusId($status->id)
  603. ->whereProfileId($status->profile_id)
  604. ->firstOrFail();
  605. $status->scope = $archive->original_scope;
  606. $status->visibility = $archive->original_scope;
  607. $status->save();
  608. $archive->delete();
  609. StatusService::del($status->id, true);
  610. AccountService::syncPostCount($status->profile_id);
  611. return [200];
  612. }
  613. public function archivedPosts(Request $request)
  614. {
  615. abort_if(! $request->user() || ! $request->user()->token(), 403);
  616. abort_unless($request->user()->tokenCan('read'), 403);
  617. if (config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  618. abort_if(BouncerService::checkIp($request->ip()), 404);
  619. }
  620. $statuses = Status::whereProfileId($request->user()->profile_id)
  621. ->whereScope('archived')
  622. ->orderByDesc('id')
  623. ->cursorPaginate(10);
  624. return StatusStateless::collection($statuses);
  625. }
  626. public function placesById(Request $request, $id, $slug)
  627. {
  628. abort_if(! $request->user() || ! $request->user()->token(), 403);
  629. abort_unless($request->user()->tokenCan('read'), 403);
  630. if (config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  631. abort_if(BouncerService::checkIp($request->ip()), 404);
  632. }
  633. $place = Place::whereSlug($slug)->findOrFail($id);
  634. $posts = Cache::remember('pf-api:v1.1:places-by-id:'.$place->id, 3600, function () use ($place) {
  635. return Status::wherePlaceId($place->id)
  636. ->whereNull('uri')
  637. ->whereScope('public')
  638. ->orderByDesc('created_at')
  639. ->limit(60)
  640. ->pluck('id');
  641. });
  642. $posts = $posts->map(function ($id) {
  643. return StatusService::get($id);
  644. })
  645. ->filter()
  646. ->values();
  647. return [
  648. 'place' => [
  649. 'id' => $place->id,
  650. 'name' => $place->name,
  651. 'slug' => $place->slug,
  652. 'country' => $place->country,
  653. 'lat' => $place->lat,
  654. 'long' => $place->long,
  655. ],
  656. 'posts' => $posts];
  657. }
  658. public function moderatePost(Request $request, $id)
  659. {
  660. abort_if(! $request->user() || ! $request->user()->token(), 403);
  661. abort_if($request->user()->is_admin != true, 403);
  662. abort_unless($request->user()->tokenCan('admin:write'), 403);
  663. if (config('pixelfed.bouncer.cloud_ips.ban_signups')) {
  664. abort_if(BouncerService::checkIp($request->ip()), 404);
  665. }
  666. $this->validate($request, [
  667. 'action' => 'required|in:cw,mark-public,mark-unlisted,mark-private,mark-spammer,delete',
  668. ]);
  669. $action = $request->input('action');
  670. $status = Status::find($id);
  671. if (! $status) {
  672. return response()->json(['error' => 'Cannot find status'], 400);
  673. }
  674. if ($status->uri == null) {
  675. if ($status->profile->user && $status->profile->user->is_admin) {
  676. return response()->json(['error' => 'Cannot moderate admin accounts'], 400);
  677. }
  678. }
  679. if ($action == 'mark-spammer') {
  680. $status->profile->update([
  681. 'unlisted' => true,
  682. 'cw' => true,
  683. 'no_autolink' => true,
  684. ]);
  685. Status::whereProfileId($status->profile_id)
  686. ->get()
  687. ->each(function ($s) {
  688. if (in_array($s->scope, ['public', 'unlisted'])) {
  689. $s->scope = 'private';
  690. $s->visibility = 'private';
  691. }
  692. $s->is_nsfw = true;
  693. $s->save();
  694. StatusService::del($s->id, true);
  695. });
  696. Cache::forget('pf:bouncer_v0:exemption_by_pid:'.$status->profile_id);
  697. Cache::forget('pf:bouncer_v0:recent_by_pid:'.$status->profile_id);
  698. Cache::forget('admin-dash:reports:spam-count');
  699. } elseif ($action == 'cw') {
  700. $state = $status->is_nsfw;
  701. $status->is_nsfw = ! $state;
  702. $status->save();
  703. StatusService::del($status->id);
  704. } elseif ($action == 'mark-public') {
  705. $state = $status->scope;
  706. $status->scope = 'public';
  707. $status->visibility = 'public';
  708. $status->save();
  709. StatusService::del($status->id, true);
  710. if ($state !== 'public') {
  711. if ($status->uri) {
  712. if ($status->in_reply_to_id == null && $status->reblog_of_id == null) {
  713. NetworkTimelineService::add($status->id);
  714. }
  715. } else {
  716. if ($status->in_reply_to_id == null && $status->reblog_of_id == null) {
  717. PublicTimelineService::add($status->id);
  718. }
  719. }
  720. }
  721. } elseif ($action == 'mark-unlisted') {
  722. $state = $status->scope;
  723. $status->scope = 'unlisted';
  724. $status->visibility = 'unlisted';
  725. $status->save();
  726. StatusService::del($status->id);
  727. if ($state == 'public') {
  728. PublicTimelineService::del($status->id);
  729. NetworkTimelineService::del($status->id);
  730. }
  731. } elseif ($action == 'mark-private') {
  732. $state = $status->scope;
  733. $status->scope = 'private';
  734. $status->visibility = 'private';
  735. $status->save();
  736. StatusService::del($status->id);
  737. if ($state == 'public') {
  738. PublicTimelineService::del($status->id);
  739. NetworkTimelineService::del($status->id);
  740. }
  741. } elseif ($action == 'delete') {
  742. PublicTimelineService::del($status->id);
  743. NetworkTimelineService::del($status->id);
  744. Cache::forget('_api:statuses:recent_9:'.$status->profile_id);
  745. Cache::forget('profile:status_count:'.$status->profile_id);
  746. Cache::forget('profile:embed:'.$status->profile_id);
  747. StatusService::del($status->id, true);
  748. Cache::forget('profile:status_count:'.$status->profile_id);
  749. $status->uri ? RemoteStatusDelete::dispatch($status) : StatusDelete::dispatch($status);
  750. return [];
  751. }
  752. Cache::forget('_api:statuses:recent_9:'.$status->profile_id);
  753. return StatusService::get($status->id, false);
  754. }
  755. public function getWebSettings(Request $request)
  756. {
  757. abort_if(! $request->user() || ! $request->user()->token(), 403);
  758. abort_unless($request->user()->tokenCan('read'), 403);
  759. $uid = $request->user()->id;
  760. $settings = UserSetting::firstOrCreate([
  761. 'user_id' => $uid,
  762. ]);
  763. if (! $settings->other) {
  764. return [];
  765. }
  766. return $settings->other;
  767. }
  768. public function setWebSettings(Request $request)
  769. {
  770. abort_if(! $request->user() || ! $request->user()->token(), 403);
  771. abort_unless($request->user()->tokenCan('write'), 403);
  772. $this->validate($request, [
  773. 'field' => 'required|in:enable_reblogs,hide_reblog_banner',
  774. 'value' => 'required',
  775. ]);
  776. $field = $request->input('field');
  777. $value = $request->input('value');
  778. $settings = UserSetting::firstOrCreate([
  779. 'user_id' => $request->user()->id,
  780. ]);
  781. if (! $settings->other) {
  782. $other = [];
  783. } else {
  784. $other = $settings->other;
  785. }
  786. $other[$field] = $value;
  787. $settings->other = $other;
  788. $settings->save();
  789. return [200];
  790. }
  791. public function getMutualAccounts(Request $request, $id)
  792. {
  793. abort_if(! $request->user() || ! $request->user()->token(), 403);
  794. abort_unless($request->user()->tokenCan('follow'), 403);
  795. $account = AccountService::get($id, true);
  796. if (! $account || ! isset($account['id'])) {
  797. return [];
  798. }
  799. $res = collect(FollowerService::mutualAccounts($request->user()->profile_id, $id))
  800. ->map(function ($accountId) {
  801. return AccountService::get($accountId, true);
  802. })
  803. ->filter()
  804. ->take(24)
  805. ->values();
  806. return $this->json($res);
  807. }
  808. public function accountUsernameToId(Request $request, $username)
  809. {
  810. abort_if(! $request->user() || ! $request->user()->token() || ! $username, 403);
  811. abort_unless($request->user()->tokenCan('read'), 403);
  812. $username = trim($username);
  813. $rateLimiting = (bool) config_cache('api.rate-limits.v1Dot1.accounts.usernameToId.enabled');
  814. $ipRateLimiting = (bool) config_cache('api.rate-limits.v1Dot1.accounts.usernameToId.ip_enabled');
  815. if ($ipRateLimiting) {
  816. $userLimit = (int) config_cache('api.rate-limits.v1Dot1.accounts.usernameToId.ip_limit');
  817. $userDecay = (int) config_cache('api.rate-limits.v1Dot1.accounts.usernameToId.ip_decay');
  818. $userKey = 'pf:apiv1.1:acctU2ID:byIp:'.$request->ip();
  819. if (RateLimiter::tooManyAttempts($userKey, $userLimit)) {
  820. $limits = [
  821. 'X-Rate-Limit-Limit' => $userLimit,
  822. 'X-Rate-Limit-Remaining' => RateLimiter::remaining($userKey, $userLimit),
  823. 'X-Rate-Limit-Reset' => RateLimiter::availableIn($userKey),
  824. ];
  825. return $this->json(['error' => 'Too many attempts!'], 429, $limits);
  826. }
  827. RateLimiter::increment($userKey, $userDecay);
  828. $limits = [
  829. 'X-Rate-Limit-Limit' => $userLimit,
  830. 'X-Rate-Limit-Remaining' => RateLimiter::remaining($userKey, $userLimit),
  831. 'X-Rate-Limit-Reset' => RateLimiter::availableIn($userKey),
  832. ];
  833. }
  834. if ($rateLimiting) {
  835. $userLimit = (int) config_cache('api.rate-limits.v1Dot1.accounts.usernameToId.limit');
  836. $userDecay = (int) config_cache('api.rate-limits.v1Dot1.accounts.usernameToId.decay');
  837. $userKey = 'pf:apiv1.1:acctU2ID:byUid:'.$request->user()->id;
  838. if (RateLimiter::tooManyAttempts($userKey, $userLimit)) {
  839. $limits = [
  840. 'X-Rate-Limit-Limit' => $userLimit,
  841. 'X-Rate-Limit-Remaining' => RateLimiter::remaining($userKey, $userLimit),
  842. 'X-Rate-Limit-Reset' => RateLimiter::availableIn($userKey),
  843. ];
  844. return $this->json(['error' => 'Too many attempts!'], 429, $limits);
  845. }
  846. RateLimiter::increment($userKey, $userDecay);
  847. $limits = [
  848. 'X-Rate-Limit-Limit' => $userLimit,
  849. 'X-Rate-Limit-Remaining' => RateLimiter::remaining($userKey, $userLimit),
  850. 'X-Rate-Limit-Reset' => RateLimiter::availableIn($userKey),
  851. ];
  852. }
  853. if (str_ends_with($username, config_cache('pixelfed.domain.app'))) {
  854. $pre = str_starts_with($username, '@') ? substr($username, 1) : $username;
  855. $parts = explode('@', $pre);
  856. $username = $parts[0];
  857. }
  858. $accountId = AccountService::usernameToId($username, true);
  859. if (! $accountId) {
  860. return [];
  861. }
  862. $account = AccountService::get($accountId);
  863. return $this->json($account, 200, $rateLimiting ? $limits : []);
  864. }
  865. public function getPushState(Request $request)
  866. {
  867. abort_unless($request->hasHeader('X-PIXELFED-APP'), 404, 'Not found');
  868. abort_if(! $request->user() || ! $request->user()->token(), 403);
  869. abort_unless($request->user()->tokenCan('push'), 403);
  870. abort_unless(NotificationAppGatewayService::enabled(), 404, 'Push notifications are not supported on this server.');
  871. $user = $request->user();
  872. abort_if($user->status, 422, 'Cannot access this resource at this time');
  873. $res = [
  874. 'version' => PushNotificationService::PUSH_GATEWAY_VERSION,
  875. 'username' => (string) $user->username,
  876. 'profile_id' => (string) $user->profile_id,
  877. 'notify_enabled' => (bool) $user->notify_enabled,
  878. 'has_token' => (bool) $user->expo_token,
  879. 'notify_like' => (bool) $user->notify_like,
  880. 'notify_follow' => (bool) $user->notify_follow,
  881. 'notify_mention' => (bool) $user->notify_mention,
  882. 'notify_comment' => (bool) $user->notify_comment,
  883. ];
  884. return $this->json($res);
  885. }
  886. public function disablePush(Request $request)
  887. {
  888. abort_unless($request->hasHeader('X-PIXELFED-APP'), 404, 'Not found');
  889. abort_if(! $request->user() || ! $request->user()->token(), 403);
  890. abort_unless($request->user()->tokenCan('push'), 403);
  891. abort_unless(NotificationAppGatewayService::enabled(), 404, 'Push notifications are not supported on this server.');
  892. abort_if($request->user()->status, 422, 'Cannot access this resource at this time');
  893. $request->user()->update([
  894. 'notify_enabled' => false,
  895. 'expo_token' => null,
  896. 'notify_like' => false,
  897. 'notify_follow' => false,
  898. 'notify_mention' => false,
  899. 'notify_comment' => false,
  900. ]);
  901. PushNotificationService::removeMemberFromAll($request->user()->profile_id);
  902. $user = $request->user();
  903. return $this->json([
  904. 'version' => PushNotificationService::PUSH_GATEWAY_VERSION,
  905. 'username' => (string) $user->username,
  906. 'profile_id' => (string) $user->profile_id,
  907. 'notify_enabled' => (bool) $user->notify_enabled,
  908. 'has_token' => (bool) $user->expo_token,
  909. 'notify_like' => (bool) $user->notify_like,
  910. 'notify_follow' => (bool) $user->notify_follow,
  911. 'notify_mention' => (bool) $user->notify_mention,
  912. 'notify_comment' => (bool) $user->notify_comment,
  913. ]);
  914. }
  915. public function comparePush(Request $request)
  916. {
  917. abort_unless($request->hasHeader('X-PIXELFED-APP'), 404, 'Not found');
  918. abort_if(! $request->user() || ! $request->user()->token(), 403);
  919. abort_unless($request->user()->tokenCan('push'), 403);
  920. abort_unless(NotificationAppGatewayService::enabled(), 404, 'Push notifications are not supported on this server.');
  921. abort_if($request->user()->status, 422, 'Cannot access this resource at this time');
  922. $this->validate($request, [
  923. 'expo_token' => ['required', 'string', new ExpoPushTokenRule],
  924. ]);
  925. $user = $request->user();
  926. if (empty($user->expo_token)) {
  927. return $this->json([
  928. 'version' => PushNotificationService::PUSH_GATEWAY_VERSION,
  929. 'username' => (string) $user->username,
  930. 'profile_id' => (string) $user->profile_id,
  931. 'notify_enabled' => (bool) $user->notify_enabled,
  932. 'match' => false,
  933. 'has_existing' => false,
  934. ]);
  935. }
  936. $token = $request->input('expo_token');
  937. $knownToken = $user->expo_token;
  938. $match = hash_equals($knownToken, $token);
  939. return $this->json([
  940. 'version' => PushNotificationService::PUSH_GATEWAY_VERSION,
  941. 'username' => (string) $user->username,
  942. 'profile_id' => (string) $user->profile_id,
  943. 'notify_enabled' => (bool) $user->notify_enabled,
  944. 'match' => $match,
  945. 'has_existing' => true,
  946. ]);
  947. }
  948. public function updatePush(Request $request)
  949. {
  950. abort_unless($request->hasHeader('X-PIXELFED-APP'), 404, 'Not found');
  951. abort_if(! $request->user() || ! $request->user()->token(), 403);
  952. abort_unless($request->user()->tokenCan('push'), 403);
  953. abort_unless(NotificationAppGatewayService::enabled(), 404, 'Push notifications are not supported on this server.');
  954. abort_if($request->user()->status, 422, 'Cannot access this resource at this time');
  955. $this->validate($request, [
  956. 'notify_enabled' => 'required',
  957. 'token' => ['required', 'string', new ExpoPushTokenRule],
  958. 'notify_like' => 'sometimes',
  959. 'notify_follow' => 'sometimes',
  960. 'notify_mention' => 'sometimes',
  961. 'notify_comment' => 'sometimes',
  962. ]);
  963. $pid = $request->user()->profile_id;
  964. abort_if(! $pid, 422, 'An error occured');
  965. $expoToken = $request->input('token');
  966. $existing = User::where('profile_id', '!=', $pid)->whereExpoToken($expoToken)->count();
  967. abort_if($existing && $existing > 5, 400, 'Push token is already used by another account');
  968. $request->user()->update([
  969. 'notify_enabled' => $request->boolean('notify_enabled'),
  970. 'expo_token' => $expoToken,
  971. ]);
  972. if ($request->filled('notify_like')) {
  973. $request->user()->update(['notify_like' => (bool) $request->boolean('notify_like')]);
  974. $request->boolean('notify_like') == true ?
  975. PushNotificationService::set('like', $pid) :
  976. PushNotificationService::removeMember('like', $pid);
  977. }
  978. if ($request->filled('notify_follow')) {
  979. $request->user()->update(['notify_follow' => (bool) $request->boolean('notify_follow')]);
  980. $request->boolean('notify_follow') == true ?
  981. PushNotificationService::set('follow', $pid) :
  982. PushNotificationService::removeMember('follow', $pid);
  983. }
  984. if ($request->filled('notify_mention')) {
  985. $request->user()->update(['notify_mention' => (bool) $request->boolean('notify_mention')]);
  986. $request->boolean('notify_mention') == true ?
  987. PushNotificationService::set('mention', $pid) :
  988. PushNotificationService::removeMember('mention', $pid);
  989. }
  990. if ($request->filled('notify_comment')) {
  991. $request->user()->update(['notify_comment' => (bool) $request->boolean('notify_comment')]);
  992. $request->boolean('notify_comment') == true ?
  993. PushNotificationService::set('comment', $pid) :
  994. PushNotificationService::removeMember('comment', $pid);
  995. }
  996. if ($request->boolean('notify_enabled') == false) {
  997. PushNotificationService::removeMemberFromAll($request->user()->profile_id);
  998. }
  999. $user = $request->user();
  1000. $res = [
  1001. 'version' => PushNotificationService::PUSH_GATEWAY_VERSION,
  1002. 'notify_enabled' => (bool) $user->notify_enabled,
  1003. 'has_token' => (bool) $user->expo_token,
  1004. 'notify_like' => (bool) $user->notify_like,
  1005. 'notify_follow' => (bool) $user->notify_follow,
  1006. 'notify_mention' => (bool) $user->notify_mention,
  1007. 'notify_comment' => (bool) $user->notify_comment,
  1008. ];
  1009. return $this->json($res);
  1010. }
  1011. /**
  1012. * POST /api/v1.1/status/create
  1013. *
  1014. *
  1015. * @return StatusTransformer
  1016. */
  1017. public function statusCreate(Request $request)
  1018. {
  1019. abort_if(! $request->user() || ! $request->user()->token(), 403);
  1020. abort_unless($request->user()->tokenCan('write'), 403);
  1021. $this->validate($request, [
  1022. 'status' => 'nullable|string|max:'.(int) config_cache('pixelfed.max_caption_length'),
  1023. 'file' => [
  1024. 'required',
  1025. 'file',
  1026. 'mimetypes:'.config_cache('pixelfed.media_types'),
  1027. 'max:'.config_cache('pixelfed.max_photo_size'),
  1028. function ($attribute, $value, $fail) {
  1029. if (is_array($value) && count($value) > 1) {
  1030. $fail('Only one file can be uploaded at a time.');
  1031. }
  1032. },
  1033. ],
  1034. 'sensitive' => 'nullable',
  1035. 'visibility' => 'string|in:private,unlisted,public',
  1036. 'spoiler_text' => 'sometimes|max:140',
  1037. ]);
  1038. if ($request->hasHeader('idempotency-key')) {
  1039. $key = 'pf:api:v1:status:idempotency-key:'.$request->user()->id.':'.hash('sha1', $request->header('idempotency-key'));
  1040. $exists = Cache::has($key);
  1041. abort_if($exists, 400, 'Duplicate idempotency key.');
  1042. Cache::put($key, 1, 3600);
  1043. }
  1044. if (config('costar.enabled') == true) {
  1045. $blockedKeywords = config('costar.keyword.block');
  1046. if ($blockedKeywords !== null && $request->status) {
  1047. $keywords = config('costar.keyword.block');
  1048. foreach ($keywords as $kw) {
  1049. if (Str::contains($request->status, $kw) == true) {
  1050. abort(400, 'Invalid object. Contains banned keyword.');
  1051. }
  1052. }
  1053. }
  1054. }
  1055. $user = $request->user();
  1056. if ($user->has_roles) {
  1057. abort_if(! UserRoleService::can('can-post', $user->id), 403, 'Invalid permissions for this action');
  1058. }
  1059. $profile = $user->profile;
  1060. $limitKey = 'compose:rate-limit:media-upload:'.$user->id;
  1061. $photo = $request->file('file');
  1062. $fileSize = $photo->getSize();
  1063. $sizeInKbs = (int) ceil($fileSize / 1000);
  1064. $accountSize = UserStorageService::get($user->id);
  1065. abort_if($accountSize === -1, 403, 'Invalid request.');
  1066. $updatedAccountSize = (int) $accountSize + (int) $sizeInKbs;
  1067. if ((bool) config_cache('pixelfed.enforce_account_limit') == true) {
  1068. $limit = (int) config_cache('pixelfed.max_account_size');
  1069. if ($updatedAccountSize >= $limit) {
  1070. abort(403, 'Account size limit reached.');
  1071. }
  1072. }
  1073. $mimes = explode(',', config_cache('pixelfed.media_types'));
  1074. if (in_array($photo->getMimeType(), $mimes) == false) {
  1075. abort(403, 'Invalid or unsupported mime type.');
  1076. }
  1077. $storagePath = MediaPathService::get($user, 2);
  1078. $path = $photo->storePublicly($storagePath);
  1079. $hash = \hash_file('sha256', $photo);
  1080. $license = null;
  1081. $mime = $photo->getMimeType();
  1082. $settings = UserSetting::whereUserId($user->id)->first();
  1083. if ($settings && ! empty($settings->compose_settings)) {
  1084. $compose = $settings->compose_settings;
  1085. if (isset($compose['default_license']) && $compose['default_license'] != 1) {
  1086. $license = $compose['default_license'];
  1087. }
  1088. }
  1089. abort_if(MediaBlocklistService::exists($hash) == true, 451);
  1090. $visibility = $profile->is_private ? 'private' : (
  1091. $profile->unlisted == true &&
  1092. $request->input('visibility', 'public') == 'public' ?
  1093. 'unlisted' :
  1094. $request->input('visibility', 'public'));
  1095. if ($user->last_active_at == null) {
  1096. return [];
  1097. }
  1098. $defaultCaption = '';
  1099. $content = $request->filled('status') ? strip_tags(Purify::clean($request->input('status'))) : $defaultCaption;
  1100. $cw = $user->profile->cw == true ? true : $request->boolean('sensitive', false);
  1101. $spoilerText = $cw && $request->filled('spoiler_text') ? $request->input('spoiler_text') : null;
  1102. $status = new Status;
  1103. $status->caption = $content;
  1104. $status->rendered = $defaultCaption;
  1105. $status->profile_id = $user->profile_id;
  1106. $status->is_nsfw = $cw;
  1107. $status->cw_summary = $spoilerText;
  1108. $status->scope = $visibility;
  1109. $status->visibility = $visibility;
  1110. $status->type = StatusController::mimeTypeCheck([$mime]);
  1111. $status->save();
  1112. if (! $status) {
  1113. abort(500, 'An error occured.');
  1114. }
  1115. $media = new Media;
  1116. $media->status_id = $status->id;
  1117. $media->profile_id = $profile->id;
  1118. $media->user_id = $user->id;
  1119. $media->media_path = $path;
  1120. $media->original_sha256 = $hash;
  1121. $media->size = $photo->getSize();
  1122. $media->mime = $mime;
  1123. $media->order = 1;
  1124. $media->caption = $request->input('description');
  1125. if ($license) {
  1126. $media->license = $license;
  1127. }
  1128. $media->save();
  1129. switch ($media->mime) {
  1130. case 'image/jpeg':
  1131. case 'image/png':
  1132. ImageOptimize::dispatch($media)->onQueue('mmo');
  1133. break;
  1134. case 'video/mp4':
  1135. VideoThumbnail::dispatch($media)->onQueue('mmo');
  1136. $preview_url = '/storage/no-preview.png';
  1137. $url = '/storage/no-preview.png';
  1138. break;
  1139. }
  1140. $user->storage_used = (int) $updatedAccountSize;
  1141. $user->storage_used_updated_at = now();
  1142. $user->save();
  1143. NewStatusPipeline::dispatch($status);
  1144. Cache::forget('user:account:id:'.$user->id);
  1145. Cache::forget('_api:statuses:recent_9:'.$user->profile_id);
  1146. Cache::forget('profile:status_count:'.$user->profile_id);
  1147. Cache::forget($user->storageUsedKey());
  1148. Cache::forget('profile:embed:'.$status->profile_id);
  1149. Cache::forget($limitKey);
  1150. $res = StatusService::getMastodon($status->id, false);
  1151. $res['favourited'] = false;
  1152. $res['language'] = 'en';
  1153. $res['bookmarked'] = false;
  1154. $res['card'] = null;
  1155. return $this->json($res);
  1156. }
  1157. public function nagState(Request $request)
  1158. {
  1159. abort_unless((bool) config_cache('pixelfed.oauth_enabled'), 404);
  1160. return [
  1161. 'active' => NotificationAppGatewayService::enabled(),
  1162. ];
  1163. }
  1164. }