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