diff options
Diffstat (limited to 'lib/pleroma/web/mastodon_api')
46 files changed, 852 insertions, 273 deletions
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index ea6e593..9226a2d 100755..100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -30,7 +30,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do alias Pleroma.Web.TwitterAPI.TwitterAPI alias Pleroma.Web.Utils.Params - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug(:skip_auth when action in [:create, :lookup]) @@ -92,7 +92,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do plug( RateLimiter, - [name: :relation_id_action, params: [:id, :uri]] when action in @relationship_actions + [name: :relation_id_action, params: ["id", "uri"]] when action in @relationship_actions ) plug(RateLimiter, [name: :relations_actions] when action in @relationship_actions) @@ -104,7 +104,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AccountOperation @doc "POST /api/v1/accounts" - def create(%{assigns: %{app: app}, body_params: params} = conn, _params) do + def create( + %{assigns: %{app: app}, private: %{open_api_spex: %{body_params: params}}} = conn, + _params + ) do with :ok <- validate_email_param(params), :ok <- TwitterAPI.validate_captcha(app, params), {:ok, user} <- TwitterAPI.register_user(params), @@ -168,7 +171,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do end @doc "PATCH /api/v1/accounts/update_credentials" - def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _params) do + def update_credentials( + %{assigns: %{user: user}, private: %{open_api_spex: %{body_params: params}}} = conn, + _params + ) do params = params |> Enum.filter(fn {_, value} -> not is_nil(value) end) @@ -235,7 +241,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do # So we first build the normal local changeset, then apply it to the # user data, but don't persist it. With this, we generate the object # data for our update activity. We feed this and the changeset as meta - # inforation into the pipeline, where they will be properly updated and + # information into the pipeline, where they will be properly updated and # federated. with changeset <- User.update_changeset(user, user_params), {:ok, unpersisted_user} <- Ecto.Changeset.apply_action(changeset, :update), @@ -263,24 +269,36 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do {:error, %Ecto.Changeset{errors: [background: {"file is too large", _}]}} -> render_error(conn, :request_entity_too_large, "File is too large") + {:error, %Ecto.Changeset{errors: [{:bio, {_, _}} | _]}} -> + render_error(conn, :request_entity_too_large, "Bio is too long") + + {:error, %Ecto.Changeset{errors: [{:name, {_, _}} | _]}} -> + render_error(conn, :request_entity_too_large, "Name is too long") + + {:error, %Ecto.Changeset{errors: [{:fields, {"invalid", _}} | _]}} -> + render_error(conn, :request_entity_too_large, "One or more field entries are too long") + + {:error, %Ecto.Changeset{errors: [{:fields, {_, _}} | _]}} -> + render_error(conn, :request_entity_too_large, "Too many field entries") + _e -> render_error(conn, :forbidden, "Invalid request") end end defp normalize_fields_attributes(fields) do - if Enum.all?(fields, &is_tuple/1) do - Enum.map(fields, fn {_, v} -> v end) - else - Enum.map(fields, fn - %{} = field -> %{"name" => field.name, "value" => field.value} - field -> field - end) - end + if(Enum.all?(fields, &is_tuple/1), do: Enum.map(fields, fn {_, v} -> v end), else: fields) + |> Enum.map(fn + %{} = field -> %{"name" => field.name, "value" => field.value} + field -> field + end) end @doc "GET /api/v1/accounts/relationships" - def relationships(%{assigns: %{user: user}} = conn, %{id: id}) do + def relationships( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, + _ + ) do targets = User.get_all_by_ids(List.wrap(id)) render(conn, "relationships.json", user: user, targets: targets) @@ -290,7 +308,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do 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} = params) do + def show( + %{ + assigns: %{user: for_user}, + private: %{open_api_spex: %{params: %{id: nickname_or_id} = params}} + } = conn, + _params + ) do with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id, for: for_user), :visible <- User.visible_for(user, for_user) do render(conn, "show.json", @@ -304,7 +328,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do end @doc "GET /api/v1/accounts/:id/statuses" - def statuses(%{assigns: %{user: reading_user}} = conn, params) do + def statuses( + %{assigns: %{user: reading_user}, private: %{open_api_spex: %{params: params}}} = conn, + _params + ) do with %User{} = user <- User.get_cached_by_nickname_or_id(params.id, for: reading_user), :visible <- User.visible_for(user, reading_user) do params = @@ -339,7 +366,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do end @doc "GET /api/v1/accounts/:id/followers" - def followers(%{assigns: %{user: for_user, account: user}} = conn, params) do + def followers( + %{assigns: %{user: for_user, account: user}, private: %{open_api_spex: %{params: params}}} = + conn, + _params + ) do params = params |> Enum.map(fn {key, value} -> {to_string(key), value} end) @@ -364,7 +395,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do end @doc "GET /api/v1/accounts/:id/following" - def following(%{assigns: %{user: for_user, account: user}} = conn, params) do + def following( + %{assigns: %{user: for_user, account: user}, private: %{open_api_spex: %{params: params}}} = + conn, + _params + ) do params = params |> Enum.map(fn {key, value} -> {to_string(key), value} end) @@ -402,7 +437,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do {:error, "Can not follow yourself"} end - def follow(%{body_params: params, assigns: %{user: follower, account: followed}} = conn, _) do + def follow( + %{ + assigns: %{user: follower, account: followed}, + private: %{open_api_spex: %{body_params: params}} + } = conn, + _ + ) do with {:ok, follower} <- MastodonAPI.follow(follower, followed, params) do render(conn, "relationship.json", user: follower, target: followed) else @@ -422,7 +463,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do end @doc "POST /api/v1/accounts/:id/mute" - def mute(%{assigns: %{user: muter, account: muted}, body_params: params} = conn, _params) do + def mute( + %{ + assigns: %{user: muter, account: muted}, + private: %{open_api_spex: %{body_params: params}} + } = conn, + _params + ) do params = params |> Map.put_new(:duration, Map.get(params, :expires_in, 0)) @@ -463,7 +510,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do @doc "POST /api/v1/accounts/:id/note" def note( - %{assigns: %{user: noter, account: target}, body_params: %{comment: comment}} = conn, + %{ + assigns: %{user: noter, account: target}, + private: %{open_api_spex: %{body_params: %{comment: comment}}} + } = conn, _params ) do with {:ok, _user_note} <- UserNote.create(noter, target, comment) do @@ -504,7 +554,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do end @doc "POST /api/v1/follows" - def follow_by_uri(%{body_params: %{uri: uri}} = conn, _) do + def follow_by_uri(%{private: %{open_api_spex: %{body_params: %{uri: uri}}}} = conn, _) do case User.get_cached_by_nickname(uri) do %User{} = user -> conn @@ -543,11 +593,16 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do conn |> add_link_headers(users) - |> render("index.json", users: users, for: user, as: :user) + |> render("index.json", + users: users, + for: user, + as: :user, + embed_relationships: embed_relationships?(params) + ) end @doc "GET /api/v1/accounts/lookup" - def lookup(conn, %{acct: nickname} = _params) do + def lookup(%{private: %{open_api_spex: %{params: %{acct: nickname}}}} = conn, _params) do with %User{} = user <- User.get_by_nickname(nickname) do render(conn, "show.json", user: user, diff --git a/lib/pleroma/web/mastodon_api/controllers/announcement_controller.ex b/lib/pleroma/web/mastodon_api/controllers/announcement_controller.ex index 080af96..080af96 100755..100644 --- a/lib/pleroma/web/mastodon_api/controllers/announcement_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/announcement_controller.ex diff --git a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex index 844673a..844673a 100755..100644 --- a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex index fbb54a1..fbb54a1 100755..100644 --- a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex diff --git a/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex b/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex index 9cc6225..9cc6225 100755..100644 --- a/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex diff --git a/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex b/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex index 8b27b0b..8b27b0b 100755..100644 --- a/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex diff --git a/lib/pleroma/web/mastodon_api/controllers/directory_controller.ex b/lib/pleroma/web/mastodon_api/controllers/directory_controller.ex index 253f06c..f894259 100755..100644 --- a/lib/pleroma/web/mastodon_api/controllers/directory_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/directory_controller.ex @@ -15,7 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.DirectoryController do plug(Pleroma.Web.ApiSpec.CastAndValidate) - plug(:skip_auth when action == "index") + plug(:skip_auth when action == :index) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.DirectoryOperation diff --git a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex index b2e347e..4615794 100755..100644 --- a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex @@ -8,7 +8,7 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockController do alias Pleroma.User alias Pleroma.Web.Plugs.OAuthScopesPlug - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.DomainBlockOperation plug( @@ -27,23 +27,31 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockController do end @doc "POST /api/v1/domain_blocks" - def create(%{assigns: %{user: blocker}, body_params: %{domain: domain}} = conn, _params) do + def create( + %{assigns: %{user: blocker}, private: %{open_api_spex: %{body_params: %{domain: domain}}}} = + conn, + _params + ) do User.block_domain(blocker, domain) json(conn, %{}) end - def create(%{assigns: %{user: blocker}} = conn, %{domain: domain}) do + def create(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do User.block_domain(blocker, domain) json(conn, %{}) end @doc "DELETE /api/v1/domain_blocks" - def delete(%{assigns: %{user: blocker}, body_params: %{domain: domain}} = conn, _params) do + def delete( + %{assigns: %{user: blocker}, private: %{open_api_spex: %{body_params: %{domain: domain}}}} = + conn, + _params + ) do User.unblock_domain(blocker, domain) json(conn, %{}) end - def delete(%{assigns: %{user: blocker}} = conn, %{domain: domain}) do + def delete(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do User.unblock_domain(blocker, domain) json(conn, %{}) end diff --git a/lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex b/lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex index 1c650eb..1c650eb 100755..100644 --- a/lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex diff --git a/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex b/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex index 0959b4b..0959b4b 100755..100644 --- a/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex 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 ba6d074..6eee55d 100755..100644 --- a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestController do alias Pleroma.Web.CommonAPI alias Pleroma.Web.Plugs.OAuthScopesPlug - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug(:assign_follower when action != :index) action_fallback(:errors) @@ -44,7 +44,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestController do end end - defp assign_follower(%{params: %{id: id}} = conn, _) do + defp assign_follower(%{private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) do case User.get_cached_by_id(id) do %User{} = follower -> assign(conn, :follower, follower) nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt() diff --git a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex index 6410e87..3e66490 100755..100644 --- a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceController do plug(Pleroma.Web.ApiSpec.CastAndValidate) - plug(:skip_auth when action in [:show, :peers]) + plug(:skip_auth when action in [:show, :show2, :peers]) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.InstanceOperation @@ -16,6 +16,11 @@ defmodule Pleroma.Web.MastodonAPI.InstanceController do render(conn, "show.json") end + @doc "GET /api/v2/instance" + def show2(conn, _params) do + render(conn, "show2.json") + end + @doc "GET /api/v1/instance/peers" def peers(conn, _params) do json(conn, Pleroma.Stats.get_peers()) diff --git a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex index 2117aae..3bfc365 100755..100644 --- a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex @@ -11,7 +11,7 @@ defmodule Pleroma.Web.MastodonAPI.ListController do @oauth_read_actions [:index, :show, :list_accounts] - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug(:list_by_id_and_user when action not in [:index, :create]) plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action in @oauth_read_actions) plug(OAuthScopesPlug, %{scopes: ["write:lists"]} when action not in @oauth_read_actions) @@ -21,25 +21,33 @@ defmodule Pleroma.Web.MastodonAPI.ListController do defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ListOperation # GET /api/v1/lists - def index(%{assigns: %{user: user}} = conn, opts) do - lists = Pleroma.List.for_user(user, opts) + def index(%{assigns: %{user: user}, private: %{open_api_spex: %{params: params}}} = conn, _) do + lists = Pleroma.List.for_user(user, params) render(conn, "index.json", lists: lists) end # POST /api/v1/lists - def create(%{assigns: %{user: user}, body_params: %{title: title}} = conn, _) do + def create( + %{assigns: %{user: user}, private: %{open_api_spex: %{body_params: %{title: title}}}} = + conn, + _ + ) do with {:ok, %Pleroma.List{} = list} <- Pleroma.List.create(title, user) do render(conn, "show.json", list: list) end end - # GET /api/v1/lists/:id + # GET /api/v1/lists/:idOB def show(%{assigns: %{list: list}} = conn, _) do render(conn, "show.json", list: list) end # PUT /api/v1/lists/:id - def update(%{assigns: %{list: list}, body_params: %{title: title}} = conn, _) do + def update( + %{assigns: %{list: list}, private: %{open_api_spex: %{body_params: %{title: title}}}} = + conn, + _ + ) do with {:ok, list} <- Pleroma.List.rename(list, title) do render(conn, "show.json", list: list) end @@ -62,7 +70,13 @@ defmodule Pleroma.Web.MastodonAPI.ListController do end # POST /api/v1/lists/:id/accounts - def add_to_list(%{assigns: %{list: list}, body_params: %{account_ids: account_ids}} = conn, _) do + def add_to_list( + %{ + assigns: %{list: list}, + private: %{open_api_spex: %{body_params: %{account_ids: account_ids}}} + } = conn, + _ + ) do Enum.each(account_ids, fn account_id -> with %User{} = followed <- User.get_cached_by_id(account_id) do Pleroma.List.follow(list, followed) @@ -74,9 +88,22 @@ defmodule Pleroma.Web.MastodonAPI.ListController do # DELETE /api/v1/lists/:id/accounts def remove_from_list( - %{assigns: %{list: list}, params: %{account_ids: account_ids}} = conn, + %{ + private: %{open_api_spex: %{params: %{account_ids: account_ids}}} + } = conn, _ ) do + do_remove_from_list(conn, account_ids) + end + + def remove_from_list( + %{private: %{open_api_spex: %{body_params: %{account_ids: account_ids}}}} = conn, + _ + ) do + do_remove_from_list(conn, account_ids) + end + + defp do_remove_from_list(%{assigns: %{list: list}} = conn, account_ids) do Enum.each(account_ids, fn account_id -> with %User{} = followed <- User.get_cached_by_id(account_id) do Pleroma.List.unfollow(list, followed) @@ -86,11 +113,10 @@ defmodule Pleroma.Web.MastodonAPI.ListController do json(conn, %{}) end - def remove_from_list(%{body_params: params} = conn, _) do - remove_from_list(%{conn | params: params}, %{}) - end - - defp list_by_id_and_user(%{assigns: %{user: user}, params: %{id: id}} = conn, _) do + defp list_by_id_and_user( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, + _ + ) do case Pleroma.List.get(id, user) do %Pleroma.List{} = list -> assign(conn, :list, list) nil -> conn |> render_error(:not_found, "List not found") |> halt() diff --git a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex index 4ad30f3..4ad30f3 100755..100644 --- a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex 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 0aa7b37..0aa7b37 100755..100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex diff --git a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex index 7d9a63c..056bad8 100755..100644 --- a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex @@ -12,7 +12,7 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do action_fallback(Pleroma.Web.MastodonAPI.FallbackController) plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:create, :create2]) - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug(OAuthScopesPlug, %{scopes: ["read:media"]} when action == :show) plug(OAuthScopesPlug, %{scopes: ["write:media"]} when action != :show) @@ -20,7 +20,11 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.MediaOperation @doc "POST /api/v1/media" - def create(%{assigns: %{user: user}, body_params: %{file: file} = data} = conn, _) do + def create( + %{assigns: %{user: user}, private: %{open_api_spex: %{body_params: %{file: file} = data}}} = + conn, + _ + ) do with {:ok, object} <- ActivityPub.upload( file, @@ -36,7 +40,11 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do def create(_conn, _data), do: {:error, :bad_request} @doc "POST /api/v2/media" - def create2(%{assigns: %{user: user}, body_params: %{file: file} = data} = conn, _) do + def create2( + %{assigns: %{user: user}, private: %{open_api_spex: %{body_params: %{file: file} = data}}} = + conn, + _ + ) do with {:ok, object} <- ActivityPub.upload( file, @@ -54,7 +62,15 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do def create2(_conn, _data), do: {:error, :bad_request} @doc "PUT /api/v1/media/:id" - def update(%{assigns: %{user: user}, body_params: %{description: description}} = conn, %{id: id}) do + def update( + %{ + assigns: %{user: user}, + private: %{ + open_api_spex: %{body_params: %{description: description}, params: %{id: id}} + } + } = conn, + _ + ) do with %Object{} = object <- Object.get_by_id(id), :ok <- Object.authorize_access(object, user), {:ok, %Object{data: data}} <- Object.update_data(object, %{"name" => description}) do @@ -67,7 +83,7 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do def update(conn, data), do: show(conn, data) @doc "GET /api/v1/media/:id" - def show(%{assigns: %{user: user}} = conn, %{id: id}) do + def show(%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) do with %Object{data: data, id: object_id} = object <- Object.get_by_id(id), :ok <- Object.authorize_access(object, user) do attachment_data = Map.put(data, "id", object_id) diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex index a490e83..e305aea 100755..100644 --- a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex @@ -13,7 +13,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do @oauth_read_actions [:show, :index] - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug( OAuthScopesPlug, @@ -24,8 +24,20 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.NotificationOperation + @default_notification_types ~w{ + mention + follow + follow_request + reblog + favourite + move + pleroma:emoji_reaction + poll + update + } + # GET /api/v1/notifications - def index(conn, %{account_id: account_id} = params) do + def index(%{private: %{open_api_spex: %{params: %{account_id: account_id} = params}}} = conn, _) do case Pleroma.User.get_cached_by_id(account_id) do %{ap_id: account_ap_id} -> params = @@ -33,7 +45,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do |> Map.delete(:account_id) |> Map.put(:account_ap_id, account_ap_id) - index(conn, params) + do_get_notifications(conn, params) _ -> conn @@ -42,18 +54,11 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do end end - @default_notification_types ~w{ - mention - follow - follow_request - reblog - favourite - move - pleroma:emoji_reaction - poll - update - } - def index(%{assigns: %{user: user}} = conn, params) do + def index(%{private: %{open_api_spex: %{params: params}}} = conn, _) do + do_get_notifications(conn, params) + end + + defp do_get_notifications(%{assigns: %{user: user}} = conn, params) do params = Map.new(params, fn {k, v} -> {to_string(k), v} end) |> Map.put_new("types", Map.get(params, :include_types, @default_notification_types)) @@ -69,7 +74,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do end # GET /api/v1/notifications/:id - def show(%{assigns: %{user: user}} = conn, %{id: id}) do + def show(%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) do with {:ok, notification} <- Notification.get(user, id) do render(conn, "show.json", notification: notification, for: user) else @@ -88,8 +93,20 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do # POST /api/v1/notifications/:id/dismiss - def dismiss(%{assigns: %{user: user}} = conn, %{id: id} = _params) do - with {:ok, _notif} <- Notification.dismiss(user, id) do + def dismiss(%{private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) do + do_dismiss(conn, id) + end + + # POST /api/v1/notifications/dismiss (deprecated) + def dismiss_via_body( + %{private: %{open_api_spex: %{body_params: %{id: id}}}} = conn, + _ + ) do + do_dismiss(conn, id) + end + + defp do_dismiss(%{assigns: %{user: user}} = conn, notification_id) do + with {:ok, _notif} <- Notification.dismiss(user, notification_id) do json(conn, %{}) else {:error, reason} -> @@ -99,13 +116,11 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do end end - # POST /api/v1/notifications/dismiss (deprecated) - def dismiss_via_body(%{body_params: params} = conn, _) do - dismiss(conn, params) - end - # DELETE /api/v1/notifications/destroy_multiple - def destroy_multiple(%{assigns: %{user: user}} = conn, %{ids: ids} = _params) do + def destroy_multiple( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{ids: ids}}}} = conn, + _ + ) do Notification.destroy_multiple(user, ids) json(conn, %{}) end diff --git a/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex b/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex index 002c210..b074ee4 100755..100644 --- a/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex @@ -15,7 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.PollController do action_fallback(Pleroma.Web.MastodonAPI.FallbackController) - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug( OAuthScopesPlug, @@ -29,7 +29,7 @@ defmodule Pleroma.Web.MastodonAPI.PollController do @cachex Pleroma.Config.get([:cachex, :provider], Cachex) @doc "GET /api/v1/polls/:id" - def show(%{assigns: %{user: user}} = conn, %{id: id}) do + def show(%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) 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 @@ -41,7 +41,13 @@ defmodule Pleroma.Web.MastodonAPI.PollController do end @doc "POST /api/v1/polls/:id/votes" - def vote(%{assigns: %{user: user}, body_params: %{choices: choices}} = conn, %{id: id}) do + def vote( + %{ + assigns: %{user: user}, + private: %{open_api_spex: %{body_params: %{choices: choices}, params: %{id: id}}} + } = conn, + _ + ) 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), diff --git a/lib/pleroma/web/mastodon_api/controllers/report_controller.ex b/lib/pleroma/web/mastodon_api/controllers/report_controller.ex index 3db80d7..3db80d7 100755..100644 --- a/lib/pleroma/web/mastodon_api/controllers/report_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/report_controller.ex diff --git a/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex b/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex index 0392fce..1b7095e 100755..100644 --- a/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex @@ -13,7 +13,7 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityController do @oauth_read_actions [:show, :index] - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in @oauth_read_actions) plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action not in @oauth_read_actions) plug(:assign_scheduled_activity when action != :index) @@ -23,7 +23,7 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityController do defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ScheduledActivityOperation @doc "GET /api/v1/scheduled_statuses" - def index(%{assigns: %{user: user}} = conn, params) do + def index(%{assigns: %{user: user}, private: %{open_api_spex: %{params: params}}} = conn, _) do params = Map.new(params, fn {key, value} -> {to_string(key), value} end) with scheduled_activities <- MastodonAPI.get_scheduled_activities(user, params) do @@ -39,7 +39,13 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityController do end @doc "PUT /api/v1/scheduled_statuses/:id" - def update(%{assigns: %{scheduled_activity: scheduled_activity}, body_params: params} = conn, _) do + def update( + %{ + assigns: %{scheduled_activity: scheduled_activity}, + private: %{open_api_spex: %{body_params: params}} + } = conn, + _ + ) do with {:ok, scheduled_activity} <- ScheduledActivity.update(scheduled_activity, params) do render(conn, "show.json", scheduled_activity: scheduled_activity) end @@ -52,7 +58,10 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityController do end end - defp assign_scheduled_activity(%{assigns: %{user: user}, params: %{id: id}} = conn, _) do + defp assign_scheduled_activity( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, + _ + ) do case ScheduledActivity.get(user, id) do %ScheduledActivity{} = activity -> assign(conn, :scheduled_activity, activity) nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt() diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex index 5e6e047..628aa31 100755..100644 --- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex @@ -5,7 +5,6 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do use Pleroma.Web, :controller - alias Pleroma.Activity alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.ControllerHelper @@ -19,7 +18,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do @search_limit 40 - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) # Note: Mastodon doesn't allow unauthenticated access (requires read:accounts / read:search) plug(OAuthScopesPlug, %{scopes: ["read:search"], fallback: :proceed_unauthenticated}) @@ -30,7 +29,11 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.SearchOperation - def account_search(%{assigns: %{user: user}} = conn, %{q: query} = params) do + def account_search( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{q: query} = params}}} = + conn, + _ + ) do accounts = User.search(query, search_options(params, user)) conn @@ -45,7 +48,12 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do def search2(conn, params), do: do_search(:v2, conn, params) def search(conn, params), do: do_search(:v1, conn, params) - defp do_search(version, %{assigns: %{user: user}} = conn, %{q: query} = params) do + defp do_search( + version, + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{q: query} = params}}} = + conn, + _ + ) do query = String.trim(query) options = search_options(params, user) timeout = Keyword.get(Repo.config(), :timeout, 15_000) @@ -100,7 +108,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do end defp resource_search(_, "statuses", query, options) do - statuses = with_fallback(fn -> Activity.search(options[:for_user], query, options) end) + statuses = with_fallback(fn -> Pleroma.Search.search(query, options) end) StatusView.render("index.json", activities: statuses, @@ -148,7 +156,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do tags end - Pleroma.Pagination.paginate(tags, options) + Pleroma.Pagination.paginate_list(tags, options) end defp add_joined_tag(tags) do diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index e594ea4..83e1bee 100755..100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -12,6 +12,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do alias Pleroma.Activity alias Pleroma.Bookmark + alias Pleroma.BookmarkFolder alias Pleroma.Object alias Pleroma.Repo alias Pleroma.ScheduledActivity @@ -25,7 +26,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do alias Pleroma.Web.Plugs.OAuthScopesPlug alias Pleroma.Web.Plugs.RateLimiter - plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug(:skip_public_check when action in [:index, :show]) @@ -37,7 +38,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do when action in [ :index, :show, - :card, :context, :show_history, :show_source @@ -110,7 +110,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do `ids` query param is required """ - def index(%{assigns: %{user: user}} = conn, %{ids: ids} = params) do + def index( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{ids: ids} = params}}} = + conn, + _ + ) do limit = 100 activities = @@ -134,7 +138,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do def create( %{ assigns: %{user: user}, - body_params: %{status: _, scheduled_at: scheduled_at} = params + private: %{ + open_api_spex: %{body_params: %{status: _, scheduled_at: scheduled_at} = params} + } } = conn, _ ) @@ -156,7 +162,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do else {:far_enough, _} -> params = Map.drop(params, [:scheduled_at]) - create(%Plug.Conn{conn | body_params: params}, %{}) + + put_in( + conn, + [Access.key(:private), Access.key(:open_api_spex), Access.key(:body_params)], + params + ) + |> do_create error -> error @@ -164,7 +176,35 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end # Creates a regular status - def create(%{assigns: %{user: user}, body_params: %{status: _} = params} = conn, _) do + def create( + %{ + private: %{open_api_spex: %{body_params: %{status: _}}} + } = conn, + _ + ) do + do_create(conn) + end + + def create( + %{ + assigns: %{user: _user}, + private: %{open_api_spex: %{body_params: %{media_ids: _} = params}} + } = conn, + _ + ) do + params = Map.put(params, :status, "") + + put_in( + conn, + [Access.key(:private), Access.key(:open_api_spex), Access.key(:body_params)], + params + ) + |> do_create + end + + defp do_create( + %{assigns: %{user: user}, private: %{open_api_spex: %{body_params: params}}} = conn + ) do params = Map.put(params, :in_reply_to_status_id, params[:in_reply_to_id]) |> put_application(conn) @@ -189,13 +229,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end end - def create(%{assigns: %{user: _user}, body_params: %{media_ids: _} = params} = conn, _) do - params = Map.put(params, :status, "") - create(%Plug.Conn{conn | body_params: params}, %{}) - end - @doc "GET /api/v1/statuses/:id/history" - def show_history(%{assigns: assigns} = conn, %{id: id} = params) do + def show_history( + %{assigns: assigns, private: %{open_api_spex: %{params: %{id: id} = params}}} = conn, + _ + ) do with user = assigns[:user], %Activity{} = activity <- Activity.get_by_id_with_object(id), true <- Visibility.visible_for_user?(activity, user) do @@ -211,7 +249,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "GET /api/v1/statuses/:id/source" - def show_source(%{assigns: assigns} = conn, %{id: id} = _params) do + def show_source(%{assigns: assigns, private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) do with user = assigns[:user], %Activity{} = activity <- Activity.get_by_id_with_object(id), true <- Visibility.visible_for_user?(activity, user) do @@ -225,7 +263,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "PUT /api/v1/statuses/:id" - def update(%{assigns: %{user: user}, body_params: body_params} = conn, %{id: id} = params) do + def update( + %{ + assigns: %{user: user}, + private: %{open_api_spex: %{body_params: body_params, params: %{id: id} = params}} + } = conn, + _ + ) do with {_, %Activity{}} = {_, activity} <- {:activity, Activity.get_by_id_with_object(id)}, {_, true} <- {:visible, Visibility.visible_for_user?(activity, user)}, {_, true} <- {:is_create, activity.data["type"] == "Create"}, @@ -248,7 +292,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "GET /api/v1/statuses/:id" - def show(%{assigns: %{user: user}} = conn, %{id: id} = params) do + def show( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id} = params}}} = + conn, + _ + ) do with %Activity{} = activity <- Activity.get_by_id_with_object(id), true <- Visibility.visible_for_user?(activity, user) do try_render(conn, "show.json", @@ -263,7 +311,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "DELETE /api/v1/statuses/:id" - def delete(%{assigns: %{user: user}} = conn, %{id: id}) do + def delete(%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) do with %Activity{} = activity <- Activity.get_by_id_with_object(id), {:ok, %Activity{}} <- CommonAPI.delete(id, user) do try_render(conn, "show.json", @@ -278,7 +326,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "POST /api/v1/statuses/:id/reblog" - def reblog(%{assigns: %{user: user}, body_params: params} = conn, %{id: ap_id_or_id}) do + def reblog( + %{ + assigns: %{user: user}, + private: %{open_api_spex: %{body_params: params, params: %{id: ap_id_or_id}}} + } = conn, + _ + ) do with {:ok, announce} <- 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}) @@ -286,7 +340,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "POST /api/v1/statuses/:id/unreblog" - def unreblog(%{assigns: %{user: user}} = conn, %{id: activity_id}) do + def unreblog( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: activity_id}}}} = + conn, + _ + ) do with {:ok, _unannounce} <- CommonAPI.unrepeat(activity_id, user), %Activity{} = activity <- Activity.get_by_id(activity_id) do try_render(conn, "show.json", %{activity: activity, for: user, as: :activity}) @@ -294,7 +352,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "POST /api/v1/statuses/:id/favourite" - def favourite(%{assigns: %{user: user}} = conn, %{id: activity_id}) do + def favourite( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: activity_id}}}} = + conn, + _ + ) do with {:ok, _fav} <- CommonAPI.favorite(user, activity_id), %Activity{} = activity <- Activity.get_by_id(activity_id) do try_render(conn, "show.json", activity: activity, for: user, as: :activity) @@ -302,7 +364,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "POST /api/v1/statuses/:id/unfavourite" - def unfavourite(%{assigns: %{user: user}} = conn, %{id: activity_id}) do + def unfavourite( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: activity_id}}}} = + conn, + _ + ) do with {:ok, _unfav} <- CommonAPI.unfavorite(activity_id, user), %Activity{} = activity <- Activity.get_by_id(activity_id) do try_render(conn, "show.json", activity: activity, for: user, as: :activity) @@ -310,7 +376,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "POST /api/v1/statuses/:id/pin" - def pin(%{assigns: %{user: user}} = conn, %{id: ap_id_or_id}) do + def pin( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: ap_id_or_id}}}} = + conn, + _ + ) do with {:ok, activity} <- CommonAPI.pin(ap_id_or_id, user) do try_render(conn, "show.json", activity: activity, for: user, as: :activity) else @@ -329,24 +399,43 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "POST /api/v1/statuses/:id/unpin" - def unpin(%{assigns: %{user: user}} = conn, %{id: ap_id_or_id}) do + def unpin( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: ap_id_or_id}}}} = + conn, + _ + ) do with {:ok, activity} <- CommonAPI.unpin(ap_id_or_id, user) do try_render(conn, "show.json", activity: activity, for: user, as: :activity) end end @doc "POST /api/v1/statuses/:id/bookmark" - def bookmark(%{assigns: %{user: user}} = conn, %{id: id}) do + def bookmark( + %{ + assigns: %{user: user}, + private: %{open_api_spex: %{body_params: body_params, params: %{id: id}}} + } = conn, + _ + ) do with %Activity{} = activity <- Activity.get_by_id_with_object(id), %User{} = user <- User.get_cached_by_nickname(user.nickname), true <- Visibility.visible_for_user?(activity, user), - {:ok, _bookmark} <- Bookmark.create(user.id, activity.id) do + folder_id <- Map.get(body_params, :folder_id, nil), + folder_id <- + if(folder_id && BookmarkFolder.belongs_to_user?(folder_id, user.id), + do: folder_id, + else: nil + ), + {:ok, _bookmark} <- Bookmark.create(user.id, activity.id, folder_id) do try_render(conn, "show.json", activity: activity, for: user, as: :activity) end end @doc "POST /api/v1/statuses/:id/unbookmark" - def unbookmark(%{assigns: %{user: user}} = conn, %{id: id}) do + def unbookmark( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, + _ + ) do with %Activity{} = activity <- Activity.get_by_id_with_object(id), %User{} = user <- User.get_cached_by_nickname(user.nickname), true <- Visibility.visible_for_user?(activity, user), @@ -356,7 +445,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "POST /api/v1/statuses/:id/mute" - def mute_conversation(%{assigns: %{user: user}, body_params: params} = conn, %{id: id}) do + def mute_conversation( + %{ + assigns: %{user: user}, + private: %{open_api_spex: %{body_params: params, params: %{id: id}}} + } = conn, + _ + ) do with %Activity{} = activity <- Activity.get_by_id(id), {:ok, activity} <- CommonAPI.add_mute(user, activity, params) do try_render(conn, "show.json", activity: activity, for: user, as: :activity) @@ -364,27 +459,24 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "POST /api/v1/statuses/:id/unmute" - def unmute_conversation(%{assigns: %{user: user}} = conn, %{id: id}) do + def unmute_conversation( + %{ + assigns: %{user: user}, + private: %{open_api_spex: %{params: %{id: id}}} + } = conn, + _ + ) do with %Activity{} = activity <- Activity.get_by_id(id), {:ok, activity} <- CommonAPI.remove_mute(user, activity) do try_render(conn, "show.json", activity: activity, for: user, as: :activity) end end - @doc "GET /api/v1/statuses/:id/card" - @deprecated "https://github.com/tootsuite/mastodon/pull/11213" - def card(%{assigns: %{user: user}} = conn, %{id: status_id}) do - with %Activity{} = activity <- Activity.get_by_id(status_id), - true <- Visibility.visible_for_user?(activity, user) do - data = Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) - render(conn, "card.json", data) - else - _ -> render_error(conn, :not_found, "Record not found") - end - end - @doc "GET /api/v1/statuses/:id/favourited_by" - def favourited_by(%{assigns: %{user: user}} = conn, %{id: id}) do + def favourited_by( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, + _ + ) do with true <- Pleroma.Config.get([:instance, :show_reactions]), %Activity{} = activity <- Activity.get_by_id_with_object(id), {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)}, @@ -405,7 +497,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "GET /api/v1/statuses/:id/reblogged_by" - def reblogged_by(%{assigns: %{user: user}} = conn, %{id: id}) do + def reblogged_by( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, + _ + ) do with %Activity{} = activity <- Activity.get_by_id_with_object(id), {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)}, %Object{data: %{"announcements" => announces, "id" => ap_id}} <- @@ -437,7 +532,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "GET /api/v1/statuses/:id/context" - def context(%{assigns: %{user: user}} = conn, %{id: id}) do + def context( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, + _ + ) do with %Activity{} = activity <- Activity.get_by_id(id) do activities = ActivityPub.fetch_activities_for_context(activity.data["context"], %{ @@ -451,7 +549,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "GET /api/v1/favourites" - def favourites(%{assigns: %{user: %User{} = user}} = conn, params) do + def favourites( + %{assigns: %{user: %User{} = user}, private: %{open_api_spex: %{params: params}}} = conn, + _ + ) do activities = ActivityPub.fetch_favourites(user, params) conn @@ -464,12 +565,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "GET /api/v1/bookmarks" - def bookmarks(%{assigns: %{user: user}} = conn, params) do + def bookmarks(%{assigns: %{user: user}, private: %{open_api_spex: %{params: params}}} = conn, _) do user = User.get_cached_by_id(user.id) + folder_id = Map.get(params, :folder_id) bookmarks = user.id - |> Bookmark.for_user_query() + |> Bookmark.for_user_query(folder_id) |> Pleroma.Pagination.fetch_paginated(params) activities = diff --git a/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex b/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex index 9cc0071..9cc0071 100755..100644 --- a/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex diff --git a/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex b/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex index 69ae70a..69ae70a 100755..100644 --- a/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex index 293c61b..293c61b 100755..100644 --- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex index 467dc2f..467dc2f 100755..100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index cc3e358..267c3e3 100755..100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.AccountView do @@ -194,6 +194,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do end defp do_render("show.json", %{user: user} = opts) do + self = opts[:for] == user + user = User.sanitize_html(user, User.html_filter_policy(opts[:for])) display_name = user.name || user.nickname @@ -203,16 +205,16 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do header_static = User.banner_url(user) |> MediaProxy.preview_url(static: true) following_count = - if !user.hide_follows_count or !user.hide_follows or opts[:for] == user, + if !user.hide_follows_count or !user.hide_follows or self, do: user.following_count, else: 0 followers_count = - if !user.hide_followers_count or !user.hide_followers or opts[:for] == user, + if !user.hide_followers_count or !user.hide_followers or self, do: user.follower_count, else: 0 - bot = user.actor_type == "Service" + bot = bot?(user) emojis = Enum.map(user.emoji, fn {shortcode, raw_url} -> @@ -249,6 +251,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do nil end + last_status_at = + user.last_status_at && + user.last_status_at |> NaiveDateTime.to_date() |> Date.to_iso8601() + %{ id: to_string(user.id), username: username_from_nickname(user.nickname), @@ -277,7 +283,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do actor_type: user.actor_type } }, - last_status_at: user.last_status_at, + last_status_at: last_status_at, # Pleroma extensions # Note: it's insecure to output :email but fully-qualified nickname may serve as safe stub @@ -464,4 +470,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do defp image_url(%{"url" => [%{"href" => href} | _]}), do: href defp image_url(_), do: nil + + defp bot?(user) do + # Because older and/or Mastodon clients may not recognize a Group actor properly, + # and currently the group actor can only boost things, we should let these clients + # think groups are bots. + # See https://git.pleroma.social/pleroma/pleroma-meta/-/issues/14 + user.actor_type == "Service" || user.actor_type == "Group" + end end diff --git a/lib/pleroma/web/mastodon_api/views/announcement_view.ex b/lib/pleroma/web/mastodon_api/views/announcement_view.ex index 93fdfb1..93fdfb1 100755..100644 --- a/lib/pleroma/web/mastodon_api/views/announcement_view.ex +++ b/lib/pleroma/web/mastodon_api/views/announcement_view.ex diff --git a/lib/pleroma/web/mastodon_api/views/app_view.ex b/lib/pleroma/web/mastodon_api/views/app_view.ex index 92cccd4..92cccd4 100755..100644 --- a/lib/pleroma/web/mastodon_api/views/app_view.ex +++ b/lib/pleroma/web/mastodon_api/views/app_view.ex diff --git a/lib/pleroma/web/mastodon_api/views/conversation_view.ex b/lib/pleroma/web/mastodon_api/views/conversation_view.ex index f6577cd..f6577cd 100755..100644 --- a/lib/pleroma/web/mastodon_api/views/conversation_view.ex +++ b/lib/pleroma/web/mastodon_api/views/conversation_view.ex diff --git a/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex b/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex index cd59ab9..cd59ab9 100755..100644 --- a/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex +++ b/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex diff --git a/lib/pleroma/web/mastodon_api/views/filter_view.ex b/lib/pleroma/web/mastodon_api/views/filter_view.ex index 0c97061..0c97061 100755..100644 --- a/lib/pleroma/web/mastodon_api/views/filter_view.ex +++ b/lib/pleroma/web/mastodon_api/views/filter_view.ex diff --git a/lib/pleroma/web/mastodon_api/views/follow_request_view.ex b/lib/pleroma/web/mastodon_api/views/follow_request_view.ex index 5e50bc2..5e50bc2 100755..100644 --- a/lib/pleroma/web/mastodon_api/views/follow_request_view.ex +++ b/lib/pleroma/web/mastodon_api/views/follow_request_view.ex diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index abf7f29..890dd39 100755..100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -13,12 +13,11 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do def render("show.json", _) do instance = Config.get(:instance) - %{ - uri: Pleroma.Web.Endpoint.url(), - title: Keyword.get(instance, :name), + common_information(instance) + |> Map.merge(%{ + uri: Pleroma.Web.WebFinger.host(), description: Keyword.get(instance, :description), short_description: Keyword.get(instance, :short_description), - version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})", email: Keyword.get(instance, :email), urls: %{ streaming_api: Pleroma.Web.Endpoint.websocket_url() @@ -27,9 +26,10 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do thumbnail: URI.merge(Pleroma.Web.Endpoint.url(), Keyword.get(instance, :instance_thumbnail)) |> to_string, - languages: Keyword.get(instance, :languages, ["en"]), registrations: Keyword.get(instance, :registrations_open), approval_required: Keyword.get(instance, :account_approval_required), + contact_account: contact_account(Keyword.get(instance, :contact_username)), + configuration: configuration(), # Extra (not present in Mastodon): max_toot_chars: Keyword.get(instance, :limit), max_media_attachments: Keyword.get(instance, :max_media_attachments), @@ -41,19 +41,47 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do background_image: Pleroma.Web.Endpoint.url() <> Keyword.get(instance, :background_image), shout_limit: Config.get([:shout, :limit]), description_limit: Keyword.get(instance, :description_limit), - pleroma: %{ - metadata: %{ - account_activation_required: Keyword.get(instance, :account_activation_required), - features: features(), - federation: federation(), - fields_limits: fields_limits(), - post_formats: Config.get([:instance, :allowed_post_formats]), - birthday_required: Config.get([:instance, :birthday_required]), - birthday_min_age: Config.get([:instance, :birthday_min_age]) - }, - stats: %{mau: Pleroma.User.active_user_count()}, - vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key) - } + chat_limit: Keyword.get(instance, :chat_limit), + pleroma: pleroma_configuration(instance) + }) + end + + def render("show2.json", _) do + instance = Config.get(:instance) + + common_information(instance) + |> Map.merge(%{ + domain: Pleroma.Web.WebFinger.host(), + source_url: Pleroma.Application.repository(), + description: Keyword.get(instance, :short_description), + usage: %{users: %{active_month: Pleroma.User.active_user_count()}}, + thumbnail: %{ + url: + URI.merge(Pleroma.Web.Endpoint.url(), Keyword.get(instance, :instance_thumbnail)) + |> to_string + }, + configuration: configuration2(), + registrations: %{ + enabled: Keyword.get(instance, :registrations_open), + approval_required: Keyword.get(instance, :account_approval_required), + message: nil, + url: nil + }, + contact: %{ + email: Keyword.get(instance, :email), + account: contact_account(Keyword.get(instance, :contact_username)) + }, + # Extra (not present in Mastodon): + pleroma: pleroma_configuration2(instance) + }) + end + + defp common_information(instance) do + %{ + title: Keyword.get(instance, :name), + version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})", + languages: Keyword.get(instance, :languages, ["en"]), + rules: [] } end @@ -69,6 +97,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do "multifetch", "pleroma:api/v1/notifications:include_types_filter", "editing", + "quote_posting", if Config.get([:activitypub, :blockers_visible]) do "blockers_visible" end, @@ -92,6 +121,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do "safe_dm_mentions" end, "pleroma_emoji_reactions", + "pleroma_custom_emoji_reactions", "pleroma_chat_messages", if Config.get([:instance, :show_reactions]) do "exposable_reactions" @@ -99,7 +129,9 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do if Config.get([:instance, :profile_directory]) do "profile_directory" end, - "pleroma:get:main/ostatus" + "pleroma:get:main/ostatus", + "pleroma:group_actors", + "pleroma:bookmark_folders" ] |> Enum.filter(& &1) end @@ -131,7 +163,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do |> Map.put(:enabled, Config.get([:instance, :federating])) end - def fields_limits do + defp fields_limits do %{ max_fields: Config.get([:instance, :max_account_fields]), max_remote_fields: Config.get([:instance, :max_remote_account_fields]), @@ -139,4 +171,94 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do value_length: Config.get([:instance, :account_field_value_length]) } end + + defp contact_account(nil), do: nil + + defp contact_account("@" <> username) do + contact_account(username) + end + + defp contact_account(username) do + user = Pleroma.User.get_cached_by_nickname(username) + + if user do + Pleroma.Web.MastodonAPI.AccountView.render("show.json", %{user: user, for: nil}) + else + nil + end + end + + defp configuration do + %{ + accounts: %{ + max_featured_tags: 0 + }, + statuses: %{ + max_characters: Config.get([:instance, :limit]), + max_media_attachments: Config.get([:instance, :max_media_attachments]) + }, + media_attachments: %{ + image_size_limit: Config.get([:instance, :upload_limit]), + video_size_limit: Config.get([:instance, :upload_limit]), + supported_mime_types: ["application/octet-stream"] + }, + polls: %{ + max_options: Config.get([:instance, :poll_limits, :max_options]), + max_characters_per_option: Config.get([:instance, :poll_limits, :max_option_chars]), + min_expiration: Config.get([:instance, :poll_limits, :min_expiration]), + max_expiration: Config.get([:instance, :poll_limits, :max_expiration]) + } + } + end + + defp configuration2 do + configuration() + |> put_in([:accounts, :max_pinned_statuses], Config.get([:instance, :max_pinned_statuses], 0)) + |> put_in([:statuses, :characters_reserved_per_url], 0) + |> Map.merge(%{ + urls: %{ + streaming: Pleroma.Web.Endpoint.websocket_url(), + status: Config.get([:instance, :status_page]) + }, + vapid: %{ + public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key) + } + }) + end + + defp pleroma_configuration(instance) do + %{ + metadata: %{ + account_activation_required: Keyword.get(instance, :account_activation_required), + features: features(), + federation: federation(), + fields_limits: fields_limits(), + post_formats: Config.get([:instance, :allowed_post_formats]), + birthday_required: Config.get([:instance, :birthday_required]), + birthday_min_age: Config.get([:instance, :birthday_min_age]) + }, + stats: %{mau: Pleroma.User.active_user_count()}, + vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key) + } + end + + defp pleroma_configuration2(instance) do + configuration = pleroma_configuration(instance) + + configuration + |> Map.merge(%{ + metadata: + configuration.metadata + |> Map.merge(%{ + 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), + background_image: + Pleroma.Web.Endpoint.url() <> Keyword.get(instance, :background_image), + chat_limit: Keyword.get(instance, :chat_limit), + description_limit: Keyword.get(instance, :description_limit), + shout_limit: Config.get([:shout, :limit]) + }) + }) + end end diff --git a/lib/pleroma/web/mastodon_api/views/list_view.ex b/lib/pleroma/web/mastodon_api/views/list_view.ex index a7ae7c5..a7ae7c5 100755..100644 --- a/lib/pleroma/web/mastodon_api/views/list_view.ex +++ b/lib/pleroma/web/mastodon_api/views/list_view.ex diff --git a/lib/pleroma/web/mastodon_api/views/marker_view.ex b/lib/pleroma/web/mastodon_api/views/marker_view.ex index 944769b..944769b 100755..100644 --- a/lib/pleroma/web/mastodon_api/views/marker_view.ex +++ b/lib/pleroma/web/mastodon_api/views/marker_view.ex diff --git a/lib/pleroma/web/mastodon_api/views/media_view.ex b/lib/pleroma/web/mastodon_api/views/media_view.ex index 4db72de..4db72de 100755..100644 --- a/lib/pleroma/web/mastodon_api/views/media_view.ex +++ b/lib/pleroma/web/mastodon_api/views/media_view.ex diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex index b5b5b23..2a51f37 100755..100644 --- a/lib/pleroma/web/mastodon_api/views/notification_view.ex +++ b/lib/pleroma/web/mastodon_api/views/notification_view.ex @@ -17,6 +17,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.NotificationView alias Pleroma.Web.MastodonAPI.StatusView + alias Pleroma.Web.MediaProxy alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView defp object_id_for(%{data: %{"object" => %{"id" => id}}}) when is_binary(id), do: id @@ -145,7 +146,9 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do end defp put_emoji(response, activity) do - Map.put(response, :emoji, activity.data["content"]) + response + |> Map.put(:emoji, activity.data["content"]) + |> Map.put(:emoji_url, MediaProxy.url(Pleroma.Emoji.emoji_url(activity.data))) end defp put_chat_message(response, activity, reading_user, opts) do diff --git a/lib/pleroma/web/mastodon_api/views/poll_view.ex b/lib/pleroma/web/mastodon_api/views/poll_view.ex index 34e2387..1e3c9f3 100755..100644 --- a/lib/pleroma/web/mastodon_api/views/poll_view.ex +++ b/lib/pleroma/web/mastodon_api/views/poll_view.ex @@ -21,7 +21,10 @@ defmodule Pleroma.Web.MastodonAPI.PollView do votes_count: votes_count, voters_count: voters_count(object), options: options, - emojis: Pleroma.Web.MastodonAPI.StatusView.build_emojis(object.data["emoji"]) + emojis: Pleroma.Web.MastodonAPI.StatusView.build_emojis(object.data["emoji"]), + pleroma: %{ + non_anonymous: object.data["nonAnonymous"] || false + } } if params[:for] do diff --git a/lib/pleroma/web/mastodon_api/views/report_view.ex b/lib/pleroma/web/mastodon_api/views/report_view.ex index 983f7bd..983f7bd 100755..100644 --- a/lib/pleroma/web/mastodon_api/views/report_view.ex +++ b/lib/pleroma/web/mastodon_api/views/report_view.ex diff --git a/lib/pleroma/web/mastodon_api/views/scheduled_activity_view.ex b/lib/pleroma/web/mastodon_api/views/scheduled_activity_view.ex index 772d22f..772d22f 100755..100644 --- a/lib/pleroma/web/mastodon_api/views/scheduled_activity_view.ex +++ b/lib/pleroma/web/mastodon_api/views/scheduled_activity_view.ex diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 0a8c98b..c945290 100755..100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -21,6 +21,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.MediaProxy alias Pleroma.Web.PleromaAPI.EmojiReactionController + alias Pleroma.Web.RichMedia.Card import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1, visible_for_user?: 2] @@ -29,9 +30,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do # pagination is restricted to 40 activities at a time defp fetch_rich_media_for_activities(activities) do Enum.each(activities, fn activity -> - spawn(fn -> - Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) - end) + spawn(fn -> Card.get_by_activity(activity) end) end) end @@ -57,6 +56,27 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do end) end + defp get_quoted_activities([]), do: %{} + + defp get_quoted_activities(activities) do + activities + |> Enum.map(fn + %{data: %{"type" => "Create"}} = activity -> + object = Object.normalize(activity, fetch: false) + object && object.data["quoteUrl"] != "" && object.data["quoteUrl"] + + _ -> + nil + end) + |> Enum.filter(& &1) + |> Activity.create_by_object_ap_id_with_object() + |> Repo.all() + |> Enum.reduce(%{}, fn activity, acc -> + object = Object.normalize(activity, fetch: false) + if object, do: Map.put(acc, object.data["id"], activity), else: acc + end) + end + # DEPRECATED This field seems to be a left-over from the StatusNet era. # If your application uses `pleroma.conversation_id`: this field is deprecated. # It is currently stubbed instead by doing a CRC32 of the context, and @@ -92,11 +112,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do # To do: check AdminAPIControllerTest on the reasons behind nil activities in the list activities = Enum.filter(opts.activities, & &1) - # Start fetching rich media before doing anything else, so that later calls to get the cards - # only block for timeout in the worst case, as opposed to - # length(activities_with_links) * timeout + # Start prefetching rich media before doing anything else fetch_rich_media_for_activities(activities) replied_to_activities = get_replied_to_activities(activities) + quoted_activities = get_quoted_activities(activities) parent_activities = activities @@ -129,6 +148,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do opts = opts |> Map.put(:replied_to_activities, replied_to_activities) + |> Map.put(:quoted_activities, quoted_activities) |> Map.put(:parent_activities, parent_activities) |> Map.put(:relationships, relationships_opt) @@ -161,7 +181,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || []) - bookmarked = Activity.get_bookmark(reblogged_parent_activity, opts[:for]) != nil + bookmark = Activity.get_bookmark(reblogged_parent_activity, opts[:for]) + + bookmark_folder = + if bookmark != nil do + bookmark.folder_id + else + nil + end mentions = activity.recipients @@ -190,7 +217,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do favourites_count: 0, reblogged: reblogged?(reblogged_parent_activity, opts[:for]), favourited: present?(favorited), - bookmarked: present?(bookmarked), + bookmarked: present?(bookmark), muted: false, pinned: pinned?, sensitive: false, @@ -204,7 +231,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do emojis: [], pleroma: %{ local: activity.local, - pinned_at: pinned_at + pinned_at: pinned_at, + bookmark_folder: bookmark_folder } } end @@ -241,7 +269,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || []) - bookmarked = Activity.get_bookmark(activity, opts[:for]) != nil + bookmark = Activity.get_bookmark(activity, opts[:for]) + + bookmark_folder = + if bookmark != nil do + bookmark.folder_id + else + nil + end client_posted_this_activity = opts[:for] && user.id == opts[:for].id @@ -277,7 +312,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do end reply_to = get_reply_to(activity, opts) - reply_to_user = reply_to && CommonAPI.get_user(reply_to.data["actor"]) history_len = @@ -290,6 +324,22 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do # Here the implicit index of the current content is 0 chrono_order = history_len - 1 + quote_activity = get_quote(activity, opts) + + quote_id = + case quote_activity do + %Activity{id: id} -> id + _ -> nil + end + + quote_post = + if visible_for_user?(quote_activity, opts[:for]) and opts[:show_quote] != false do + quote_rendering_opts = Map.merge(opts, %{activity: quote_activity, show_quote: false}) + render("show.json", quote_rendering_opts) + else + nil + end + content = object |> render_content() @@ -311,7 +361,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do summary = object.data["summary"] || "" - card = render("card.json", Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)) + card = + case Card.get_by_activity(activity) do + %Card{} = result -> render("card.json", result) + _ -> nil + end url = if user.local do @@ -334,14 +388,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do end emoji_reactions = - object.data - |> Map.get("reactions", []) + object + |> Object.get_emoji_reactions() |> EmojiReactionController.filter_allowed_users( opts[:for], Map.get(opts, :with_muted, false) ) - |> Stream.map(fn {emoji, users} -> - build_emoji_map(emoji, users, opts[:for]) + |> Stream.map(fn {emoji, users, url} -> + build_emoji_map(emoji, users, url, opts[:for]) end) |> Enum.to_list() @@ -380,7 +434,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do favourites_count: like_count, reblogged: reblogged?(activity, opts[:for]), favourited: present?(favorited), - bookmarked: present?(bookmarked), + bookmarked: present?(bookmark), muted: muted, pinned: pinned?, sensitive: sensitive, @@ -398,6 +452,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do conversation_id: get_context_id(activity), context: object.data["context"], in_reply_to_account_acct: reply_to_user && reply_to_user.nickname, + quote: quote_post, + quote_id: quote_id, + quote_url: object.data["quoteUrl"], + quote_visible: visible_for_user?(quote_activity, opts[:for]), content: %{"text/plain" => content_plaintext}, spoiler_text: %{"text/plain" => summary}, expires_at: expires_at, @@ -405,7 +463,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do thread_muted: thread_muted?, emoji_reactions: emoji_reactions, parent_visible: visible_for_user?(reply_to, opts[:for]), - pinned_at: pinned_at + pinned_at: pinned_at, + quotes_count: object.data["quotesCount"] || 0, + bookmark_folder: bookmark_folder } } end @@ -508,37 +568,30 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do } end - def render("card.json", %{rich_media: rich_media, page_url: page_url}) do - page_url_data = URI.parse(page_url) - - page_url_data = - if is_binary(rich_media["url"]) do - URI.merge(page_url_data, URI.parse(rich_media["url"])) - else - page_url_data - end + def render("card.json", %Card{fields: rich_media}) do + page_url_data = URI.parse(rich_media["url"]) page_url = page_url_data |> to_string - image_url_data = - if is_binary(rich_media["image"]) do - URI.parse(rich_media["image"]) - else - nil - end - - image_url = build_image_url(image_url_data, page_url_data) + image_url = proxied_url(rich_media["image"], page_url_data) + audio_url = proxied_url(rich_media["audio"], page_url_data) + video_url = proxied_url(rich_media["video"], page_url_data) %{ type: "link", provider_name: page_url_data.host, provider_url: page_url_data.scheme <> "://" <> page_url_data.host, url: page_url, - image: image_url |> MediaProxy.url(), + image: image_url, + image_description: rich_media["image:alt"] || "", title: rich_media["title"] || "", description: rich_media["description"] || "", pleroma: %{ - opengraph: rich_media + opengraph: + rich_media + |> Maps.put_if_present("image", image_url) + |> Maps.put_if_present("audio", audio_url) + |> Maps.put_if_present("video", video_url) } } end @@ -633,6 +686,25 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do end end + def get_quote(activity, %{quoted_activities: quoted_activities}) do + object = Object.normalize(activity, fetch: false) + + with nil <- quoted_activities[object.data["quoteUrl"]] do + # For when a quote post is inside an Announce + Activity.get_create_by_object_ap_id_with_object(object.data["quoteUrl"]) + end + end + + def get_quote(%{data: %{"object" => _object}} = activity, _) do + object = Object.normalize(activity, fetch: false) + + if object.data["quoteUrl"] && object.data["quoteUrl"] != "" do + Activity.get_create_by_object_ap_id(object.data["quoteUrl"]) + else + nil + end + end + def render_content(%{data: %{"name" => name}} = object) when not is_nil(name) and name != "" do url = object.data["url"] || object.data["id"] @@ -702,11 +774,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do end end - defp build_emoji_map(emoji, users, current_user) do + defp build_emoji_map(emoji, users, url, current_user) do %{ - name: emoji, + name: Pleroma.Web.PleromaAPI.EmojiReactionView.emoji_name(emoji, url), count: length(users), - me: !!(current_user && current_user.ap_id in users) + url: MediaProxy.url(url), + me: !!(current_user && current_user.ap_id in users), + account_ids: Enum.map(users, fn user -> User.get_cached_by_ap_id(user).id end) } end @@ -733,8 +807,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do URI.merge(page_url_data, image_url_data) |> to_string end - defp build_image_url(_, _), do: nil - defp get_source_text(%{"content" => content} = _source) do content end @@ -754,4 +826,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do defp get_source_content_type(_source) do Utils.get_content_type(nil) end + + defp proxied_url(url, page_url_data) do + if is_binary(url) do + build_image_url(URI.parse(url), page_url_data) |> MediaProxy.url() + else + nil + end + end end diff --git a/lib/pleroma/web/mastodon_api/views/subscription_view.ex b/lib/pleroma/web/mastodon_api/views/subscription_view.ex index baa1e03..baa1e03 100755..100644 --- a/lib/pleroma/web/mastodon_api/views/subscription_view.ex +++ b/lib/pleroma/web/mastodon_api/views/subscription_view.ex diff --git a/lib/pleroma/web/mastodon_api/views/suggestion_view.ex b/lib/pleroma/web/mastodon_api/views/suggestion_view.ex index d3df4ef..d3df4ef 100755..100644 --- a/lib/pleroma/web/mastodon_api/views/suggestion_view.ex +++ b/lib/pleroma/web/mastodon_api/views/suggestion_view.ex diff --git a/lib/pleroma/web/mastodon_api/views/timeline_view.ex b/lib/pleroma/web/mastodon_api/views/timeline_view.ex index 702eb7e..702eb7e 100755..100644 --- a/lib/pleroma/web/mastodon_api/views/timeline_view.ex +++ b/lib/pleroma/web/mastodon_api/views/timeline_view.ex diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex index 8844410..bb27d80 100755..100644 --- a/lib/pleroma/web/mastodon_api/websocket_handler.ex +++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -9,124 +9,128 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do alias Pleroma.User alias Pleroma.Web.OAuth.Token alias Pleroma.Web.Streamer + alias Pleroma.Web.StreamerView - @behaviour :cowboy_websocket + @behaviour Phoenix.Socket.Transport # Client ping period. @tick :timer.seconds(30) - # Cowboy timeout period. - @timeout :timer.seconds(60) - # Hibernate every X messages - @hibernate_every 100 - - def init(%{qs: qs} = req, state) do - with params <- Enum.into(:cow_qs.parse_qs(qs), %{}), - sec_websocket <- :cowboy_req.header("sec-websocket-protocol", req, nil), - access_token <- Map.get(params, "access_token"), - {:ok, user, oauth_token} <- authenticate_request(access_token, sec_websocket), - {:ok, topic} <- Streamer.get_topic(params["stream"], user, oauth_token, params) do - req = - if sec_websocket do - :cowboy_req.set_resp_header("sec-websocket-protocol", sec_websocket, req) + + @impl Phoenix.Socket.Transport + def child_spec(_opts), do: :ignore + + # This only prepares the connection and is not in the process yet + @impl Phoenix.Socket.Transport + def connect(%{params: params} = transport_info) do + with access_token <- Map.get(params, "access_token"), + {:ok, user, oauth_token} <- authenticate_request(access_token), + {:ok, topic} <- + Streamer.get_topic(params["stream"], user, oauth_token, params) do + topics = + if topic do + [topic] else - req + [] end - {:cowboy_websocket, req, - %{user: user, topic: topic, oauth_token: oauth_token, count: 0, timer: nil}, - %{idle_timeout: @timeout}} + state = %{ + user: user, + topics: topics, + oauth_token: oauth_token, + count: 0, + timer: nil + } + + {:ok, state} else {:error, :bad_topic} -> - Logger.debug("#{__MODULE__} bad topic #{inspect(req)}") - req = :cowboy_req.reply(404, req) - {:ok, req, state} + Logger.debug("#{__MODULE__} bad topic #{inspect(transport_info)}") + + {:error, :bad_topic} {:error, :unauthorized} -> - Logger.debug("#{__MODULE__} authentication error: #{inspect(req)}") - req = :cowboy_req.reply(401, req) - {:ok, req, state} + Logger.debug("#{__MODULE__} authentication error: #{inspect(transport_info)}") + {:error, :unauthorized} end end - def websocket_init(state) do - Logger.debug( - "#{__MODULE__} accepted websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topic #{state.topic}" - ) + # All subscriptions/links and messages cannot be created + # until the processed is launched with init/1 + @impl Phoenix.Socket.Transport + def init(state) do + Enum.each(state.topics, fn topic -> Streamer.add_socket(topic, state.oauth_token) end) - Streamer.add_socket(state.topic, state.oauth_token) - {:ok, %{state | timer: timer()}} - end + Process.send_after(self(), :ping, @tick) - # Client's Pong frame. - def websocket_handle(:pong, state) do - if state.timer, do: Process.cancel_timer(state.timer) - {:ok, %{state | timer: timer()}} + {:ok, state} end - # We only receive pings for now - def websocket_handle(:ping, state), do: {:ok, state} + @impl Phoenix.Socket.Transport + def handle_in({text, [opcode: :text]}, state) do + with {:ok, %{} = event} <- Jason.decode(text) do + handle_client_event(event, state) + else + _ -> + Logger.error("#{__MODULE__} received non-JSON event: #{inspect(text)}") + {:ok, state} + end + end - def websocket_handle(frame, state) do + def handle_in(frame, state) do Logger.error("#{__MODULE__} received frame: #{inspect(frame)}") {:ok, state} end - def websocket_info({:render_with_user, view, template, item}, state) do + @impl Phoenix.Socket.Transport + def handle_info({:render_with_user, view, template, item, topic}, state) do user = %User{} = User.get_cached_by_ap_id(state.user.ap_id) unless Streamer.filtered_by_user?(user, item) do - websocket_info({:text, view.render(template, item, user)}, %{state | user: user}) + message = view.render(template, item, user, topic) + {:push, {:text, message}, %{state | user: user}} else {:ok, state} end end - def websocket_info({:text, message}, state) do - # If the websocket processed X messages, force an hibernate/GC. - # We don't hibernate at every message to balance CPU usage/latency with RAM usage. - if state.count > @hibernate_every do - {:reply, {:text, message}, %{state | count: 0}, :hibernate} - else - {:reply, {:text, message}, %{state | count: state.count + 1}} - end + def handle_info({:text, text}, state) do + {:push, {:text, text}, state} end - # Ping tick. We don't re-queue a timer there, it is instead queued when :pong is received. - # As we hibernate there, reset the count to 0. - # If the client misses :pong, Cowboy will automatically timeout the connection after - # `@idle_timeout`. - def websocket_info(:tick, state) do - {:reply, :ping, %{state | timer: nil, count: 0}, :hibernate} + def handle_info(:ping, state) do + Process.send_after(self(), :ping, @tick) + + {:push, {:ping, ""}, state} end - def websocket_info(:close, state) do - {:stop, state} + def handle_info(:close, state) do + {:stop, {:closed, 'connection closed by server'}, state} end - # State can be `[]` only in case we terminate before switching to websocket, - # we already log errors for these cases in `init/1`, so just do nothing here - def terminate(_reason, _req, []), do: :ok + def handle_info(msg, state) do + Logger.debug("#{__MODULE__} received info: #{inspect(msg)}") + + {:ok, state} + end - def terminate(reason, _req, state) do + @impl Phoenix.Socket.Transport + def terminate(reason, state) do Logger.debug( - "#{__MODULE__} terminating websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topic #{state.topic || "?"}: #{inspect(reason)}" + "#{__MODULE__} terminating websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topics #{state.topics || "?"}: #{inspect(reason)})" ) - Streamer.remove_socket(state.topic) + Enum.each(state.topics, fn topic -> Streamer.remove_socket(topic) end) :ok end # Public streams without authentication. - defp authenticate_request(nil, nil) do + defp authenticate_request(nil) do {:ok, nil, nil} end # Authenticated streams. - defp authenticate_request(access_token, sec_websocket) do - token = access_token || sec_websocket - - with true <- is_bitstring(token), - oauth_token = %Token{user_id: user_id} <- Repo.get_by(Token, token: token), + defp authenticate_request(access_token) do + with oauth_token = %Token{user_id: user_id} <- Repo.get_by(Token, token: access_token), user = %User{} <- User.get_cached_by_id(user_id) do {:ok, user, oauth_token} else @@ -134,7 +138,110 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do end end - defp timer do - Process.send_after(self(), :tick, @tick) + defp handle_client_event(%{"type" => "subscribe", "stream" => _topic} = params, state) do + with {_, {:ok, topic}} <- + {:topic, Streamer.get_topic(params["stream"], state.user, state.oauth_token, params)}, + {_, false} <- {:subscribed, topic in state.topics} do + Streamer.add_socket(topic, state.oauth_token) + + message = + StreamerView.render("pleroma_respond.json", %{type: "subscribe", result: "success"}) + + {:reply, :ok, {:text, message}, %{state | topics: [topic | state.topics]}} + else + {:subscribed, true} -> + message = + StreamerView.render("pleroma_respond.json", %{type: "subscribe", result: "ignored"}) + + {:reply, :error, {:text, message}, state} + + {:topic, {:error, error}} -> + message = + StreamerView.render("pleroma_respond.json", %{ + type: "subscribe", + result: "error", + error: error + }) + + {:reply, :error, {:text, message}, state} + end + end + + defp handle_client_event(%{"type" => "unsubscribe", "stream" => _topic} = params, state) do + with {_, {:ok, topic}} <- + {:topic, Streamer.get_topic(params["stream"], state.user, state.oauth_token, params)}, + {_, true} <- {:subscribed, topic in state.topics} do + Streamer.remove_socket(topic) + + message = + StreamerView.render("pleroma_respond.json", %{type: "unsubscribe", result: "success"}) + + {:reply, :ok, {:text, message}, %{state | topics: List.delete(state.topics, topic)}} + else + {:subscribed, false} -> + message = + StreamerView.render("pleroma_respond.json", %{type: "unsubscribe", result: "ignored"}) + + {:reply, :error, {:text, message}, state} + + {:topic, {:error, error}} -> + message = + StreamerView.render("pleroma_respond.json", %{ + type: "unsubscribe", + result: "error", + error: error + }) + + {:reply, :error, {:text, message}, state} + end + end + + defp handle_client_event( + %{"type" => "pleroma:authenticate", "token" => access_token} = _params, + state + ) do + with {:auth, nil, nil} <- {:auth, state.user, state.oauth_token}, + {:ok, user, oauth_token} <- authenticate_request(access_token) do + message = + StreamerView.render("pleroma_respond.json", %{ + type: "pleroma:authenticate", + result: "success" + }) + + {:reply, :ok, {:text, message}, %{state | user: user, oauth_token: oauth_token}} + else + {:auth, _, _} -> + message = + StreamerView.render("pleroma_respond.json", %{ + type: "pleroma:authenticate", + result: "error", + error: :already_authenticated + }) + + {:reply, :error, {:text, message}, state} + + _ -> + message = + StreamerView.render("pleroma_respond.json", %{ + type: "pleroma:authenticate", + result: "error", + error: :unauthorized + }) + + {:reply, :error, {:text, message}, state} + end + end + + defp handle_client_event(params, state) do + Logger.error("#{__MODULE__} received unknown event: #{inspect(params)}") + {:ok, state} + end + + def handle_error(conn, :unauthorized) do + Plug.Conn.send_resp(conn, 401, "Unauthorized") + end + + def handle_error(conn, _reason) do + Plug.Conn.send_resp(conn, 404, "Not Found") end end |
