]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame_incremental - server/lib/emailer.ts
Prevent torrent indicators in player to overflow with small sizes
[github/Chocobozzz/PeerTube.git] / server / lib / emailer.ts
... / ...
CommitLineData
1import { createTransport, Transporter } from 'nodemailer'
2import { isTestInstance } from '../helpers/core-utils'
3import { bunyanLogger, logger } from '../helpers/logger'
4import { CONFIG, isEmailEnabled } from '../initializers/config'
5import { JobQueue } from './job-queue'
6import { EmailPayload } from './job-queue/handlers/email'
7import { readFileSync } from 'fs-extra'
8import { WEBSERVER } from '../initializers/constants'
9import {
10 MCommentOwnerVideo,
11 MVideo,
12 MVideoAbuseVideo,
13 MVideoAccountLight,
14 MVideoBlacklistLightVideo,
15 MVideoBlacklistVideo
16} from '../typings/models/video'
17import { MActorFollowActors, MActorFollowFull, MUser } from '../typings/models'
18import { MVideoImport, MVideoImportVideo } from '@server/typings/models/video/video-import'
19
20type SendEmailOptions = {
21 to: string[]
22 subject: string
23 text: string
24
25 fromDisplayName?: string
26 replyTo?: string
27}
28
29class Emailer {
30
31 private static instance: Emailer
32 private initialized = false
33 private transporter: Transporter
34
35 private constructor () {
36 }
37
38 init () {
39 // Already initialized
40 if (this.initialized === true) return
41 this.initialized = true
42
43 if (isEmailEnabled()) {
44 if (CONFIG.SMTP.TRANSPORT === 'smtp') {
45 logger.info('Using %s:%s as SMTP server.', CONFIG.SMTP.HOSTNAME, CONFIG.SMTP.PORT)
46
47 let tls
48 if (CONFIG.SMTP.CA_FILE) {
49 tls = {
50 ca: [ readFileSync(CONFIG.SMTP.CA_FILE) ]
51 }
52 }
53
54 let auth
55 if (CONFIG.SMTP.USERNAME && CONFIG.SMTP.PASSWORD) {
56 auth = {
57 user: CONFIG.SMTP.USERNAME,
58 pass: CONFIG.SMTP.PASSWORD
59 }
60 }
61
62 this.transporter = createTransport({
63 host: CONFIG.SMTP.HOSTNAME,
64 port: CONFIG.SMTP.PORT,
65 secure: CONFIG.SMTP.TLS,
66 debug: CONFIG.LOG.LEVEL === 'debug',
67 logger: bunyanLogger as any,
68 ignoreTLS: CONFIG.SMTP.DISABLE_STARTTLS,
69 tls,
70 auth
71 })
72 } else { // sendmail
73 logger.info('Using sendmail to send emails')
74
75 this.transporter = createTransport({
76 sendmail: true,
77 newline: 'unix',
78 path: CONFIG.SMTP.SENDMAIL,
79 })
80 }
81 } else {
82 if (!isTestInstance()) {
83 logger.error('Cannot use SMTP server because of lack of configuration. PeerTube will not be able to send mails!')
84 }
85 }
86 }
87
88 static isEnabled () {
89 if (CONFIG.SMTP.TRANSPORT === 'sendmail') {
90 return !!CONFIG.SMTP.SENDMAIL
91 } else if (CONFIG.SMTP.TRANSPORT === 'smtp') {
92 return !!CONFIG.SMTP.HOSTNAME && !!CONFIG.SMTP.PORT
93 } else {
94 return false
95 }
96 }
97
98 async checkConnectionOrDie () {
99 if (!this.transporter || CONFIG.SMTP.TRANSPORT !== 'smtp') return
100
101 logger.info('Testing SMTP server...')
102
103 try {
104 const success = await this.transporter.verify()
105 if (success !== true) this.dieOnConnectionFailure()
106
107 logger.info('Successfully connected to SMTP server.')
108 } catch (err) {
109 this.dieOnConnectionFailure(err)
110 }
111 }
112
113 addNewVideoFromSubscriberNotification (to: string[], video: MVideoAccountLight) {
114 const channelName = video.VideoChannel.getDisplayName()
115 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
116
117 const text = 'Hi dear user,\n\n' +
118 `Your subscription ${channelName} just published a new video: ${video.name}` +
119 '\n\n' +
120 `You can view it on ${videoUrl} ` +
121 '\n\n' +
122 'Cheers,\n' +
123 `${CONFIG.EMAIL.BODY.SIGNATURE}`
124
125 const emailPayload: EmailPayload = {
126 to,
127 subject: CONFIG.EMAIL.SUBJECT.PREFIX + channelName + ' just published a new video',
128 text
129 }
130
131 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
132 }
133
134 addNewFollowNotification (to: string[], actorFollow: MActorFollowFull, followType: 'account' | 'channel') {
135 const followerName = actorFollow.ActorFollower.Account.getDisplayName()
136 const followingName = (actorFollow.ActorFollowing.VideoChannel || actorFollow.ActorFollowing.Account).getDisplayName()
137
138 const text = 'Hi dear user,\n\n' +
139 `Your ${followType} ${followingName} has a new subscriber: ${followerName}` +
140 '\n\n' +
141 'Cheers,\n' +
142 `${CONFIG.EMAIL.BODY.SIGNATURE}`
143
144 const emailPayload: EmailPayload = {
145 to,
146 subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New follower on your channel ' + followingName,
147 text
148 }
149
150 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
151 }
152
153 addNewInstanceFollowerNotification (to: string[], actorFollow: MActorFollowActors) {
154 const awaitingApproval = actorFollow.state === 'pending' ? ' awaiting manual approval.' : ''
155
156 const text = 'Hi dear admin,\n\n' +
157 `Your instance has a new follower: ${actorFollow.ActorFollower.url}${awaitingApproval}` +
158 '\n\n' +
159 'Cheers,\n' +
160 `${CONFIG.EMAIL.BODY.SIGNATURE}`
161
162 const emailPayload: EmailPayload = {
163 to,
164 subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New instance follower',
165 text
166 }
167
168 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
169 }
170
171 addAutoInstanceFollowingNotification (to: string[], actorFollow: MActorFollowActors) {
172 const text = 'Hi dear admin,\n\n' +
173 `Your instance automatically followed a new instance: ${actorFollow.ActorFollowing.url}` +
174 '\n\n' +
175 'Cheers,\n' +
176 `${CONFIG.EMAIL.BODY.SIGNATURE}`
177
178 const emailPayload: EmailPayload = {
179 to,
180 subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Auto instance following',
181 text
182 }
183
184 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
185 }
186
187 myVideoPublishedNotification (to: string[], video: MVideo) {
188 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
189
190 const text = 'Hi dear user,\n\n' +
191 `Your video ${video.name} has been published.` +
192 '\n\n' +
193 `You can view it on ${videoUrl} ` +
194 '\n\n' +
195 'Cheers,\n' +
196 `${CONFIG.EMAIL.BODY.SIGNATURE}`
197
198 const emailPayload: EmailPayload = {
199 to,
200 subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Your video ${video.name} is published`,
201 text
202 }
203
204 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
205 }
206
207 myVideoImportSuccessNotification (to: string[], videoImport: MVideoImportVideo) {
208 const videoUrl = WEBSERVER.URL + videoImport.Video.getWatchStaticPath()
209
210 const text = 'Hi dear user,\n\n' +
211 `Your video import ${videoImport.getTargetIdentifier()} is finished.` +
212 '\n\n' +
213 `You can view the imported video on ${videoUrl} ` +
214 '\n\n' +
215 'Cheers,\n' +
216 `${CONFIG.EMAIL.BODY.SIGNATURE}`
217
218 const emailPayload: EmailPayload = {
219 to,
220 subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Your video import ${videoImport.getTargetIdentifier()} is finished`,
221 text
222 }
223
224 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
225 }
226
227 myVideoImportErrorNotification (to: string[], videoImport: MVideoImport) {
228 const importUrl = WEBSERVER.URL + '/my-account/video-imports'
229
230 const text = 'Hi dear user,\n\n' +
231 `Your video import ${videoImport.getTargetIdentifier()} encountered an error.` +
232 '\n\n' +
233 `See your videos import dashboard for more information: ${importUrl}` +
234 '\n\n' +
235 'Cheers,\n' +
236 `${CONFIG.EMAIL.BODY.SIGNATURE}`
237
238 const emailPayload: EmailPayload = {
239 to,
240 subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Your video import ${videoImport.getTargetIdentifier()} encountered an error`,
241 text
242 }
243
244 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
245 }
246
247 addNewCommentOnMyVideoNotification (to: string[], comment: MCommentOwnerVideo) {
248 const accountName = comment.Account.getDisplayName()
249 const video = comment.Video
250 const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath()
251
252 const text = 'Hi dear user,\n\n' +
253 `A new comment has been posted by ${accountName} on your video ${video.name}` +
254 '\n\n' +
255 `You can view it on ${commentUrl} ` +
256 '\n\n' +
257 'Cheers,\n' +
258 `${CONFIG.EMAIL.BODY.SIGNATURE}`
259
260 const emailPayload: EmailPayload = {
261 to,
262 subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New comment on your video ' + video.name,
263 text
264 }
265
266 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
267 }
268
269 addNewCommentMentionNotification (to: string[], comment: MCommentOwnerVideo) {
270 const accountName = comment.Account.getDisplayName()
271 const video = comment.Video
272 const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath()
273
274 const text = 'Hi dear user,\n\n' +
275 `${accountName} mentioned you on video ${video.name}` +
276 '\n\n' +
277 `You can view the comment on ${commentUrl} ` +
278 '\n\n' +
279 'Cheers,\n' +
280 `${CONFIG.EMAIL.BODY.SIGNATURE}`
281
282 const emailPayload: EmailPayload = {
283 to,
284 subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Mention on video ' + video.name,
285 text
286 }
287
288 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
289 }
290
291 addVideoAbuseModeratorsNotification (to: string[], videoAbuse: MVideoAbuseVideo) {
292 const videoUrl = WEBSERVER.URL + videoAbuse.Video.getWatchStaticPath()
293
294 const text = 'Hi,\n\n' +
295 `${WEBSERVER.HOST} received an abuse for the following video ${videoUrl}\n\n` +
296 'Cheers,\n' +
297 `${CONFIG.EMAIL.BODY.SIGNATURE}`
298
299 const emailPayload: EmailPayload = {
300 to,
301 subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Received a video abuse',
302 text
303 }
304
305 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
306 }
307
308 addVideoAutoBlacklistModeratorsNotification (to: string[], videoBlacklist: MVideoBlacklistLightVideo) {
309 const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list'
310 const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath()
311
312 const text = 'Hi,\n\n' +
313 'A recently added video was auto-blacklisted and requires moderator review before publishing.' +
314 '\n\n' +
315 `You can view it and take appropriate action on ${videoUrl}` +
316 '\n\n' +
317 `A full list of auto-blacklisted videos can be reviewed here: ${VIDEO_AUTO_BLACKLIST_URL}` +
318 '\n\n' +
319 'Cheers,\n' +
320 `${CONFIG.EMAIL.BODY.SIGNATURE}`
321
322 const emailPayload: EmailPayload = {
323 to,
324 subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'An auto-blacklisted video is awaiting review',
325 text
326 }
327
328 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
329 }
330
331 addNewUserRegistrationNotification (to: string[], user: MUser) {
332 const text = 'Hi,\n\n' +
333 `User ${user.username} just registered on ${WEBSERVER.HOST} PeerTube instance.\n\n` +
334 'Cheers,\n' +
335 `${CONFIG.EMAIL.BODY.SIGNATURE}`
336
337 const emailPayload: EmailPayload = {
338 to,
339 subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New user registration on ' + WEBSERVER.HOST,
340 text
341 }
342
343 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
344 }
345
346 addVideoBlacklistNotification (to: string[], videoBlacklist: MVideoBlacklistVideo) {
347 const videoName = videoBlacklist.Video.name
348 const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath()
349
350 const reasonString = videoBlacklist.reason ? ` for the following reason: ${videoBlacklist.reason}` : ''
351 const blockedString = `Your video ${videoName} (${videoUrl} on ${WEBSERVER.HOST} has been blacklisted${reasonString}.`
352
353 const text = 'Hi,\n\n' +
354 blockedString +
355 '\n\n' +
356 'Cheers,\n' +
357 `${CONFIG.EMAIL.BODY.SIGNATURE}`
358
359 const emailPayload: EmailPayload = {
360 to,
361 subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Video ${videoName} blacklisted`,
362 text
363 }
364
365 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
366 }
367
368 addVideoUnblacklistNotification (to: string[], video: MVideo) {
369 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
370
371 const text = 'Hi,\n\n' +
372 `Your video ${video.name} (${videoUrl}) on ${WEBSERVER.HOST} has been unblacklisted.` +
373 '\n\n' +
374 'Cheers,\n' +
375 `${CONFIG.EMAIL.BODY.SIGNATURE}`
376
377 const emailPayload: EmailPayload = {
378 to,
379 subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Video ${video.name} unblacklisted`,
380 text
381 }
382
383 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
384 }
385
386 addPasswordResetEmailJob (to: string, resetPasswordUrl: string) {
387 const text = 'Hi dear user,\n\n' +
388 `A reset password procedure for your account ${to} has been requested on ${WEBSERVER.HOST} ` +
389 `Please follow this link to reset it: ${resetPasswordUrl} (the link will expire within 1 hour)\n\n` +
390 'If you are not the person who initiated this request, please ignore this email.\n\n' +
391 'Cheers,\n' +
392 `${CONFIG.EMAIL.BODY.SIGNATURE}`
393
394 const emailPayload: EmailPayload = {
395 to: [ to ],
396 subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Reset your password',
397 text
398 }
399
400 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
401 }
402
403 addPasswordCreateEmailJob (username: string, to: string, resetPasswordUrl: string) {
404 const text = 'Hi,\n\n' +
405 `Welcome to your ${WEBSERVER.HOST} PeerTube instance. Your username is: ${username}.\n\n` +
406 `Please set your password by following this link: ${resetPasswordUrl} (this link will expire within seven days).\n\n` +
407 'Cheers,\n' +
408 `${CONFIG.EMAIL.BODY.SIGNATURE}`
409
410 const emailPayload: EmailPayload = {
411 to: [ to ],
412 subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New PeerTube account password',
413 text
414 }
415
416 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
417 }
418
419 addVerifyEmailJob (to: string, verifyEmailUrl: string) {
420 const text = 'Welcome to PeerTube,\n\n' +
421 `To start using PeerTube on ${WEBSERVER.HOST} you must verify your email! ` +
422 `Please follow this link to verify this email belongs to you: ${verifyEmailUrl}\n\n` +
423 'If you are not the person who initiated this request, please ignore this email.\n\n' +
424 'Cheers,\n' +
425 `${CONFIG.EMAIL.BODY.SIGNATURE}`
426
427 const emailPayload: EmailPayload = {
428 to: [ to ],
429 subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Verify your email',
430 text
431 }
432
433 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
434 }
435
436 addUserBlockJob (user: MUser, blocked: boolean, reason?: string) {
437 const reasonString = reason ? ` for the following reason: ${reason}` : ''
438 const blockedWord = blocked ? 'blocked' : 'unblocked'
439 const blockedString = `Your account ${user.username} on ${WEBSERVER.HOST} has been ${blockedWord}${reasonString}.`
440
441 const text = 'Hi,\n\n' +
442 blockedString +
443 '\n\n' +
444 'Cheers,\n' +
445 `${CONFIG.EMAIL.BODY.SIGNATURE}`
446
447 const to = user.email
448 const emailPayload: EmailPayload = {
449 to: [ to ],
450 subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Account ' + blockedWord,
451 text
452 }
453
454 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
455 }
456
457 addContactFormJob (fromEmail: string, fromName: string, subject: string, body: string) {
458 const text = 'Hello dear admin,\n\n' +
459 fromName + ' sent you a message' +
460 '\n\n---------------------------------------\n\n' +
461 body +
462 '\n\n---------------------------------------\n\n' +
463 'Cheers,\n' +
464 'PeerTube.'
465
466 const emailPayload: EmailPayload = {
467 fromDisplayName: fromEmail,
468 replyTo: fromEmail,
469 to: [ CONFIG.ADMIN.EMAIL ],
470 subject: CONFIG.EMAIL.SUBJECT.PREFIX + subject,
471 text
472 }
473
474 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
475 }
476
477 async sendMail (options: EmailPayload) {
478 if (!isEmailEnabled()) {
479 throw new Error('Cannot send mail because SMTP is not configured.')
480 }
481
482 const fromDisplayName = options.fromDisplayName
483 ? options.fromDisplayName
484 : WEBSERVER.HOST
485
486 for (const to of options.to) {
487 await this.transporter.sendMail({
488 from: `"${fromDisplayName}" <${CONFIG.SMTP.FROM_ADDRESS}>`,
489 replyTo: options.replyTo,
490 to,
491 subject: options.subject,
492 text: options.text
493 })
494 }
495 }
496
497 private dieOnConnectionFailure (err?: Error) {
498 logger.error('Failed to connect to SMTP %s:%d.', CONFIG.SMTP.HOSTNAME, CONFIG.SMTP.PORT, { err })
499 process.exit(-1)
500 }
501
502 static get Instance () {
503 return this.instance || (this.instance = new this())
504 }
505}
506
507// ---------------------------------------------------------------------------
508
509export {
510 Emailer,
511 SendEmailOptions
512}