1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
6 use Pleroma.Web, :controller
8 import Pleroma.Web.ControllerHelper,
9 only: [json_response: 3, add_link_headers: 2, truthy_param?: 1]
12 alias Pleroma.Activity
13 alias Pleroma.Bookmark
15 alias Pleroma.Conversation.Participation
19 alias Pleroma.Pagination
20 alias Pleroma.Plugs.RateLimiter
25 alias Pleroma.Web.ActivityPub.ActivityPub
26 alias Pleroma.Web.ActivityPub.Visibility
27 alias Pleroma.Web.CommonAPI
28 alias Pleroma.Web.MastodonAPI.AccountView
29 alias Pleroma.Web.MastodonAPI.AppView
30 alias Pleroma.Web.MastodonAPI.ConversationView
31 alias Pleroma.Web.MastodonAPI.ListView
32 alias Pleroma.Web.MastodonAPI.MastodonAPI
33 alias Pleroma.Web.MastodonAPI.MastodonView
34 alias Pleroma.Web.MastodonAPI.ReportView
35 alias Pleroma.Web.MastodonAPI.StatusView
36 alias Pleroma.Web.MediaProxy
37 alias Pleroma.Web.OAuth.App
38 alias Pleroma.Web.OAuth.Authorization
39 alias Pleroma.Web.OAuth.Scopes
40 alias Pleroma.Web.OAuth.Token
41 alias Pleroma.Web.TwitterAPI.TwitterAPI
46 require Pleroma.Constants
48 @rate_limited_relations_actions ~w(follow unfollow)a
52 {:relations_id_action, params: ["id", "uri"]} when action in @rate_limited_relations_actions
55 plug(RateLimiter, :relations_actions when action in @rate_limited_relations_actions)
56 plug(RateLimiter, :app_account_creation when action == :account_register)
57 plug(RateLimiter, :search when action in [:search, :search2, :account_search])
58 plug(RateLimiter, :password_reset when action == :password_reset)
59 plug(RateLimiter, :account_confirmation_resend when action == :account_confirmation_resend)
61 @local_mastodon_name "Mastodon-Local"
63 action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
65 def create_app(conn, params) do
66 scopes = Scopes.fetch_scopes(params, ["read"])
70 |> Map.drop(["scope", "scopes"])
71 |> Map.put("scopes", scopes)
73 with cs <- App.register_changeset(%App{}, app_attrs),
74 false <- cs.changes[:client_name] == @local_mastodon_name,
75 {:ok, app} <- Repo.insert(cs) do
78 |> render("show.json", %{app: app})
87 value_function \\ fn x -> {:ok, x} end
89 if Map.has_key?(params, params_field) do
90 case value_function.(params[params_field]) do
91 {:ok, new_value} -> Map.put(map, map_field, new_value)
99 def update_credentials(%{assigns: %{user: user}} = conn, params) do
104 |> add_if_present(params, "display_name", :name)
105 |> add_if_present(params, "note", :bio, fn value -> {:ok, User.parse_bio(value, user)} end)
106 |> add_if_present(params, "avatar", :avatar, fn value ->
107 with %Plug.Upload{} <- value,
108 {:ok, object} <- ActivityPub.upload(value, type: :avatar) do
115 emojis_text = (user_params["display_name"] || "") <> (user_params["note"] || "")
119 |> Map.get(:emoji, [])
120 |> Enum.concat(Emoji.Formatter.get_emoji_map(emojis_text))
127 :hide_followers_count,
133 :skip_thread_containment,
136 |> Enum.reduce(%{}, fn key, acc ->
137 add_if_present(acc, params, to_string(key), key, fn value ->
138 {:ok, truthy_param?(value)}
141 |> add_if_present(params, "default_scope", :default_scope)
142 |> add_if_present(params, "fields", :fields, fn fields ->
143 fields = Enum.map(fields, fn f -> Map.update!(f, "value", &AutoLinker.link(&1)) end)
147 |> add_if_present(params, "fields", :raw_fields)
148 |> add_if_present(params, "pleroma_settings_store", :pleroma_settings_store, fn value ->
149 {:ok, Map.merge(user.info.pleroma_settings_store, value)}
151 |> add_if_present(params, "header", :banner, fn value ->
152 with %Plug.Upload{} <- value,
153 {:ok, object} <- ActivityPub.upload(value, type: :banner) do
159 |> add_if_present(params, "pleroma_background_image", :background, fn value ->
160 with %Plug.Upload{} <- value,
161 {:ok, object} <- ActivityPub.upload(value, type: :background) do
167 |> Map.put(:emoji, user_info_emojis)
171 |> User.update_changeset(user_params)
172 |> User.change_info(&User.Info.profile_update(&1, info_params))
174 with {:ok, user} <- User.update_and_set_cache(changeset) do
175 if original_user != user, do: CommonAPI.update(user)
179 AccountView.render("account.json", %{user: user, for: user, with_pleroma_settings: true})
182 _e -> render_error(conn, :forbidden, "Invalid request")
186 def update_avatar(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
187 change = Changeset.change(user, %{avatar: nil})
188 {:ok, user} = User.update_and_set_cache(change)
189 CommonAPI.update(user)
191 json(conn, %{url: nil})
194 def update_avatar(%{assigns: %{user: user}} = conn, params) do
195 {:ok, object} = ActivityPub.upload(params, type: :avatar)
196 change = Changeset.change(user, %{avatar: object.data})
197 {:ok, user} = User.update_and_set_cache(change)
198 CommonAPI.update(user)
199 %{"url" => [%{"href" => href} | _]} = object.data
201 json(conn, %{url: href})
204 def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do
205 new_info = %{"banner" => %{}}
207 with {:ok, user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
208 CommonAPI.update(user)
209 json(conn, %{url: nil})
213 def update_banner(%{assigns: %{user: user}} = conn, params) do
214 with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner),
215 new_info <- %{"banner" => object.data},
216 {:ok, user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
217 CommonAPI.update(user)
218 %{"url" => [%{"href" => href} | _]} = object.data
220 json(conn, %{url: href})
224 def update_background(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
225 new_info = %{"background" => %{}}
227 with {:ok, _user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
228 json(conn, %{url: nil})
232 def update_background(%{assigns: %{user: user}} = conn, params) do
233 with {:ok, object} <- ActivityPub.upload(params, type: :background),
234 new_info <- %{"background" => object.data},
235 {:ok, _user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
236 %{"url" => [%{"href" => href} | _]} = object.data
238 json(conn, %{url: href})
242 def verify_credentials(%{assigns: %{user: user}} = conn, _) do
243 chat_token = Phoenix.Token.sign(conn, "user socket", user.id)
246 AccountView.render("account.json", %{
249 with_pleroma_settings: true,
250 with_chat_token: chat_token
256 def verify_app_credentials(%{assigns: %{user: _user, token: token}} = conn, _) do
257 with %Token{app: %App{} = app} <- Repo.preload(token, :app) do
260 |> render("short.json", %{app: app})
264 def user(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do
265 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id, for: for_user),
266 true <- User.auth_active?(user) || user.id == for_user.id || User.superuser?(for_user) do
267 account = AccountView.render("account.json", %{user: user, for: for_user})
270 _e -> render_error(conn, :not_found, "Can't find user")
274 @mastodon_api_level "2.7.2"
276 def masto_instance(conn, _params) do
277 instance = Config.get(:instance)
281 title: Keyword.get(instance, :name),
282 description: Keyword.get(instance, :description),
283 version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})",
284 email: Keyword.get(instance, :email),
286 streaming_api: Pleroma.Web.Endpoint.websocket_url()
288 stats: Stats.get_stats(),
289 thumbnail: Web.base_url() <> "/instance/thumbnail.jpeg",
291 registrations: Pleroma.Config.get([:instance, :registrations_open]),
292 # Extra (not present in Mastodon):
293 max_toot_chars: Keyword.get(instance, :limit),
294 poll_limits: Keyword.get(instance, :poll_limits)
300 def peers(conn, _params) do
301 json(conn, Stats.get_peers())
304 defp mastodonized_emoji do
305 Pleroma.Emoji.get_all()
306 |> Enum.map(fn {shortcode, %Pleroma.Emoji{file: relative_url, tags: tags}} ->
307 url = to_string(URI.merge(Web.base_url(), relative_url))
310 "shortcode" => shortcode,
312 "visible_in_picker" => true,
315 # Assuming that a comma is authorized in the category name
316 "category" => (tags -- ["Custom"]) |> Enum.join(",")
321 def custom_emojis(conn, _params) do
322 mastodon_emoji = mastodonized_emoji()
323 json(conn, mastodon_emoji)
326 def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do
327 with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user) do
330 |> Map.put("tag", params["tagged"])
332 activities = ActivityPub.fetch_user_activities(user, reading_user, params)
335 |> add_link_headers(activities)
336 |> put_view(StatusView)
337 |> render("index.json", %{
338 activities: activities,
345 def get_poll(%{assigns: %{user: user}} = conn, %{"id" => id}) do
346 with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60),
347 %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
348 true <- Visibility.visible_for_user?(activity, user) do
350 |> put_view(StatusView)
351 |> try_render("poll.json", %{object: object, for: user})
353 error when is_nil(error) or error == false ->
354 render_error(conn, :not_found, "Record not found")
358 defp get_cached_vote_or_vote(user, object, choices) do
359 idempotency_key = "polls:#{user.id}:#{object.data["id"]}"
362 Cachex.fetch(:idempotency_cache, idempotency_key, fn _ ->
363 case CommonAPI.vote(user, object, choices) do
364 {:error, _message} = res -> {:ignore, res}
365 res -> {:commit, res}
372 def poll_vote(%{assigns: %{user: user}} = conn, %{"id" => id, "choices" => choices}) do
373 with %Object{} = object <- Object.get_by_id(id),
374 true <- object.data["type"] == "Question",
375 %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
376 true <- Visibility.visible_for_user?(activity, user),
377 {:ok, _activities, object} <- get_cached_vote_or_vote(user, object, choices) do
379 |> put_view(StatusView)
380 |> try_render("poll.json", %{object: object, for: user})
383 render_error(conn, :not_found, "Record not found")
386 render_error(conn, :not_found, "Record not found")
390 |> put_status(:unprocessable_entity)
391 |> json(%{error: message})
395 def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do
397 q = from(u in User, where: u.id in ^id)
398 targets = Repo.all(q)
401 |> put_view(AccountView)
402 |> render("relationships.json", %{user: user, targets: targets})
405 # Instead of returning a 400 when no "id" params is present, Mastodon returns an empty array.
406 def relationships(%{assigns: %{user: _user}} = conn, _), do: json(conn, [])
408 def update_media(%{assigns: %{user: user}} = conn, data) do
409 with %Object{} = object <- Repo.get(Object, data["id"]),
410 true <- Object.authorize_mutation(object, user),
411 true <- is_binary(data["description"]),
412 description <- data["description"] do
413 new_data = %{object.data | "name" => description}
417 |> Object.change(%{data: new_data})
420 attachment_data = Map.put(new_data, "id", object.id)
423 |> put_view(StatusView)
424 |> render("attachment.json", %{attachment: attachment_data})
428 def upload(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do
429 with {:ok, object} <-
432 actor: User.ap_id(user),
433 description: Map.get(data, "description")
435 attachment_data = Map.put(object.data, "id", object.id)
438 |> put_view(StatusView)
439 |> render("attachment.json", %{attachment: attachment_data})
443 def set_mascot(%{assigns: %{user: user}} = conn, %{"file" => file}) do
444 with {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)),
445 %{} = attachment_data <- Map.put(object.data, "id", object.id),
446 # Reject if not an image
447 %{type: "image"} = rendered <-
448 StatusView.render("attachment.json", %{attachment: attachment_data}) do
450 # Save to the user's info
451 {:ok, _user} = User.update_info(user, &User.Info.mascot_update(&1, rendered))
455 %{type: _} -> render_error(conn, :unsupported_media_type, "mascots can only be images")
459 def get_mascot(%{assigns: %{user: user}} = conn, _params) do
460 mascot = User.get_mascot(user)
466 def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
467 with %User{} = user <- User.get_cached_by_id(id),
468 followers <- MastodonAPI.get_followers(user, params) do
471 for_user && user.id == for_user.id -> followers
472 user.info.hide_followers -> []
477 |> add_link_headers(followers)
478 |> put_view(AccountView)
479 |> render("accounts.json", %{for: for_user, users: followers, as: :user})
483 def following(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
484 with %User{} = user <- User.get_cached_by_id(id),
485 followers <- MastodonAPI.get_friends(user, params) do
488 for_user && user.id == for_user.id -> followers
489 user.info.hide_follows -> []
494 |> add_link_headers(followers)
495 |> put_view(AccountView)
496 |> render("accounts.json", %{for: for_user, users: followers, as: :user})
500 def follow_requests(%{assigns: %{user: followed}} = conn, _params) do
501 follow_requests = User.get_follow_requests(followed)
504 |> put_view(AccountView)
505 |> render("accounts.json", %{for: followed, users: follow_requests, as: :user})
508 def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
509 with %User{} = follower <- User.get_cached_by_id(id),
510 {:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do
512 |> put_view(AccountView)
513 |> render("relationship.json", %{user: followed, target: follower})
517 |> put_status(:forbidden)
518 |> json(%{error: message})
522 def reject_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
523 with %User{} = follower <- User.get_cached_by_id(id),
524 {:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
526 |> put_view(AccountView)
527 |> render("relationship.json", %{user: followed, target: follower})
531 |> put_status(:forbidden)
532 |> json(%{error: message})
536 def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
537 with {_, %User{} = followed} <- {:followed, User.get_cached_by_id(id)},
538 {_, true} <- {:followed, follower.id != followed.id},
539 {:ok, follower} <- MastodonAPI.follow(follower, followed, conn.params) do
541 |> put_view(AccountView)
542 |> render("relationship.json", %{user: follower, target: followed})
549 |> put_status(:forbidden)
550 |> json(%{error: message})
554 def follow(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
555 with {_, %User{} = followed} <- {:followed, User.get_cached_by_nickname(uri)},
556 {_, true} <- {:followed, follower.id != followed.id},
557 {:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do
559 |> put_view(AccountView)
560 |> render("account.json", %{user: followed, for: follower})
567 |> put_status(:forbidden)
568 |> json(%{error: message})
572 def unfollow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
573 with {_, %User{} = followed} <- {:followed, User.get_cached_by_id(id)},
574 {_, true} <- {:followed, follower.id != followed.id},
575 {:ok, follower} <- CommonAPI.unfollow(follower, followed) do
577 |> put_view(AccountView)
578 |> render("relationship.json", %{user: follower, target: followed})
588 def mute(%{assigns: %{user: muter}} = conn, %{"id" => id} = params) do
590 if Map.has_key?(params, "notifications"),
591 do: params["notifications"] in [true, "True", "true", "1"],
594 with %User{} = muted <- User.get_cached_by_id(id),
595 {:ok, muter} <- User.mute(muter, muted, notifications) do
597 |> put_view(AccountView)
598 |> render("relationship.json", %{user: muter, target: muted})
602 |> put_status(:forbidden)
603 |> json(%{error: message})
607 def unmute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
608 with %User{} = muted <- User.get_cached_by_id(id),
609 {:ok, muter} <- User.unmute(muter, muted) do
611 |> put_view(AccountView)
612 |> render("relationship.json", %{user: muter, target: muted})
616 |> put_status(:forbidden)
617 |> json(%{error: message})
621 def mutes(%{assigns: %{user: user}} = conn, _) do
622 with muted_accounts <- User.muted_users(user) do
623 res = AccountView.render("accounts.json", users: muted_accounts, for: user, as: :user)
628 def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
629 with %User{} = blocked <- User.get_cached_by_id(id),
630 {:ok, blocker} <- User.block(blocker, blocked),
631 {:ok, _activity} <- ActivityPub.block(blocker, blocked) do
633 |> put_view(AccountView)
634 |> render("relationship.json", %{user: blocker, target: blocked})
638 |> put_status(:forbidden)
639 |> json(%{error: message})
643 def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
644 with %User{} = blocked <- User.get_cached_by_id(id),
645 {:ok, blocker} <- User.unblock(blocker, blocked),
646 {:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do
648 |> put_view(AccountView)
649 |> render("relationship.json", %{user: blocker, target: blocked})
653 |> put_status(:forbidden)
654 |> json(%{error: message})
658 def blocks(%{assigns: %{user: user}} = conn, _) do
659 with blocked_accounts <- User.blocked_users(user) do
660 res = AccountView.render("accounts.json", users: blocked_accounts, for: user, as: :user)
665 def subscribe(%{assigns: %{user: user}} = conn, %{"id" => id}) do
666 with %User{} = subscription_target <- User.get_cached_by_id(id),
667 {:ok, subscription_target} = User.subscribe(user, subscription_target) do
669 |> put_view(AccountView)
670 |> render("relationship.json", %{user: user, target: subscription_target})
674 |> put_status(:forbidden)
675 |> json(%{error: message})
679 def unsubscribe(%{assigns: %{user: user}} = conn, %{"id" => id}) do
680 with %User{} = subscription_target <- User.get_cached_by_id(id),
681 {:ok, subscription_target} = User.unsubscribe(user, subscription_target) do
683 |> put_view(AccountView)
684 |> render("relationship.json", %{user: user, target: subscription_target})
688 |> put_status(:forbidden)
689 |> json(%{error: message})
693 def favourites(%{assigns: %{user: user}} = conn, params) do
696 |> Map.put("type", "Create")
697 |> Map.put("favorited_by", user.ap_id)
698 |> Map.put("blocking_user", user)
701 ActivityPub.fetch_activities([], params)
705 |> add_link_headers(activities)
706 |> put_view(StatusView)
707 |> render("index.json", %{activities: activities, for: user, as: :activity})
710 def user_favourites(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
711 with %User{} = user <- User.get_by_id(id),
712 false <- user.info.hide_favorites do
715 |> Map.put("type", "Create")
716 |> Map.put("favorited_by", user.ap_id)
717 |> Map.put("blocking_user", for_user)
721 [Pleroma.Constants.as_public()] ++ [for_user.ap_id | for_user.following]
723 [Pleroma.Constants.as_public()]
728 |> ActivityPub.fetch_activities(params)
732 |> add_link_headers(activities)
733 |> put_view(StatusView)
734 |> render("index.json", %{activities: activities, for: for_user, as: :activity})
736 nil -> {:error, :not_found}
737 true -> render_error(conn, :forbidden, "Can't get favorites")
741 def bookmarks(%{assigns: %{user: user}} = conn, params) do
742 user = User.get_cached_by_id(user.id)
745 Bookmark.for_user_query(user.id)
746 |> Pagination.fetch_paginated(params)
750 |> Enum.map(fn b -> Map.put(b.activity, :bookmark, Map.delete(b, :activity)) end)
753 |> add_link_headers(bookmarks)
754 |> put_view(StatusView)
755 |> render("index.json", %{activities: activities, for: user, as: :activity})
758 def account_lists(%{assigns: %{user: user}} = conn, %{"id" => account_id}) do
759 lists = Pleroma.List.get_lists_account_belongs(user, account_id)
760 res = ListView.render("lists.json", lists: lists)
764 def index(%{assigns: %{user: user}} = conn, _params) do
765 token = get_session(conn, :oauth_token)
768 mastodon_emoji = mastodonized_emoji()
770 limit = Config.get([:instance, :limit])
773 Map.put(%{}, user.id, AccountView.render("account.json", %{user: user, for: user}))
778 streaming_api_base_url: Pleroma.Web.Endpoint.websocket_url(),
781 domain: Pleroma.Web.Endpoint.host(),
784 unfollow_modal: false,
787 auto_play_gif: false,
788 display_sensitive_media: false,
789 reduce_motion: false,
790 max_toot_chars: limit,
791 mascot: User.get_mascot(user)["url"]
793 poll_limits: Config.get([:instance, :poll_limits]),
795 delete_others_notice: present?(user.info.is_moderator),
796 admin: present?(user.info.is_admin)
800 default_privacy: user.info.default_scope,
801 default_sensitive: false,
802 allow_content_types: Config.get([:instance, :allowed_post_formats])
804 media_attachments: %{
805 accept_content_types: [
821 user.info.settings ||
851 push_subscription: nil,
853 custom_emojis: mastodon_emoji,
860 |> put_view(MastodonView)
861 |> render("index.html", %{initial_state: initial_state})
864 |> put_session(:return_to, conn.request_path)
865 |> redirect(to: "/web/login")
869 def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do
870 with {:ok, _} <- User.update_info(user, &User.Info.mastodon_settings_update(&1, settings)) do
875 |> put_status(:internal_server_error)
876 |> json(%{error: inspect(e)})
880 def login(%{assigns: %{user: %User{}}} = conn, _params) do
881 redirect(conn, to: local_mastodon_root_path(conn))
884 @doc "Local Mastodon FE login init action"
885 def login(conn, %{"code" => auth_token}) do
886 with {:ok, app} <- get_or_make_app(),
887 %Authorization{} = auth <- Repo.get_by(Authorization, token: auth_token, app_id: app.id),
888 {:ok, token} <- Token.exchange_token(app, auth) do
890 |> put_session(:oauth_token, token.token)
891 |> redirect(to: local_mastodon_root_path(conn))
895 @doc "Local Mastodon FE callback action"
896 def login(conn, _) do
897 with {:ok, app} <- get_or_make_app() do
902 response_type: "code",
903 client_id: app.client_id,
905 scope: Enum.join(app.scopes, " ")
908 redirect(conn, to: path)
912 defp local_mastodon_root_path(conn) do
913 case get_session(conn, :return_to) do
915 mastodon_api_path(conn, :index, ["getting-started"])
918 delete_session(conn, :return_to)
923 defp get_or_make_app do
924 find_attrs = %{client_name: @local_mastodon_name, redirect_uris: "."}
925 scopes = ["read", "write", "follow", "push"]
927 with %App{} = app <- Repo.get_by(App, find_attrs) do
929 if app.scopes == scopes do
933 |> Changeset.change(%{scopes: scopes})
941 App.register_changeset(
943 Map.put(find_attrs, :scopes, scopes)
950 def logout(conn, _) do
956 def relationship_noop(%{assigns: %{user: user}} = conn, %{"id" => id}) do
957 Logger.debug("Unimplemented, returning unmodified relationship")
959 with %User{} = target <- User.get_cached_by_id(id) do
961 |> put_view(AccountView)
962 |> render("relationship.json", %{user: user, target: target})
966 def empty_array(conn, _) do
967 Logger.debug("Unimplemented, returning an empty array")
971 def empty_object(conn, _) do
972 Logger.debug("Unimplemented, returning an empty object")
976 def suggestions(%{assigns: %{user: user}} = conn, _) do
977 suggestions = Config.get(:suggestions)
979 if Keyword.get(suggestions, :enabled, false) do
980 api = Keyword.get(suggestions, :third_party_engine, "")
981 timeout = Keyword.get(suggestions, :timeout, 5000)
982 limit = Keyword.get(suggestions, :limit, 23)
984 host = Config.get([Pleroma.Web.Endpoint, :url, :host])
990 |> String.replace("{{host}}", host)
991 |> String.replace("{{user}}", user)
993 with {:ok, %{status: 200, body: body}} <-
994 HTTP.get(url, [], adapter: [recv_timeout: timeout, pool: :default]),
995 {:ok, data} <- Jason.decode(body) do
998 |> Enum.slice(0, limit)
1001 |> Map.put("id", fetch_suggestion_id(x))
1002 |> Map.put("avatar", MediaProxy.url(x["avatar"]))
1003 |> Map.put("avatar_static", MediaProxy.url(x["avatar_static"]))
1009 Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}")
1016 defp fetch_suggestion_id(attrs) do
1017 case User.get_or_fetch(attrs["acct"]) do
1018 {:ok, %User{id: id}} -> id
1023 def reports(%{assigns: %{user: user}} = conn, params) do
1024 case CommonAPI.report(user, params) do
1027 |> put_view(ReportView)
1028 |> try_render("report.json", %{activity: activity})
1032 |> put_status(:bad_request)
1033 |> json(%{error: err})
1037 def account_register(
1038 %{assigns: %{app: app}} = conn,
1039 %{"username" => nickname, "email" => _, "password" => _, "agreement" => true} = params
1047 "captcha_answer_data",
1051 |> Map.put("nickname", nickname)
1052 |> Map.put("fullname", params["fullname"] || nickname)
1053 |> Map.put("bio", params["bio"] || "")
1054 |> Map.put("confirm", params["password"])
1056 with {:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true),
1057 {:ok, token} <- Token.create_token(app, user, %{scopes: app.scopes}) do
1059 token_type: "Bearer",
1060 access_token: token.token,
1062 created_at: Token.Utils.format_created_at(token)
1067 |> put_status(:bad_request)
1072 def account_register(%{assigns: %{app: _app}} = conn, _params) do
1073 render_error(conn, :bad_request, "Missing parameters")
1076 def account_register(conn, _) do
1077 render_error(conn, :forbidden, "Invalid credentials")
1080 def conversations(%{assigns: %{user: user}} = conn, params) do
1081 participations = Participation.for_user_with_last_activity_id(user, params)
1084 Enum.map(participations, fn participation ->
1085 ConversationView.render("participation.json", %{participation: participation, for: user})
1089 |> add_link_headers(participations)
1090 |> json(conversations)
1093 def conversation_read(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do
1094 with %Participation{} = participation <-
1095 Repo.get_by(Participation, id: participation_id, user_id: user.id),
1096 {:ok, participation} <- Participation.mark_as_read(participation) do
1097 participation_view =
1098 ConversationView.render("participation.json", %{participation: participation, for: user})
1101 |> json(participation_view)
1105 def password_reset(conn, params) do
1106 nickname_or_email = params["email"] || params["nickname"]
1108 with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do
1110 |> put_status(:no_content)
1113 {:error, "unknown user"} ->
1114 send_resp(conn, :not_found, "")
1117 send_resp(conn, :bad_request, "")
1121 def account_confirmation_resend(conn, params) do
1122 nickname_or_email = params["email"] || params["nickname"]
1124 with %User{} = user <- User.get_by_nickname_or_email(nickname_or_email),
1125 {:ok, _} <- User.try_send_confirmation_email(user) do
1127 |> json_response(:no_content, "")
1131 def try_render(conn, target, params)
1132 when is_binary(target) do
1133 case render(conn, target, params) do
1134 nil -> render_error(conn, :not_implemented, "Can't display this activity")
1139 def try_render(conn, _, _) do
1140 render_error(conn, :not_implemented, "Can't display this activity")
1143 defp present?(nil), do: false
1144 defp present?(false), do: false
1145 defp present?(_), do: true