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