[#1559] Support for "follow_request" notifications (configurable).
authorIvan Tashkinov <ivantashkinov@gmail.com>
Tue, 7 Apr 2020 18:52:32 +0000 (21:52 +0300)
committerIvan Tashkinov <ivantashkinov@gmail.com>
Tue, 7 Apr 2020 18:52:32 +0000 (21:52 +0300)
(Not currently supported by PleromaFE, thus disabled by default).

CHANGELOG.md
config/config.exs
config/description.exs
lib/pleroma/activity.ex
lib/pleroma/notification.ex
lib/pleroma/user.ex
lib/pleroma/web/mastodon_api/views/notification_view.ex
lib/pleroma/web/push/impl.ex
lib/pleroma/web/push/subscription.ex

index b6e5d807c47f19edf0e09785476dcedbdc495af0..b3b63ac544c00a710af945950a99079cb312e564 100644 (file)
@@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - NodeInfo: `pleroma_emoji_reactions` to the `features` list.
 - Configuration: `:restrict_unauthenticated` setting, restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses.
 - New HTTP adapter [gun](https://github.com/ninenines/gun). Gun adapter requires minimum OTP version of 22.2 otherwise Pleroma won’t start. For hackney OTP update is not required.
+- Notifications: Added `follow_request` notification type (configurable, see `[:notifications, :enable_follow_request_notifications]` setting).
 <details>
   <summary>API Changes</summary>
 - Mastodon API: Support for `include_types` in `/api/v1/notifications`.
index 232a91bf132c8063f8f0f730121bb68eade6ad6f..d40c2240bd39d750a9e68bf5cac4cdc36370346a 100644 (file)
@@ -559,6 +559,8 @@ config :pleroma, :email_notifications,
     inactivity_threshold: 7
   }
 
+config :pleroma, :notifications, enable_follow_request_notifications: false
+
 config :pleroma, :oauth2,
   token_expires_in: 600,
   issue_new_refresh_token: true,
index 642f1a3ce9d2be466a15fe24495dfb189bf8ed0b..b1938912cb7fa72c1e69cc66cb7fc10937d65762 100644 (file)
@@ -2267,6 +2267,20 @@ config :pleroma, :config_description, [
       }
     ]
   },
+  %{
+    group: :pleroma,
+    key: :notifications,
+    type: :group,
+    description: "Notification settings",
+    children: [
+      %{
+        key: :enable_follow_request_notifications,
+        type: :boolean,
+        description:
+          "Enables notifications on new follow requests (causes issues with older PleromaFE versions)."
+      }
+    ]
+  },
   %{
     group: :pleroma,
     key: Pleroma.Emails.UserEmail,
index 5a8329e69368de5c03e1ed4fc7ad22de7f7e6ad2..3803d8e501df5210c10b0db2c6d2d1d76bae564c 100644 (file)
@@ -27,17 +27,13 @@ defmodule Pleroma.Activity do
   # https://github.com/tootsuite/mastodon/blob/master/app/models/notification.rb#L19
   @mastodon_notification_types %{
     "Create" => "mention",
-    "Follow" => "follow",
+    "Follow" => ["follow", "follow_request"],
     "Announce" => "reblog",
     "Like" => "favourite",
     "Move" => "move",
     "EmojiReact" => "pleroma:emoji_reaction"
   }
 
-  @mastodon_to_ap_notification_types for {k, v} <- @mastodon_notification_types,
-                                         into: %{},
-                                         do: {v, k}
-
   schema "activities" do
     field(:data, :map)
     field(:local, :boolean, default: true)
@@ -291,15 +287,41 @@ defmodule Pleroma.Activity do
 
   defp purge_web_resp_cache(nil), do: nil
 
-  for {ap_type, type} <- @mastodon_notification_types do
+  def follow_accepted?(
+        %Activity{data: %{"type" => "Follow", "object" => followed_ap_id}} = activity
+      ) do
+    with %User{} = follower <- Activity.user_actor(activity),
+         %User{} = followed <- User.get_cached_by_ap_id(followed_ap_id) do
+      Pleroma.FollowingRelationship.following?(follower, followed)
+    else
+      _ -> false
+    end
+  end
+
+  def follow_accepted?(_), do: false
+
+  for {ap_type, type} <- @mastodon_notification_types, not is_list(type) do
     def mastodon_notification_type(%Activity{data: %{"type" => unquote(ap_type)}}),
       do: unquote(type)
   end
 
+  def mastodon_notification_type(%Activity{data: %{"type" => "Follow"}} = activity) do
+    if follow_accepted?(activity) do
+      "follow"
+    else
+      "follow_request"
+    end
+  end
+
   def mastodon_notification_type(%Activity{}), do: nil
 
   def from_mastodon_notification_type(type) do
-    Map.get(@mastodon_to_ap_notification_types, type)
+    with {k, _v} <-
+           Enum.find(@mastodon_notification_types, fn {_k, v} ->
+             v == type or (is_list(v) and type in v)
+           end) do
+      k
+    end
   end
 
   def all_by_actor_and_id(actor, status_ids \\ [])
index 04ee510b9f13fa7d346064988dacaea2d95babc1..73e19bf970cb3edc5c10fd36fc9e08dbcc059b57 100644 (file)
@@ -284,8 +284,17 @@ defmodule Pleroma.Notification do
     end
   end
 
+  def create_notifications(%Activity{data: %{"type" => "Follow"}} = activity) do
+    if Pleroma.Config.get([:notifications, :enable_follow_request_notifications]) ||
+         Activity.follow_accepted?(activity) do
+      do_create_notifications(activity)
+    else
+      {:ok, []}
+    end
+  end
+
   def create_notifications(%Activity{data: %{"type" => type}} = activity)
-      when type in ["Like", "Announce", "Follow", "Move", "EmojiReact"] do
+      when type in ["Like", "Announce", "Move", "EmojiReact"] do
     do_create_notifications(activity)
   end
 
index 71c8c3a4efe8e1975ff11c2469c782a30e4b274f..ac2594417ce8c206010ae28d3a6cbf9dbfa6d6d5 100644 (file)
@@ -699,6 +699,8 @@ defmodule Pleroma.User do
   def needs_update?(_), do: true
 
   @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
+
+  # "Locked" (self-locked) users demand explicit authorization of follow requests
   def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
     follow(follower, followed, "pending")
   end
index ae87d47016cd031dab3fd73d7fc36d9058dcc38b..feed471296f927f7b4269517e648593f5f644dc7 100644 (file)
@@ -116,6 +116,9 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
         "follow" ->
           response
 
+        "follow_request" ->
+          response
+
         "pleroma:emoji_reaction" ->
           response
           |> put_status(parent_activity_fn.(), reading_user, render_opts)
index afa510f086184f29ce55489e747e47f5ba0642d9..89d45b2e1f985f0a6f7d85000f4cc0725ce2e123 100644 (file)
@@ -16,6 +16,8 @@ defmodule Pleroma.Web.Push.Impl do
   require Logger
   import Ecto.Query
 
+  defdelegate mastodon_notification_type(activity), to: Activity
+
   @types ["Create", "Follow", "Announce", "Like", "Move"]
 
   @doc "Performs sending notifications for user subscriptions"
@@ -24,32 +26,32 @@ defmodule Pleroma.Web.Push.Impl do
         %{
           activity: %{data: %{"type" => activity_type}} = activity,
           user: %User{id: user_id}
-        } = notif
+        } = notification
       )
       when activity_type in @types do
-    actor = User.get_cached_by_ap_id(notif.activity.data["actor"])
+    actor = User.get_cached_by_ap_id(notification.activity.data["actor"])
 
-    type = Activity.mastodon_notification_type(notif.activity)
+    mastodon_type = mastodon_notification_type(notification.activity)
     gcm_api_key = Application.get_env(:web_push_encryption, :gcm_api_key)
     avatar_url = User.avatar_url(actor)
     object = Object.normalize(activity)
     user = User.get_cached_by_id(user_id)
     direct_conversation_id = Activity.direct_conversation_id(activity, user)
 
-    for subscription <- fetch_subsriptions(user_id),
-        get_in(subscription.data, ["alerts", type]) do
+    for subscription <- fetch_subscriptions(user_id),
+        Subscription.enabled?(subscription, mastodon_type) do
       %{
         access_token: subscription.token.token,
-        notification_id: notif.id,
-        notification_type: type,
+        notification_id: notification.id,
+        notification_type: mastodon_type,
         icon: avatar_url,
         preferred_locale: "en",
         pleroma: %{
-          activity_id: notif.activity.id,
+          activity_id: notification.activity.id,
           direct_conversation_id: direct_conversation_id
         }
       }
-      |> Map.merge(build_content(notif, actor, object))
+      |> Map.merge(build_content(notification, actor, object, mastodon_type))
       |> Jason.encode!()
       |> push_message(build_sub(subscription), gcm_api_key, subscription)
     end
@@ -82,7 +84,7 @@ defmodule Pleroma.Web.Push.Impl do
   end
 
   @doc "Gets user subscriptions"
-  def fetch_subsriptions(user_id) do
+  def fetch_subscriptions(user_id) do
     Subscription
     |> where(user_id: ^user_id)
     |> preload(:token)
@@ -99,28 +101,36 @@ defmodule Pleroma.Web.Push.Impl do
     }
   end
 
+  def build_content(notification, actor, object, mastodon_type \\ nil)
+
   def build_content(
         %{
           activity: %{data: %{"directMessage" => true}},
           user: %{notification_settings: %{privacy_option: true}}
         },
         actor,
-        _
+        _object,
+        _mastodon_type
       ) do
     %{title: "New Direct Message", body: "@#{actor.nickname}"}
   end
 
-  def build_content(notif, actor, object) do
+  def build_content(notification, actor, object, mastodon_type) do
+    mastodon_type = mastodon_type || mastodon_notification_type(notification.activity)
+
     %{
-      title: format_title(notif),
-      body: format_body(notif, actor, object)
+      title: format_title(notification, mastodon_type),
+      body: format_body(notification, actor, object, mastodon_type)
     }
   end
 
+  def format_body(activity, actor, object, mastodon_type \\ nil)
+
   def format_body(
         %{activity: %{data: %{"type" => "Create"}}},
         actor,
-        %{data: %{"content" => content}}
+        %{data: %{"content" => content}},
+        _mastodon_type
       ) do
     "@#{actor.nickname}: #{Utils.scrub_html_and_truncate(content, 80)}"
   end
@@ -128,33 +138,43 @@ defmodule Pleroma.Web.Push.Impl do
   def format_body(
         %{activity: %{data: %{"type" => "Announce"}}},
         actor,
-        %{data: %{"content" => content}}
+        %{data: %{"content" => content}},
+        _mastodon_type
       ) do
     "@#{actor.nickname} repeated: #{Utils.scrub_html_and_truncate(content, 80)}"
   end
 
   def format_body(
-        %{activity: %{data: %{"type" => type}}},
+        %{activity: %{data: %{"type" => type}}} = notification,
         actor,
-        _object
+        _object,
+        mastodon_type
       )
       when type in ["Follow", "Like"] do
-    case type do
-      "Follow" -> "@#{actor.nickname} has followed you"
-      "Like" -> "@#{actor.nickname} has favorited your post"
+    mastodon_type = mastodon_type || mastodon_notification_type(notification.activity)
+
+    case {type, mastodon_type} do
+      {"Follow", "follow"} -> "@#{actor.nickname} has followed you"
+      {"Follow", "follow_request"} -> "@#{actor.nickname} has requested to follow you"
+      {"Like", _} -> "@#{actor.nickname} has favorited your post"
     end
   end
 
-  def format_title(%{activity: %{data: %{"directMessage" => true}}}) do
+  def format_title(activity, mastodon_type \\ nil)
+
+  def format_title(%{activity: %{data: %{"directMessage" => true}}}, _mastodon_type) do
     "New Direct Message"
   end
 
-  def format_title(%{activity: %{data: %{"type" => type}}}) do
-    case type do
-      "Create" -> "New Mention"
-      "Follow" -> "New Follower"
-      "Announce" -> "New Repeat"
-      "Like" -> "New Favorite"
+  def format_title(%{activity: %{data: %{"type" => type}}} = notification, mastodon_type) do
+    mastodon_type = mastodon_type || mastodon_notification_type(notification.activity)
+
+    case {type, mastodon_type} do
+      {"Create", _} -> "New Mention"
+      {"Follow", "follow"} -> "New Follower"
+      {"Follow", "follow_request"} -> "New Follow Request"
+      {"Announce", _} -> "New Repeat"
+      {"Like", _} -> "New Favorite"
     end
   end
 end
index 5c448d6c9e2c3ef4247a144602fa9dfaf172f9c9..b99b0c5fb494279896e5061311739de4b5e17ab6 100644 (file)
@@ -32,6 +32,14 @@ defmodule Pleroma.Web.Push.Subscription do
     %{"alerts" => alerts}
   end
 
+  def enabled?(subscription, "follow_request") do
+    enabled?(subscription, "follow")
+  end
+
+  def enabled?(subscription, alert_type) do
+    get_in(subscription.data, ["alerts", alert_type])
+  end
+
   def create(
         %User{} = user,
         %Token{} = token,