Init commit

This commit is contained in:
ShreejitPanchal
2026-05-19 19:56:02 +08:00
commit 6601501eff
4047 changed files with 2185372 additions and 0 deletions

View File

@@ -0,0 +1 @@
export * from './notifier.js'

View File

@@ -0,0 +1,401 @@
import { UserNotificationSettingValue, UserNotificationSettingValueType } from '@peertube/peertube-models'
import { MRegistration, MUser, MUserDefault, MUserWithNotificationSetting } 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,
MChannelAccountDefault,
MChannelCollaboratorAccount,
MChannelDefault,
MCommentOwnerVideo,
MPlugin,
MVideoAccountLight,
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'
import { AcceptedToCollaborateToChannel } from './shared/channel/accepted-to-collaborate-to-channel.js'
import { InvitedToCollaborateToChannel } from './shared/channel/invited-to-collaborate-to-channel.js'
import { RefusedToCollaborateToChannel } from './shared/channel/refused-to-collaborate-to-channel.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,
VideoTranscriptionGeneratedForOwner
} from './shared/index.js'
const lTags = loggerTagsFactory('notifier')
class Notifier {
private readonly notificationModels = {
newVideoOrLive: [ NewVideoOrLiveForSubscribers ],
publicationAfterTranscoding: [ OwnedPublicationAfterTranscoding ],
publicationAfterScheduleUpdate: [ OwnedPublicationAfterScheduleUpdate ],
publicationAfterAutoUnblacklist: [ OwnedPublicationAfterAutoUnblacklist ],
newComment: [ CommentMention, NewCommentForVideoOwner ],
commentApproval: [ CommentMention ],
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 ],
videoTranscriptionGenerated: [ VideoTranscriptionGeneratedForOwner ],
channelCollaboratorInvitation: [ InvitedToCollaborateToChannel ],
channelCollaborationAccepted: [ AcceptedToCollaborateToChannel ],
channelCollaborationRefused: [ RefusedToCollaborateToChannel ]
}
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 }))
}
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 }))
}
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 }))
}
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 }))
}
notifyOfChannelCollaboratorInvitation (collaborator: MChannelCollaboratorAccount, channel: MChannelAccountDefault) {
const models = this.notificationModels.channelCollaboratorInvitation
const channelName = channel.Actor.preferredUsername
const collaboratorName = collaborator.Account.Actor.preferredUsername
logger.debug('Notify on channel collaborator invitation', { channelName, collaboratorName, ...lTags() })
this.sendNotifications(models, { channel, collaborator })
.catch(err => logger.error(`Cannot notify ${collaboratorName} of invitation to collaborate to channel ${channelName}`, { err }))
}
notifyOfAcceptedChannelCollaborator (collaborator: MChannelCollaboratorAccount, channel: MChannelDefault) {
const models = this.notificationModels.channelCollaborationAccepted
const channelName = channel.Actor.preferredUsername
const channelOwner = collaborator.Account.Actor.preferredUsername
logger.debug('Notify of accepted channel collaboration invitation', { channelName, channelOwner, ...lTags() })
this.sendNotifications(models, { channel, collaborator })
.catch(err => logger.error(`Cannot notify ${channelOwner} of accepted invitation to collaborate to channel ${channelName}`, { err }))
}
notifyOfRefusedChannelCollaborator (collaborator: MChannelCollaboratorAccount, channel: MChannelDefault) {
const models = this.notificationModels.channelCollaborationRefused
const channelName = channel.Actor.preferredUsername
const channelOwner = collaborator.Account.Actor.preferredUsername
logger.debug('Notify of refused channel collaboration invitation', { channelName, channelOwner, ...lTags() })
this.sendNotifications(models, { channel, collaborator })
.catch(err => logger.error(`Cannot notify ${channelOwner} of refused invitation to collaborate to channel ${channelName}`, { 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 toUsers: MUserWithNotificationSetting[] = []
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) {
toUsers.push(user)
}
Hooks.runAction('action:notifier.notification.created', { webNotificationEnabled, emailNotificationEnabled, user, notification })
}
for (const to of toUsers) {
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) === UserNotificationSettingValue.EMAIL
}
private isWebNotificationEnabled (value: UserNotificationSettingValueType) {
return (value & UserNotificationSettingValue.WEB) === 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
}

View File

@@ -0,0 +1,74 @@
import { To, UserNotificationType } from '@peertube/peertube-models'
import { t } from '@server/helpers/i18n.js'
import { getAdminAbuseUrl, getUserAbuseUrl } from '@server/lib/client-urls.js'
import { AccountModel } from '@server/models/account/account.js'
import { UserNotificationModel } from '@server/models/user/user-notification.js'
import {
MAbuseFull,
MAbuseMessage,
MAccountDefault,
MUserWithNotificationSetting,
UserNotificationModelForApi
} from '@server/types/models/index.js'
import { AbstractNotification } from '../common/abstract-notification.js'
type NewAbuseMessagePayload = {
abuse: MAbuseFull
message: MAbuseMessage
}
export abstract class AbstractNewAbuseMessage extends AbstractNotification<NewAbuseMessagePayload> {
protected messageAccount: MAccountDefault
async loadMessageAccount () {
this.messageAccount = await AccountModel.load(this.message.accountId)
}
getSetting (user: MUserWithNotificationSetting) {
return user.NotificationSetting.abuseNewMessage
}
createNotification (user: MUserWithNotificationSetting) {
const notification = UserNotificationModel.build<UserNotificationModelForApi>({
type: UserNotificationType.ABUSE_NEW_MESSAGE,
userId: user.id,
abuseId: this.abuse.id
})
notification.Abuse = this.abuse
return notification
}
protected createEmailFor (to: To, target: 'moderator' | 'reporter') {
const text = t('New message on report #{id}', to.language, { id: this.abuse.id })
const abuseUrl = target === 'moderator'
? getAdminAbuseUrl(this.abuse)
: getUserAbuseUrl(this.abuse)
const action = {
text: t('View report #{id}', to.language, { id: this.abuse.id }),
url: abuseUrl
}
return {
template: 'abuse-new-message',
to,
subject: text,
locals: {
abuseId: this.abuse.id,
abuseUrl,
messageAccountName: this.messageAccount.getDisplayName(),
messageText: this.message.message,
action
}
}
}
protected get abuse () {
return this.payload.abuse
}
protected get message () {
return this.payload.message
}
}

View File

@@ -0,0 +1,77 @@
import { AbuseState, UserNotificationType } from '@peertube/peertube-models'
import { t } from '@server/helpers/i18n.js'
import { logger } from '@server/helpers/logger.js'
import { getAbuseIdentifier } from '@server/lib/activitypub/url.js'
import { getUserAbuseUrl } from '@server/lib/client-urls.js'
import { UserNotificationModel } from '@server/models/user/user-notification.js'
import { UserModel } from '@server/models/user/user.js'
import { MAbuseFull, MUserDefault, MUserWithNotificationSetting, UserNotificationModelForApi } from '@server/types/models/index.js'
import { AbstractNotification } from '../common/abstract-notification.js'
export class AbuseStateChangeForReporter extends AbstractNotification<MAbuseFull> {
private user: MUserDefault
async prepare () {
const reporter = this.abuse.ReporterAccount
if (reporter.isLocal() !== true) return
this.user = await UserModel.loadByAccountActorId(this.abuse.ReporterAccount.Actor.id)
}
log () {
logger.info('Notifying reporter of abuse % of state change.', getAbuseIdentifier(this.abuse))
}
getSetting (user: MUserWithNotificationSetting) {
return user.NotificationSetting.abuseStateChange
}
getTargetUsers () {
if (!this.user) return []
return [ this.user ]
}
createNotification (user: MUserWithNotificationSetting) {
const notification = UserNotificationModel.build<UserNotificationModelForApi>({
type: UserNotificationType.ABUSE_STATE_CHANGE,
userId: user.id,
abuseId: this.abuse.id
})
notification.Abuse = this.abuse
return notification
}
createEmail (user: MUserWithNotificationSetting) {
const language = user.getLanguage()
const to = { email: user.email, language }
const text = this.abuse.state === AbuseState.ACCEPTED
? t('Report #{id} has been accepted', language, { id: this.abuse.id })
: t('Report #{id} has been rejected', language, { id: this.abuse.id })
const abuseUrl = getUserAbuseUrl(this.abuse)
const action = {
text: t('View report #{id}', language, { id: this.abuse.id }),
url: abuseUrl
}
return {
template: 'abuse-state-change',
to,
subject: text,
locals: {
action,
abuseId: this.abuse.id,
abuseUrl,
isAccepted: this.abuse.state === AbuseState.ACCEPTED
}
}
}
private get abuse () {
return this.payload
}
}

View File

@@ -0,0 +1,4 @@
export * from './abuse-state-change-for-reporter.js'
export * from './new-abuse-for-moderators.js'
export * from './new-abuse-message-for-reporter.js'
export * from './new-abuse-message-for-moderators.js'

View File

@@ -0,0 +1,123 @@
import { To, UserAbuse, UserNotificationType, UserRight } from '@peertube/peertube-models'
import { t } from '@server/helpers/i18n.js'
import { logger } from '@server/helpers/logger.js'
import { WEBSERVER } from '@server/initializers/constants.js'
import { getAbuseIdentifier } from '@server/lib/activitypub/url.js'
import { getAdminAbuseUrl } from '@server/lib/client-urls.js'
import { UserNotificationModel } from '@server/models/user/user-notification.js'
import { UserModel } from '@server/models/user/user.js'
import { MAbuseFull, MUserDefault, MUserWithNotificationSetting, UserNotificationModelForApi } from '@server/types/models/index.js'
import { AbstractNotification } from '../common/abstract-notification.js'
export type NewAbusePayload = { abuse: Pick<UserAbuse, 'reason' | 'video'>, abuseInstance: MAbuseFull, reporter: string }
export class NewAbuseForModerators extends AbstractNotification<NewAbusePayload> {
private moderators: MUserDefault[]
async prepare () {
this.moderators = await UserModel.listWithRight(UserRight.MANAGE_ABUSES)
}
log () {
logger.info('Notifying %s user/moderators of new abuse %s.', this.moderators.length, getAbuseIdentifier(this.payload.abuseInstance))
}
getSetting (user: MUserWithNotificationSetting) {
return user.NotificationSetting.abuseAsModerator
}
getTargetUsers () {
return this.moderators
}
createNotification (user: MUserWithNotificationSetting) {
const notification = UserNotificationModel.build<UserNotificationModelForApi>({
type: UserNotificationType.NEW_ABUSE_FOR_MODERATORS,
userId: user.id,
abuseId: this.payload.abuseInstance.id
})
notification.Abuse = this.payload.abuseInstance
return notification
}
createEmail (user: MUserWithNotificationSetting) {
const to = { email: user.email, language: user.getLanguage() }
const abuseInstance = this.payload.abuseInstance
if (abuseInstance.VideoAbuse) return this.createVideoAbuseEmail(to)
if (abuseInstance.VideoCommentAbuse) return this.createCommentAbuseEmail(to)
return this.createAccountAbuseEmail(to)
}
private createVideoAbuseEmail (to: To) {
const video = this.payload.abuseInstance.VideoAbuse.Video
const channel = video.VideoChannel
return {
template: 'video-abuse-new',
to,
subject: t('New video abuse report from {reporter}', to.language, { reporter: this.payload.reporter }),
locals: {
videoUrl: WEBSERVER.URL + video.getWatchStaticPath(),
isLocal: video.remote === false,
videoCreatedAt: new Date(video.createdAt).toLocaleString(),
videoPublishedAt: new Date(video.publishedAt).toLocaleString(),
videoName: video.name,
reason: this.payload.abuse.reason,
channelDisplayName: channel.getDisplayName(),
channelUrl: channel.getClientUrl(),
reporter: this.payload.reporter,
action: this.buildEmailAction(to)
}
}
}
private createCommentAbuseEmail (to: To) {
const comment = this.payload.abuseInstance.VideoCommentAbuse.VideoComment
return {
template: 'video-comment-abuse-new',
to,
subject: t('New comment abuse report from {reporter}', to.language, { reporter: this.payload.reporter }),
locals: {
commentUrl: WEBSERVER.URL + comment.getCommentStaticPath(),
videoName: comment.Video.name,
isLocal: comment.isLocal(),
commentCreatedAt: new Date(comment.createdAt).toLocaleString(),
reason: this.payload.abuse.reason,
flaggedAccount: this.payload.abuseInstance.FlaggedAccount.getDisplayName(),
reporter: this.payload.reporter,
action: this.buildEmailAction(to)
}
}
}
private createAccountAbuseEmail (to: To) {
const account = this.payload.abuseInstance.FlaggedAccount
const accountUrl = account.getClientUrl()
return {
template: 'account-abuse-new',
to,
subject: t('New account abuse report from {reporter}', to.language, { reporter: this.payload.reporter }),
locals: {
accountUrl,
accountDisplayName: account.getDisplayName(),
isLocal: account.isLocal(),
reason: this.payload.abuse.reason,
reporter: this.payload.reporter,
action: this.buildEmailAction(to)
}
}
}
private buildEmailAction (to: To) {
return {
text: t('View report #' + this.payload.abuseInstance.id, to.language),
url: getAdminAbuseUrl(this.payload.abuseInstance)
}
}
}

View File

@@ -0,0 +1,34 @@
import { UserRight } from '@peertube/peertube-models'
import { logger } from '@server/helpers/logger.js'
import { getAbuseIdentifier } from '@server/lib/activitypub/url.js'
import { UserModel } from '@server/models/user/user.js'
import { MUserDefault, MUserWithNotificationSetting } from '@server/types/models/index.js'
import { AbstractNewAbuseMessage } from './abstract-new-abuse-message.js'
export class NewAbuseMessageForModerators extends AbstractNewAbuseMessage {
private moderators: MUserDefault[]
async prepare () {
this.moderators = await UserModel.listWithRight(UserRight.MANAGE_ABUSES)
// Don't notify my own message
this.moderators = this.moderators.filter(m => m.Account.id !== this.message.accountId)
if (this.moderators.length === 0) return
await this.loadMessageAccount()
}
log () {
logger.info('Notifying moderators of new abuse message on %s.', getAbuseIdentifier(this.abuse))
}
getTargetUsers () {
return this.moderators
}
createEmail (user: MUserWithNotificationSetting) {
const to = { email: user.email, language: user.getLanguage() }
return this.createEmailFor(to, 'moderator')
}
}

View File

@@ -0,0 +1,38 @@
import { logger } from '@server/helpers/logger.js'
import { getAbuseIdentifier } from '@server/lib/activitypub/url.js'
import { UserModel } from '@server/models/user/user.js'
import { MUserDefault, MUserWithNotificationSetting } from '@server/types/models/index.js'
import { AbstractNewAbuseMessage } from './abstract-new-abuse-message.js'
export class NewAbuseMessageForReporter extends AbstractNewAbuseMessage {
private reporter: MUserDefault
async prepare () {
// Only notify our users
if (this.abuse.ReporterAccount.isLocal() !== true) return
await this.loadMessageAccount()
const reporter = await UserModel.loadByAccountActorId(this.abuse.ReporterAccount.Actor.id)
// Don't notify my own message
if (reporter.Account.id === this.message.accountId) return
this.reporter = reporter
}
log () {
logger.info('Notifying reporter of new abuse message on %s.', getAbuseIdentifier(this.abuse))
}
getTargetUsers () {
if (!this.reporter) return []
return [ this.reporter ]
}
createEmail (user: MUserWithNotificationSetting) {
const to = { email: user.email, language: user.getLanguage() }
return this.createEmailFor(to, 'reporter')
}
}

View File

@@ -0,0 +1,3 @@
export * from './new-auto-blacklist-for-moderators.js'
export * from './new-blacklist-for-owner.js'
export * from './unblacklist-for-owner.js'

View File

@@ -0,0 +1,67 @@
import { UserNotificationType, UserRight } from '@peertube/peertube-models'
import { tu } from '@server/helpers/i18n.js'
import { logger } from '@server/helpers/logger.js'
import { WEBSERVER } from '@server/initializers/constants.js'
import { videoAutoBlacklistUrl } from '@server/lib/client-urls.js'
import { UserNotificationModel } from '@server/models/user/user-notification.js'
import { UserModel } from '@server/models/user/user.js'
import { VideoChannelModel } from '@server/models/video/video-channel.js'
import {
MUserDefault,
MUserWithNotificationSetting,
MVideoBlacklistLightVideo,
UserNotificationModelForApi
} from '@server/types/models/index.js'
import { AbstractNotification } from '../common/abstract-notification.js'
export class NewAutoBlacklistForModerators extends AbstractNotification<MVideoBlacklistLightVideo> {
private moderators: MUserDefault[]
async prepare () {
this.moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_BLACKLIST)
}
log () {
logger.info('Notifying %s moderators of video auto-blacklist %s.', this.moderators.length, this.payload.Video.url)
}
getSetting (user: MUserWithNotificationSetting) {
return user.NotificationSetting.videoAutoBlacklistAsModerator
}
getTargetUsers () {
return this.moderators
}
createNotification (user: MUserWithNotificationSetting) {
const notification = UserNotificationModel.build<UserNotificationModelForApi>({
type: UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS,
userId: user.id,
videoBlacklistId: this.payload.id
})
notification.VideoBlacklist = this.payload
return notification
}
async createEmail (user: MUserWithNotificationSetting) {
const video = this.payload.Video
const channel = await VideoChannelModel.loadAndPopulateAccount(video.channelId)
return {
template: 'video-auto-blacklist-new',
to: { email: user.email, language: user.getLanguage() },
subject: tu('A new video is pending moderation', user),
locals: {
channelDisplayName: channel.getDisplayName(),
channelUrl: channel.getClientUrl(),
videoUrl: WEBSERVER.URL + video.getWatchStaticPath(),
videoName: video.name,
action: {
text: tu('Review video', user),
url: videoAutoBlacklistUrl
}
}
}
}
}

View File

@@ -0,0 +1,69 @@
import { UserNotificationType } from '@peertube/peertube-models'
import { tu } from '@server/helpers/i18n.js'
import { logger } from '@server/helpers/logger.js'
import { CONFIG } from '@server/initializers/config.js'
import { WEBSERVER } from '@server/initializers/constants.js'
import { UserNotificationModel } from '@server/models/user/user-notification.js'
import { UserModel } from '@server/models/user/user.js'
import {
MUserDefault,
MUserWithNotificationSetting,
MVideoBlacklistVideo,
UserNotificationModelForApi
} from '@server/types/models/index.js'
import { AbstractNotification } from '../common/abstract-notification.js'
export class NewBlacklistForOwner extends AbstractNotification<MVideoBlacklistVideo> {
private user: MUserDefault
async prepare () {
this.user = await UserModel.loadByVideoId(this.payload.videoId)
}
log () {
logger.info('Notifying user %s that its video %s has been blacklisted.', this.user.username, this.payload.Video.url)
}
getSetting (user: MUserWithNotificationSetting) {
return user.NotificationSetting.blacklistOnMyVideo
}
getTargetUsers () {
if (!this.user) return []
return [ this.user ]
}
createNotification (user: MUserWithNotificationSetting) {
const notification = UserNotificationModel.build<UserNotificationModelForApi>({
type: UserNotificationType.BLACKLIST_ON_MY_VIDEO,
userId: user.id,
videoBlacklistId: this.payload.id
})
notification.VideoBlacklist = this.payload
return notification
}
createEmail (user: MUserWithNotificationSetting) {
const to = { email: user.email, language: user.getLanguage() }
const videoName = this.payload.Video.name
const videoUrl = WEBSERVER.URL + this.payload.Video.getWatchStaticPath()
return {
template: 'video-owner-blacklist-new',
to,
subject: tu('Your video has been blocked', user),
locals: {
instanceName: CONFIG.INSTANCE.NAME,
videoName,
reason: this.payload.reason,
action: {
text: tu('View video', user),
url: videoUrl
}
}
}
}
}

View File

@@ -0,0 +1,63 @@
import { logger } from '@server/helpers/logger.js'
import { CONFIG } from '@server/initializers/config.js'
import { WEBSERVER } from '@server/initializers/constants.js'
import { UserModel } from '@server/models/user/user.js'
import { UserNotificationModel } from '@server/models/user/user-notification.js'
import { MUserDefault, MUserWithNotificationSetting, MVideoFullLight, UserNotificationModelForApi } from '@server/types/models/index.js'
import { UserNotificationType } from '@peertube/peertube-models'
import { AbstractNotification } from '../common/abstract-notification.js'
import { tu } from '@server/helpers/i18n.js'
export class UnblacklistForOwner extends AbstractNotification<MVideoFullLight> {
private user: MUserDefault
async prepare () {
this.user = await UserModel.loadByVideoId(this.payload.id)
}
log () {
logger.info('Notifying user %s that its video %s has been unblacklisted.', this.user.username, this.payload.url)
}
getSetting (user: MUserWithNotificationSetting) {
return user.NotificationSetting.blacklistOnMyVideo
}
getTargetUsers () {
if (!this.user) return []
return [ this.user ]
}
createNotification (user: MUserWithNotificationSetting) {
const notification = UserNotificationModel.build<UserNotificationModelForApi>({
type: UserNotificationType.UNBLACKLIST_ON_MY_VIDEO,
userId: user.id,
videoId: this.payload.id
})
notification.Video = this.payload
return notification
}
createEmail (user: MUserWithNotificationSetting) {
const to = { email: user.email, language: user.getLanguage() }
const video = this.payload
const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
return {
template: 'video-owner-unblacklist',
to,
subject: tu('Your video has been unblocked', user),
locals: {
instanceName: CONFIG.INSTANCE.NAME,
videoName: video.name,
action: {
text: tu('View video', user),
url: videoUrl
}
}
}
}
}

View File

@@ -0,0 +1 @@
export * from './video-transcription-generated-for-owner.js'

View File

@@ -0,0 +1,71 @@
import { UserNotificationType } from '@peertube/peertube-models'
import { t } from '@server/helpers/i18n.js'
import { logger } from '@server/helpers/logger.js'
import { VIDEO_LANGUAGES, WEBSERVER } from '@server/initializers/constants.js'
import { UserNotificationModel } from '@server/models/user/user-notification.js'
import { UserModel } from '@server/models/user/user.js'
import { MUserDefault, MUserWithNotificationSetting, MVideoCaptionVideo, UserNotificationModelForApi } from '@server/types/models/index.js'
import { AbstractNotification } from '../common/abstract-notification.js'
export class VideoTranscriptionGeneratedForOwner extends AbstractNotification<MVideoCaptionVideo> {
private user: MUserDefault
async prepare () {
this.user = await UserModel.loadByVideoId(this.payload.videoId)
}
log () {
logger.info(
'Notifying user %s the transcription %s of video %s is generated.',
this.user.username,
this.payload.language,
this.payload.Video.url
)
}
getSetting (user: MUserWithNotificationSetting) {
return user.NotificationSetting.myVideoTranscriptionGenerated
}
getTargetUsers () {
if (!this.user) return []
return [ this.user ]
}
createNotification (user: MUserWithNotificationSetting) {
const notification = UserNotificationModel.build<UserNotificationModelForApi>({
type: UserNotificationType.MY_VIDEO_TRANSCRIPTION_GENERATED,
userId: user.id,
videoCaptionId: this.payload.id
})
notification.VideoCaption = this.payload
return notification
}
createEmail (user: MUserWithNotificationSetting) {
const userLanguage = user.getLanguage()
const to = { email: user.email, language: userLanguage }
const video = this.payload.Video
const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
const language = VIDEO_LANGUAGES[this.payload.language]
return {
to,
subject: t('Transcription of your video has been generated', userLanguage),
text: t('Transcription in {language} of your video {videoName} has been generated.', userLanguage, {
language,
videoName: video.name
}),
locals: {
action: {
text: t('View video', userLanguage),
url: videoUrl
}
}
}
}
}

View File

@@ -0,0 +1,68 @@
import { UserNotificationSettingValue, UserNotificationType } from '@peertube/peertube-models'
import { t } from '@server/helpers/i18n.js'
import { logger } from '@server/helpers/logger.js'
import { UserModel } from '@server/models/user/user.js'
import { MUserDefault, MUserWithNotificationSetting } from '@server/types/models/index.js'
import { AbstractNotification } from '../common/abstract-notification.js'
import { buildCollaborateToChannelNotification, NotificationCollaboratePayload } from './collaborate-to-channel-utils.js'
export class AcceptedToCollaborateToChannel extends AbstractNotification<NotificationCollaboratePayload> {
private user: MUserDefault
async prepare () {
this.user = await UserModel.loadByAccountId(this.payload.channel.accountId)
}
log () {
logger.info(
`Notifying user ${this.user.username} of accepted invitation to collaborate on channel ${this.payload.channel.Actor.getIdentifier()}`
)
}
isDisabled () {
return false
}
getSetting (_user: MUserWithNotificationSetting) {
// Always notify
return UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL
}
getTargetUsers () {
return [ this.user ]
}
createNotification (user: MUserWithNotificationSetting) {
return buildCollaborateToChannelNotification({
user,
payload: this.payload,
notificationType: UserNotificationType.ACCEPTED_TO_COLLABORATE_TO_CHANNEL
})
}
// ---------------------------------------------------------------------------
createEmail (user: MUserWithNotificationSetting) {
const userLanguage = user.getLanguage()
const to = { email: user.email, language: userLanguage }
const { channel, collaborator } = this.payload
const text = t('{collaboratorName} accepted your invitation to become a collaborator of {channelName}', userLanguage, {
collaboratorName: collaborator.Account.getDisplayName(),
channelName: channel.getDisplayName()
})
return {
to,
subject: text,
text,
locals: {
action: {
text: t('Manage your channel', userLanguage),
url: channel.getClientManageUrl()
}
}
}
}
}

View File

@@ -0,0 +1,31 @@
import { UserNotificationType_Type } from '@peertube/peertube-models'
import { UserNotificationModel } from '@server/models/user/user-notification.js'
import { MChannelAccountDefault, MChannelCollaboratorAccount } from '@server/types/models/index.js'
import { MUserId, UserNotificationModelForApi } from '@server/types/models/user/index.js'
export type NotificationCollaboratePayload = {
collaborator: MChannelCollaboratorAccount
channel: MChannelAccountDefault
}
export function buildCollaborateToChannelNotification (options: {
user: MUserId
payload: NotificationCollaboratePayload
notificationType: UserNotificationType_Type
}): UserNotificationModelForApi {
const { user, payload, notificationType } = options
const notification = UserNotificationModel.build<UserNotificationModelForApi>({
type: notificationType,
userId: user.id,
channelCollaboratorId: payload.collaborator.id
})
notification.VideoChannelCollaborator = Object.assign(payload.collaborator, {
Account: payload.collaborator.Account,
Channel: payload.channel
})
return notification
}

View File

@@ -0,0 +1,76 @@
import { UserNotificationSettingValue, UserNotificationType } from '@peertube/peertube-models'
import { t } from '@server/helpers/i18n.js'
import { logger } from '@server/helpers/logger.js'
import { WEBSERVER } from '@server/initializers/constants.js'
import { AccountBlocklistModel } from '@server/models/account/account-blocklist.js'
import { UserModel } from '@server/models/user/user.js'
import { MUserDefault, MUserWithNotificationSetting } from '@server/types/models/index.js'
import { AbstractNotification } from '../common/abstract-notification.js'
import { buildCollaborateToChannelNotification, NotificationCollaboratePayload } from './collaborate-to-channel-utils.js'
export class InvitedToCollaborateToChannel extends AbstractNotification<NotificationCollaboratePayload> {
private user: MUserDefault
private channelAccountMuted = false
async prepare () {
this.user = await UserModel.loadByAccountId(this.payload.collaborator.accountId)
const hash = await AccountBlocklistModel.isAccountMutedByAccounts([ this.user.Account.id ], this.payload.channel.accountId)
this.channelAccountMuted = hash[this.user.Account.id] === true
}
log () {
logger.info(
`Notifying user ${this.user.username} of invitation to collaborate on channel ${this.payload.channel.Actor.getIdentifier()}`
)
}
isDisabled () {
return false
}
getSetting (_user: MUserWithNotificationSetting) {
if (this.channelAccountMuted) return UserNotificationSettingValue.NONE
// Always notify
return UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL
}
getTargetUsers () {
return [ this.user ]
}
createNotification (user: MUserWithNotificationSetting) {
return buildCollaborateToChannelNotification({
user,
payload: this.payload,
notificationType: UserNotificationType.INVITED_TO_COLLABORATE_TO_CHANNEL
})
}
// ---------------------------------------------------------------------------
createEmail (user: MUserWithNotificationSetting) {
const userLanguage = user.getLanguage()
const to = { email: user.email, language: userLanguage }
const { channel } = this.payload
const text = t('{channelOwner} invited you to become a collaborator of channel {channelName}', userLanguage, {
channelOwner: channel.Account.getDisplayName(),
channelName: channel.getDisplayName()
})
return {
to,
subject: text,
text,
locals: {
action: {
text: t('Review the invitation', userLanguage),
url: WEBSERVER.URL + '/my-account/notifications'
}
}
}
}
}

View File

@@ -0,0 +1,68 @@
import { UserNotificationSettingValue, UserNotificationType } from '@peertube/peertube-models'
import { t } from '@server/helpers/i18n.js'
import { logger } from '@server/helpers/logger.js'
import { UserModel } from '@server/models/user/user.js'
import { MUserDefault, MUserWithNotificationSetting } from '@server/types/models/index.js'
import { AbstractNotification } from '../common/abstract-notification.js'
import { buildCollaborateToChannelNotification, NotificationCollaboratePayload } from './collaborate-to-channel-utils.js'
export class RefusedToCollaborateToChannel extends AbstractNotification<NotificationCollaboratePayload> {
private user: MUserDefault
async prepare () {
this.user = await UserModel.loadByAccountId(this.payload.channel.accountId)
}
log () {
logger.info(
`Notifying user ${this.user.username} of refused invitation to collaborate on channel ${this.payload.channel.Actor.getIdentifier()}`
)
}
isDisabled () {
return false
}
getSetting (_user: MUserWithNotificationSetting) {
// Always notify
return UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL
}
getTargetUsers () {
return [ this.user ]
}
createNotification (user: MUserWithNotificationSetting) {
return buildCollaborateToChannelNotification({
user,
payload: this.payload,
notificationType: UserNotificationType.REFUSED_TO_COLLABORATE_TO_CHANNEL
})
}
// ---------------------------------------------------------------------------
createEmail (user: MUserWithNotificationSetting) {
const userLanguage = user.getLanguage()
const to = { email: user.email, language: userLanguage }
const { channel, collaborator } = this.payload
const text = t('{collaboratorName} refused your invitation to become a collaborator of {channelName}', userLanguage, {
collaboratorName: collaborator.Account.getDisplayName(),
channelName: channel.getDisplayName()
})
return {
to,
subject: text,
text,
locals: {
action: {
text: t('Manage your channel', userLanguage),
url: channel.getClientManageUrl()
}
}
}
}
}

View File

@@ -0,0 +1,121 @@
import { UserNotificationSettingValue, UserNotificationType } from '@peertube/peertube-models'
import { tu } from '@server/helpers/i18n.js'
import { logger } from '@server/helpers/logger.js'
import { toSafeHtml } from '@server/helpers/markdown.js'
import { WEBSERVER } from '@server/initializers/constants.js'
import { AccountBlocklistModel } from '@server/models/account/account-blocklist.js'
import { getServerActor } from '@server/models/application/application.js'
import { ServerBlocklistModel } from '@server/models/server/server-blocklist.js'
import { UserNotificationModel } from '@server/models/user/user-notification.js'
import { UserModel } from '@server/models/user/user.js'
import {
MCommentOwnerVideo,
MUserDefault,
MUserNotifSettingAccount,
MUserWithNotificationSetting,
UserNotificationModelForApi
} from '@server/types/models/index.js'
import { AbstractNotification } from '../common/index.js'
export class CommentMention extends AbstractNotification<MCommentOwnerVideo, MUserNotifSettingAccount> {
private users: MUserDefault[]
private serverAccountId: number
private accountMutedHash: { [id: number]: boolean }
private instanceMutedHash: { [id: number]: boolean }
isDisabled () {
return this.payload.heldForReview === true
}
async prepare () {
const extractedUsernames = this.payload.extractMentions()
logger.debug(
'Extracted %d username from comment %s.',
extractedUsernames.length,
this.payload.url,
{ usernames: extractedUsernames, text: this.payload.text }
)
this.users = await UserModel.listByUsernames(extractedUsernames)
if (this.payload.Video.isLocal()) {
const userException = await UserModel.loadByVideoId(this.payload.videoId)
this.users = this.users.filter(u => u.id !== userException.id)
}
// Don't notify if I mentioned myself
this.users = this.users.filter(u => u.Account.id !== this.payload.accountId)
if (this.users.length === 0) return
this.serverAccountId = (await getServerActor()).Account.id
const sourceAccounts = this.users.map(u => u.Account.id).concat([ this.serverAccountId ])
this.accountMutedHash = await AccountBlocklistModel.isAccountMutedByAccounts(sourceAccounts, this.payload.accountId)
this.instanceMutedHash = await ServerBlocklistModel.isServerMutedByAccounts(sourceAccounts, this.payload.Account.Actor.serverId)
}
log () {
logger.info('Notifying %d users of new comment mention %s.', this.users.length, this.payload.url)
}
getSetting (user: MUserNotifSettingAccount) {
const accountId = user.Account.id
if (
this.accountMutedHash[accountId] === true || this.instanceMutedHash[accountId] === true ||
this.accountMutedHash[this.serverAccountId] === true || this.instanceMutedHash[this.serverAccountId] === true
) {
return UserNotificationSettingValue.NONE
}
return user.NotificationSetting.commentMention
}
getTargetUsers () {
return this.users
}
createNotification (user: MUserWithNotificationSetting) {
const notification = UserNotificationModel.build<UserNotificationModelForApi>({
type: UserNotificationType.COMMENT_MENTION,
userId: user.id,
commentId: this.payload.id
})
notification.VideoComment = this.payload
return notification
}
createEmail (user: MUserWithNotificationSetting) {
const to = { email: user.email, language: user.getLanguage() }
const comment = this.payload
const accountName = comment.Account.getDisplayName()
const video = comment.Video
const videoUrl = WEBSERVER.URL + comment.Video.getWatchStaticPath()
const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath()
const commentHtml = toSafeHtml(comment.text)
return {
template: 'video-comment-mention',
to,
subject: tu('Mention on video {videoName}', user, { videoName: video.name }),
locals: {
comment,
commentHtml,
video,
videoUrl,
accountName,
accountUrl: comment.Account.getClientUrl(),
action: {
text: tu('View comment', user),
url: commentUrl
}
}
}
}
}

View File

@@ -0,0 +1,2 @@
export * from './comment-mention.js'
export * from './new-comment-for-video-owner.js'

View File

@@ -0,0 +1,88 @@
import { UserNotificationType } from '@peertube/peertube-models'
import { t } from '@server/helpers/i18n.js'
import { logger } from '@server/helpers/logger.js'
import { toSafeHtml } from '@server/helpers/markdown.js'
import { WEBSERVER } from '@server/initializers/constants.js'
import { isBlockedByServerOrAccount } from '@server/lib/blocklist.js'
import { UserNotificationModel } from '@server/models/user/user-notification.js'
import { UserModel } from '@server/models/user/user.js'
import { MCommentOwnerVideo, MUserDefault, MUserWithNotificationSetting, UserNotificationModelForApi } from '@server/types/models/index.js'
import { AbstractNotification } from '../common/abstract-notification.js'
export class NewCommentForVideoOwner extends AbstractNotification<MCommentOwnerVideo> {
private user: MUserDefault
async prepare () {
this.user = await UserModel.loadByVideoId(this.payload.videoId)
}
log () {
logger.info('Notifying owner of a video %s of new comment %s.', this.user.username, this.payload.url)
}
isDisabled () {
if (this.payload.Video.isLocal() === false) return true
// Not our user or user comments its own video
if (!this.user || this.payload.Account.userId === this.user.id) return true
return isBlockedByServerOrAccount(this.payload.Account, this.user.Account)
}
getSetting (user: MUserWithNotificationSetting) {
return user.NotificationSetting.newCommentOnMyVideo
}
getTargetUsers () {
if (!this.user) return []
return [ this.user ]
}
createNotification (user: MUserWithNotificationSetting) {
const notification = UserNotificationModel.build<UserNotificationModelForApi>({
type: UserNotificationType.NEW_COMMENT_ON_MY_VIDEO,
userId: user.id,
commentId: this.payload.id
})
notification.VideoComment = this.payload
return notification
}
createEmail (user: MUserWithNotificationSetting) {
const to = { email: user.email, language: user.getLanguage() }
const comment = this.payload
const video = comment.Video
const videoUrl = WEBSERVER.URL + comment.Video.getWatchStaticPath()
const commentHtml = toSafeHtml(comment.text)
const commentUrl = comment.heldForReview
? WEBSERVER.URL + comment.getCommentUserReviewPath()
: WEBSERVER.URL + comment.getCommentStaticPath()
return {
template: 'video-comment-new',
to,
subject: t('New comment on your video', to.language),
locals: {
accountName: this.payload.Account.getDisplayName(),
accountUrl: this.payload.Account.getClientUrl(),
comment: this.payload,
commentHtml,
video,
videoUrl,
requiresApproval: this.payload.heldForReview,
action: {
text: comment.heldForReview
? t('Review comment', to.language)
: t('View comment', to.language),
url: commentUrl
}
}
}
}
}

View File

@@ -0,0 +1,20 @@
import { EmailPayload, UserNotificationSettingValueType } from '@peertube/peertube-models'
import { MUserWithNotificationSetting, UserNotificationModelForApi } from '@server/types/models/index.js'
export abstract class AbstractNotification<T, U = MUserWithNotificationSetting> {
constructor (protected readonly payload: T) {
}
abstract prepare (): Promise<void>
abstract log (): void
abstract getSetting (user: U): UserNotificationSettingValueType
abstract getTargetUsers (): U[]
abstract createNotification (user: U): UserNotificationModelForApi
abstract createEmail (to: U): EmailPayload | Promise<EmailPayload>
isDisabled (): boolean | Promise<boolean> {
return false
}
}

View File

@@ -0,0 +1 @@
export * from './abstract-notification.js'

View File

@@ -0,0 +1,61 @@
import { UserNotificationType, UserRight } from '@peertube/peertube-models'
import { t } from '@server/helpers/i18n.js'
import { logger } from '@server/helpers/logger.js'
import { instanceFollowingUrl } from '@server/lib/client-urls.js'
import { UserNotificationModel } from '@server/models/user/user-notification.js'
import { UserModel } from '@server/models/user/user.js'
import { MActorFollowFull, MUserDefault, MUserWithNotificationSetting, UserNotificationModelForApi } from '@server/types/models/index.js'
import { AbstractNotification } from '../common/abstract-notification.js'
export class AutoFollowForInstance extends AbstractNotification<MActorFollowFull> {
private admins: MUserDefault[]
async prepare () {
this.admins = await UserModel.listWithRight(UserRight.MANAGE_SERVER_FOLLOW)
}
log () {
logger.info('Notifying %d administrators of auto instance following: %s.', this.admins.length, this.actorFollow.ActorFollowing.url)
}
getSetting (user: MUserWithNotificationSetting) {
return user.NotificationSetting.autoInstanceFollowing
}
getTargetUsers () {
return this.admins
}
createNotification (user: MUserWithNotificationSetting) {
const notification = UserNotificationModel.build<UserNotificationModelForApi>({
type: UserNotificationType.AUTO_INSTANCE_FOLLOWING,
userId: user.id,
actorFollowId: this.actorFollow.id
})
notification.ActorFollow = this.actorFollow
return notification
}
createEmail (user: MUserWithNotificationSetting) {
const to = { email: user.email, language: user.getLanguage() }
const subscription = this.actorFollow.ActorFollowing
return {
to,
subject: t('Auto platform follow', to.language),
text: t('Your platform automatically followed {identifier}', to.language, { identifier: subscription.getIdentifier() }),
locals: {
action: {
text: t('View subscription', to.language),
url: instanceFollowingUrl
}
}
}
}
private get actorFollow () {
return this.payload
}
}

View File

@@ -0,0 +1,74 @@
import { UserNotificationType, UserRight } from '@peertube/peertube-models'
import { t } from '@server/helpers/i18n.js'
import { logger } from '@server/helpers/logger.js'
import { CONFIG } from '@server/initializers/config.js'
import { WEBSERVER } from '@server/initializers/constants.js'
import { isBlockedByServerOrAccount } from '@server/lib/blocklist.js'
import { UserNotificationModel } from '@server/models/user/user-notification.js'
import { UserModel } from '@server/models/user/user.js'
import { MActorFollowFull, MUserDefault, MUserWithNotificationSetting, UserNotificationModelForApi } from '@server/types/models/index.js'
import { AbstractNotification } from '../common/abstract-notification.js'
export class FollowForInstance extends AbstractNotification<MActorFollowFull> {
private admins: MUserDefault[]
async prepare () {
this.admins = await UserModel.listWithRight(UserRight.MANAGE_SERVER_FOLLOW)
}
isDisabled () {
const follower = Object.assign(this.actorFollow.ActorFollower.Account, { Actor: this.actorFollow.ActorFollower })
return isBlockedByServerOrAccount(follower)
}
log () {
logger.info('Notifying %d administrators of new instance follower: %s.', this.admins.length, this.actorFollow.ActorFollower.url)
}
getSetting (user: MUserWithNotificationSetting) {
return user.NotificationSetting.newInstanceFollower
}
getTargetUsers () {
return this.admins
}
createNotification (user: MUserWithNotificationSetting) {
const notification = UserNotificationModel.build<UserNotificationModelForApi>({
type: UserNotificationType.NEW_INSTANCE_FOLLOWER,
userId: user.id,
actorFollowId: this.actorFollow.id
})
notification.ActorFollow = this.actorFollow
return notification
}
createEmail (user: MUserWithNotificationSetting) {
const to = { email: user.email, language: user.getLanguage() }
const language = user.getLanguage()
const context = { instanceName: CONFIG.INSTANCE.NAME, handle: this.actorFollow.ActorFollower.getIdentifier() }
const text = this.actorFollow.state === 'pending'
? t('{instanceName} has a new follower, {handle}, awaiting manual approval.', language, context)
: t('{instanceName} has a new follower: {handle}.', language, context)
return {
to,
subject: t('New follower for {instanceName}', language, { instanceName: CONFIG.INSTANCE.NAME }),
text,
locals: {
action: {
text: t('Review followers', language),
url: WEBSERVER.URL + '/admin/follows/followers-list'
}
}
}
}
private get actorFollow () {
return this.payload
}
}

View File

@@ -0,0 +1,91 @@
import { UserNotificationType } from '@peertube/peertube-models'
import { t } from '@server/helpers/i18n.js'
import { logger } from '@server/helpers/logger.js'
import { isBlockedByServerOrAccount } from '@server/lib/blocklist.js'
import { UserNotificationModel } from '@server/models/user/user-notification.js'
import { UserModel } from '@server/models/user/user.js'
import { MActorFollowFull, MUserDefault, MUserWithNotificationSetting, UserNotificationModelForApi } from '@server/types/models/index.js'
import { AbstractNotification } from '../common/abstract-notification.js'
export class FollowForUser extends AbstractNotification<MActorFollowFull> {
private followType: 'account' | 'channel'
private user: MUserDefault
async prepare () {
// Account follows one of our account?
this.followType = 'channel'
this.user = await UserModel.loadByChannelActorId(this.actorFollow.ActorFollowing.id)
// Account follows one of our channel?
if (!this.user) {
this.user = await UserModel.loadByAccountActorId(this.actorFollow.ActorFollowing.id)
this.followType = 'account'
}
}
async isDisabled () {
if (this.payload.ActorFollowing.isLocal() === false) return true
const followerAccount = this.actorFollow.ActorFollower.Account
const followerAccountWithActor = Object.assign(followerAccount, { Actor: this.actorFollow.ActorFollower })
return isBlockedByServerOrAccount(followerAccountWithActor, this.user.Account)
}
log () {
logger.info('Notifying user %s of new follower: %s.', this.user.username, this.actorFollow.ActorFollower.Account.getDisplayName())
}
getSetting (user: MUserWithNotificationSetting) {
return user.NotificationSetting.newFollow
}
getTargetUsers () {
if (!this.user) return []
return [ this.user ]
}
createNotification (user: MUserWithNotificationSetting) {
const notification = UserNotificationModel.build<UserNotificationModelForApi>({
type: UserNotificationType.NEW_FOLLOW,
userId: user.id,
actorFollowId: this.actorFollow.id
})
notification.ActorFollow = this.actorFollow
return notification
}
createEmail (user: MUserWithNotificationSetting) {
const to = { email: user.email, language: user.getLanguage() }
const following = this.actorFollow.ActorFollowing
const follower = this.actorFollow.ActorFollower
const followingAccountOrChannel = Object.assign(following.VideoChannel || following.Account, { Actor: following })
const followerAccountOrChannel = Object.assign(follower.VideoChannel || follower.Account, { Actor: follower })
const followingName = followingAccountOrChannel.getDisplayName()
return {
template: 'follower-on-channel',
to,
subject: this.followType === 'account'
? t('New follower on your account {followingName}', to.language, { followingName })
: t('New follower on your channel {followingName}', to.language, { followingName }),
locals: {
followerName: follower.Account.getDisplayName(),
followerUrl: followerAccountOrChannel.getClientUrl(),
followingUrl: followingAccountOrChannel.getClientUrl(),
followingName,
accountFollowType: this.followType === 'account'
}
}
}
private get actorFollow () {
return this.payload
}
}

View File

@@ -0,0 +1,3 @@
export * from './auto-follow-for-instance.js'
export * from './follow-for-instance.js'
export * from './follow-for-user.js'

View File

@@ -0,0 +1,8 @@
export * from './abuse/index.js'
export * from './blacklist/index.js'
export * from './caption/index.js'
export * from './comment/index.js'
export * from './common/index.js'
export * from './follow/index.js'
export * from './instance/index.js'
export * from './video-publication/index.js'

View File

@@ -0,0 +1,60 @@
import { UserNotificationType, UserRight } from '@peertube/peertube-models'
import { t } from '@server/helpers/i18n.js'
import { logger } from '@server/helpers/logger.js'
import { CONFIG } from '@server/initializers/config.js'
import { adminUsersListUrl } from '@server/lib/client-urls.js'
import { UserNotificationModel } from '@server/models/user/user-notification.js'
import { UserModel } from '@server/models/user/user.js'
import { MUserDefault, MUserWithNotificationSetting, UserNotificationModelForApi } from '@server/types/models/index.js'
import { AbstractNotification } from '../common/abstract-notification.js'
export class DirectRegistrationForModerators extends AbstractNotification<MUserDefault> {
private moderators: MUserDefault[]
async prepare () {
this.moderators = await UserModel.listWithRight(UserRight.MANAGE_USERS)
}
log () {
logger.info('Notifying %s moderators of new user registration of %s.', this.moderators.length, this.payload.username)
}
getSetting (user: MUserWithNotificationSetting) {
return user.NotificationSetting.newUserRegistration
}
getTargetUsers () {
return this.moderators
}
createNotification (user: MUserWithNotificationSetting) {
const notification = UserNotificationModel.build<UserNotificationModelForApi>({
type: UserNotificationType.NEW_USER_REGISTRATION,
userId: user.id,
accountId: this.payload.Account.id
})
notification.Account = this.payload.Account
return notification
}
createEmail (user: MUserWithNotificationSetting) {
const to = { email: user.email, language: user.getLanguage() }
return {
template: 'user-registered',
to,
subject: t('A new user registered on {instanceName}', to.language, { instanceName: CONFIG.INSTANCE.NAME }),
locals: {
userUsername: this.payload.username,
userEmail: this.payload.email,
userPendingEmail: this.payload.pendingEmail,
accountUrl: this.payload.Account.getClientUrl(),
action: {
type: t('View users', to.language),
url: adminUsersListUrl
}
}
}
}
}

View File

@@ -0,0 +1,4 @@
export * from './new-peertube-version-for-admins.js'
export * from './new-plugin-version-for-admins.js'
export * from './direct-registration-for-moderators.js'
export * from './registration-request-for-moderators.js'

View File

@@ -0,0 +1,57 @@
import { UserNotificationType, UserRight } from '@peertube/peertube-models'
import { t } from '@server/helpers/i18n.js'
import { logger } from '@server/helpers/logger.js'
import { UserNotificationModel } from '@server/models/user/user-notification.js'
import { UserModel } from '@server/models/user/user.js'
import { MApplication, MUserDefault, MUserWithNotificationSetting, UserNotificationModelForApi } from '@server/types/models/index.js'
import { AbstractNotification } from '../common/abstract-notification.js'
export type NewPeerTubeVersionForAdminsPayload = {
application: MApplication
latestVersion: string
}
export class NewPeerTubeVersionForAdmins extends AbstractNotification<NewPeerTubeVersionForAdminsPayload> {
private admins: MUserDefault[]
async prepare () {
// Use the debug right to know who is an administrator
this.admins = await UserModel.listWithRight(UserRight.MANAGE_DEBUG)
}
log () {
logger.info('Notifying %s admins of new PeerTube version %s.', this.admins.length, this.payload.latestVersion)
}
getSetting (user: MUserWithNotificationSetting) {
return user.NotificationSetting.newPeerTubeVersion
}
getTargetUsers () {
return this.admins
}
createNotification (user: MUserWithNotificationSetting) {
const notification = UserNotificationModel.build<UserNotificationModelForApi>({
type: UserNotificationType.NEW_PEERTUBE_VERSION,
userId: user.id,
applicationId: this.payload.application.id
})
notification.Application = this.payload.application
return notification
}
createEmail (user: MUserWithNotificationSetting) {
const to = { email: user.email, language: user.getLanguage() }
return {
to,
template: 'peertube-version-new',
subject: t('A new PeerTube version is available: {latestVersion}', to.language, { latestVersion: this.payload.latestVersion }),
locals: {
latestVersion: this.payload.latestVersion
}
}
}
}

View File

@@ -0,0 +1,68 @@
import { PluginType, UserNotificationType, UserRight } from '@peertube/peertube-models'
import { t } from '@server/helpers/i18n.js'
import { logger } from '@server/helpers/logger.js'
import { getPluginUrl } from '@server/lib/client-urls.js'
import { UserNotificationModel } from '@server/models/user/user-notification.js'
import { UserModel } from '@server/models/user/user.js'
import { MPlugin, MUserDefault, MUserWithNotificationSetting, UserNotificationModelForApi } from '@server/types/models/index.js'
import { AbstractNotification } from '../common/abstract-notification.js'
export class NewPluginVersionForAdmins extends AbstractNotification<MPlugin> {
private admins: MUserDefault[]
async prepare () {
// Use the debug right to know who is an administrator
this.admins = await UserModel.listWithRight(UserRight.MANAGE_DEBUG)
}
log () {
logger.info('Notifying %s admins of new PeerTube version %s.', this.admins.length, this.payload.latestVersion)
}
getSetting (user: MUserWithNotificationSetting) {
return user.NotificationSetting.newPluginVersion
}
getTargetUsers () {
return this.admins
}
createNotification (user: MUserWithNotificationSetting) {
const notification = UserNotificationModel.build<UserNotificationModelForApi>({
type: UserNotificationType.NEW_PLUGIN_VERSION,
userId: user.id,
pluginId: this.plugin.id
})
notification.Plugin = this.plugin
return notification
}
createEmail (user: MUserWithNotificationSetting) {
const to = { email: user.email, language: user.getLanguage() }
const language = user.getLanguage()
const pluginUrl = getPluginUrl(this.plugin.type)
const context = { pluginName: this.plugin.name, latestVersion: this.plugin.latestVersion }
return {
to,
template: 'plugin-version-new',
subject: this.plugin.type === PluginType.PLUGIN
? t('A new version of the plugin {pluginName} is available: {latestVersion}', language, context)
: t('A new version of the theme {pluginName} is available: {latestVersion}', language, context),
locals: {
pluginName: this.plugin.name,
latestVersion: this.plugin.latestVersion,
isPlugin: this.plugin.type === PluginType.PLUGIN,
pluginUrl
}
}
}
private get plugin () {
return this.payload
}
}

View File

@@ -0,0 +1,59 @@
import { UserNotificationType, UserRight } from '@peertube/peertube-models'
import { t } from '@server/helpers/i18n.js'
import { logger } from '@server/helpers/logger.js'
import { CONFIG } from '@server/initializers/config.js'
import { adminRegistrationsListUrl } from '@server/lib/client-urls.js'
import { UserNotificationModel } from '@server/models/user/user-notification.js'
import { UserModel } from '@server/models/user/user.js'
import { MRegistration, MUserDefault, MUserWithNotificationSetting, UserNotificationModelForApi } from '@server/types/models/index.js'
import { AbstractNotification } from '../common/abstract-notification.js'
export class RegistrationRequestForModerators extends AbstractNotification<MRegistration> {
private moderators: MUserDefault[]
async prepare () {
this.moderators = await UserModel.listWithRight(UserRight.MANAGE_REGISTRATIONS)
}
log () {
logger.info('Notifying %s moderators of new user registration request of %s.', this.moderators.length, this.payload.username)
}
getSetting (user: MUserWithNotificationSetting) {
return user.NotificationSetting.newUserRegistration
}
getTargetUsers () {
return this.moderators
}
createNotification (user: MUserWithNotificationSetting) {
const notification = UserNotificationModel.build<UserNotificationModelForApi>({
type: UserNotificationType.NEW_USER_REGISTRATION_REQUEST,
userId: user.id,
userRegistrationId: this.payload.id
})
notification.UserRegistration = this.payload
return notification
}
createEmail (user: MUserWithNotificationSetting) {
const to = { email: user.email, language: user.getLanguage() }
const language = user.getLanguage()
return {
template: 'user-registration-request',
to,
subject: t('A new user wants to register: {username}', to.language, { username: this.payload.username }),
locals: {
registration: this.payload,
instanceName: CONFIG.INSTANCE.NAME,
action: {
url: adminRegistrationsListUrl,
text: t('View registration request', language)
}
}
}
}
}

View File

@@ -0,0 +1,61 @@
import { logger } from '@server/helpers/logger.js'
import { t } from '@server/helpers/i18n.js'
import { WEBSERVER } from '@server/initializers/constants.js'
import { UserModel } from '@server/models/user/user.js'
import { UserNotificationModel } from '@server/models/user/user-notification.js'
import { MUserDefault, MUserWithNotificationSetting, MVideoFullLight, UserNotificationModelForApi } from '@server/types/models/index.js'
import { UserNotificationType } from '@peertube/peertube-models'
import { AbstractNotification } from '../common/abstract-notification.js'
export abstract class AbstractOwnedVideoPublication extends AbstractNotification<MVideoFullLight> {
protected user: MUserDefault
async prepare () {
this.user = await UserModel.loadByVideoId(this.payload.id)
}
log () {
logger.info('Notifying user %s of the publication of its video %s.', this.user.username, this.payload.url)
}
getSetting (user: MUserWithNotificationSetting) {
return user.NotificationSetting.myVideoPublished
}
getTargetUsers () {
if (!this.user) return []
return [ this.user ]
}
createNotification (user: MUserWithNotificationSetting) {
const notification = UserNotificationModel.build<UserNotificationModelForApi>({
type: UserNotificationType.MY_VIDEO_PUBLISHED,
userId: user.id,
videoId: this.payload.id
})
notification.Video = this.payload
return notification
}
createEmail (user: MUserWithNotificationSetting) {
const to = { email: user.email, language: user.getLanguage() }
const language = user.getLanguage()
const videoUrl = WEBSERVER.URL + this.payload.getWatchStaticPath()
return {
to,
subject: t('Your video has been published', language),
text: t('Your video {videoName} has been published.', language, { videoName: this.payload.name }),
locals: {
title: t('Your video is live', language),
action: {
text: t('View video', language),
url: videoUrl
}
}
}
}
}

View File

@@ -0,0 +1,100 @@
import { To, UserNotificationType } from '@peertube/peertube-models'
import { t } from '@server/helpers/i18n.js'
import { logger } from '@server/helpers/logger.js'
import { WEBSERVER } from '@server/initializers/constants.js'
import { myVideoImportsUrl } from '@server/lib/client-urls.js'
import { UserNotificationModel } from '@server/models/user/user-notification.js'
import { UserModel } from '@server/models/user/user.js'
import { MUserDefault, MUserWithNotificationSetting, MVideoImportVideo, UserNotificationModelForApi } from '@server/types/models/index.js'
import { AbstractNotification } from '../common/abstract-notification.js'
export type ImportFinishedForOwnerPayload = {
videoImport: MVideoImportVideo
success: boolean
}
export class ImportFinishedForOwner extends AbstractNotification<ImportFinishedForOwnerPayload> {
private user: MUserDefault
async prepare () {
this.user = await UserModel.loadByVideoImportId(this.videoImport.id)
}
log () {
logger.info('Notifying user %s its video import %s is finished.', this.user.username, this.videoImport.getTargetIdentifier())
}
getSetting (user: MUserWithNotificationSetting) {
return user.NotificationSetting.myVideoImportFinished
}
getTargetUsers () {
if (!this.user) return []
return [ this.user ]
}
createNotification (user: MUserWithNotificationSetting) {
const notification = UserNotificationModel.build<UserNotificationModelForApi>({
type: this.payload.success
? UserNotificationType.MY_VIDEO_IMPORT_SUCCESS
: UserNotificationType.MY_VIDEO_IMPORT_ERROR,
userId: user.id,
videoImportId: this.videoImport.id
})
notification.VideoImport = this.videoImport
return notification
}
createEmail (user: MUserWithNotificationSetting) {
const to = { email: user.email, language: user.getLanguage() }
if (this.payload.success) return this.createSuccessEmail(to)
return this.createFailEmail(to)
}
private createSuccessEmail (to: To) {
const videoUrl = WEBSERVER.URL + this.videoImport.Video.getWatchStaticPath()
const language = to.language
const targetId = this.videoImport.getTargetIdentifier()
return {
to,
subject: t('Your video import is complete', language),
text: t('Your video {targetId} has just finished importing.', language, { targetId }),
locals: {
action: {
text: t('View video', language),
url: videoUrl
}
}
}
}
private createFailEmail (to: To) {
const language = to.language
const targetId = this.videoImport.getTargetIdentifier()
const text = t('Your video import {targetId} encountered an error.', language, { targetId })
return {
to,
subject: t('Your video import encountered an error', language),
text,
locals: {
title: t('Import failed', language),
action: {
text: t('Review imports', language),
url: myVideoImportsUrl
}
}
}
}
private get videoImport () {
return this.payload.videoImport
}
}

View File

@@ -0,0 +1,6 @@
export * from './new-video-or-live-for-subscribers.js'
export * from './import-finished-for-owner.js'
export * from './owned-publication-after-auto-unblacklist.js'
export * from './owned-publication-after-schedule-update.js'
export * from './owned-publication-after-transcoding.js'
export * from './studio-edition-finished-for-owner.js'

View File

@@ -0,0 +1,84 @@
import { UserNotificationType, VideoPrivacy, VideoState } from '@peertube/peertube-models'
import { logger } from '@server/helpers/logger.js'
import { WEBSERVER } from '@server/initializers/constants.js'
import { UserNotificationModel } from '@server/models/user/user-notification.js'
import { UserModel } from '@server/models/user/user.js'
import { MUserWithNotificationSetting, MVideoAccountLight, UserNotificationModelForApi } from '@server/types/models/index.js'
import { AbstractNotification } from '../common/abstract-notification.js'
import { t } from '@server/helpers/i18n.js'
export class NewVideoOrLiveForSubscribers extends AbstractNotification<MVideoAccountLight> {
private users: MUserWithNotificationSetting[]
async prepare () {
// List all followers that are users
this.users = await UserModel.listUserSubscribersOf(this.payload.VideoChannel.Actor.id)
}
log () {
logger.info('Notifying %d users of new video %s.', this.users.length, this.payload.url)
}
isDisabled () {
if (this.payload.privacy !== VideoPrivacy.PUBLIC && this.payload.privacy !== VideoPrivacy.INTERNAL) return true
if (this.payload.state !== VideoState.PUBLISHED) return true
if (this.payload.isBlacklisted()) return true
return false
}
getSetting (user: MUserWithNotificationSetting) {
return user.NotificationSetting.newVideoFromSubscription
}
getTargetUsers () {
return this.users
}
createNotification (user: MUserWithNotificationSetting) {
const notification = UserNotificationModel.build<UserNotificationModelForApi>({
type: this.payload.isLive
? UserNotificationType.NEW_LIVE_FROM_SUBSCRIPTION
: UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION,
userId: user.id,
videoId: this.payload.id
})
notification.Video = this.payload
return notification
}
// ---------------------------------------------------------------------------
createEmail (user: MUserWithNotificationSetting) {
const to = { email: user.email, language: user.getLanguage() }
const channelName = this.payload.VideoChannel.getDisplayName()
const channelUrl = WEBSERVER.URL + this.payload.VideoChannel.getClientUrl()
const videoUrl = WEBSERVER.URL + this.payload.getWatchStaticPath()
const videoName = this.payload.name
const isLive = this.payload.isLive
const subject = isLive
? t('{channelName} is live streaming', to.language, { channelName })
: t('{channelName} just published a new video: { videoName }', to.language, { channelName, videoName })
return {
template: 'video-published-for-subscribers',
to,
subject,
locals: {
channelName,
channelUrl,
videoName,
videoUrl,
isLive,
action: {
text: t('View video', to.language),
url: videoUrl
}
}
}
}
}

View File

@@ -0,0 +1,10 @@
import { VideoState } from '@peertube/peertube-models'
import { AbstractOwnedVideoPublication } from './abstract-owned-video-publication.js'
export class OwnedPublicationAfterAutoUnblacklist extends AbstractOwnedVideoPublication {
isDisabled () {
// Don't notify if video is still waiting for transcoding or scheduled update
return !!this.payload.ScheduleVideoUpdate || (this.payload.waitTranscoding && this.payload.state !== VideoState.PUBLISHED)
}
}

View File

@@ -0,0 +1,10 @@
import { VideoState } from '@peertube/peertube-models'
import { AbstractOwnedVideoPublication } from './abstract-owned-video-publication.js'
export class OwnedPublicationAfterScheduleUpdate extends AbstractOwnedVideoPublication {
isDisabled () {
// Don't notify if video is still blacklisted or waiting for transcoding
return !!this.payload.VideoBlacklist || (this.payload.waitTranscoding && this.payload.state !== VideoState.PUBLISHED)
}
}

View File

@@ -0,0 +1,9 @@
import { AbstractOwnedVideoPublication } from './abstract-owned-video-publication.js'
export class OwnedPublicationAfterTranscoding extends AbstractOwnedVideoPublication {
isDisabled () {
// Don't notify if didn't wait for transcoding or video is still blacklisted/waiting for scheduled update
return !this.payload.waitTranscoding || !!this.payload.VideoBlacklist || !!this.payload.ScheduleVideoUpdate
}
}

View File

@@ -0,0 +1,59 @@
import { UserNotificationType } from '@peertube/peertube-models'
import { t } from '@server/helpers/i18n.js'
import { logger } from '@server/helpers/logger.js'
import { WEBSERVER } from '@server/initializers/constants.js'
import { UserNotificationModel } from '@server/models/user/user-notification.js'
import { UserModel } from '@server/models/user/user.js'
import { MUserDefault, MUserWithNotificationSetting, MVideoFullLight, UserNotificationModelForApi } from '@server/types/models/index.js'
import { AbstractNotification } from '../common/abstract-notification.js'
export class StudioEditionFinishedForOwner extends AbstractNotification<MVideoFullLight> {
private user: MUserDefault
async prepare () {
this.user = await UserModel.loadByVideoId(this.payload.id)
}
log () {
logger.info('Notifying user %s its video studio edition %s is finished.', this.user.username, this.payload.url)
}
getSetting (user: MUserWithNotificationSetting) {
return user.NotificationSetting.myVideoStudioEditionFinished
}
getTargetUsers () {
if (!this.user) return []
return [ this.user ]
}
createNotification (user: MUserWithNotificationSetting) {
const notification = UserNotificationModel.build<UserNotificationModelForApi>({
type: UserNotificationType.MY_VIDEO_STUDIO_EDITION_FINISHED,
userId: user.id,
videoId: this.payload.id
})
notification.Video = this.payload
return notification
}
createEmail (user: MUserWithNotificationSetting) {
const to = { email: user.email, language: user.getLanguage() }
const videoUrl = WEBSERVER.URL + this.payload.getWatchStaticPath()
return {
to,
subject: t('Edition of your video has finished', to.language),
text: t('Edition of your video {videoName} has finished.', to.language, { videoName: this.payload.name }),
locals: {
title: t('Video edition has finished', to.language),
action: {
text: t('View video', to.language),
url: videoUrl
}
}
}
}
}