PeerTube/server/core/lib/notifier/notifier.ts

356 lines
14 KiB
TypeScript
Raw Normal View History

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,
2024-06-13 09:23:12 +02:00
MVideoCaptionVideo,
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'
2021-07-30 16:51:27 +02:00
import {
AbstractNotification,
AbuseStateChangeForReporter,
AutoFollowForInstance,
CommentMention,
2023-01-19 09:27:16 +01:00
DirectRegistrationForModerators,
2021-07-30 16:51:27 +02:00
FollowForInstance,
FollowForUser,
ImportFinishedForOwner,
ImportFinishedForOwnerPayload,
NewAbuseForModerators,
NewAbuseMessageForModerators,
NewAbuseMessageForReporter,
NewAbusePayload,
NewAutoBlacklistForModerators,
NewBlacklistForOwner,
NewCommentForVideoOwner,
NewPeerTubeVersionForAdmins,
NewPluginVersionForAdmins,
NewVideoOrLiveForSubscribers,
2021-07-30 16:51:27 +02:00
OwnedPublicationAfterAutoUnblacklist,
OwnedPublicationAfterScheduleUpdate,
OwnedPublicationAfterTranscoding,
2023-01-19 09:27:16 +01:00
RegistrationRequestForModerators,
2022-08-03 11:33:43 +02:00
StudioEditionFinishedForOwner,
2024-06-13 09:23:12 +02:00
UnblacklistForOwner,
VideoTranscriptionGeneratedForOwner
} from './shared/index.js'
2021-07-30 16:51:27 +02:00
const lTags = loggerTagsFactory('notifier')
2021-07-30 16:51:27 +02:00
class Notifier {
private readonly notificationModels = {
newVideoOrLive: [ NewVideoOrLiveForSubscribers ],
2021-07-30 16:51:27 +02:00
publicationAfterTranscoding: [ OwnedPublicationAfterTranscoding ],
publicationAfterScheduleUpdate: [ OwnedPublicationAfterScheduleUpdate ],
publicationAfterAutoUnblacklist: [ OwnedPublicationAfterAutoUnblacklist ],
newComment: [ CommentMention, NewCommentForVideoOwner ],
commentApproval: [ CommentMention ],
2021-07-30 16:51:27 +02:00
newAbuse: [ NewAbuseForModerators ],
newBlacklist: [ NewBlacklistForOwner ],
unblacklist: [ UnblacklistForOwner ],
importFinished: [ ImportFinishedForOwner ],
2023-01-19 09:27:16 +01:00
directRegistration: [ DirectRegistrationForModerators ],
registrationRequest: [ RegistrationRequestForModerators ],
2021-07-30 16:51:27 +02:00
userFollow: [ FollowForUser ],
instanceFollow: [ FollowForInstance ],
autoInstanceFollow: [ AutoFollowForInstance ],
newAutoBlacklist: [ NewAutoBlacklistForModerators ],
abuseStateChange: [ AbuseStateChangeForReporter ],
newAbuseMessage: [ NewAbuseMessageForReporter, NewAbuseMessageForModerators ],
newPeertubeVersion: [ NewPeerTubeVersionForAdmins ],
newPluginVersion: [ NewPluginVersionForAdmins ],
2024-06-13 09:23:12 +02:00
videoStudioEditionFinished: [ StudioEditionFinishedForOwner ],
videoTranscriptionGenerated: [ VideoTranscriptionGeneratedForOwner ]
2021-07-30 16:51:27 +02:00
}
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() })
2021-07-30 16:51:27 +02:00
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() })
2021-07-30 16:51:27 +02:00
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() })
2021-07-30 16:51:27 +02:00
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() })
2021-07-30 16:51:27 +02:00
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() })
2021-07-30 16:51:27 +02:00
this.sendNotifications(models, comment)
2023-10-30 14:14:19 +01:00
.catch(err => logger.error('Cannot notify of new comment %s.', comment.url, { err }))
2021-07-30 16:51:27 +02:00
}
notifyOnNewCommentApproval (comment: MCommentOwnerVideo): void {
const models = this.notificationModels.commentApproval
logger.debug('Notify on comment approval', { comment: comment.url, ...lTags() })
this.sendNotifications(models, comment)
.catch(err => logger.error('Cannot notify on comment approval %s.', comment.url, { err }))
}
2021-07-30 16:51:27 +02:00
notifyOnNewAbuse (payload: NewAbusePayload): void {
const models = this.notificationModels.newAbuse
logger.debug('Notify on new abuse', { abuse: payload.abuseInstance.id, ...lTags() })
2021-07-30 16:51:27 +02:00
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() })
2021-07-30 16:51:27 +02:00
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() })
2021-07-30 16:51:27 +02:00
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() })
2021-07-30 16:51:27 +02:00
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() })
2021-07-30 16:51:27 +02:00
this.sendNotifications(models, payload)
.catch(err => {
logger.error('Cannot notify owner that its video import %s is finished.', payload.videoImport.getTargetIdentifier(), { err })
})
}
2023-01-19 09:27:16 +01:00
notifyOnNewDirectRegistration (user: MUserDefault): void {
const models = this.notificationModels.directRegistration
2021-07-30 16:51:27 +02:00
logger.debug('Notify on new direct registration', { user: user.username, ...lTags() })
2021-07-30 16:51:27 +02:00
this.sendNotifications(models, user)
.catch(err => logger.error('Cannot notify moderators of new user registration (%s).', user.username, { err }))
}
2023-01-19 09:27:16 +01:00
notifyOnNewRegistrationRequest (registration: MRegistration): void {
const models = this.notificationModels.registrationRequest
logger.debug('Notify on new registration request', { registration: registration.username, ...lTags() })
2023-01-19 09:27:16 +01:00
this.sendNotifications(models, registration)
.catch(err => logger.error('Cannot notify moderators of new registration request (%s).', registration.username, { err }))
}
2021-07-30 16:51:27 +02:00
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() })
2021-07-30 16:51:27 +02:00
this.sendNotifications(models, actorFollow)
.catch(err => {
logger.error('Cannot notify owner of channel %s of a new follow by %s.', following, follower, { err })
2021-07-30 16:51:27 +02:00
})
}
notifyOfNewInstanceFollow (actorFollow: MActorFollowFull): void {
const models = this.notificationModels.instanceFollow
logger.debug('Notify on new instance follow', { follower: actorFollow.ActorFollower.url, ...lTags() })
2021-07-30 16:51:27 +02:00
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() })
2021-07-30 16:51:27 +02:00
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() })
2021-07-30 16:51:27 +02:00
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() })
2021-07-30 16:51:27 +02:00
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() })
2021-07-30 16:51:27 +02:00
this.sendNotifications(models, { application, latestVersion })
.catch(err => logger.error('Cannot notify on new PeerTube version %s.', latestVersion, { err }))
2021-07-30 16:51:27 +02:00
}
notifyOfNewPluginVersion (plugin: MPlugin) {
const models = this.notificationModels.newPluginVersion
logger.debug('Notify on new plugin version', { plugin: plugin.name, ...lTags() })
2021-07-30 16:51:27 +02:00
this.sendNotifications(models, plugin)
.catch(err => logger.error('Cannot notify on new plugin version %s.', plugin.name, { err }))
}
2022-03-22 16:58:49 +01:00
notifyOfFinishedVideoStudioEdition (video: MVideoFullLight) {
const models = this.notificationModels.videoStudioEditionFinished
logger.debug('Notify on finished video studio edition', { video: video.url, ...lTags() })
this.sendNotifications(models, video)
2022-03-22 16:58:49 +01:00
.catch(err => logger.error('Cannot notify on finished studio edition %s.', video.url, { err }))
}
2024-06-13 09:23:12 +02:00
notifyOfGeneratedVideoTranscription (caption: MVideoCaptionVideo) {
const models = this.notificationModels.videoTranscriptionGenerated
const video = caption.Video
logger.debug('Notify on generated video transcription', { language: caption.language, video: video.url, ...lTags() })
this.sendNotifications(models, caption)
.catch(err => logger.error('Cannot notify on generated video transcription %s of video %s.', caption.language, video.url, { err }))
}
2021-07-30 16:51:27 +02:00
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)
2022-08-03 11:33:43 +02:00
const webNotificationEnabled = this.isWebNotificationEnabled(setting)
const emailNotificationEnabled = this.isEmailEnabled(user, setting)
const notification = object.createNotification(user)
if (webNotificationEnabled) {
await notification.save()
2021-07-30 16:51:27 +02:00
PeerTubeSocket.Instance.sendNotification(user.id, notification)
}
2022-08-03 11:33:43 +02:00
if (emailNotificationEnabled) {
2021-07-30 16:51:27 +02:00
toEmails.push(user.email)
}
2022-08-03 11:33:43 +02:00
Hooks.runAction('action:notifier.notification.created', { webNotificationEnabled, emailNotificationEnabled, user, notification })
2021-07-30 16:51:27 +02:00
}
for (const to of toEmails) {
const payload = await object.createEmail(to)
2022-08-08 15:48:17 +02:00
JobQueue.Instance.createJobAsync({ type: 'email', payload })
2021-07-30 16:51:27 +02:00
}
}
private isEmailEnabled (user: MUser, value: UserNotificationSettingValueType) {
2021-07-30 16:51:27 +02:00
if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION === true && user.emailVerified === false) return false
return value & UserNotificationSettingValue.EMAIL
}
private isWebNotificationEnabled (value: UserNotificationSettingValueType) {
2021-07-30 16:51:27 +02:00
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
}