cad5a8fddef775801a1615c52286008482677795
[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, followed, _} <- CommonAPI.follow(follower, followed) do
737 conn
738 |> put_view(AccountView)
739 |> render("relationship.json", %{user: follower, target: followed})
740 else
741 {:error, message} ->
742 conn
743 |> put_resp_content_type("application/json")
744 |> send_resp(403, Jason.encode!(%{"error" => message}))
745 end
746 end
747
748 def follow(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
749 with %User{} = followed <- Repo.get_by(User, nickname: uri),
750 {:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do
751 conn
752 |> put_view(AccountView)
753 |> render("account.json", %{user: followed, for: follower})
754 else
755 {:error, message} ->
756 conn
757 |> put_resp_content_type("application/json")
758 |> send_resp(403, Jason.encode!(%{"error" => message}))
759 end
760 end
761
762 def unfollow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
763 with %User{} = followed <- Repo.get(User, id),
764 {:ok, _activity} <- ActivityPub.unfollow(follower, followed),
765 {:ok, follower, _} <- User.unfollow(follower, followed) do
766 conn
767 |> put_view(AccountView)
768 |> render("relationship.json", %{user: follower, target: followed})
769 end
770 end
771
772 def mute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
773 with %User{} = muted <- Repo.get(User, id),
774 {:ok, muter} <- User.mute(muter, muted) do
775 conn
776 |> put_view(AccountView)
777 |> render("relationship.json", %{user: muter, target: muted})
778 else
779 {:error, message} ->
780 conn
781 |> put_resp_content_type("application/json")
782 |> send_resp(403, Jason.encode!(%{"error" => message}))
783 end
784 end
785
786 def unmute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
787 with %User{} = muted <- Repo.get(User, id),
788 {:ok, muter} <- User.unmute(muter, muted) do
789 conn
790 |> put_view(AccountView)
791 |> render("relationship.json", %{user: muter, target: muted})
792 else
793 {:error, message} ->
794 conn
795 |> put_resp_content_type("application/json")
796 |> send_resp(403, Jason.encode!(%{"error" => message}))
797 end
798 end
799
800 def mutes(%{assigns: %{user: user}} = conn, _) do
801 with muted_accounts <- User.muted_users(user) do
802 res = AccountView.render("accounts.json", users: muted_accounts, for: user, as: :user)
803 json(conn, res)
804 end
805 end
806
807 def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
808 with %User{} = blocked <- Repo.get(User, id),
809 {:ok, blocker} <- User.block(blocker, blocked),
810 {:ok, _activity} <- ActivityPub.block(blocker, blocked) do
811 conn
812 |> put_view(AccountView)
813 |> render("relationship.json", %{user: blocker, target: blocked})
814 else
815 {:error, message} ->
816 conn
817 |> put_resp_content_type("application/json")
818 |> send_resp(403, Jason.encode!(%{"error" => message}))
819 end
820 end
821
822 def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
823 with %User{} = blocked <- Repo.get(User, id),
824 {:ok, blocker} <- User.unblock(blocker, blocked),
825 {:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do
826 conn
827 |> put_view(AccountView)
828 |> render("relationship.json", %{user: blocker, target: blocked})
829 else
830 {:error, message} ->
831 conn
832 |> put_resp_content_type("application/json")
833 |> send_resp(403, Jason.encode!(%{"error" => message}))
834 end
835 end
836
837 def blocks(%{assigns: %{user: user}} = conn, _) do
838 with blocked_accounts <- User.blocked_users(user) do
839 res = AccountView.render("accounts.json", users: blocked_accounts, for: user, as: :user)
840 json(conn, res)
841 end
842 end
843
844 def domain_blocks(%{assigns: %{user: %{info: info}}} = conn, _) do
845 json(conn, info.domain_blocks || [])
846 end
847
848 def block_domain(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do
849 User.block_domain(blocker, domain)
850 json(conn, %{})
851 end
852
853 def unblock_domain(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do
854 User.unblock_domain(blocker, domain)
855 json(conn, %{})
856 end
857
858 def status_search(user, query) do
859 fetched =
860 if Regex.match?(~r/https?:/, query) do
861 with {:ok, object} <- ActivityPub.fetch_object_from_id(query),
862 %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
863 true <- Visibility.visible_for_user?(activity, user) do
864 [activity]
865 else
866 _e -> []
867 end
868 end || []
869
870 q =
871 from(
872 a in Activity,
873 where: fragment("?->>'type' = 'Create'", a.data),
874 where: "https://www.w3.org/ns/activitystreams#Public" in a.recipients,
875 where:
876 fragment(
877 "to_tsvector('english', ?->'object'->>'content') @@ plainto_tsquery('english', ?)",
878 a.data,
879 ^query
880 ),
881 limit: 20,
882 order_by: [desc: :id]
883 )
884
885 Repo.all(q) ++ fetched
886 end
887
888 def search2(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
889 accounts = User.search(query, resolve: params["resolve"] == "true", for_user: user)
890
891 statuses = status_search(user, query)
892
893 tags_path = Web.base_url() <> "/tag/"
894
895 tags =
896 query
897 |> String.split()
898 |> Enum.uniq()
899 |> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
900 |> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
901 |> Enum.map(fn tag -> %{name: tag, url: tags_path <> tag} end)
902
903 res = %{
904 "accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
905 "statuses" =>
906 StatusView.render("index.json", activities: statuses, for: user, as: :activity),
907 "hashtags" => tags
908 }
909
910 json(conn, res)
911 end
912
913 def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
914 accounts = User.search(query, resolve: params["resolve"] == "true", for_user: user)
915
916 statuses = status_search(user, query)
917
918 tags =
919 query
920 |> String.split()
921 |> Enum.uniq()
922 |> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
923 |> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
924
925 res = %{
926 "accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
927 "statuses" =>
928 StatusView.render("index.json", activities: statuses, for: user, as: :activity),
929 "hashtags" => tags
930 }
931
932 json(conn, res)
933 end
934
935 def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
936 accounts = User.search(query, resolve: params["resolve"] == "true", for_user: user)
937
938 res = AccountView.render("accounts.json", users: accounts, for: user, as: :user)
939
940 json(conn, res)
941 end
942
943 def favourites(%{assigns: %{user: user}} = conn, params) do
944 activities =
945 params
946 |> Map.put("type", "Create")
947 |> Map.put("favorited_by", user.ap_id)
948 |> Map.put("blocking_user", user)
949 |> ActivityPub.fetch_public_activities()
950 |> Enum.reverse()
951
952 conn
953 |> add_link_headers(:favourites, activities)
954 |> put_view(StatusView)
955 |> render("index.json", %{activities: activities, for: user, as: :activity})
956 end
957
958 def bookmarks(%{assigns: %{user: user}} = conn, _) do
959 user = Repo.get(User, user.id)
960
961 activities =
962 user.bookmarks
963 |> Enum.map(fn id -> Activity.get_create_by_object_ap_id(id) end)
964 |> Enum.reverse()
965
966 conn
967 |> put_view(StatusView)
968 |> render("index.json", %{activities: activities, for: user, as: :activity})
969 end
970
971 def get_lists(%{assigns: %{user: user}} = conn, opts) do
972 lists = Pleroma.List.for_user(user, opts)
973 res = ListView.render("lists.json", lists: lists)
974 json(conn, res)
975 end
976
977 def get_list(%{assigns: %{user: user}} = conn, %{"id" => id}) do
978 with %Pleroma.List{} = list <- Pleroma.List.get(id, user) do
979 res = ListView.render("list.json", list: list)
980 json(conn, res)
981 else
982 _e ->
983 conn
984 |> put_status(404)
985 |> json(%{error: "Record not found"})
986 end
987 end
988
989 def account_lists(%{assigns: %{user: user}} = conn, %{"id" => account_id}) do
990 lists = Pleroma.List.get_lists_account_belongs(user, account_id)
991 res = ListView.render("lists.json", lists: lists)
992 json(conn, res)
993 end
994
995 def delete_list(%{assigns: %{user: user}} = conn, %{"id" => id}) do
996 with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
997 {:ok, _list} <- Pleroma.List.delete(list) do
998 json(conn, %{})
999 else
1000 _e ->
1001 json(conn, "error")
1002 end
1003 end
1004
1005 def create_list(%{assigns: %{user: user}} = conn, %{"title" => title}) do
1006 with {:ok, %Pleroma.List{} = list} <- Pleroma.List.create(title, user) do
1007 res = ListView.render("list.json", list: list)
1008 json(conn, res)
1009 end
1010 end
1011
1012 def add_to_list(%{assigns: %{user: user}} = conn, %{"id" => id, "account_ids" => accounts}) do
1013 accounts
1014 |> Enum.each(fn account_id ->
1015 with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
1016 %User{} = followed <- Repo.get(User, account_id) do
1017 Pleroma.List.follow(list, followed)
1018 end
1019 end)
1020
1021 json(conn, %{})
1022 end
1023
1024 def remove_from_list(%{assigns: %{user: user}} = conn, %{"id" => id, "account_ids" => accounts}) do
1025 accounts
1026 |> Enum.each(fn account_id ->
1027 with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
1028 %User{} = followed <- Repo.get(Pleroma.User, account_id) do
1029 Pleroma.List.unfollow(list, followed)
1030 end
1031 end)
1032
1033 json(conn, %{})
1034 end
1035
1036 def list_accounts(%{assigns: %{user: user}} = conn, %{"id" => id}) do
1037 with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
1038 {:ok, users} = Pleroma.List.get_following(list) do
1039 conn
1040 |> put_view(AccountView)
1041 |> render("accounts.json", %{users: users, as: :user})
1042 end
1043 end
1044
1045 def rename_list(%{assigns: %{user: user}} = conn, %{"id" => id, "title" => title}) do
1046 with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
1047 {:ok, list} <- Pleroma.List.rename(list, title) do
1048 res = ListView.render("list.json", list: list)
1049 json(conn, res)
1050 else
1051 _e ->
1052 json(conn, "error")
1053 end
1054 end
1055
1056 def list_timeline(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params) do
1057 with %Pleroma.List{title: _title, following: following} <- Pleroma.List.get(id, user) do
1058 params =
1059 params
1060 |> Map.put("type", "Create")
1061 |> Map.put("blocking_user", user)
1062 |> Map.put("muting_user", user)
1063
1064 # we must filter the following list for the user to avoid leaking statuses the user
1065 # does not actually have permission to see (for more info, peruse security issue #270).
1066 activities =
1067 following
1068 |> Enum.filter(fn x -> x in user.following end)
1069 |> ActivityPub.fetch_activities_bounded(following, params)
1070 |> Enum.reverse()
1071
1072 conn
1073 |> put_view(StatusView)
1074 |> render("index.json", %{activities: activities, for: user, as: :activity})
1075 else
1076 _e ->
1077 conn
1078 |> put_status(403)
1079 |> json(%{error: "Error."})
1080 end
1081 end
1082
1083 def index(%{assigns: %{user: user}} = conn, _params) do
1084 token =
1085 conn
1086 |> get_session(:oauth_token)
1087
1088 if user && token do
1089 mastodon_emoji = mastodonized_emoji()
1090
1091 limit = Config.get([:instance, :limit])
1092
1093 accounts =
1094 Map.put(%{}, user.id, AccountView.render("account.json", %{user: user, for: user}))
1095
1096 flavour = get_user_flavour(user)
1097
1098 initial_state =
1099 %{
1100 meta: %{
1101 streaming_api_base_url:
1102 String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws"),
1103 access_token: token,
1104 locale: "en",
1105 domain: Pleroma.Web.Endpoint.host(),
1106 admin: "1",
1107 me: "#{user.id}",
1108 unfollow_modal: false,
1109 boost_modal: false,
1110 delete_modal: true,
1111 auto_play_gif: false,
1112 display_sensitive_media: false,
1113 reduce_motion: false,
1114 max_toot_chars: limit
1115 },
1116 rights: %{
1117 delete_others_notice: present?(user.info.is_moderator),
1118 admin: present?(user.info.is_admin)
1119 },
1120 compose: %{
1121 me: "#{user.id}",
1122 default_privacy: user.info.default_scope,
1123 default_sensitive: false
1124 },
1125 media_attachments: %{
1126 accept_content_types: [
1127 ".jpg",
1128 ".jpeg",
1129 ".png",
1130 ".gif",
1131 ".webm",
1132 ".mp4",
1133 ".m4v",
1134 "image\/jpeg",
1135 "image\/png",
1136 "image\/gif",
1137 "video\/webm",
1138 "video\/mp4"
1139 ]
1140 },
1141 settings:
1142 user.info.settings ||
1143 %{
1144 onboarded: true,
1145 home: %{
1146 shows: %{
1147 reblog: true,
1148 reply: true
1149 }
1150 },
1151 notifications: %{
1152 alerts: %{
1153 follow: true,
1154 favourite: true,
1155 reblog: true,
1156 mention: true
1157 },
1158 shows: %{
1159 follow: true,
1160 favourite: true,
1161 reblog: true,
1162 mention: true
1163 },
1164 sounds: %{
1165 follow: true,
1166 favourite: true,
1167 reblog: true,
1168 mention: true
1169 }
1170 }
1171 },
1172 push_subscription: nil,
1173 accounts: accounts,
1174 custom_emojis: mastodon_emoji,
1175 char_limit: limit
1176 }
1177 |> Jason.encode!()
1178
1179 conn
1180 |> put_layout(false)
1181 |> put_view(MastodonView)
1182 |> render("index.html", %{initial_state: initial_state, flavour: flavour})
1183 else
1184 conn
1185 |> redirect(to: "/web/login")
1186 end
1187 end
1188
1189 def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do
1190 info_cng = User.Info.mastodon_settings_update(user.info, settings)
1191
1192 with changeset <- Ecto.Changeset.change(user),
1193 changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng),
1194 {:ok, _user} <- User.update_and_set_cache(changeset) do
1195 json(conn, %{})
1196 else
1197 e ->
1198 conn
1199 |> put_resp_content_type("application/json")
1200 |> send_resp(500, Jason.encode!(%{"error" => inspect(e)}))
1201 end
1202 end
1203
1204 @supported_flavours ["glitch", "vanilla"]
1205
1206 def set_flavour(%{assigns: %{user: user}} = conn, %{"flavour" => flavour} = _params)
1207 when flavour in @supported_flavours do
1208 flavour_cng = User.Info.mastodon_flavour_update(user.info, flavour)
1209
1210 with changeset <- Ecto.Changeset.change(user),
1211 changeset <- Ecto.Changeset.put_embed(changeset, :info, flavour_cng),
1212 {:ok, user} <- User.update_and_set_cache(changeset),
1213 flavour <- user.info.flavour do
1214 json(conn, flavour)
1215 else
1216 e ->
1217 conn
1218 |> put_resp_content_type("application/json")
1219 |> send_resp(500, Jason.encode!(%{"error" => inspect(e)}))
1220 end
1221 end
1222
1223 def set_flavour(conn, _params) do
1224 conn
1225 |> put_status(400)
1226 |> json(%{error: "Unsupported flavour"})
1227 end
1228
1229 def get_flavour(%{assigns: %{user: user}} = conn, _params) do
1230 json(conn, get_user_flavour(user))
1231 end
1232
1233 defp get_user_flavour(%User{info: %{flavour: flavour}}) when flavour in @supported_flavours do
1234 flavour
1235 end
1236
1237 defp get_user_flavour(_) do
1238 "glitch"
1239 end
1240
1241 def login(conn, %{"code" => code}) do
1242 with {:ok, app} <- get_or_make_app(),
1243 %Authorization{} = auth <- Repo.get_by(Authorization, token: code, app_id: app.id),
1244 {:ok, token} <- Token.exchange_token(app, auth) do
1245 conn
1246 |> put_session(:oauth_token, token.token)
1247 |> redirect(to: "/web/getting-started")
1248 end
1249 end
1250
1251 def login(conn, _) do
1252 with {:ok, app} <- get_or_make_app() do
1253 path =
1254 o_auth_path(
1255 conn,
1256 :authorize,
1257 response_type: "code",
1258 client_id: app.client_id,
1259 redirect_uri: ".",
1260 scope: Enum.join(app.scopes, " ")
1261 )
1262
1263 conn
1264 |> redirect(to: path)
1265 end
1266 end
1267
1268 defp get_or_make_app() do
1269 find_attrs = %{client_name: @local_mastodon_name, redirect_uris: "."}
1270 scopes = ["read", "write", "follow", "push"]
1271
1272 with %App{} = app <- Repo.get_by(App, find_attrs) do
1273 {:ok, app} =
1274 if app.scopes == scopes do
1275 {:ok, app}
1276 else
1277 app
1278 |> Ecto.Changeset.change(%{scopes: scopes})
1279 |> Repo.update()
1280 end
1281
1282 {:ok, app}
1283 else
1284 _e ->
1285 cs =
1286 App.register_changeset(
1287 %App{},
1288 Map.put(find_attrs, :scopes, scopes)
1289 )
1290
1291 Repo.insert(cs)
1292 end
1293 end
1294
1295 def logout(conn, _) do
1296 conn
1297 |> clear_session
1298 |> redirect(to: "/")
1299 end
1300
1301 def relationship_noop(%{assigns: %{user: user}} = conn, %{"id" => id}) do
1302 Logger.debug("Unimplemented, returning unmodified relationship")
1303
1304 with %User{} = target <- Repo.get(User, id) do
1305 conn
1306 |> put_view(AccountView)
1307 |> render("relationship.json", %{user: user, target: target})
1308 end
1309 end
1310
1311 def empty_array(conn, _) do
1312 Logger.debug("Unimplemented, returning an empty array")
1313 json(conn, [])
1314 end
1315
1316 def empty_object(conn, _) do
1317 Logger.debug("Unimplemented, returning an empty object")
1318 json(conn, %{})
1319 end
1320
1321 def render_notification(user, %{id: id, activity: activity, inserted_at: created_at} = _params) do
1322 actor = User.get_cached_by_ap_id(activity.data["actor"])
1323 parent_activity = Activity.get_create_by_object_ap_id(activity.data["object"])
1324 mastodon_type = Activity.mastodon_notification_type(activity)
1325
1326 response = %{
1327 id: to_string(id),
1328 type: mastodon_type,
1329 created_at: CommonAPI.Utils.to_masto_date(created_at),
1330 account: AccountView.render("account.json", %{user: actor, for: user})
1331 }
1332
1333 case mastodon_type do
1334 "mention" ->
1335 response
1336 |> Map.merge(%{
1337 status: StatusView.render("status.json", %{activity: activity, for: user})
1338 })
1339
1340 "favourite" ->
1341 response
1342 |> Map.merge(%{
1343 status: StatusView.render("status.json", %{activity: parent_activity, for: user})
1344 })
1345
1346 "reblog" ->
1347 response
1348 |> Map.merge(%{
1349 status: StatusView.render("status.json", %{activity: parent_activity, for: user})
1350 })
1351
1352 "follow" ->
1353 response
1354
1355 _ ->
1356 nil
1357 end
1358 end
1359
1360 def get_filters(%{assigns: %{user: user}} = conn, _) do
1361 filters = Filter.get_filters(user)
1362 res = FilterView.render("filters.json", filters: filters)
1363 json(conn, res)
1364 end
1365
1366 def create_filter(
1367 %{assigns: %{user: user}} = conn,
1368 %{"phrase" => phrase, "context" => context} = params
1369 ) do
1370 query = %Filter{
1371 user_id: user.id,
1372 phrase: phrase,
1373 context: context,
1374 hide: Map.get(params, "irreversible", nil),
1375 whole_word: Map.get(params, "boolean", true)
1376 # expires_at
1377 }
1378
1379 {:ok, response} = Filter.create(query)
1380 res = FilterView.render("filter.json", filter: response)
1381 json(conn, res)
1382 end
1383
1384 def get_filter(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do
1385 filter = Filter.get(filter_id, user)
1386 res = FilterView.render("filter.json", filter: filter)
1387 json(conn, res)
1388 end
1389
1390 def update_filter(
1391 %{assigns: %{user: user}} = conn,
1392 %{"phrase" => phrase, "context" => context, "id" => filter_id} = params
1393 ) do
1394 query = %Filter{
1395 user_id: user.id,
1396 filter_id: filter_id,
1397 phrase: phrase,
1398 context: context,
1399 hide: Map.get(params, "irreversible", nil),
1400 whole_word: Map.get(params, "boolean", true)
1401 # expires_at
1402 }
1403
1404 {:ok, response} = Filter.update(query)
1405 res = FilterView.render("filter.json", filter: response)
1406 json(conn, res)
1407 end
1408
1409 def delete_filter(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do
1410 query = %Filter{
1411 user_id: user.id,
1412 filter_id: filter_id
1413 }
1414
1415 {:ok, _} = Filter.delete(query)
1416 json(conn, %{})
1417 end
1418
1419 def create_push_subscription(%{assigns: %{user: user, token: token}} = conn, params) do
1420 true = Push.enabled()
1421 Subscription.delete_if_exists(user, token)
1422 {:ok, subscription} = Subscription.create(user, token, params)
1423 view = PushSubscriptionView.render("push_subscription.json", subscription: subscription)
1424 json(conn, view)
1425 end
1426
1427 def get_push_subscription(%{assigns: %{user: user, token: token}} = conn, _params) do
1428 true = Push.enabled()
1429 subscription = Subscription.get(user, token)
1430 view = PushSubscriptionView.render("push_subscription.json", subscription: subscription)
1431 json(conn, view)
1432 end
1433
1434 def update_push_subscription(
1435 %{assigns: %{user: user, token: token}} = conn,
1436 params
1437 ) do
1438 true = Push.enabled()
1439 {:ok, subscription} = Subscription.update(user, token, params)
1440 view = PushSubscriptionView.render("push_subscription.json", subscription: subscription)
1441 json(conn, view)
1442 end
1443
1444 def delete_push_subscription(%{assigns: %{user: user, token: token}} = conn, _params) do
1445 true = Push.enabled()
1446 {:ok, _response} = Subscription.delete(user, token)
1447 json(conn, %{})
1448 end
1449
1450 def errors(conn, _) do
1451 conn
1452 |> put_status(500)
1453 |> json("Something went wrong")
1454 end
1455
1456 def suggestions(%{assigns: %{user: user}} = conn, _) do
1457 suggestions = Config.get(:suggestions)
1458
1459 if Keyword.get(suggestions, :enabled, false) do
1460 api = Keyword.get(suggestions, :third_party_engine, "")
1461 timeout = Keyword.get(suggestions, :timeout, 5000)
1462 limit = Keyword.get(suggestions, :limit, 23)
1463
1464 host = Config.get([Pleroma.Web.Endpoint, :url, :host])
1465
1466 user = user.nickname
1467
1468 url =
1469 api
1470 |> String.replace("{{host}}", host)
1471 |> String.replace("{{user}}", user)
1472
1473 with {:ok, %{status: 200, body: body}} <-
1474 @httpoison.get(
1475 url,
1476 [],
1477 adapter: [
1478 timeout: timeout,
1479 recv_timeout: timeout,
1480 pool: :default
1481 ]
1482 ),
1483 {:ok, data} <- Jason.decode(body) do
1484 data =
1485 data
1486 |> Enum.slice(0, limit)
1487 |> Enum.map(fn x ->
1488 Map.put(
1489 x,
1490 "id",
1491 case User.get_or_fetch(x["acct"]) do
1492 %{id: id} -> id
1493 _ -> 0
1494 end
1495 )
1496 end)
1497 |> Enum.map(fn x ->
1498 Map.put(x, "avatar", MediaProxy.url(x["avatar"]))
1499 end)
1500 |> Enum.map(fn x ->
1501 Map.put(x, "avatar_static", MediaProxy.url(x["avatar_static"]))
1502 end)
1503
1504 conn
1505 |> json(data)
1506 else
1507 e -> Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}")
1508 end
1509 else
1510 json(conn, [])
1511 end
1512 end
1513
1514 def status_card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do
1515 with %Activity{} = activity <- Repo.get(Activity, status_id),
1516 true <- Visibility.visible_for_user?(activity, user) do
1517 data =
1518 StatusView.render(
1519 "card.json",
1520 Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
1521 )
1522
1523 json(conn, data)
1524 else
1525 _e ->
1526 %{}
1527 end
1528 end
1529
1530 def reports(%{assigns: %{user: user}} = conn, params) do
1531 case CommonAPI.report(user, params) do
1532 {:ok, activity} ->
1533 conn
1534 |> put_view(ReportView)
1535 |> try_render("report.json", %{activity: activity})
1536
1537 {:error, err} ->
1538 conn
1539 |> put_status(:bad_request)
1540 |> json(%{error: err})
1541 end
1542 end
1543
1544 def try_render(conn, target, params)
1545 when is_binary(target) do
1546 res = render(conn, target, params)
1547
1548 if res == nil do
1549 conn
1550 |> put_status(501)
1551 |> json(%{error: "Can't display this activity"})
1552 else
1553 res
1554 end
1555 end
1556
1557 def try_render(conn, _, _) do
1558 conn
1559 |> put_status(501)
1560 |> json(%{error: "Can't display this activity"})
1561 end
1562
1563 defp present?(nil), do: false
1564 defp present?(false), do: false
1565 defp present?(_), do: true
1566 end