aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/pleroma/application.ex3
-rw-r--r--lib/pleroma/list.ex21
-rw-r--r--lib/pleroma/notification.ex1
-rw-r--r--lib/pleroma/reverse_proxy/reverse_proxy.ex27
-rw-r--r--lib/pleroma/subscription_notification.ex260
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex41
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub_controller.ex28
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex51
-rw-r--r--lib/pleroma/web/activity_pub/utils.ex19
-rw-r--r--lib/pleroma/web/activity_pub/views/object_view.ex3
-rw-r--r--lib/pleroma/web/activity_pub/views/user_view.ex5
-rw-r--r--lib/pleroma/web/activity_pub/visibility.ex5
-rw-r--r--lib/pleroma/web/admin_api/views/report_view.ex2
-rw-r--r--lib/pleroma/web/chat_channel.ex2
-rw-r--r--lib/pleroma/web/common_api/common_api.ex36
-rw-r--r--lib/pleroma/web/controller_helper.ex19
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/account_controller.ex304
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/app_controller.ex39
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/auth_controller.ex91
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex32
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/list_controller.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex816
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/media_controller.ex42
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/poll_controller.ex53
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/report_controller.ex16
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/search_controller.ex4
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/status_controller.ex24
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex63
-rw-r--r--lib/pleroma/web/mastodon_api/views/account_view.ex26
-rw-r--r--lib/pleroma/web/mastodon_api/views/conversation_view.ex21
-rw-r--r--lib/pleroma/web/mastodon_api/views/notification_view.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/views/poll_view.ex74
-rw-r--r--lib/pleroma/web/mastodon_api/views/report_view.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/views/status_view.ex93
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/account_controller.ex143
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex35
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex52
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/subscription_notification_controller.ex71
-rw-r--r--lib/pleroma/web/pleroma_api/pleroma_api.ex40
-rw-r--r--lib/pleroma/web/pleroma_api/views/subscription_notification_view.ex61
-rw-r--r--lib/pleroma/web/push/impl.ex3
-rw-r--r--lib/pleroma/web/router.ex121
43 files changed, 1325 insertions, 1430 deletions
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index 7aec2c545..9e35b02c0 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -102,7 +102,8 @@ defmodule Pleroma.Application do
build_cachex("scrubber", limit: 2500),
build_cachex("idempotency", expiration: idempotency_expiration(), limit: 2500),
build_cachex("web_resp", limit: 2500),
- build_cachex("emoji_packs", expiration: emoji_packs_expiration(), limit: 10)
+ build_cachex("emoji_packs", expiration: emoji_packs_expiration(), limit: 10),
+ build_cachex("failed_proxy_url", limit: 2500)
]
end
diff --git a/lib/pleroma/list.ex b/lib/pleroma/list.ex
index c5db1cb62..08a94c62c 100644
--- a/lib/pleroma/list.ex
+++ b/lib/pleroma/list.ex
@@ -84,22 +84,11 @@ defmodule Pleroma.List do
end
# Get lists to which the account belongs.
- def get_lists_account_belongs(%User{} = owner, account_id) do
- user = User.get_cached_by_id(account_id)
-
- query =
- from(
- l in Pleroma.List,
- where:
- l.user_id == ^owner.id and
- fragment(
- "? = ANY(?)",
- ^user.follower_address,
- l.following
- )
- )
-
- Repo.all(query)
+ def get_lists_account_belongs(%User{} = owner, user) do
+ Pleroma.List
+ |> where([l], l.user_id == ^owner.id)
+ |> where([l], fragment("? = ANY(?)", ^user.follower_address, l.following))
+ |> Repo.all()
end
def rename(%Pleroma.List{} = list, title) do
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex
index d19924289..d94ae5971 100644
--- a/lib/pleroma/notification.ex
+++ b/lib/pleroma/notification.ex
@@ -230,6 +230,7 @@ defmodule Pleroma.Notification do
[]
|> Utils.maybe_notify_to_recipients(activity)
|> Utils.maybe_notify_mentioned_recipients(activity)
+ |> Utils.maybe_notify_subscribers(activity)
|> Enum.uniq()
User.get_users_from_set(recipients, local_only)
diff --git a/lib/pleroma/reverse_proxy/reverse_proxy.ex b/lib/pleroma/reverse_proxy/reverse_proxy.ex
index 03efad30a..78144cae3 100644
--- a/lib/pleroma/reverse_proxy/reverse_proxy.ex
+++ b/lib/pleroma/reverse_proxy/reverse_proxy.ex
@@ -15,6 +15,7 @@ defmodule Pleroma.ReverseProxy do
@valid_resp_codes [200, 206, 304]
@max_read_duration :timer.seconds(30)
@max_body_length :infinity
+ @failed_request_ttl :timer.seconds(60)
@methods ~w(GET HEAD)
@moduledoc """
@@ -48,6 +49,8 @@ defmodule Pleroma.ReverseProxy do
* `max_read_duration` (default `#{inspect(@max_read_duration)}` ms): the total time the connection is allowed to
read from the remote upstream.
+ * `failed_request_ttl` (default `#{inspect(@failed_request_ttl)}` ms): the time the failed request is cached and cannot be retried.
+
* `inline_content_types`:
* `true` will not alter `content-disposition` (up to the upstream),
* `false` will add `content-disposition: attachment` to any request,
@@ -83,6 +86,7 @@ defmodule Pleroma.ReverseProxy do
{:keep_user_agent, boolean}
| {:max_read_duration, :timer.time() | :infinity}
| {:max_body_length, non_neg_integer() | :infinity}
+ | {:failed_request_ttl, :timer.time() | :infinity}
| {:http, []}
| {:req_headers, [{String.t(), String.t()}]}
| {:resp_headers, [{String.t(), String.t()}]}
@@ -108,7 +112,8 @@ defmodule Pleroma.ReverseProxy do
opts
end
- with {:ok, code, headers, client} <- request(method, url, req_headers, hackney_opts),
+ with {:ok, nil} <- Cachex.get(:failed_proxy_url_cache, url),
+ {:ok, code, headers, client} <- request(method, url, req_headers, hackney_opts),
:ok <-
header_length_constraint(
headers,
@@ -116,12 +121,18 @@ defmodule Pleroma.ReverseProxy do
) do
response(conn, client, url, code, headers, opts)
else
+ {:ok, true} ->
+ conn
+ |> error_or_redirect(url, 500, "Request failed", opts)
+ |> halt()
+
{:ok, code, headers} ->
head_response(conn, url, code, headers, opts)
|> halt()
{:error, {:invalid_http_response, code}} ->
Logger.error("#{__MODULE__}: request to #{inspect(url)} failed with HTTP status #{code}")
+ track_failed_url(url, code, opts)
conn
|> error_or_redirect(
@@ -134,6 +145,7 @@ defmodule Pleroma.ReverseProxy do
{:error, error} ->
Logger.error("#{__MODULE__}: request to #{inspect(url)} failed: #{inspect(error)}")
+ track_failed_url(url, error, opts)
conn
|> error_or_redirect(url, 500, "Request failed", opts)
@@ -388,4 +400,17 @@ defmodule Pleroma.ReverseProxy do
end
defp client, do: Pleroma.ReverseProxy.Client
+
+ defp track_failed_url(url, code, opts) do
+ code = to_string(code)
+
+ ttl =
+ if code in ["403", "404"] or String.starts_with?(code, "5") do
+ Keyword.get(opts, :failed_request_ttl, @failed_request_ttl)
+ else
+ nil
+ end
+
+ Cachex.put(:failed_proxy_url_cache, url, true, ttl: ttl)
+ end
end
diff --git a/lib/pleroma/subscription_notification.ex b/lib/pleroma/subscription_notification.ex
deleted file mode 100644
index 1349d988c..000000000
--- a/lib/pleroma/subscription_notification.ex
+++ /dev/null
@@ -1,260 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.SubscriptionNotification do
- use Ecto.Schema
-
- alias Pleroma.Activity
- alias Pleroma.Object
- alias Pleroma.Pagination
- alias Pleroma.Repo
- alias Pleroma.SubscriptionNotification
- alias Pleroma.User
- alias Pleroma.Web.CommonAPI.Utils
- alias Pleroma.Web.Push
- alias Pleroma.Web.Streamer
-
- import Ecto.Query
- import Ecto.Changeset
-
- @type t :: %__MODULE__{}
-
- schema "subscription_notifications" do
- belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
- belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
-
- timestamps()
- end
-
- def changeset(%SubscriptionNotification{} = notification, attrs) do
- cast(notification, attrs, [])
- end
-
- def for_user_query(user, opts \\ []) do
- query =
- SubscriptionNotification
- |> where(user_id: ^user.id)
- |> where(
- [n, a],
- fragment(
- "? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')",
- a.actor
- )
- )
- |> join(:inner, [n], activity in assoc(n, :activity))
- |> join(:left, [n, a], object in Object,
- on:
- fragment(
- "(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)",
- object.data,
- a.data
- )
- )
- |> preload([n, a, o], activity: {a, object: o})
-
- if opts[:with_muted] do
- query
- else
- query
- |> where([n, a], a.actor not in ^user.info.muted_notifications)
- |> where([n, a], a.actor not in ^user.info.blocks)
- |> where(
- [n, a],
- fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.info.domain_blocks
- )
- |> join(:left, [n, a], tm in Pleroma.ThreadMute,
- on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data)
- )
- |> where([n, a, o, tm], is_nil(tm.user_id))
- end
- end
-
- def for_user(user, opts \\ %{}) do
- user
- |> for_user_query(opts)
- |> Pagination.fetch_paginated(opts)
- end
-
- @doc """
- Returns notifications for user received since given date.
-
- ## Examples
-
- iex> Pleroma.SubscriptionNotification.for_user_since(%Pleroma.User{}, ~N[2019-04-13 11:22:33])
- [%Pleroma.SubscriptionNotification{}, %Pleroma.SubscriptionNotification{}]
-
- iex> Pleroma.SubscriptionNotification.for_user_since(%Pleroma.User{}, ~N[2019-04-15 11:22:33])
- []
- """
- @spec for_user_since(Pleroma.User.t(), NaiveDateTime.t()) :: [t()]
- def for_user_since(user, date) do
- user
- |> for_user_query()
- |> where([n], n.updated_at > ^date)
- |> Repo.all()
- end
-
- def clear_up_to(%{id: user_id} = _user, id) do
- from(
- n in SubscriptionNotification,
- where: n.user_id == ^user_id,
- where: n.id <= ^id
- )
- |> Repo.delete_all([])
- end
-
- def get(%{id: user_id} = _user, id) do
- query =
- from(
- n in SubscriptionNotification,
- where: n.id == ^id,
- join: activity in assoc(n, :activity),
- preload: [activity: activity]
- )
-
- case Repo.one(query) do
- %{user_id: ^user_id} = notification ->
- {:ok, notification}
-
- _ ->
- {:error, "Cannot get notification"}
- end
- end
-
- def clear(user) do
- from(n in SubscriptionNotification, where: n.user_id == ^user.id)
- |> Repo.delete_all()
- end
-
- def destroy_multiple(%{id: user_id} = _user, ids) do
- from(n in SubscriptionNotification,
- where: n.id in ^ids,
- where: n.user_id == ^user_id
- )
- |> Repo.delete_all()
- end
-
- def dismiss(%{id: user_id} = _user, id) do
- case Repo.get(SubscriptionNotification, id) do
- %{user_id: ^user_id} = notification ->
- Repo.delete(notification)
-
- _ ->
- {:error, "Cannot dismiss notification"}
- end
- end
-
- def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity) do
- case Object.normalize(activity) do
- %{data: %{"type" => "Answer"}} ->
- {:ok, []}
-
- _ ->
- users = get_notified_from_activity(activity)
- notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
- {:ok, notifications}
- end
- end
-
- def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity)
- when type in ["Like", "Announce", "Follow"] do
- notifications =
- activity
- |> get_notified_from_activity()
- |> Enum.map(&create_notification(activity, &1))
-
- {:ok, notifications}
- end
-
- def create_notifications(_), do: {:ok, []}
-
- # TODO move to sql, too.
- def create_notification(%Activity{} = activity, %User{} = user) do
- unless skip?(activity, user) do
- notification = %SubscriptionNotification{user_id: user.id, activity: activity}
- {:ok, notification} = Repo.insert(notification)
- Streamer.stream("user", notification)
- Streamer.stream("user:subscription_notification", notification)
- Push.send(notification)
- notification
- end
- end
-
- def get_notified_from_activity(activity, local_only \\ true)
-
- def get_notified_from_activity(
- %Activity{data: %{"to" => _, "type" => type} = _data} = activity,
- local_only
- )
- when type in ["Create", "Like", "Announce", "Follow"] do
- []
- |> Utils.maybe_notify_subscribers(activity)
- |> Enum.uniq()
- |> User.get_users_from_set(local_only)
- end
-
- def get_notified_from_activity(_, _local_only), do: []
-
- @spec skip?(Activity.t(), User.t()) :: boolean()
- def skip?(activity, user) do
- [
- :self,
- :followers,
- :follows,
- :non_followers,
- :non_follows,
- :recently_followed
- ]
- |> Enum.any?(&skip?(&1, activity, user))
- end
-
- @spec skip?(atom(), Activity.t(), User.t()) :: boolean()
- def skip?(:self, activity, user) do
- activity.data["actor"] == user.ap_id
- end
-
- def skip?(
- :followers,
- %{data: %{"actor" => actor}},
- %{info: %{notification_settings: %{"followers" => false}}} = user
- ) do
- actor
- |> User.get_cached_by_ap_id()
- |> User.following?(user)
- end
-
- def skip?(
- :non_followers,
- activity,
- %{info: %{notification_settings: %{"non_followers" => false}}} = user
- ) do
- actor = activity.data["actor"]
- follower = User.get_cached_by_ap_id(actor)
- !User.following?(follower, user)
- end
-
- def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do
- actor = activity.data["actor"]
- followed = User.get_cached_by_ap_id(actor)
- User.following?(user, followed)
- end
-
- def skip?(
- :non_follows,
- activity,
- %{info: %{notification_settings: %{"non_follows" => false}}} = user
- ) do
- actor = activity.data["actor"]
- followed = User.get_cached_by_ap_id(actor)
- !User.following?(user, followed)
- end
-
- def skip?(:recently_followed, %{data: %{"type" => "Follow", "actor" => actor}}, user) do
- user
- |> SubscriptionNotification.for_user()
- |> Enum.any?(&match?(%{activity: %{data: %{"type" => "Follow", "actor" => ^actor}}}, &1))
- end
-
- def skip?(_, _, _), do: false
-end
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 7e83e27e5..c52efb578 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -13,7 +13,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.Object.Fetcher
alias Pleroma.Pagination
alias Pleroma.Repo
- alias Pleroma.SubscriptionNotification
alias Pleroma.Upload
alias Pleroma.User
alias Pleroma.Web.ActivityPub.MRF
@@ -152,7 +151,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
BackgroundWorker.enqueue("fetch_data_for_activity", %{"activity_id" => activity.id})
Notification.create_notifications(activity)
- SubscriptionNotification.create_notifications(activity)
participations =
activity
@@ -250,6 +248,26 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
+ def listen(%{to: to, actor: actor, context: context, object: object} = params) do
+ additional = params[:additional] || %{}
+ # only accept false as false value
+ local = !(params[:local] == false)
+ published = params[:published]
+
+ with listen_data <-
+ make_listen_data(
+ %{to: to, actor: actor, published: published, context: context, object: object},
+ additional
+ ),
+ {:ok, activity} <- insert(listen_data, local),
+ :ok <- maybe_federate(activity) do
+ {:ok, activity}
+ else
+ {:error, message} ->
+ {:error, message}
+ end
+ end
+
def accept(%{to: to, actor: actor, object: object} = params) do
# only accept false as false value
local = !(params[:local] == false)
@@ -328,7 +346,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
local \\ true,
public \\ true
) do
- with true <- is_public?(object),
+ with true <- is_announceable?(object, user, public),
announce_data <- make_announce_data(user, object, activity_id, public),
{:ok, activity} <- insert(announce_data, local),
{:ok, object} <- add_announce_to_object(activity, object),
@@ -590,6 +608,23 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_thread_visibility(query, _, _), do: query
+ def fetch_user_abstract_activities(user, reading_user, params \\ %{}) do
+ params =
+ params
+ |> Map.put("user", reading_user)
+ |> Map.put("actor_id", user.ap_id)
+ |> Map.put("whole_db", true)
+
+ recipients =
+ user_activities_recipients(%{
+ "godmode" => params["godmode"],
+ "reading_user" => reading_user
+ })
+
+ fetch_activities(recipients, params)
+ |> Enum.reverse()
+ end
+
def fetch_user_activities(user, reading_user, params \\ %{}) do
params =
params
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index 8112f6642..7cd13b4b8 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -334,6 +334,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|> represent_service_actor(conn)
end
+ @doc "Returns the authenticated user's ActivityPub User object or a 404 Not Found if non-authenticated"
def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
conn
|> put_resp_content_type("application/activity+json")
@@ -509,4 +510,31 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
{new_user, for_user}
end
+
+ # TODO: Add support for "object" field
+ @doc """
+ Endpoint based on <https://www.w3.org/wiki/SocialCG/ActivityPub/MediaUpload>
+
+ Parameters:
+ - (required) `file`: data of the media
+ - (optionnal) `description`: description of the media, intended for accessibility
+
+ Response:
+ - HTTP Code: 201 Created
+ - HTTP Body: ActivityPub object to be inserted into another's `attachment` field
+ """
+ def upload_media(%{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
+ Logger.debug(inspect(object))
+
+ conn
+ |> put_status(:created)
+ |> json(object.data)
+ end
+ end
end
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index dad2fead8..3ca2e8773 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -431,6 +431,36 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def handle_incoming(
+ %{"type" => "Listen", "object" => %{"type" => "Audio"} = object} = data,
+ options
+ ) do
+ actor = Containment.get_actor(data)
+
+ data =
+ Map.put(data, "actor", actor)
+ |> fix_addressing
+
+ with {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do
+ options = Keyword.put(options, :depth, (options[:depth] || 0) + 1)
+ object = fix_object(object, options)
+
+ params = %{
+ to: data["to"],
+ object: object,
+ actor: user,
+ context: nil,
+ local: false,
+ published: data["published"],
+ additional: Map.take(data, ["cc", "id"])
+ }
+
+ ActivityPub.listen(params)
+ else
+ _e -> :error
+ end
+ end
+
+ def handle_incoming(
%{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data,
_options
) do
@@ -723,6 +753,24 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
+ # For Undos that don't have the complete object attached, try to find it in our database.
+ def handle_incoming(
+ %{
+ "type" => "Undo",
+ "object" => object
+ } = activity,
+ options
+ )
+ when is_binary(object) do
+ with %Activity{data: data} <- Activity.get_by_ap_id(object) do
+ activity
+ |> Map.put("object", data)
+ |> handle_incoming(options)
+ else
+ _e -> :error
+ end
+ end
+
def handle_incoming(_, _), do: :error
@spec get_obj_helper(String.t(), Keyword.t()) :: {:ok, Object.t()} | nil
@@ -765,7 +813,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
# internal -> Mastodon
# """
- def prepare_outgoing(%{"type" => "Create", "object" => object_id} = data) do
+ def prepare_outgoing(%{"type" => activity_type, "object" => object_id} = data)
+ when activity_type in ["Create", "Listen"] do
object =
object_id
|> Object.normalize()
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index 30628a793..0828591ee 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -20,7 +20,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
require Logger
require Pleroma.Constants
- @supported_object_types ["Article", "Note", "Video", "Page", "Question", "Answer"]
+ @supported_object_types ["Article", "Note", "Video", "Page", "Question", "Answer", "Audio"]
@supported_report_states ~w(open closed resolved)
@valid_visibilities ~w(public unlisted private direct)
@@ -494,7 +494,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
@spec add_announce_to_object(Activity.t(), Object.t()) ::
{:ok, Object.t()} | {:error, Ecto.Changeset.t()}
def add_announce_to_object(
- %Activity{data: %{"actor" => actor, "cc" => [Pleroma.Constants.as_public()]}},
+ %Activity{data: %{"actor" => actor}},
object
) do
announcements = take_announcements(object)
@@ -581,6 +581,21 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|> Map.merge(additional)
end
+ #### Listen-related helpers
+ def make_listen_data(params, additional) do
+ published = params.published || make_date()
+
+ %{
+ "type" => "Listen",
+ "to" => params.to |> Enum.uniq(),
+ "actor" => params.actor.ap_id,
+ "object" => params.object,
+ "published" => published,
+ "context" => params.context
+ }
+ |> Map.merge(additional)
+ end
+
#### Flag-related helpers
@spec make_flag_data(map(), map()) :: map()
def make_flag_data(%{actor: actor, context: context, content: content} = params, additional) do
diff --git a/lib/pleroma/web/activity_pub/views/object_view.ex b/lib/pleroma/web/activity_pub/views/object_view.ex
index 0d63f0707..88c55acdd 100644
--- a/lib/pleroma/web/activity_pub/views/object_view.ex
+++ b/lib/pleroma/web/activity_pub/views/object_view.ex
@@ -15,7 +15,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do
Map.merge(base, additional)
end
- def render("object.json", %{object: %Activity{data: %{"type" => "Create"}} = activity}) do
+ def render("object.json", %{object: %Activity{data: %{"type" => activity_type}} = activity})
+ when activity_type in ["Create", "Listen"] do
base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header()
object = Object.normalize(activity)
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index 993307287..6bc55c85b 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -22,9 +22,10 @@ defmodule Pleroma.Web.ActivityPub.UserView do
def render("endpoints.json", %{user: %User{local: true} = _user}) do
%{
"oauthAuthorizationEndpoint" => Helpers.o_auth_url(Endpoint, :authorize),
- "oauthRegistrationEndpoint" => Helpers.mastodon_api_url(Endpoint, :create_app),
+ "oauthRegistrationEndpoint" => Helpers.app_url(Endpoint, :create),
"oauthTokenEndpoint" => Helpers.o_auth_url(Endpoint, :token_exchange),
- "sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox)
+ "sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox),
+ "uploadMedia" => Helpers.activity_pub_url(Endpoint, :upload_media)
}
end
diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex
index dfb166b65..270d0fa02 100644
--- a/lib/pleroma/web/activity_pub/visibility.ex
+++ b/lib/pleroma/web/activity_pub/visibility.ex
@@ -27,6 +27,11 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
end
end
+ def is_announceable?(activity, user, public \\ true) do
+ is_public?(activity) ||
+ (!public && is_private?(activity) && activity.data["actor"] == user.ap_id)
+ end
+
def is_direct?(%Activity{data: %{"directMessage" => true}}), do: true
def is_direct?(%Object{data: %{"directMessage" => true}}), do: true
diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex
index 8c06364a3..101a74c63 100644
--- a/lib/pleroma/web/admin_api/views/report_view.ex
+++ b/lib/pleroma/web/admin_api/views/report_view.ex
@@ -43,7 +43,7 @@ defmodule Pleroma.Web.AdminAPI.ReportView do
end
defp merge_account_views(%User{} = user) do
- Pleroma.Web.MastodonAPI.AccountView.render("account.json", %{user: user})
+ Pleroma.Web.MastodonAPI.AccountView.render("show.json", %{user: user})
|> Map.merge(Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: user}))
end
diff --git a/lib/pleroma/web/chat_channel.ex b/lib/pleroma/web/chat_channel.ex
index b543909f1..08841a3e8 100644
--- a/lib/pleroma/web/chat_channel.ex
+++ b/lib/pleroma/web/chat_channel.ex
@@ -22,7 +22,7 @@ defmodule Pleroma.Web.ChatChannel do
if String.length(text) > 0 do
author = User.get_cached_by_nickname(user_name)
- author = Pleroma.Web.MastodonAPI.AccountView.render("account.json", user: author)
+ author = Pleroma.Web.MastodonAPI.AccountView.render("show.json", user: author)
message = ChatChannelState.add_message(%{text: text, author: author})
broadcast!(socket, "new_msg", message)
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index a00e4b0d8..677a53ddf 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -76,11 +76,12 @@ defmodule Pleroma.Web.CommonAPI do
end
end
- def repeat(id_or_ap_id, user) do
+ def repeat(id_or_ap_id, user, params \\ %{}) do
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
object <- Object.normalize(activity),
- nil <- Utils.get_existing_announce(user.ap_id, object) do
- ActivityPub.announce(user, object)
+ nil <- Utils.get_existing_announce(user.ap_id, object),
+ public <- get_announce_visibility(object, params) do
+ ActivityPub.announce(user, object, nil, true, public)
else
_ -> {:error, dgettext("errors", "Could not repeat")}
end
@@ -169,6 +170,14 @@ defmodule Pleroma.Web.CommonAPI do
end
end
+ def get_announce_visibility(_, %{"visibility" => visibility})
+ when visibility in ~w{public unlisted private direct},
+ do: visibility in ~w(public unlisted)
+
+ def get_announce_visibility(object, _) do
+ Visibility.is_public?(object)
+ end
+
def get_visibility(_, _, %Participation{}), do: {"direct", "direct"}
def get_visibility(%{"visibility" => visibility}, in_reply_to, _)
@@ -212,6 +221,27 @@ defmodule Pleroma.Web.CommonAPI do
|> check_expiry_date()
end
+ def listen(user, %{"title" => _} = data) do
+ with visibility <- data["visibility"] || "public",
+ {to, cc} <- get_to_and_cc(user, [], nil, visibility, nil),
+ listen_data <-
+ Map.take(data, ["album", "artist", "title", "length"])
+ |> Map.put("type", "Audio")
+ |> Map.put("to", to)
+ |> Map.put("cc", cc)
+ |> Map.put("actor", user.ap_id),
+ {:ok, activity} <-
+ ActivityPub.listen(%{
+ actor: user,
+ to: to,
+ object: listen_data,
+ context: Utils.generate_context_id(),
+ additional: %{"cc" => cc}
+ }) do
+ {:ok, activity}
+ end
+ end
+
def post(user, %{"status" => _} = data) do
with {:ok, draft} <- Pleroma.Web.CommonAPI.ActivityDraft.create(user, data) do
draft.changes
diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex
index e90bf842e..9a4e322c9 100644
--- a/lib/pleroma/web/controller_helper.ex
+++ b/lib/pleroma/web/controller_helper.ex
@@ -68,4 +68,23 @@ defmodule Pleroma.Web.ControllerHelper do
conn
end
end
+
+ def assign_account_by_id(%{params: %{"id" => id}} = conn, _) do
+ case Pleroma.User.get_cached_by_id(id) do
+ %Pleroma.User{} = account -> assign(conn, :account, account)
+ nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt()
+ end
+ end
+
+ def 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
+
+ def try_render(conn, _, _) do
+ render_error(conn, :not_implemented, "Can't display this activity")
+ end
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
new file mode 100644
index 000000000..df14ad66f
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -0,0 +1,304 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.AccountController do
+ use Pleroma.Web, :controller
+
+ import Pleroma.Web.ControllerHelper,
+ only: [add_link_headers: 2, truthy_param?: 1, assign_account_by_id: 2, json_response: 3]
+
+ alias Pleroma.Emoji
+ alias Pleroma.Plugs.RateLimiter
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.MastodonAPI.ListView
+ alias Pleroma.Web.MastodonAPI.MastodonAPI
+ alias Pleroma.Web.MastodonAPI.StatusView
+ alias Pleroma.Web.OAuth.Token
+ alias Pleroma.Web.TwitterAPI.TwitterAPI
+
+ @relations [:follow, :unfollow]
+ @needs_account ~W(followers following lists follow unfollow mute unmute block unblock)a
+
+ plug(RateLimiter, {:relations_id_action, params: ["id", "uri"]} when action in @relations)
+ plug(RateLimiter, :relations_actions when action in @relations)
+ plug(RateLimiter, :app_account_creation when action == :create)
+ plug(:assign_account_by_id when action in @needs_account)
+
+ action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+
+ @doc "POST /api/v1/accounts"
+ def create(
+ %{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} -> json_response(conn, :bad_request, errors)
+ end
+ end
+
+ def create(%{assigns: %{app: _app}} = conn, _) do
+ render_error(conn, :bad_request, "Missing parameters")
+ end
+
+ def create(conn, _) do
+ render_error(conn, :forbidden, "Invalid credentials")
+ end
+
+ @doc "GET /api/v1/accounts/verify_credentials"
+ def verify_credentials(%{assigns: %{user: user}} = conn, _) do
+ chat_token = Phoenix.Token.sign(conn, "user socket", user.id)
+
+ render(conn, "show.json",
+ user: user,
+ for: user,
+ with_pleroma_settings: true,
+ with_chat_token: chat_token
+ )
+ end
+
+ @doc "PATCH /api/v1/accounts/update_credentials"
+ def update_credentials(%{assigns: %{user: original_user}} = conn, params) do
+ user = original_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}
+ 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, &{:ok, truthy_param?(&1)})
+ 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}
+ 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}
+ 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)
+
+ render(conn, "show.json", user: user, for: user, with_pleroma_settings: true)
+ else
+ _e -> render_error(conn, :forbidden, "Invalid request")
+ end
+ end
+
+ defp add_if_present(map, params, params_field, map_field, value_function \\ &{:ok, &1}) do
+ with true <- Map.has_key?(params, params_field),
+ {:ok, new_value} <- value_function.(params[params_field]) do
+ Map.put(map, map_field, new_value)
+ else
+ _ -> map
+ end
+ end
+
+ @doc "GET /api/v1/accounts/relationships"
+ def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+ targets = User.get_all_by_ids(List.wrap(id))
+
+ render(conn, "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, [])
+
+ @doc "GET /api/v1/accounts/:id"
+ def show(%{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
+ render(conn, "show.json", user: user, for: for_user)
+ else
+ _e -> render_error(conn, :not_found, "Can't find user")
+ end
+ end
+
+ @doc "GET /api/v1/accounts/:id/statuses"
+ def 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 = Map.put(params, "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
+
+ @doc "GET /api/v1/accounts/:id/followers"
+ def followers(%{assigns: %{user: for_user, account: user}} = conn, params) do
+ followers =
+ cond do
+ for_user && user.id == for_user.id -> MastodonAPI.get_followers(user, params)
+ user.info.hide_followers -> []
+ true -> MastodonAPI.get_followers(user, params)
+ end
+
+ conn
+ |> add_link_headers(followers)
+ |> render("index.json", for: for_user, users: followers, as: :user)
+ end
+
+ @doc "GET /api/v1/accounts/:id/following"
+ def following(%{assigns: %{user: for_user, account: user}} = conn, params) do
+ followers =
+ cond do
+ for_user && user.id == for_user.id -> MastodonAPI.get_friends(user, params)
+ user.info.hide_follows -> []
+ true -> MastodonAPI.get_friends(user, params)
+ end
+
+ conn
+ |> add_link_headers(followers)
+ |> render("index.json", for: for_user, users: followers, as: :user)
+ end
+
+ @doc "GET /api/v1/accounts/:id/lists"
+ def lists(%{assigns: %{user: user, account: account}} = conn, _params) do
+ lists = Pleroma.List.get_lists_account_belongs(user, account)
+
+ conn
+ |> put_view(ListView)
+ |> render("index.json", lists: lists)
+ end
+
+ @doc "POST /api/v1/accounts/:id/follow"
+ def follow(%{assigns: %{user: %{id: id}, account: %{id: id}}}, _params) do
+ {:error, :not_found}
+ end
+
+ def follow(%{assigns: %{user: follower, account: followed}} = conn, _params) do
+ with {:ok, follower} <- MastodonAPI.follow(follower, followed, conn.params) do
+ render(conn, "relationship.json", user: follower, target: followed)
+ else
+ {:error, message} -> json_response(conn, :forbidden, %{error: message})
+ end
+ end
+
+ @doc "POST /api/v1/accounts/:id/unfollow"
+ def unfollow(%{assigns: %{user: %{id: id}, account: %{id: id}}}, _params) do
+ {:error, :not_found}
+ end
+
+ def unfollow(%{assigns: %{user: follower, account: followed}} = conn, _params) do
+ with {:ok, follower} <- CommonAPI.unfollow(follower, followed) do
+ render(conn, "relationship.json", user: follower, target: followed)
+ end
+ end
+
+ @doc "POST /api/v1/accounts/:id/mute"
+ def mute(%{assigns: %{user: muter, account: muted}} = conn, params) do
+ notifications? = params |> Map.get("notifications", true) |> truthy_param?()
+
+ with {:ok, muter} <- User.mute(muter, muted, notifications?) do
+ render(conn, "relationship.json", user: muter, target: muted)
+ else
+ {:error, message} -> json_response(conn, :forbidden, %{error: message})
+ end
+ end
+
+ @doc "POST /api/v1/accounts/:id/unmute"
+ def unmute(%{assigns: %{user: muter, account: muted}} = conn, _params) do
+ with {:ok, muter} <- User.unmute(muter, muted) do
+ render(conn, "relationship.json", user: muter, target: muted)
+ else
+ {:error, message} -> json_response(conn, :forbidden, %{error: message})
+ end
+ end
+
+ @doc "POST /api/v1/accounts/:id/block"
+ def block(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
+ with {:ok, blocker} <- User.block(blocker, blocked),
+ {:ok, _activity} <- ActivityPub.block(blocker, blocked) do
+ render(conn, "relationship.json", user: blocker, target: blocked)
+ else
+ {:error, message} -> json_response(conn, :forbidden, %{error: message})
+ end
+ end
+
+ @doc "POST /api/v1/accounts/:id/unblock"
+ def unblock(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
+ with {:ok, blocker} <- User.unblock(blocker, blocked),
+ {:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do
+ render(conn, "relationship.json", user: blocker, target: blocked)
+ else
+ {:error, message} -> json_response(conn, :forbidden, %{error: message})
+ end
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex
new file mode 100644
index 000000000..abbe16a88
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex
@@ -0,0 +1,39 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.AppController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Repo
+ alias Pleroma.Web.OAuth.App
+ alias Pleroma.Web.OAuth.Scopes
+ alias Pleroma.Web.OAuth.Token
+
+ action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+
+ @local_mastodon_name "Mastodon-Local"
+
+ @doc "POST /api/v1/apps"
+ def create(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
+ render(conn, "show.json", app: app)
+ end
+ end
+
+ @doc "GET /api/v1/apps/verify_credentials"
+ def verify_credentials(%{assigns: %{user: _user, token: token}} = conn, _) do
+ with %Token{app: %App{} = app} <- Repo.preload(token, :app) do
+ render(conn, "short.json", app: app)
+ end
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
new file mode 100644
index 000000000..0dee670af
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
@@ -0,0 +1,91 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.AuthController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.User
+ alias Pleroma.Web.OAuth.App
+ alias Pleroma.Web.OAuth.Authorization
+ alias Pleroma.Web.OAuth.Token
+ alias Pleroma.Web.TwitterAPI.TwitterAPI
+
+ action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+
+ @local_mastodon_name "Mastodon-Local"
+
+ plug(Pleroma.Plugs.RateLimiter, :password_reset when action == :password_reset)
+
+ @doc "GET /web/login"
+ 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
+
+ @doc "DELETE /auth/sign_out"
+ def logout(conn, _) do
+ conn
+ |> clear_session
+ |> redirect(to: "/")
+ end
+
+ @doc "POST /auth/password"
+ 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
+
+ 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
+ %{client_name: @local_mastodon_name, redirect_uris: "."}
+ |> App.get_or_make(["read", "write", "follow", "push"])
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex b/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex
new file mode 100644
index 000000000..ea1e36a12
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex
@@ -0,0 +1,32 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.ConversationController do
+ use Pleroma.Web, :controller
+
+ import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
+
+ alias Pleroma.Conversation.Participation
+ alias Pleroma.Repo
+
+ action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+
+ @doc "GET /api/v1/conversations"
+ def index(%{assigns: %{user: user}} = conn, params) do
+ participations = Participation.for_user_with_last_activity_id(user, params)
+
+ conn
+ |> add_link_headers(participations)
+ |> render("participations.json", participations: participations, for: user)
+ end
+
+ @doc "POST /api/v1/conversations/:id/read"
+ def 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
+ render(conn, "participation.json", participation: participation, for: user)
+ end
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex
index 267014b97..ce7b625ee 100644
--- a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex
@@ -17,7 +17,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestController do
def index(%{assigns: %{user: followed}} = conn, _params) do
follow_requests = User.get_follow_requests(followed)
- render(conn, "accounts.json", for: followed, users: follow_requests, as: :user)
+ render(conn, "index.json", for: followed, users: follow_requests, as: :user)
end
@doc "POST /api/v1/follow_requests/:id/authorize"
diff --git a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex
index 2873deda8..50f42bee5 100644
--- a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex
@@ -49,7 +49,7 @@ defmodule Pleroma.Web.MastodonAPI.ListController do
with {:ok, users} <- Pleroma.List.get_following(list) do
conn
|> put_view(AccountView)
- |> render("accounts.json", for: user, users: users, as: :user)
+ |> render("index.json", for: user, users: users, as: :user)
end
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
index 0878f7ba6..81a95bc4a 100644
--- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
@@ -5,270 +5,24 @@
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
use Pleroma.Web, :controller
- import Pleroma.Web.ControllerHelper,
- only: [json_response: 3, add_link_headers: 2, truthy_param?: 1]
+ import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
- alias Ecto.Changeset
- alias Pleroma.Activity
alias Pleroma.Bookmark
alias Pleroma.Config
- alias Pleroma.Conversation.Participation
- alias Pleroma.Emoji
- alias Pleroma.HTTP
- alias Pleroma.Object
alias Pleroma.Pagination
- alias Pleroma.Plugs.RateLimiter
- alias Pleroma.Repo
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.ListView
- alias Pleroma.Web.MastodonAPI.MastodonAPI
alias Pleroma.Web.MastodonAPI.MastodonView
- alias Pleroma.Web.MastodonAPI.ReportView
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.TwitterAPI.TwitterAPI
require Logger
- require Pleroma.Constants
-
- @rate_limited_relations_actions ~w(follow unfollow)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, :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, 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
@@ -289,7 +43,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
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)
+ poll_limits: Keyword.get(instance, :poll_limits),
+ upload_limit: Keyword.get(instance, :upload_limit),
+ avatar_upload_limit: Keyword.get(instance, :avatar_upload_limit),
+ background_upload_limit: Keyword.get(instance, :background_upload_limit),
+ banner_upload_limit: Keyword.get(instance, :banner_upload_limit)
}
json(conn, response)
@@ -321,253 +79,17 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
json(conn, mastodon_emoji)
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 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 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 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(%{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
+ def follows(%{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})
+ |> render("show.json", %{user: followed, for: follower})
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)
@@ -577,72 +99,18 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
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)
+ res = AccountView.render("index.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)
+ res = AccountView.render("index.json", users: blocked_accounts, for: user, as: :user)
json(conn, res)
end
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
@@ -660,37 +128,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|> 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)
@@ -708,14 +145,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|> 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 index(%{assigns: %{user: user}} = conn, _params) do
token = get_session(conn, :oauth_token)
@@ -724,8 +153,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
limit = Config.get([:instance, :limit])
- accounts =
- Map.put(%{}, user.id, AccountView.render("account.json", %{user: user, for: user}))
+ accounts = Map.put(%{}, user.id, AccountView.render("show.json", %{user: user, for: user}))
initial_state =
%{
@@ -832,61 +260,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
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
@@ -899,173 +272,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
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
-
- 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
-
- def 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
-
- def 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
diff --git a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex
new file mode 100644
index 000000000..57a5b60fb
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex
@@ -0,0 +1,42 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.MediaController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Object
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ActivityPub
+
+ action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+ plug(:put_view, Pleroma.Web.MastodonAPI.StatusView)
+
+ @doc "POST /api/v1/media"
+ def create(%{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)
+
+ render(conn, "attachment.json", %{attachment: attachment_data})
+ end
+ end
+
+ @doc "PUT /api/v1/media/:id"
+ def update(%{assigns: %{user: user}} = conn, %{"id" => id, "description" => description})
+ when is_binary(description) do
+ with %Object{} = object <- Object.get_by_id(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)
+
+ render(conn, "attachment.json", %{attachment: attachment_data})
+ end
+ end
+
+ def update(_conn, _data), do: {:error, :bad_request}
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex b/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex
new file mode 100644
index 000000000..fbf7f8673
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex
@@ -0,0 +1,53 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.PollController do
+ use Pleroma.Web, :controller
+
+ import Pleroma.Web.ControllerHelper, only: [try_render: 3, json_response: 3]
+
+ alias Pleroma.Activity
+ alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.Visibility
+ alias Pleroma.Web.CommonAPI
+
+ action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+
+ @doc "GET /api/v1/polls/:id"
+ def show(%{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
+ try_render(conn, "show.json", %{object: object, for: user})
+ else
+ error when is_nil(error) or error == false ->
+ render_error(conn, :not_found, "Record not found")
+ end
+ end
+
+ @doc "POST /api/v1/polls/:id/votes"
+ def vote(%{assigns: %{user: user}} = conn, %{"id" => id, "choices" => choices}) do
+ with %Object{data: %{"type" => "Question"}} = object <- Object.get_by_id(id),
+ %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
+ try_render(conn, "show.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} -> json_response(conn, :unprocessable_entity, %{error: message})
+ end
+ end
+
+ defp get_cached_vote_or_vote(user, object, choices) do
+ idempotency_key = "polls:#{user.id}:#{object.data["id"]}"
+
+ Cachex.fetch!(:idempotency_cache, idempotency_key, fn ->
+ case CommonAPI.vote(user, object, choices) do
+ {:error, _message} = res -> {:ignore, res}
+ res -> {:commit, res}
+ end
+ end)
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/report_controller.ex b/lib/pleroma/web/mastodon_api/controllers/report_controller.ex
new file mode 100644
index 000000000..1c084b740
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/controllers/report_controller.ex
@@ -0,0 +1,16 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.ReportController do
+ use Pleroma.Web, :controller
+
+ action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+
+ @doc "POST /api/v1/reports"
+ def create(%{assigns: %{user: user}} = conn, params) do
+ with {:ok, activity} <- Pleroma.Web.CommonAPI.report(user, params) do
+ render(conn, "show.json", activity: activity)
+ end
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex
index c91713773..3fc89d645 100644
--- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex
@@ -22,7 +22,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
conn
|> put_view(AccountView)
- |> render("accounts.json", users: accounts, for: user, as: :user)
+ |> render("index.json", users: accounts, for: user, as: :user)
end
def search2(conn, params), do: do_search(:v2, conn, params)
@@ -72,7 +72,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
defp resource_search(_, "accounts", query, options) do
accounts = with_fallback(fn -> User.search(query, options) end)
- AccountView.render("accounts.json", users: accounts, for: options[:for_user], as: :user)
+ AccountView.render("index.json", users: accounts, for: options[:for_user], as: :user)
end
defp resource_search(_, "statuses", query, options) do
diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
index f4de9285b..79cced163 100644
--- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
@@ -5,7 +5,7 @@
defmodule Pleroma.Web.MastodonAPI.StatusController do
use Pleroma.Web, :controller
- import Pleroma.Web.MastodonAPI.MastodonAPIController, only: [try_render: 3]
+ import Pleroma.Web.ControllerHelper, only: [try_render: 3]
require Ecto.Query
@@ -125,8 +125,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
end
@doc "POST /api/v1/statuses/:id/reblog"
- def reblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
- with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user),
+ def reblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id} = params) do
+ with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user, params),
%Activity{} = announce <- Activity.normalize(announce.data) do
try_render(conn, "show.json", %{activity: announce, for: user, as: :activity})
end
@@ -231,7 +231,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
conn
|> put_view(AccountView)
- |> render("accounts.json", for: user, users: users, as: :user)
+ |> render("index.json", for: user, users: users, as: :user)
else
{:visible, false} -> {:error, :not_found}
_ -> json(conn, [])
@@ -242,7 +242,19 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
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
+ %Object{data: %{"announcements" => announces, "id" => ap_id}} <-
+ Object.normalize(activity) do
+ announces =
+ "Announce"
+ |> Activity.Queries.by_type()
+ |> Ecto.Query.where([a], a.actor in ^announces)
+ # this is to use the index
+ |> Activity.Queries.by_object_id(ap_id)
+ |> Repo.all()
+ |> Enum.filter(&Visibility.visible_for_user?(&1, user))
+ |> Enum.map(& &1.actor)
+ |> Enum.uniq()
+
users =
User
|> Ecto.Query.where([u], u.ap_id in ^announces)
@@ -251,7 +263,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
conn
|> put_view(AccountView)
- |> render("accounts.json", for: user, users: users, as: :user)
+ |> render("index.json", for: user, users: users, as: :user)
else
{:visible, false} -> {:error, :not_found}
_ -> json(conn, [])
diff --git a/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex b/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex
new file mode 100644
index 000000000..9076bb849
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex
@@ -0,0 +1,63 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.SuggestionController do
+ use Pleroma.Web, :controller
+
+ require Logger
+
+ alias Pleroma.Config
+ alias Pleroma.User
+ alias Pleroma.Web.MediaProxy
+
+ action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+
+ @doc "GET /api/v1/suggestions"
+ def index(%{assigns: %{user: user}} = conn, _) do
+ if Config.get([:suggestions, :enabled], false) do
+ with {:ok, data} <- fetch_suggestions(user) do
+ limit = Config.get([:suggestions, :limit], 23)
+
+ 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)
+ end
+ else
+ json(conn, [])
+ end
+ end
+
+ defp fetch_suggestions(user) do
+ api = Config.get([:suggestions, :third_party_engine], "")
+ timeout = Config.get([:suggestions, :timeout], 5000)
+ host = Config.get([Pleroma.Web.Endpoint, :url, :host])
+
+ url =
+ api
+ |> String.replace("{{host}}", host)
+ |> String.replace("{{user}}", user.nickname)
+
+ with {:ok, %{status: 200, body: body}} <-
+ Pleroma.HTTP.get(url, [], adapter: [recv_timeout: timeout, pool: :default]) do
+ Jason.decode(body)
+ else
+ e -> Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}")
+ end
+ end
+
+ defp fetch_suggestion_id(attrs) do
+ case User.get_or_fetch(attrs["acct"]) do
+ {:ok, %User{id: id}} -> id
+ _ -> 0
+ end
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index a23aeea9b..99169ef95 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -11,15 +11,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MediaProxy
- def render("accounts.json", %{users: users} = opts) do
+ def render("index.json", %{users: users} = opts) do
users
- |> render_many(AccountView, "account.json", opts)
+ |> render_many(AccountView, "show.json", opts)
|> Enum.filter(&Enum.any?/1)
end
- def render("account.json", %{user: user} = opts) do
+ def render("show.json", %{user: user} = opts) do
if User.visible_for?(user, opts[:for]),
- do: do_render("account.json", opts),
+ do: do_render("show.json", opts),
else: %{}
end
@@ -66,7 +66,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
render_many(targets, AccountView, "relationship.json", user: user, as: :target)
end
- defp do_render("account.json", %{user: user} = opts) do
+ defp do_render("show.json", %{user: user} = opts) do
display_name = HTML.strip_tags(user.name || user.nickname)
image = User.avatar_url(user) |> MediaProxy.url()
@@ -166,6 +166,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|> maybe_put_settings_store(user, opts[:for], opts)
|> maybe_put_chat_token(user, opts[:for], opts)
|> maybe_put_activation_status(user, opts[:for])
+ |> maybe_put_follow_requests_count(user, opts[:for])
end
defp username_from_nickname(string) when is_binary(string) do
@@ -174,6 +175,21 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
defp username_from_nickname(_), do: nil
+ defp maybe_put_follow_requests_count(
+ data,
+ %User{id: user_id} = user,
+ %User{id: user_id}
+ ) do
+ count =
+ User.get_follow_requests(user)
+ |> length()
+
+ data
+ |> Kernel.put_in([:follow_requests_count], count)
+ end
+
+ defp maybe_put_follow_requests_count(data, _, _), do: data
+
defp maybe_put_settings(
data,
%User{id: user_id} = user,
diff --git a/lib/pleroma/web/mastodon_api/views/conversation_view.ex b/lib/pleroma/web/mastodon_api/views/conversation_view.ex
index 4aeb79d81..e9d2735b3 100644
--- a/lib/pleroma/web/mastodon_api/views/conversation_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/conversation_view.ex
@@ -11,6 +11,10 @@ defmodule Pleroma.Web.MastodonAPI.ConversationView do
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.StatusView
+ def render("participations.json", %{participations: participations, for: user}) do
+ render_many(participations, __MODULE__, "participation.json", as: :participation, for: user)
+ end
+
def render("participation.json", %{participation: participation, for: user}) do
participation = Repo.preload(participation, conversation: [], recipients: [])
@@ -23,25 +27,14 @@ defmodule Pleroma.Web.MastodonAPI.ConversationView do
end
activity = Activity.get_by_id_with_object(last_activity_id)
-
- last_status = StatusView.render("show.json", %{activity: activity, for: user})
-
# Conversations return all users except the current user.
- users =
- participation.recipients
- |> Enum.reject(&(&1.id == user.id))
-
- accounts =
- AccountView.render("accounts.json", %{
- users: users,
- as: :user
- })
+ users = Enum.reject(participation.recipients, &(&1.id == user.id))
%{
id: participation.id |> to_string(),
- accounts: accounts,
+ accounts: render(AccountView, "index.json", users: users, as: :user),
unread: !participation.read,
- last_status: last_status
+ last_status: render(StatusView, "show.json", activity: activity, for: user)
}
end
end
diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex
index 05110a192..60b58dc90 100644
--- a/lib/pleroma/web/mastodon_api/views/notification_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/notification_view.ex
@@ -29,7 +29,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
id: to_string(notification.id),
type: mastodon_type,
created_at: CommonAPI.Utils.to_masto_date(notification.inserted_at),
- account: AccountView.render("account.json", %{user: actor, for: user}),
+ account: AccountView.render("show.json", %{user: actor, for: user}),
pleroma: %{
is_seen: notification.seen
}
diff --git a/lib/pleroma/web/mastodon_api/views/poll_view.ex b/lib/pleroma/web/mastodon_api/views/poll_view.ex
new file mode 100644
index 000000000..753039da3
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/views/poll_view.ex
@@ -0,0 +1,74 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.PollView do
+ use Pleroma.Web, :view
+
+ alias Pleroma.HTML
+ alias Pleroma.Web.CommonAPI.Utils
+
+ def render("show.json", %{object: object, multiple: multiple, options: options} = params) do
+ {end_time, expired} = end_time_and_expired(object)
+ {options, votes_count} = options_and_votes_count(options)
+
+ %{
+ # Mastodon uses separate ids for polls, but an object can't have
+ # more than one poll embedded so object id is fine
+ id: to_string(object.id),
+ expires_at: end_time,
+ expired: expired,
+ multiple: multiple,
+ votes_count: votes_count,
+ options: options,
+ voted: voted?(params),
+ emojis: Pleroma.Web.MastodonAPI.StatusView.build_emojis(object.data["emoji"])
+ }
+ end
+
+ def render("show.json", %{object: object} = params) do
+ case object.data do
+ %{"anyOf" => options} when is_list(options) ->
+ render(__MODULE__, "show.json", Map.merge(params, %{multiple: true, options: options}))
+
+ %{"oneOf" => options} when is_list(options) ->
+ render(__MODULE__, "show.json", Map.merge(params, %{multiple: false, options: options}))
+
+ _ ->
+ nil
+ end
+ end
+
+ defp end_time_and_expired(object) do
+ case object.data["closed"] || object.data["endTime"] do
+ end_time when is_binary(end_time) ->
+ end_time = NaiveDateTime.from_iso8601!(end_time)
+ expired = NaiveDateTime.compare(end_time, NaiveDateTime.utc_now()) == :lt
+
+ {Utils.to_masto_date(end_time), expired}
+
+ _ ->
+ {nil, false}
+ end
+ end
+
+ defp options_and_votes_count(options) do
+ Enum.map_reduce(options, 0, fn %{"name" => name} = option, count ->
+ current_count = option["replies"]["totalItems"] || 0
+
+ {%{
+ title: HTML.strip_tags(name),
+ votes_count: current_count
+ }, current_count + count}
+ end)
+ end
+
+ defp voted?(%{object: object} = opts) do
+ if opts[:for] do
+ existing_votes = Pleroma.Web.ActivityPub.Utils.get_existing_votes(opts[:for].ap_id, object)
+ existing_votes != [] or opts[:for].ap_id == object.data["actor"]
+ else
+ false
+ end
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/views/report_view.ex b/lib/pleroma/web/mastodon_api/views/report_view.ex
index a16e7ff10..9da2dd740 100644
--- a/lib/pleroma/web/mastodon_api/views/report_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/report_view.ex
@@ -5,7 +5,7 @@
defmodule Pleroma.Web.MastodonAPI.ReportView do
use Pleroma.Web, :view
- def render("report.json", %{activity: activity}) do
+ def render("show.json", %{activity: activity}) do
%{
id: to_string(activity.id),
action_taken: false
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index 2321d0de2..9b8dd3086 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -18,6 +18,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.AccountView
+ alias Pleroma.Web.MastodonAPI.PollView
alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.MediaProxy
@@ -108,7 +109,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
id: to_string(activity.id),
uri: activity_object.data["id"],
url: activity_object.data["id"],
- account: AccountView.render("account.json", %{user: user, for: opts[:for]}),
+ account: AccountView.render("show.json", %{user: user, for: opts[:for]}),
in_reply_to_id: nil,
in_reply_to_account_id: nil,
reblog: reblogged,
@@ -124,7 +125,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
pinned: pinned?(activity, user),
sensitive: false,
spoiler_text: "",
- visibility: "public",
+ visibility: get_visibility(activity),
media_attachments: reblogged[:media_attachments] || [],
mentions: mentions,
tags: reblogged[:tags] || [],
@@ -258,7 +259,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
id: to_string(activity.id),
uri: object.data["id"],
url: url,
- account: AccountView.render("account.json", %{user: user, for: opts[:for]}),
+ account: AccountView.render("show.json", %{user: user, for: opts[:for]}),
in_reply_to_id: reply_to && to_string(reply_to.id),
in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id),
reblog: nil,
@@ -277,7 +278,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
spoiler_text: summary_html,
visibility: get_visibility(object),
media_attachments: attachments,
- poll: render("poll.json", %{object: object, for: opts[:for]}),
+ poll: render(PollView, "show.json", object: object, for: opts[:for]),
mentions: mentions,
tags: build_tags(tags),
application: %{
@@ -368,73 +369,25 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
}
end
- def render("poll.json", %{object: object} = opts) do
- {multiple, options} =
- case object.data do
- %{"anyOf" => options} when is_list(options) -> {true, options}
- %{"oneOf" => options} when is_list(options) -> {false, options}
- _ -> {nil, nil}
- end
+ def render("listen.json", %{activity: %Activity{data: %{"type" => "Listen"}} = activity} = opts) do
+ object = Object.normalize(activity)
- if options do
- {end_time, expired} =
- case object.data["closed"] || object.data["endTime"] do
- end_time when is_binary(end_time) ->
- end_time =
- (object.data["closed"] || object.data["endTime"])
- |> NaiveDateTime.from_iso8601!()
-
- expired =
- end_time
- |> NaiveDateTime.compare(NaiveDateTime.utc_now())
- |> case do
- :lt -> true
- _ -> false
- end
-
- end_time = Utils.to_masto_date(end_time)
-
- {end_time, expired}
-
- _ ->
- {nil, false}
- end
-
- voted =
- if opts[:for] do
- existing_votes =
- Pleroma.Web.ActivityPub.Utils.get_existing_votes(opts[:for].ap_id, object)
-
- existing_votes != [] or opts[:for].ap_id == object.data["actor"]
- else
- false
- end
-
- {options, votes_count} =
- Enum.map_reduce(options, 0, fn %{"name" => name} = option, count ->
- current_count = option["replies"]["totalItems"] || 0
-
- {%{
- title: HTML.strip_tags(name),
- votes_count: current_count
- }, current_count + count}
- end)
-
- %{
- # Mastodon uses separate ids for polls, but an object can't have
- # more than one poll embedded so object id is fine
- id: to_string(object.id),
- expires_at: end_time,
- expired: expired,
- multiple: multiple,
- votes_count: votes_count,
- options: options,
- voted: voted,
- emojis: build_emojis(object.data["emoji"])
- }
- else
- nil
- end
+ user = get_user(activity.data["actor"])
+ created_at = Utils.to_masto_date(activity.data["published"])
+
+ %{
+ id: activity.id,
+ account: AccountView.render("show.json", %{user: user, for: opts[:for]}),
+ created_at: created_at,
+ title: object.data["title"] |> HTML.strip_tags(),
+ artist: object.data["artist"] |> HTML.strip_tags(),
+ album: object.data["album"] |> HTML.strip_tags(),
+ length: object.data["length"]
+ }
+ end
+
+ def render("listens.json", opts) do
+ safe_render_many(opts.activities, StatusView, "listen.json", opts)
end
def render("context.json", %{activity: activity, activities: activities, user: user}) do
diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
new file mode 100644
index 000000000..63c44086c
--- /dev/null
+++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
@@ -0,0 +1,143 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.AccountController do
+ use Pleroma.Web, :controller
+
+ import Pleroma.Web.ControllerHelper,
+ only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2]
+
+ alias Ecto.Changeset
+ alias Pleroma.Plugs.RateLimiter
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.MastodonAPI.StatusView
+
+ require Pleroma.Constants
+
+ plug(RateLimiter, :account_confirmation_resend when action == :confirmation_resend)
+ plug(:assign_account_by_id when action in [:favourites, :subscribe, :unsubscribe])
+ plug(:put_view, Pleroma.Web.MastodonAPI.AccountView)
+
+ @doc "POST /api/v1/pleroma/accounts/confirmation_resend"
+ def 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
+ json_response(conn, :no_content, "")
+ end
+ end
+
+ @doc "PATCH /api/v1/pleroma/accounts/update_avatar"
+ def update_avatar(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
+ {:ok, user} =
+ user
+ |> Changeset.change(%{avatar: nil})
+ |> User.update_and_set_cache()
+
+ CommonAPI.update(user)
+
+ json(conn, %{url: nil})
+ end
+
+ def update_avatar(%{assigns: %{user: user}} = conn, params) do
+ {:ok, %{data: data}} = ActivityPub.upload(params, type: :avatar)
+ {:ok, user} = user |> Changeset.change(%{avatar: data}) |> User.update_and_set_cache()
+ %{"url" => [%{"href" => href} | _]} = data
+
+ CommonAPI.update(user)
+
+ json(conn, %{url: href})
+ end
+
+ @doc "PATCH /api/v1/pleroma/accounts/update_banner"
+ 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
+
+ @doc "PATCH /api/v1/pleroma/accounts/update_background"
+ 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
+
+ @doc "GET /api/v1/pleroma/accounts/:id/favourites"
+ def favourites(%{assigns: %{account: %{info: %{hide_favorites: true}}}} = conn, _params) do
+ render_error(conn, :forbidden, "Can't get favorites")
+ end
+
+ def favourites(%{assigns: %{user: for_user, account: user}} = conn, params) 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)
+ end
+
+ @doc "POST /api/v1/pleroma/accounts/:id/subscribe"
+ def subscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do
+ with {:ok, subscription_target} <- User.subscribe(user, subscription_target) do
+ render(conn, "relationship.json", user: user, target: subscription_target)
+ else
+ {:error, message} -> json_response(conn, :forbidden, %{error: message})
+ end
+ end
+
+ @doc "POST /api/v1/pleroma/accounts/:id/unsubscribe"
+ def unsubscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do
+ with {:ok, subscription_target} <- User.unsubscribe(user, subscription_target) do
+ render(conn, "relationship.json", user: user, target: subscription_target)
+ else
+ {:error, message} -> json_response(conn, :forbidden, %{error: message})
+ end
+ end
+end
diff --git a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex
new file mode 100644
index 000000000..7f6a76c0e
--- /dev/null
+++ b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex
@@ -0,0 +1,35 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.MascotController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ActivityPub
+
+ @doc "GET /api/v1/pleroma/mascot"
+ def show(%{assigns: %{user: user}} = conn, _params) do
+ json(conn, User.get_mascot(user))
+ end
+
+ @doc "PUT /api/v1/pleroma/mascot"
+ def update(%{assigns: %{user: user}} = conn, %{"file" => file}) do
+ with {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)),
+ # Reject if not an image
+ %{type: "image"} = attachment <- render_attachment(object) do
+ # Sure!
+ # Save to the user's info
+ {:ok, _user} = User.update_info(user, &User.Info.mascot_update(&1, attachment))
+
+ json(conn, attachment)
+ else
+ %{type: _} -> render_error(conn, :unsupported_media_type, "mascots can only be images")
+ end
+ end
+
+ defp render_attachment(object) do
+ attachment_data = Map.put(object.data, "id", object.id)
+ Pleroma.Web.MastodonAPI.StatusView.render("attachment.json", %{attachment: attachment_data})
+ end
+end
diff --git a/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex b/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex
new file mode 100644
index 000000000..0fb978c5d
--- /dev/null
+++ b/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex
@@ -0,0 +1,52 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.ScrobbleController do
+ use Pleroma.Web, :controller
+
+ import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, fetch_integer_param: 2]
+
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.MastodonAPI.StatusView
+
+ def new_scrobble(%{assigns: %{user: user}} = conn, %{"title" => _} = params) do
+ params =
+ if !params["length"] do
+ params
+ else
+ params
+ |> Map.put("length", fetch_integer_param(params, "length"))
+ end
+
+ with {:ok, activity} <- CommonAPI.listen(user, params) do
+ conn
+ |> put_view(StatusView)
+ |> render("listen.json", %{activity: activity, for: user})
+ else
+ {:error, message} ->
+ conn
+ |> put_status(:bad_request)
+ |> json(%{"error" => message})
+ end
+ end
+
+ def user_scrobbles(%{assigns: %{user: reading_user}} = conn, params) do
+ with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user) do
+ params = Map.put(params, "type", ["Listen"])
+
+ activities = ActivityPub.fetch_user_abstract_activities(user, reading_user, params)
+
+ conn
+ |> add_link_headers(activities)
+ |> put_view(StatusView)
+ |> render("listens.json", %{
+ activities: activities,
+ for: reading_user,
+ as: :activity
+ })
+ end
+ end
+end
diff --git a/lib/pleroma/web/pleroma_api/controllers/subscription_notification_controller.ex b/lib/pleroma/web/pleroma_api/controllers/subscription_notification_controller.ex
deleted file mode 100644
index 37c2222de..000000000
--- a/lib/pleroma/web/pleroma_api/controllers/subscription_notification_controller.ex
+++ /dev/null
@@ -1,71 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.PleromaAPI.SubscriptionNotificationController do
- use Pleroma.Web, :controller
-
- import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
-
- alias Pleroma.Activity
- alias Pleroma.SubscriptionNotification
- alias Pleroma.User
- alias Pleroma.Web.PleromaAPI.PleromaAPI
-
- def index(%{assigns: %{user: user}} = conn, params) do
- notifications =
- user
- |> PleromaAPI.get_subscription_notifications(params)
- |> Enum.map(&build_notification_data/1)
-
- conn
- |> add_link_headers(notifications)
- |> render("index.json", %{notifications: notifications, for: user})
- end
-
- def show(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
- with {:ok, notification} <- SubscriptionNotification.get(user, id) do
- render(conn, "show.json", %{
- subscription_notification: build_notification_data(notification),
- for: user
- })
- else
- {:error, reason} ->
- conn
- |> put_status(:forbidden)
- |> json(%{"error" => reason})
- end
- end
-
- def clear(%{assigns: %{user: user}} = conn, _params) do
- SubscriptionNotification.clear(user)
- json(conn, %{})
- end
-
- def dismiss(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
- with {:ok, _notif} <- SubscriptionNotification.dismiss(user, id) do
- json(conn, %{})
- else
- {:error, reason} ->
- conn
- |> put_status(:forbidden)
- |> json(%{"error" => reason})
- end
- end
-
- def destroy_multiple(
- %{assigns: %{user: user}} = conn,
- %{"ids" => ids} = _params
- ) do
- SubscriptionNotification.destroy_multiple(user, ids)
- json(conn, %{})
- end
-
- defp build_notification_data(%{activity: %{data: data}} = notification) do
- %{
- notification: notification,
- actor: User.get_cached_by_ap_id(data["actor"]),
- parent_activity: Activity.get_create_by_object_ap_id(data["object"])
- }
- end
-end
diff --git a/lib/pleroma/web/pleroma_api/pleroma_api.ex b/lib/pleroma/web/pleroma_api/pleroma_api.ex
deleted file mode 100644
index 480964845..000000000
--- a/lib/pleroma/web/pleroma_api/pleroma_api.ex
+++ /dev/null
@@ -1,40 +0,0 @@
-defmodule Pleroma.Web.PleromaAPI.PleromaAPI do
- import Ecto.Query
- import Ecto.Changeset
-
- alias Pleroma.Activity
- alias Pleroma.Pagination
- alias Pleroma.SubscriptionNotification
-
- def get_subscription_notifications(user, params \\ %{}) do
- options = cast_params(params)
-
- user
- |> SubscriptionNotification.for_user_query(options)
- |> restrict(:exclude_types, options)
- |> Pagination.fetch_paginated(params)
- end
-
- defp cast_params(params) do
- param_types = %{
- exclude_types: {:array, :string},
- reblogs: :boolean,
- with_muted: :boolean
- }
-
- changeset = cast({%{}, param_types}, params, Map.keys(param_types))
- changeset.changes
- end
-
- defp restrict(query, :exclude_types, %{exclude_types: mastodon_types = [_ | _]}) do
- ap_types =
- mastodon_types
- |> Enum.map(&Activity.from_mastodon_notification_type/1)
- |> Enum.filter(& &1)
-
- query
- |> where([q, a], not fragment("? @> ARRAY[?->>'type']::varchar[]", ^ap_types, a.data))
- end
-
- defp restrict(query, _, _), do: query
-end
diff --git a/lib/pleroma/web/pleroma_api/views/subscription_notification_view.ex b/lib/pleroma/web/pleroma_api/views/subscription_notification_view.ex
deleted file mode 100644
index fc41a7389..000000000
--- a/lib/pleroma/web/pleroma_api/views/subscription_notification_view.ex
+++ /dev/null
@@ -1,61 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.PleromaAPI.SubscriptionNotificationView do
- use Pleroma.Web, :view
-
- alias Pleroma.Activity
- alias Pleroma.Web.CommonAPI
- alias Pleroma.Web.MastodonAPI.AccountView
- alias Pleroma.Web.MastodonAPI.StatusView
- alias Pleroma.Web.PleromaAPI.SubscriptionNotificationView
-
- def render("index.json", %{notifications: notifications, for: user}) do
- safe_render_many(notifications, SubscriptionNotificationView, "show.json", %{for: user})
- end
-
- def render("show.json", %{
- subscription_notification: %{
- notification: %{activity: activity} = notification,
- actor: actor,
- parent_activity: parent_activity
- },
- for: user
- }) do
- mastodon_type = Activity.mastodon_notification_type(activity)
-
- response = %{
- id: to_string(notification.id),
- type: mastodon_type,
- created_at: CommonAPI.Utils.to_masto_date(notification.inserted_at),
- account: AccountView.render("account.json", %{user: actor, for: user})
- }
-
- case mastodon_type do
- "mention" ->
- response
- |> Map.merge(%{
- status: StatusView.render("show.json", %{activity: activity, for: user})
- })
-
- "favourite" ->
- response
- |> Map.merge(%{
- status: StatusView.render("show.json", %{activity: parent_activity, for: user})
- })
-
- "reblog" ->
- response
- |> Map.merge(%{
- status: StatusView.render("show.json", %{activity: parent_activity, for: user})
- })
-
- "follow" ->
- response
-
- _ ->
- nil
- end
- end
-end
diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex
index 7ea5607fa..35d3ff07c 100644
--- a/lib/pleroma/web/push/impl.ex
+++ b/lib/pleroma/web/push/impl.ex
@@ -9,7 +9,6 @@ defmodule Pleroma.Web.Push.Impl do
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Repo
- alias Pleroma.SubscriptionNotification
alias Pleroma.User
alias Pleroma.Web.Metadata.Utils
alias Pleroma.Web.Push.Subscription
@@ -20,7 +19,7 @@ defmodule Pleroma.Web.Push.Impl do
@types ["Create", "Follow", "Announce", "Like"]
@doc "Performs sending notifications for user subscriptions"
- @spec perform(Notification.t() | SubscriptionNotification.t()) :: list(any) | :error
+ @spec perform(Notification.t()) :: list(any) | :error
def perform(
%{
activity: %{data: %{"type" => activity_type}, id: activity_id} = activity,
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index a025474e2..501978994 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -287,27 +287,50 @@ defmodule Pleroma.Web.Router do
end
scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
- pipe_through(:authenticated_api)
-
scope [] do
+ pipe_through(:authenticated_api)
pipe_through(:oauth_read)
get("/conversations/:id/statuses", PleromaAPIController, :conversation_statuses)
get("/conversations/:id", PleromaAPIController, :conversation)
-
- scope "/subscription_notifications" do
- post("/clear", SubscriptionNotificationController, :clear)
- post("/dismiss", SubscriptionNotificationController, :dismiss)
- delete("/destroy_multiple", SubscriptionNotificationController, :destroy_multiple)
- get("/", SubscriptionNotificationController, :index)
- get("/:id", SubscriptionNotificationController, :show)
- end
end
scope [] do
+ pipe_through(:authenticated_api)
pipe_through(:oauth_write)
patch("/conversations/:id", PleromaAPIController, :update_conversation)
post("/notifications/read", PleromaAPIController, :read_notification)
+
+ patch("/accounts/update_avatar", AccountController, :update_avatar)
+ patch("/accounts/update_banner", AccountController, :update_banner)
+ patch("/accounts/update_background", AccountController, :update_background)
+
+ get("/mascot", MascotController, :show)
+ put("/mascot", MascotController, :update)
+
+ post("/scrobble", ScrobbleController, :new_scrobble)
+ end
+
+ scope [] do
+ pipe_through(:api)
+ pipe_through(:oauth_read_or_public)
+ get("/accounts/:id/favourites", AccountController, :favourites)
+ end
+
+ scope [] do
+ pipe_through(:authenticated_api)
+ pipe_through(:oauth_follow)
+
+ post("/accounts/:id/subscribe", AccountController, :subscribe)
+ post("/accounts/:id/unsubscribe", AccountController, :unsubscribe)
end
+
+ post("/accounts/confirmation_resend", AccountController, :confirmation_resend)
+ end
+
+ scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
+ pipe_through([:api, :oauth_read_or_public])
+
+ get("/accounts/:id/scrobbles", ScrobbleController, :user_scrobbles)
end
scope "/api/v1", Pleroma.Web.MastodonAPI do
@@ -316,11 +339,11 @@ defmodule Pleroma.Web.Router do
scope [] do
pipe_through(:oauth_read)
- get("/accounts/verify_credentials", MastodonAPIController, :verify_credentials)
+ get("/accounts/verify_credentials", AccountController, :verify_credentials)
- get("/accounts/relationships", MastodonAPIController, :relationships)
+ get("/accounts/relationships", AccountController, :relationships)
- get("/accounts/:id/lists", MastodonAPIController, :account_lists)
+ get("/accounts/:id/lists", AccountController, :lists)
get("/accounts/:id/identity_proofs", MastodonAPIController, :empty_array)
get("/follow_requests", FollowRequestController, :index)
@@ -350,10 +373,10 @@ defmodule Pleroma.Web.Router do
get("/filters", FilterController, :index)
- get("/suggestions", MastodonAPIController, :suggestions)
+ get("/suggestions", SuggestionController, :index)
- get("/conversations", MastodonAPIController, :conversations)
- post("/conversations/:id/read", MastodonAPIController, :conversation_read)
+ get("/conversations", ConversationController, :index)
+ post("/conversations/:id/read", ConversationController, :read)
get("/endorsements", MastodonAPIController, :empty_array)
end
@@ -361,7 +384,7 @@ defmodule Pleroma.Web.Router do
scope [] do
pipe_through(:oauth_write)
- patch("/accounts/update_credentials", MastodonAPIController, :update_credentials)
+ patch("/accounts/update_credentials", AccountController, :update_credentials)
post("/statuses", StatusController, :create)
delete("/statuses/:id", StatusController, :delete)
@@ -380,10 +403,10 @@ defmodule Pleroma.Web.Router do
put("/scheduled_statuses/:id", ScheduledActivityController, :update)
delete("/scheduled_statuses/:id", ScheduledActivityController, :delete)
- post("/polls/:id/votes", MastodonAPIController, :poll_vote)
+ post("/polls/:id/votes", PollController, :vote)
- post("/media", MastodonAPIController, :upload)
- put("/media/:id", MastodonAPIController, :update_media)
+ post("/media", MediaController, :create)
+ put("/media/:id", MediaController, :update)
delete("/lists/:id", ListController, :delete)
post("/lists", ListController, :create)
@@ -397,36 +420,25 @@ defmodule Pleroma.Web.Router do
put("/filters/:id", FilterController, :update)
delete("/filters/:id", FilterController, :delete)
- patch("/pleroma/accounts/update_avatar", MastodonAPIController, :update_avatar)
- patch("/pleroma/accounts/update_banner", MastodonAPIController, :update_banner)
- patch("/pleroma/accounts/update_background", MastodonAPIController, :update_background)
-
- get("/pleroma/mascot", MastodonAPIController, :get_mascot)
- put("/pleroma/mascot", MastodonAPIController, :set_mascot)
-
- post("/reports", MastodonAPIController, :reports)
+ post("/reports", ReportController, :create)
end
scope [] do
pipe_through(:oauth_follow)
- post("/follows", MastodonAPIController, :follow)
- post("/accounts/:id/follow", MastodonAPIController, :follow)
-
- post("/accounts/:id/unfollow", MastodonAPIController, :unfollow)
- post("/accounts/:id/block", MastodonAPIController, :block)
- post("/accounts/:id/unblock", MastodonAPIController, :unblock)
- post("/accounts/:id/mute", MastodonAPIController, :mute)
- post("/accounts/:id/unmute", MastodonAPIController, :unmute)
+ post("/follows", MastodonAPIController, :follows)
+ post("/accounts/:id/follow", AccountController, :follow)
+ post("/accounts/:id/unfollow", AccountController, :unfollow)
+ post("/accounts/:id/block", AccountController, :block)
+ post("/accounts/:id/unblock", AccountController, :unblock)
+ post("/accounts/:id/mute", AccountController, :mute)
+ post("/accounts/:id/unmute", AccountController, :unmute)
post("/follow_requests/:id/authorize", FollowRequestController, :authorize)
post("/follow_requests/:id/reject", FollowRequestController, :reject)
post("/domain_blocks", DomainBlockController, :create)
delete("/domain_blocks", DomainBlockController, :delete)
-
- post("/pleroma/accounts/:id/subscribe", MastodonAPIController, :subscribe)
- post("/pleroma/accounts/:id/unsubscribe", MastodonAPIController, :unsubscribe)
end
scope [] do
@@ -448,12 +460,12 @@ defmodule Pleroma.Web.Router do
scope "/api/v1", Pleroma.Web.MastodonAPI do
pipe_through(:api)
- post("/accounts", MastodonAPIController, :account_register)
+ post("/accounts", AccountController, :create)
get("/instance", MastodonAPIController, :masto_instance)
get("/instance/peers", MastodonAPIController, :peers)
- post("/apps", MastodonAPIController, :create_app)
- get("/apps/verify_credentials", MastodonAPIController, :verify_app_credentials)
+ post("/apps", AppController, :create)
+ get("/apps/verify_credentials", AppController, :verify_credentials)
get("/custom_emojis", MastodonAPIController, :custom_emojis)
get("/statuses/:id/card", StatusController, :card)
@@ -465,12 +477,6 @@ defmodule Pleroma.Web.Router do
get("/accounts/search", SearchController, :account_search)
- post(
- "/pleroma/accounts/confirmation_resend",
- MastodonAPIController,
- :account_confirmation_resend
- )
-
scope [] do
pipe_through(:oauth_read_or_public)
@@ -482,16 +488,14 @@ defmodule Pleroma.Web.Router do
get("/statuses/:id", StatusController, :show)
get("/statuses/:id/context", StatusController, :context)
- get("/polls/:id", MastodonAPIController, :get_poll)
+ get("/polls/:id", PollController, :show)
- get("/accounts/:id/statuses", MastodonAPIController, :user_statuses)
- get("/accounts/:id/followers", MastodonAPIController, :followers)
- get("/accounts/:id/following", MastodonAPIController, :following)
- get("/accounts/:id", MastodonAPIController, :user)
+ get("/accounts/:id/statuses", AccountController, :statuses)
+ get("/accounts/:id/followers", AccountController, :followers)
+ get("/accounts/:id/following", AccountController, :following)
+ get("/accounts/:id", AccountController, :show)
get("/search", SearchController, :search)
-
- get("/pleroma/accounts/:id/favourites", MastodonAPIController, :user_favourites)
end
end
@@ -605,6 +609,7 @@ defmodule Pleroma.Web.Router do
scope [] do
pipe_through(:oauth_write)
post("/users/:nickname/outbox", ActivityPubController, :update_outbox)
+ post("/api/ap/upload_media", ActivityPubController, :upload_media)
end
scope [] do
@@ -656,10 +661,10 @@ defmodule Pleroma.Web.Router do
scope "/", Pleroma.Web.MastodonAPI do
pipe_through(:mastodon_html)
- get("/web/login", MastodonAPIController, :login)
- delete("/auth/sign_out", MastodonAPIController, :logout)
+ get("/web/login", AuthController, :login)
+ delete("/auth/sign_out", AuthController, :logout)
- post("/auth/password", MastodonAPIController, :password_reset)
+ post("/auth/password", AuthController, :password_reset)
scope [] do
pipe_through(:oauth_read)