First
[anni] / lib / pleroma / emails / user_email.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Emails.UserEmail do
6   @moduledoc "User emails"
7
8   require Pleroma.Web.Gettext
9
10   alias Pleroma.Config
11   alias Pleroma.User
12   alias Pleroma.Web.Endpoint
13   alias Pleroma.Web.Gettext
14   alias Pleroma.Web.Router
15
16   import Swoosh.Email
17   import Phoenix.Swoosh, except: [render_body: 3]
18   import Pleroma.Config.Helpers, only: [instance_name: 0, sender: 0]
19
20   def render_body(email, template, assigns \\ %{}) do
21     email
22     |> put_new_layout({Pleroma.Web.LayoutView, :email})
23     |> put_new_view(Pleroma.Web.EmailView)
24     |> Phoenix.Swoosh.render_body(template, assigns)
25   end
26
27   defp recipient(email, nil), do: email
28   defp recipient(email, name), do: {name, email}
29   defp recipient(%User{} = user), do: recipient(user.email, user.name)
30
31   @spec welcome(User.t(), map()) :: Swoosh.Email.t()
32   def welcome(user, opts \\ %{}) do
33     Gettext.with_locale_or_default user.language do
34       new()
35       |> to(recipient(user))
36       |> from(Map.get(opts, :sender, sender()))
37       |> subject(
38         Map.get(
39           opts,
40           :subject,
41           Gettext.dpgettext(
42             "static_pages",
43             "welcome email subject",
44             "Welcome to %{instance_name}!",
45             instance_name: instance_name()
46           )
47         )
48       )
49       |> html_body(
50         Map.get(
51           opts,
52           :html,
53           Gettext.dpgettext(
54             "static_pages",
55             "welcome email html body",
56             "Welcome to %{instance_name}!",
57             instance_name: instance_name()
58           )
59         )
60       )
61       |> text_body(
62         Map.get(
63           opts,
64           :text,
65           Gettext.dpgettext(
66             "static_pages",
67             "welcome email text body",
68             "Welcome to %{instance_name}!",
69             instance_name: instance_name()
70           )
71         )
72       )
73     end
74   end
75
76   def password_reset_email(user, token) when is_binary(token) do
77     Gettext.with_locale_or_default user.language do
78       password_reset_url = Router.Helpers.reset_password_url(Endpoint, :reset, token)
79
80       html_body =
81         Gettext.dpgettext(
82           "static_pages",
83           "password reset email body",
84           """
85           <h3>Reset your password at %{instance_name}</h3>
86           <p>Someone has requested password change for your account at %{instance_name}.</p>
87           <p>If it was you, visit the following link to proceed: <a href="%{password_reset_url}">reset password</a>.</p>
88           <p>If it was someone else, nothing to worry about: your data is secure and your password has not been changed.</p>
89           """,
90           instance_name: instance_name(),
91           password_reset_url: password_reset_url
92         )
93
94       new()
95       |> to(recipient(user))
96       |> from(sender())
97       |> subject(
98         Gettext.dpgettext("static_pages", "password reset email subject", "Password reset")
99       )
100       |> html_body(html_body)
101     end
102   end
103
104   def user_invitation_email(
105         user,
106         %Pleroma.UserInviteToken{} = user_invite_token,
107         to_email,
108         to_name \\ nil
109       ) do
110     Gettext.with_locale_or_default user.language do
111       registration_url =
112         Router.Helpers.redirect_url(
113           Endpoint,
114           :registration_page,
115           user_invite_token.token
116         )
117
118       html_body =
119         Gettext.dpgettext(
120           "static_pages",
121           "user invitation email body",
122           """
123           <h3>You are invited to %{instance_name}</h3>
124           <p>%{inviter_name} invites you to join %{instance_name}, an instance of Pleroma federated social networking platform.</p>
125           <p>Click the following link to register: <a href="%{registration_url}">accept invitation</a>.</p>
126           """,
127           instance_name: instance_name(),
128           inviter_name: user.name,
129           registration_url: registration_url
130         )
131
132       new()
133       |> to(recipient(to_email, to_name))
134       |> from(sender())
135       |> subject(
136         Gettext.dpgettext(
137           "static_pages",
138           "user invitation email subject",
139           "Invitation to %{instance_name}",
140           instance_name: instance_name()
141         )
142       )
143       |> html_body(html_body)
144     end
145   end
146
147   def account_confirmation_email(user) do
148     Gettext.with_locale_or_default user.language do
149       confirmation_url =
150         Router.Helpers.confirm_email_url(
151           Endpoint,
152           :confirm_email,
153           user.id,
154           to_string(user.confirmation_token)
155         )
156
157       html_body =
158         Gettext.dpgettext(
159           "static_pages",
160           "confirmation email body",
161           """
162           <h3>Thank you for registering on %{instance_name}</h3>
163           <p>Email confirmation is required to activate the account.</p>
164           <p>Please click the following link to <a href="%{confirmation_url}">activate your account</a>.</p>
165           """,
166           instance_name: instance_name(),
167           confirmation_url: confirmation_url
168         )
169
170       new()
171       |> to(recipient(user))
172       |> from(sender())
173       |> subject(
174         Gettext.dpgettext(
175           "static_pages",
176           "confirmation email subject",
177           "%{instance_name} account confirmation",
178           instance_name: instance_name()
179         )
180       )
181       |> html_body(html_body)
182     end
183   end
184
185   def approval_pending_email(user) do
186     Gettext.with_locale_or_default user.language do
187       html_body =
188         Gettext.dpgettext(
189           "static_pages",
190           "approval pending email body",
191           """
192           <h3>Awaiting Approval</h3>
193           <p>Your account at %{instance_name} is being reviewed by staff. You will receive another email once your account is approved.</p>
194           """,
195           instance_name: instance_name()
196         )
197
198       new()
199       |> to(recipient(user))
200       |> from(sender())
201       |> subject(
202         Gettext.dpgettext(
203           "static_pages",
204           "approval pending email subject",
205           "Your account is awaiting approval"
206         )
207       )
208       |> html_body(html_body)
209     end
210   end
211
212   def successful_registration_email(user) do
213     Gettext.with_locale_or_default user.language do
214       html_body =
215         Gettext.dpgettext(
216           "static_pages",
217           "successful registration email body",
218           """
219           <h3>Hello @%{nickname},</h3>
220           <p>Your account at %{instance_name} has been registered successfully.</p>
221           <p>No further action is required to activate your account.</p>
222           """,
223           nickname: user.nickname,
224           instance_name: instance_name()
225         )
226
227       new()
228       |> to(recipient(user))
229       |> from(sender())
230       |> subject(
231         Gettext.dpgettext(
232           "static_pages",
233           "successful registration email subject",
234           "Account registered on %{instance_name}",
235           instance_name: instance_name()
236         )
237       )
238       |> html_body(html_body)
239     end
240   end
241
242   @doc """
243   Email used in digest email notifications
244   Includes Mentions and New Followers data
245   If there are no mentions (even when new followers exist), the function will return nil
246   """
247   @spec digest_email(User.t()) :: Swoosh.Email.t() | nil
248   def digest_email(user) do
249     Gettext.with_locale_or_default user.language do
250       notifications = Pleroma.Notification.for_user_since(user, user.last_digest_emailed_at)
251
252       mentions =
253         notifications
254         |> Enum.filter(&(&1.activity.data["type"] == "Create"))
255         |> Enum.map(fn notification ->
256           object = Pleroma.Object.normalize(notification.activity, fetch: false)
257
258           if not is_nil(object) do
259             object = update_in(object.data["content"], &format_links/1)
260
261             %{
262               data: notification,
263               object: object,
264               from: User.get_by_ap_id(notification.activity.actor)
265             }
266           end
267         end)
268         |> Enum.filter(& &1)
269
270       followers =
271         notifications
272         |> Enum.filter(&(&1.activity.data["type"] == "Follow"))
273         |> Enum.map(fn notification ->
274           from = User.get_by_ap_id(notification.activity.actor)
275
276           if not is_nil(from) do
277             %{
278               data: notification,
279               object: Pleroma.Object.normalize(notification.activity, fetch: false),
280               from: User.get_by_ap_id(notification.activity.actor)
281             }
282           end
283         end)
284         |> Enum.filter(& &1)
285
286       unless Enum.empty?(mentions) do
287         styling = Config.get([__MODULE__, :styling])
288         logo = Config.get([__MODULE__, :logo])
289
290         html_data = %{
291           instance: instance_name(),
292           user: user,
293           mentions: mentions,
294           followers: followers,
295           unsubscribe_link: unsubscribe_url(user, "digest"),
296           styling: styling
297         }
298
299         logo_path =
300           if is_nil(logo) do
301             Path.join(:code.priv_dir(:pleroma), "static/static/logo.svg")
302           else
303             Path.join(Config.get([:instance, :static_dir]), logo)
304           end
305
306         new()
307         |> to(recipient(user))
308         |> from(sender())
309         |> subject(
310           Gettext.dpgettext(
311             "static_pages",
312             "digest email subject",
313             "Your digest from %{instance_name}",
314             instance_name: instance_name()
315           )
316         )
317         |> put_layout(false)
318         |> render_body("digest.html", html_data)
319         |> attachment(Swoosh.Attachment.new(logo_path, filename: "logo.svg", type: :inline))
320       end
321     end
322   end
323
324   defp format_links(str) do
325     re = ~r/<a.+href=['"].*>/iU
326     %{link_color: color} = Config.get([__MODULE__, :styling])
327
328     Regex.replace(re, str, fn link ->
329       String.replace(link, "<a", "<a style=\"color: #{color};text-decoration: none;\"")
330     end)
331   end
332
333   @doc """
334   Generate unsubscribe link for given user and notifications type.
335   The link contains JWT token with the data, and subscription can be modified without
336   authorization.
337   """
338   @spec unsubscribe_url(User.t(), String.t()) :: String.t()
339   def unsubscribe_url(user, notifications_type) do
340     token =
341       %{"sub" => user.id, "act" => %{"unsubscribe" => notifications_type}, "exp" => false}
342       |> Pleroma.JWT.generate_and_sign!()
343       |> Base.encode64()
344
345     Router.Helpers.subscription_url(Endpoint, :unsubscribe, token)
346   end
347
348   def backup_is_ready_email(backup, admin_user_id \\ nil) do
349     %{user: user} = Pleroma.Repo.preload(backup, :user)
350
351     Gettext.with_locale_or_default user.language do
352       download_url = Pleroma.Web.PleromaAPI.BackupView.download_url(backup)
353
354       html_body =
355         if is_nil(admin_user_id) do
356           Gettext.dpgettext(
357             "static_pages",
358             "account archive email body - self-requested",
359             """
360             <p>You requested a full backup of your Pleroma account. It's ready for download:</p>
361             <p><a href="%{download_url}">%{download_url}</a></p>
362             """,
363             download_url: download_url
364           )
365         else
366           admin = Pleroma.Repo.get(User, admin_user_id)
367
368           Gettext.dpgettext(
369             "static_pages",
370             "account archive email body - admin requested",
371             """
372             <p>Admin @%{admin_nickname} requested a full backup of your Pleroma account. It's ready for download:</p>
373             <p><a href="%{download_url}">%{download_url}</a></p>
374             """,
375             admin_nickname: admin.nickname,
376             download_url: download_url
377           )
378         end
379
380       new()
381       |> to(recipient(user))
382       |> from(sender())
383       |> subject(
384         Gettext.dpgettext(
385           "static_pages",
386           "account archive email subject",
387           "Your account archive is ready"
388         )
389       )
390       |> html_body(html_body)
391     end
392   end
393 end