]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/lib/emailer.ts
Add import finished and video published notifs
[github/Chocobozzz/PeerTube.git] / server / lib / emailer.ts
CommitLineData
ecb4e35f
C
1import { createTransport, Transporter } from 'nodemailer'
2import { isTestInstance } from '../helpers/core-utils'
05e67d62 3import { bunyanLogger, logger } from '../helpers/logger'
ecb4e35f 4import { CONFIG } from '../initializers'
ba75d268
C
5import { UserModel } from '../models/account/user'
6import { VideoModel } from '../models/video/video'
ecb4e35f
C
7import { JobQueue } from './job-queue'
8import { EmailPayload } from './job-queue/handlers/email'
c9d5c64f 9import { readFileSync } from 'fs-extra'
cef534ed
C
10import { VideoCommentModel } from '../models/video/video-comment'
11import { VideoAbuseModel } from '../models/video/video-abuse'
12import { VideoBlacklistModel } from '../models/video/video-blacklist'
dc133480 13import { VideoImportModel } from '../models/video/video-import'
ecb4e35f
C
14
15class Emailer {
16
17 private static instance: Emailer
18 private initialized = false
19 private transporter: Transporter
3b3b1820 20 private enabled = false
ecb4e35f
C
21
22 private constructor () {}
23
24 init () {
25 // Already initialized
26 if (this.initialized === true) return
27 this.initialized = true
28
29 if (CONFIG.SMTP.HOSTNAME && CONFIG.SMTP.PORT) {
30 logger.info('Using %s:%s as SMTP server.', CONFIG.SMTP.HOSTNAME, CONFIG.SMTP.PORT)
31
32 let tls
33 if (CONFIG.SMTP.CA_FILE) {
34 tls = {
35 ca: [ readFileSync(CONFIG.SMTP.CA_FILE) ]
36 }
37 }
38
f076daa7
C
39 let auth
40 if (CONFIG.SMTP.USERNAME && CONFIG.SMTP.PASSWORD) {
41 auth = {
42 user: CONFIG.SMTP.USERNAME,
43 pass: CONFIG.SMTP.PASSWORD
44 }
45 }
46
ecb4e35f
C
47 this.transporter = createTransport({
48 host: CONFIG.SMTP.HOSTNAME,
49 port: CONFIG.SMTP.PORT,
50 secure: CONFIG.SMTP.TLS,
05e67d62
C
51 debug: CONFIG.LOG.LEVEL === 'debug',
52 logger: bunyanLogger as any,
bebf2d89 53 ignoreTLS: CONFIG.SMTP.DISABLE_STARTTLS,
ecb4e35f 54 tls,
f076daa7 55 auth
ecb4e35f 56 })
3b3b1820
C
57
58 this.enabled = true
ecb4e35f
C
59 } else {
60 if (!isTestInstance()) {
61 logger.error('Cannot use SMTP server because of lack of configuration. PeerTube will not be able to send mails!')
62 }
63 }
64 }
65
3b3b1820
C
66 isEnabled () {
67 return this.enabled
68 }
69
ecb4e35f
C
70 async checkConnectionOrDie () {
71 if (!this.transporter) return
72
3d3441d6
C
73 logger.info('Testing SMTP server...')
74
ecb4e35f
C
75 try {
76 const success = await this.transporter.verify()
77 if (success !== true) this.dieOnConnectionFailure()
78
79 logger.info('Successfully connected to SMTP server.')
80 } catch (err) {
81 this.dieOnConnectionFailure(err)
82 }
83 }
84
cef534ed
C
85 addNewVideoFromSubscriberNotification (to: string[], video: VideoModel) {
86 const channelName = video.VideoChannel.getDisplayName()
87 const videoUrl = CONFIG.WEBSERVER.URL + video.getWatchStaticPath()
88
ecb4e35f 89 const text = `Hi dear user,\n\n` +
cef534ed
C
90 `Your subscription ${channelName} just published a new video: ${video.name}` +
91 `\n\n` +
92 `You can view it on ${videoUrl} ` +
93 `\n\n` +
ecb4e35f
C
94 `Cheers,\n` +
95 `PeerTube.`
96
97 const emailPayload: EmailPayload = {
cef534ed
C
98 to,
99 subject: channelName + ' just published a new video',
ecb4e35f
C
100 text
101 }
102
103 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
104 }
105
dc133480
C
106 myVideoPublishedNotification (to: string[], video: VideoModel) {
107 const videoUrl = CONFIG.WEBSERVER.URL + video.getWatchStaticPath()
108
109 const text = `Hi dear user,\n\n` +
110 `Your video ${video.name} has been published.` +
111 `\n\n` +
112 `You can view it on ${videoUrl} ` +
113 `\n\n` +
114 `Cheers,\n` +
115 `PeerTube.`
116
117 const emailPayload: EmailPayload = {
118 to,
119 subject: `Your video ${video.name} is published`,
120 text
121 }
122
123 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
124 }
125
126 myVideoImportSuccessNotification (to: string[], videoImport: VideoImportModel) {
127 const videoUrl = CONFIG.WEBSERVER.URL + videoImport.Video.getWatchStaticPath()
128
129 const text = `Hi dear user,\n\n` +
130 `Your video import ${videoImport.getTargetIdentifier()} is finished.` +
131 `\n\n` +
132 `You can view the imported video on ${videoUrl} ` +
133 `\n\n` +
134 `Cheers,\n` +
135 `PeerTube.`
136
137 const emailPayload: EmailPayload = {
138 to,
139 subject: `Your video import ${videoImport.getTargetIdentifier()} is finished`,
140 text
141 }
142
143 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
144 }
145
146 myVideoImportErrorNotification (to: string[], videoImport: VideoImportModel) {
147 const importUrl = CONFIG.WEBSERVER.URL + '/my-account/video-imports'
148
149 const text = `Hi dear user,\n\n` +
150 `Your video import ${videoImport.getTargetIdentifier()} encountered an error.` +
151 `\n\n` +
152 `See your videos import dashboard for more information: ${importUrl}` +
153 `\n\n` +
154 `Cheers,\n` +
155 `PeerTube.`
156
157 const emailPayload: EmailPayload = {
158 to,
159 subject: `Your video import ${videoImport.getTargetIdentifier()} encountered an error`,
160 text
161 }
162
163 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
164 }
165
cef534ed
C
166 addNewCommentOnMyVideoNotification (to: string[], comment: VideoCommentModel) {
167 const accountName = comment.Account.getDisplayName()
168 const video = comment.Video
169 const commentUrl = CONFIG.WEBSERVER.URL + comment.getCommentStaticPath()
170
171 const text = `Hi dear user,\n\n` +
172 `A new comment has been posted by ${accountName} on your video ${video.name}` +
173 `\n\n` +
174 `You can view it on ${commentUrl} ` +
175 `\n\n` +
d9eaee39
JM
176 `Cheers,\n` +
177 `PeerTube.`
178
179 const emailPayload: EmailPayload = {
cef534ed
C
180 to,
181 subject: 'New comment on your video ' + video.name,
d9eaee39
JM
182 text
183 }
184
185 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
186 }
187
cef534ed
C
188 async addVideoAbuseModeratorsNotification (to: string[], videoAbuse: VideoAbuseModel) {
189 const videoUrl = CONFIG.WEBSERVER.URL + videoAbuse.Video.getWatchStaticPath()
ba75d268
C
190
191 const text = `Hi,\n\n` +
cef534ed 192 `${CONFIG.WEBSERVER.HOST} received an abuse for the following video ${videoUrl}\n\n` +
ba75d268
C
193 `Cheers,\n` +
194 `PeerTube.`
195
ba75d268
C
196 const emailPayload: EmailPayload = {
197 to,
198 subject: '[PeerTube] Received a video abuse',
199 text
200 }
201
202 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
203 }
204
cef534ed
C
205 async addVideoBlacklistNotification (to: string[], videoBlacklist: VideoBlacklistModel) {
206 const videoName = videoBlacklist.Video.name
207 const videoUrl = CONFIG.WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath()
26b7305a 208
cef534ed
C
209 const reasonString = videoBlacklist.reason ? ` for the following reason: ${videoBlacklist.reason}` : ''
210 const blockedString = `Your video ${videoName} (${videoUrl} on ${CONFIG.WEBSERVER.HOST} has been blacklisted${reasonString}.`
26b7305a
C
211
212 const text = 'Hi,\n\n' +
213 blockedString +
214 '\n\n' +
215 'Cheers,\n' +
216 `PeerTube.`
217
26b7305a 218 const emailPayload: EmailPayload = {
cef534ed
C
219 to,
220 subject: `[PeerTube] Video ${videoName} blacklisted`,
26b7305a
C
221 text
222 }
223
224 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
225 }
226
cef534ed
C
227 async addVideoUnblacklistNotification (to: string[], video: VideoModel) {
228 const videoUrl = CONFIG.WEBSERVER.URL + video.getWatchStaticPath()
26b7305a
C
229
230 const text = 'Hi,\n\n' +
cef534ed 231 `Your video ${video.name} (${videoUrl}) on ${CONFIG.WEBSERVER.HOST} has been unblacklisted.` +
26b7305a
C
232 '\n\n' +
233 'Cheers,\n' +
234 `PeerTube.`
235
26b7305a 236 const emailPayload: EmailPayload = {
cef534ed 237 to,
26b7305a
C
238 subject: `[PeerTube] Video ${video.name} unblacklisted`,
239 text
240 }
241
242 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
243 }
244
cef534ed
C
245 addForgetPasswordEmailJob (to: string, resetPasswordUrl: string) {
246 const text = `Hi dear user,\n\n` +
247 `It seems you forgot your password on ${CONFIG.WEBSERVER.HOST}! ` +
248 `Please follow this link to reset it: ${resetPasswordUrl}\n\n` +
249 `If you are not the person who initiated this request, please ignore this email.\n\n` +
250 `Cheers,\n` +
251 `PeerTube.`
252
253 const emailPayload: EmailPayload = {
254 to: [ to ],
255 subject: 'Reset your PeerTube password',
256 text
257 }
258
259 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
260 }
261
262 addVerifyEmailJob (to: string, verifyEmailUrl: string) {
263 const text = `Welcome to PeerTube,\n\n` +
264 `To start using PeerTube on ${CONFIG.WEBSERVER.HOST} you must verify your email! ` +
265 `Please follow this link to verify this email belongs to you: ${verifyEmailUrl}\n\n` +
266 `If you are not the person who initiated this request, please ignore this email.\n\n` +
267 `Cheers,\n` +
268 `PeerTube.`
269
270 const emailPayload: EmailPayload = {
271 to: [ to ],
272 subject: 'Verify your PeerTube email',
273 text
274 }
275
276 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
277 }
278
eacb25c4
C
279 addUserBlockJob (user: UserModel, blocked: boolean, reason?: string) {
280 const reasonString = reason ? ` for the following reason: ${reason}` : ''
281 const blockedWord = blocked ? 'blocked' : 'unblocked'
282 const blockedString = `Your account ${user.username} on ${CONFIG.WEBSERVER.HOST} has been ${blockedWord}${reasonString}.`
283
284 const text = 'Hi,\n\n' +
285 blockedString +
286 '\n\n' +
287 'Cheers,\n' +
288 `PeerTube.`
289
290 const to = user.email
291 const emailPayload: EmailPayload = {
292 to: [ to ],
293 subject: '[PeerTube] Account ' + blockedWord,
294 text
295 }
296
297 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
298 }
299
ecb4e35f 300 sendMail (to: string[], subject: string, text: string) {
cef534ed 301 if (!this.enabled) {
ecb4e35f
C
302 throw new Error('Cannot send mail because SMTP is not configured.')
303 }
304
305 return this.transporter.sendMail({
306 from: CONFIG.SMTP.FROM_ADDRESS,
307 to: to.join(','),
308 subject,
309 text
310 })
311 }
312
313 private dieOnConnectionFailure (err?: Error) {
d5b7d911 314 logger.error('Failed to connect to SMTP %s:%d.', CONFIG.SMTP.HOSTNAME, CONFIG.SMTP.PORT, { err })
ecb4e35f
C
315 process.exit(-1)
316 }
317
318 static get Instance () {
319 return this.instance || (this.instance = new this())
320 }
321}
322
323// ---------------------------------------------------------------------------
324
325export {
326 Emailer
327}