Merge branch 'develop' of https://git.pleroma.social/pleroma/pleroma into develop
[akkoma] / lib / pleroma / web / mastodon_api / controllers / mastodon_api_controller.ex
index 8f6b3456a30f8ceed80e31a46d2263d6f17a77a4..14075307de35b0f154a9ec1ea723823f3353e241 100644 (file)
 # Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   use Pleroma.Web, :controller
 
-  import Pleroma.Web.ControllerHelper,
-    only: [json_response: 3, add_link_headers: 2, add_link_headers: 3]
-
-  alias Ecto.Changeset
-  alias Pleroma.Activity
-  alias Pleroma.Bookmark
-  alias Pleroma.Config
-  alias Pleroma.Conversation.Participation
-  alias Pleroma.Emoji
-  alias Pleroma.Filter
-  alias Pleroma.HTTP
-  alias Pleroma.Object
-  alias Pleroma.Pagination
-  alias Pleroma.Plugs.RateLimiter
-  alias Pleroma.Repo
-  alias Pleroma.ScheduledActivity
-  alias Pleroma.Stats
-  alias Pleroma.User
-  alias Pleroma.Web
-  alias Pleroma.Web.ActivityPub.ActivityPub
-  alias Pleroma.Web.ActivityPub.Visibility
-  alias Pleroma.Web.CommonAPI
-  alias Pleroma.Web.MastodonAPI.AccountView
-  alias Pleroma.Web.MastodonAPI.AppView
-  alias Pleroma.Web.MastodonAPI.ConversationView
-  alias Pleroma.Web.MastodonAPI.FilterView
-  alias Pleroma.Web.MastodonAPI.ListView
-  alias Pleroma.Web.MastodonAPI.MastodonAPI
-  alias Pleroma.Web.MastodonAPI.MastodonView
-  alias Pleroma.Web.MastodonAPI.ReportView
-  alias Pleroma.Web.MastodonAPI.ScheduledActivityView
-  alias Pleroma.Web.MastodonAPI.StatusView
-  alias Pleroma.Web.MediaProxy
-  alias Pleroma.Web.OAuth.App
-  alias Pleroma.Web.OAuth.Authorization
-  alias Pleroma.Web.OAuth.Scopes
-  alias Pleroma.Web.OAuth.Token
-  alias Pleroma.Web.RichMedia
-  alias Pleroma.Web.TwitterAPI.TwitterAPI
-
-  alias Pleroma.Web.ControllerHelper
-  import Ecto.Query
-
   require Logger
-  require Pleroma.Constants
-
-  @rate_limited_relations_actions ~w(follow unfollow)a
-
-  @rate_limited_status_actions ~w(reblog_status unreblog_status fav_status unfav_status
-    post_status delete_status)a
-
-  plug(
-    RateLimiter,
-    {:status_id_action, bucket_name: "status_id_action:reblog_unreblog", params: ["id"]}
-    when action in ~w(reblog_status unreblog_status)a
-  )
-
-  plug(
-    RateLimiter,
-    {:status_id_action, bucket_name: "status_id_action:fav_unfav", params: ["id"]}
-    when action in ~w(fav_status unfav_status)a
-  )
-
-  plug(
-    RateLimiter,
-    {:relations_id_action, params: ["id", "uri"]} when action in @rate_limited_relations_actions
-  )
-
-  plug(RateLimiter, :relations_actions when action in @rate_limited_relations_actions)
-  plug(RateLimiter, :statuses_actions when action in @rate_limited_status_actions)
-  plug(RateLimiter, :app_account_creation when action == :account_register)
-  plug(RateLimiter, :search when action in [:search, :search2, :account_search])
-  plug(RateLimiter, :password_reset when action == :password_reset)
-  plug(RateLimiter, :account_confirmation_resend when action == :account_confirmation_resend)
-
-  @local_mastodon_name "Mastodon-Local"
 
   action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
 
-  def create_app(conn, params) do
-    scopes = Scopes.fetch_scopes(params, ["read"])
-
-    app_attrs =
-      params
-      |> Map.drop(["scope", "scopes"])
-      |> Map.put("scopes", scopes)
-
-    with cs <- App.register_changeset(%App{}, app_attrs),
-         false <- cs.changes[:client_name] == @local_mastodon_name,
-         {:ok, app} <- Repo.insert(cs) do
-      conn
-      |> put_view(AppView)
-      |> render("show.json", %{app: app})
-    end
-  end
-
-  defp add_if_present(
-         map,
-         params,
-         params_field,
-         map_field,
-         value_function \\ fn x -> {:ok, x} end
-       ) do
-    if Map.has_key?(params, params_field) do
-      case value_function.(params[params_field]) do
-        {:ok, new_value} -> Map.put(map, map_field, new_value)
-        :error -> map
-      end
-    else
-      map
-    end
-  end
-
-  def update_credentials(%{assigns: %{user: user}} = conn, params) do
-    original_user = user
-
-    user_params =
-      %{}
-      |> add_if_present(params, "display_name", :name)
-      |> add_if_present(params, "note", :bio, fn value -> {:ok, User.parse_bio(value, user)} end)
-      |> add_if_present(params, "avatar", :avatar, fn value ->
-        with %Plug.Upload{} <- value,
-             {:ok, object} <- ActivityPub.upload(value, type: :avatar) do
-          {:ok, object.data}
-        else
-          _ -> :error
-        end
-      end)
-
-    emojis_text = (user_params["display_name"] || "") <> (user_params["note"] || "")
-
-    user_info_emojis =
-      user.info
-      |> Map.get(:emoji, [])
-      |> Enum.concat(Emoji.Formatter.get_emoji_map(emojis_text))
-      |> Enum.dedup()
-
-    info_params =
-      [
-        :no_rich_text,
-        :locked,
-        :hide_followers_count,
-        :hide_follows_count,
-        :hide_followers,
-        :hide_follows,
-        :hide_favorites,
-        :show_role,
-        :skip_thread_containment,
-        :discoverable
-      ]
-      |> Enum.reduce(%{}, fn key, acc ->
-        add_if_present(acc, params, to_string(key), key, fn value ->
-          {:ok, ControllerHelper.truthy_param?(value)}
-        end)
-      end)
-      |> add_if_present(params, "default_scope", :default_scope)
-      |> add_if_present(params, "fields", :fields, fn fields ->
-        fields = Enum.map(fields, fn f -> Map.update!(f, "value", &AutoLinker.link(&1)) end)
-
-        {:ok, fields}
-      end)
-      |> add_if_present(params, "fields", :raw_fields)
-      |> add_if_present(params, "pleroma_settings_store", :pleroma_settings_store, fn value ->
-        {:ok, Map.merge(user.info.pleroma_settings_store, value)}
-      end)
-      |> add_if_present(params, "header", :banner, fn value ->
-        with %Plug.Upload{} <- value,
-             {:ok, object} <- ActivityPub.upload(value, type: :banner) do
-          {:ok, object.data}
-        else
-          _ -> :error
-        end
-      end)
-      |> add_if_present(params, "pleroma_background_image", :background, fn value ->
-        with %Plug.Upload{} <- value,
-             {:ok, object} <- ActivityPub.upload(value, type: :background) do
-          {:ok, object.data}
-        else
-          _ -> :error
-        end
-      end)
-      |> Map.put(:emoji, user_info_emojis)
-
-    changeset =
-      user
-      |> User.update_changeset(user_params)
-      |> User.change_info(&User.Info.profile_update(&1, info_params))
-
-    with {:ok, user} <- User.update_and_set_cache(changeset) do
-      if original_user != user, do: CommonAPI.update(user)
-
-      json(
-        conn,
-        AccountView.render("account.json", %{user: user, for: user, with_pleroma_settings: true})
-      )
-    else
-      _e -> render_error(conn, :forbidden, "Invalid request")
-    end
-  end
-
-  def update_avatar(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
-    change = Changeset.change(user, %{avatar: nil})
-    {:ok, user} = User.update_and_set_cache(change)
-    CommonAPI.update(user)
-
-    json(conn, %{url: nil})
-  end
-
-  def update_avatar(%{assigns: %{user: user}} = conn, params) do
-    {:ok, object} = ActivityPub.upload(params, type: :avatar)
-    change = Changeset.change(user, %{avatar: object.data})
-    {:ok, user} = User.update_and_set_cache(change)
-    CommonAPI.update(user)
-    %{"url" => [%{"href" => href} | _]} = object.data
-
-    json(conn, %{url: href})
-  end
-
-  def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do
-    new_info = %{"banner" => %{}}
-
-    with {:ok, user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
-      CommonAPI.update(user)
-      json(conn, %{url: nil})
-    end
-  end
-
-  def update_banner(%{assigns: %{user: user}} = conn, params) do
-    with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner),
-         new_info <- %{"banner" => object.data},
-         {:ok, user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
-      CommonAPI.update(user)
-      %{"url" => [%{"href" => href} | _]} = object.data
-
-      json(conn, %{url: href})
-    end
-  end
-
-  def update_background(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
-    new_info = %{"background" => %{}}
-
-    with {:ok, _user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
-      json(conn, %{url: nil})
-    end
-  end
-
-  def update_background(%{assigns: %{user: user}} = conn, params) do
-    with {:ok, object} <- ActivityPub.upload(params, type: :background),
-         new_info <- %{"background" => object.data},
-         {:ok, _user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
-      %{"url" => [%{"href" => href} | _]} = object.data
-
-      json(conn, %{url: href})
-    end
-  end
-
-  def verify_credentials(%{assigns: %{user: user}} = conn, _) do
-    chat_token = Phoenix.Token.sign(conn, "user socket", user.id)
-
-    account =
-      AccountView.render("account.json", %{
-        user: user,
-        for: user,
-        with_pleroma_settings: true,
-        with_chat_token: chat_token
-      })
-
-    json(conn, account)
-  end
-
-  def verify_app_credentials(%{assigns: %{user: _user, token: token}} = conn, _) do
-    with %Token{app: %App{} = app} <- Repo.preload(token, :app) do
-      conn
-      |> put_view(AppView)
-      |> render("short.json", %{app: app})
-    end
-  end
-
-  def user(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do
-    with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id, for: for_user),
-         true <- User.auth_active?(user) || user.id == for_user.id || User.superuser?(for_user) do
-      account = AccountView.render("account.json", %{user: user, for: for_user})
-      json(conn, account)
-    else
-      _e -> render_error(conn, :not_found, "Can't find user")
-    end
-  end
-
-  @mastodon_api_level "2.7.2"
-
-  def masto_instance(conn, _params) do
-    instance = Config.get(:instance)
-
-    response = %{
-      uri: Web.base_url(),
-      title: Keyword.get(instance, :name),
-      description: Keyword.get(instance, :description),
-      version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})",
-      email: Keyword.get(instance, :email),
-      urls: %{
-        streaming_api: Pleroma.Web.Endpoint.websocket_url()
-      },
-      stats: Stats.get_stats(),
-      thumbnail: Web.base_url() <> "/instance/thumbnail.jpeg",
-      languages: ["en"],
-      registrations: Pleroma.Config.get([:instance, :registrations_open]),
-      # Extra (not present in Mastodon):
-      max_toot_chars: Keyword.get(instance, :limit),
-      poll_limits: Keyword.get(instance, :poll_limits)
-    }
-
-    json(conn, response)
-  end
-
-  def peers(conn, _params) do
-    json(conn, Stats.get_peers())
-  end
-
-  defp mastodonized_emoji do
-    Pleroma.Emoji.get_all()
-    |> Enum.map(fn {shortcode, %Pleroma.Emoji{file: relative_url, tags: tags}} ->
-      url = to_string(URI.merge(Web.base_url(), relative_url))
-
-      %{
-        "shortcode" => shortcode,
-        "static_url" => url,
-        "visible_in_picker" => true,
-        "url" => url,
-        "tags" => tags,
-        # Assuming that a comma is authorized in the category name
-        "category" => (tags -- ["Custom"]) |> Enum.join(",")
-      }
-    end)
-  end
-
-  def custom_emojis(conn, _params) do
-    mastodon_emoji = mastodonized_emoji()
-    json(conn, mastodon_emoji)
-  end
-
-  def home_timeline(%{assigns: %{user: user}} = conn, params) do
-    params =
-      params
-      |> Map.put("type", ["Create", "Announce"])
-      |> Map.put("blocking_user", user)
-      |> Map.put("muting_user", user)
-      |> Map.put("user", user)
-
-    activities =
-      [user.ap_id | user.following]
-      |> ActivityPub.fetch_activities(params)
-      |> Enum.reverse()
-
-    conn
-    |> add_link_headers(activities)
-    |> put_view(StatusView)
-    |> render("index.json", %{activities: activities, for: user, as: :activity})
-  end
-
-  def public_timeline(%{assigns: %{user: user}} = conn, params) do
-    local_only = params["local"] in [true, "True", "true", "1"]
-
-    activities =
-      params
-      |> Map.put("type", ["Create", "Announce"])
-      |> Map.put("local_only", local_only)
-      |> Map.put("blocking_user", user)
-      |> Map.put("muting_user", user)
-      |> ActivityPub.fetch_public_activities()
-      |> Enum.reverse()
-
-    conn
-    |> add_link_headers(activities, %{"local" => local_only})
-    |> put_view(StatusView)
-    |> render("index.json", %{activities: activities, for: user, as: :activity})
-  end
-
-  def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do
-    with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user) do
-      params =
-        params
-        |> Map.put("tag", params["tagged"])
-
-      activities = ActivityPub.fetch_user_activities(user, reading_user, params)
-
-      conn
-      |> add_link_headers(activities)
-      |> put_view(StatusView)
-      |> render("index.json", %{
-        activities: activities,
-        for: reading_user,
-        as: :activity
-      })
-    end
-  end
-
-  def dm_timeline(%{assigns: %{user: user}} = conn, params) do
-    params =
-      params
-      |> Map.put("type", "Create")
-      |> Map.put("blocking_user", user)
-      |> Map.put("user", user)
-      |> Map.put(:visibility, "direct")
-
-    activities =
-      [user.ap_id]
-      |> ActivityPub.fetch_activities_query(params)
-      |> Pagination.fetch_paginated(params)
-
-    conn
-    |> add_link_headers(activities)
-    |> put_view(StatusView)
-    |> render("index.json", %{activities: activities, for: user, as: :activity})
-  end
-
-  def get_statuses(%{assigns: %{user: user}} = conn, %{"ids" => ids}) do
-    limit = 100
-
-    activities =
-      ids
-      |> Enum.take(limit)
-      |> Activity.all_by_ids_with_object()
-      |> Enum.filter(&Visibility.visible_for_user?(&1, user))
-
-    conn
-    |> put_view(StatusView)
-    |> render("index.json", activities: activities, for: user, as: :activity)
-  end
-
-  def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
-    with %Activity{} = activity <- Activity.get_by_id_with_object(id),
-         true <- Visibility.visible_for_user?(activity, user) do
-      conn
-      |> put_view(StatusView)
-      |> try_render("status.json", %{activity: activity, for: user})
-    end
-  end
-
-  def get_context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
-    with %Activity{} = activity <- Activity.get_by_id(id),
-         activities <-
-           ActivityPub.fetch_activities_for_context(activity.data["context"], %{
-             "blocking_user" => user,
-             "user" => user,
-             "exclude_id" => activity.id
-           }),
-         grouped_activities <- Enum.group_by(activities, fn %{id: id} -> id < activity.id end) do
-      result = %{
-        ancestors:
-          StatusView.render("index.json",
-            for: user,
-            activities: grouped_activities[true] || [],
-            as: :activity
-          )
-          |> Enum.reverse(),
-        # credo:disable-for-previous-line Credo.Check.Refactor.PipeChainStart
-        descendants:
-          StatusView.render("index.json",
-            for: user,
-            activities: grouped_activities[false] || [],
-            as: :activity
-          )
-          |> Enum.reverse()
-        # credo:disable-for-previous-line Credo.Check.Refactor.PipeChainStart
-      }
-
-      json(conn, result)
-    end
-  end
-
-  def get_poll(%{assigns: %{user: user}} = conn, %{"id" => id}) do
-    with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60),
-         %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
-         true <- Visibility.visible_for_user?(activity, user) do
-      conn
-      |> put_view(StatusView)
-      |> try_render("poll.json", %{object: object, for: user})
-    else
-      error when is_nil(error) or error == false ->
-        render_error(conn, :not_found, "Record not found")
-    end
-  end
-
-  defp get_cached_vote_or_vote(user, object, choices) do
-    idempotency_key = "polls:#{user.id}:#{object.data["id"]}"
-
-    {_, res} =
-      Cachex.fetch(:idempotency_cache, idempotency_key, fn _ ->
-        case CommonAPI.vote(user, object, choices) do
-          {:error, _message} = res -> {:ignore, res}
-          res -> {:commit, res}
-        end
-      end)
-
-    res
-  end
-
-  def poll_vote(%{assigns: %{user: user}} = conn, %{"id" => id, "choices" => choices}) do
-    with %Object{} = object <- Object.get_by_id(id),
-         true <- object.data["type"] == "Question",
-         %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
-         true <- Visibility.visible_for_user?(activity, user),
-         {:ok, _activities, object} <- get_cached_vote_or_vote(user, object, choices) do
-      conn
-      |> put_view(StatusView)
-      |> try_render("poll.json", %{object: object, for: user})
-    else
-      nil ->
-        render_error(conn, :not_found, "Record not found")
-
-      false ->
-        render_error(conn, :not_found, "Record not found")
-
-      {:error, message} ->
-        conn
-        |> put_status(:unprocessable_entity)
-        |> json(%{error: message})
-    end
-  end
-
-  def scheduled_statuses(%{assigns: %{user: user}} = conn, params) do
-    with scheduled_activities <- MastodonAPI.get_scheduled_activities(user, params) do
-      conn
-      |> add_link_headers(scheduled_activities)
-      |> put_view(ScheduledActivityView)
-      |> render("index.json", %{scheduled_activities: scheduled_activities})
-    end
-  end
-
-  def show_scheduled_status(%{assigns: %{user: user}} = conn, %{"id" => scheduled_activity_id}) do
-    with %ScheduledActivity{} = scheduled_activity <-
-           ScheduledActivity.get(user, scheduled_activity_id) do
-      conn
-      |> put_view(ScheduledActivityView)
-      |> render("show.json", %{scheduled_activity: scheduled_activity})
-    else
-      _ -> {:error, :not_found}
-    end
-  end
-
-  def update_scheduled_status(
-        %{assigns: %{user: user}} = conn,
-        %{"id" => scheduled_activity_id} = params
-      ) do
-    with %ScheduledActivity{} = scheduled_activity <-
-           ScheduledActivity.get(user, scheduled_activity_id),
-         {:ok, scheduled_activity} <- ScheduledActivity.update(scheduled_activity, params) do
-      conn
-      |> put_view(ScheduledActivityView)
-      |> render("show.json", %{scheduled_activity: scheduled_activity})
-    else
-      nil -> {:error, :not_found}
-      error -> error
-    end
-  end
-
-  def delete_scheduled_status(%{assigns: %{user: user}} = conn, %{"id" => scheduled_activity_id}) do
-    with %ScheduledActivity{} = scheduled_activity <-
-           ScheduledActivity.get(user, scheduled_activity_id),
-         {:ok, scheduled_activity} <- ScheduledActivity.delete(scheduled_activity) do
-      conn
-      |> put_view(ScheduledActivityView)
-      |> render("show.json", %{scheduled_activity: scheduled_activity})
-    else
-      nil -> {:error, :not_found}
-      error -> error
-    end
-  end
-
-  def post_status(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do
-    params =
-      params
-      |> Map.put("in_reply_to_status_id", params["in_reply_to_id"])
-
-    scheduled_at = params["scheduled_at"]
-
-    if scheduled_at && ScheduledActivity.far_enough?(scheduled_at) do
-      with {:ok, scheduled_activity} <-
-             ScheduledActivity.create(user, %{"params" => params, "scheduled_at" => scheduled_at}) do
-        conn
-        |> put_view(ScheduledActivityView)
-        |> render("show.json", %{scheduled_activity: scheduled_activity})
-      end
-    else
-      params = Map.drop(params, ["scheduled_at"])
-
-      case CommonAPI.post(user, params) do
-        {:error, message} ->
-          conn
-          |> put_status(:unprocessable_entity)
-          |> json(%{error: message})
-
-        {:ok, activity} ->
-          conn
-          |> put_view(StatusView)
-          |> try_render("status.json", %{
-            activity: activity,
-            for: user,
-            as: :activity,
-            with_direct_conversation_id: true
-          })
-      end
-    end
-  end
-
-  def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
-    with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
-      json(conn, %{})
-    else
-      _e -> render_error(conn, :forbidden, "Can't delete this post")
-    end
-  end
-
-  def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
-    with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user),
-         %Activity{} = announce <- Activity.normalize(announce.data) do
-      conn
-      |> put_view(StatusView)
-      |> try_render("status.json", %{activity: announce, for: user, as: :activity})
-    end
-  end
-
-  def unreblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
-    with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user),
-         %Activity{} = activity <- Activity.get_create_by_object_ap_id_with_object(id) do
-      conn
-      |> put_view(StatusView)
-      |> try_render("status.json", %{activity: activity, for: user, as: :activity})
-    end
-  end
-
-  def fav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
-    with {:ok, _fav, %{data: %{"id" => id}}} <- CommonAPI.favorite(ap_id_or_id, user),
-         %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
-      conn
-      |> put_view(StatusView)
-      |> try_render("status.json", %{activity: activity, for: user, as: :activity})
-    end
-  end
-
-  def unfav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
-    with {:ok, _, _, %{data: %{"id" => id}}} <- CommonAPI.unfavorite(ap_id_or_id, user),
-         %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
-      conn
-      |> put_view(StatusView)
-      |> try_render("status.json", %{activity: activity, for: user, as: :activity})
-    end
-  end
-
-  def pin_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
-    with {:ok, activity} <- CommonAPI.pin(ap_id_or_id, user) do
-      conn
-      |> put_view(StatusView)
-      |> try_render("status.json", %{activity: activity, for: user, as: :activity})
-    end
-  end
-
-  def unpin_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
-    with {:ok, activity} <- CommonAPI.unpin(ap_id_or_id, user) do
-      conn
-      |> put_view(StatusView)
-      |> try_render("status.json", %{activity: activity, for: user, as: :activity})
-    end
-  end
-
-  def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
-    with %Activity{} = activity <- Activity.get_by_id_with_object(id),
-         %User{} = user <- User.get_cached_by_nickname(user.nickname),
-         true <- Visibility.visible_for_user?(activity, user),
-         {:ok, _bookmark} <- Bookmark.create(user.id, activity.id) do
-      conn
-      |> put_view(StatusView)
-      |> try_render("status.json", %{activity: activity, for: user, as: :activity})
-    end
-  end
-
-  def unbookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
-    with %Activity{} = activity <- Activity.get_by_id_with_object(id),
-         %User{} = user <- User.get_cached_by_nickname(user.nickname),
-         true <- Visibility.visible_for_user?(activity, user),
-         {:ok, _bookmark} <- Bookmark.destroy(user.id, activity.id) do
-      conn
-      |> put_view(StatusView)
-      |> try_render("status.json", %{activity: activity, for: user, as: :activity})
-    end
-  end
-
-  def mute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
-    activity = Activity.get_by_id(id)
-
-    with {:ok, activity} <- CommonAPI.add_mute(user, activity) do
-      conn
-      |> put_view(StatusView)
-      |> try_render("status.json", %{activity: activity, for: user, as: :activity})
-    end
-  end
-
-  def unmute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
-    activity = Activity.get_by_id(id)
-
-    with {:ok, activity} <- CommonAPI.remove_mute(user, activity) do
-      conn
-      |> put_view(StatusView)
-      |> try_render("status.json", %{activity: activity, for: user, as: :activity})
-    end
-  end
-
-  def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do
-    targets = User.get_all_by_ids(List.wrap(id))
-
-    conn
-    |> put_view(AccountView)
-    |> render("relationships.json", %{user: user, targets: targets})
-  end
-
-  # Instead of returning a 400 when no "id" params is present, Mastodon returns an empty array.
-  def relationships(%{assigns: %{user: _user}} = conn, _), do: json(conn, [])
-
-  def update_media(
-        %{assigns: %{user: user}} = conn,
-        %{"id" => id, "description" => description} = _
-      )
-      when is_binary(description) do
-    with %Object{} = object <- Repo.get(Object, id),
-         true <- Object.authorize_mutation(object, user),
-         {:ok, %Object{data: data}} <- Object.update_data(object, %{"name" => description}) do
-      attachment_data = Map.put(data, "id", object.id)
-
-      conn
-      |> put_view(StatusView)
-      |> render("attachment.json", %{attachment: attachment_data})
-    end
-  end
-
-  def update_media(_conn, _data), do: {:error, :bad_request}
-
-  def upload(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do
-    with {:ok, object} <-
-           ActivityPub.upload(
-             file,
-             actor: User.ap_id(user),
-             description: Map.get(data, "description")
-           ) do
-      attachment_data = Map.put(object.data, "id", object.id)
-
-      conn
-      |> put_view(StatusView)
-      |> render("attachment.json", %{attachment: attachment_data})
-    end
-  end
-
-  def set_mascot(%{assigns: %{user: user}} = conn, %{"file" => file}) do
-    with {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)),
-         %{} = attachment_data <- Map.put(object.data, "id", object.id),
-         # Reject if not an image
-         %{type: "image"} = rendered <-
-           StatusView.render("attachment.json", %{attachment: attachment_data}) do
-      # Sure!
-      # Save to the user's info
-      {:ok, _user} = User.update_info(user, &User.Info.mascot_update(&1, rendered))
-
-      json(conn, rendered)
-    else
-      %{type: _} -> render_error(conn, :unsupported_media_type, "mascots can only be images")
-    end
-  end
-
-  def get_mascot(%{assigns: %{user: user}} = conn, _params) do
-    mascot = User.get_mascot(user)
-
-    json(conn, mascot)
-  end
-
-  def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
-    with %Activity{} = activity <- Activity.get_by_id_with_object(id),
-         {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)},
-         %Object{data: %{"likes" => likes}} <- Object.normalize(activity) do
-      q = from(u in User, where: u.ap_id in ^likes)
-
-      users =
-        Repo.all(q)
-        |> Enum.filter(&(not User.blocks?(user, &1)))
-
-      conn
-      |> put_view(AccountView)
-      |> render("accounts.json", %{for: user, users: users, as: :user})
-    else
-      {:visible, false} -> {:error, :not_found}
-      _ -> json(conn, [])
-    end
-  end
-
-  def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
-    with %Activity{} = activity <- Activity.get_by_id_with_object(id),
-         {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)},
-         %Object{data: %{"announcements" => announces}} <- Object.normalize(activity) do
-      q = from(u in User, where: u.ap_id in ^announces)
-
-      users =
-        Repo.all(q)
-        |> Enum.filter(&(not User.blocks?(user, &1)))
-
-      conn
-      |> put_view(AccountView)
-      |> render("accounts.json", %{for: user, users: users, as: :user})
-    else
-      {:visible, false} -> {:error, :not_found}
-      _ -> json(conn, [])
-    end
-  end
-
-  def hashtag_timeline(%{assigns: %{user: user}} = conn, params) do
-    local_only = params["local"] in [true, "True", "true", "1"]
-
-    tags =
-      [params["tag"], params["any"]]
-      |> List.flatten()
-      |> Enum.uniq()
-      |> Enum.filter(& &1)
-      |> Enum.map(&String.downcase(&1))
-
-    tag_all =
-      params["all"] ||
-        []
-        |> Enum.map(&String.downcase(&1))
-
-    tag_reject =
-      params["none"] ||
-        []
-        |> Enum.map(&String.downcase(&1))
-
-    activities =
-      params
-      |> Map.put("type", "Create")
-      |> Map.put("local_only", local_only)
-      |> Map.put("blocking_user", user)
-      |> Map.put("muting_user", user)
-      |> Map.put("user", user)
-      |> Map.put("tag", tags)
-      |> Map.put("tag_all", tag_all)
-      |> Map.put("tag_reject", tag_reject)
-      |> ActivityPub.fetch_public_activities()
-      |> Enum.reverse()
-
-    conn
-    |> add_link_headers(activities, %{"local" => local_only})
-    |> put_view(StatusView)
-    |> render("index.json", %{activities: activities, for: user, as: :activity})
-  end
-
-  def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
-    with %User{} = user <- User.get_cached_by_id(id),
-         followers <- MastodonAPI.get_followers(user, params) do
-      followers =
-        cond do
-          for_user && user.id == for_user.id -> followers
-          user.info.hide_followers -> []
-          true -> followers
-        end
-
-      conn
-      |> add_link_headers(followers)
-      |> put_view(AccountView)
-      |> render("accounts.json", %{for: for_user, users: followers, as: :user})
-    end
-  end
-
-  def following(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
-    with %User{} = user <- User.get_cached_by_id(id),
-         followers <- MastodonAPI.get_friends(user, params) do
-      followers =
-        cond do
-          for_user && user.id == for_user.id -> followers
-          user.info.hide_follows -> []
-          true -> followers
-        end
-
-      conn
-      |> add_link_headers(followers)
-      |> put_view(AccountView)
-      |> render("accounts.json", %{for: for_user, users: followers, as: :user})
-    end
-  end
-
-  def follow_requests(%{assigns: %{user: followed}} = conn, _params) do
-    follow_requests = User.get_follow_requests(followed)
-
-    conn
-    |> put_view(AccountView)
-    |> render("accounts.json", %{for: followed, users: follow_requests, as: :user})
-  end
-
-  def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
-    with %User{} = follower <- User.get_cached_by_id(id),
-         {:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do
-      conn
-      |> put_view(AccountView)
-      |> render("relationship.json", %{user: followed, target: follower})
-    else
-      {:error, message} ->
-        conn
-        |> put_status(:forbidden)
-        |> json(%{error: message})
-    end
-  end
-
-  def reject_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
-    with %User{} = follower <- User.get_cached_by_id(id),
-         {:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
-      conn
-      |> put_view(AccountView)
-      |> render("relationship.json", %{user: followed, target: follower})
-    else
-      {:error, message} ->
-        conn
-        |> put_status(:forbidden)
-        |> json(%{error: message})
-    end
-  end
-
-  def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
-    with {_, %User{} = followed} <- {:followed, User.get_cached_by_id(id)},
-         {_, true} <- {:followed, follower.id != followed.id},
-         {:ok, follower} <- MastodonAPI.follow(follower, followed, conn.params) do
-      conn
-      |> put_view(AccountView)
-      |> render("relationship.json", %{user: follower, target: followed})
-    else
-      {:followed, _} ->
-        {:error, :not_found}
-
-      {:error, message} ->
-        conn
-        |> put_status(:forbidden)
-        |> json(%{error: message})
-    end
-  end
-
-  def follow(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
-    with {_, %User{} = followed} <- {:followed, User.get_cached_by_nickname(uri)},
-         {_, true} <- {:followed, follower.id != followed.id},
-         {:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do
-      conn
-      |> put_view(AccountView)
-      |> render("account.json", %{user: followed, for: follower})
-    else
-      {:followed, _} ->
-        {:error, :not_found}
-
-      {:error, message} ->
-        conn
-        |> put_status(:forbidden)
-        |> json(%{error: message})
-    end
-  end
-
-  def unfollow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
-    with {_, %User{} = followed} <- {:followed, User.get_cached_by_id(id)},
-         {_, true} <- {:followed, follower.id != followed.id},
-         {:ok, follower} <- CommonAPI.unfollow(follower, followed) do
-      conn
-      |> put_view(AccountView)
-      |> render("relationship.json", %{user: follower, target: followed})
-    else
-      {:followed, _} ->
-        {:error, :not_found}
-
-      error ->
-        error
-    end
-  end
-
-  def mute(%{assigns: %{user: muter}} = conn, %{"id" => id} = params) do
-    notifications =
-      if Map.has_key?(params, "notifications"),
-        do: params["notifications"] in [true, "True", "true", "1"],
-        else: true
-
-    with %User{} = muted <- User.get_cached_by_id(id),
-         {:ok, muter} <- User.mute(muter, muted, notifications) do
-      conn
-      |> put_view(AccountView)
-      |> render("relationship.json", %{user: muter, target: muted})
-    else
-      {:error, message} ->
-        conn
-        |> put_status(:forbidden)
-        |> json(%{error: message})
-    end
-  end
-
-  def unmute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
-    with %User{} = muted <- User.get_cached_by_id(id),
-         {:ok, muter} <- User.unmute(muter, muted) do
-      conn
-      |> put_view(AccountView)
-      |> render("relationship.json", %{user: muter, target: muted})
-    else
-      {:error, message} ->
-        conn
-        |> put_status(:forbidden)
-        |> json(%{error: message})
-    end
-  end
-
-  def mutes(%{assigns: %{user: user}} = conn, _) do
-    with muted_accounts <- User.muted_users(user) do
-      res = AccountView.render("accounts.json", users: muted_accounts, for: user, as: :user)
-      json(conn, res)
-    end
-  end
-
-  def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
-    with %User{} = blocked <- User.get_cached_by_id(id),
-         {:ok, blocker} <- User.block(blocker, blocked),
-         {:ok, _activity} <- ActivityPub.block(blocker, blocked) do
-      conn
-      |> put_view(AccountView)
-      |> render("relationship.json", %{user: blocker, target: blocked})
-    else
-      {:error, message} ->
-        conn
-        |> put_status(:forbidden)
-        |> json(%{error: message})
-    end
-  end
-
-  def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
-    with %User{} = blocked <- User.get_cached_by_id(id),
-         {:ok, blocker} <- User.unblock(blocker, blocked),
-         {:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do
-      conn
-      |> put_view(AccountView)
-      |> render("relationship.json", %{user: blocker, target: blocked})
-    else
-      {:error, message} ->
-        conn
-        |> put_status(:forbidden)
-        |> json(%{error: message})
-    end
-  end
-
-  def blocks(%{assigns: %{user: user}} = conn, _) do
-    with blocked_accounts <- User.blocked_users(user) do
-      res = AccountView.render("accounts.json", users: blocked_accounts, for: user, as: :user)
-      json(conn, res)
-    end
-  end
-
-  def domain_blocks(%{assigns: %{user: %{info: info}}} = conn, _) do
-    json(conn, info.domain_blocks || [])
-  end
-
-  def block_domain(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do
-    User.block_domain(blocker, domain)
-    json(conn, %{})
-  end
-
-  def unblock_domain(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do
-    User.unblock_domain(blocker, domain)
-    json(conn, %{})
-  end
-
-  def subscribe(%{assigns: %{user: user}} = conn, %{"id" => id}) do
-    with %User{} = subscription_target <- User.get_cached_by_id(id),
-         {:ok, subscription_target} = User.subscribe(user, subscription_target) do
-      conn
-      |> put_view(AccountView)
-      |> render("relationship.json", %{user: user, target: subscription_target})
-    else
-      nil -> {:error, :not_found}
-      e -> e
-    end
-  end
-
-  def unsubscribe(%{assigns: %{user: user}} = conn, %{"id" => id}) do
-    with %User{} = subscription_target <- User.get_cached_by_id(id),
-         {:ok, subscription_target} = User.unsubscribe(user, subscription_target) do
-      conn
-      |> put_view(AccountView)
-      |> render("relationship.json", %{user: user, target: subscription_target})
-    else
-      nil -> {:error, :not_found}
-      e -> e
-    end
-  end
-
-  def favourites(%{assigns: %{user: user}} = conn, params) do
-    params =
-      params
-      |> Map.put("type", "Create")
-      |> Map.put("favorited_by", user.ap_id)
-      |> Map.put("blocking_user", user)
-
-    activities =
-      ActivityPub.fetch_activities([], params)
-      |> Enum.reverse()
-
-    conn
-    |> add_link_headers(activities)
-    |> put_view(StatusView)
-    |> render("index.json", %{activities: activities, for: user, as: :activity})
-  end
-
-  def user_favourites(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
-    with %User{} = user <- User.get_by_id(id),
-         false <- user.info.hide_favorites do
-      params =
-        params
-        |> Map.put("type", "Create")
-        |> Map.put("favorited_by", user.ap_id)
-        |> Map.put("blocking_user", for_user)
-
-      recipients =
-        if for_user do
-          [Pleroma.Constants.as_public()] ++ [for_user.ap_id | for_user.following]
-        else
-          [Pleroma.Constants.as_public()]
-        end
-
-      activities =
-        recipients
-        |> ActivityPub.fetch_activities(params)
-        |> Enum.reverse()
-
-      conn
-      |> add_link_headers(activities)
-      |> put_view(StatusView)
-      |> render("index.json", %{activities: activities, for: for_user, as: :activity})
-    else
-      nil -> {:error, :not_found}
-      true -> render_error(conn, :forbidden, "Can't get favorites")
-    end
-  end
-
-  def bookmarks(%{assigns: %{user: user}} = conn, params) do
-    user = User.get_cached_by_id(user.id)
-
-    bookmarks =
-      Bookmark.for_user_query(user.id)
-      |> Pagination.fetch_paginated(params)
-
-    activities =
-      bookmarks
-      |> Enum.map(fn b -> Map.put(b.activity, :bookmark, Map.delete(b, :activity)) end)
-
-    conn
-    |> add_link_headers(bookmarks)
-    |> put_view(StatusView)
-    |> render("index.json", %{activities: activities, for: user, as: :activity})
-  end
-
-  def account_lists(%{assigns: %{user: user}} = conn, %{"id" => account_id}) do
-    lists = Pleroma.List.get_lists_account_belongs(user, account_id)
-
-    conn
-    |> put_view(ListView)
-    |> render("index.json", %{lists: lists})
-  end
-
-  def list_timeline(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params) do
-    with %Pleroma.List{title: _title, following: following} <- Pleroma.List.get(id, user) do
-      params =
-        params
-        |> Map.put("type", "Create")
-        |> Map.put("blocking_user", user)
-        |> Map.put("user", user)
-        |> Map.put("muting_user", user)
-
-      # we must filter the following list for the user to avoid leaking statuses the user
-      # does not actually have permission to see (for more info, peruse security issue #270).
-      activities =
-        following
-        |> Enum.filter(fn x -> x in user.following end)
-        |> ActivityPub.fetch_activities_bounded(following, params)
-        |> Enum.reverse()
-
-      conn
-      |> put_view(StatusView)
-      |> render("index.json", %{activities: activities, for: user, as: :activity})
-    else
-      _e -> render_error(conn, :forbidden, "Error.")
-    end
-  end
-
-  def index(%{assigns: %{user: user}} = conn, _params) do
-    token = get_session(conn, :oauth_token)
-
-    if user && token do
-      mastodon_emoji = mastodonized_emoji()
-
-      limit = Config.get([:instance, :limit])
-
-      accounts =
-        Map.put(%{}, user.id, AccountView.render("account.json", %{user: user, for: user}))
-
-      initial_state =
-        %{
-          meta: %{
-            streaming_api_base_url: Pleroma.Web.Endpoint.websocket_url(),
-            access_token: token,
-            locale: "en",
-            domain: Pleroma.Web.Endpoint.host(),
-            admin: "1",
-            me: "#{user.id}",
-            unfollow_modal: false,
-            boost_modal: false,
-            delete_modal: true,
-            auto_play_gif: false,
-            display_sensitive_media: false,
-            reduce_motion: false,
-            max_toot_chars: limit,
-            mascot: User.get_mascot(user)["url"]
-          },
-          poll_limits: Config.get([:instance, :poll_limits]),
-          rights: %{
-            delete_others_notice: present?(user.info.is_moderator),
-            admin: present?(user.info.is_admin)
-          },
-          compose: %{
-            me: "#{user.id}",
-            default_privacy: user.info.default_scope,
-            default_sensitive: false,
-            allow_content_types: Config.get([:instance, :allowed_post_formats])
-          },
-          media_attachments: %{
-            accept_content_types: [
-              ".jpg",
-              ".jpeg",
-              ".png",
-              ".gif",
-              ".webm",
-              ".mp4",
-              ".m4v",
-              "image\/jpeg",
-              "image\/png",
-              "image\/gif",
-              "video\/webm",
-              "video\/mp4"
-            ]
-          },
-          settings:
-            user.info.settings ||
-              %{
-                onboarded: true,
-                home: %{
-                  shows: %{
-                    reblog: true,
-                    reply: true
-                  }
-                },
-                notifications: %{
-                  alerts: %{
-                    follow: true,
-                    favourite: true,
-                    reblog: true,
-                    mention: true
-                  },
-                  shows: %{
-                    follow: true,
-                    favourite: true,
-                    reblog: true,
-                    mention: true
-                  },
-                  sounds: %{
-                    follow: true,
-                    favourite: true,
-                    reblog: true,
-                    mention: true
-                  }
-                }
-              },
-          push_subscription: nil,
-          accounts: accounts,
-          custom_emojis: mastodon_emoji,
-          char_limit: limit
-        }
-        |> Jason.encode!()
-
-      conn
-      |> put_layout(false)
-      |> put_view(MastodonView)
-      |> render("index.html", %{initial_state: initial_state})
-    else
-      conn
-      |> put_session(:return_to, conn.request_path)
-      |> redirect(to: "/web/login")
-    end
-  end
-
-  def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do
-    with {:ok, _} <- User.update_info(user, &User.Info.mastodon_settings_update(&1, settings)) do
-      json(conn, %{})
-    else
-      e ->
-        conn
-        |> put_status(:internal_server_error)
-        |> json(%{error: inspect(e)})
-    end
-  end
-
-  def login(%{assigns: %{user: %User{}}} = conn, _params) do
-    redirect(conn, to: local_mastodon_root_path(conn))
-  end
-
-  @doc "Local Mastodon FE login init action"
-  def login(conn, %{"code" => auth_token}) do
-    with {:ok, app} <- get_or_make_app(),
-         {:ok, auth} <- Authorization.get_by_token(app, auth_token),
-         {:ok, token} <- Token.exchange_token(app, auth) do
-      conn
-      |> put_session(:oauth_token, token.token)
-      |> redirect(to: local_mastodon_root_path(conn))
-    end
-  end
-
-  @doc "Local Mastodon FE callback action"
-  def login(conn, _) do
-    with {:ok, app} <- get_or_make_app() do
-      path =
-        o_auth_path(conn, :authorize,
-          response_type: "code",
-          client_id: app.client_id,
-          redirect_uri: ".",
-          scope: Enum.join(app.scopes, " ")
-        )
-
-      redirect(conn, to: path)
-    end
-  end
-
-  defp local_mastodon_root_path(conn) do
-    case get_session(conn, :return_to) do
-      nil ->
-        mastodon_api_path(conn, :index, ["getting-started"])
-
-      return_to ->
-        delete_session(conn, :return_to)
-        return_to
-    end
-  end
-
-  @spec get_or_make_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
-  defp get_or_make_app do
-    App.get_or_make(
-      %{client_name: @local_mastodon_name, redirect_uris: "."},
-      ["read", "write", "follow", "push"]
-    )
-  end
-
-  def logout(conn, _) do
-    conn
-    |> clear_session
-    |> redirect(to: "/")
-  end
-
   # Stubs for unimplemented mastodon api
   #
   def empty_array(conn, _) do
@@ -1368,248 +16,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     json(conn, [])
   end
 
-  def get_filters(%{assigns: %{user: user}} = conn, _) do
-    filters = Filter.get_filters(user)
-    res = FilterView.render("filters.json", filters: filters)
-    json(conn, res)
-  end
-
-  def create_filter(
-        %{assigns: %{user: user}} = conn,
-        %{"phrase" => phrase, "context" => context} = params
-      ) do
-    query = %Filter{
-      user_id: user.id,
-      phrase: phrase,
-      context: context,
-      hide: Map.get(params, "irreversible", false),
-      whole_word: Map.get(params, "boolean", true)
-      # expires_at
-    }
-
-    {:ok, response} = Filter.create(query)
-    res = FilterView.render("filter.json", filter: response)
-    json(conn, res)
-  end
-
-  def get_filter(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do
-    filter = Filter.get(filter_id, user)
-    res = FilterView.render("filter.json", filter: filter)
-    json(conn, res)
-  end
-
-  def update_filter(
-        %{assigns: %{user: user}} = conn,
-        %{"phrase" => phrase, "context" => context, "id" => filter_id} = params
-      ) do
-    query = %Filter{
-      user_id: user.id,
-      filter_id: filter_id,
-      phrase: phrase,
-      context: context,
-      hide: Map.get(params, "irreversible", nil),
-      whole_word: Map.get(params, "boolean", true)
-      # expires_at
-    }
-
-    {:ok, response} = Filter.update(query)
-    res = FilterView.render("filter.json", filter: response)
-    json(conn, res)
-  end
-
-  def delete_filter(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do
-    query = %Filter{
-      user_id: user.id,
-      filter_id: filter_id
-    }
-
-    {:ok, _} = Filter.delete(query)
+  def empty_object(conn, _) do
+    Logger.debug("Unimplemented, returning an empty object")
     json(conn, %{})
   end
-
-  def suggestions(%{assigns: %{user: user}} = conn, _) do
-    suggestions = Config.get(:suggestions)
-
-    if Keyword.get(suggestions, :enabled, false) do
-      api = Keyword.get(suggestions, :third_party_engine, "")
-      timeout = Keyword.get(suggestions, :timeout, 5000)
-      limit = Keyword.get(suggestions, :limit, 23)
-
-      host = Config.get([Pleroma.Web.Endpoint, :url, :host])
-
-      user = user.nickname
-
-      url =
-        api
-        |> String.replace("{{host}}", host)
-        |> String.replace("{{user}}", user)
-
-      with {:ok, %{status: 200, body: body}} <-
-             HTTP.get(url, [], adapter: [recv_timeout: timeout, pool: :default]),
-           {:ok, data} <- Jason.decode(body) do
-        data =
-          data
-          |> Enum.slice(0, limit)
-          |> Enum.map(fn x ->
-            x
-            |> Map.put("id", fetch_suggestion_id(x))
-            |> Map.put("avatar", MediaProxy.url(x["avatar"]))
-            |> Map.put("avatar_static", MediaProxy.url(x["avatar_static"]))
-          end)
-
-        json(conn, data)
-      else
-        e ->
-          Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}")
-      end
-    else
-      json(conn, [])
-    end
-  end
-
-  defp fetch_suggestion_id(attrs) do
-    case User.get_or_fetch(attrs["acct"]) do
-      {:ok, %User{id: id}} -> id
-      _ -> 0
-    end
-  end
-
-  @doc false
-  @deprecated "https://github.com/tootsuite/mastodon/pull/11213"
-  def status_card(%{assigns: %{user: user}} = conn, %{"id" => id}) do
-    with %Activity{} = activity <- Activity.get_by_id(id),
-         true <- Visibility.visible_for_user?(activity, user) do
-      data = RichMedia.Helpers.fetch_data_for_activity(activity)
-
-      conn
-      |> put_view(StatusView)
-      |> render("card.json", data)
-    else
-      _e -> {:error, :not_found}
-    end
-  end
-
-  def reports(%{assigns: %{user: user}} = conn, params) do
-    case CommonAPI.report(user, params) do
-      {:ok, activity} ->
-        conn
-        |> put_view(ReportView)
-        |> try_render("report.json", %{activity: activity})
-
-      {:error, err} ->
-        conn
-        |> put_status(:bad_request)
-        |> json(%{error: err})
-    end
-  end
-
-  def account_register(
-        %{assigns: %{app: app}} = conn,
-        %{"username" => nickname, "email" => _, "password" => _, "agreement" => true} = params
-      ) do
-    params =
-      params
-      |> Map.take([
-        "email",
-        "captcha_solution",
-        "captcha_token",
-        "captcha_answer_data",
-        "token",
-        "password"
-      ])
-      |> Map.put("nickname", nickname)
-      |> Map.put("fullname", params["fullname"] || nickname)
-      |> Map.put("bio", params["bio"] || "")
-      |> Map.put("confirm", params["password"])
-
-    with {:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true),
-         {:ok, token} <- Token.create_token(app, user, %{scopes: app.scopes}) do
-      json(conn, %{
-        token_type: "Bearer",
-        access_token: token.token,
-        scope: app.scopes,
-        created_at: Token.Utils.format_created_at(token)
-      })
-    else
-      {:error, errors} ->
-        conn
-        |> put_status(:bad_request)
-        |> json(errors)
-    end
-  end
-
-  def account_register(%{assigns: %{app: _app}} = conn, _) do
-    render_error(conn, :bad_request, "Missing parameters")
-  end
-
-  def account_register(conn, _) do
-    render_error(conn, :forbidden, "Invalid credentials")
-  end
-
-  def conversations(%{assigns: %{user: user}} = conn, params) do
-    participations = Participation.for_user_with_last_activity_id(user, params)
-
-    conversations =
-      Enum.map(participations, fn participation ->
-        ConversationView.render("participation.json", %{participation: participation, for: user})
-      end)
-
-    conn
-    |> add_link_headers(participations)
-    |> json(conversations)
-  end
-
-  def conversation_read(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do
-    with %Participation{} = participation <-
-           Repo.get_by(Participation, id: participation_id, user_id: user.id),
-         {:ok, participation} <- Participation.mark_as_read(participation) do
-      participation_view =
-        ConversationView.render("participation.json", %{participation: participation, for: user})
-
-      conn
-      |> json(participation_view)
-    end
-  end
-
-  def password_reset(conn, params) do
-    nickname_or_email = params["email"] || params["nickname"]
-
-    with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do
-      conn
-      |> put_status(:no_content)
-      |> json("")
-    else
-      {:error, "unknown user"} ->
-        send_resp(conn, :not_found, "")
-
-      {:error, _} ->
-        send_resp(conn, :bad_request, "")
-    end
-  end
-
-  def account_confirmation_resend(conn, params) do
-    nickname_or_email = params["email"] || params["nickname"]
-
-    with %User{} = user <- User.get_by_nickname_or_email(nickname_or_email),
-         {:ok, _} <- User.try_send_confirmation_email(user) do
-      conn
-      |> json_response(:no_content, "")
-    end
-  end
-
-  defp try_render(conn, target, params)
-       when is_binary(target) do
-    case render(conn, target, params) do
-      nil -> render_error(conn, :not_implemented, "Can't display this activity")
-      res -> res
-    end
-  end
-
-  defp try_render(conn, _, _) do
-    render_error(conn, :not_implemented, "Can't display this activity")
-  end
-
-  defp present?(nil), do: false
-  defp present?(false), do: false
-  defp present?(_), do: true
 end