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 domain_blocks(%{assigns: %{user: %{info: info}}} = conn, _) do
666 json(conn, info.domain_blocks || [])
669 def block_domain(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do
670 User.block_domain(blocker, domain)
674 def unblock_domain(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do
675 User.unblock_domain(blocker, domain)
679 def subscribe(%{assigns: %{user: user}} = conn, %{"id" => id}) do
680 with %User{} = subscription_target <- User.get_cached_by_id(id),
681 {:ok, subscription_target} = User.subscribe(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 unsubscribe(%{assigns: %{user: user}} = conn, %{"id" => id}) do
694 with %User{} = subscription_target <- User.get_cached_by_id(id),
695 {:ok, subscription_target} = User.unsubscribe(user, subscription_target) do
697 |> put_view(AccountView)
698 |> render("relationship.json", %{user: user, target: subscription_target})
702 |> put_status(:forbidden)
703 |> json(%{error: message})
707 def favourites(%{assigns: %{user: user}} = conn, params) do
710 |> Map.put("type", "Create")
711 |> Map.put("favorited_by", user.ap_id)
712 |> Map.put("blocking_user", user)
715 ActivityPub.fetch_activities([], params)
719 |> add_link_headers(activities)
720 |> put_view(StatusView)
721 |> render("index.json", %{activities: activities, for: user, as: :activity})
724 def user_favourites(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
725 with %User{} = user <- User.get_by_id(id),
726 false <- user.info.hide_favorites do
729 |> Map.put("type", "Create")
730 |> Map.put("favorited_by", user.ap_id)
731 |> Map.put("blocking_user", for_user)
735 [Pleroma.Constants.as_public()] ++ [for_user.ap_id | for_user.following]
737 [Pleroma.Constants.as_public()]
742 |> ActivityPub.fetch_activities(params)
746 |> add_link_headers(activities)
747 |> put_view(StatusView)
748 |> render("index.json", %{activities: activities, for: for_user, as: :activity})
750 nil -> {:error, :not_found}
751 true -> render_error(conn, :forbidden, "Can't get favorites")
755 def bookmarks(%{assigns: %{user: user}} = conn, params) do
756 user = User.get_cached_by_id(user.id)
759 Bookmark.for_user_query(user.id)
760 |> Pagination.fetch_paginated(params)
764 |> Enum.map(fn b -> Map.put(b.activity, :bookmark, Map.delete(b, :activity)) end)
767 |> add_link_headers(bookmarks)
768 |> put_view(StatusView)
769 |> render("index.json", %{activities: activities, for: user, as: :activity})
772 def account_lists(%{assigns: %{user: user}} = conn, %{"id" => account_id}) do
773 lists = Pleroma.List.get_lists_account_belongs(user, account_id)
774 res = ListView.render("lists.json", lists: lists)
778 def index(%{assigns: %{user: user}} = conn, _params) do
779 token = get_session(conn, :oauth_token)
782 mastodon_emoji = mastodonized_emoji()
784 limit = Config.get([:instance, :limit])
787 Map.put(%{}, user.id, AccountView.render("account.json", %{user: user, for: user}))
792 streaming_api_base_url: Pleroma.Web.Endpoint.websocket_url(),
795 domain: Pleroma.Web.Endpoint.host(),
798 unfollow_modal: false,
801 auto_play_gif: false,
802 display_sensitive_media: false,
803 reduce_motion: false,
804 max_toot_chars: limit,
805 mascot: User.get_mascot(user)["url"]
807 poll_limits: Config.get([:instance, :poll_limits]),
809 delete_others_notice: present?(user.info.is_moderator),
810 admin: present?(user.info.is_admin)
814 default_privacy: user.info.default_scope,
815 default_sensitive: false,
816 allow_content_types: Config.get([:instance, :allowed_post_formats])
818 media_attachments: %{
819 accept_content_types: [
835 user.info.settings ||
865 push_subscription: nil,
867 custom_emojis: mastodon_emoji,
874 |> put_view(MastodonView)
875 |> render("index.html", %{initial_state: initial_state})
878 |> put_session(:return_to, conn.request_path)
879 |> redirect(to: "/web/login")
883 def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do
884 with {:ok, _} <- User.update_info(user, &User.Info.mastodon_settings_update(&1, settings)) do
889 |> put_status(:internal_server_error)
890 |> json(%{error: inspect(e)})
894 def login(%{assigns: %{user: %User{}}} = conn, _params) do
895 redirect(conn, to: local_mastodon_root_path(conn))
898 @doc "Local Mastodon FE login init action"
899 def login(conn, %{"code" => auth_token}) do
900 with {:ok, app} <- get_or_make_app(),
901 %Authorization{} = auth <- Repo.get_by(Authorization, token: auth_token, app_id: app.id),
902 {:ok, token} <- Token.exchange_token(app, auth) do
904 |> put_session(:oauth_token, token.token)
905 |> redirect(to: local_mastodon_root_path(conn))
909 @doc "Local Mastodon FE callback action"
910 def login(conn, _) do
911 with {:ok, app} <- get_or_make_app() do
916 response_type: "code",
917 client_id: app.client_id,
919 scope: Enum.join(app.scopes, " ")
922 redirect(conn, to: path)
926 defp local_mastodon_root_path(conn) do
927 case get_session(conn, :return_to) do
929 mastodon_api_path(conn, :index, ["getting-started"])
932 delete_session(conn, :return_to)
937 defp get_or_make_app do
938 find_attrs = %{client_name: @local_mastodon_name, redirect_uris: "."}
939 scopes = ["read", "write", "follow", "push"]
941 with %App{} = app <- Repo.get_by(App, find_attrs) do
943 if app.scopes == scopes do
947 |> Changeset.change(%{scopes: scopes})
955 App.register_changeset(
957 Map.put(find_attrs, :scopes, scopes)
964 def logout(conn, _) do
970 def relationship_noop(%{assigns: %{user: user}} = conn, %{"id" => id}) do
971 Logger.debug("Unimplemented, returning unmodified relationship")
973 with %User{} = target <- User.get_cached_by_id(id) do
975 |> put_view(AccountView)
976 |> render("relationship.json", %{user: user, target: target})
980 def empty_array(conn, _) do
981 Logger.debug("Unimplemented, returning an empty array")
985 def empty_object(conn, _) do
986 Logger.debug("Unimplemented, returning an empty object")
990 def suggestions(%{assigns: %{user: user}} = conn, _) do
991 suggestions = Config.get(:suggestions)
993 if Keyword.get(suggestions, :enabled, false) do
994 api = Keyword.get(suggestions, :third_party_engine, "")
995 timeout = Keyword.get(suggestions, :timeout, 5000)
996 limit = Keyword.get(suggestions, :limit, 23)
998 host = Config.get([Pleroma.Web.Endpoint, :url, :host])
1000 user = user.nickname
1004 |> String.replace("{{host}}", host)
1005 |> String.replace("{{user}}", user)
1007 with {:ok, %{status: 200, body: body}} <-
1008 HTTP.get(url, [], adapter: [recv_timeout: timeout, pool: :default]),
1009 {:ok, data} <- Jason.decode(body) do
1012 |> Enum.slice(0, limit)
1015 |> Map.put("id", fetch_suggestion_id(x))
1016 |> Map.put("avatar", MediaProxy.url(x["avatar"]))
1017 |> Map.put("avatar_static", MediaProxy.url(x["avatar_static"]))
1023 Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}")
1030 defp fetch_suggestion_id(attrs) do
1031 case User.get_or_fetch(attrs["acct"]) do
1032 {:ok, %User{id: id}} -> id
1037 def reports(%{assigns: %{user: user}} = conn, params) do
1038 case CommonAPI.report(user, params) do
1041 |> put_view(ReportView)
1042 |> try_render("report.json", %{activity: activity})
1046 |> put_status(:bad_request)
1047 |> json(%{error: err})
1051 def account_register(
1052 %{assigns: %{app: app}} = conn,
1053 %{"username" => nickname, "email" => _, "password" => _, "agreement" => true} = params
1061 "captcha_answer_data",
1065 |> Map.put("nickname", nickname)
1066 |> Map.put("fullname", params["fullname"] || nickname)
1067 |> Map.put("bio", params["bio"] || "")
1068 |> Map.put("confirm", params["password"])
1070 with {:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true),
1071 {:ok, token} <- Token.create_token(app, user, %{scopes: app.scopes}) do
1073 token_type: "Bearer",
1074 access_token: token.token,
1076 created_at: Token.Utils.format_created_at(token)
1081 |> put_status(:bad_request)
1086 def account_register(%{assigns: %{app: _app}} = conn, _params) do
1087 render_error(conn, :bad_request, "Missing parameters")
1090 def account_register(conn, _) do
1091 render_error(conn, :forbidden, "Invalid credentials")
1094 def conversations(%{assigns: %{user: user}} = conn, params) do
1095 participations = Participation.for_user_with_last_activity_id(user, params)
1098 Enum.map(participations, fn participation ->
1099 ConversationView.render("participation.json", %{participation: participation, for: user})
1103 |> add_link_headers(participations)
1104 |> json(conversations)
1107 def conversation_read(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do
1108 with %Participation{} = participation <-
1109 Repo.get_by(Participation, id: participation_id, user_id: user.id),
1110 {:ok, participation} <- Participation.mark_as_read(participation) do
1111 participation_view =
1112 ConversationView.render("participation.json", %{participation: participation, for: user})
1115 |> json(participation_view)
1119 def password_reset(conn, params) do
1120 nickname_or_email = params["email"] || params["nickname"]
1122 with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do
1124 |> put_status(:no_content)
1127 {:error, "unknown user"} ->
1128 send_resp(conn, :not_found, "")
1131 send_resp(conn, :bad_request, "")
1135 def account_confirmation_resend(conn, params) do
1136 nickname_or_email = params["email"] || params["nickname"]
1138 with %User{} = user <- User.get_by_nickname_or_email(nickname_or_email),
1139 {:ok, _} <- User.try_send_confirmation_email(user) do
1141 |> json_response(:no_content, "")
1145 def try_render(conn, target, params)
1146 when is_binary(target) do
1147 case render(conn, target, params) do
1148 nil -> render_error(conn, :not_implemented, "Can't display this activity")
1153 def try_render(conn, _, _) do
1154 render_error(conn, :not_implemented, "Can't display this activity")
1157 defp present?(nil), do: false
1158 defp present?(false), do: false
1159 defp present?(_), do: true