1
0

AdminReportController.php 55 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560
  1. <?php
  2. namespace App\Http\Controllers\Admin;
  3. use App\AccountInterstitial;
  4. use App\Http\Resources\AdminRemoteReport;
  5. use App\Http\Resources\AdminReport;
  6. use App\Http\Resources\AdminSpamReport;
  7. use App\Jobs\DeletePipeline\DeleteAccountPipeline;
  8. use App\Jobs\DeletePipeline\DeleteRemoteProfilePipeline;
  9. use App\Jobs\StatusPipeline\RemoteStatusDelete;
  10. use App\Jobs\StatusPipeline\StatusDelete;
  11. use App\Jobs\StoryPipeline\StoryDelete;
  12. use App\Models\ModeratedProfile;
  13. use App\Models\RemoteReport;
  14. use App\Notification;
  15. use App\Profile;
  16. use App\Report;
  17. use App\Services\AccountService;
  18. use App\Services\ModLogService;
  19. use App\Services\NetworkTimelineService;
  20. use App\Services\NotificationService;
  21. use App\Services\PublicTimelineService;
  22. use App\Services\StatusService;
  23. use App\Status;
  24. use App\Story;
  25. use App\User;
  26. use Cache;
  27. use Carbon\Carbon;
  28. use Illuminate\Http\Request;
  29. use Illuminate\Support\Facades\DB;
  30. use Illuminate\Support\Facades\Redis;
  31. use Storage;
  32. trait AdminReportController
  33. {
  34. public function reports(Request $request)
  35. {
  36. $filter = $request->input('filter') == 'closed' ? 'closed' : 'open';
  37. $page = $request->input('page') ?? 1;
  38. $ai = Cache::remember('admin-dash:reports:ai-count', 3600, function () {
  39. return AccountInterstitial::whereNotNull('appeal_requested_at')->whereNull('appeal_handled_at')->count();
  40. });
  41. $spam = Cache::remember('admin-dash:reports:spam-count', 3600, function () {
  42. return AccountInterstitial::whereType('post.autospam')->whereNull('appeal_handled_at')->count();
  43. });
  44. $mailVerifications = Redis::scard('email:manual');
  45. if ($filter == 'open' && $page == 1) {
  46. $reports = Cache::remember('admin-dash:reports:list-cache', 300, function () use ($filter) {
  47. return Report::whereHas('status')
  48. ->whereHas('reportedUser')
  49. ->whereHas('reporter')
  50. ->orderBy('created_at', 'desc')
  51. ->when($filter, function ($q, $filter) {
  52. return $filter == 'open' ?
  53. $q->whereNull('admin_seen') :
  54. $q->whereNotNull('admin_seen');
  55. })
  56. ->paginate(6);
  57. });
  58. } else {
  59. $reports = Report::whereHas('status')
  60. ->whereHas('reportedUser')
  61. ->whereHas('reporter')
  62. ->orderBy('created_at', 'desc')
  63. ->when($filter, function ($q, $filter) {
  64. return $filter == 'open' ?
  65. $q->whereNull('admin_seen') :
  66. $q->whereNotNull('admin_seen');
  67. })
  68. ->paginate(6);
  69. }
  70. return view('admin.reports.home', compact('reports', 'ai', 'spam', 'mailVerifications'));
  71. }
  72. public function showReport(Request $request, $id)
  73. {
  74. $report = Report::with('status')->findOrFail($id);
  75. if ($request->has('ref') && $request->input('ref') == 'email') {
  76. return redirect('/i/admin/reports?tab=report&id='.$report->id);
  77. }
  78. return view('admin.reports.show', compact('report'));
  79. }
  80. public function appeals(Request $request)
  81. {
  82. $appeals = AccountInterstitial::whereNotNull('appeal_requested_at')
  83. ->whereNull('appeal_handled_at')
  84. ->latest()
  85. ->paginate(6);
  86. return view('admin.reports.appeals', compact('appeals'));
  87. }
  88. public function showAppeal(Request $request, $id)
  89. {
  90. $appeal = AccountInterstitial::whereNotNull('appeal_requested_at')
  91. ->whereNull('appeal_handled_at')
  92. ->findOrFail($id);
  93. $meta = json_decode($appeal->meta);
  94. return view('admin.reports.show_appeal', compact('appeal', 'meta'));
  95. }
  96. public function spam(Request $request)
  97. {
  98. $this->validate($request, [
  99. 'tab' => 'sometimes|in:home,not-spam,spam,settings,custom,exemptions',
  100. ]);
  101. $tab = $request->input('tab', 'home');
  102. $openCount = Cache::remember('admin-dash:reports:spam-count', 3600, function () {
  103. return AccountInterstitial::whereType('post.autospam')
  104. ->whereNull('appeal_handled_at')
  105. ->count();
  106. });
  107. $monthlyCount = Cache::remember('admin-dash:reports:spam-count:30d', 43200, function () {
  108. return AccountInterstitial::whereType('post.autospam')
  109. ->where('created_at', '>', now()->subMonth())
  110. ->count();
  111. });
  112. $totalCount = Cache::remember('admin-dash:reports:spam-count:total', 43200, function () {
  113. return AccountInterstitial::whereType('post.autospam')->count();
  114. });
  115. $uncategorized = Cache::remember('admin-dash:reports:spam-sync', 3600, function () {
  116. return AccountInterstitial::whereType('post.autospam')
  117. ->whereIsSpam(null)
  118. ->whereNotNull('appeal_handled_at')
  119. ->exists();
  120. });
  121. $avg = Cache::remember('admin-dash:reports:spam-count:avg', 43200, function () {
  122. if (config('database.default') != 'mysql') {
  123. return 0;
  124. }
  125. return AccountInterstitial::selectRaw('*, count(id) as counter')
  126. ->whereType('post.autospam')
  127. ->groupBy('user_id')
  128. ->get()
  129. ->avg('counter');
  130. });
  131. $avgOpen = Cache::remember('admin-dash:reports:spam-count:avgopen', 43200, function () {
  132. if (config('database.default') != 'mysql') {
  133. return '0';
  134. }
  135. $seconds = AccountInterstitial::selectRaw('DATE(created_at) AS start_date, AVG(TIME_TO_SEC(TIMEDIFF(appeal_handled_at, created_at))) AS timediff')->whereType('post.autospam')->whereNotNull('appeal_handled_at')->where('created_at', '>', now()->subMonth())->get();
  136. if (! $seconds) {
  137. return '0';
  138. }
  139. $mins = floor($seconds->avg('timediff') / 60);
  140. if ($mins < 60) {
  141. return $mins.' min(s)';
  142. }
  143. if ($mins < 2880) {
  144. return floor($mins / 60).' hour(s)';
  145. }
  146. return floor($mins / 60 / 24).' day(s)';
  147. });
  148. $avgCount = $totalCount && $avg ? floor($totalCount / $avg) : '0';
  149. if (in_array($tab, ['home', 'spam', 'not-spam'])) {
  150. $appeals = AccountInterstitial::whereType('post.autospam')
  151. ->when($tab, function ($q, $tab) {
  152. switch ($tab) {
  153. case 'home':
  154. return $q->whereNull('appeal_handled_at');
  155. break;
  156. case 'spam':
  157. return $q->whereIsSpam(true);
  158. break;
  159. case 'not-spam':
  160. return $q->whereIsSpam(false);
  161. break;
  162. }
  163. })
  164. ->latest()
  165. ->paginate(6);
  166. if ($tab !== 'home') {
  167. $appeals = $appeals->appends(['tab' => $tab]);
  168. }
  169. } else {
  170. $appeals = new class
  171. {
  172. public function count()
  173. {
  174. return 0;
  175. }
  176. public function render() {}
  177. };
  178. }
  179. return view('admin.reports.spam', compact('tab', 'appeals', 'openCount', 'monthlyCount', 'totalCount', 'avgCount', 'avgOpen', 'uncategorized'));
  180. }
  181. public function showSpam(Request $request, $id)
  182. {
  183. $appeal = AccountInterstitial::whereType('post.autospam')
  184. ->findOrFail($id);
  185. if ($request->has('ref') && $request->input('ref') == 'email') {
  186. return redirect('/i/admin/reports?tab=autospam&id='.$appeal->id);
  187. }
  188. $meta = json_decode($appeal->meta);
  189. return view('admin.reports.show_spam', compact('appeal', 'meta'));
  190. }
  191. public function fixUncategorizedSpam(Request $request)
  192. {
  193. if (Cache::get('admin-dash:reports:spam-sync-active')) {
  194. return redirect('/i/admin/reports/autospam');
  195. }
  196. Cache::put('admin-dash:reports:spam-sync-active', 1, 900);
  197. AccountInterstitial::chunk(500, function ($reports) {
  198. foreach ($reports as $report) {
  199. if ($report->item_type != 'App\Status') {
  200. continue;
  201. }
  202. if ($report->type != 'post.autospam') {
  203. continue;
  204. }
  205. if ($report->is_spam != null) {
  206. continue;
  207. }
  208. $status = StatusService::get($report->item_id, false);
  209. if (! $status) {
  210. return;
  211. }
  212. $scope = $status['visibility'];
  213. $report->is_spam = $scope == 'unlisted';
  214. $report->in_violation = $report->is_spam;
  215. $report->severity_index = 1;
  216. $report->save();
  217. }
  218. });
  219. Cache::forget('admin-dash:reports:spam-sync');
  220. return redirect('/i/admin/reports/autospam');
  221. }
  222. public function updateSpam(Request $request, $id)
  223. {
  224. $this->validate($request, [
  225. 'action' => 'required|in:dismiss,approve,dismiss-all,approve-all,delete-account,mark-spammer',
  226. ]);
  227. $action = $request->input('action');
  228. $appeal = AccountInterstitial::whereType('post.autospam')
  229. ->whereNull('appeal_handled_at')
  230. ->findOrFail($id);
  231. $meta = json_decode($appeal->meta);
  232. $res = ['status' => 'success'];
  233. $now = now();
  234. Cache::forget('admin-dash:reports:spam-count:total');
  235. Cache::forget('admin-dash:reports:spam-count:30d');
  236. if ($action == 'delete-account') {
  237. if (config('pixelfed.account_deletion') == false) {
  238. abort(404);
  239. }
  240. $user = User::findOrFail($appeal->user_id);
  241. $profile = $user->profile;
  242. if ($user->is_admin == true) {
  243. $mid = $request->user()->id;
  244. abort_if($user->id < $mid, 403);
  245. }
  246. $ts = now()->addMonth();
  247. $user->status = 'delete';
  248. $profile->status = 'delete';
  249. $user->delete_after = $ts;
  250. $profile->delete_after = $ts;
  251. $user->save();
  252. $profile->save();
  253. ModLogService::boot()
  254. ->objectUid($user->id)
  255. ->objectId($user->id)
  256. ->objectType('App\User::class')
  257. ->user($request->user())
  258. ->action('admin.user.delete')
  259. ->accessLevel('admin')
  260. ->save();
  261. Cache::forget('profiles:private');
  262. DeleteAccountPipeline::dispatch($user);
  263. return;
  264. }
  265. if ($action == 'dismiss') {
  266. $appeal->is_spam = true;
  267. $appeal->appeal_handled_at = $now;
  268. $appeal->save();
  269. Cache::forget('pf:bouncer_v0:exemption_by_pid:'.$appeal->user->profile_id);
  270. Cache::forget('pf:bouncer_v0:recent_by_pid:'.$appeal->user->profile_id);
  271. Cache::forget('admin-dash:reports:spam-count');
  272. return $res;
  273. }
  274. if ($action == 'dismiss-all') {
  275. AccountInterstitial::whereType('post.autospam')
  276. ->whereItemType('App\Status')
  277. ->whereNull('appeal_handled_at')
  278. ->whereUserId($appeal->user_id)
  279. ->update(['appeal_handled_at' => $now, 'is_spam' => true]);
  280. Cache::forget('pf:bouncer_v0:exemption_by_pid:'.$appeal->user->profile_id);
  281. Cache::forget('pf:bouncer_v0:recent_by_pid:'.$appeal->user->profile_id);
  282. Cache::forget('admin-dash:reports:spam-count');
  283. return $res;
  284. }
  285. if ($action == 'approve-all') {
  286. AccountInterstitial::whereType('post.autospam')
  287. ->whereItemType('App\Status')
  288. ->whereNull('appeal_handled_at')
  289. ->whereUserId($appeal->user_id)
  290. ->get()
  291. ->each(function ($report) use ($meta) {
  292. $report->is_spam = false;
  293. $report->appeal_handled_at = now();
  294. $report->save();
  295. $status = Status::find($report->item_id);
  296. if ($status) {
  297. $status->is_nsfw = $meta->is_nsfw;
  298. $status->scope = 'public';
  299. $status->visibility = 'public';
  300. $status->save();
  301. StatusService::del($status->id, true);
  302. }
  303. });
  304. Cache::forget('pf:bouncer_v0:exemption_by_pid:'.$appeal->user->profile_id);
  305. Cache::forget('pf:bouncer_v0:recent_by_pid:'.$appeal->user->profile_id);
  306. Cache::forget('admin-dash:reports:spam-count');
  307. return $res;
  308. }
  309. if ($action == 'mark-spammer') {
  310. AccountInterstitial::whereType('post.autospam')
  311. ->whereItemType('App\Status')
  312. ->whereNull('appeal_handled_at')
  313. ->whereUserId($appeal->user_id)
  314. ->update(['appeal_handled_at' => $now, 'is_spam' => true]);
  315. $pro = Profile::whereUserId($appeal->user_id)->firstOrFail();
  316. $pro->update([
  317. 'unlisted' => true,
  318. 'cw' => true,
  319. 'no_autolink' => true,
  320. ]);
  321. Status::whereProfileId($pro->id)
  322. ->get()
  323. ->each(function ($report) {
  324. $status->is_nsfw = $meta->is_nsfw;
  325. $status->scope = 'public';
  326. $status->visibility = 'public';
  327. $status->save();
  328. StatusService::del($status->id, true);
  329. });
  330. Cache::forget('pf:bouncer_v0:exemption_by_pid:'.$appeal->user->profile_id);
  331. Cache::forget('pf:bouncer_v0:recent_by_pid:'.$appeal->user->profile_id);
  332. Cache::forget('admin-dash:reports:spam-count');
  333. return $res;
  334. }
  335. $status = $appeal->status;
  336. $status->is_nsfw = $meta->is_nsfw;
  337. $status->scope = 'public';
  338. $status->visibility = 'public';
  339. $status->save();
  340. $appeal->is_spam = false;
  341. $appeal->appeal_handled_at = now();
  342. $appeal->save();
  343. StatusService::del($status->id);
  344. Cache::forget('pf:bouncer_v0:exemption_by_pid:'.$appeal->user->profile_id);
  345. Cache::forget('pf:bouncer_v0:recent_by_pid:'.$appeal->user->profile_id);
  346. Cache::forget('admin-dash:reports:spam-count');
  347. return $res;
  348. }
  349. public function updateAppeal(Request $request, $id)
  350. {
  351. $this->validate($request, [
  352. 'action' => 'required|in:dismiss,approve',
  353. ]);
  354. $action = $request->input('action');
  355. $appeal = AccountInterstitial::whereNotNull('appeal_requested_at')
  356. ->whereNull('appeal_handled_at')
  357. ->findOrFail($id);
  358. if ($action == 'dismiss') {
  359. $appeal->appeal_handled_at = now();
  360. $appeal->save();
  361. Cache::forget('admin-dash:reports:ai-count');
  362. return redirect('/i/admin/reports/appeals');
  363. }
  364. switch ($appeal->type) {
  365. case 'post.cw':
  366. $status = $appeal->status;
  367. $status->is_nsfw = false;
  368. $status->save();
  369. break;
  370. case 'post.unlist':
  371. $status = $appeal->status;
  372. $status->scope = 'public';
  373. $status->visibility = 'public';
  374. $status->save();
  375. break;
  376. default:
  377. // code...
  378. break;
  379. }
  380. $appeal->appeal_handled_at = now();
  381. $appeal->save();
  382. StatusService::del($status->id, true);
  383. Cache::forget('admin-dash:reports:ai-count');
  384. return redirect('/i/admin/reports/appeals');
  385. }
  386. public function updateReport(Request $request, $id)
  387. {
  388. $this->validate($request, [
  389. 'action' => 'required|string',
  390. ]);
  391. $action = $request->input('action');
  392. $actions = [
  393. 'ignore',
  394. 'cw',
  395. 'unlist',
  396. 'delete',
  397. 'shadowban',
  398. 'ban',
  399. ];
  400. if (! in_array($action, $actions)) {
  401. return abort(403);
  402. }
  403. $report = Report::findOrFail($id);
  404. $this->handleReportAction($report, $action);
  405. Cache::forget('admin-dash:reports:list-cache');
  406. return response()->json(['msg' => 'Success']);
  407. }
  408. public function handleReportAction(Report $report, $action)
  409. {
  410. $item = $report->reported();
  411. $report->admin_seen = Carbon::now();
  412. switch ($action) {
  413. case 'ignore':
  414. $report->not_interested = true;
  415. break;
  416. case 'cw':
  417. Cache::forget('status:thumb:'.$item->id);
  418. $item->is_nsfw = true;
  419. $item->save();
  420. $report->nsfw = true;
  421. StatusService::del($item->id, true);
  422. break;
  423. case 'unlist':
  424. $item->visibility = 'unlisted';
  425. $item->save();
  426. Cache::forget('profiles:private');
  427. StatusService::del($item->id, true);
  428. break;
  429. case 'delete':
  430. // Todo: fire delete job
  431. $report->admin_seen = null;
  432. StatusService::del($item->id, true);
  433. break;
  434. case 'shadowban':
  435. // Todo: fire delete job
  436. $report->admin_seen = null;
  437. break;
  438. case 'ban':
  439. // Todo: fire delete job
  440. $report->admin_seen = null;
  441. break;
  442. default:
  443. $report->admin_seen = null;
  444. break;
  445. }
  446. $report->save();
  447. return $this;
  448. }
  449. protected function actionMap()
  450. {
  451. return [
  452. '1' => 'ignore',
  453. '2' => 'cw',
  454. '3' => 'unlist',
  455. '4' => 'delete',
  456. '5' => 'shadowban',
  457. '6' => 'ban',
  458. ];
  459. }
  460. public function bulkUpdateReport(Request $request)
  461. {
  462. $this->validate($request, [
  463. 'action' => 'required|integer|min:1|max:10',
  464. 'ids' => 'required|array',
  465. ]);
  466. $action = $this->actionMap()[$request->input('action')];
  467. $ids = $request->input('ids');
  468. $reports = Report::whereIn('id', $ids)->whereNull('admin_seen')->get();
  469. foreach ($reports as $report) {
  470. $this->handleReportAction($report, $action);
  471. }
  472. $res = [
  473. 'message' => 'Success',
  474. 'code' => 200,
  475. ];
  476. return response()->json($res);
  477. }
  478. public function reportMailVerifications(Request $request)
  479. {
  480. $ids = Redis::smembers('email:manual');
  481. $ignored = Redis::smembers('email:manual-ignored');
  482. $reports = [];
  483. if ($ids) {
  484. $reports = collect($ids)
  485. ->filter(function ($id) use ($ignored) {
  486. return ! in_array($id, $ignored);
  487. })
  488. ->map(function ($id) {
  489. $user = User::whereProfileId($id)->first();
  490. if (! $user || $user->email_verified_at) {
  491. return [];
  492. }
  493. $account = AccountService::get($id, true);
  494. if (! $account) {
  495. return [];
  496. }
  497. $account['email'] = $user->email;
  498. return $account;
  499. })
  500. ->filter(function ($res) {
  501. return $res && isset($res['id']);
  502. })
  503. ->values();
  504. }
  505. return view('admin.reports.mail_verification', compact('reports', 'ignored'));
  506. }
  507. public function reportMailVerifyIgnore(Request $request)
  508. {
  509. $id = $request->input('id');
  510. Redis::sadd('email:manual-ignored', $id);
  511. return redirect('/i/admin/reports');
  512. }
  513. public function reportMailVerifyApprove(Request $request)
  514. {
  515. $id = $request->input('id');
  516. $user = User::whereProfileId($id)->firstOrFail();
  517. Redis::srem('email:manual', $id);
  518. Redis::srem('email:manual-ignored', $id);
  519. $user->email_verified_at = now();
  520. $user->save();
  521. return redirect('/i/admin/reports');
  522. }
  523. public function reportMailVerifyClearIgnored(Request $request)
  524. {
  525. Redis::del('email:manual-ignored');
  526. return [200];
  527. }
  528. public function reportsStats(Request $request)
  529. {
  530. $stats = [
  531. 'total' => Report::count(),
  532. 'open' => Report::whereNull('admin_seen')->count(),
  533. 'closed' => Report::whereNotNull('admin_seen')->count(),
  534. 'autospam' => AccountInterstitial::whereType('post.autospam')->count(),
  535. 'autospam_open' => AccountInterstitial::whereType('post.autospam')->whereNull(['appeal_handled_at'])->count(),
  536. 'appeals' => AccountInterstitial::whereNotNull('appeal_requested_at')->whereNull('appeal_handled_at')->count(),
  537. 'remote_open' => RemoteReport::whereNull('action_taken_at')->count(),
  538. 'email_verification_requests' => Redis::scard('email:manual'),
  539. ];
  540. return $stats;
  541. }
  542. public function reportsApiAll(Request $request)
  543. {
  544. $filter = $request->input('filter') == 'closed' ? 'closed' : 'open';
  545. $reports = AdminReport::collection(
  546. Report::orderBy('id', 'desc')
  547. ->when($filter, function ($q, $filter) {
  548. return $filter == 'open' ?
  549. $q->whereNull('admin_seen') :
  550. $q->whereNotNull('admin_seen');
  551. })
  552. ->groupBy(['id', 'object_id', 'object_type', 'profile_id'])
  553. ->cursorPaginate(6)
  554. ->withQueryString()
  555. );
  556. return $reports;
  557. }
  558. public function reportsApiRemote(Request $request)
  559. {
  560. $filter = $request->input('filter') == 'closed' ? 'closed' : 'open';
  561. $reports = AdminRemoteReport::collection(
  562. RemoteReport::orderBy('id', 'desc')
  563. ->when($filter, function ($q, $filter) {
  564. return $filter == 'open' ?
  565. $q->whereNull('action_taken_at') :
  566. $q->whereNotNull('action_taken_at');
  567. })
  568. ->cursorPaginate(6)
  569. ->withQueryString()
  570. );
  571. return $reports;
  572. }
  573. public function reportsApiGet(Request $request, $id)
  574. {
  575. $report = Report::findOrFail($id);
  576. return new AdminReport($report);
  577. }
  578. public function reportsApiHandle(Request $request)
  579. {
  580. $this->validate($request, [
  581. 'object_id' => 'required',
  582. 'object_type' => 'required',
  583. 'id' => 'required',
  584. 'action' => 'required|in:ignore,nsfw,unlist,private,delete,delete-all',
  585. 'action_type' => 'required|in:post,profile,story',
  586. ]);
  587. $report = Report::whereObjectId($request->input('object_id'))->findOrFail($request->input('id'));
  588. if ($request->input('action_type') === 'profile') {
  589. return $this->reportsHandleProfileAction($report, $request->input('action'));
  590. } elseif ($request->input('action_type') === 'post') {
  591. return $this->reportsHandleStatusAction($report, $request->input('action'));
  592. } elseif ($request->input('action_type') === 'story') {
  593. return $this->reportsHandleStoryAction($report, $request->input('action'));
  594. }
  595. return $report;
  596. }
  597. protected function reportsHandleStoryAction($report, $action)
  598. {
  599. switch ($action) {
  600. case 'ignore':
  601. Report::whereObjectId($report->object_id)
  602. ->whereObjectType($report->object_type)
  603. ->update([
  604. 'admin_seen' => now(),
  605. ]);
  606. return [200];
  607. break;
  608. case 'delete':
  609. $profile = Profile::find($report->reported_profile_id);
  610. $story = Story::whereProfileId($profile->id)->find($report->object_id);
  611. abort_if(! $story, 400, 'Invalid or missing story');
  612. $story->active = false;
  613. $story->save();
  614. ModLogService::boot()
  615. ->objectUid($profile->id)
  616. ->objectId($report->object_id)
  617. ->objectType('App\Story::class')
  618. ->user(request()->user())
  619. ->action('admin.user.moderate')
  620. ->metadata([
  621. 'action' => 'delete',
  622. 'message' => 'Success!',
  623. ])
  624. ->accessLevel('admin')
  625. ->save();
  626. Report::whereObjectId($report->object_id)
  627. ->whereObjectType($report->object_type)
  628. ->update([
  629. 'admin_seen' => now(),
  630. ]);
  631. StoryDelete::dispatch($story)->onQueue('story');
  632. return [200];
  633. break;
  634. case 'delete-all':
  635. $profile = Profile::find($report->reported_profile_id);
  636. $stories = Story::whereProfileId($profile->id)->whereActive(true)->get();
  637. abort_if(! $stories || ! $stories->count(), 400, 'Invalid or missing stories');
  638. ModLogService::boot()
  639. ->objectUid($profile->id)
  640. ->objectId($report->object_id)
  641. ->objectType('App\Story::class')
  642. ->user(request()->user())
  643. ->action('admin.user.moderate')
  644. ->metadata([
  645. 'action' => 'delete-all',
  646. 'message' => 'Success!',
  647. ])
  648. ->accessLevel('admin')
  649. ->save();
  650. Report::where('reported_profile_id', $profile->id)
  651. ->whereObjectType('App\Story')
  652. ->whereNull('admin_seen')
  653. ->update([
  654. 'admin_seen' => now(),
  655. ]);
  656. $stories->each(function ($story) {
  657. StoryDelete::dispatch($story)->onQueue('story');
  658. });
  659. return [200];
  660. break;
  661. }
  662. }
  663. protected function reportsHandleProfileAction($report, $action)
  664. {
  665. switch ($action) {
  666. case 'ignore':
  667. Report::whereObjectId($report->object_id)
  668. ->whereObjectType($report->object_type)
  669. ->update([
  670. 'admin_seen' => now(),
  671. ]);
  672. return [200];
  673. break;
  674. case 'nsfw':
  675. if ($report->object_type === 'App\Profile') {
  676. $profile = Profile::find($report->object_id);
  677. } elseif ($report->object_type === 'App\Status') {
  678. $status = Status::find($report->object_id);
  679. if (! $status) {
  680. return [200];
  681. }
  682. $profile = Profile::find($status->profile_id);
  683. }
  684. if (! $profile) {
  685. return;
  686. }
  687. abort_if($profile->user && $profile->user->is_admin, 400, 'Cannot moderate an admin account.');
  688. $profile->cw = true;
  689. $profile->save();
  690. if ($profile->remote_url) {
  691. ModeratedProfile::updateOrCreate([
  692. 'profile_url' => $profile->remote_url,
  693. 'profile_id' => $profile->id,
  694. ], [
  695. 'is_nsfw' => true,
  696. 'domain' => $profile->domain,
  697. ]);
  698. }
  699. foreach (Status::whereProfileId($profile->id)->cursor() as $status) {
  700. $status->is_nsfw = true;
  701. $status->save();
  702. StatusService::del($status->id);
  703. PublicTimelineService::rem($status->id);
  704. }
  705. ModLogService::boot()
  706. ->objectUid($profile->id)
  707. ->objectId($profile->id)
  708. ->objectType('App\Profile::class')
  709. ->user(request()->user())
  710. ->action('admin.user.moderate')
  711. ->metadata([
  712. 'action' => 'cw',
  713. 'message' => 'Success!',
  714. ])
  715. ->accessLevel('admin')
  716. ->save();
  717. Report::whereObjectId($report->object_id)
  718. ->whereObjectType($report->object_type)
  719. ->update([
  720. 'nsfw' => true,
  721. 'admin_seen' => now(),
  722. ]);
  723. return [200];
  724. break;
  725. case 'unlist':
  726. if ($report->object_type === 'App\Profile') {
  727. $profile = Profile::find($report->object_id);
  728. } elseif ($report->object_type === 'App\Status') {
  729. $status = Status::find($report->object_id);
  730. if (! $status) {
  731. return [200];
  732. }
  733. $profile = Profile::find($status->profile_id);
  734. }
  735. if (! $profile) {
  736. return;
  737. }
  738. abort_if($profile->user && $profile->user->is_admin, 400, 'Cannot moderate an admin account.');
  739. $profile->unlisted = true;
  740. $profile->save();
  741. if ($profile->remote_url) {
  742. ModeratedProfile::updateOrCreate([
  743. 'profile_url' => $profile->remote_url,
  744. 'profile_id' => $profile->id,
  745. ], [
  746. 'is_unlisted' => true,
  747. 'domain' => $profile->domain,
  748. ]);
  749. }
  750. foreach (Status::whereProfileId($profile->id)->whereScope('public')->cursor() as $status) {
  751. $status->scope = 'unlisted';
  752. $status->visibility = 'unlisted';
  753. $status->save();
  754. StatusService::del($status->id);
  755. PublicTimelineService::rem($status->id);
  756. }
  757. ModLogService::boot()
  758. ->objectUid($profile->id)
  759. ->objectId($profile->id)
  760. ->objectType('App\Profile::class')
  761. ->user(request()->user())
  762. ->action('admin.user.moderate')
  763. ->metadata([
  764. 'action' => 'unlisted',
  765. 'message' => 'Success!',
  766. ])
  767. ->accessLevel('admin')
  768. ->save();
  769. Report::whereObjectId($report->object_id)
  770. ->whereObjectType($report->object_type)
  771. ->update([
  772. 'admin_seen' => now(),
  773. ]);
  774. return [200];
  775. break;
  776. case 'private':
  777. if ($report->object_type === 'App\Profile') {
  778. $profile = Profile::find($report->object_id);
  779. } elseif ($report->object_type === 'App\Status') {
  780. $status = Status::find($report->object_id);
  781. if (! $status) {
  782. return [200];
  783. }
  784. $profile = Profile::find($status->profile_id);
  785. }
  786. if (! $profile) {
  787. return;
  788. }
  789. abort_if($profile->user && $profile->user->is_admin, 400, 'Cannot moderate an admin account.');
  790. $profile->unlisted = true;
  791. $profile->save();
  792. if ($profile->remote_url) {
  793. ModeratedProfile::updateOrCreate([
  794. 'profile_url' => $profile->remote_url,
  795. 'profile_id' => $profile->id,
  796. ], [
  797. 'is_unlisted' => true,
  798. 'domain' => $profile->domain,
  799. ]);
  800. }
  801. foreach (Status::whereProfileId($profile->id)->cursor() as $status) {
  802. $status->scope = 'private';
  803. $status->visibility = 'private';
  804. $status->save();
  805. StatusService::del($status->id);
  806. PublicTimelineService::rem($status->id);
  807. }
  808. ModLogService::boot()
  809. ->objectUid($profile->id)
  810. ->objectId($profile->id)
  811. ->objectType('App\Profile::class')
  812. ->user(request()->user())
  813. ->action('admin.user.moderate')
  814. ->metadata([
  815. 'action' => 'private',
  816. 'message' => 'Success!',
  817. ])
  818. ->accessLevel('admin')
  819. ->save();
  820. Report::whereObjectId($report->object_id)
  821. ->whereObjectType($report->object_type)
  822. ->update([
  823. 'admin_seen' => now(),
  824. ]);
  825. return [200];
  826. break;
  827. case 'delete':
  828. if (config('pixelfed.account_deletion') == false) {
  829. abort(404);
  830. }
  831. if ($report->object_type === 'App\Profile') {
  832. $profile = Profile::find($report->object_id);
  833. } elseif ($report->object_type === 'App\Status') {
  834. $status = Status::find($report->object_id);
  835. if (! $status) {
  836. return [200];
  837. }
  838. $profile = Profile::find($status->profile_id);
  839. }
  840. if (! $profile) {
  841. return;
  842. }
  843. abort_if($profile->user && $profile->user->is_admin, 400, 'Cannot delete an admin account.');
  844. $ts = now()->addMonth();
  845. if ($profile->remote_url) {
  846. ModeratedProfile::updateOrCreate([
  847. 'profile_url' => $profile->remote_url,
  848. 'profile_id' => $profile->id,
  849. ], [
  850. 'is_banned' => true,
  851. 'domain' => $profile->domain,
  852. ]);
  853. }
  854. if ($profile->user_id) {
  855. $user = $profile->user;
  856. abort_if($user->is_admin, 403, 'You cannot delete admin accounts.');
  857. $user->status = 'delete';
  858. $user->delete_after = $ts;
  859. $user->save();
  860. }
  861. $profile->status = 'delete';
  862. $profile->delete_after = $ts;
  863. $profile->save();
  864. ModLogService::boot()
  865. ->objectUid($profile->id)
  866. ->objectId($profile->id)
  867. ->objectType('App\Profile::class')
  868. ->user(request()->user())
  869. ->action('admin.user.delete')
  870. ->accessLevel('admin')
  871. ->save();
  872. Report::whereObjectId($report->object_id)
  873. ->whereObjectType($report->object_type)
  874. ->update([
  875. 'admin_seen' => now(),
  876. ]);
  877. if ($profile->user_id) {
  878. DB::table('oauth_access_tokens')->whereUserId($user->id)->delete();
  879. DB::table('oauth_auth_codes')->whereUserId($user->id)->delete();
  880. $user->email = $user->id;
  881. $user->password = '';
  882. $user->status = 'delete';
  883. $user->save();
  884. $profile->status = 'delete';
  885. $profile->delete_after = now()->addMonth();
  886. $profile->save();
  887. AccountService::del($profile->id);
  888. DeleteAccountPipeline::dispatch($user)->onQueue('high');
  889. } else {
  890. $profile->status = 'delete';
  891. $profile->delete_after = now()->addMonth();
  892. $profile->save();
  893. AccountService::del($profile->id);
  894. DeleteRemoteProfilePipeline::dispatch($profile)->onQueue('high');
  895. }
  896. return [200];
  897. break;
  898. }
  899. }
  900. protected function reportsHandleStatusAction($report, $action)
  901. {
  902. switch ($action) {
  903. case 'ignore':
  904. Report::whereObjectId($report->object_id)
  905. ->whereObjectType($report->object_type)
  906. ->update([
  907. 'admin_seen' => now(),
  908. ]);
  909. return [200];
  910. break;
  911. case 'nsfw':
  912. $status = Status::find($report->object_id);
  913. if (! $status) {
  914. return [200];
  915. }
  916. abort_if($status->profile->user && $status->profile->user->is_admin, 400, 'Cannot moderate an admin account post.');
  917. $status->is_nsfw = true;
  918. $status->save();
  919. StatusService::del($status->id);
  920. ModLogService::boot()
  921. ->objectUid($status->profile_id)
  922. ->objectId($status->profile_id)
  923. ->objectType('App\Status::class')
  924. ->user(request()->user())
  925. ->action('admin.status.moderate')
  926. ->metadata([
  927. 'action' => 'cw',
  928. 'message' => 'Success!',
  929. ])
  930. ->accessLevel('admin')
  931. ->save();
  932. Report::whereObjectId($report->object_id)
  933. ->whereObjectType($report->object_type)
  934. ->update([
  935. 'nsfw' => true,
  936. 'admin_seen' => now(),
  937. ]);
  938. return [200];
  939. break;
  940. case 'private':
  941. $status = Status::find($report->object_id);
  942. if (! $status) {
  943. return [200];
  944. }
  945. abort_if($status->profile->user && $status->profile->user->is_admin, 400, 'Cannot moderate an admin account post.');
  946. $status->scope = 'private';
  947. $status->visibility = 'private';
  948. $status->save();
  949. StatusService::del($status->id);
  950. PublicTimelineService::rem($status->id);
  951. ModLogService::boot()
  952. ->objectUid($status->profile_id)
  953. ->objectId($status->profile_id)
  954. ->objectType('App\Status::class')
  955. ->user(request()->user())
  956. ->action('admin.status.moderate')
  957. ->metadata([
  958. 'action' => 'private',
  959. 'message' => 'Success!',
  960. ])
  961. ->accessLevel('admin')
  962. ->save();
  963. Report::whereObjectId($report->object_id)
  964. ->whereObjectType($report->object_type)
  965. ->update([
  966. 'admin_seen' => now(),
  967. ]);
  968. return [200];
  969. break;
  970. case 'unlist':
  971. $status = Status::find($report->object_id);
  972. if (! $status) {
  973. return [200];
  974. }
  975. abort_if($status->profile->user && $status->profile->user->is_admin, 400, 'Cannot moderate an admin account post.');
  976. if ($status->scope === 'public') {
  977. $status->scope = 'unlisted';
  978. $status->visibility = 'unlisted';
  979. $status->save();
  980. StatusService::del($status->id);
  981. PublicTimelineService::rem($status->id);
  982. }
  983. ModLogService::boot()
  984. ->objectUid($status->profile_id)
  985. ->objectId($status->profile_id)
  986. ->objectType('App\Status::class')
  987. ->user(request()->user())
  988. ->action('admin.status.moderate')
  989. ->metadata([
  990. 'action' => 'unlist',
  991. 'message' => 'Success!',
  992. ])
  993. ->accessLevel('admin')
  994. ->save();
  995. Report::whereObjectId($report->object_id)
  996. ->whereObjectType($report->object_type)
  997. ->update([
  998. 'admin_seen' => now(),
  999. ]);
  1000. return [200];
  1001. break;
  1002. case 'delete':
  1003. $status = Status::find($report->object_id);
  1004. if (! $status) {
  1005. return [200];
  1006. }
  1007. $profile = $status->profile;
  1008. abort_if($profile->user && $profile->user->is_admin, 400, 'Cannot delete an admin account post.');
  1009. StatusService::del($status->id);
  1010. if ($profile->user_id != null && $profile->domain == null) {
  1011. PublicTimelineService::del($status->id);
  1012. StatusDelete::dispatch($status)->onQueue('high');
  1013. } else {
  1014. NetworkTimelineService::del($status->id);
  1015. RemoteStatusDelete::dispatch($status)->onQueue('high');
  1016. }
  1017. Report::whereObjectId($report->object_id)
  1018. ->whereObjectType($report->object_type)
  1019. ->update([
  1020. 'admin_seen' => now(),
  1021. ]);
  1022. return [200];
  1023. break;
  1024. }
  1025. }
  1026. public function reportsApiSpamAll(Request $request)
  1027. {
  1028. $tab = $request->input('tab', 'home');
  1029. $appeals = AdminSpamReport::collection(
  1030. AccountInterstitial::orderBy('id', 'desc')
  1031. ->whereType('post.autospam')
  1032. ->whereNull('appeal_handled_at')
  1033. ->cursorPaginate(6)
  1034. ->withQueryString()
  1035. );
  1036. return $appeals;
  1037. }
  1038. public function reportsApiSpamHandle(Request $request)
  1039. {
  1040. $this->validate($request, [
  1041. 'id' => 'required',
  1042. 'action' => 'required|in:mark-read,mark-not-spam,mark-all-read,mark-all-not-spam,delete-profile',
  1043. ]);
  1044. $action = $request->input('action');
  1045. abort_if(
  1046. $action === 'delete-profile' &&
  1047. ! config('pixelfed.account_deletion'),
  1048. 404,
  1049. "Cannot delete profile, account_deletion is disabled.\n\n Set `ACCOUNT_DELETION=true` in .env and re-cache config."
  1050. );
  1051. $report = AccountInterstitial::with('user')
  1052. ->whereType('post.autospam')
  1053. ->whereNull('appeal_handled_at')
  1054. ->findOrFail($request->input('id'));
  1055. $this->reportsHandleSpamAction($report, $action);
  1056. Cache::forget('admin-dash:reports:spam-count');
  1057. Cache::forget('pf:bouncer_v0:exemption_by_pid:'.$report->user->profile_id);
  1058. Cache::forget('pf:bouncer_v0:recent_by_pid:'.$report->user->profile_id);
  1059. return [$action, $report];
  1060. }
  1061. public function reportsHandleSpamAction($appeal, $action)
  1062. {
  1063. $meta = json_decode($appeal->meta);
  1064. if ($action == 'mark-read') {
  1065. $appeal->is_spam = true;
  1066. $appeal->appeal_handled_at = now();
  1067. $appeal->save();
  1068. PublicTimelineService::del($appeal->item_id);
  1069. }
  1070. if ($action == 'mark-not-spam') {
  1071. $status = $appeal->status;
  1072. $status->is_nsfw = $meta->is_nsfw;
  1073. $status->scope = 'public';
  1074. $status->visibility = 'public';
  1075. $status->save();
  1076. $appeal->is_spam = false;
  1077. $appeal->appeal_handled_at = now();
  1078. $appeal->save();
  1079. Notification::whereAction('autospam.warning')
  1080. ->whereProfileId($appeal->user->profile_id)
  1081. ->get()
  1082. ->each(function ($n) use ($appeal) {
  1083. NotificationService::del($appeal->user->profile_id, $n->id);
  1084. $n->forceDelete();
  1085. });
  1086. StatusService::del($status->id);
  1087. StatusService::get($status->id);
  1088. if ($status->in_reply_to_id == null && $status->reblog_of_id == null) {
  1089. PublicTimelineService::add($status->id);
  1090. }
  1091. }
  1092. if ($action == 'mark-all-read') {
  1093. AccountInterstitial::whereType('post.autospam')
  1094. ->whereItemType('App\Status')
  1095. ->whereNull('appeal_handled_at')
  1096. ->whereUserId($appeal->user_id)
  1097. ->update([
  1098. 'appeal_handled_at' => now(),
  1099. 'is_spam' => true,
  1100. ]);
  1101. }
  1102. if ($action == 'mark-all-not-spam') {
  1103. AccountInterstitial::whereType('post.autospam')
  1104. ->whereItemType('App\Status')
  1105. ->whereUserId($appeal->user_id)
  1106. ->get()
  1107. ->each(function ($report) use ($meta) {
  1108. $report->is_spam = false;
  1109. $report->appeal_handled_at = now();
  1110. $report->save();
  1111. $status = Status::find($report->item_id);
  1112. if ($status) {
  1113. $status->is_nsfw = $meta->is_nsfw;
  1114. $status->scope = 'public';
  1115. $status->visibility = 'public';
  1116. $status->save();
  1117. StatusService::del($status->id);
  1118. }
  1119. Notification::whereAction('autospam.warning')
  1120. ->whereProfileId($report->user->profile_id)
  1121. ->get()
  1122. ->each(function ($n) use ($report) {
  1123. NotificationService::del($report->user->profile_id, $n->id);
  1124. $n->forceDelete();
  1125. });
  1126. });
  1127. }
  1128. if ($action == 'delete-profile') {
  1129. $user = User::findOrFail($appeal->user_id);
  1130. $profile = $user->profile;
  1131. if ($user->is_admin == true) {
  1132. $mid = request()->user()->id;
  1133. abort_if($user->id < $mid, 403, 'You cannot delete an admin account.');
  1134. }
  1135. $ts = now()->addMonth();
  1136. $user->status = 'delete';
  1137. $profile->status = 'delete';
  1138. $user->delete_after = $ts;
  1139. $profile->delete_after = $ts;
  1140. $user->save();
  1141. $profile->save();
  1142. $appeal->appeal_handled_at = now();
  1143. $appeal->save();
  1144. ModLogService::boot()
  1145. ->objectUid($user->id)
  1146. ->objectId($user->id)
  1147. ->objectType('App\User::class')
  1148. ->user(request()->user())
  1149. ->action('admin.user.delete')
  1150. ->accessLevel('admin')
  1151. ->save();
  1152. Cache::forget('profiles:private');
  1153. DeleteAccountPipeline::dispatch($user);
  1154. }
  1155. }
  1156. public function reportsApiSpamGet(Request $request, $id)
  1157. {
  1158. $report = AccountInterstitial::findOrFail($id);
  1159. return new AdminSpamReport($report);
  1160. }
  1161. public function reportsApiRemoteHandle(Request $request)
  1162. {
  1163. $this->validate($request, [
  1164. 'id' => 'required|exists:remote_reports,id',
  1165. 'action' => 'required|in:mark-read,cw-posts,unlist-posts,delete-posts,private-posts,mark-all-read-by-domain,mark-all-read-by-username,cw-all-posts,private-all-posts,unlist-all-posts',
  1166. ]);
  1167. $report = RemoteReport::findOrFail($request->input('id'));
  1168. $user = User::whereProfileId($report->account_id)->first();
  1169. $ogPublicStatuses = [];
  1170. $ogUnlistedStatuses = [];
  1171. $ogNonCwStatuses = [];
  1172. switch ($request->input('action')) {
  1173. case 'mark-read':
  1174. $report->action_taken_at = now();
  1175. $report->save();
  1176. break;
  1177. case 'mark-all-read-by-domain':
  1178. RemoteReport::whereInstanceId($report->instance_id)->update(['action_taken_at' => now()]);
  1179. break;
  1180. case 'cw-posts':
  1181. $statuses = Status::find($report->status_ids);
  1182. foreach ($statuses as $status) {
  1183. if ($report->account_id != $status->profile_id) {
  1184. continue;
  1185. }
  1186. if (! $status->is_nsfw) {
  1187. $ogNonCwStatuses[] = $status->id;
  1188. }
  1189. $status->is_nsfw = true;
  1190. $status->saveQuietly();
  1191. StatusService::del($status->id);
  1192. }
  1193. $report->action_taken_at = now();
  1194. $report->save();
  1195. break;
  1196. case 'cw-all-posts':
  1197. foreach (Status::whereProfileId($report->account_id)->lazyById(50, 'id') as $status) {
  1198. if ($status->is_nsfw || $status->reblog_of_id) {
  1199. continue;
  1200. }
  1201. if (! $status->is_nsfw) {
  1202. $ogNonCwStatuses[] = $status->id;
  1203. }
  1204. $status->is_nsfw = true;
  1205. $status->saveQuietly();
  1206. StatusService::del($status->id);
  1207. }
  1208. break;
  1209. case 'unlist-posts':
  1210. $statuses = Status::find($report->status_ids);
  1211. foreach ($statuses as $status) {
  1212. if ($report->account_id != $status->profile_id) {
  1213. continue;
  1214. }
  1215. if ($status->scope === 'public') {
  1216. $ogPublicStatuses[] = $status->id;
  1217. $status->scope = 'unlisted';
  1218. $status->visibility = 'unlisted';
  1219. $status->saveQuietly();
  1220. StatusService::del($status->id);
  1221. }
  1222. }
  1223. $report->action_taken_at = now();
  1224. $report->save();
  1225. break;
  1226. case 'unlist-all-posts':
  1227. foreach (Status::whereProfileId($report->account_id)->lazyById(50, 'id') as $status) {
  1228. if ($status->visibility !== 'public' || $status->reblog_of_id) {
  1229. continue;
  1230. }
  1231. $ogPublicStatuses[] = $status->id;
  1232. $status->visibility = 'unlisted';
  1233. $status->scope = 'unlisted';
  1234. $status->saveQuietly();
  1235. StatusService::del($status->id);
  1236. }
  1237. break;
  1238. case 'private-posts':
  1239. $statuses = Status::find($report->status_ids);
  1240. foreach ($statuses as $status) {
  1241. if ($report->account_id != $status->profile_id) {
  1242. continue;
  1243. }
  1244. if (in_array($status->scope, ['public', 'unlisted', 'private'])) {
  1245. if ($status->scope === 'public') {
  1246. $ogPublicStatuses[] = $status->id;
  1247. }
  1248. $status->scope = 'private';
  1249. $status->visibility = 'private';
  1250. $status->saveQuietly();
  1251. StatusService::del($status->id);
  1252. }
  1253. }
  1254. $report->action_taken_at = now();
  1255. $report->save();
  1256. break;
  1257. case 'private-all-posts':
  1258. foreach (Status::whereProfileId($report->account_id)->lazyById(50, 'id') as $status) {
  1259. if (! in_array($status->visibility, ['public', 'unlisted']) || $status->reblog_of_id) {
  1260. continue;
  1261. }
  1262. if ($status->visibility === 'public') {
  1263. $ogPublicStatuses[] = $status->id;
  1264. } elseif ($status->visibility === 'unlisted') {
  1265. $ogUnlistedStatuses[] = $status->id;
  1266. }
  1267. $status->visibility = 'private';
  1268. $status->scope = 'private';
  1269. $status->saveQuietly();
  1270. StatusService::del($status->id);
  1271. }
  1272. break;
  1273. case 'delete-posts':
  1274. $statuses = Status::find($report->status_ids);
  1275. foreach ($statuses as $status) {
  1276. if ($report->account_id != $status->profile_id) {
  1277. continue;
  1278. }
  1279. StatusDelete::dispatch($status);
  1280. }
  1281. $report->action_taken_at = now();
  1282. $report->save();
  1283. break;
  1284. case 'mark-all-read-by-username':
  1285. RemoteReport::whereNull('action_taken_at')->whereAccountId($report->account_id)->update(['action_taken_at' => now()]);
  1286. break;
  1287. default:
  1288. abort(404);
  1289. break;
  1290. }
  1291. if ($ogPublicStatuses && count($ogPublicStatuses)) {
  1292. Storage::disk('local')->put('mod-log-cache/'.$report->account_id.'/'.now()->format('Y-m-d').'-og-public-statuses.json', json_encode($ogPublicStatuses, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
  1293. }
  1294. if ($ogNonCwStatuses && count($ogNonCwStatuses)) {
  1295. Storage::disk('local')->put('mod-log-cache/'.$report->account_id.'/'.now()->format('Y-m-d').'-og-noncw-statuses.json', json_encode($ogNonCwStatuses, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
  1296. }
  1297. if ($ogUnlistedStatuses && count($ogUnlistedStatuses)) {
  1298. Storage::disk('local')->put('mod-log-cache/'.$report->account_id.'/'.now()->format('Y-m-d').'-og-unlisted-statuses.json', json_encode($ogUnlistedStatuses, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
  1299. }
  1300. ModLogService::boot()
  1301. ->user(request()->user())
  1302. ->objectUid($user ? $user->id : null)
  1303. ->objectId($report->id)
  1304. ->objectType('App\Report::class')
  1305. ->action('admin.report.moderate')
  1306. ->metadata([
  1307. 'action' => $request->input('action'),
  1308. 'duration_active' => now()->parse($report->created_at)->diffForHumans(),
  1309. ])
  1310. ->accessLevel('admin')
  1311. ->save();
  1312. if ($report->status_ids) {
  1313. foreach ($report->status_ids as $sid) {
  1314. RemoteReport::whereNull('action_taken_at')
  1315. ->whereJsonContains('status_ids', [$sid])
  1316. ->update(['action_taken_at' => now()]);
  1317. }
  1318. }
  1319. return [200];
  1320. }
  1321. }