1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.SubscriptionNotification do
10 alias Pleroma.Pagination
12 alias Pleroma.SubscriptionNotification
14 alias Pleroma.Web.CommonAPI.Utils
15 alias Pleroma.Web.Push
16 alias Pleroma.Web.Streamer
21 @type t :: %__MODULE__{}
23 schema "subscription_notifications" do
24 belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
25 belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
30 def changeset(%SubscriptionNotification{} = notification, attrs) do
31 cast(notification, attrs, [])
34 def for_user_query(user, opts \\ []) do
36 SubscriptionNotification
37 |> where(user_id: ^user.id)
41 "? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')",
45 |> join(:inner, [n], activity in assoc(n, :activity))
46 |> join(:left, [n, a], object in Object,
49 "(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)",
54 |> preload([n, a, o], activity: {a, object: o})
56 if opts[:with_muted] do
60 |> where([n, a], a.actor not in ^user.info.muted_notifications)
61 |> where([n, a], a.actor not in ^user.info.blocks)
64 fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.info.domain_blocks
66 |> join(:left, [n, a], tm in Pleroma.ThreadMute,
67 on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data)
69 |> where([n, a, o, tm], is_nil(tm.user_id))
73 def for_user(user, opts \\ %{}) do
75 |> for_user_query(opts)
76 |> Pagination.fetch_paginated(opts)
80 Returns notifications for user received since given date.
84 iex> Pleroma.SubscriptionNotification.for_user_since(%Pleroma.User{}, ~N[2019-04-13 11:22:33])
85 [%Pleroma.SubscriptionNotification{}, %Pleroma.SubscriptionNotification{}]
87 iex> Pleroma.SubscriptionNotification.for_user_since(%Pleroma.User{}, ~N[2019-04-15 11:22:33])
90 @spec for_user_since(Pleroma.User.t(), NaiveDateTime.t()) :: [t()]
91 def for_user_since(user, date) do
94 |> where([n], n.updated_at > ^date)
98 def clear_up_to(%{id: user_id} = _user, id) do
100 n in SubscriptionNotification,
101 where: n.user_id == ^user_id,
104 |> Repo.delete_all([])
107 def get(%{id: user_id} = _user, id) do
110 n in SubscriptionNotification,
112 join: activity in assoc(n, :activity),
113 preload: [activity: activity]
116 case Repo.one(query) do
117 %{user_id: ^user_id} = notification ->
121 {:error, "Cannot get notification"}
126 from(n in SubscriptionNotification, where: n.user_id == ^user.id)
130 def destroy_multiple(%{id: user_id} = _user, ids) do
131 from(n in SubscriptionNotification,
133 where: n.user_id == ^user_id
138 def dismiss(%{id: user_id} = _user, id) do
139 case Repo.get(SubscriptionNotification, id) do
140 %{user_id: ^user_id} = notification ->
141 Repo.delete(notification)
144 {:error, "Cannot dismiss notification"}
148 def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity) do
149 case Object.normalize(activity) do
150 %{data: %{"type" => "Answer"}} ->
154 users = get_notified_from_activity(activity)
155 notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
160 def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity)
161 when type in ["Like", "Announce", "Follow"] do
164 |> get_notified_from_activity()
165 |> Enum.map(&create_notification(activity, &1))
170 def create_notifications(_), do: {:ok, []}
172 # TODO move to sql, too.
173 def create_notification(%Activity{} = activity, %User{} = user) do
174 unless skip?(activity, user) do
175 notification = %SubscriptionNotification{user_id: user.id, activity: activity}
176 {:ok, notification} = Repo.insert(notification)
177 Streamer.stream("user", notification)
178 Streamer.stream("user:subscription_notification", notification)
179 Push.send(notification)
184 def get_notified_from_activity(activity, local_only \\ true)
186 def get_notified_from_activity(
187 %Activity{data: %{"to" => _, "type" => type} = _data} = activity,
190 when type in ["Create", "Like", "Announce", "Follow"] do
192 |> Utils.maybe_notify_subscribers(activity)
194 |> User.get_users_from_set(local_only)
197 def get_notified_from_activity(_, _local_only), do: []
199 @spec skip?(Activity.t(), User.t()) :: boolean()
200 def skip?(activity, user) do
209 |> Enum.any?(&skip?(&1, activity, user))
212 @spec skip?(atom(), Activity.t(), User.t()) :: boolean()
213 def skip?(:self, activity, user) do
214 activity.data["actor"] == user.ap_id
219 %{data: %{"actor" => actor}},
220 %{info: %{notification_settings: %{"followers" => false}}} = user
223 |> User.get_cached_by_ap_id()
224 |> User.following?(user)
230 %{info: %{notification_settings: %{"non_followers" => false}}} = user
232 actor = activity.data["actor"]
233 follower = User.get_cached_by_ap_id(actor)
234 !User.following?(follower, user)
237 def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do
238 actor = activity.data["actor"]
239 followed = User.get_cached_by_ap_id(actor)
240 User.following?(user, followed)
246 %{info: %{notification_settings: %{"non_follows" => false}}} = user
248 actor = activity.data["actor"]
249 followed = User.get_cached_by_ap_id(actor)
250 !User.following?(user, followed)
253 def skip?(:recently_followed, %{data: %{"type" => "Follow", "actor" => actor}}, user) do
255 |> SubscriptionNotification.for_user()
256 |> Enum.any?(&match?(%{activity: %{data: %{"type" => "Follow", "actor" => ^actor}}}, &1))
259 def skip?(_, _, _), do: false