mirror of https://github.com/Chocobozzz/PeerTube
				
				
				
			
		
			
				
	
	
		
			333 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			TypeScript
		
	
	
			
		
		
	
	
			333 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			TypeScript
		
	
	
import { UserNotificationSettingValue, UserNotificationSettingValueType } from '@peertube/peertube-models'
 | 
						|
import { MRegistration, MUser, MUserDefault } from '@server/types/models/user/index.js'
 | 
						|
import { MVideoBlacklistLightVideo, MVideoBlacklistVideo } from '@server/types/models/video/video-blacklist.js'
 | 
						|
import { logger, loggerTagsFactory } from '../../helpers/logger.js'
 | 
						|
import { CONFIG } from '../../initializers/config.js'
 | 
						|
import {
 | 
						|
  MAbuseFull,
 | 
						|
  MAbuseMessage,
 | 
						|
  MActorFollowFull,
 | 
						|
  MApplication,
 | 
						|
  MCommentOwnerVideo,
 | 
						|
  MPlugin,
 | 
						|
  MVideoAccountLight,
 | 
						|
  MVideoFullLight
 | 
						|
} from '../../types/models/index.js'
 | 
						|
import { JobQueue } from '../job-queue/index.js'
 | 
						|
import { PeerTubeSocket } from '../peertube-socket.js'
 | 
						|
import { Hooks } from '../plugins/hooks.js'
 | 
						|
import {
 | 
						|
  AbstractNotification,
 | 
						|
  AbuseStateChangeForReporter,
 | 
						|
  AutoFollowForInstance,
 | 
						|
  CommentMention,
 | 
						|
  DirectRegistrationForModerators,
 | 
						|
  FollowForInstance,
 | 
						|
  FollowForUser,
 | 
						|
  ImportFinishedForOwner,
 | 
						|
  ImportFinishedForOwnerPayload,
 | 
						|
  NewAbuseForModerators,
 | 
						|
  NewAbuseMessageForModerators,
 | 
						|
  NewAbuseMessageForReporter,
 | 
						|
  NewAbusePayload,
 | 
						|
  NewAutoBlacklistForModerators,
 | 
						|
  NewBlacklistForOwner,
 | 
						|
  NewCommentForVideoOwner,
 | 
						|
  NewPeerTubeVersionForAdmins,
 | 
						|
  NewPluginVersionForAdmins,
 | 
						|
  NewVideoOrLiveForSubscribers,
 | 
						|
  OwnedPublicationAfterAutoUnblacklist,
 | 
						|
  OwnedPublicationAfterScheduleUpdate,
 | 
						|
  OwnedPublicationAfterTranscoding,
 | 
						|
  RegistrationRequestForModerators,
 | 
						|
  StudioEditionFinishedForOwner,
 | 
						|
  UnblacklistForOwner
 | 
						|
} from './shared/index.js'
 | 
						|
 | 
						|
const lTags = loggerTagsFactory('notifier')
 | 
						|
 | 
						|
class Notifier {
 | 
						|
 | 
						|
  private readonly notificationModels = {
 | 
						|
    newVideoOrLive: [ NewVideoOrLiveForSubscribers ],
 | 
						|
    publicationAfterTranscoding: [ OwnedPublicationAfterTranscoding ],
 | 
						|
    publicationAfterScheduleUpdate: [ OwnedPublicationAfterScheduleUpdate ],
 | 
						|
    publicationAfterAutoUnblacklist: [ OwnedPublicationAfterAutoUnblacklist ],
 | 
						|
    newComment: [ CommentMention, NewCommentForVideoOwner ],
 | 
						|
    newAbuse: [ NewAbuseForModerators ],
 | 
						|
    newBlacklist: [ NewBlacklistForOwner ],
 | 
						|
    unblacklist: [ UnblacklistForOwner ],
 | 
						|
    importFinished: [ ImportFinishedForOwner ],
 | 
						|
    directRegistration: [ DirectRegistrationForModerators ],
 | 
						|
    registrationRequest: [ RegistrationRequestForModerators ],
 | 
						|
    userFollow: [ FollowForUser ],
 | 
						|
    instanceFollow: [ FollowForInstance ],
 | 
						|
    autoInstanceFollow: [ AutoFollowForInstance ],
 | 
						|
    newAutoBlacklist: [ NewAutoBlacklistForModerators ],
 | 
						|
    abuseStateChange: [ AbuseStateChangeForReporter ],
 | 
						|
    newAbuseMessage: [ NewAbuseMessageForReporter, NewAbuseMessageForModerators ],
 | 
						|
    newPeertubeVersion: [ NewPeerTubeVersionForAdmins ],
 | 
						|
    newPluginVersion: [ NewPluginVersionForAdmins ],
 | 
						|
    videoStudioEditionFinished: [ StudioEditionFinishedForOwner ]
 | 
						|
  }
 | 
						|
 | 
						|
  private static instance: Notifier
 | 
						|
 | 
						|
  private constructor () {
 | 
						|
  }
 | 
						|
 | 
						|
  notifyOnNewVideoOrLiveIfNeeded (video: MVideoAccountLight): void {
 | 
						|
    const models = this.notificationModels.newVideoOrLive
 | 
						|
 | 
						|
    logger.debug('Notify on new video or live if needed', { video: video.url, ...lTags() })
 | 
						|
 | 
						|
    this.sendNotifications(models, video)
 | 
						|
      .catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err }))
 | 
						|
  }
 | 
						|
 | 
						|
  notifyOnVideoPublishedAfterTranscoding (video: MVideoFullLight): void {
 | 
						|
    const models = this.notificationModels.publicationAfterTranscoding
 | 
						|
 | 
						|
    logger.debug('Notify on published video after transcoding', { video: video.url, ...lTags() })
 | 
						|
 | 
						|
    this.sendNotifications(models, video)
 | 
						|
      .catch(err => logger.error('Cannot notify owner that its video %s has been published after transcoding.', video.url, { err }))
 | 
						|
  }
 | 
						|
 | 
						|
  notifyOnVideoPublishedAfterScheduledUpdate (video: MVideoFullLight): void {
 | 
						|
    const models = this.notificationModels.publicationAfterScheduleUpdate
 | 
						|
 | 
						|
    logger.debug('Notify on published video after scheduled update', { video: video.url, ...lTags() })
 | 
						|
 | 
						|
    this.sendNotifications(models, video)
 | 
						|
      .catch(err => logger.error('Cannot notify owner that its video %s has been published after scheduled update.', video.url, { err }))
 | 
						|
  }
 | 
						|
 | 
						|
  notifyOnVideoPublishedAfterRemovedFromAutoBlacklist (video: MVideoFullLight): void {
 | 
						|
    const models = this.notificationModels.publicationAfterAutoUnblacklist
 | 
						|
 | 
						|
    logger.debug('Notify on published video after being removed from auto blacklist', { video: video.url, ...lTags() })
 | 
						|
 | 
						|
    this.sendNotifications(models, video)
 | 
						|
      .catch(err => {
 | 
						|
        logger.error('Cannot notify owner that its video %s has been published after removed from auto-blacklist.', video.url, { err })
 | 
						|
      })
 | 
						|
  }
 | 
						|
 | 
						|
  notifyOnNewComment (comment: MCommentOwnerVideo): void {
 | 
						|
    const models = this.notificationModels.newComment
 | 
						|
 | 
						|
    logger.debug('Notify on new comment', { comment: comment.url, ...lTags() })
 | 
						|
 | 
						|
    this.sendNotifications(models, comment)
 | 
						|
      .catch(err => logger.error('Cannot notify of new comment %s.', comment.url, { err }))
 | 
						|
  }
 | 
						|
 | 
						|
  notifyOnNewAbuse (payload: NewAbusePayload): void {
 | 
						|
    const models = this.notificationModels.newAbuse
 | 
						|
 | 
						|
    logger.debug('Notify on new abuse', { abuse: payload.abuseInstance.id, ...lTags() })
 | 
						|
 | 
						|
    this.sendNotifications(models, payload)
 | 
						|
      .catch(err => logger.error('Cannot notify of new abuse %d.', payload.abuseInstance.id, { err }))
 | 
						|
  }
 | 
						|
 | 
						|
  notifyOnVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo): void {
 | 
						|
    const models = this.notificationModels.newAutoBlacklist
 | 
						|
 | 
						|
    logger.debug('Notify on video auto blacklist', { video: videoBlacklist?.Video?.url, ...lTags() })
 | 
						|
 | 
						|
    this.sendNotifications(models, videoBlacklist)
 | 
						|
      .catch(err => logger.error('Cannot notify of auto-blacklist of video %s.', videoBlacklist.Video.url, { err }))
 | 
						|
  }
 | 
						|
 | 
						|
  notifyOnVideoBlacklist (videoBlacklist: MVideoBlacklistVideo): void {
 | 
						|
    const models = this.notificationModels.newBlacklist
 | 
						|
 | 
						|
    logger.debug('Notify on video manual blacklist', { video: videoBlacklist?.Video?.url, ...lTags() })
 | 
						|
 | 
						|
    this.sendNotifications(models, videoBlacklist)
 | 
						|
      .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err }))
 | 
						|
  }
 | 
						|
 | 
						|
  notifyOnVideoUnblacklist (video: MVideoFullLight): void {
 | 
						|
    const models = this.notificationModels.unblacklist
 | 
						|
 | 
						|
    logger.debug('Notify on video unblacklist', { video: video.url, ...lTags() })
 | 
						|
 | 
						|
    this.sendNotifications(models, video)
 | 
						|
        .catch(err => logger.error('Cannot notify video owner of unblacklist of %s.', video.url, { err }))
 | 
						|
  }
 | 
						|
 | 
						|
  notifyOnFinishedVideoImport (payload: ImportFinishedForOwnerPayload): void {
 | 
						|
    const models = this.notificationModels.importFinished
 | 
						|
 | 
						|
    logger.debug('Notify on finished video import', { import: payload.videoImport.getTargetIdentifier(), ...lTags() })
 | 
						|
 | 
						|
    this.sendNotifications(models, payload)
 | 
						|
        .catch(err => {
 | 
						|
          logger.error('Cannot notify owner that its video import %s is finished.', payload.videoImport.getTargetIdentifier(), { err })
 | 
						|
        })
 | 
						|
  }
 | 
						|
 | 
						|
  notifyOnNewDirectRegistration (user: MUserDefault): void {
 | 
						|
    const models = this.notificationModels.directRegistration
 | 
						|
 | 
						|
    logger.debug('Notify on new direct registration', { user: user.username, ...lTags() })
 | 
						|
 | 
						|
    this.sendNotifications(models, user)
 | 
						|
      .catch(err => logger.error('Cannot notify moderators of new user registration (%s).', user.username, { err }))
 | 
						|
  }
 | 
						|
 | 
						|
  notifyOnNewRegistrationRequest (registration: MRegistration): void {
 | 
						|
    const models = this.notificationModels.registrationRequest
 | 
						|
 | 
						|
    logger.debug('Notify on new registration request', { registration: registration.username, ...lTags() })
 | 
						|
 | 
						|
    this.sendNotifications(models, registration)
 | 
						|
      .catch(err => logger.error('Cannot notify moderators of new registration request (%s).', registration.username, { err }))
 | 
						|
  }
 | 
						|
 | 
						|
  notifyOfNewUserFollow (actorFollow: MActorFollowFull): void {
 | 
						|
    const models = this.notificationModels.userFollow
 | 
						|
 | 
						|
    const following = actorFollow?.ActorFollowing?.VideoChannel?.getDisplayName()
 | 
						|
    const follower = actorFollow?.ActorFollower?.Account?.getDisplayName()
 | 
						|
 | 
						|
    logger.debug('Notify on new user follow', { following, follower, ...lTags() })
 | 
						|
 | 
						|
    this.sendNotifications(models, actorFollow)
 | 
						|
      .catch(err => {
 | 
						|
        logger.error('Cannot notify owner of channel %s of a new follow by %s.', following, follower, { err })
 | 
						|
      })
 | 
						|
  }
 | 
						|
 | 
						|
  notifyOfNewInstanceFollow (actorFollow: MActorFollowFull): void {
 | 
						|
    const models = this.notificationModels.instanceFollow
 | 
						|
 | 
						|
    logger.debug('Notify on new instance follow', { follower: actorFollow.ActorFollower.url, ...lTags() })
 | 
						|
 | 
						|
    this.sendNotifications(models, actorFollow)
 | 
						|
      .catch(err => logger.error('Cannot notify administrators of new follower %s.', actorFollow.ActorFollower.url, { err }))
 | 
						|
  }
 | 
						|
 | 
						|
  notifyOfAutoInstanceFollowing (actorFollow: MActorFollowFull): void {
 | 
						|
    const models = this.notificationModels.autoInstanceFollow
 | 
						|
 | 
						|
    logger.debug('Notify on new instance auto following', { following: actorFollow.ActorFollowing.url, ...lTags() })
 | 
						|
 | 
						|
    this.sendNotifications(models, actorFollow)
 | 
						|
      .catch(err => logger.error('Cannot notify administrators of auto instance following %s.', actorFollow.ActorFollowing.url, { err }))
 | 
						|
  }
 | 
						|
 | 
						|
  notifyOnAbuseStateChange (abuse: MAbuseFull): void {
 | 
						|
    const models = this.notificationModels.abuseStateChange
 | 
						|
 | 
						|
    logger.debug('Notify on abuse state change', { abuse: abuse.id, ...lTags() })
 | 
						|
 | 
						|
    this.sendNotifications(models, abuse)
 | 
						|
      .catch(err => logger.error('Cannot notify of abuse %d state change.', abuse.id, { err }))
 | 
						|
  }
 | 
						|
 | 
						|
  notifyOnAbuseMessage (abuse: MAbuseFull, message: MAbuseMessage): void {
 | 
						|
    const models = this.notificationModels.newAbuseMessage
 | 
						|
 | 
						|
    logger.debug('Notify on abuse message', { abuse: abuse.id, message, ...lTags() })
 | 
						|
 | 
						|
    this.sendNotifications(models, { abuse, message })
 | 
						|
      .catch(err => logger.error('Cannot notify on new abuse %d message.', abuse.id, { err }))
 | 
						|
  }
 | 
						|
 | 
						|
  notifyOfNewPeerTubeVersion (application: MApplication, latestVersion: string) {
 | 
						|
    const models = this.notificationModels.newPeertubeVersion
 | 
						|
 | 
						|
    logger.debug('Notify on new peertube version', { currentVersion: application.version, latestVersion, ...lTags() })
 | 
						|
 | 
						|
    this.sendNotifications(models, { application, latestVersion })
 | 
						|
      .catch(err => logger.error('Cannot notify on new PeerTube version %s.', latestVersion, { err }))
 | 
						|
  }
 | 
						|
 | 
						|
  notifyOfNewPluginVersion (plugin: MPlugin) {
 | 
						|
    const models = this.notificationModels.newPluginVersion
 | 
						|
 | 
						|
    logger.debug('Notify on new plugin version', { plugin: plugin.name, ...lTags() })
 | 
						|
 | 
						|
    this.sendNotifications(models, plugin)
 | 
						|
      .catch(err => logger.error('Cannot notify on new plugin version %s.', plugin.name, { err }))
 | 
						|
  }
 | 
						|
 | 
						|
  notifyOfFinishedVideoStudioEdition (video: MVideoFullLight) {
 | 
						|
    const models = this.notificationModels.videoStudioEditionFinished
 | 
						|
 | 
						|
    logger.debug('Notify on finished video studio edition', { video: video.url, ...lTags() })
 | 
						|
 | 
						|
    this.sendNotifications(models, video)
 | 
						|
      .catch(err => logger.error('Cannot notify on finished studio edition %s.', video.url, { err }))
 | 
						|
  }
 | 
						|
 | 
						|
  private async notify <T> (object: AbstractNotification<T>) {
 | 
						|
    await object.prepare()
 | 
						|
 | 
						|
    const users = object.getTargetUsers()
 | 
						|
 | 
						|
    if (users.length === 0) return
 | 
						|
    if (await object.isDisabled()) return
 | 
						|
 | 
						|
    object.log()
 | 
						|
 | 
						|
    const toEmails: string[] = []
 | 
						|
 | 
						|
    for (const user of users) {
 | 
						|
      const setting = object.getSetting(user)
 | 
						|
 | 
						|
      const webNotificationEnabled = this.isWebNotificationEnabled(setting)
 | 
						|
      const emailNotificationEnabled = this.isEmailEnabled(user, setting)
 | 
						|
      const notification = object.createNotification(user)
 | 
						|
 | 
						|
      if (webNotificationEnabled) {
 | 
						|
        await notification.save()
 | 
						|
 | 
						|
        PeerTubeSocket.Instance.sendNotification(user.id, notification)
 | 
						|
      }
 | 
						|
 | 
						|
      if (emailNotificationEnabled) {
 | 
						|
        toEmails.push(user.email)
 | 
						|
      }
 | 
						|
 | 
						|
      Hooks.runAction('action:notifier.notification.created', { webNotificationEnabled, emailNotificationEnabled, user, notification })
 | 
						|
    }
 | 
						|
 | 
						|
    for (const to of toEmails) {
 | 
						|
      const payload = await object.createEmail(to)
 | 
						|
      JobQueue.Instance.createJobAsync({ type: 'email', payload })
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  private isEmailEnabled (user: MUser, value: UserNotificationSettingValueType) {
 | 
						|
    if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION === true && user.emailVerified === false) return false
 | 
						|
 | 
						|
    return value & UserNotificationSettingValue.EMAIL
 | 
						|
  }
 | 
						|
 | 
						|
  private isWebNotificationEnabled (value: UserNotificationSettingValueType) {
 | 
						|
    return value & UserNotificationSettingValue.WEB
 | 
						|
  }
 | 
						|
 | 
						|
  private async sendNotifications <T> (models: (new (payload: T) => AbstractNotification<T>)[], payload: T) {
 | 
						|
    for (const model of models) {
 | 
						|
      // eslint-disable-next-line new-cap
 | 
						|
      await this.notify(new model(payload))
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  static get Instance () {
 | 
						|
    return this.instance || (this.instance = new this())
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
// ---------------------------------------------------------------------------
 | 
						|
 | 
						|
export {
 | 
						|
  Notifier
 | 
						|
}
 |