notifier.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  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 { VideoAbuseModel } from '../models/video/video-abuse'
  12. import { VideoBlacklistModel } from '../models/video/video-blacklist'
  13. import * as Bluebird from 'bluebird'
  14. import { VideoImportModel } from '../models/video/video-import'
  15. import { AccountBlocklistModel } from '../models/account/account-blocklist'
  16. import { ActorFollowModel } from '../models/activitypub/actor-follow'
  17. import { AccountModel } from '../models/account/account'
  18. class Notifier {
  19. private static instance: Notifier
  20. private constructor () {}
  21. notifyOnNewVideo (video: VideoModel): void {
  22. // Only notify on public and published videos which are not blacklisted
  23. if (video.privacy !== VideoPrivacy.PUBLIC || video.state !== VideoState.PUBLISHED || video.VideoBlacklist) return
  24. this.notifySubscribersOfNewVideo(video)
  25. .catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err }))
  26. }
  27. notifyOnVideoPublishedAfterTranscoding (video: VideoModel): void {
  28. // don't notify if didn't wait for transcoding or video is still blacklisted/waiting for scheduled update
  29. if (!video.waitTranscoding || video.VideoBlacklist || video.ScheduleVideoUpdate) return
  30. this.notifyOwnedVideoHasBeenPublished(video)
  31. .catch(err => logger.error('Cannot notify owner that its video %s has been published after transcoding.', video.url, { err }))
  32. }
  33. notifyOnVideoPublishedAfterScheduledUpdate (video: VideoModel): void {
  34. // don't notify if video is still blacklisted or waiting for transcoding
  35. if (video.VideoBlacklist || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return
  36. this.notifyOwnedVideoHasBeenPublished(video)
  37. .catch(err => logger.error('Cannot notify owner that its video %s has been published after scheduled update.', video.url, { err }))
  38. }
  39. notifyOnVideoPublishedAfterRemovedFromAutoBlacklist (video: VideoModel): void {
  40. // don't notify if video is still waiting for transcoding or scheduled update
  41. if (video.ScheduleVideoUpdate || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return
  42. this.notifyOwnedVideoHasBeenPublished(video)
  43. .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
  44. }
  45. notifyOnNewComment (comment: VideoCommentModel): void {
  46. this.notifyVideoOwnerOfNewComment(comment)
  47. .catch(err => logger.error('Cannot notify video owner of new comment %s.', comment.url, { err }))
  48. this.notifyOfCommentMention(comment)
  49. .catch(err => logger.error('Cannot notify mentions of comment %s.', comment.url, { err }))
  50. }
  51. notifyOnNewVideoAbuse (videoAbuse: VideoAbuseModel): void {
  52. this.notifyModeratorsOfNewVideoAbuse(videoAbuse)
  53. .catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err }))
  54. }
  55. notifyOnVideoAutoBlacklist (video: VideoModel): void {
  56. this.notifyModeratorsOfVideoAutoBlacklist(video)
  57. .catch(err => logger.error('Cannot notify of auto-blacklist of video %s.', video.url, { err }))
  58. }
  59. notifyOnVideoBlacklist (videoBlacklist: VideoBlacklistModel): void {
  60. this.notifyVideoOwnerOfBlacklist(videoBlacklist)
  61. .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err }))
  62. }
  63. notifyOnVideoUnblacklist (video: VideoModel): void {
  64. this.notifyVideoOwnerOfUnblacklist(video)
  65. .catch(err => logger.error('Cannot notify video owner of unblacklist of %s.', video.url, { err }))
  66. }
  67. notifyOnFinishedVideoImport (videoImport: VideoImportModel, success: boolean): void {
  68. this.notifyOwnerVideoImportIsFinished(videoImport, success)
  69. .catch(err => logger.error('Cannot notify owner that its video import %s is finished.', videoImport.getTargetIdentifier(), { err }))
  70. }
  71. notifyOnNewUserRegistration (user: UserModel): void {
  72. this.notifyModeratorsOfNewUserRegistration(user)
  73. .catch(err => logger.error('Cannot notify moderators of new user registration (%s).', user.username, { err }))
  74. }
  75. notifyOfNewUserFollow (actorFollow: ActorFollowModel): void {
  76. this.notifyUserOfNewActorFollow(actorFollow)
  77. .catch(err => {
  78. logger.error(
  79. 'Cannot notify owner of channel %s of a new follow by %s.',
  80. actorFollow.ActorFollowing.VideoChannel.getDisplayName(),
  81. actorFollow.ActorFollower.Account.getDisplayName(),
  82. { err }
  83. )
  84. })
  85. }
  86. notifyOfNewInstanceFollow (actorFollow: ActorFollowModel): void {
  87. this.notifyAdminsOfNewInstanceFollow(actorFollow)
  88. .catch(err => {
  89. logger.error('Cannot notify administrators of new follower %s.', actorFollow.ActorFollower.url, { err })
  90. })
  91. }
  92. private async notifySubscribersOfNewVideo (video: VideoModel) {
  93. // List all followers that are users
  94. const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId)
  95. logger.info('Notifying %d users of new video %s.', users.length, video.url)
  96. function settingGetter (user: UserModel) {
  97. return user.NotificationSetting.newVideoFromSubscription
  98. }
  99. async function notificationCreator (user: UserModel) {
  100. const notification = await UserNotificationModel.create({
  101. type: UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION,
  102. userId: user.id,
  103. videoId: video.id
  104. })
  105. notification.Video = video
  106. return notification
  107. }
  108. function emailSender (emails: string[]) {
  109. return Emailer.Instance.addNewVideoFromSubscriberNotification(emails, video)
  110. }
  111. return this.notify({ users, settingGetter, notificationCreator, emailSender })
  112. }
  113. private async notifyVideoOwnerOfNewComment (comment: VideoCommentModel) {
  114. if (comment.Video.isOwned() === false) return
  115. const user = await UserModel.loadByVideoId(comment.videoId)
  116. // Not our user or user comments its own video
  117. if (!user || comment.Account.userId === user.id) return
  118. const accountMuted = await AccountBlocklistModel.isAccountMutedBy(user.Account.id, comment.accountId)
  119. if (accountMuted) return
  120. logger.info('Notifying user %s of new comment %s.', user.username, comment.url)
  121. function settingGetter (user: UserModel) {
  122. return user.NotificationSetting.newCommentOnMyVideo
  123. }
  124. async function notificationCreator (user: UserModel) {
  125. const notification = await UserNotificationModel.create({
  126. type: UserNotificationType.NEW_COMMENT_ON_MY_VIDEO,
  127. userId: user.id,
  128. commentId: comment.id
  129. })
  130. notification.Comment = comment
  131. return notification
  132. }
  133. function emailSender (emails: string[]) {
  134. return Emailer.Instance.addNewCommentOnMyVideoNotification(emails, comment)
  135. }
  136. return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
  137. }
  138. private async notifyOfCommentMention (comment: VideoCommentModel) {
  139. const extractedUsernames = comment.extractMentions()
  140. logger.debug(
  141. 'Extracted %d username from comment %s.', extractedUsernames.length, comment.url,
  142. { usernames: extractedUsernames, text: comment.text }
  143. )
  144. let users = await UserModel.listByUsernames(extractedUsernames)
  145. if (comment.Video.isOwned()) {
  146. const userException = await UserModel.loadByVideoId(comment.videoId)
  147. users = users.filter(u => u.id !== userException.id)
  148. }
  149. // Don't notify if I mentioned myself
  150. users = users.filter(u => u.Account.id !== comment.accountId)
  151. if (users.length === 0) return
  152. const accountMutedHash = await AccountBlocklistModel.isAccountMutedByMulti(users.map(u => u.Account.id), comment.accountId)
  153. logger.info('Notifying %d users of new comment %s.', users.length, comment.url)
  154. function settingGetter (user: UserModel) {
  155. if (accountMutedHash[user.Account.id] === true) return UserNotificationSettingValue.NONE
  156. return user.NotificationSetting.commentMention
  157. }
  158. async function notificationCreator (user: UserModel) {
  159. const notification = await UserNotificationModel.create({
  160. type: UserNotificationType.COMMENT_MENTION,
  161. userId: user.id,
  162. commentId: comment.id
  163. })
  164. notification.Comment = comment
  165. return notification
  166. }
  167. function emailSender (emails: string[]) {
  168. return Emailer.Instance.addNewCommentMentionNotification(emails, comment)
  169. }
  170. return this.notify({ users, settingGetter, notificationCreator, emailSender })
  171. }
  172. private async notifyUserOfNewActorFollow (actorFollow: ActorFollowModel) {
  173. if (actorFollow.ActorFollowing.isOwned() === false) return
  174. // Account follows one of our account?
  175. let followType: 'account' | 'channel' = 'channel'
  176. let user = await UserModel.loadByChannelActorId(actorFollow.ActorFollowing.id)
  177. // Account follows one of our channel?
  178. if (!user) {
  179. user = await UserModel.loadByAccountActorId(actorFollow.ActorFollowing.id)
  180. followType = 'account'
  181. }
  182. if (!user) return
  183. if (!actorFollow.ActorFollower.Account || !actorFollow.ActorFollower.Account.name) {
  184. actorFollow.ActorFollower.Account = await actorFollow.ActorFollower.$get('Account') as AccountModel
  185. }
  186. const followerAccount = actorFollow.ActorFollower.Account
  187. const accountMuted = await AccountBlocklistModel.isAccountMutedBy(user.Account.id, followerAccount.id)
  188. if (accountMuted) return
  189. logger.info('Notifying user %s of new follower: %s.', user.username, followerAccount.getDisplayName())
  190. function settingGetter (user: UserModel) {
  191. return user.NotificationSetting.newFollow
  192. }
  193. async function notificationCreator (user: UserModel) {
  194. const notification = await UserNotificationModel.create({
  195. type: UserNotificationType.NEW_FOLLOW,
  196. userId: user.id,
  197. actorFollowId: actorFollow.id
  198. })
  199. notification.ActorFollow = actorFollow
  200. return notification
  201. }
  202. function emailSender (emails: string[]) {
  203. return Emailer.Instance.addNewFollowNotification(emails, actorFollow, followType)
  204. }
  205. return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
  206. }
  207. private async notifyAdminsOfNewInstanceFollow (actorFollow: ActorFollowModel) {
  208. const admins = await UserModel.listWithRight(UserRight.MANAGE_SERVER_FOLLOW)
  209. logger.info('Notifying %d administrators of new instance follower: %s.', admins.length, actorFollow.ActorFollower.url)
  210. function settingGetter (user: UserModel) {
  211. return user.NotificationSetting.newInstanceFollower
  212. }
  213. async function notificationCreator (user: UserModel) {
  214. const notification = await UserNotificationModel.create({
  215. type: UserNotificationType.NEW_INSTANCE_FOLLOWER,
  216. userId: user.id,
  217. actorFollowId: actorFollow.id
  218. })
  219. notification.ActorFollow = actorFollow
  220. return notification
  221. }
  222. function emailSender (emails: string[]) {
  223. return Emailer.Instance.addNewInstanceFollowerNotification(emails, actorFollow)
  224. }
  225. return this.notify({ users: admins, settingGetter, notificationCreator, emailSender })
  226. }
  227. private async notifyModeratorsOfNewVideoAbuse (videoAbuse: VideoAbuseModel) {
  228. const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_ABUSES)
  229. if (moderators.length === 0) return
  230. logger.info('Notifying %s user/moderators of new video abuse %s.', moderators.length, videoAbuse.Video.url)
  231. function settingGetter (user: UserModel) {
  232. return user.NotificationSetting.videoAbuseAsModerator
  233. }
  234. async function notificationCreator (user: UserModel) {
  235. const notification = await UserNotificationModel.create({
  236. type: UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS,
  237. userId: user.id,
  238. videoAbuseId: videoAbuse.id
  239. })
  240. notification.VideoAbuse = videoAbuse
  241. return notification
  242. }
  243. function emailSender (emails: string[]) {
  244. return Emailer.Instance.addVideoAbuseModeratorsNotification(emails, videoAbuse)
  245. }
  246. return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
  247. }
  248. private async notifyModeratorsOfVideoAutoBlacklist (video: VideoModel) {
  249. const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_BLACKLIST)
  250. if (moderators.length === 0) return
  251. logger.info('Notifying %s moderators of video auto-blacklist %s.', moderators.length, video.url)
  252. function settingGetter (user: UserModel) {
  253. return user.NotificationSetting.videoAutoBlacklistAsModerator
  254. }
  255. async function notificationCreator (user: UserModel) {
  256. const notification = await UserNotificationModel.create({
  257. type: UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS,
  258. userId: user.id,
  259. videoId: video.id
  260. })
  261. notification.Video = video
  262. return notification
  263. }
  264. function emailSender (emails: string[]) {
  265. return Emailer.Instance.addVideoAutoBlacklistModeratorsNotification(emails, video)
  266. }
  267. return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
  268. }
  269. private async notifyVideoOwnerOfBlacklist (videoBlacklist: VideoBlacklistModel) {
  270. const user = await UserModel.loadByVideoId(videoBlacklist.videoId)
  271. if (!user) return
  272. logger.info('Notifying user %s that its video %s has been blacklisted.', user.username, videoBlacklist.Video.url)
  273. function settingGetter (user: UserModel) {
  274. return user.NotificationSetting.blacklistOnMyVideo
  275. }
  276. async function notificationCreator (user: UserModel) {
  277. const notification = await UserNotificationModel.create({
  278. type: UserNotificationType.BLACKLIST_ON_MY_VIDEO,
  279. userId: user.id,
  280. videoBlacklistId: videoBlacklist.id
  281. })
  282. notification.VideoBlacklist = videoBlacklist
  283. return notification
  284. }
  285. function emailSender (emails: string[]) {
  286. return Emailer.Instance.addVideoBlacklistNotification(emails, videoBlacklist)
  287. }
  288. return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
  289. }
  290. private async notifyVideoOwnerOfUnblacklist (video: VideoModel) {
  291. const user = await UserModel.loadByVideoId(video.id)
  292. if (!user) return
  293. logger.info('Notifying user %s that its video %s has been unblacklisted.', user.username, video.url)
  294. function settingGetter (user: UserModel) {
  295. return user.NotificationSetting.blacklistOnMyVideo
  296. }
  297. async function notificationCreator (user: UserModel) {
  298. const notification = await UserNotificationModel.create({
  299. type: UserNotificationType.UNBLACKLIST_ON_MY_VIDEO,
  300. userId: user.id,
  301. videoId: video.id
  302. })
  303. notification.Video = video
  304. return notification
  305. }
  306. function emailSender (emails: string[]) {
  307. return Emailer.Instance.addVideoUnblacklistNotification(emails, video)
  308. }
  309. return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
  310. }
  311. private async notifyOwnedVideoHasBeenPublished (video: VideoModel) {
  312. const user = await UserModel.loadByVideoId(video.id)
  313. if (!user) return
  314. logger.info('Notifying user %s of the publication of its video %s.', user.username, video.url)
  315. function settingGetter (user: UserModel) {
  316. return user.NotificationSetting.myVideoPublished
  317. }
  318. async function notificationCreator (user: UserModel) {
  319. const notification = await UserNotificationModel.create({
  320. type: UserNotificationType.MY_VIDEO_PUBLISHED,
  321. userId: user.id,
  322. videoId: video.id
  323. })
  324. notification.Video = video
  325. return notification
  326. }
  327. function emailSender (emails: string[]) {
  328. return Emailer.Instance.myVideoPublishedNotification(emails, video)
  329. }
  330. return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
  331. }
  332. private async notifyOwnerVideoImportIsFinished (videoImport: VideoImportModel, success: boolean) {
  333. const user = await UserModel.loadByVideoImportId(videoImport.id)
  334. if (!user) return
  335. logger.info('Notifying user %s its video import %s is finished.', user.username, videoImport.getTargetIdentifier())
  336. function settingGetter (user: UserModel) {
  337. return user.NotificationSetting.myVideoImportFinished
  338. }
  339. async function notificationCreator (user: UserModel) {
  340. const notification = await UserNotificationModel.create({
  341. type: success ? UserNotificationType.MY_VIDEO_IMPORT_SUCCESS : UserNotificationType.MY_VIDEO_IMPORT_ERROR,
  342. userId: user.id,
  343. videoImportId: videoImport.id
  344. })
  345. notification.VideoImport = videoImport
  346. return notification
  347. }
  348. function emailSender (emails: string[]) {
  349. return success
  350. ? Emailer.Instance.myVideoImportSuccessNotification(emails, videoImport)
  351. : Emailer.Instance.myVideoImportErrorNotification(emails, videoImport)
  352. }
  353. return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
  354. }
  355. private async notifyModeratorsOfNewUserRegistration (registeredUser: UserModel) {
  356. const moderators = await UserModel.listWithRight(UserRight.MANAGE_USERS)
  357. if (moderators.length === 0) return
  358. logger.info(
  359. 'Notifying %s moderators of new user registration of %s.',
  360. moderators.length, registeredUser.Account.Actor.preferredUsername
  361. )
  362. function settingGetter (user: UserModel) {
  363. return user.NotificationSetting.newUserRegistration
  364. }
  365. async function notificationCreator (user: UserModel) {
  366. const notification = await UserNotificationModel.create({
  367. type: UserNotificationType.NEW_USER_REGISTRATION,
  368. userId: user.id,
  369. accountId: registeredUser.Account.id
  370. })
  371. notification.Account = registeredUser.Account
  372. return notification
  373. }
  374. function emailSender (emails: string[]) {
  375. return Emailer.Instance.addNewUserRegistrationNotification(emails, registeredUser)
  376. }
  377. return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
  378. }
  379. private async notify (options: {
  380. users: UserModel[],
  381. notificationCreator: (user: UserModel) => Promise<UserNotificationModel>,
  382. emailSender: (emails: string[]) => Promise<any> | Bluebird<any>,
  383. settingGetter: (user: UserModel) => UserNotificationSettingValue
  384. }) {
  385. const emails: string[] = []
  386. for (const user of options.users) {
  387. if (this.isWebNotificationEnabled(options.settingGetter(user))) {
  388. const notification = await options.notificationCreator(user)
  389. PeerTubeSocket.Instance.sendNotification(user.id, notification)
  390. }
  391. if (this.isEmailEnabled(user, options.settingGetter(user))) {
  392. emails.push(user.email)
  393. }
  394. }
  395. if (emails.length !== 0) {
  396. await options.emailSender(emails)
  397. }
  398. }
  399. private isEmailEnabled (user: UserModel, value: UserNotificationSettingValue) {
  400. if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION === true && user.emailVerified === false) return false
  401. return value & UserNotificationSettingValue.EMAIL
  402. }
  403. private isWebNotificationEnabled (value: UserNotificationSettingValue) {
  404. return value & UserNotificationSettingValue.WEB
  405. }
  406. static get Instance () {
  407. return this.instance || (this.instance = new this())
  408. }
  409. }
  410. // ---------------------------------------------------------------------------
  411. export {
  412. Notifier
  413. }