Fix html tag with blacklisted video
[oweals/peertube.git] / server / lib / notifier.ts
1 import { UserNotificationSettingValue, UserNotificationType, UserRight } from '../../shared/models/users'
2 import { logger } from '../helpers/logger'
3 import { VideoModel } from '../models/video/video'
4 import { Emailer } from './emailer'
5 import { UserNotificationModel } from '../models/account/user-notification'
6 import { VideoCommentModel } from '../models/video/video-comment'
7 import { UserModel } from '../models/account/user'
8 import { PeerTubeSocket } from './peertube-socket'
9 import { CONFIG } from '../initializers/config'
10 import { VideoPrivacy, VideoState } from '../../shared/models/videos'
11 import { VideoBlacklistModel } from '../models/video/video-blacklist'
12 import * as Bluebird from 'bluebird'
13 import { VideoImportModel } from '../models/video/video-import'
14 import { AccountBlocklistModel } from '../models/account/account-blocklist'
15 import {
16   MCommentOwnerVideo,
17   MVideo,
18   MVideoAbuseVideo,
19   MVideoAccountLight,
20   MVideoBlacklistVideo,
21   MVideoFullLight
22 } from '../typings/models/video'
23 import { MUser, MUserAccount, MUserWithNotificationSetting, UserNotificationModelForApi } from '@server/typings/models/user'
24 import { MActorFollowActors, MActorFollowFull, MActorFollowFollowingFullFollowerAccount } from '../typings/models'
25 import { ActorFollowModel } from '../models/activitypub/actor-follow'
26 import { MVideoImportVideo } from '@server/typings/models/video/video-import'
27 import { AccountModel } from '@server/models/account/account'
28
29 class Notifier {
30
31   private static instance: Notifier
32
33   private constructor () {}
34
35   notifyOnNewVideoIfNeeded (video: MVideoAccountLight): void {
36     // Only notify on public and published videos which are not blacklisted
37     if (video.privacy !== VideoPrivacy.PUBLIC || video.state !== VideoState.PUBLISHED || video.isBlacklisted()) return
38
39     this.notifySubscribersOfNewVideo(video)
40       .catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err }))
41   }
42
43   notifyOnVideoPublishedAfterTranscoding (video: MVideoFullLight): void {
44     // don't notify if didn't wait for transcoding or video is still blacklisted/waiting for scheduled update
45     if (!video.waitTranscoding || video.VideoBlacklist || video.ScheduleVideoUpdate) return
46
47     this.notifyOwnedVideoHasBeenPublished(video)
48         .catch(err => logger.error('Cannot notify owner that its video %s has been published after transcoding.', video.url, { err }))
49   }
50
51   notifyOnVideoPublishedAfterScheduledUpdate (video: MVideoFullLight): void {
52     // don't notify if video is still blacklisted or waiting for transcoding
53     if (video.VideoBlacklist || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return
54
55     this.notifyOwnedVideoHasBeenPublished(video)
56         .catch(err => logger.error('Cannot notify owner that its video %s has been published after scheduled update.', video.url, { err }))
57   }
58
59   notifyOnVideoPublishedAfterRemovedFromAutoBlacklist (video: MVideoFullLight): void {
60     // don't notify if video is still waiting for transcoding or scheduled update
61     if (video.ScheduleVideoUpdate || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return
62
63     this.notifyOwnedVideoHasBeenPublished(video)
64         .catch(err => logger.error('Cannot notify owner that its video %s has been published after removed from auto-blacklist.', video.url, { err })) // tslint:disable-line:max-line-length
65   }
66
67   notifyOnNewComment (comment: MCommentOwnerVideo): void {
68     this.notifyVideoOwnerOfNewComment(comment)
69         .catch(err => logger.error('Cannot notify video owner of new comment %s.', comment.url, { err }))
70
71     this.notifyOfCommentMention(comment)
72         .catch(err => logger.error('Cannot notify mentions of comment %s.', comment.url, { err }))
73   }
74
75   notifyOnNewVideoAbuse (videoAbuse: MVideoAbuseVideo): void {
76     this.notifyModeratorsOfNewVideoAbuse(videoAbuse)
77       .catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err }))
78   }
79
80   notifyOnVideoAutoBlacklist (video: MVideo): void {
81     this.notifyModeratorsOfVideoAutoBlacklist(video)
82       .catch(err => logger.error('Cannot notify of auto-blacklist of video %s.', video.url, { err }))
83   }
84
85   notifyOnVideoBlacklist (videoBlacklist: MVideoBlacklistVideo): void {
86     this.notifyVideoOwnerOfBlacklist(videoBlacklist)
87       .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err }))
88   }
89
90   notifyOnVideoUnblacklist (video: MVideo): void {
91     this.notifyVideoOwnerOfUnblacklist(video)
92         .catch(err => logger.error('Cannot notify video owner of unblacklist of %s.', video.url, { err }))
93   }
94
95   notifyOnFinishedVideoImport (videoImport: MVideoImportVideo, success: boolean): void {
96     this.notifyOwnerVideoImportIsFinished(videoImport, success)
97       .catch(err => logger.error('Cannot notify owner that its video import %s is finished.', videoImport.getTargetIdentifier(), { err }))
98   }
99
100   notifyOnNewUserRegistration (user: MUserAccount): void {
101     this.notifyModeratorsOfNewUserRegistration(user)
102         .catch(err => logger.error('Cannot notify moderators of new user registration (%s).', user.username, { err }))
103   }
104
105   notifyOfNewUserFollow (actorFollow: MActorFollowFollowingFullFollowerAccount): void {
106     this.notifyUserOfNewActorFollow(actorFollow)
107       .catch(err => {
108         logger.error(
109           'Cannot notify owner of channel %s of a new follow by %s.',
110           actorFollow.ActorFollowing.VideoChannel.getDisplayName(),
111           actorFollow.ActorFollower.Account.getDisplayName(),
112           { err }
113         )
114       })
115   }
116
117   notifyOfNewInstanceFollow (actorFollow: MActorFollowActors): void {
118     this.notifyAdminsOfNewInstanceFollow(actorFollow)
119         .catch(err => {
120           logger.error('Cannot notify administrators of new follower %s.', actorFollow.ActorFollower.url, { err })
121         })
122   }
123
124   private async notifySubscribersOfNewVideo (video: MVideoAccountLight) {
125     // List all followers that are users
126     const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId)
127
128     logger.info('Notifying %d users of new video %s.', users.length, video.url)
129
130     function settingGetter (user: UserModel) {
131       return user.NotificationSetting.newVideoFromSubscription
132     }
133
134     async function notificationCreator (user: UserModel) {
135       const notification = await UserNotificationModel.create({
136         type: UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION,
137         userId: user.id,
138         videoId: video.id
139       })
140       notification.Video = video as VideoModel
141
142       return notification
143     }
144
145     function emailSender (emails: string[]) {
146       return Emailer.Instance.addNewVideoFromSubscriberNotification(emails, video)
147     }
148
149     return this.notify({ users, settingGetter, notificationCreator, emailSender })
150   }
151
152   private async notifyVideoOwnerOfNewComment (comment: MCommentOwnerVideo) {
153     if (comment.Video.isOwned() === false) return
154
155     const user = await UserModel.loadByVideoId(comment.videoId)
156
157     // Not our user or user comments its own video
158     if (!user || comment.Account.userId === user.id) return
159
160     const accountMuted = await AccountBlocklistModel.isAccountMutedBy(user.Account.id, comment.accountId)
161     if (accountMuted) return
162
163     logger.info('Notifying user %s of new comment %s.', user.username, comment.url)
164
165     function settingGetter (user: UserModel) {
166       return user.NotificationSetting.newCommentOnMyVideo
167     }
168
169     async function notificationCreator (user: UserModel) {
170       const notification = await UserNotificationModel.create({
171         type: UserNotificationType.NEW_COMMENT_ON_MY_VIDEO,
172         userId: user.id,
173         commentId: comment.id
174       })
175       notification.Comment = comment as VideoCommentModel
176
177       return notification
178     }
179
180     function emailSender (emails: string[]) {
181       return Emailer.Instance.addNewCommentOnMyVideoNotification(emails, comment)
182     }
183
184     return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
185   }
186
187   private async notifyOfCommentMention (comment: MCommentOwnerVideo) {
188     const extractedUsernames = comment.extractMentions()
189     logger.debug(
190       'Extracted %d username from comment %s.', extractedUsernames.length, comment.url,
191       { usernames: extractedUsernames, text: comment.text }
192     )
193
194     let users = await UserModel.listByUsernames(extractedUsernames)
195
196     if (comment.Video.isOwned()) {
197       const userException = await UserModel.loadByVideoId(comment.videoId)
198       users = users.filter(u => u.id !== userException.id)
199     }
200
201     // Don't notify if I mentioned myself
202     users = users.filter(u => u.Account.id !== comment.accountId)
203
204     if (users.length === 0) return
205
206     const accountMutedHash = await AccountBlocklistModel.isAccountMutedByMulti(users.map(u => u.Account.id), comment.accountId)
207
208     logger.info('Notifying %d users of new comment %s.', users.length, comment.url)
209
210     function settingGetter (user: UserModel) {
211       if (accountMutedHash[user.Account.id] === true) return UserNotificationSettingValue.NONE
212
213       return user.NotificationSetting.commentMention
214     }
215
216     async function notificationCreator (user: UserModel) {
217       const notification = await UserNotificationModel.create({
218         type: UserNotificationType.COMMENT_MENTION,
219         userId: user.id,
220         commentId: comment.id
221       })
222       notification.Comment = comment as VideoCommentModel
223
224       return notification
225     }
226
227     function emailSender (emails: string[]) {
228       return Emailer.Instance.addNewCommentMentionNotification(emails, comment)
229     }
230
231     return this.notify({ users, settingGetter, notificationCreator, emailSender })
232   }
233
234   private async notifyUserOfNewActorFollow (actorFollow: MActorFollowFollowingFullFollowerAccount) {
235     if (actorFollow.ActorFollowing.isOwned() === false) return
236
237     // Account follows one of our account?
238     let followType: 'account' | 'channel' = 'channel'
239     let user = await UserModel.loadByChannelActorId(actorFollow.ActorFollowing.id)
240
241     // Account follows one of our channel?
242     if (!user) {
243       user = await UserModel.loadByAccountActorId(actorFollow.ActorFollowing.id)
244       followType = 'account'
245     }
246
247     if (!user) return
248
249     const followerAccount = actorFollow.ActorFollower.Account
250
251     const accountMuted = await AccountBlocklistModel.isAccountMutedBy(user.Account.id, followerAccount.id)
252     if (accountMuted) return
253
254     logger.info('Notifying user %s of new follower: %s.', user.username, followerAccount.getDisplayName())
255
256     function settingGetter (user: UserModel) {
257       return user.NotificationSetting.newFollow
258     }
259
260     async function notificationCreator (user: UserModel) {
261       const notification = await UserNotificationModel.create({
262         type: UserNotificationType.NEW_FOLLOW,
263         userId: user.id,
264         actorFollowId: actorFollow.id
265       })
266       notification.ActorFollow = actorFollow as ActorFollowModel
267
268       return notification
269     }
270
271     function emailSender (emails: string[]) {
272       return Emailer.Instance.addNewFollowNotification(emails, actorFollow, followType)
273     }
274
275     return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
276   }
277
278   private async notifyAdminsOfNewInstanceFollow (actorFollow: MActorFollowActors) {
279     const admins = await UserModel.listWithRight(UserRight.MANAGE_SERVER_FOLLOW)
280
281     logger.info('Notifying %d administrators of new instance follower: %s.', admins.length, actorFollow.ActorFollower.url)
282
283     function settingGetter (user: UserModel) {
284       return user.NotificationSetting.newInstanceFollower
285     }
286
287     async function notificationCreator (user: UserModel) {
288       const notification = await UserNotificationModel.create({
289         type: UserNotificationType.NEW_INSTANCE_FOLLOWER,
290         userId: user.id,
291         actorFollowId: actorFollow.id
292       })
293       notification.ActorFollow = actorFollow as ActorFollowModel
294
295       return notification
296     }
297
298     function emailSender (emails: string[]) {
299       return Emailer.Instance.addNewInstanceFollowerNotification(emails, actorFollow)
300     }
301
302     return this.notify({ users: admins, settingGetter, notificationCreator, emailSender })
303   }
304
305   private async notifyModeratorsOfNewVideoAbuse (videoAbuse: MVideoAbuseVideo) {
306     const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_ABUSES)
307     if (moderators.length === 0) return
308
309     logger.info('Notifying %s user/moderators of new video abuse %s.', moderators.length, videoAbuse.Video.url)
310
311     function settingGetter (user: UserModel) {
312       return user.NotificationSetting.videoAbuseAsModerator
313     }
314
315     async function notificationCreator (user: UserModel) {
316       const notification: UserNotificationModelForApi = await UserNotificationModel.create({
317         type: UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS,
318         userId: user.id,
319         videoAbuseId: videoAbuse.id
320       })
321       notification.VideoAbuse = videoAbuse
322
323       return notification
324     }
325
326     function emailSender (emails: string[]) {
327       return Emailer.Instance.addVideoAbuseModeratorsNotification(emails, videoAbuse)
328     }
329
330     return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
331   }
332
333   private async notifyModeratorsOfVideoAutoBlacklist (video: MVideo) {
334     const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_BLACKLIST)
335     if (moderators.length === 0) return
336
337     logger.info('Notifying %s moderators of video auto-blacklist %s.', moderators.length, video.url)
338
339     function settingGetter (user: UserModel) {
340       return user.NotificationSetting.videoAutoBlacklistAsModerator
341     }
342     async function notificationCreator (user: UserModel) {
343
344       const notification = await UserNotificationModel.create({
345         type: UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS,
346         userId: user.id,
347         videoId: video.id
348       })
349       notification.Video = video as VideoModel
350
351       return notification
352     }
353
354     function emailSender (emails: string[]) {
355       return Emailer.Instance.addVideoAutoBlacklistModeratorsNotification(emails, video)
356     }
357
358     return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
359   }
360
361   private async notifyVideoOwnerOfBlacklist (videoBlacklist: MVideoBlacklistVideo) {
362     const user = await UserModel.loadByVideoId(videoBlacklist.videoId)
363     if (!user) return
364
365     logger.info('Notifying user %s that its video %s has been blacklisted.', user.username, videoBlacklist.Video.url)
366
367     function settingGetter (user: UserModel) {
368       return user.NotificationSetting.blacklistOnMyVideo
369     }
370
371     async function notificationCreator (user: UserModel) {
372       const notification = await UserNotificationModel.create({
373         type: UserNotificationType.BLACKLIST_ON_MY_VIDEO,
374         userId: user.id,
375         videoBlacklistId: videoBlacklist.id
376       })
377       notification.VideoBlacklist = videoBlacklist as VideoBlacklistModel
378
379       return notification
380     }
381
382     function emailSender (emails: string[]) {
383       return Emailer.Instance.addVideoBlacklistNotification(emails, videoBlacklist)
384     }
385
386     return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
387   }
388
389   private async notifyVideoOwnerOfUnblacklist (video: MVideo) {
390     const user = await UserModel.loadByVideoId(video.id)
391     if (!user) return
392
393     logger.info('Notifying user %s that its video %s has been unblacklisted.', user.username, video.url)
394
395     function settingGetter (user: UserModel) {
396       return user.NotificationSetting.blacklistOnMyVideo
397     }
398
399     async function notificationCreator (user: UserModel) {
400       const notification = await UserNotificationModel.create({
401         type: UserNotificationType.UNBLACKLIST_ON_MY_VIDEO,
402         userId: user.id,
403         videoId: video.id
404       })
405       notification.Video = video as VideoModel
406
407       return notification
408     }
409
410     function emailSender (emails: string[]) {
411       return Emailer.Instance.addVideoUnblacklistNotification(emails, video)
412     }
413
414     return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
415   }
416
417   private async notifyOwnedVideoHasBeenPublished (video: MVideoFullLight) {
418     const user = await UserModel.loadByVideoId(video.id)
419     if (!user) return
420
421     logger.info('Notifying user %s of the publication of its video %s.', user.username, video.url)
422
423     function settingGetter (user: UserModel) {
424       return user.NotificationSetting.myVideoPublished
425     }
426
427     async function notificationCreator (user: UserModel) {
428       const notification = await UserNotificationModel.create({
429         type: UserNotificationType.MY_VIDEO_PUBLISHED,
430         userId: user.id,
431         videoId: video.id
432       })
433       notification.Video = video as VideoModel
434
435       return notification
436     }
437
438     function emailSender (emails: string[]) {
439       return Emailer.Instance.myVideoPublishedNotification(emails, video)
440     }
441
442     return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
443   }
444
445   private async notifyOwnerVideoImportIsFinished (videoImport: MVideoImportVideo, success: boolean) {
446     const user = await UserModel.loadByVideoImportId(videoImport.id)
447     if (!user) return
448
449     logger.info('Notifying user %s its video import %s is finished.', user.username, videoImport.getTargetIdentifier())
450
451     function settingGetter (user: UserModel) {
452       return user.NotificationSetting.myVideoImportFinished
453     }
454
455     async function notificationCreator (user: UserModel) {
456       const notification = await UserNotificationModel.create({
457         type: success ? UserNotificationType.MY_VIDEO_IMPORT_SUCCESS : UserNotificationType.MY_VIDEO_IMPORT_ERROR,
458         userId: user.id,
459         videoImportId: videoImport.id
460       })
461       notification.VideoImport = videoImport as VideoImportModel
462
463       return notification
464     }
465
466     function emailSender (emails: string[]) {
467       return success
468         ? Emailer.Instance.myVideoImportSuccessNotification(emails, videoImport)
469         : Emailer.Instance.myVideoImportErrorNotification(emails, videoImport)
470     }
471
472     return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
473   }
474
475   private async notifyModeratorsOfNewUserRegistration (registeredUser: MUserAccount) {
476     const moderators = await UserModel.listWithRight(UserRight.MANAGE_USERS)
477     if (moderators.length === 0) return
478
479     logger.info(
480       'Notifying %s moderators of new user registration of %s.',
481       moderators.length, registeredUser.username
482     )
483
484     function settingGetter (user: UserModel) {
485       return user.NotificationSetting.newUserRegistration
486     }
487
488     async function notificationCreator (user: UserModel) {
489       const notification = await UserNotificationModel.create({
490         type: UserNotificationType.NEW_USER_REGISTRATION,
491         userId: user.id,
492         accountId: registeredUser.Account.id
493       })
494       notification.Account = registeredUser.Account as AccountModel
495
496       return notification
497     }
498
499     function emailSender (emails: string[]) {
500       return Emailer.Instance.addNewUserRegistrationNotification(emails, registeredUser)
501     }
502
503     return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
504   }
505
506   private async notify (options: {
507     users: MUserWithNotificationSetting[],
508     notificationCreator: (user: MUserWithNotificationSetting) => Promise<UserNotificationModelForApi>,
509     emailSender: (emails: string[]) => Promise<any> | Bluebird<any>,
510     settingGetter: (user: MUserWithNotificationSetting) => UserNotificationSettingValue
511   }) {
512     const emails: string[] = []
513
514     for (const user of options.users) {
515       if (this.isWebNotificationEnabled(options.settingGetter(user))) {
516         const notification = await options.notificationCreator(user)
517
518         PeerTubeSocket.Instance.sendNotification(user.id, notification)
519       }
520
521       if (this.isEmailEnabled(user, options.settingGetter(user))) {
522         emails.push(user.email)
523       }
524     }
525
526     if (emails.length !== 0) {
527       await options.emailSender(emails)
528     }
529   }
530
531   private isEmailEnabled (user: MUser, value: UserNotificationSettingValue) {
532     if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION === true && user.emailVerified === false) return false
533
534     return value & UserNotificationSettingValue.EMAIL
535   }
536
537   private isWebNotificationEnabled (value: UserNotificationSettingValue) {
538     return value & UserNotificationSettingValue.WEB
539   }
540
541   static get Instance () {
542     return this.instance || (this.instance = new this())
543   }
544 }
545
546 // ---------------------------------------------------------------------------
547
548 export {
549   Notifier
550 }