Merge remote-tracking branch 'upstream/develop' into by-approval
[akkoma] / lib / pleroma / notification.ex
index 49e27c05a4a255034c4a271e03230ed760c202c0..32bcfcaba34d9c830211c3f8180481feff193b5d 100644 (file)
@@ -30,6 +30,9 @@ defmodule Pleroma.Notification do
 
   schema "notifications" do
     field(:seen, :boolean, default: false)
+    # This is an enum type in the database. If you add a new notification type,
+    # remember to add a migration to add it to the `notifications_type` enum
+    # as well.
     field(:type, :string)
     belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
     belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
@@ -37,26 +40,6 @@ defmodule Pleroma.Notification do
     timestamps()
   end
 
-  def fill_in_notification_types do
-    query =
-      from(n in __MODULE__,
-        where: is_nil(n.type),
-        preload: :activity
-      )
-
-    query
-    |> Repo.all()
-    |> Enum.each(fn notification ->
-      type =
-        notification.activity
-        |> type_from_activity(no_cachex: true)
-
-      notification
-      |> changeset(%{type: type})
-      |> Repo.update()
-    end)
-  end
-
   def update_notification_type(user, activity) do
     with %__MODULE__{} = notification <-
            Repo.get_by(__MODULE__, user_id: user.id, activity_id: activity.id) do
@@ -78,9 +61,21 @@ defmodule Pleroma.Notification do
     |> Repo.aggregate(:count, :id)
   end
 
+  @notification_types ~w{
+    favourite
+    follow
+    follow_request
+    mention
+    move
+    pleroma:chat_mention
+    pleroma:emoji_reaction
+    reblog
+  }
+
   def changeset(%Notification{} = notification, attrs) do
     notification
     |> cast(attrs, [:seen, :type])
+    |> validate_inclusion(:type, @notification_types)
   end
 
   @spec last_read_query(User.t()) :: Ecto.Queryable.t()
@@ -135,6 +130,7 @@ defmodule Pleroma.Notification do
     |> preload([n, a, o], activity: {a, object: o})
     |> exclude_notification_muted(user, exclude_notification_muted_opts)
     |> exclude_blocked(user, exclude_blocked_opts)
+    |> exclude_filtered(user)
     |> exclude_visibility(opts)
   end
 
@@ -163,6 +159,20 @@ defmodule Pleroma.Notification do
     |> where([n, a, o, tm], is_nil(tm.user_id))
   end
 
+  defp exclude_filtered(query, user) do
+    case Pleroma.Filter.compose_regex(user) do
+      nil ->
+        query
+
+      regex ->
+        from([_n, a, o] in query,
+          where:
+            fragment("not(?->>'content' ~* ?)", o.data, ^regex) or
+              fragment("?->>'actor' = ?", o.data, ^user.ap_id)
+        )
+    end
+  end
+
   @valid_visibilities ~w[direct unlisted public private]
 
   defp exclude_visibility(query, %{exclude_visibilities: visibility})
@@ -171,8 +181,16 @@ defmodule Pleroma.Notification do
       query
       |> join(:left, [n, a], mutated_activity in Pleroma.Activity,
         on:
-          fragment("?->>'context'", a.data) ==
-            fragment("?->>'context'", mutated_activity.data) and
+          fragment(
+            "COALESCE((?->'object')->>'id', ?->>'object')",
+            a.data,
+            a.data
+          ) ==
+            fragment(
+              "COALESCE((?->'object')->>'id', ?->>'object')",
+              mutated_activity.data,
+              mutated_activity.data
+            ) and
             fragment("(?->>'type' = 'Like' or ?->>'type' = 'Announce')", a.data, a.data) and
             fragment("?->>'type'", mutated_activity.data) == "Create",
         as: :mutated_activity
@@ -334,6 +352,7 @@ defmodule Pleroma.Notification do
     end
   end
 
+  @spec create_notifications(Activity.t(), keyword()) :: {:ok, [Notification.t()] | []}
   def create_notifications(activity, options \\ [])
 
   def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity, options) do
@@ -364,27 +383,15 @@ defmodule Pleroma.Notification do
         do_send = do_send && user in enabled_receivers
         create_notification(activity, user, do_send)
       end)
+      |> Enum.reject(&is_nil/1)
 
     {:ok, notifications}
   end
 
-  defp type_from_activity(%{data: %{"type" => type}} = activity, opts \\ []) do
+  defp type_from_activity(%{data: %{"type" => type}} = activity) do
     case type do
       "Follow" ->
-        accepted_function =
-          if Keyword.get(opts, :no_cachex, false) do
-            # A special function to make this usable in a migration.
-            fn activity ->
-              with %User{} = follower <- User.get_by_ap_id(activity.data["actor"]),
-                   %User{} = followed <- User.get_by_ap_id(activity.data["object"]) do
-                Pleroma.FollowingRelationship.following?(follower, followed)
-              end
-            end
-          else
-            &Activity.follow_accepted?/1
-          end
-
-        if accepted_function.(activity) do
+        if Activity.follow_accepted?(activity) do
           "follow"
         else
           "follow_request"
@@ -490,6 +497,10 @@ defmodule Pleroma.Notification do
     end
   end
 
+  def get_potential_receiver_ap_ids(%{data: %{"type" => "Follow", "object" => object_id}}) do
+    [object_id]
+  end
+
   def get_potential_receiver_ap_ids(activity) do
     []
     |> Utils.maybe_notify_to_recipients(activity)
@@ -559,11 +570,13 @@ defmodule Pleroma.Notification do
   def skip?(%Activity{} = activity, %User{} = user) do
     [
       :self,
+      :invisible,
       :followers,
       :follows,
       :non_followers,
       :non_follows,
-      :recently_followed
+      :recently_followed,
+      :filtered
     ]
     |> Enum.find(&skip?(&1, activity, user))
   end
@@ -575,6 +588,12 @@ defmodule Pleroma.Notification do
     activity.data["actor"] == user.ap_id
   end
 
+  def skip?(:invisible, %Activity{} = activity, _) do
+    actor = activity.data["actor"]
+    user = User.get_cached_by_ap_id(actor)
+    User.invisible?(user)
+  end
+
   def skip?(
         :followers,
         %Activity{} = activity,
@@ -626,6 +645,26 @@ defmodule Pleroma.Notification do
     end)
   end
 
+  def skip?(:filtered, %{data: %{"type" => type}}, _) when type in ["Follow", "Move"], do: false
+
+  def skip?(:filtered, activity, user) do
+    object = Object.normalize(activity)
+
+    cond do
+      is_nil(object) ->
+        false
+
+      object.data["actor"] == user.ap_id ->
+        false
+
+      not is_nil(regex = Pleroma.Filter.compose_regex(user, :re)) ->
+        Regex.match?(regex, object.data["content"])
+
+      true ->
+        false
+    end
+  end
+
   def skip?(_, _, _), do: false
 
   def for_user_and_activity(user, activity) do