diff options
author | marcin mikołajczak <git@mkljczk.pl> | 2021-12-28 18:07:19 +0100 |
---|---|---|
committer | marcin mikołajczak <git@mkljczk.pl> | 2021-12-28 18:07:19 +0100 |
commit | 9032d065e6710400420320edbee9a9ca5e490eae (patch) | |
tree | d49601451ea29dcf2a79a948541ddf5790e389f5 /lib/pleroma/web/mastodon_api | |
parent | a9b0027071e3edf4d6a899ae5772e37806b4fc7d (diff) | |
parent | de7f84deb3ebe57744630950860f8fbf64a414a2 (diff) | |
download | pleroma-9032d065e6710400420320edbee9a9ca5e490eae.tar.gz |
wip
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
Diffstat (limited to 'lib/pleroma/web/mastodon_api')
9 files changed, 266 insertions, 16 deletions
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 47911103f..c5b964d55 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -15,6 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do alias Pleroma.Maps alias Pleroma.User + alias Pleroma.UserNote alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Builder alias Pleroma.Web.ActivityPub.Pipeline @@ -55,7 +56,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do plug( OAuthScopesPlug, - %{scopes: ["write:accounts"]} when action in [:update_credentials, :endorse, :unendorse] + %{scopes: ["write:accounts"]} + when action in [:update_credentials, :note, :endorse, :unendorse] ) plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :lists) @@ -82,7 +84,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action in [:mute, :unmute]) @relationship_actions [:follow, :unfollow] - @needs_account ~W(followers following lists follow unfollow mute unmute block unblock endorse unendorse)a + @needs_account ~W(followers following lists follow unfollow mute unmute block unblock endorse unendorse endorse unendorse)a plug( RateLimiter, @@ -438,6 +440,16 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do end end + @doc "POST /api/v1/accounts/:id/note" + def note( + %{assigns: %{user: noter, account: target}, body_params: %{comment: comment}} = conn, + _params + ) do + with {:ok, _user_note} <- UserNote.create(noter, target, comment) do + render(conn, "relationship.json", user: noter, target: target) + end + end + @doc "POST /api/v1/accounts/:id/mute" def endorse(%{assigns: %{user: endorser, account: endorsed}} = conn, _params) do with {:ok, _user_relationships} <- User.endorse(endorser, endorsed) do diff --git a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex index 93e63ba03..8d18140ad 100644 --- a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex @@ -10,7 +10,9 @@ defmodule Pleroma.Web.MastodonAPI.AppController do use Pleroma.Web, :controller + alias Pleroma.Maps alias Pleroma.Repo + alias Pleroma.User alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.Scopes alias Pleroma.Web.OAuth.Token @@ -26,11 +28,13 @@ defmodule Pleroma.Web.MastodonAPI.AppController do @doc "POST /api/v1/apps" def create(%{body_params: params} = conn, _params) do scopes = Scopes.fetch_scopes(params, ["read"]) + user_id = get_user_id(conn) app_attrs = params |> Map.take([:client_name, :redirect_uris, :website]) |> Map.put(:scopes, scopes) + |> Maps.put_if_present(:user_id, user_id) with cs <- App.register_changeset(%App{}, app_attrs), {:ok, app} <- Repo.insert(cs) do @@ -38,6 +42,9 @@ defmodule Pleroma.Web.MastodonAPI.AppController do end end + defp get_user_id(%{assigns: %{user: %User{id: user_id}}}), do: user_id + defp get_user_id(_conn), do: nil + @doc """ GET /api/v1/apps/verify_credentials Gets compact non-secret representation of the app. Supports app tokens and user tokens. diff --git a/lib/pleroma/web/mastodon_api/controllers/directory_controller.ex b/lib/pleroma/web/mastodon_api/controllers/directory_controller.ex new file mode 100644 index 000000000..45ef227fb --- /dev/null +++ b/lib/pleroma/web/mastodon_api/controllers/directory_controller.ex @@ -0,0 +1,82 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.DirectoryController do + use Pleroma.Web, :controller + + import Ecto.Query + alias Pleroma.Pagination + alias Pleroma.User + alias Pleroma.UserRelationship + alias Pleroma.Web.MastodonAPI.AccountView + + require Logger + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + + plug(:skip_auth when action == "index") + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.DirectoryOperation + + @doc "GET /api/v1/directory" + def index(%{assigns: %{user: user}} = conn, params) do + with true <- Pleroma.Config.get([:instance, :profile_directory]) do + limit = Map.get(params, :limit, 20) |> min(80) + + users = + User.Query.build(%{is_discoverable: true, invisible: false, limit: limit}) + |> order_by_creation_date(params) + |> exclude_remote(params) + |> exclude_user(user) + |> exclude_relationships(user, [:block, :mute]) + |> Pagination.fetch_paginated(params, :offset) + + conn + |> put_view(AccountView) + |> render("index.json", for: user, users: users, as: :user) + else + _ -> json(conn, []) + end + end + + defp order_by_creation_date(query, %{order: "new"}) do + query + end + + defp order_by_creation_date(query, _params) do + query + |> order_by([u], desc_nulls_last: u.last_status_at) + end + + defp exclude_remote(query, %{local: true}) do + where(query, [u], u.local == true) + end + + defp exclude_remote(query, _params) do + query + end + + defp exclude_user(query, %User{id: user_id}) do + where(query, [u], u.id != ^user_id) + end + + defp exclude_user(query, _user) do + query + end + + defp exclude_relationships(query, %User{id: user_id}, relationship_types) do + query + |> join(:left, [u], r in UserRelationship, + as: :user_relationships, + on: + r.target_id == u.id and r.source_id == ^user_id and + r.relationship_type in ^relationship_types + ) + |> where([user_relationships: r], is_nil(r.target_id)) + end + + defp exclude_relationships(query, _user, _relationship_types) do + query + 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 64b177eb3..1459fc492 100644 --- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex @@ -17,6 +17,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do require Logger + @search_limit 40 + plug(Pleroma.Web.ApiSpec.CastAndValidate) # Note: Mastodon doesn't allow unauthenticated access (requires read:accounts / read:search) @@ -77,7 +79,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do [ resolve: params[:resolve], following: params[:following], - limit: params[:limit], + limit: min(params[:limit], @search_limit), offset: params[:offset], type: params[:type], author: get_author(params), diff --git a/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex b/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex index 01e122dd9..e913fcf4b 100644 --- a/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex @@ -4,11 +4,16 @@ defmodule Pleroma.Web.MastodonAPI.SuggestionController do use Pleroma.Web, :controller + import Ecto.Query + alias Pleroma.FollowingRelationship + alias Pleroma.User + alias Pleroma.UserRelationship require Logger plug(Pleroma.Web.ApiSpec.CastAndValidate) - plug(Pleroma.Web.Plugs.OAuthScopesPlug, %{scopes: ["read"]} when action == :index) + plug(Pleroma.Web.Plugs.OAuthScopesPlug, %{scopes: ["read"]} when action in [:index, :index2]) + plug(Pleroma.Web.Plugs.OAuthScopesPlug, %{scopes: ["write"]} when action in [:dismiss]) def open_api_operation(action) do operation = String.to_existing_atom("#{action}_operation") @@ -26,7 +31,90 @@ defmodule Pleroma.Web.MastodonAPI.SuggestionController do } end + def index2_operation do + %OpenApiSpex.Operation{ + tags: ["Suggestions"], + summary: "Follow suggestions", + operationId: "SuggestionController.index2", + responses: %{ + 200 => Pleroma.Web.ApiSpec.Helpers.empty_array_response() + } + } + end + + def dismiss_operation do + %OpenApiSpex.Operation{ + tags: ["Suggestions"], + summary: "Remove a suggestion", + operationId: "SuggestionController.dismiss", + parameters: [ + OpenApiSpex.Operation.parameter( + :account_id, + :path, + %OpenApiSpex.Schema{type: :string}, + "Account to dismiss", + required: true + ) + ], + responses: %{ + 200 => Pleroma.Web.ApiSpec.Helpers.empty_object_response() + } + } + end + @doc "GET /api/v1/suggestions" def index(conn, params), do: Pleroma.Web.MastodonAPI.MastodonAPIController.empty_array(conn, params) + + @doc "GET /api/v2/suggestions" + def index2(%{assigns: %{user: user}} = conn, params) do + limit = Map.get(params, :limit, 40) |> min(80) + + users = + %{is_suggested: true, invisible: false, limit: limit} + |> User.Query.build() + |> exclude_user(user) + |> exclude_relationships(user, [:block, :mute, :suggestion_dismiss]) + |> exclude_following(user) + |> Pleroma.Repo.all() + + render(conn, "index.json", %{ + users: users, + source: :staff, + for: user, + skip_visibility_check: true + }) + end + + defp exclude_user(query, %User{id: user_id}) do + where(query, [u], u.id != ^user_id) + end + + defp exclude_relationships(query, %User{id: user_id}, relationship_types) do + query + |> join(:left, [u], r in UserRelationship, + as: :user_relationships, + on: + r.target_id == u.id and r.source_id == ^user_id and + r.relationship_type in ^relationship_types + ) + |> where([user_relationships: r], is_nil(r.target_id)) + end + + defp exclude_following(query, %User{id: user_id}) do + query + |> join(:left, [u], r in FollowingRelationship, + as: :following_relationships, + on: r.following_id == u.id and r.follower_id == ^user_id and r.state == :follow_accept + ) + |> where([following_relationships: r], is_nil(r.following_id)) + end + + @doc "DELETE /api/v1/suggestions/:account_id" + def dismiss(%{assigns: %{user: source}} = conn, %{account_id: user_id}) do + with %User{} = target <- User.get_cached_by_id(user_id), + {:ok, _} <- UserRelationship.create(:suggestion_dismiss, source, target) do + json(conn, %{}) + end + end end diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex index 71479550e..23846b36a 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex @@ -24,6 +24,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do with {:ok, follower, _followed, _} <- result do options = cast_params(params) set_reblogs_visibility(options[:reblogs], result) + set_subscription(options[:notify], result) {:ok, follower} end end @@ -36,6 +37,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do CommonAPI.show_reblogs(follower, followed) end + defp set_subscription(true, {:ok, follower, followed, _}) do + User.subscribe(follower, followed) + end + + defp set_subscription(false, {:ok, follower, followed, _}) do + User.unsubscribe(follower, followed) + end + + defp set_subscription(_, _), do: {:ok, nil} + @spec get_followers(User.t(), map()) :: list(User.t()) def get_followers(user, params \\ %{}) do user @@ -73,7 +84,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do exclude_visibilities: {:array, :string}, reblogs: :boolean, with_muted: :boolean, - account_ap_id: :string + account_ap_id: :string, + notify: :boolean } changeset = cast({%{}, param_types}, params, Map.keys(param_types)) diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 136dcf929..b964fdc54 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do alias Pleroma.FollowingRelationship alias Pleroma.User + alias Pleroma.UserNote alias Pleroma.UserRelationship alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.MastodonAPI.AccountView @@ -101,6 +102,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do User.following?(target, reading_user) end + subscribing = + UserRelationship.exists?( + user_relationships, + :inverse_subscription, + target, + reading_user, + &User.subscribed_to?(&2, &1) + ) + # NOTE: adjust UserRelationship.view_relationships_option/2 on new relation-related flags %{ id: to_string(target.id), @@ -138,14 +148,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do target, &User.muted_notifications?(&1, &2) ), - subscribing: - UserRelationship.exists?( - user_relationships, - :inverse_subscription, - target, - reading_user, - &User.subscribed_to?(&2, &1) - ), + subscribing: subscribing, + notifying: subscribing, requested: follow_state == :follow_pending, domain_blocking: User.blocks_domain?(reading_user, target), showing_reblogs: @@ -156,6 +160,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do target, &User.muting_reblogs?(&1, &2) ), + note: + UserNote.show( + reading_user, + target + ), endorsed: UserRelationship.exists?( user_relationships, @@ -163,7 +172,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do target, reading_user, &User.endorses?(&2, &1) - ), + ) } end @@ -268,6 +277,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do actor_type: user.actor_type } }, + last_status_at: user.last_status_at, # Pleroma extensions # Note: it's insecure to output :email but fully-qualified nickname may serve as safe stub @@ -276,6 +286,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do ap_id: user.ap_id, also_known_as: user.also_known_as, is_confirmed: user.is_confirmed, + is_suggested: user.is_suggested, tags: user.tags, hide_followers_count: user.hide_followers_count, hide_follows_count: user.hide_follows_count, diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index ef208062b..8e657ee0f 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -45,7 +45,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do features: features(), federation: federation(), fields_limits: fields_limits(), - post_formats: Config.get([:instance, :allowed_post_formats]) + post_formats: Config.get([:instance, :allowed_post_formats]), + privileged_staff: Config.get([:instance, :privileged_staff]) }, stats: %{mau: Pleroma.User.active_user_count()}, vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key) @@ -59,6 +60,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do "mastodon_api", "mastodon_api_streaming", "polls", + "v2_suggestions", "pleroma_explicit_addressing", "shareable_emoji_packs", "multifetch", @@ -83,7 +85,13 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do "safe_dm_mentions" end, "pleroma_emoji_reactions", - "pleroma_chat_messages" + "pleroma_chat_messages", + if Config.get([:instance, :show_reactions]) do + "exposable_reactions" + end, + if Config.get([:instance, :profile_directory]) do + "profile_directory" + end ] |> Enum.filter(& &1) end diff --git a/lib/pleroma/web/mastodon_api/views/suggestion_view.ex b/lib/pleroma/web/mastodon_api/views/suggestion_view.ex new file mode 100644 index 000000000..865229a88 --- /dev/null +++ b/lib/pleroma/web/mastodon_api/views/suggestion_view.ex @@ -0,0 +1,28 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.SuggestionView do + use Pleroma.Web, :view + alias Pleroma.Web.MastodonAPI.AccountView + + @source_types [:staff, :global, :past_interactions] + + def render("index.json", %{users: users} = opts) do + Enum.map(users, fn user -> + opts = + opts + |> Map.put(:user, user) + |> Map.delete(:users) + + render("show.json", opts) + end) + end + + def render("show.json", %{source: source, user: _user} = opts) when source in @source_types do + %{ + source: source, + account: AccountView.render("show.json", opts) + } + end +end |