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