Merge branch 'feature/add-roles-to-users-admin-api' into 'develop'
[akkoma] / lib / pleroma / web / mastodon_api / mastodon_api_controller.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
6 use Pleroma.Web, :controller
7 alias Pleroma.Activity
8 alias Pleroma.Config
9 alias Pleroma.Filter
10 alias Pleroma.Notification
11 alias Pleroma.Object
12 alias Pleroma.Repo
13 alias Pleroma.Stats
14 alias Pleroma.User
15 alias Pleroma.Web
16 alias Pleroma.Web.CommonAPI
17 alias Pleroma.Web.MediaProxy
18
19 alias Pleroma.Web.MastodonAPI.AccountView
20 alias Pleroma.Web.MastodonAPI.FilterView
21 alias Pleroma.Web.MastodonAPI.ListView
22 alias Pleroma.Web.MastodonAPI.MastodonView
23 alias Pleroma.Web.MastodonAPI.StatusView
24 alias Pleroma.Web.MastodonAPI.ReportView
25 alias Pleroma.Web.MastodonAPI.MastodonAPI
26 alias Pleroma.Web.ActivityPub.ActivityPub
27 alias Pleroma.Web.ActivityPub.Utils
28 alias Pleroma.Web.ActivityPub.Visibility
29 alias Pleroma.Web.OAuth.App
30 alias Pleroma.Web.OAuth.Authorization
31 alias Pleroma.Web.OAuth.Token
32
33 import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2]
34 import Ecto.Query
35
36 require Logger
37
38 @httpoison Application.get_env(:pleroma, :httpoison)
39 @local_mastodon_name "Mastodon-Local"
40
41 action_fallback(:errors)
42
43 def create_app(conn, params) do
44 scopes = oauth_scopes(params, ["read"])
45
46 app_attrs =
47 params
48 |> Map.drop(["scope", "scopes"])
49 |> Map.put("scopes", scopes)
50
51 with cs <- App.register_changeset(%App{}, app_attrs),
52 false <- cs.changes[:client_name] == @local_mastodon_name,
53 {:ok, app} <- Repo.insert(cs) do
54 res = %{
55 id: app.id |> to_string,
56 name: app.client_name,
57 client_id: app.client_id,
58 client_secret: app.client_secret,
59 redirect_uri: app.redirect_uris,
60 website: app.website
61 }
62
63 json(conn, res)
64 end
65 end
66
67 defp add_if_present(
68 map,
69 params,
70 params_field,
71 map_field,
72 value_function \\ fn x -> {:ok, x} end
73 ) do
74 if Map.has_key?(params, params_field) do
75 case value_function.(params[params_field]) do
76 {:ok, new_value} -> Map.put(map, map_field, new_value)
77 :error -> map
78 end
79 else
80 map
81 end
82 end
83
84 def update_credentials(%{assigns: %{user: user}} = conn, params) do
85 original_user = user
86
87 user_params =
88 %{}
89 |> add_if_present(params, "display_name", :name)
90 |> add_if_present(params, "note", :bio, fn value -> {:ok, User.parse_bio(value)} end)
91 |> add_if_present(params, "avatar", :avatar, fn value ->
92 with %Plug.Upload{} <- value,
93 {:ok, object} <- ActivityPub.upload(value, type: :avatar) do
94 {:ok, object.data}
95 else
96 _ -> :error
97 end
98 end)
99
100 info_params =
101 %{}
102 |> add_if_present(params, "locked", :locked, fn value -> {:ok, value == "true"} end)
103 |> add_if_present(params, "header", :banner, fn value ->
104 with %Plug.Upload{} <- value,
105 {:ok, object} <- ActivityPub.upload(value, type: :banner) do
106 {:ok, object.data}
107 else
108 _ -> :error
109 end
110 end)
111
112 info_cng = User.Info.mastodon_profile_update(user.info, info_params)
113
114 with changeset <- User.update_changeset(user, user_params),
115 changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng),
116 {:ok, user} <- User.update_and_set_cache(changeset) do
117 if original_user != user do
118 CommonAPI.update(user)
119 end
120
121 json(conn, AccountView.render("account.json", %{user: user, for: user}))
122 else
123 _e ->
124 conn
125 |> put_status(403)
126 |> json(%{error: "Invalid request"})
127 end
128 end
129
130 def verify_credentials(%{assigns: %{user: user}} = conn, _) do
131 account = AccountView.render("account.json", %{user: user, for: user})
132 json(conn, account)
133 end
134
135 def user(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do
136 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id),
137 true <- User.auth_active?(user) || user.id == for_user.id || User.superuser?(for_user) do
138 account = AccountView.render("account.json", %{user: user, for: for_user})
139 json(conn, account)
140 else
141 _e ->
142 conn
143 |> put_status(404)
144 |> json(%{error: "Can't find user"})
145 end
146 end
147
148 @mastodon_api_level "2.5.0"
149
150 def masto_instance(conn, _params) do
151 instance = Config.get(:instance)
152
153 response = %{
154 uri: Web.base_url(),
155 title: Keyword.get(instance, :name),
156 description: Keyword.get(instance, :description),
157 version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})",
158 email: Keyword.get(instance, :email),
159 urls: %{
160 streaming_api: Pleroma.Web.Endpoint.websocket_url()
161 },
162 stats: Stats.get_stats(),
163 thumbnail: Web.base_url() <> "/instance/thumbnail.jpeg",
164 max_toot_chars: Keyword.get(instance, :limit)
165 }
166
167 json(conn, response)
168 end
169
170 def peers(conn, _params) do
171 json(conn, Stats.get_peers())
172 end
173
174 defp mastodonized_emoji do
175 Pleroma.Emoji.get_all()
176 |> Enum.map(fn {shortcode, relative_url} ->
177 url = to_string(URI.merge(Web.base_url(), relative_url))
178
179 %{
180 "shortcode" => shortcode,
181 "static_url" => url,
182 "visible_in_picker" => true,
183 "url" => url
184 }
185 end)
186 end
187
188 def custom_emojis(conn, _params) do
189 mastodon_emoji = mastodonized_emoji()
190 json(conn, mastodon_emoji)
191 end
192
193 defp add_link_headers(conn, method, activities, param \\ nil, params \\ %{}) do
194 params =
195 conn.params
196 |> Map.drop(["since_id", "max_id"])
197 |> Map.merge(params)
198
199 last = List.last(activities)
200 first = List.first(activities)
201
202 if last do
203 min = last.id
204 max = first.id
205
206 {next_url, prev_url} =
207 if param do
208 {
209 mastodon_api_url(
210 Pleroma.Web.Endpoint,
211 method,
212 param,
213 Map.merge(params, %{max_id: min})
214 ),
215 mastodon_api_url(
216 Pleroma.Web.Endpoint,
217 method,
218 param,
219 Map.merge(params, %{since_id: max})
220 )
221 }
222 else
223 {
224 mastodon_api_url(
225 Pleroma.Web.Endpoint,
226 method,
227 Map.merge(params, %{max_id: min})
228 ),
229 mastodon_api_url(
230 Pleroma.Web.Endpoint,
231 method,
232 Map.merge(params, %{since_id: max})
233 )
234 }
235 end
236
237 conn
238 |> put_resp_header("link", "<#{next_url}>; rel=\"next\", <#{prev_url}>; rel=\"prev\"")
239 else
240 conn
241 end
242 end
243
244 def home_timeline(%{assigns: %{user: user}} = conn, params) do
245 params =
246 params
247 |> Map.put("type", ["Create", "Announce"])
248 |> Map.put("blocking_user", user)
249 |> Map.put("muting_user", user)
250 |> Map.put("user", user)
251
252 activities =
253 [user.ap_id | user.following]
254 |> ActivityPub.fetch_activities(params)
255 |> ActivityPub.contain_timeline(user)
256 |> Enum.reverse()
257
258 conn
259 |> add_link_headers(:home_timeline, activities)
260 |> put_view(StatusView)
261 |> render("index.json", %{activities: activities, for: user, as: :activity})
262 end
263
264 def public_timeline(%{assigns: %{user: user}} = conn, params) do
265 local_only = params["local"] in [true, "True", "true", "1"]
266
267 activities =
268 params
269 |> Map.put("type", ["Create", "Announce"])
270 |> Map.put("local_only", local_only)
271 |> Map.put("blocking_user", user)
272 |> Map.put("muting_user", user)
273 |> ActivityPub.fetch_public_activities()
274 |> Enum.reverse()
275
276 conn
277 |> add_link_headers(:public_timeline, activities, false, %{"local" => local_only})
278 |> put_view(StatusView)
279 |> render("index.json", %{activities: activities, for: user, as: :activity})
280 end
281
282 def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do
283 with %User{} = user <- Repo.get(User, params["id"]) do
284 activities = ActivityPub.fetch_user_activities(user, reading_user, params)
285
286 conn
287 |> add_link_headers(:user_statuses, activities, params["id"])
288 |> put_view(StatusView)
289 |> render("index.json", %{
290 activities: activities,
291 for: reading_user,
292 as: :activity
293 })
294 end
295 end
296
297 def dm_timeline(%{assigns: %{user: user}} = conn, params) do
298 params =
299 params
300 |> Map.put("type", "Create")
301 |> Map.put("blocking_user", user)
302 |> Map.put("user", user)
303 |> Map.put(:visibility, "direct")
304
305 activities =
306 [user.ap_id]
307 |> ActivityPub.fetch_activities_query(params)
308 |> Repo.all()
309
310 conn
311 |> add_link_headers(:dm_timeline, activities)
312 |> put_view(StatusView)
313 |> render("index.json", %{activities: activities, for: user, as: :activity})
314 end
315
316 def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
317 with %Activity{} = activity <- Repo.get(Activity, id),
318 true <- Visibility.visible_for_user?(activity, user) do
319 conn
320 |> put_view(StatusView)
321 |> try_render("status.json", %{activity: activity, for: user})
322 end
323 end
324
325 def get_context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
326 with %Activity{} = activity <- Repo.get(Activity, id),
327 activities <-
328 ActivityPub.fetch_activities_for_context(activity.data["context"], %{
329 "blocking_user" => user,
330 "user" => user
331 }),
332 activities <-
333 activities |> Enum.filter(fn %{id: aid} -> to_string(aid) != to_string(id) end),
334 activities <-
335 activities |> Enum.filter(fn %{data: %{"type" => type}} -> type == "Create" end),
336 grouped_activities <- Enum.group_by(activities, fn %{id: id} -> id < activity.id end) do
337 result = %{
338 ancestors:
339 StatusView.render(
340 "index.json",
341 for: user,
342 activities: grouped_activities[true] || [],
343 as: :activity
344 )
345 |> Enum.reverse(),
346 # credo:disable-for-previous-line Credo.Check.Refactor.PipeChainStart
347 descendants:
348 StatusView.render(
349 "index.json",
350 for: user,
351 activities: grouped_activities[false] || [],
352 as: :activity
353 )
354 |> Enum.reverse()
355 # credo:disable-for-previous-line Credo.Check.Refactor.PipeChainStart
356 }
357
358 json(conn, result)
359 end
360 end
361
362 def post_status(conn, %{"status" => "", "media_ids" => media_ids} = params)
363 when length(media_ids) > 0 do
364 params =
365 params
366 |> Map.put("status", ".")
367
368 post_status(conn, params)
369 end
370
371 def post_status(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do
372 params =
373 params
374 |> Map.put("in_reply_to_status_id", params["in_reply_to_id"])
375
376 idempotency_key =
377 case get_req_header(conn, "idempotency-key") do
378 [key] -> key
379 _ -> Ecto.UUID.generate()
380 end
381
382 {:ok, activity} =
383 Cachex.fetch!(:idempotency_cache, idempotency_key, fn _ -> CommonAPI.post(user, params) end)
384
385 conn
386 |> put_view(StatusView)
387 |> try_render("status.json", %{activity: activity, for: user, as: :activity})
388 end
389
390 def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
391 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
392 json(conn, %{})
393 else
394 _e ->
395 conn
396 |> put_status(403)
397 |> json(%{error: "Can't delete this post"})
398 end
399 end
400
401 def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
402 with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user) do
403 conn
404 |> put_view(StatusView)
405 |> try_render("status.json", %{activity: announce, for: user, as: :activity})
406 end
407 end
408
409 def unreblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
410 with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user),
411 %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
412 conn
413 |> put_view(StatusView)
414 |> try_render("status.json", %{activity: activity, for: user, as: :activity})
415 end
416 end
417
418 def fav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
419 with {:ok, _fav, %{data: %{"id" => id}}} <- CommonAPI.favorite(ap_id_or_id, user),
420 %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
421 conn
422 |> put_view(StatusView)
423 |> try_render("status.json", %{activity: activity, for: user, as: :activity})
424 end
425 end
426
427 def unfav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
428 with {:ok, _, _, %{data: %{"id" => id}}} <- CommonAPI.unfavorite(ap_id_or_id, user),
429 %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
430 conn
431 |> put_view(StatusView)
432 |> try_render("status.json", %{activity: activity, for: user, as: :activity})
433 end
434 end
435
436 def pin_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
437 with {:ok, activity} <- CommonAPI.pin(ap_id_or_id, user) do
438 conn
439 |> put_view(StatusView)
440 |> try_render("status.json", %{activity: activity, for: user, as: :activity})
441 else
442 {:error, reason} ->
443 conn
444 |> put_resp_content_type("application/json")
445 |> send_resp(:bad_request, Jason.encode!(%{"error" => reason}))
446 end
447 end
448
449 def unpin_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
450 with {:ok, activity} <- CommonAPI.unpin(ap_id_or_id, user) do
451 conn
452 |> put_view(StatusView)
453 |> try_render("status.json", %{activity: activity, for: user, as: :activity})
454 end
455 end
456
457 def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
458 with %Activity{} = activity <- Repo.get(Activity, id),
459 %User{} = user <- User.get_by_nickname(user.nickname),
460 true <- Visibility.visible_for_user?(activity, user),
461 {:ok, user} <- User.bookmark(user, activity.data["object"]["id"]) do
462 conn
463 |> put_view(StatusView)
464 |> try_render("status.json", %{activity: activity, for: user, as: :activity})
465 end
466 end
467
468 def unbookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
469 with %Activity{} = activity <- Repo.get(Activity, id),
470 %User{} = user <- User.get_by_nickname(user.nickname),
471 true <- Visibility.visible_for_user?(activity, user),
472 {:ok, user} <- User.unbookmark(user, activity.data["object"]["id"]) do
473 conn
474 |> put_view(StatusView)
475 |> try_render("status.json", %{activity: activity, for: user, as: :activity})
476 end
477 end
478
479 def mute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
480 activity = Activity.get_by_id(id)
481
482 with {:ok, activity} <- CommonAPI.add_mute(user, activity) do
483 conn
484 |> put_view(StatusView)
485 |> try_render("status.json", %{activity: activity, for: user, as: :activity})
486 else
487 {:error, reason} ->
488 conn
489 |> put_resp_content_type("application/json")
490 |> send_resp(:bad_request, Jason.encode!(%{"error" => reason}))
491 end
492 end
493
494 def unmute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
495 activity = Activity.get_by_id(id)
496
497 with {:ok, activity} <- CommonAPI.remove_mute(user, activity) do
498 conn
499 |> put_view(StatusView)
500 |> try_render("status.json", %{activity: activity, for: user, as: :activity})
501 end
502 end
503
504 def notifications(%{assigns: %{user: user}} = conn, params) do
505 notifications = Notification.for_user(user, params)
506
507 result =
508 notifications
509 |> Enum.map(fn x -> render_notification(user, x) end)
510 |> Enum.filter(& &1)
511
512 conn
513 |> add_link_headers(:notifications, notifications)
514 |> json(result)
515 end
516
517 def get_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
518 with {:ok, notification} <- Notification.get(user, id) do
519 json(conn, render_notification(user, notification))
520 else
521 {:error, reason} ->
522 conn
523 |> put_resp_content_type("application/json")
524 |> send_resp(403, Jason.encode!(%{"error" => reason}))
525 end
526 end
527
528 def clear_notifications(%{assigns: %{user: user}} = conn, _params) do
529 Notification.clear(user)
530 json(conn, %{})
531 end
532
533 def dismiss_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
534 with {:ok, _notif} <- Notification.dismiss(user, id) do
535 json(conn, %{})
536 else
537 {:error, reason} ->
538 conn
539 |> put_resp_content_type("application/json")
540 |> send_resp(403, Jason.encode!(%{"error" => reason}))
541 end
542 end
543
544 def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do
545 id = List.wrap(id)
546 q = from(u in User, where: u.id in ^id)
547 targets = Repo.all(q)
548
549 conn
550 |> put_view(AccountView)
551 |> render("relationships.json", %{user: user, targets: targets})
552 end
553
554 # Instead of returning a 400 when no "id" params is present, Mastodon returns an empty array.
555 def relationships(%{assigns: %{user: _user}} = conn, _), do: json(conn, [])
556
557 def update_media(%{assigns: %{user: user}} = conn, data) do
558 with %Object{} = object <- Repo.get(Object, data["id"]),
559 true <- Object.authorize_mutation(object, user),
560 true <- is_binary(data["description"]),
561 description <- data["description"] do
562 new_data = %{object.data | "name" => description}
563
564 {:ok, _} =
565 object
566 |> Object.change(%{data: new_data})
567 |> Repo.update()
568
569 attachment_data = Map.put(new_data, "id", object.id)
570
571 conn
572 |> put_view(StatusView)
573 |> render("attachment.json", %{attachment: attachment_data})
574 end
575 end
576
577 def upload(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do
578 with {:ok, object} <-
579 ActivityPub.upload(
580 file,
581 actor: User.ap_id(user),
582 description: Map.get(data, "description")
583 ) do
584 attachment_data = Map.put(object.data, "id", object.id)
585
586 conn
587 |> put_view(StatusView)
588 |> render("attachment.json", %{attachment: attachment_data})
589 end
590 end
591
592 def favourited_by(conn, %{"id" => id}) do
593 with %Activity{data: %{"object" => %{"likes" => likes}}} <- Repo.get(Activity, id) do
594 q = from(u in User, where: u.ap_id in ^likes)
595 users = Repo.all(q)
596
597 conn
598 |> put_view(AccountView)
599 |> render(AccountView, "accounts.json", %{users: users, as: :user})
600 else
601 _ -> json(conn, [])
602 end
603 end
604
605 def reblogged_by(conn, %{"id" => id}) do
606 with %Activity{data: %{"object" => %{"announcements" => announces}}} <- Repo.get(Activity, id) do
607 q = from(u in User, where: u.ap_id in ^announces)
608 users = Repo.all(q)
609
610 conn
611 |> put_view(AccountView)
612 |> render("accounts.json", %{users: users, as: :user})
613 else
614 _ -> json(conn, [])
615 end
616 end
617
618 def hashtag_timeline(%{assigns: %{user: user}} = conn, params) do
619 local_only = params["local"] in [true, "True", "true", "1"]
620
621 tags =
622 [params["tag"], params["any"]]
623 |> List.flatten()
624 |> Enum.uniq()
625 |> Enum.filter(& &1)
626 |> Enum.map(&String.downcase(&1))
627
628 tag_all =
629 params["all"] ||
630 []
631 |> Enum.map(&String.downcase(&1))
632
633 tag_reject =
634 params["none"] ||
635 []
636 |> Enum.map(&String.downcase(&1))
637
638 activities =
639 params
640 |> Map.put("type", "Create")
641 |> Map.put("local_only", local_only)
642 |> Map.put("blocking_user", user)
643 |> Map.put("muting_user", user)
644 |> Map.put("tag", tags)
645 |> Map.put("tag_all", tag_all)
646 |> Map.put("tag_reject", tag_reject)
647 |> ActivityPub.fetch_public_activities()
648 |> Enum.reverse()
649
650 conn
651 |> add_link_headers(:hashtag_timeline, activities, params["tag"], %{"local" => local_only})
652 |> put_view(StatusView)
653 |> render("index.json", %{activities: activities, for: user, as: :activity})
654 end
655
656 def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
657 with %User{} = user <- Repo.get(User, id),
658 followers <- MastodonAPI.get_followers(user, params) do
659 followers =
660 cond do
661 for_user && user.id == for_user.id -> followers
662 user.info.hide_followers -> []
663 true -> followers
664 end
665
666 conn
667 |> add_link_headers(:followers, followers, user)
668 |> put_view(AccountView)
669 |> render("accounts.json", %{users: followers, as: :user})
670 end
671 end
672
673 def following(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
674 with %User{} = user <- Repo.get(User, id),
675 followers <- MastodonAPI.get_friends(user, params) do
676 followers =
677 cond do
678 for_user && user.id == for_user.id -> followers
679 user.info.hide_follows -> []
680 true -> followers
681 end
682
683 conn
684 |> add_link_headers(:following, followers, user)
685 |> put_view(AccountView)
686 |> render("accounts.json", %{users: followers, as: :user})
687 end
688 end
689
690 def follow_requests(%{assigns: %{user: followed}} = conn, _params) do
691 with {:ok, follow_requests} <- User.get_follow_requests(followed) do
692 conn
693 |> put_view(AccountView)
694 |> render("accounts.json", %{users: follow_requests, as: :user})
695 end
696 end
697
698 def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
699 with %User{} = follower <- Repo.get(User, id),
700 {:ok, follower} <- User.maybe_follow(follower, followed),
701 %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
702 {:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "accept"),
703 {:ok, _activity} <-
704 ActivityPub.accept(%{
705 to: [follower.ap_id],
706 actor: followed,
707 object: follow_activity.data["id"],
708 type: "Accept"
709 }) do
710 conn
711 |> put_view(AccountView)
712 |> render("relationship.json", %{user: followed, target: follower})
713 else
714 {:error, message} ->
715 conn
716 |> put_resp_content_type("application/json")
717 |> send_resp(403, Jason.encode!(%{"error" => message}))
718 end
719 end
720
721 def reject_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
722 with %User{} = follower <- Repo.get(User, id),
723 %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
724 {:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "reject"),
725 {:ok, _activity} <-
726 ActivityPub.reject(%{
727 to: [follower.ap_id],
728 actor: followed,
729 object: follow_activity.data["id"],
730 type: "Reject"
731 }) do
732 conn
733 |> put_view(AccountView)
734 |> render("relationship.json", %{user: followed, target: follower})
735 else
736 {:error, message} ->
737 conn
738 |> put_resp_content_type("application/json")
739 |> send_resp(403, Jason.encode!(%{"error" => message}))
740 end
741 end
742
743 def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
744 with %User{} = followed <- Repo.get(User, id),
745 {:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do
746 conn
747 |> put_view(AccountView)
748 |> render("relationship.json", %{user: follower, target: followed})
749 else
750 {:error, message} ->
751 conn
752 |> put_resp_content_type("application/json")
753 |> send_resp(403, Jason.encode!(%{"error" => message}))
754 end
755 end
756
757 def follow(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
758 with %User{} = followed <- Repo.get_by(User, nickname: uri),
759 {:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do
760 conn
761 |> put_view(AccountView)
762 |> render("account.json", %{user: followed, for: follower})
763 else
764 {:error, message} ->
765 conn
766 |> put_resp_content_type("application/json")
767 |> send_resp(403, Jason.encode!(%{"error" => message}))
768 end
769 end
770
771 def unfollow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
772 with %User{} = followed <- Repo.get(User, id),
773 {:ok, _activity} <- ActivityPub.unfollow(follower, followed),
774 {:ok, follower, _} <- User.unfollow(follower, followed) do
775 conn
776 |> put_view(AccountView)
777 |> render("relationship.json", %{user: follower, target: followed})
778 end
779 end
780
781 def mute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
782 with %User{} = muted <- Repo.get(User, id),
783 {:ok, muter} <- User.mute(muter, muted) do
784 conn
785 |> put_view(AccountView)
786 |> render("relationship.json", %{user: muter, target: muted})
787 else
788 {:error, message} ->
789 conn
790 |> put_resp_content_type("application/json")
791 |> send_resp(403, Jason.encode!(%{"error" => message}))
792 end
793 end
794
795 def unmute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
796 with %User{} = muted <- Repo.get(User, id),
797 {:ok, muter} <- User.unmute(muter, muted) do
798 conn
799 |> put_view(AccountView)
800 |> render("relationship.json", %{user: muter, target: muted})
801 else
802 {:error, message} ->
803 conn
804 |> put_resp_content_type("application/json")
805 |> send_resp(403, Jason.encode!(%{"error" => message}))
806 end
807 end
808
809 def mutes(%{assigns: %{user: user}} = conn, _) do
810 with muted_accounts <- User.muted_users(user) do
811 res = AccountView.render("accounts.json", users: muted_accounts, for: user, as: :user)
812 json(conn, res)
813 end
814 end
815
816 def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
817 with %User{} = blocked <- Repo.get(User, id),
818 {:ok, blocker} <- User.block(blocker, blocked),
819 {:ok, _activity} <- ActivityPub.block(blocker, blocked) do
820 conn
821 |> put_view(AccountView)
822 |> render("relationship.json", %{user: blocker, target: blocked})
823 else
824 {:error, message} ->
825 conn
826 |> put_resp_content_type("application/json")
827 |> send_resp(403, Jason.encode!(%{"error" => message}))
828 end
829 end
830
831 def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
832 with %User{} = blocked <- Repo.get(User, id),
833 {:ok, blocker} <- User.unblock(blocker, blocked),
834 {:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do
835 conn
836 |> put_view(AccountView)
837 |> render("relationship.json", %{user: blocker, target: blocked})
838 else
839 {:error, message} ->
840 conn
841 |> put_resp_content_type("application/json")
842 |> send_resp(403, Jason.encode!(%{"error" => message}))
843 end
844 end
845
846 def blocks(%{assigns: %{user: user}} = conn, _) do
847 with blocked_accounts <- User.blocked_users(user) do
848 res = AccountView.render("accounts.json", users: blocked_accounts, for: user, as: :user)
849 json(conn, res)
850 end
851 end
852
853 def domain_blocks(%{assigns: %{user: %{info: info}}} = conn, _) do
854 json(conn, info.domain_blocks || [])
855 end
856
857 def block_domain(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do
858 User.block_domain(blocker, domain)
859 json(conn, %{})
860 end
861
862 def unblock_domain(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do
863 User.unblock_domain(blocker, domain)
864 json(conn, %{})
865 end
866
867 def status_search(user, query) do
868 fetched =
869 if Regex.match?(~r/https?:/, query) do
870 with {:ok, object} <- ActivityPub.fetch_object_from_id(query),
871 %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
872 true <- Visibility.visible_for_user?(activity, user) do
873 [activity]
874 else
875 _e -> []
876 end
877 end || []
878
879 q =
880 from(
881 a in Activity,
882 where: fragment("?->>'type' = 'Create'", a.data),
883 where: "https://www.w3.org/ns/activitystreams#Public" in a.recipients,
884 where:
885 fragment(
886 "to_tsvector('english', ?->'object'->>'content') @@ plainto_tsquery('english', ?)",
887 a.data,
888 ^query
889 ),
890 limit: 20,
891 order_by: [desc: :id]
892 )
893
894 Repo.all(q) ++ fetched
895 end
896
897 def search2(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
898 accounts = User.search(query, resolve: params["resolve"] == "true", for_user: user)
899
900 statuses = status_search(user, query)
901
902 tags_path = Web.base_url() <> "/tag/"
903
904 tags =
905 query
906 |> String.split()
907 |> Enum.uniq()
908 |> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
909 |> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
910 |> Enum.map(fn tag -> %{name: tag, url: tags_path <> tag} end)
911
912 res = %{
913 "accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
914 "statuses" =>
915 StatusView.render("index.json", activities: statuses, for: user, as: :activity),
916 "hashtags" => tags
917 }
918
919 json(conn, res)
920 end
921
922 def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
923 accounts = User.search(query, resolve: params["resolve"] == "true", for_user: user)
924
925 statuses = status_search(user, query)
926
927 tags =
928 query
929 |> String.split()
930 |> Enum.uniq()
931 |> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
932 |> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
933
934 res = %{
935 "accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
936 "statuses" =>
937 StatusView.render("index.json", activities: statuses, for: user, as: :activity),
938 "hashtags" => tags
939 }
940
941 json(conn, res)
942 end
943
944 def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
945 accounts = User.search(query, resolve: params["resolve"] == "true", for_user: user)
946
947 res = AccountView.render("accounts.json", users: accounts, for: user, as: :user)
948
949 json(conn, res)
950 end
951
952 def favourites(%{assigns: %{user: user}} = conn, params) do
953 activities =
954 params
955 |> Map.put("type", "Create")
956 |> Map.put("favorited_by", user.ap_id)
957 |> Map.put("blocking_user", user)
958 |> ActivityPub.fetch_public_activities()
959 |> Enum.reverse()
960
961 conn
962 |> add_link_headers(:favourites, activities)
963 |> put_view(StatusView)
964 |> render("index.json", %{activities: activities, for: user, as: :activity})
965 end
966
967 def bookmarks(%{assigns: %{user: user}} = conn, _) do
968 user = Repo.get(User, user.id)
969
970 activities =
971 user.bookmarks
972 |> Enum.map(fn id -> Activity.get_create_by_object_ap_id(id) end)
973 |> Enum.reverse()
974
975 conn
976 |> put_view(StatusView)
977 |> render("index.json", %{activities: activities, for: user, as: :activity})
978 end
979
980 def get_lists(%{assigns: %{user: user}} = conn, opts) do
981 lists = Pleroma.List.for_user(user, opts)
982 res = ListView.render("lists.json", lists: lists)
983 json(conn, res)
984 end
985
986 def get_list(%{assigns: %{user: user}} = conn, %{"id" => id}) do
987 with %Pleroma.List{} = list <- Pleroma.List.get(id, user) do
988 res = ListView.render("list.json", list: list)
989 json(conn, res)
990 else
991 _e ->
992 conn
993 |> put_status(404)
994 |> json(%{error: "Record not found"})
995 end
996 end
997
998 def account_lists(%{assigns: %{user: user}} = conn, %{"id" => account_id}) do
999 lists = Pleroma.List.get_lists_account_belongs(user, account_id)
1000 res = ListView.render("lists.json", lists: lists)
1001 json(conn, res)
1002 end
1003
1004 def delete_list(%{assigns: %{user: user}} = conn, %{"id" => id}) do
1005 with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
1006 {:ok, _list} <- Pleroma.List.delete(list) do
1007 json(conn, %{})
1008 else
1009 _e ->
1010 json(conn, "error")
1011 end
1012 end
1013
1014 def create_list(%{assigns: %{user: user}} = conn, %{"title" => title}) do
1015 with {:ok, %Pleroma.List{} = list} <- Pleroma.List.create(title, user) do
1016 res = ListView.render("list.json", list: list)
1017 json(conn, res)
1018 end
1019 end
1020
1021 def add_to_list(%{assigns: %{user: user}} = conn, %{"id" => id, "account_ids" => accounts}) do
1022 accounts
1023 |> Enum.each(fn account_id ->
1024 with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
1025 %User{} = followed <- Repo.get(User, account_id) do
1026 Pleroma.List.follow(list, followed)
1027 end
1028 end)
1029
1030 json(conn, %{})
1031 end
1032
1033 def remove_from_list(%{assigns: %{user: user}} = conn, %{"id" => id, "account_ids" => accounts}) do
1034 accounts
1035 |> Enum.each(fn account_id ->
1036 with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
1037 %User{} = followed <- Repo.get(Pleroma.User, account_id) do
1038 Pleroma.List.unfollow(list, followed)
1039 end
1040 end)
1041
1042 json(conn, %{})
1043 end
1044
1045 def list_accounts(%{assigns: %{user: user}} = conn, %{"id" => id}) do
1046 with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
1047 {:ok, users} = Pleroma.List.get_following(list) do
1048 conn
1049 |> put_view(AccountView)
1050 |> render("accounts.json", %{users: users, as: :user})
1051 end
1052 end
1053
1054 def rename_list(%{assigns: %{user: user}} = conn, %{"id" => id, "title" => title}) do
1055 with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
1056 {:ok, list} <- Pleroma.List.rename(list, title) do
1057 res = ListView.render("list.json", list: list)
1058 json(conn, res)
1059 else
1060 _e ->
1061 json(conn, "error")
1062 end
1063 end
1064
1065 def list_timeline(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params) do
1066 with %Pleroma.List{title: _title, following: following} <- Pleroma.List.get(id, user) do
1067 params =
1068 params
1069 |> Map.put("type", "Create")
1070 |> Map.put("blocking_user", user)
1071 |> Map.put("muting_user", user)
1072
1073 # we must filter the following list for the user to avoid leaking statuses the user
1074 # does not actually have permission to see (for more info, peruse security issue #270).
1075 activities =
1076 following
1077 |> Enum.filter(fn x -> x in user.following end)
1078 |> ActivityPub.fetch_activities_bounded(following, params)
1079 |> Enum.reverse()
1080
1081 conn
1082 |> put_view(StatusView)
1083 |> render("index.json", %{activities: activities, for: user, as: :activity})
1084 else
1085 _e ->
1086 conn
1087 |> put_status(403)
1088 |> json(%{error: "Error."})
1089 end
1090 end
1091
1092 def index(%{assigns: %{user: user}} = conn, _params) do
1093 token =
1094 conn
1095 |> get_session(:oauth_token)
1096
1097 if user && token do
1098 mastodon_emoji = mastodonized_emoji()
1099
1100 limit = Config.get([:instance, :limit])
1101
1102 accounts =
1103 Map.put(%{}, user.id, AccountView.render("account.json", %{user: user, for: user}))
1104
1105 flavour = get_user_flavour(user)
1106
1107 initial_state =
1108 %{
1109 meta: %{
1110 streaming_api_base_url:
1111 String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws"),
1112 access_token: token,
1113 locale: "en",
1114 domain: Pleroma.Web.Endpoint.host(),
1115 admin: "1",
1116 me: "#{user.id}",
1117 unfollow_modal: false,
1118 boost_modal: false,
1119 delete_modal: true,
1120 auto_play_gif: false,
1121 display_sensitive_media: false,
1122 reduce_motion: false,
1123 max_toot_chars: limit
1124 },
1125 rights: %{
1126 delete_others_notice: present?(user.info.is_moderator),
1127 admin: present?(user.info.is_admin)
1128 },
1129 compose: %{
1130 me: "#{user.id}",
1131 default_privacy: user.info.default_scope,
1132 default_sensitive: false
1133 },
1134 media_attachments: %{
1135 accept_content_types: [
1136 ".jpg",
1137 ".jpeg",
1138 ".png",
1139 ".gif",
1140 ".webm",
1141 ".mp4",
1142 ".m4v",
1143 "image\/jpeg",
1144 "image\/png",
1145 "image\/gif",
1146 "video\/webm",
1147 "video\/mp4"
1148 ]
1149 },
1150 settings:
1151 user.info.settings ||
1152 %{
1153 onboarded: true,
1154 home: %{
1155 shows: %{
1156 reblog: true,
1157 reply: true
1158 }
1159 },
1160 notifications: %{
1161 alerts: %{
1162 follow: true,
1163 favourite: true,
1164 reblog: true,
1165 mention: true
1166 },
1167 shows: %{
1168 follow: true,
1169 favourite: true,
1170 reblog: true,
1171 mention: true
1172 },
1173 sounds: %{
1174 follow: true,
1175 favourite: true,
1176 reblog: true,
1177 mention: true
1178 }
1179 }
1180 },
1181 push_subscription: nil,
1182 accounts: accounts,
1183 custom_emojis: mastodon_emoji,
1184 char_limit: limit
1185 }
1186 |> Jason.encode!()
1187
1188 conn
1189 |> put_layout(false)
1190 |> put_view(MastodonView)
1191 |> render("index.html", %{initial_state: initial_state, flavour: flavour})
1192 else
1193 conn
1194 |> redirect(to: "/web/login")
1195 end
1196 end
1197
1198 def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do
1199 info_cng = User.Info.mastodon_settings_update(user.info, settings)
1200
1201 with changeset <- Ecto.Changeset.change(user),
1202 changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng),
1203 {:ok, _user} <- User.update_and_set_cache(changeset) do
1204 json(conn, %{})
1205 else
1206 e ->
1207 conn
1208 |> put_resp_content_type("application/json")
1209 |> send_resp(500, Jason.encode!(%{"error" => inspect(e)}))
1210 end
1211 end
1212
1213 @supported_flavours ["glitch", "vanilla"]
1214
1215 def set_flavour(%{assigns: %{user: user}} = conn, %{"flavour" => flavour} = _params)
1216 when flavour in @supported_flavours do
1217 flavour_cng = User.Info.mastodon_flavour_update(user.info, flavour)
1218
1219 with changeset <- Ecto.Changeset.change(user),
1220 changeset <- Ecto.Changeset.put_embed(changeset, :info, flavour_cng),
1221 {:ok, user} <- User.update_and_set_cache(changeset),
1222 flavour <- user.info.flavour do
1223 json(conn, flavour)
1224 else
1225 e ->
1226 conn
1227 |> put_resp_content_type("application/json")
1228 |> send_resp(500, Jason.encode!(%{"error" => inspect(e)}))
1229 end
1230 end
1231
1232 def set_flavour(conn, _params) do
1233 conn
1234 |> put_status(400)
1235 |> json(%{error: "Unsupported flavour"})
1236 end
1237
1238 def get_flavour(%{assigns: %{user: user}} = conn, _params) do
1239 json(conn, get_user_flavour(user))
1240 end
1241
1242 defp get_user_flavour(%User{info: %{flavour: flavour}}) when flavour in @supported_flavours do
1243 flavour
1244 end
1245
1246 defp get_user_flavour(_) do
1247 "glitch"
1248 end
1249
1250 def login(conn, %{"code" => code}) do
1251 with {:ok, app} <- get_or_make_app(),
1252 %Authorization{} = auth <- Repo.get_by(Authorization, token: code, app_id: app.id),
1253 {:ok, token} <- Token.exchange_token(app, auth) do
1254 conn
1255 |> put_session(:oauth_token, token.token)
1256 |> redirect(to: "/web/getting-started")
1257 end
1258 end
1259
1260 def login(conn, _) do
1261 with {:ok, app} <- get_or_make_app() do
1262 path =
1263 o_auth_path(
1264 conn,
1265 :authorize,
1266 response_type: "code",
1267 client_id: app.client_id,
1268 redirect_uri: ".",
1269 scope: Enum.join(app.scopes, " ")
1270 )
1271
1272 conn
1273 |> redirect(to: path)
1274 end
1275 end
1276
1277 defp get_or_make_app() do
1278 find_attrs = %{client_name: @local_mastodon_name, redirect_uris: "."}
1279 scopes = ["read", "write", "follow", "push"]
1280
1281 with %App{} = app <- Repo.get_by(App, find_attrs) do
1282 {:ok, app} =
1283 if app.scopes == scopes do
1284 {:ok, app}
1285 else
1286 app
1287 |> Ecto.Changeset.change(%{scopes: scopes})
1288 |> Repo.update()
1289 end
1290
1291 {:ok, app}
1292 else
1293 _e ->
1294 cs =
1295 App.register_changeset(
1296 %App{},
1297 Map.put(find_attrs, :scopes, scopes)
1298 )
1299
1300 Repo.insert(cs)
1301 end
1302 end
1303
1304 def logout(conn, _) do
1305 conn
1306 |> clear_session
1307 |> redirect(to: "/")
1308 end
1309
1310 def relationship_noop(%{assigns: %{user: user}} = conn, %{"id" => id}) do
1311 Logger.debug("Unimplemented, returning unmodified relationship")
1312
1313 with %User{} = target <- Repo.get(User, id) do
1314 conn
1315 |> put_view(AccountView)
1316 |> render("relationship.json", %{user: user, target: target})
1317 end
1318 end
1319
1320 def empty_array(conn, _) do
1321 Logger.debug("Unimplemented, returning an empty array")
1322 json(conn, [])
1323 end
1324
1325 def empty_object(conn, _) do
1326 Logger.debug("Unimplemented, returning an empty object")
1327 json(conn, %{})
1328 end
1329
1330 def render_notification(user, %{id: id, activity: activity, inserted_at: created_at} = _params) do
1331 actor = User.get_cached_by_ap_id(activity.data["actor"])
1332 parent_activity = Activity.get_create_by_object_ap_id(activity.data["object"])
1333 mastodon_type = Activity.mastodon_notification_type(activity)
1334
1335 response = %{
1336 id: to_string(id),
1337 type: mastodon_type,
1338 created_at: CommonAPI.Utils.to_masto_date(created_at),
1339 account: AccountView.render("account.json", %{user: actor, for: user})
1340 }
1341
1342 case mastodon_type do
1343 "mention" ->
1344 response
1345 |> Map.merge(%{
1346 status: StatusView.render("status.json", %{activity: activity, for: user})
1347 })
1348
1349 "favourite" ->
1350 response
1351 |> Map.merge(%{
1352 status: StatusView.render("status.json", %{activity: parent_activity, for: user})
1353 })
1354
1355 "reblog" ->
1356 response
1357 |> Map.merge(%{
1358 status: StatusView.render("status.json", %{activity: parent_activity, for: user})
1359 })
1360
1361 "follow" ->
1362 response
1363
1364 _ ->
1365 nil
1366 end
1367 end
1368
1369 def get_filters(%{assigns: %{user: user}} = conn, _) do
1370 filters = Filter.get_filters(user)
1371 res = FilterView.render("filters.json", filters: filters)
1372 json(conn, res)
1373 end
1374
1375 def create_filter(
1376 %{assigns: %{user: user}} = conn,
1377 %{"phrase" => phrase, "context" => context} = params
1378 ) do
1379 query = %Filter{
1380 user_id: user.id,
1381 phrase: phrase,
1382 context: context,
1383 hide: Map.get(params, "irreversible", nil),
1384 whole_word: Map.get(params, "boolean", true)
1385 # expires_at
1386 }
1387
1388 {:ok, response} = Filter.create(query)
1389 res = FilterView.render("filter.json", filter: response)
1390 json(conn, res)
1391 end
1392
1393 def get_filter(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do
1394 filter = Filter.get(filter_id, user)
1395 res = FilterView.render("filter.json", filter: filter)
1396 json(conn, res)
1397 end
1398
1399 def update_filter(
1400 %{assigns: %{user: user}} = conn,
1401 %{"phrase" => phrase, "context" => context, "id" => filter_id} = params
1402 ) do
1403 query = %Filter{
1404 user_id: user.id,
1405 filter_id: filter_id,
1406 phrase: phrase,
1407 context: context,
1408 hide: Map.get(params, "irreversible", nil),
1409 whole_word: Map.get(params, "boolean", true)
1410 # expires_at
1411 }
1412
1413 {:ok, response} = Filter.update(query)
1414 res = FilterView.render("filter.json", filter: response)
1415 json(conn, res)
1416 end
1417
1418 def delete_filter(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do
1419 query = %Filter{
1420 user_id: user.id,
1421 filter_id: filter_id
1422 }
1423
1424 {:ok, _} = Filter.delete(query)
1425 json(conn, %{})
1426 end
1427
1428 # fallback action
1429 #
1430 def errors(conn, _) do
1431 conn
1432 |> put_status(500)
1433 |> json("Something went wrong")
1434 end
1435
1436 def suggestions(%{assigns: %{user: user}} = conn, _) do
1437 suggestions = Config.get(:suggestions)
1438
1439 if Keyword.get(suggestions, :enabled, false) do
1440 api = Keyword.get(suggestions, :third_party_engine, "")
1441 timeout = Keyword.get(suggestions, :timeout, 5000)
1442 limit = Keyword.get(suggestions, :limit, 23)
1443
1444 host = Config.get([Pleroma.Web.Endpoint, :url, :host])
1445
1446 user = user.nickname
1447
1448 url =
1449 api
1450 |> String.replace("{{host}}", host)
1451 |> String.replace("{{user}}", user)
1452
1453 with {:ok, %{status: 200, body: body}} <-
1454 @httpoison.get(
1455 url,
1456 [],
1457 adapter: [
1458 recv_timeout: timeout,
1459 pool: :default
1460 ]
1461 ),
1462 {:ok, data} <- Jason.decode(body) do
1463 data =
1464 data
1465 |> Enum.slice(0, limit)
1466 |> Enum.map(fn x ->
1467 Map.put(
1468 x,
1469 "id",
1470 case User.get_or_fetch(x["acct"]) do
1471 %{id: id} -> id
1472 _ -> 0
1473 end
1474 )
1475 end)
1476 |> Enum.map(fn x ->
1477 Map.put(x, "avatar", MediaProxy.url(x["avatar"]))
1478 end)
1479 |> Enum.map(fn x ->
1480 Map.put(x, "avatar_static", MediaProxy.url(x["avatar_static"]))
1481 end)
1482
1483 conn
1484 |> json(data)
1485 else
1486 e -> Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}")
1487 end
1488 else
1489 json(conn, [])
1490 end
1491 end
1492
1493 def status_card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do
1494 with %Activity{} = activity <- Repo.get(Activity, status_id),
1495 true <- Visibility.visible_for_user?(activity, user) do
1496 data =
1497 StatusView.render(
1498 "card.json",
1499 Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
1500 )
1501
1502 json(conn, data)
1503 else
1504 _e ->
1505 %{}
1506 end
1507 end
1508
1509 def reports(%{assigns: %{user: user}} = conn, params) do
1510 case CommonAPI.report(user, params) do
1511 {:ok, activity} ->
1512 conn
1513 |> put_view(ReportView)
1514 |> try_render("report.json", %{activity: activity})
1515
1516 {:error, err} ->
1517 conn
1518 |> put_status(:bad_request)
1519 |> json(%{error: err})
1520 end
1521 end
1522
1523 def try_render(conn, target, params)
1524 when is_binary(target) do
1525 res = render(conn, target, params)
1526
1527 if res == nil do
1528 conn
1529 |> put_status(501)
1530 |> json(%{error: "Can't display this activity"})
1531 else
1532 res
1533 end
1534 end
1535
1536 def try_render(conn, _, _) do
1537 conn
1538 |> put_status(501)
1539 |> json(%{error: "Can't display this activity"})
1540 end
1541
1542 defp present?(nil), do: false
1543 defp present?(false), do: false
1544 defp present?(_), do: true
1545 end