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