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