From 16bcffd64adad9e205fa1f85b1366efca31e020d Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Tue, 16 Jun 2020 08:41:57 +0300 Subject: [PATCH] unify notification settings --- .../API/differences_in_mastoapi_responses.md | 6 ++ docs/development/API/pleroma_api.md | 12 ++- lib/pleroma/notification.ex | 2 + lib/pleroma/user/notification_setting.ex | 14 +++- lib/pleroma/web/masto_fe_controller.ex | 19 ++++- lib/pleroma/web/mastodon_api/mastodon_api.ex | 9 ++ lib/pleroma/web/push/impl.ex | 5 +- lib/pleroma/web/push/subscription.ex | 3 +- lib/pleroma/web/streamer.ex | 11 ++- ...clude_types_into_user_notification_settings.exs | 11 +++ .../controllers/notification_controller_test.exs | 77 +++++++++++++++++ .../web/mastodon_api/masto_fe_controller_test.exs | 97 +++++++++++++++++++--- .../web/mastodon_api/views/account_view_test.exs | 6 ++ test/pleroma/web/push/impl_test.exs | 21 +++++ .../web/twitter_api/util_controller_test.exs | 9 ++ 15 files changed, 283 insertions(+), 19 deletions(-) create mode 100644 priv/repo/migrations/20200602101826_add_exclude_types_into_user_notification_settings.exs diff --git a/docs/development/API/differences_in_mastoapi_responses.md b/docs/development/API/differences_in_mastoapi_responses.md index a14fcb416..75382bb3c 100644 --- a/docs/development/API/differences_in_mastoapi_responses.md +++ b/docs/development/API/differences_in_mastoapi_responses.md @@ -334,6 +334,12 @@ The message payload consist of: Both user muting and thread muting can be done for only a certain time by adding an `expires_in` parameter to the API calls and giving the expiration time in seconds. +## Subscriptions + +Has additional field in parameters: + +- `pleroma:emoji_reaction`: Receive emoji reaction notifications? + ## Not implemented Pleroma is generally compatible with the Mastodon 2.7.2 API, but some newer features and non-essential features are omitted. These features usually return an HTTP 200 status code, but with an empty response. While they may be added in the future, they are considered low priority. diff --git a/docs/development/API/pleroma_api.md b/docs/development/API/pleroma_api.md index d896f0ce7..9f93494ec 100644 --- a/docs/development/API/pleroma_api.md +++ b/docs/development/API/pleroma_api.md @@ -301,12 +301,20 @@ See [Admin-API](admin_api.md) Can only accept images - any attempt to upload non-image files will be met with `HTTP 415 Unsupported Media Type`. ## `/api/v1/pleroma/notification_settings` + ### Updates user notification settings + * Method `PUT` * Authentication: required * Params: - * `block_from_strangers`: BOOLEAN field, blocks notifications from accounts you do not follow - * `hide_notification_contents`: BOOLEAN field. When set to true, it removes the contents of a message from the push notification. + * `block_from_strangers`: BOOLEAN field, blocks notifications from accounts you do not follow + * `hide_notification_contents`: BOOLEAN field. When set to true, it removes the contents of a message from the push notification. + * `followers`: BOOLEAN field, receives notifications from followers + * `follows`: BOOLEAN field, receives notifications from people the user follows + * `remote`: BOOLEAN field, receives notifications from people on remote instances + * `local`: BOOLEAN field, receives notifications from people on the local instance + * `privacy_option`: BOOLEAN field. When set to true, it removes the contents of a message from the push notification. + * `exclude_types`: ARRAY field. What notification types to exclude. * Response: JSON. Returns `{"status": "success"}` if the update was successful, otherwise returns `{"error": "error_msg"}` ## `/api/v1/pleroma/healthcheck` diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 7efbdc49a..246993f4d 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -74,6 +74,8 @@ defmodule Pleroma.Notification do reblog } + def types, do: @notification_types + def changeset(%Notification{} = notification, attrs) do notification |> cast(attrs, [:seen, :type]) diff --git a/lib/pleroma/user/notification_setting.ex b/lib/pleroma/user/notification_setting.ex index a7cd61499..820ed903c 100644 --- a/lib/pleroma/user/notification_setting.ex +++ b/lib/pleroma/user/notification_setting.ex @@ -12,13 +12,25 @@ defmodule Pleroma.User.NotificationSetting do embedded_schema do field(:block_from_strangers, :boolean, default: false) field(:hide_notification_contents, :boolean, default: false) + field(:followers, :boolean, default: true) + field(:follows, :boolean, default: true) + field(:non_follows, :boolean, default: true) + field(:non_followers, :boolean, default: true) + field(:privacy_option, :boolean, default: false) + field(:exclude_types, {:array, :string}, default: []) end def changeset(schema, params) do schema |> cast(prepare_attrs(params), [ :block_from_strangers, - :hide_notification_contents + :hide_notification_contents, + :followers, + :follows, + :non_follows, + :non_followers, + :privacy_option, + :exclude_types ]) end diff --git a/lib/pleroma/web/masto_fe_controller.ex b/lib/pleroma/web/masto_fe_controller.ex index e788ab37a..6ec6fa951 100644 --- a/lib/pleroma/web/masto_fe_controller.ex +++ b/lib/pleroma/web/masto_fe_controller.ex @@ -53,7 +53,24 @@ defmodule Pleroma.Web.MastoFEController do @doc "PUT /api/web/settings: Backend-obscure settings blob for MastoFE, don't parse/reuse elsewhere" def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do - with {:ok, _} <- User.mastodon_settings_update(user, settings) do + with {:ok, user} <- User.mastodon_settings_update(user, settings) do + if settings = get_in(user.settings, ["notifications", "shows"]) do + notify_settings = + Enum.map(settings, fn {k, v} -> + if v == false do + k + end + end) + |> Enum.filter(& &1) + + notification_settings = + user.notification_settings + |> Map.from_struct() + |> Map.put(:exclude_types, notify_settings) + + User.update_notification_settings(user, notification_settings) + end + json(conn, %{}) else e -> diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex index 71479550e..17cfc5443 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex @@ -52,6 +52,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do def get_notifications(user, params \\ %{}) do options = cast_params(params) + user_exclude_types = user.notification_settings.exclude_types + + options = + if (!options[:exclude_types] or options[:exclude_types] == []) and user_exclude_types != [] do + Map.put(options, :exclude_types, user_exclude_types) + else + options + end + user |> Notification.for_user_query(options) |> restrict(:include_types, options) diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex index 83cbdc870..ec68b40f8 100644 --- a/lib/pleroma/web/push/impl.ex +++ b/lib/pleroma/web/push/impl.ex @@ -19,7 +19,7 @@ defmodule Pleroma.Web.Push.Impl do @types ["Create", "Follow", "Announce", "Like", "Move", "EmojiReact"] @doc "Performs sending notifications for user subscriptions" - @spec perform(Notification.t()) :: list(any) | :error | {:error, :unknown_type} + @spec perform(Notification.t()) :: {:ok, list(any)} | {:error, :unknown_type} def perform( %{ activity: %{data: %{"type" => activity_type}} = activity, @@ -164,13 +164,14 @@ defmodule Pleroma.Web.Push.Impl do _object, mastodon_type ) - when type in ["Follow", "Like"] do + when type in ["Follow", "Like", "EmojiReact"] do mastodon_type = mastodon_type || notification.type case mastodon_type do "follow" -> "@#{actor.nickname} has followed you" "follow_request" -> "@#{actor.nickname} has requested to follow you" "favourite" -> "@#{actor.nickname} has favorited your post" + "pleroma:emoji_reaction" -> "@#{actor.nickname} has reacted to your post" end end diff --git a/lib/pleroma/web/push/subscription.ex b/lib/pleroma/web/push/subscription.ex index 4f6c9bc9f..7ea32b258 100644 --- a/lib/pleroma/web/push/subscription.ex +++ b/lib/pleroma/web/push/subscription.ex @@ -29,7 +29,8 @@ defmodule Pleroma.Web.Push.Subscription do @supported_alert_types ~w[follow favourite mention reblog pleroma:chat_mention pleroma:emoji_reaction]a defp alerts(%{data: %{alerts: alerts}}) do - alerts = Map.take(alerts, @supported_alert_types) + types = Enum.map(Pleroma.Notification.types(), &String.to_atom/1) + alerts = Map.take(alerts, types) %{"alerts" => alerts} end diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index fc3bbb130..6c77e4a57 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -171,8 +171,15 @@ defmodule Pleroma.Web.Streamer do end end - def filtered_by_user?(%User{} = user, %Notification{activity: activity}, _) do - filtered_by_user?(user, activity, :notification) + def filtered_by_user?( + %User{} = user, + %Notification{activity: activity, type: notification_type}, + _ + ) do + notification_settings = user.notification_settings + + notification_type not in notification_settings.exclude_types and + filtered_by_user?(user, activity, :notification) end defp do_stream("direct", item) do diff --git a/priv/repo/migrations/20200602101826_add_exclude_types_into_user_notification_settings.exs b/priv/repo/migrations/20200602101826_add_exclude_types_into_user_notification_settings.exs new file mode 100644 index 000000000..a9204b9cf --- /dev/null +++ b/priv/repo/migrations/20200602101826_add_exclude_types_into_user_notification_settings.exs @@ -0,0 +1,11 @@ +defmodule Pleroma.Repo.Migrations.AddExcludeTypesIntoUserNotificationSettings do + use Ecto.Migration + + def up do + execute(""" + UPDATE users SET notification_settings = jsonb_set(notification_settings, '{exclude_types}', '[]') WHERE local = true; + """) + end + + def down, do: :ok +end diff --git a/test/pleroma/web/mastodon_api/controllers/notification_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/notification_controller_test.exs index 2615912a8..f51f77946 100644 --- a/test/pleroma/web/mastodon_api/controllers/notification_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/notification_controller_test.exs @@ -408,6 +408,83 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do assert [%{"id" => ^reblog_notification_id}] = json_response_and_validate_schema(conn_res, 200) end + defp update_notification_settings_and_conn(user, conn, exclude_types) do + {:ok, user} = + User.update_notification_settings(user, %{ + "exclude_types" => exclude_types + }) + + conn + |> assign(:user, user) + |> get("/api/v1/notifications") + end + + test "filters notifications with user settings for exclude_types" do + %{user: user, conn: conn} = oauth_access(["read:notifications"]) + + other_user = insert(:user) + + {:ok, mention_activity} = CommonAPI.post(other_user, %{status: "hey @#{user.nickname}"}) + {:ok, create_activity} = CommonAPI.post(user, %{status: "hey"}) + {:ok, favorite_activity} = CommonAPI.favorite(other_user, create_activity.id) + {:ok, reblog_activity} = CommonAPI.repeat(create_activity.id, other_user) + {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user) + + mention_notification_id = get_notification_id_by_activity(mention_activity) + favorite_notification_id = get_notification_id_by_activity(favorite_activity) + reblog_notification_id = get_notification_id_by_activity(reblog_activity) + follow_notification_id = get_notification_id_by_activity(follow_activity) + + conn_res = + update_notification_settings_and_conn(user, conn, ["mention", "favourite", "reblog"]) + + assert [%{"id" => ^follow_notification_id}] = json_response_and_validate_schema(conn_res, 200) + + conn_res = + update_notification_settings_and_conn(user, conn, ["favourite", "reblog", "follow"]) + + assert [%{"id" => ^mention_notification_id}] = + json_response_and_validate_schema(conn_res, 200) + + conn_res = update_notification_settings_and_conn(user, conn, ["reblog", "follow", "mention"]) + + assert [%{"id" => ^favorite_notification_id}] = + json_response_and_validate_schema(conn_res, 200) + + conn_res = + update_notification_settings_and_conn(user, conn, ["follow", "mention", "favourite"]) + + assert [%{"id" => ^reblog_notification_id}] = json_response_and_validate_schema(conn_res, 200) + end + + test "exclude_types params have high priority than user settings" do + %{user: user, conn: conn} = oauth_access(["read:notifications"]) + + other_user = insert(:user) + + {:ok, _mention_activity} = CommonAPI.post(other_user, %{status: "hey @#{user.nickname}"}) + {:ok, create_activity} = CommonAPI.post(user, %{status: "hey"}) + {:ok, _favorite_activity} = CommonAPI.favorite(other_user, create_activity.id) + {:ok, _reblog_activity} = CommonAPI.repeat(create_activity.id, other_user) + {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user) + + follow_notification_id = get_notification_id_by_activity(follow_activity) + + {:ok, user} = + User.update_notification_settings(user, %{ + "exclude_types" => ["favourite", "reblog", "follow", "mention"] + }) + + query = params_to_query(%{exclude_types: ["mention", "favourite", "reblog"]}) + + conn_res = + conn + |> assign(:user, user) + |> get("/api/v1/notifications?" <> query) + + assert [%{"id" => ^follow_notification_id}] = json_response_and_validate_schema(conn_res, 200) + end + test "filters notifications using include_types" do %{user: user, conn: conn} = oauth_access(["read:notifications"]) other_user = insert(:user) diff --git a/test/pleroma/web/mastodon_api/masto_fe_controller_test.exs b/test/pleroma/web/mastodon_api/masto_fe_controller_test.exs index ea66c708f..c13b9cb73 100644 --- a/test/pleroma/web/mastodon_api/masto_fe_controller_test.exs +++ b/test/pleroma/web/mastodon_api/masto_fe_controller_test.exs @@ -11,19 +11,96 @@ defmodule Pleroma.Web.MastodonAPI.MastoFEControllerTest do setup do: clear_config([:instance, :public]) - test "put settings", %{conn: conn} do - user = insert(:user) + describe "put_settings/2" do + setup do + %{conn: conn, user: user} = oauth_access(["write:accounts"]) + [conn: conn, user: user] + end - conn = - conn - |> assign(:user, user) - |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:accounts"])) - |> put("/api/web/settings", %{"data" => %{"programming" => "socks"}}) + test "common", %{conn: conn, user: user} do + assert conn + |> put("/api/web/settings", %{"data" => %{"programming" => "socks"}}) + |> json_response(200) - assert _result = json_response(conn, 200) + user = User.get_cached_by_ap_id(user.ap_id) + assert user.mastofe_settings == %{"programming" => "socks"} + end - user = User.get_cached_by_ap_id(user.ap_id) - assert user.mastofe_settings == %{"programming" => "socks"} + test "saves notification settings", %{conn: conn, user: user} do + assert conn + |> put("/api/web/settings", %{ + "data" => %{ + "notifications" => %{ + "alerts" => %{ + "favourite" => true, + "follow" => true, + "follow_request" => true, + "mention" => true, + "poll" => true, + "reblog" => true + }, + "quickFilter" => %{"active" => "all", "advanced" => true, "show" => true}, + "shows" => %{ + "favourite" => false, + "follow" => false, + "follow_request" => false, + "mention" => false, + "poll" => false, + "reblog" => false + }, + "sounds" => %{ + "favourite" => true, + "follow" => true, + "follow_request" => true, + "mention" => true, + "poll" => true, + "reblog" => true + } + } + } + }) + + user = User.get_cached_by_ap_id(user.ap_id) + + assert user.settings == %{ + "notifications" => %{ + "alerts" => %{ + "favourite" => true, + "follow" => true, + "follow_request" => true, + "mention" => true, + "poll" => true, + "reblog" => true + }, + "quickFilter" => %{"active" => "all", "advanced" => true, "show" => true}, + "shows" => %{ + "favourite" => false, + "follow" => false, + "follow_request" => false, + "mention" => false, + "poll" => false, + "reblog" => false + }, + "sounds" => %{ + "favourite" => true, + "follow" => true, + "follow_request" => true, + "mention" => true, + "poll" => true, + "reblog" => true + } + } + } + + assert user.notification_settings.exclude_types == [ + "favourite", + "follow", + "follow_request", + "mention", + "poll", + "reblog" + ] + end end describe "index/2 redirections" do diff --git a/test/pleroma/web/mastodon_api/views/account_view_test.exs b/test/pleroma/web/mastodon_api/views/account_view_test.exs index 5373a17c3..a9c66e63e 100644 --- a/test/pleroma/web/mastodon_api/views/account_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/account_view_test.exs @@ -125,6 +125,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do notification_settings = %{ block_from_strangers: false, hide_notification_contents: false + followers: true, + follows: true, + non_followers: true, + non_follows: true, + privacy_option: false, + exclude_types: [] } privacy = user.default_scope diff --git a/test/pleroma/web/push/impl_test.exs b/test/pleroma/web/push/impl_test.exs index b3ca1a337..a7c2431ed 100644 --- a/test/pleroma/web/push/impl_test.exs +++ b/test/pleroma/web/push/impl_test.exs @@ -69,6 +69,27 @@ defmodule Pleroma.Web.Push.ImplTest do assert Impl.perform(notif) == {:ok, [:ok, :ok]} end + test "notification for follow_request" do + user = insert(:user) + reaction_user = insert(:user) + + insert(:push_subscription, user: user, data: %{alerts: %{"pleroma:emoji_reaction" => true}}) + insert(:push_subscription, user: user, data: %{alerts: %{"pleroma:emoji_reaction" => false}}) + + {:ok, activity} = CommonAPI.post(user, %{status: " put("/api/pleroma/notification_settings", %{ "block_from_strangers" => true, "bar" => 1 + "followers" => false, + "bar" => 1, + "exclude_types" => ["follow"] }) |> json_response(:ok) @@ -36,6 +39,12 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do assert %Pleroma.User.NotificationSetting{ block_from_strangers: true, hide_notification_contents: false + followers: false, + follows: true, + non_follows: true, + non_followers: true, + privacy_option: false, + exclude_types: ["follow"] } == user.notification_settings end