DirectMessageController.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819
  1. <?php
  2. namespace App\Http\Controllers;
  3. use Auth, Cache;
  4. use Illuminate\Http\Request;
  5. use App\{
  6. DirectMessage,
  7. Media,
  8. Notification,
  9. Profile,
  10. Status,
  11. User,
  12. UserFilter,
  13. UserSetting
  14. };
  15. use App\Services\MediaPathService;
  16. use App\Services\MediaBlocklistService;
  17. use App\Jobs\StatusPipeline\NewStatusPipeline;
  18. use Illuminate\Support\Str;
  19. use App\Util\ActivityPub\Helpers;
  20. use App\Services\WebfingerService;
  21. use App\Models\Conversation;
  22. class DirectMessageController extends Controller
  23. {
  24. public function __construct()
  25. {
  26. $this->middleware('auth');
  27. }
  28. public function browse(Request $request)
  29. {
  30. $this->validate($request, [
  31. 'a' => 'nullable|string|in:inbox,sent,filtered',
  32. 'page' => 'nullable|integer|min:1|max:99'
  33. ]);
  34. $profile = $request->user()->profile_id;
  35. $action = $request->input('a', 'inbox');
  36. $page = $request->input('page');
  37. if(config('database.default') == 'pgsql') {
  38. if($action == 'inbox') {
  39. $dms = DirectMessage::select('id', 'type', 'to_id', 'from_id', 'id', 'status_id', 'is_hidden', 'meta', 'created_at', 'read_at')
  40. ->whereToId($profile)
  41. ->with(['author','status'])
  42. ->whereIsHidden(false)
  43. ->when($page, function($q, $page) {
  44. if($page > 1) {
  45. return $q->offset($page * 8 - 8);
  46. }
  47. })
  48. ->latest()
  49. ->get()
  50. ->unique('from_id')
  51. ->take(8)
  52. ->map(function($r) use($profile) {
  53. return $r->from_id !== $profile ? [
  54. 'id' => (string) $r->from_id,
  55. 'name' => $r->author->name,
  56. 'username' => $r->author->username,
  57. 'avatar' => $r->author->avatarUrl(),
  58. 'url' => $r->author->url(),
  59. 'isLocal' => (bool) !$r->author->domain,
  60. 'domain' => $r->author->domain,
  61. 'timeAgo' => $r->created_at->diffForHumans(null, true, true),
  62. 'lastMessage' => $r->status->caption,
  63. 'messages' => []
  64. ] : [
  65. 'id' => (string) $r->to_id,
  66. 'name' => $r->recipient->name,
  67. 'username' => $r->recipient->username,
  68. 'avatar' => $r->recipient->avatarUrl(),
  69. 'url' => $r->recipient->url(),
  70. 'isLocal' => (bool) !$r->recipient->domain,
  71. 'domain' => $r->recipient->domain,
  72. 'timeAgo' => $r->created_at->diffForHumans(null, true, true),
  73. 'lastMessage' => $r->status->caption,
  74. 'messages' => []
  75. ];
  76. })->values();
  77. }
  78. if($action == 'sent') {
  79. $dms = DirectMessage::select('id', 'type', 'to_id', 'from_id', 'id', 'status_id', 'is_hidden', 'meta', 'created_at', 'read_at')
  80. ->whereFromId($profile)
  81. ->with(['author','status'])
  82. ->orderBy('id', 'desc')
  83. ->when($page, function($q, $page) {
  84. if($page > 1) {
  85. return $q->offset($page * 8 - 8);
  86. }
  87. })
  88. ->get()
  89. ->unique('to_id')
  90. ->take(8)
  91. ->map(function($r) use($profile) {
  92. return $r->from_id !== $profile ? [
  93. 'id' => (string) $r->from_id,
  94. 'name' => $r->author->name,
  95. 'username' => $r->author->username,
  96. 'avatar' => $r->author->avatarUrl(),
  97. 'url' => $r->author->url(),
  98. 'isLocal' => (bool) !$r->author->domain,
  99. 'domain' => $r->author->domain,
  100. 'timeAgo' => $r->created_at->diffForHumans(null, true, true),
  101. 'lastMessage' => $r->status->caption,
  102. 'messages' => []
  103. ] : [
  104. 'id' => (string) $r->to_id,
  105. 'name' => $r->recipient->name,
  106. 'username' => $r->recipient->username,
  107. 'avatar' => $r->recipient->avatarUrl(),
  108. 'url' => $r->recipient->url(),
  109. 'isLocal' => (bool) !$r->recipient->domain,
  110. 'domain' => $r->recipient->domain,
  111. 'timeAgo' => $r->created_at->diffForHumans(null, true, true),
  112. 'lastMessage' => $r->status->caption,
  113. 'messages' => []
  114. ];
  115. });
  116. }
  117. if($action == 'filtered') {
  118. $dms = DirectMessage::select('id', 'type', 'to_id', 'from_id', 'id', 'status_id', 'is_hidden', 'meta', 'created_at', 'read_at')
  119. ->whereToId($profile)
  120. ->with(['author','status'])
  121. ->whereIsHidden(true)
  122. ->orderBy('id', 'desc')
  123. ->when($page, function($q, $page) {
  124. if($page > 1) {
  125. return $q->offset($page * 8 - 8);
  126. }
  127. })
  128. ->get()
  129. ->unique('from_id')
  130. ->take(8)
  131. ->map(function($r) use($profile) {
  132. return $r->from_id !== $profile ? [
  133. 'id' => (string) $r->from_id,
  134. 'name' => $r->author->name,
  135. 'username' => $r->author->username,
  136. 'avatar' => $r->author->avatarUrl(),
  137. 'url' => $r->author->url(),
  138. 'isLocal' => (bool) !$r->author->domain,
  139. 'domain' => $r->author->domain,
  140. 'timeAgo' => $r->created_at->diffForHumans(null, true, true),
  141. 'lastMessage' => $r->status->caption,
  142. 'messages' => []
  143. ] : [
  144. 'id' => (string) $r->to_id,
  145. 'name' => $r->recipient->name,
  146. 'username' => $r->recipient->username,
  147. 'avatar' => $r->recipient->avatarUrl(),
  148. 'url' => $r->recipient->url(),
  149. 'isLocal' => (bool) !$r->recipient->domain,
  150. 'domain' => $r->recipient->domain,
  151. 'timeAgo' => $r->created_at->diffForHumans(null, true, true),
  152. 'lastMessage' => $r->status->caption,
  153. 'messages' => []
  154. ];
  155. });
  156. }
  157. } elseif(config('database.default') == 'mysql') {
  158. if($action == 'inbox') {
  159. $dms = DirectMessage::selectRaw('*, max(created_at) as createdAt')
  160. ->whereToId($profile)
  161. ->with(['author','status'])
  162. ->whereIsHidden(false)
  163. ->groupBy('from_id')
  164. ->latest()
  165. ->when($page, function($q, $page) {
  166. if($page > 1) {
  167. return $q->offset($page * 8 - 8);
  168. }
  169. })
  170. ->limit(8)
  171. ->get()
  172. ->map(function($r) use($profile) {
  173. return $r->from_id !== $profile ? [
  174. 'id' => (string) $r->from_id,
  175. 'name' => $r->author->name,
  176. 'username' => $r->author->username,
  177. 'avatar' => $r->author->avatarUrl(),
  178. 'url' => $r->author->url(),
  179. 'isLocal' => (bool) !$r->author->domain,
  180. 'domain' => $r->author->domain,
  181. 'timeAgo' => $r->created_at->diffForHumans(null, true, true),
  182. 'lastMessage' => $r->status->caption,
  183. 'messages' => []
  184. ] : [
  185. 'id' => (string) $r->to_id,
  186. 'name' => $r->recipient->name,
  187. 'username' => $r->recipient->username,
  188. 'avatar' => $r->recipient->avatarUrl(),
  189. 'url' => $r->recipient->url(),
  190. 'isLocal' => (bool) !$r->recipient->domain,
  191. 'domain' => $r->recipient->domain,
  192. 'timeAgo' => $r->created_at->diffForHumans(null, true, true),
  193. 'lastMessage' => $r->status->caption,
  194. 'messages' => []
  195. ];
  196. });
  197. }
  198. if($action == 'sent') {
  199. $dms = DirectMessage::selectRaw('*, max(created_at) as createdAt')
  200. ->whereFromId($profile)
  201. ->with(['author','status'])
  202. ->groupBy('to_id')
  203. ->orderBy('createdAt', 'desc')
  204. ->when($page, function($q, $page) {
  205. if($page > 1) {
  206. return $q->offset($page * 8 - 8);
  207. }
  208. })
  209. ->limit(8)
  210. ->get()
  211. ->map(function($r) use($profile) {
  212. return $r->from_id !== $profile ? [
  213. 'id' => (string) $r->from_id,
  214. 'name' => $r->author->name,
  215. 'username' => $r->author->username,
  216. 'avatar' => $r->author->avatarUrl(),
  217. 'url' => $r->author->url(),
  218. 'isLocal' => (bool) !$r->author->domain,
  219. 'domain' => $r->author->domain,
  220. 'timeAgo' => $r->created_at->diffForHumans(null, true, true),
  221. 'lastMessage' => $r->status->caption,
  222. 'messages' => []
  223. ] : [
  224. 'id' => (string) $r->to_id,
  225. 'name' => $r->recipient->name,
  226. 'username' => $r->recipient->username,
  227. 'avatar' => $r->recipient->avatarUrl(),
  228. 'url' => $r->recipient->url(),
  229. 'isLocal' => (bool) !$r->recipient->domain,
  230. 'domain' => $r->recipient->domain,
  231. 'timeAgo' => $r->created_at->diffForHumans(null, true, true),
  232. 'lastMessage' => $r->status->caption,
  233. 'messages' => []
  234. ];
  235. });
  236. }
  237. if($action == 'filtered') {
  238. $dms = DirectMessage::selectRaw('*, max(created_at) as createdAt')
  239. ->whereToId($profile)
  240. ->with(['author','status'])
  241. ->whereIsHidden(true)
  242. ->groupBy('from_id')
  243. ->orderBy('createdAt', 'desc')
  244. ->when($page, function($q, $page) {
  245. if($page > 1) {
  246. return $q->offset($page * 8 - 8);
  247. }
  248. })
  249. ->limit(8)
  250. ->get()
  251. ->map(function($r) use($profile) {
  252. return $r->from_id !== $profile ? [
  253. 'id' => (string) $r->from_id,
  254. 'name' => $r->author->name,
  255. 'username' => $r->author->username,
  256. 'avatar' => $r->author->avatarUrl(),
  257. 'url' => $r->author->url(),
  258. 'isLocal' => (bool) !$r->author->domain,
  259. 'domain' => $r->author->domain,
  260. 'timeAgo' => $r->created_at->diffForHumans(null, true, true),
  261. 'lastMessage' => $r->status->caption,
  262. 'messages' => []
  263. ] : [
  264. 'id' => (string) $r->to_id,
  265. 'name' => $r->recipient->name,
  266. 'username' => $r->recipient->username,
  267. 'avatar' => $r->recipient->avatarUrl(),
  268. 'url' => $r->recipient->url(),
  269. 'isLocal' => (bool) !$r->recipient->domain,
  270. 'domain' => $r->recipient->domain,
  271. 'timeAgo' => $r->created_at->diffForHumans(null, true, true),
  272. 'lastMessage' => $r->status->caption,
  273. 'messages' => []
  274. ];
  275. });
  276. }
  277. }
  278. return response()->json($dms->all());
  279. }
  280. public function create(Request $request)
  281. {
  282. $this->validate($request, [
  283. 'to_id' => 'required',
  284. 'message' => 'required|string|min:1|max:500',
  285. 'type' => 'required|in:text,emoji'
  286. ]);
  287. $profile = $request->user()->profile;
  288. $recipient = Profile::where('id', '!=', $profile->id)->findOrFail($request->input('to_id'));
  289. abort_if(in_array($profile->id, $recipient->blockedIds()->toArray()), 403);
  290. $msg = $request->input('message');
  291. if((!$recipient->domain && $recipient->user->settings->public_dm == false) || $recipient->is_private) {
  292. if($recipient->follows($profile) == true) {
  293. $hidden = false;
  294. } else {
  295. $hidden = true;
  296. }
  297. } else {
  298. $hidden = false;
  299. }
  300. $status = new Status;
  301. $status->profile_id = $profile->id;
  302. $status->caption = $msg;
  303. $status->rendered = $msg;
  304. $status->visibility = 'direct';
  305. $status->scope = 'direct';
  306. $status->in_reply_to_profile_id = $recipient->id;
  307. $status->save();
  308. $dm = new DirectMessage;
  309. $dm->to_id = $recipient->id;
  310. $dm->from_id = $profile->id;
  311. $dm->status_id = $status->id;
  312. $dm->is_hidden = $hidden;
  313. $dm->type = $request->input('type');
  314. $dm->save();
  315. Conversation::updateOrInsert(
  316. [
  317. 'to_id' => $recipient->id,
  318. 'from_id' => $profile->id
  319. ],
  320. [
  321. 'type' => $dm->type,
  322. 'status_id' => $status->id,
  323. 'dm_id' => $dm->id,
  324. 'is_hidden' => $hidden
  325. ]
  326. );
  327. if(filter_var($msg, FILTER_VALIDATE_URL)) {
  328. if(Helpers::validateUrl($msg)) {
  329. $dm->type = 'link';
  330. $dm->meta = [
  331. 'domain' => parse_url($msg, PHP_URL_HOST),
  332. 'local' => parse_url($msg, PHP_URL_HOST) ==
  333. parse_url(config('app.url'), PHP_URL_HOST)
  334. ];
  335. $dm->save();
  336. }
  337. }
  338. $nf = UserFilter::whereUserId($recipient->id)
  339. ->whereFilterableId($profile->id)
  340. ->whereFilterableType('App\Profile')
  341. ->whereFilterType('dm.mute')
  342. ->exists();
  343. if($recipient->domain == null && $hidden == false && !$nf) {
  344. $notification = new Notification();
  345. $notification->profile_id = $recipient->id;
  346. $notification->actor_id = $profile->id;
  347. $notification->action = 'dm';
  348. $notification->message = $dm->toText();
  349. $notification->rendered = $dm->toHtml();
  350. $notification->item_id = $dm->id;
  351. $notification->item_type = "App\DirectMessage";
  352. $notification->save();
  353. }
  354. if($recipient->domain) {
  355. $this->remoteDeliver($dm);
  356. }
  357. $res = [
  358. 'id' => (string) $dm->id,
  359. 'isAuthor' => $profile->id == $dm->from_id,
  360. 'reportId' => (string) $dm->status_id,
  361. 'hidden' => (bool) $dm->is_hidden,
  362. 'type' => $dm->type,
  363. 'text' => $dm->status->caption,
  364. 'media' => null,
  365. 'timeAgo' => $dm->created_at->diffForHumans(null,null,true),
  366. 'seen' => $dm->read_at != null,
  367. 'meta' => $dm->meta
  368. ];
  369. return response()->json($res);
  370. }
  371. public function thread(Request $request)
  372. {
  373. $this->validate($request, [
  374. 'pid' => 'required'
  375. ]);
  376. $uid = $request->user()->profile_id;
  377. $pid = $request->input('pid');
  378. $max_id = $request->input('max_id');
  379. $min_id = $request->input('min_id');
  380. $r = Profile::findOrFail($pid);
  381. if($min_id) {
  382. $res = DirectMessage::select('*')
  383. ->where('id', '>', $min_id)
  384. ->where(function($q) use($pid,$uid) {
  385. return $q->where([['from_id',$pid],['to_id',$uid]
  386. ])->orWhere([['from_id',$uid],['to_id',$pid]]);
  387. })
  388. ->latest()
  389. ->take(8)
  390. ->get();
  391. } else if ($max_id) {
  392. $res = DirectMessage::select('*')
  393. ->where('id', '<', $max_id)
  394. ->where(function($q) use($pid,$uid) {
  395. return $q->where([['from_id',$pid],['to_id',$uid]
  396. ])->orWhere([['from_id',$uid],['to_id',$pid]]);
  397. })
  398. ->latest()
  399. ->take(8)
  400. ->get();
  401. } else {
  402. $res = DirectMessage::where(function($q) use($pid,$uid) {
  403. return $q->where([['from_id',$pid],['to_id',$uid]
  404. ])->orWhere([['from_id',$uid],['to_id',$pid]]);
  405. })
  406. ->latest()
  407. ->take(8)
  408. ->get();
  409. }
  410. $res = $res->map(function($s) use ($uid){
  411. return [
  412. 'id' => (string) $s->id,
  413. 'hidden' => (bool) $s->is_hidden,
  414. 'isAuthor' => $uid == $s->from_id,
  415. 'type' => $s->type,
  416. 'text' => $s->status->caption,
  417. 'media' => $s->status->firstMedia() ? $s->status->firstMedia()->url() : null,
  418. 'timeAgo' => $s->created_at->diffForHumans(null,null,true),
  419. 'seen' => $s->read_at != null,
  420. 'reportId' => (string) $s->status_id,
  421. 'meta' => json_decode($s->meta,true)
  422. ];
  423. });
  424. $w = [
  425. 'id' => (string) $r->id,
  426. 'name' => $r->name,
  427. 'username' => $r->username,
  428. 'avatar' => $r->avatarUrl(),
  429. 'url' => $r->url(),
  430. 'muted' => UserFilter::whereUserId($uid)
  431. ->whereFilterableId($r->id)
  432. ->whereFilterableType('App\Profile')
  433. ->whereFilterType('dm.mute')
  434. ->first() ? true : false,
  435. 'isLocal' => (bool) !$r->domain,
  436. 'domain' => $r->domain,
  437. 'timeAgo' => $r->created_at->diffForHumans(null, true, true),
  438. 'lastMessage' => '',
  439. 'messages' => $res
  440. ];
  441. return response()->json($w, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
  442. }
  443. public function delete(Request $request)
  444. {
  445. $this->validate($request, [
  446. 'id' => 'required'
  447. ]);
  448. $sid = $request->input('id');
  449. $pid = $request->user()->profile_id;
  450. $dm = DirectMessage::whereFromId($pid)
  451. ->whereStatusId($sid)
  452. ->firstOrFail();
  453. $status = Status::whereProfileId($pid)
  454. ->findOrFail($dm->status_id);
  455. if($dm->recipient->domain) {
  456. $dmc = $dm;
  457. $this->remoteDelete($dmc);
  458. }
  459. $status->delete();
  460. $dm->delete();
  461. return [200];
  462. }
  463. public function get(Request $request, $id)
  464. {
  465. $pid = $request->user()->profile_id;
  466. $dm = DirectMessage::whereStatusId($id)->firstOrFail();
  467. abort_if($pid !== $dm->to_id && $pid !== $dm->from_id, 404);
  468. return response()->json($dm, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
  469. }
  470. public function mediaUpload(Request $request)
  471. {
  472. $this->validate($request, [
  473. 'file' => function() {
  474. return [
  475. 'required',
  476. 'mimetypes:' . config_cache('pixelfed.media_types'),
  477. 'max:' . config_cache('pixelfed.max_photo_size'),
  478. ];
  479. },
  480. 'to_id' => 'required'
  481. ]);
  482. $user = $request->user();
  483. $profile = $user->profile;
  484. $recipient = Profile::where('id', '!=', $profile->id)->findOrFail($request->input('to_id'));
  485. abort_if(in_array($profile->id, $recipient->blockedIds()->toArray()), 403);
  486. if((!$recipient->domain && $recipient->user->settings->public_dm == false) || $recipient->is_private) {
  487. if($recipient->follows($profile) == true) {
  488. $hidden = false;
  489. } else {
  490. $hidden = true;
  491. }
  492. } else {
  493. $hidden = false;
  494. }
  495. if(config_cache('pixelfed.enforce_account_limit') == true) {
  496. $size = Cache::remember($user->storageUsedKey(), now()->addDays(3), function() use($user) {
  497. return Media::whereUserId($user->id)->sum('size') / 1000;
  498. });
  499. $limit = (int) config_cache('pixelfed.max_account_size');
  500. if ($size >= $limit) {
  501. abort(403, 'Account size limit reached.');
  502. }
  503. }
  504. $photo = $request->file('file');
  505. $mimes = explode(',', config_cache('pixelfed.media_types'));
  506. if(in_array($photo->getMimeType(), $mimes) == false) {
  507. abort(403, 'Invalid or unsupported mime type.');
  508. }
  509. $storagePath = MediaPathService::get($user, 2) . Str::random(8);
  510. $path = $photo->store($storagePath);
  511. $hash = \hash_file('sha256', $photo);
  512. abort_if(MediaBlocklistService::exists($hash) == true, 451);
  513. $status = new Status;
  514. $status->profile_id = $profile->id;
  515. $status->caption = null;
  516. $status->rendered = null;
  517. $status->visibility = 'direct';
  518. $status->scope = 'direct';
  519. $status->in_reply_to_profile_id = $recipient->id;
  520. $status->save();
  521. $media = new Media();
  522. $media->status_id = $status->id;
  523. $media->profile_id = $profile->id;
  524. $media->user_id = $user->id;
  525. $media->media_path = $path;
  526. $media->original_sha256 = $hash;
  527. $media->size = $photo->getSize();
  528. $media->mime = $photo->getMimeType();
  529. $media->caption = null;
  530. $media->filter_class = null;
  531. $media->filter_name = null;
  532. $media->save();
  533. $dm = new DirectMessage;
  534. $dm->to_id = $recipient->id;
  535. $dm->from_id = $profile->id;
  536. $dm->status_id = $status->id;
  537. $dm->type = array_first(explode('/', $media->mime)) == 'video' ? 'video' : 'photo';
  538. $dm->is_hidden = $hidden;
  539. $dm->save();
  540. Conversation::updateOrInsert(
  541. [
  542. 'to_id' => $recipient->id,
  543. 'from_id' => $profile->id
  544. ],
  545. [
  546. 'type' => $dm->type,
  547. 'status_id' => $status->id,
  548. 'dm_id' => $dm->id,
  549. 'is_hidden' => $hidden
  550. ]
  551. );
  552. if($recipient->domain) {
  553. $this->remoteDeliver($dm);
  554. }
  555. return [
  556. 'id' => $dm->id,
  557. 'reportId' => (string) $dm->status_id,
  558. 'type' => $dm->type,
  559. 'url' => $media->url()
  560. ];
  561. }
  562. public function composeLookup(Request $request)
  563. {
  564. $this->validate($request, [
  565. 'q' => 'required|string|min:2|max:50',
  566. 'remote' => 'nullable',
  567. ]);
  568. $q = $request->input('q');
  569. $r = $request->input('remote', false);
  570. if($r && !Str::of($q)->contains('.')) {
  571. return [];
  572. }
  573. if($r && Helpers::validateUrl($q)) {
  574. Helpers::profileFetch($q);
  575. }
  576. if(Str::of($q)->startsWith('@')) {
  577. if(strlen($q) < 3) {
  578. return [];
  579. }
  580. if(substr_count($q, '@') == 2) {
  581. WebfingerService::lookup($q);
  582. }
  583. $q = mb_substr($q, 1);
  584. }
  585. $blocked = UserFilter::whereFilterableType('App\Profile')
  586. ->whereFilterType('block')
  587. ->whereFilterableId($request->user()->profile_id)
  588. ->pluck('user_id');
  589. $blocked->push($request->user()->profile_id);
  590. $results = Profile::select('id','domain','username')
  591. ->whereNotIn('id', $blocked)
  592. ->where('username','like','%'.$q.'%')
  593. ->orderBy('domain')
  594. ->limit(8)
  595. ->get()
  596. ->map(function($r) {
  597. return [
  598. 'local' => (bool) !$r->domain,
  599. 'id' => (string) $r->id,
  600. 'name' => $r->username,
  601. 'privacy' => true,
  602. 'avatar' => $r->avatarUrl()
  603. ];
  604. });
  605. return $results;
  606. }
  607. public function read(Request $request)
  608. {
  609. $this->validate($request, [
  610. 'pid' => 'required',
  611. 'sid' => 'required'
  612. ]);
  613. $pid = $request->input('pid');
  614. $sid = $request->input('sid');
  615. $dms = DirectMessage::whereToId($request->user()->profile_id)
  616. ->whereFromId($pid)
  617. ->where('status_id', '>=', $sid)
  618. ->get();
  619. $now = now();
  620. foreach($dms as $dm) {
  621. $dm->read_at = $now;
  622. $dm->save();
  623. }
  624. return response()->json($dms->pluck('id'));
  625. }
  626. public function mute(Request $request)
  627. {
  628. $this->validate($request, [
  629. 'id' => 'required'
  630. ]);
  631. $fid = $request->input('id');
  632. $pid = $request->user()->profile_id;
  633. UserFilter::firstOrCreate(
  634. [
  635. 'user_id' => $pid,
  636. 'filterable_id' => $fid,
  637. 'filterable_type' => 'App\Profile',
  638. 'filter_type' => 'dm.mute'
  639. ]
  640. );
  641. return [200];
  642. }
  643. public function unmute(Request $request)
  644. {
  645. $this->validate($request, [
  646. 'id' => 'required'
  647. ]);
  648. $fid = $request->input('id');
  649. $pid = $request->user()->profile_id;
  650. $f = UserFilter::whereUserId($pid)
  651. ->whereFilterableId($fid)
  652. ->whereFilterableType('App\Profile')
  653. ->whereFilterType('dm.mute')
  654. ->firstOrFail();
  655. $f->delete();
  656. return [200];
  657. }
  658. public function remoteDeliver($dm)
  659. {
  660. $profile = $dm->author;
  661. $url = $dm->recipient->sharedInbox ?? $dm->recipient->inbox_url;
  662. $tags = [
  663. [
  664. 'type' => 'Mention',
  665. 'href' => $dm->recipient->permalink(),
  666. 'name' => $dm->recipient->emailUrl(),
  667. ]
  668. ];
  669. $body = [
  670. '@context' => [
  671. 'https://www.w3.org/ns/activitystreams',
  672. 'https://w3id.org/security/v1',
  673. [
  674. 'sc' => 'http://schema.org#',
  675. 'Hashtag' => 'as:Hashtag',
  676. 'sensitive' => 'as:sensitive',
  677. ]
  678. ],
  679. 'id' => $dm->status->permalink(),
  680. 'type' => 'Create',
  681. 'actor' => $dm->status->profile->permalink(),
  682. 'published' => $dm->status->created_at->toAtomString(),
  683. 'to' => [$dm->recipient->permalink()],
  684. 'cc' => [],
  685. 'object' => [
  686. 'id' => $dm->status->url(),
  687. 'type' => 'Note',
  688. 'summary' => null,
  689. 'content' => $dm->status->rendered ?? $dm->status->caption,
  690. 'inReplyTo' => null,
  691. 'published' => $dm->status->created_at->toAtomString(),
  692. 'url' => $dm->status->url(),
  693. 'attributedTo' => $dm->status->profile->permalink(),
  694. 'to' => [$dm->recipient->permalink()],
  695. 'cc' => [],
  696. 'sensitive' => (bool) $dm->status->is_nsfw,
  697. 'attachment' => $dm->status->media()->orderBy('order')->get()->map(function ($media) {
  698. return [
  699. 'type' => $media->activityVerb(),
  700. 'mediaType' => $media->mime,
  701. 'url' => $media->url(),
  702. 'name' => $media->caption,
  703. ];
  704. })->toArray(),
  705. 'tag' => $tags,
  706. ]
  707. ];
  708. Helpers::sendSignedObject($profile, $url, $body);
  709. }
  710. public function remoteDelete($dm)
  711. {
  712. $profile = $dm->author;
  713. $url = $dm->recipient->sharedInbox ?? $dm->recipient->inbox_url;
  714. $body = [
  715. '@context' => [
  716. 'https://www.w3.org/ns/activitystreams',
  717. 'https://w3id.org/security/v1',
  718. [
  719. 'sc' => 'http://schema.org#',
  720. 'Hashtag' => 'as:Hashtag',
  721. 'sensitive' => 'as:sensitive',
  722. ]
  723. ],
  724. 'id' => $dm->status->permalink('#delete'),
  725. 'to' => [
  726. 'https://www.w3.org/ns/activitystreams#Public'
  727. ],
  728. 'type' => 'Delete',
  729. 'actor' => $dm->status->profile->permalink(),
  730. 'object' => [
  731. 'id' => $dm->status->url(),
  732. 'type' => 'Tombstone'
  733. ]
  734. ];
  735. Helpers::sendSignedObject($profile, $url, $body);
  736. }
  737. }