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