Merge branch 'match-file-name' into 'develop'
[akkoma] / lib / pleroma / notification.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Notification do
6 use Ecto.Schema
7
8 alias Pleroma.Activity
9 alias Pleroma.Notification
10 alias Pleroma.Object
11 alias Pleroma.Pagination
12 alias Pleroma.Repo
13 alias Pleroma.User
14 alias Pleroma.Web.CommonAPI.Utils
15 alias Pleroma.Web.Push
16 alias Pleroma.Web.Streamer
17
18 import Ecto.Query
19 import Ecto.Changeset
20
21 schema "notifications" do
22 field(:seen, :boolean, default: false)
23 belongs_to(:user, User, type: Pleroma.FlakeId)
24 belongs_to(:activity, Activity, type: Pleroma.FlakeId)
25
26 timestamps()
27 end
28
29 def changeset(%Notification{} = notification, attrs) do
30 notification
31 |> cast(attrs, [:seen])
32 end
33
34 def for_user_query(user, opts) do
35 query =
36 Notification
37 |> where(user_id: ^user.id)
38 |> where(
39 [n, a],
40 fragment(
41 "? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')",
42 a.actor
43 )
44 )
45 |> join(:inner, [n], activity in assoc(n, :activity))
46 |> join(:left, [n, a], object in Object,
47 on:
48 fragment(
49 "(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)",
50 object.data,
51 a.data
52 )
53 )
54 |> preload([n, a, o], activity: {a, object: o})
55
56 if opts[:with_muted] do
57 query
58 else
59 where(query, [n, a], a.actor not in ^user.info.muted_notifications)
60 |> where([n, a], a.actor not in ^user.info.blocks)
61 |> where(
62 [n, a],
63 fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.info.domain_blocks
64 )
65 |> join(:left, [n, a], tm in Pleroma.ThreadMute,
66 on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data)
67 )
68 |> where([n, a, o, tm], is_nil(tm.user_id))
69 end
70 end
71
72 def for_user(user, opts \\ %{}) do
73 user
74 |> for_user_query(opts)
75 |> Pagination.fetch_paginated(opts)
76 end
77
78 def set_read_up_to(%{id: user_id} = _user, id) do
79 query =
80 from(
81 n in Notification,
82 where: n.user_id == ^user_id,
83 where: n.id <= ^id,
84 update: [
85 set: [seen: true]
86 ]
87 )
88
89 Repo.update_all(query, [])
90 end
91
92 def read_one(%User{} = user, notification_id) do
93 with {:ok, %Notification{} = notification} <- get(user, notification_id) do
94 notification
95 |> changeset(%{seen: true})
96 |> Repo.update()
97 end
98 end
99
100 def get(%{id: user_id} = _user, id) do
101 query =
102 from(
103 n in Notification,
104 where: n.id == ^id,
105 join: activity in assoc(n, :activity),
106 preload: [activity: activity]
107 )
108
109 notification = Repo.one(query)
110
111 case notification do
112 %{user_id: ^user_id} ->
113 {:ok, notification}
114
115 _ ->
116 {:error, "Cannot get notification"}
117 end
118 end
119
120 def clear(user) do
121 from(n in Notification, where: n.user_id == ^user.id)
122 |> Repo.delete_all()
123 end
124
125 def destroy_multiple(%{id: user_id} = _user, ids) do
126 from(n in Notification,
127 where: n.id in ^ids,
128 where: n.user_id == ^user_id
129 )
130 |> Repo.delete_all()
131 end
132
133 def dismiss(%{id: user_id} = _user, id) do
134 notification = Repo.get(Notification, id)
135
136 case notification do
137 %{user_id: ^user_id} ->
138 Repo.delete(notification)
139
140 _ ->
141 {:error, "Cannot dismiss notification"}
142 end
143 end
144
145 def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity) do
146 object = Object.normalize(activity)
147
148 unless object && object.data["type"] == "Answer" do
149 users = get_notified_from_activity(activity)
150 notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
151 {:ok, notifications}
152 else
153 {:ok, []}
154 end
155 end
156
157 def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity)
158 when type in ["Like", "Announce", "Follow"] do
159 users = get_notified_from_activity(activity)
160 notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
161 {:ok, notifications}
162 end
163
164 def create_notifications(_), do: {:ok, []}
165
166 # TODO move to sql, too.
167 def create_notification(%Activity{} = activity, %User{} = user) do
168 unless skip?(activity, user) do
169 notification = %Notification{user_id: user.id, activity: activity}
170 {:ok, notification} = Repo.insert(notification)
171 Streamer.stream("user", notification)
172 Streamer.stream("user:notification", notification)
173 Push.send(notification)
174 notification
175 end
176 end
177
178 def get_notified_from_activity(activity, local_only \\ true)
179
180 def get_notified_from_activity(
181 %Activity{data: %{"to" => _, "type" => type} = _data} = activity,
182 local_only
183 )
184 when type in ["Create", "Like", "Announce", "Follow"] do
185 recipients =
186 []
187 |> Utils.maybe_notify_to_recipients(activity)
188 |> Utils.maybe_notify_mentioned_recipients(activity)
189 |> Utils.maybe_notify_subscribers(activity)
190 |> Enum.uniq()
191
192 User.get_users_from_set(recipients, local_only)
193 end
194
195 def get_notified_from_activity(_, _local_only), do: []
196
197 @spec skip?(Activity.t(), User.t()) :: boolean()
198 def skip?(activity, user) do
199 [
200 :self,
201 :followers,
202 :follows,
203 :non_followers,
204 :non_follows,
205 :recently_followed
206 ]
207 |> Enum.any?(&skip?(&1, activity, user))
208 end
209
210 @spec skip?(atom(), Activity.t(), User.t()) :: boolean()
211 def skip?(:self, activity, user) do
212 activity.data["actor"] == user.ap_id
213 end
214
215 def skip?(
216 :followers,
217 activity,
218 %{info: %{notification_settings: %{"followers" => false}}} = user
219 ) do
220 actor = activity.data["actor"]
221 follower = User.get_cached_by_ap_id(actor)
222 User.following?(follower, user)
223 end
224
225 def skip?(
226 :non_followers,
227 activity,
228 %{info: %{notification_settings: %{"non_followers" => false}}} = user
229 ) do
230 actor = activity.data["actor"]
231 follower = User.get_cached_by_ap_id(actor)
232 !User.following?(follower, user)
233 end
234
235 def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do
236 actor = activity.data["actor"]
237 followed = User.get_cached_by_ap_id(actor)
238 User.following?(user, followed)
239 end
240
241 def skip?(
242 :non_follows,
243 activity,
244 %{info: %{notification_settings: %{"non_follows" => false}}} = user
245 ) do
246 actor = activity.data["actor"]
247 followed = User.get_cached_by_ap_id(actor)
248 !User.following?(user, followed)
249 end
250
251 def skip?(:recently_followed, %{data: %{"type" => "Follow"}} = activity, user) do
252 actor = activity.data["actor"]
253
254 Notification.for_user(user)
255 |> Enum.any?(fn
256 %{activity: %{data: %{"type" => "Follow", "actor" => ^actor}}} -> true
257 _ -> false
258 end)
259 end
260
261 def skip?(_, _, _), do: false
262 end