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