1
0

AdminReportController.php 62 KB

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