Refactor notifier
This commit is contained in:
parent
2bee9db56a
commit
d26836cd95
37 changed files with 1627 additions and 1231 deletions
|
@ -1,5 +1,6 @@
|
||||||
import { WEBSERVER } from '../../initializers/constants'
|
import { WEBSERVER } from '../../initializers/constants'
|
||||||
import {
|
import {
|
||||||
|
MAbuseFull,
|
||||||
MAbuseId,
|
MAbuseId,
|
||||||
MActor,
|
MActor,
|
||||||
MActorFollowActors,
|
MActorFollowActors,
|
||||||
|
@ -112,6 +113,14 @@ function getUndoActivityPubUrl (originalUrl: string) {
|
||||||
return originalUrl + '/undo'
|
return originalUrl + '/undo'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function getAbuseTargetUrl (abuse: MAbuseFull) {
|
||||||
|
return abuse.VideoAbuse?.Video?.url ||
|
||||||
|
abuse.VideoCommentAbuse?.VideoComment?.url ||
|
||||||
|
abuse.FlaggedAccount.Actor.url
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
getLocalVideoActivityPubUrl,
|
getLocalVideoActivityPubUrl,
|
||||||
getLocalVideoPlaylistActivityPubUrl,
|
getLocalVideoPlaylistActivityPubUrl,
|
||||||
|
@ -135,5 +144,6 @@ export {
|
||||||
getLocalVideoSharesActivityPubUrl,
|
getLocalVideoSharesActivityPubUrl,
|
||||||
getLocalVideoCommentsActivityPubUrl,
|
getLocalVideoCommentsActivityPubUrl,
|
||||||
getLocalVideoLikesActivityPubUrl,
|
getLocalVideoLikesActivityPubUrl,
|
||||||
getLocalVideoDislikesActivityPubUrl
|
getLocalVideoDislikesActivityPubUrl,
|
||||||
|
getAbuseTargetUrl
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,15 @@
|
||||||
import { readFileSync } from 'fs-extra'
|
import { readFileSync } from 'fs-extra'
|
||||||
import { merge } from 'lodash'
|
import { isArray, merge } from 'lodash'
|
||||||
import { createTransport, Transporter } from 'nodemailer'
|
import { createTransport, Transporter } from 'nodemailer'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { VideoChannelModel } from '@server/models/video/video-channel'
|
import { EmailPayload } from '@shared/models'
|
||||||
import { MVideoBlacklistLightVideo, MVideoBlacklistVideo } from '@server/types/models/video/video-blacklist'
|
|
||||||
import { MVideoImport, MVideoImportVideo } from '@server/types/models/video/video-import'
|
|
||||||
import { AbuseState, EmailPayload, UserAbuse } from '@shared/models'
|
|
||||||
import { SendEmailDefaultOptions } from '../../shared/models/server/emailer.model'
|
import { SendEmailDefaultOptions } from '../../shared/models/server/emailer.model'
|
||||||
import { isTestInstance, root } from '../helpers/core-utils'
|
import { isTestInstance, root } from '../helpers/core-utils'
|
||||||
import { bunyanLogger, logger } from '../helpers/logger'
|
import { bunyanLogger, logger } from '../helpers/logger'
|
||||||
import { CONFIG, isEmailEnabled } from '../initializers/config'
|
import { CONFIG, isEmailEnabled } from '../initializers/config'
|
||||||
import { WEBSERVER } from '../initializers/constants'
|
import { WEBSERVER } from '../initializers/constants'
|
||||||
import { MAbuseFull, MAbuseMessage, MAccountDefault, MActorFollowActors, MActorFollowFull, MPlugin, MUser } from '../types/models'
|
import { MUser } from '../types/models'
|
||||||
import { MCommentOwnerVideo, MVideo, MVideoAccountLight } from '../types/models/video'
|
|
||||||
import { JobQueue } from './job-queue'
|
import { JobQueue } from './job-queue'
|
||||||
import { toSafeHtml } from '../helpers/markdown'
|
|
||||||
|
|
||||||
const Email = require('email-templates')
|
const Email = require('email-templates')
|
||||||
|
|
||||||
|
@ -59,429 +54,6 @@ class Emailer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addNewVideoFromSubscriberNotification (to: string[], video: MVideoAccountLight) {
|
|
||||||
const channelName = video.VideoChannel.getDisplayName()
|
|
||||||
const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
|
||||||
to,
|
|
||||||
subject: channelName + ' just published a new video',
|
|
||||||
text: `Your subscription ${channelName} just published a new video: "${video.name}".`,
|
|
||||||
locals: {
|
|
||||||
title: 'New content ',
|
|
||||||
action: {
|
|
||||||
text: 'View video',
|
|
||||||
url: videoUrl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
|
||||||
}
|
|
||||||
|
|
||||||
addNewFollowNotification (to: string[], actorFollow: MActorFollowFull, followType: 'account' | 'channel') {
|
|
||||||
const followingName = (actorFollow.ActorFollowing.VideoChannel || actorFollow.ActorFollowing.Account).getDisplayName()
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
|
||||||
template: 'follower-on-channel',
|
|
||||||
to,
|
|
||||||
subject: `New follower on your channel ${followingName}`,
|
|
||||||
locals: {
|
|
||||||
followerName: actorFollow.ActorFollower.Account.getDisplayName(),
|
|
||||||
followerUrl: actorFollow.ActorFollower.url,
|
|
||||||
followingName,
|
|
||||||
followingUrl: actorFollow.ActorFollowing.url,
|
|
||||||
followType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
|
||||||
}
|
|
||||||
|
|
||||||
addNewInstanceFollowerNotification (to: string[], actorFollow: MActorFollowActors) {
|
|
||||||
const awaitingApproval = actorFollow.state === 'pending' ? ' awaiting manual approval.' : ''
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
|
||||||
to,
|
|
||||||
subject: 'New instance follower',
|
|
||||||
text: `Your instance has a new follower: ${actorFollow.ActorFollower.url}${awaitingApproval}.`,
|
|
||||||
locals: {
|
|
||||||
title: 'New instance follower',
|
|
||||||
action: {
|
|
||||||
text: 'Review followers',
|
|
||||||
url: WEBSERVER.URL + '/admin/follows/followers-list'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
|
||||||
}
|
|
||||||
|
|
||||||
addAutoInstanceFollowingNotification (to: string[], actorFollow: MActorFollowActors) {
|
|
||||||
const instanceUrl = actorFollow.ActorFollowing.url
|
|
||||||
const emailPayload: EmailPayload = {
|
|
||||||
to,
|
|
||||||
subject: 'Auto instance following',
|
|
||||||
text: `Your instance automatically followed a new instance: <a href="${instanceUrl}">${instanceUrl}</a>.`
|
|
||||||
}
|
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
|
||||||
}
|
|
||||||
|
|
||||||
myVideoPublishedNotification (to: string[], video: MVideo) {
|
|
||||||
const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
|
||||||
to,
|
|
||||||
subject: `Your video ${video.name} has been published`,
|
|
||||||
text: `Your video "${video.name}" has been published.`,
|
|
||||||
locals: {
|
|
||||||
title: 'You video is live',
|
|
||||||
action: {
|
|
||||||
text: 'View video',
|
|
||||||
url: videoUrl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
|
||||||
}
|
|
||||||
|
|
||||||
myVideoImportSuccessNotification (to: string[], videoImport: MVideoImportVideo) {
|
|
||||||
const videoUrl = WEBSERVER.URL + videoImport.Video.getWatchStaticPath()
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
|
||||||
to,
|
|
||||||
subject: `Your video import ${videoImport.getTargetIdentifier()} is complete`,
|
|
||||||
text: `Your video "${videoImport.getTargetIdentifier()}" just finished importing.`,
|
|
||||||
locals: {
|
|
||||||
title: 'Import complete',
|
|
||||||
action: {
|
|
||||||
text: 'View video',
|
|
||||||
url: videoUrl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
|
||||||
}
|
|
||||||
|
|
||||||
myVideoImportErrorNotification (to: string[], videoImport: MVideoImport) {
|
|
||||||
const importUrl = WEBSERVER.URL + '/my-library/video-imports'
|
|
||||||
|
|
||||||
const text =
|
|
||||||
`Your video import "${videoImport.getTargetIdentifier()}" encountered an error.` +
|
|
||||||
'\n\n' +
|
|
||||||
`See your videos import dashboard for more information: <a href="${importUrl}">${importUrl}</a>.`
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
|
||||||
to,
|
|
||||||
subject: `Your video import "${videoImport.getTargetIdentifier()}" encountered an error`,
|
|
||||||
text,
|
|
||||||
locals: {
|
|
||||||
title: 'Import failed',
|
|
||||||
action: {
|
|
||||||
text: 'Review imports',
|
|
||||||
url: importUrl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
|
||||||
}
|
|
||||||
|
|
||||||
addNewCommentOnMyVideoNotification (to: string[], comment: MCommentOwnerVideo) {
|
|
||||||
const video = comment.Video
|
|
||||||
const videoUrl = WEBSERVER.URL + comment.Video.getWatchStaticPath()
|
|
||||||
const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath()
|
|
||||||
const commentHtml = toSafeHtml(comment.text)
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
|
||||||
template: 'video-comment-new',
|
|
||||||
to,
|
|
||||||
subject: 'New comment on your video ' + video.name,
|
|
||||||
locals: {
|
|
||||||
accountName: comment.Account.getDisplayName(),
|
|
||||||
accountUrl: comment.Account.Actor.url,
|
|
||||||
comment,
|
|
||||||
commentHtml,
|
|
||||||
video,
|
|
||||||
videoUrl,
|
|
||||||
action: {
|
|
||||||
text: 'View comment',
|
|
||||||
url: commentUrl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
|
||||||
}
|
|
||||||
|
|
||||||
addNewCommentMentionNotification (to: string[], comment: MCommentOwnerVideo) {
|
|
||||||
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)
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
|
||||||
template: 'video-comment-mention',
|
|
||||||
to,
|
|
||||||
subject: 'Mention on video ' + video.name,
|
|
||||||
locals: {
|
|
||||||
comment,
|
|
||||||
commentHtml,
|
|
||||||
video,
|
|
||||||
videoUrl,
|
|
||||||
accountName,
|
|
||||||
action: {
|
|
||||||
text: 'View comment',
|
|
||||||
url: commentUrl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
|
||||||
}
|
|
||||||
|
|
||||||
addAbuseModeratorsNotification (to: string[], parameters: {
|
|
||||||
abuse: UserAbuse
|
|
||||||
abuseInstance: MAbuseFull
|
|
||||||
reporter: string
|
|
||||||
}) {
|
|
||||||
const { abuse, abuseInstance, reporter } = parameters
|
|
||||||
|
|
||||||
const action = {
|
|
||||||
text: 'View report #' + abuse.id,
|
|
||||||
url: WEBSERVER.URL + '/admin/moderation/abuses/list?search=%23' + abuse.id
|
|
||||||
}
|
|
||||||
|
|
||||||
let emailPayload: EmailPayload
|
|
||||||
|
|
||||||
if (abuseInstance.VideoAbuse) {
|
|
||||||
const video = abuseInstance.VideoAbuse.Video
|
|
||||||
const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
|
|
||||||
|
|
||||||
emailPayload = {
|
|
||||||
template: 'video-abuse-new',
|
|
||||||
to,
|
|
||||||
subject: `New video abuse report from ${reporter}`,
|
|
||||||
locals: {
|
|
||||||
videoUrl,
|
|
||||||
isLocal: video.remote === false,
|
|
||||||
videoCreatedAt: new Date(video.createdAt).toLocaleString(),
|
|
||||||
videoPublishedAt: new Date(video.publishedAt).toLocaleString(),
|
|
||||||
videoName: video.name,
|
|
||||||
reason: abuse.reason,
|
|
||||||
videoChannel: abuse.video.channel,
|
|
||||||
reporter,
|
|
||||||
action
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (abuseInstance.VideoCommentAbuse) {
|
|
||||||
const comment = abuseInstance.VideoCommentAbuse.VideoComment
|
|
||||||
const commentUrl = WEBSERVER.URL + comment.Video.getWatchStaticPath() + ';threadId=' + comment.getThreadId()
|
|
||||||
|
|
||||||
emailPayload = {
|
|
||||||
template: 'video-comment-abuse-new',
|
|
||||||
to,
|
|
||||||
subject: `New comment abuse report from ${reporter}`,
|
|
||||||
locals: {
|
|
||||||
commentUrl,
|
|
||||||
videoName: comment.Video.name,
|
|
||||||
isLocal: comment.isOwned(),
|
|
||||||
commentCreatedAt: new Date(comment.createdAt).toLocaleString(),
|
|
||||||
reason: abuse.reason,
|
|
||||||
flaggedAccount: abuseInstance.FlaggedAccount.getDisplayName(),
|
|
||||||
reporter,
|
|
||||||
action
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const account = abuseInstance.FlaggedAccount
|
|
||||||
const accountUrl = account.getClientUrl()
|
|
||||||
|
|
||||||
emailPayload = {
|
|
||||||
template: 'account-abuse-new',
|
|
||||||
to,
|
|
||||||
subject: `New account abuse report from ${reporter}`,
|
|
||||||
locals: {
|
|
||||||
accountUrl,
|
|
||||||
accountDisplayName: account.getDisplayName(),
|
|
||||||
isLocal: account.isOwned(),
|
|
||||||
reason: abuse.reason,
|
|
||||||
reporter,
|
|
||||||
action
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
|
||||||
}
|
|
||||||
|
|
||||||
addAbuseStateChangeNotification (to: string[], abuse: MAbuseFull) {
|
|
||||||
const text = abuse.state === AbuseState.ACCEPTED
|
|
||||||
? 'Report #' + abuse.id + ' has been accepted'
|
|
||||||
: 'Report #' + abuse.id + ' has been rejected'
|
|
||||||
|
|
||||||
const abuseUrl = WEBSERVER.URL + '/my-account/abuses?search=%23' + abuse.id
|
|
||||||
|
|
||||||
const action = {
|
|
||||||
text,
|
|
||||||
url: abuseUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
|
||||||
template: 'abuse-state-change',
|
|
||||||
to,
|
|
||||||
subject: text,
|
|
||||||
locals: {
|
|
||||||
action,
|
|
||||||
abuseId: abuse.id,
|
|
||||||
abuseUrl,
|
|
||||||
isAccepted: abuse.state === AbuseState.ACCEPTED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
|
||||||
}
|
|
||||||
|
|
||||||
addAbuseNewMessageNotification (
|
|
||||||
to: string[],
|
|
||||||
options: {
|
|
||||||
target: 'moderator' | 'reporter'
|
|
||||||
abuse: MAbuseFull
|
|
||||||
message: MAbuseMessage
|
|
||||||
accountMessage: MAccountDefault
|
|
||||||
}) {
|
|
||||||
const { abuse, target, message, accountMessage } = options
|
|
||||||
|
|
||||||
const text = 'New message on report #' + abuse.id
|
|
||||||
const abuseUrl = target === 'moderator'
|
|
||||||
? WEBSERVER.URL + '/admin/moderation/abuses/list?search=%23' + abuse.id
|
|
||||||
: WEBSERVER.URL + '/my-account/abuses?search=%23' + abuse.id
|
|
||||||
|
|
||||||
const action = {
|
|
||||||
text,
|
|
||||||
url: abuseUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
|
||||||
template: 'abuse-new-message',
|
|
||||||
to,
|
|
||||||
subject: text,
|
|
||||||
locals: {
|
|
||||||
abuseId: abuse.id,
|
|
||||||
abuseUrl: action.url,
|
|
||||||
messageAccountName: accountMessage.getDisplayName(),
|
|
||||||
messageText: message.message,
|
|
||||||
action
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
|
||||||
}
|
|
||||||
|
|
||||||
async addVideoAutoBlacklistModeratorsNotification (to: string[], videoBlacklist: MVideoBlacklistLightVideo) {
|
|
||||||
const videoAutoBlacklistUrl = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list'
|
|
||||||
const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath()
|
|
||||||
const channel = (await VideoChannelModel.loadAndPopulateAccount(videoBlacklist.Video.channelId)).toFormattedSummaryJSON()
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
|
||||||
template: 'video-auto-blacklist-new',
|
|
||||||
to,
|
|
||||||
subject: 'A new video is pending moderation',
|
|
||||||
locals: {
|
|
||||||
channel,
|
|
||||||
videoUrl,
|
|
||||||
videoName: videoBlacklist.Video.name,
|
|
||||||
action: {
|
|
||||||
text: 'Review autoblacklist',
|
|
||||||
url: videoAutoBlacklistUrl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
|
||||||
}
|
|
||||||
|
|
||||||
addNewUserRegistrationNotification (to: string[], user: MUser) {
|
|
||||||
const emailPayload: EmailPayload = {
|
|
||||||
template: 'user-registered',
|
|
||||||
to,
|
|
||||||
subject: `a new user registered on ${CONFIG.INSTANCE.NAME}: ${user.username}`,
|
|
||||||
locals: {
|
|
||||||
user
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
|
||||||
}
|
|
||||||
|
|
||||||
addVideoBlacklistNotification (to: string[], videoBlacklist: MVideoBlacklistVideo) {
|
|
||||||
const videoName = videoBlacklist.Video.name
|
|
||||||
const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath()
|
|
||||||
|
|
||||||
const reasonString = videoBlacklist.reason ? ` for the following reason: ${videoBlacklist.reason}` : ''
|
|
||||||
const blockedString = `Your video ${videoName} (${videoUrl} on ${CONFIG.INSTANCE.NAME} has been blacklisted${reasonString}.`
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
|
||||||
to,
|
|
||||||
subject: `Video ${videoName} blacklisted`,
|
|
||||||
text: blockedString,
|
|
||||||
locals: {
|
|
||||||
title: 'Your video was blacklisted'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
|
||||||
}
|
|
||||||
|
|
||||||
addVideoUnblacklistNotification (to: string[], video: MVideo) {
|
|
||||||
const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
|
||||||
to,
|
|
||||||
subject: `Video ${video.name} unblacklisted`,
|
|
||||||
text: `Your video "${video.name}" (${videoUrl}) on ${CONFIG.INSTANCE.NAME} has been unblacklisted.`,
|
|
||||||
locals: {
|
|
||||||
title: 'Your video was unblacklisted'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
|
||||||
}
|
|
||||||
|
|
||||||
addNewPeerTubeVersionNotification (to: string[], latestVersion: string) {
|
|
||||||
const emailPayload: EmailPayload = {
|
|
||||||
to,
|
|
||||||
template: 'peertube-version-new',
|
|
||||||
subject: `A new PeerTube version is available: ${latestVersion}`,
|
|
||||||
locals: {
|
|
||||||
latestVersion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
|
||||||
}
|
|
||||||
|
|
||||||
addNewPlugionVersionNotification (to: string[], plugin: MPlugin) {
|
|
||||||
const pluginUrl = WEBSERVER.URL + '/admin/plugins/list-installed?pluginType=' + plugin.type
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
|
||||||
to,
|
|
||||||
template: 'plugin-version-new',
|
|
||||||
subject: `A new plugin/theme version is available: ${plugin.name}@${plugin.latestVersion}`,
|
|
||||||
locals: {
|
|
||||||
pluginName: plugin.name,
|
|
||||||
latestVersion: plugin.latestVersion,
|
|
||||||
pluginUrl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
|
||||||
}
|
|
||||||
|
|
||||||
addPasswordResetEmailJob (username: string, to: string, resetPasswordUrl: string) {
|
addPasswordResetEmailJob (username: string, to: string, resetPasswordUrl: string) {
|
||||||
const emailPayload: EmailPayload = {
|
const emailPayload: EmailPayload = {
|
||||||
template: 'password-reset',
|
template: 'password-reset',
|
||||||
|
@ -578,7 +150,11 @@ class Emailer {
|
||||||
subjectPrefix: CONFIG.EMAIL.SUBJECT.PREFIX
|
subjectPrefix: CONFIG.EMAIL.SUBJECT.PREFIX
|
||||||
})
|
})
|
||||||
|
|
||||||
for (const to of options.to) {
|
const toEmails = isArray(options.to)
|
||||||
|
? options.to
|
||||||
|
: [ options.to ]
|
||||||
|
|
||||||
|
for (const to of toEmails) {
|
||||||
const baseOptions: SendEmailDefaultOptions = {
|
const baseOptions: SendEmailDefaultOptions = {
|
||||||
template: 'common',
|
template: 'common',
|
||||||
message: {
|
message: {
|
||||||
|
|
|
@ -235,7 +235,7 @@ async function processFile (downloader: () => Promise<string>, videoImport: MVid
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Notifier.Instance.notifyOnFinishedVideoImport(videoImportUpdated, true)
|
Notifier.Instance.notifyOnFinishedVideoImport({ videoImport: videoImportUpdated, success: true })
|
||||||
|
|
||||||
if (video.isBlacklisted()) {
|
if (video.isBlacklisted()) {
|
||||||
const videoBlacklist = Object.assign(video.VideoBlacklist, { Video: video })
|
const videoBlacklist = Object.assign(video.VideoBlacklist, { Video: video })
|
||||||
|
@ -263,7 +263,7 @@ async function processFile (downloader: () => Promise<string>, videoImport: MVid
|
||||||
}
|
}
|
||||||
await videoImport.save()
|
await videoImport.save()
|
||||||
|
|
||||||
Notifier.Instance.notifyOnFinishedVideoImport(videoImport, false)
|
Notifier.Instance.notifyOnFinishedVideoImport({ videoImport, success: false })
|
||||||
|
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,796 +0,0 @@
|
||||||
import { AccountModel } from '@server/models/account/account'
|
|
||||||
import { getServerActor } from '@server/models/application/application'
|
|
||||||
import { ServerBlocklistModel } from '@server/models/server/server-blocklist'
|
|
||||||
import {
|
|
||||||
MUser,
|
|
||||||
MUserAccount,
|
|
||||||
MUserDefault,
|
|
||||||
MUserNotifSettingAccount,
|
|
||||||
MUserWithNotificationSetting,
|
|
||||||
UserNotificationModelForApi
|
|
||||||
} from '@server/types/models/user'
|
|
||||||
import { MVideoBlacklistLightVideo, MVideoBlacklistVideo } from '@server/types/models/video/video-blacklist'
|
|
||||||
import { MVideoImportVideo } from '@server/types/models/video/video-import'
|
|
||||||
import { UserAbuse } from '@shared/models'
|
|
||||||
import { UserNotificationSettingValue, UserNotificationType, UserRight } from '../../shared/models/users'
|
|
||||||
import { VideoPrivacy, VideoState } from '../../shared/models/videos'
|
|
||||||
import { logger } from '../helpers/logger'
|
|
||||||
import { CONFIG } from '../initializers/config'
|
|
||||||
import { AccountBlocklistModel } from '../models/account/account-blocklist'
|
|
||||||
import { UserModel } from '../models/user/user'
|
|
||||||
import { UserNotificationModel } from '../models/user/user-notification'
|
|
||||||
import { MAbuseFull, MAbuseMessage, MAccountServer, MActorFollowFull, MApplication, MPlugin } from '../types/models'
|
|
||||||
import { MCommentOwnerVideo, MVideoAccountLight, MVideoFullLight } from '../types/models/video'
|
|
||||||
import { isBlockedByServerOrAccount } from './blocklist'
|
|
||||||
import { Emailer } from './emailer'
|
|
||||||
import { PeerTubeSocket } from './peertube-socket'
|
|
||||||
|
|
||||||
class Notifier {
|
|
||||||
|
|
||||||
private static instance: Notifier
|
|
||||||
|
|
||||||
private constructor () {
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyOnNewVideoIfNeeded (video: MVideoAccountLight): void {
|
|
||||||
// Only notify on public and published videos which are not blacklisted
|
|
||||||
if (video.privacy !== VideoPrivacy.PUBLIC || video.state !== VideoState.PUBLISHED || video.isBlacklisted()) return
|
|
||||||
|
|
||||||
this.notifySubscribersOfNewVideo(video)
|
|
||||||
.catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err }))
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyOnVideoPublishedAfterTranscoding (video: MVideoFullLight): void {
|
|
||||||
// don't notify if didn't wait for transcoding or video is still blacklisted/waiting for scheduled update
|
|
||||||
if (!video.waitTranscoding || video.VideoBlacklist || video.ScheduleVideoUpdate) return
|
|
||||||
|
|
||||||
this.notifyOwnedVideoHasBeenPublished(video)
|
|
||||||
.catch(err => logger.error('Cannot notify owner that its video %s has been published after transcoding.', video.url, { err }))
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyOnVideoPublishedAfterScheduledUpdate (video: MVideoFullLight): void {
|
|
||||||
// don't notify if video is still blacklisted or waiting for transcoding
|
|
||||||
if (video.VideoBlacklist || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return
|
|
||||||
|
|
||||||
this.notifyOwnedVideoHasBeenPublished(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 {
|
|
||||||
// don't notify if video is still waiting for transcoding or scheduled update
|
|
||||||
if (video.ScheduleVideoUpdate || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return
|
|
||||||
|
|
||||||
this.notifyOwnedVideoHasBeenPublished(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 {
|
|
||||||
this.notifyVideoOwnerOfNewComment(comment)
|
|
||||||
.catch(err => logger.error('Cannot notify video owner of new comment %s.', comment.url, { err }))
|
|
||||||
|
|
||||||
this.notifyOfCommentMention(comment)
|
|
||||||
.catch(err => logger.error('Cannot notify mentions of comment %s.', comment.url, { err }))
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyOnNewAbuse (parameters: { abuse: UserAbuse, abuseInstance: MAbuseFull, reporter: string }): void {
|
|
||||||
this.notifyModeratorsOfNewAbuse(parameters)
|
|
||||||
.catch(err => logger.error('Cannot notify of new abuse %d.', parameters.abuseInstance.id, { err }))
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyOnVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo): void {
|
|
||||||
this.notifyModeratorsOfVideoAutoBlacklist(videoBlacklist)
|
|
||||||
.catch(err => logger.error('Cannot notify of auto-blacklist of video %s.', videoBlacklist.Video.url, { err }))
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyOnVideoBlacklist (videoBlacklist: MVideoBlacklistVideo): void {
|
|
||||||
this.notifyVideoOwnerOfBlacklist(videoBlacklist)
|
|
||||||
.catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err }))
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyOnVideoUnblacklist (video: MVideoFullLight): void {
|
|
||||||
this.notifyVideoOwnerOfUnblacklist(video)
|
|
||||||
.catch(err => logger.error('Cannot notify video owner of unblacklist of %s.', video.url, { err }))
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyOnFinishedVideoImport (videoImport: MVideoImportVideo, success: boolean): void {
|
|
||||||
this.notifyOwnerVideoImportIsFinished(videoImport, success)
|
|
||||||
.catch(err => logger.error('Cannot notify owner that its video import %s is finished.', videoImport.getTargetIdentifier(), { err }))
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyOnNewUserRegistration (user: MUserDefault): void {
|
|
||||||
this.notifyModeratorsOfNewUserRegistration(user)
|
|
||||||
.catch(err => logger.error('Cannot notify moderators of new user registration (%s).', user.username, { err }))
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyOfNewUserFollow (actorFollow: MActorFollowFull): void {
|
|
||||||
this.notifyUserOfNewActorFollow(actorFollow)
|
|
||||||
.catch(err => {
|
|
||||||
logger.error(
|
|
||||||
'Cannot notify owner of channel %s of a new follow by %s.',
|
|
||||||
actorFollow.ActorFollowing.VideoChannel.getDisplayName(),
|
|
||||||
actorFollow.ActorFollower.Account.getDisplayName(),
|
|
||||||
{ err }
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyOfNewInstanceFollow (actorFollow: MActorFollowFull): void {
|
|
||||||
this.notifyAdminsOfNewInstanceFollow(actorFollow)
|
|
||||||
.catch(err => {
|
|
||||||
logger.error('Cannot notify administrators of new follower %s.', actorFollow.ActorFollower.url, { err })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyOfAutoInstanceFollowing (actorFollow: MActorFollowFull): void {
|
|
||||||
this.notifyAdminsOfAutoInstanceFollowing(actorFollow)
|
|
||||||
.catch(err => {
|
|
||||||
logger.error('Cannot notify administrators of auto instance following %s.', actorFollow.ActorFollowing.url, { err })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyOnAbuseStateChange (abuse: MAbuseFull): void {
|
|
||||||
this.notifyReporterOfAbuseStateChange(abuse)
|
|
||||||
.catch(err => {
|
|
||||||
logger.error('Cannot notify reporter of abuse %d state change.', abuse.id, { err })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyOnAbuseMessage (abuse: MAbuseFull, message: MAbuseMessage): void {
|
|
||||||
this.notifyOfNewAbuseMessage(abuse, message)
|
|
||||||
.catch(err => {
|
|
||||||
logger.error('Cannot notify on new abuse %d message.', abuse.id, { err })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyOfNewPeerTubeVersion (application: MApplication, latestVersion: string) {
|
|
||||||
this.notifyAdminsOfNewPeerTubeVersion(application, latestVersion)
|
|
||||||
.catch(err => {
|
|
||||||
logger.error('Cannot notify on new PeerTubeb version %s.', latestVersion, { err })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyOfNewPluginVersion (plugin: MPlugin) {
|
|
||||||
this.notifyAdminsOfNewPluginVersion(plugin)
|
|
||||||
.catch(err => {
|
|
||||||
logger.error('Cannot notify on new plugin version %s.', plugin.name, { err })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private async notifySubscribersOfNewVideo (video: MVideoAccountLight) {
|
|
||||||
// List all followers that are users
|
|
||||||
const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId)
|
|
||||||
|
|
||||||
logger.info('Notifying %d users of new video %s.', users.length, video.url)
|
|
||||||
|
|
||||||
function settingGetter (user: MUserWithNotificationSetting) {
|
|
||||||
return user.NotificationSetting.newVideoFromSubscription
|
|
||||||
}
|
|
||||||
|
|
||||||
async function notificationCreator (user: MUserWithNotificationSetting) {
|
|
||||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
|
||||||
type: UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION,
|
|
||||||
userId: user.id,
|
|
||||||
videoId: video.id
|
|
||||||
})
|
|
||||||
notification.Video = video
|
|
||||||
|
|
||||||
return notification
|
|
||||||
}
|
|
||||||
|
|
||||||
function emailSender (emails: string[]) {
|
|
||||||
return Emailer.Instance.addNewVideoFromSubscriberNotification(emails, video)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.notify({ users, settingGetter, notificationCreator, emailSender })
|
|
||||||
}
|
|
||||||
|
|
||||||
private async notifyVideoOwnerOfNewComment (comment: MCommentOwnerVideo) {
|
|
||||||
if (comment.Video.isOwned() === false) return
|
|
||||||
|
|
||||||
const user = await UserModel.loadByVideoId(comment.videoId)
|
|
||||||
|
|
||||||
// Not our user or user comments its own video
|
|
||||||
if (!user || comment.Account.userId === user.id) return
|
|
||||||
|
|
||||||
if (await this.isBlockedByServerOrUser(comment.Account, user)) return
|
|
||||||
|
|
||||||
logger.info('Notifying user %s of new comment %s.', user.username, comment.url)
|
|
||||||
|
|
||||||
function settingGetter (user: MUserWithNotificationSetting) {
|
|
||||||
return user.NotificationSetting.newCommentOnMyVideo
|
|
||||||
}
|
|
||||||
|
|
||||||
async function notificationCreator (user: MUserWithNotificationSetting) {
|
|
||||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
|
||||||
type: UserNotificationType.NEW_COMMENT_ON_MY_VIDEO,
|
|
||||||
userId: user.id,
|
|
||||||
commentId: comment.id
|
|
||||||
})
|
|
||||||
notification.Comment = comment
|
|
||||||
|
|
||||||
return notification
|
|
||||||
}
|
|
||||||
|
|
||||||
function emailSender (emails: string[]) {
|
|
||||||
return Emailer.Instance.addNewCommentOnMyVideoNotification(emails, comment)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
|
|
||||||
}
|
|
||||||
|
|
||||||
private async notifyOfCommentMention (comment: MCommentOwnerVideo) {
|
|
||||||
const extractedUsernames = comment.extractMentions()
|
|
||||||
logger.debug(
|
|
||||||
'Extracted %d username from comment %s.', extractedUsernames.length, comment.url,
|
|
||||||
{ usernames: extractedUsernames, text: comment.text }
|
|
||||||
)
|
|
||||||
|
|
||||||
let users = await UserModel.listByUsernames(extractedUsernames)
|
|
||||||
|
|
||||||
if (comment.Video.isOwned()) {
|
|
||||||
const userException = await UserModel.loadByVideoId(comment.videoId)
|
|
||||||
users = users.filter(u => u.id !== userException.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't notify if I mentioned myself
|
|
||||||
users = users.filter(u => u.Account.id !== comment.accountId)
|
|
||||||
|
|
||||||
if (users.length === 0) return
|
|
||||||
|
|
||||||
const serverAccountId = (await getServerActor()).Account.id
|
|
||||||
const sourceAccounts = users.map(u => u.Account.id).concat([ serverAccountId ])
|
|
||||||
|
|
||||||
const accountMutedHash = await AccountBlocklistModel.isAccountMutedByMulti(sourceAccounts, comment.accountId)
|
|
||||||
const instanceMutedHash = await ServerBlocklistModel.isServerMutedByMulti(sourceAccounts, comment.Account.Actor.serverId)
|
|
||||||
|
|
||||||
logger.info('Notifying %d users of new comment %s.', users.length, comment.url)
|
|
||||||
|
|
||||||
function settingGetter (user: MUserNotifSettingAccount) {
|
|
||||||
const accountId = user.Account.id
|
|
||||||
if (
|
|
||||||
accountMutedHash[accountId] === true || instanceMutedHash[accountId] === true ||
|
|
||||||
accountMutedHash[serverAccountId] === true || instanceMutedHash[serverAccountId] === true
|
|
||||||
) {
|
|
||||||
return UserNotificationSettingValue.NONE
|
|
||||||
}
|
|
||||||
|
|
||||||
return user.NotificationSetting.commentMention
|
|
||||||
}
|
|
||||||
|
|
||||||
async function notificationCreator (user: MUserNotifSettingAccount) {
|
|
||||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
|
||||||
type: UserNotificationType.COMMENT_MENTION,
|
|
||||||
userId: user.id,
|
|
||||||
commentId: comment.id
|
|
||||||
})
|
|
||||||
notification.Comment = comment
|
|
||||||
|
|
||||||
return notification
|
|
||||||
}
|
|
||||||
|
|
||||||
function emailSender (emails: string[]) {
|
|
||||||
return Emailer.Instance.addNewCommentMentionNotification(emails, comment)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.notify({ users, settingGetter, notificationCreator, emailSender })
|
|
||||||
}
|
|
||||||
|
|
||||||
private async notifyUserOfNewActorFollow (actorFollow: MActorFollowFull) {
|
|
||||||
if (actorFollow.ActorFollowing.isOwned() === false) return
|
|
||||||
|
|
||||||
// Account follows one of our account?
|
|
||||||
let followType: 'account' | 'channel' = 'channel'
|
|
||||||
let user = await UserModel.loadByChannelActorId(actorFollow.ActorFollowing.id)
|
|
||||||
|
|
||||||
// Account follows one of our channel?
|
|
||||||
if (!user) {
|
|
||||||
user = await UserModel.loadByAccountActorId(actorFollow.ActorFollowing.id)
|
|
||||||
followType = 'account'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!user) return
|
|
||||||
|
|
||||||
const followerAccount = actorFollow.ActorFollower.Account
|
|
||||||
const followerAccountWithActor = Object.assign(followerAccount, { Actor: actorFollow.ActorFollower })
|
|
||||||
|
|
||||||
if (await this.isBlockedByServerOrUser(followerAccountWithActor, user)) return
|
|
||||||
|
|
||||||
logger.info('Notifying user %s of new follower: %s.', user.username, followerAccount.getDisplayName())
|
|
||||||
|
|
||||||
function settingGetter (user: MUserWithNotificationSetting) {
|
|
||||||
return user.NotificationSetting.newFollow
|
|
||||||
}
|
|
||||||
|
|
||||||
async function notificationCreator (user: MUserWithNotificationSetting) {
|
|
||||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
|
||||||
type: UserNotificationType.NEW_FOLLOW,
|
|
||||||
userId: user.id,
|
|
||||||
actorFollowId: actorFollow.id
|
|
||||||
})
|
|
||||||
notification.ActorFollow = actorFollow
|
|
||||||
|
|
||||||
return notification
|
|
||||||
}
|
|
||||||
|
|
||||||
function emailSender (emails: string[]) {
|
|
||||||
return Emailer.Instance.addNewFollowNotification(emails, actorFollow, followType)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
|
|
||||||
}
|
|
||||||
|
|
||||||
private async notifyAdminsOfNewInstanceFollow (actorFollow: MActorFollowFull) {
|
|
||||||
const admins = await UserModel.listWithRight(UserRight.MANAGE_SERVER_FOLLOW)
|
|
||||||
|
|
||||||
const follower = Object.assign(actorFollow.ActorFollower.Account, { Actor: actorFollow.ActorFollower })
|
|
||||||
if (await this.isBlockedByServerOrUser(follower)) return
|
|
||||||
|
|
||||||
logger.info('Notifying %d administrators of new instance follower: %s.', admins.length, actorFollow.ActorFollower.url)
|
|
||||||
|
|
||||||
function settingGetter (user: MUserWithNotificationSetting) {
|
|
||||||
return user.NotificationSetting.newInstanceFollower
|
|
||||||
}
|
|
||||||
|
|
||||||
async function notificationCreator (user: MUserWithNotificationSetting) {
|
|
||||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
|
||||||
type: UserNotificationType.NEW_INSTANCE_FOLLOWER,
|
|
||||||
userId: user.id,
|
|
||||||
actorFollowId: actorFollow.id
|
|
||||||
})
|
|
||||||
notification.ActorFollow = actorFollow
|
|
||||||
|
|
||||||
return notification
|
|
||||||
}
|
|
||||||
|
|
||||||
function emailSender (emails: string[]) {
|
|
||||||
return Emailer.Instance.addNewInstanceFollowerNotification(emails, actorFollow)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.notify({ users: admins, settingGetter, notificationCreator, emailSender })
|
|
||||||
}
|
|
||||||
|
|
||||||
private async notifyAdminsOfAutoInstanceFollowing (actorFollow: MActorFollowFull) {
|
|
||||||
const admins = await UserModel.listWithRight(UserRight.MANAGE_SERVER_FOLLOW)
|
|
||||||
|
|
||||||
logger.info('Notifying %d administrators of auto instance following: %s.', admins.length, actorFollow.ActorFollowing.url)
|
|
||||||
|
|
||||||
function settingGetter (user: MUserWithNotificationSetting) {
|
|
||||||
return user.NotificationSetting.autoInstanceFollowing
|
|
||||||
}
|
|
||||||
|
|
||||||
async function notificationCreator (user: MUserWithNotificationSetting) {
|
|
||||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
|
||||||
type: UserNotificationType.AUTO_INSTANCE_FOLLOWING,
|
|
||||||
userId: user.id,
|
|
||||||
actorFollowId: actorFollow.id
|
|
||||||
})
|
|
||||||
notification.ActorFollow = actorFollow
|
|
||||||
|
|
||||||
return notification
|
|
||||||
}
|
|
||||||
|
|
||||||
function emailSender (emails: string[]) {
|
|
||||||
return Emailer.Instance.addAutoInstanceFollowingNotification(emails, actorFollow)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.notify({ users: admins, settingGetter, notificationCreator, emailSender })
|
|
||||||
}
|
|
||||||
|
|
||||||
private async notifyModeratorsOfNewAbuse (parameters: {
|
|
||||||
abuse: UserAbuse
|
|
||||||
abuseInstance: MAbuseFull
|
|
||||||
reporter: string
|
|
||||||
}) {
|
|
||||||
const { abuse, abuseInstance } = parameters
|
|
||||||
|
|
||||||
const moderators = await UserModel.listWithRight(UserRight.MANAGE_ABUSES)
|
|
||||||
if (moderators.length === 0) return
|
|
||||||
|
|
||||||
const url = this.getAbuseUrl(abuseInstance)
|
|
||||||
|
|
||||||
logger.info('Notifying %s user/moderators of new abuse %s.', moderators.length, url)
|
|
||||||
|
|
||||||
function settingGetter (user: MUserWithNotificationSetting) {
|
|
||||||
return user.NotificationSetting.abuseAsModerator
|
|
||||||
}
|
|
||||||
|
|
||||||
async function notificationCreator (user: MUserWithNotificationSetting) {
|
|
||||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
|
||||||
type: UserNotificationType.NEW_ABUSE_FOR_MODERATORS,
|
|
||||||
userId: user.id,
|
|
||||||
abuseId: abuse.id
|
|
||||||
})
|
|
||||||
notification.Abuse = abuseInstance
|
|
||||||
|
|
||||||
return notification
|
|
||||||
}
|
|
||||||
|
|
||||||
function emailSender (emails: string[]) {
|
|
||||||
return Emailer.Instance.addAbuseModeratorsNotification(emails, parameters)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
|
|
||||||
}
|
|
||||||
|
|
||||||
private async notifyReporterOfAbuseStateChange (abuse: MAbuseFull) {
|
|
||||||
// Only notify our users
|
|
||||||
if (abuse.ReporterAccount.isOwned() !== true) return
|
|
||||||
|
|
||||||
const url = this.getAbuseUrl(abuse)
|
|
||||||
|
|
||||||
logger.info('Notifying reporter of abuse % of state change.', url)
|
|
||||||
|
|
||||||
const reporter = await UserModel.loadByAccountActorId(abuse.ReporterAccount.actorId)
|
|
||||||
|
|
||||||
function settingGetter (user: MUserWithNotificationSetting) {
|
|
||||||
return user.NotificationSetting.abuseStateChange
|
|
||||||
}
|
|
||||||
|
|
||||||
async function notificationCreator (user: MUserWithNotificationSetting) {
|
|
||||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
|
||||||
type: UserNotificationType.ABUSE_STATE_CHANGE,
|
|
||||||
userId: user.id,
|
|
||||||
abuseId: abuse.id
|
|
||||||
})
|
|
||||||
notification.Abuse = abuse
|
|
||||||
|
|
||||||
return notification
|
|
||||||
}
|
|
||||||
|
|
||||||
function emailSender (emails: string[]) {
|
|
||||||
return Emailer.Instance.addAbuseStateChangeNotification(emails, abuse)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.notify({ users: [ reporter ], settingGetter, notificationCreator, emailSender })
|
|
||||||
}
|
|
||||||
|
|
||||||
private async notifyOfNewAbuseMessage (abuse: MAbuseFull, message: MAbuseMessage) {
|
|
||||||
const url = this.getAbuseUrl(abuse)
|
|
||||||
logger.info('Notifying reporter and moderators of new abuse message on %s.', url)
|
|
||||||
|
|
||||||
const accountMessage = await AccountModel.load(message.accountId)
|
|
||||||
|
|
||||||
function settingGetter (user: MUserWithNotificationSetting) {
|
|
||||||
return user.NotificationSetting.abuseNewMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
async function notificationCreator (user: MUserWithNotificationSetting) {
|
|
||||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
|
||||||
type: UserNotificationType.ABUSE_NEW_MESSAGE,
|
|
||||||
userId: user.id,
|
|
||||||
abuseId: abuse.id
|
|
||||||
})
|
|
||||||
notification.Abuse = abuse
|
|
||||||
|
|
||||||
return notification
|
|
||||||
}
|
|
||||||
|
|
||||||
function emailSenderReporter (emails: string[]) {
|
|
||||||
return Emailer.Instance.addAbuseNewMessageNotification(emails, { target: 'reporter', abuse, message, accountMessage })
|
|
||||||
}
|
|
||||||
|
|
||||||
function emailSenderModerators (emails: string[]) {
|
|
||||||
return Emailer.Instance.addAbuseNewMessageNotification(emails, { target: 'moderator', abuse, message, accountMessage })
|
|
||||||
}
|
|
||||||
|
|
||||||
async function buildReporterOptions () {
|
|
||||||
// Only notify our users
|
|
||||||
if (abuse.ReporterAccount.isOwned() !== true) return undefined
|
|
||||||
|
|
||||||
const reporter = await UserModel.loadByAccountActorId(abuse.ReporterAccount.actorId)
|
|
||||||
// Don't notify my own message
|
|
||||||
if (reporter.Account.id === message.accountId) return undefined
|
|
||||||
|
|
||||||
return { users: [ reporter ], settingGetter, notificationCreator, emailSender: emailSenderReporter }
|
|
||||||
}
|
|
||||||
|
|
||||||
async function buildModeratorsOptions () {
|
|
||||||
let moderators = await UserModel.listWithRight(UserRight.MANAGE_ABUSES)
|
|
||||||
// Don't notify my own message
|
|
||||||
moderators = moderators.filter(m => m.Account.id !== message.accountId)
|
|
||||||
|
|
||||||
if (moderators.length === 0) return undefined
|
|
||||||
|
|
||||||
return { users: moderators, settingGetter, notificationCreator, emailSender: emailSenderModerators }
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = await Promise.all([
|
|
||||||
buildReporterOptions(),
|
|
||||||
buildModeratorsOptions()
|
|
||||||
])
|
|
||||||
|
|
||||||
return Promise.all(
|
|
||||||
options
|
|
||||||
.filter(opt => !!opt)
|
|
||||||
.map(opt => this.notify(opt))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private async notifyModeratorsOfVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo) {
|
|
||||||
const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_BLACKLIST)
|
|
||||||
if (moderators.length === 0) return
|
|
||||||
|
|
||||||
logger.info('Notifying %s moderators of video auto-blacklist %s.', moderators.length, videoBlacklist.Video.url)
|
|
||||||
|
|
||||||
function settingGetter (user: MUserWithNotificationSetting) {
|
|
||||||
return user.NotificationSetting.videoAutoBlacklistAsModerator
|
|
||||||
}
|
|
||||||
|
|
||||||
async function notificationCreator (user: MUserWithNotificationSetting) {
|
|
||||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
|
||||||
type: UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS,
|
|
||||||
userId: user.id,
|
|
||||||
videoBlacklistId: videoBlacklist.id
|
|
||||||
})
|
|
||||||
notification.VideoBlacklist = videoBlacklist
|
|
||||||
|
|
||||||
return notification
|
|
||||||
}
|
|
||||||
|
|
||||||
function emailSender (emails: string[]) {
|
|
||||||
return Emailer.Instance.addVideoAutoBlacklistModeratorsNotification(emails, videoBlacklist)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
|
|
||||||
}
|
|
||||||
|
|
||||||
private async notifyVideoOwnerOfBlacklist (videoBlacklist: MVideoBlacklistVideo) {
|
|
||||||
const user = await UserModel.loadByVideoId(videoBlacklist.videoId)
|
|
||||||
if (!user) return
|
|
||||||
|
|
||||||
logger.info('Notifying user %s that its video %s has been blacklisted.', user.username, videoBlacklist.Video.url)
|
|
||||||
|
|
||||||
function settingGetter (user: MUserWithNotificationSetting) {
|
|
||||||
return user.NotificationSetting.blacklistOnMyVideo
|
|
||||||
}
|
|
||||||
|
|
||||||
async function notificationCreator (user: MUserWithNotificationSetting) {
|
|
||||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
|
||||||
type: UserNotificationType.BLACKLIST_ON_MY_VIDEO,
|
|
||||||
userId: user.id,
|
|
||||||
videoBlacklistId: videoBlacklist.id
|
|
||||||
})
|
|
||||||
notification.VideoBlacklist = videoBlacklist
|
|
||||||
|
|
||||||
return notification
|
|
||||||
}
|
|
||||||
|
|
||||||
function emailSender (emails: string[]) {
|
|
||||||
return Emailer.Instance.addVideoBlacklistNotification(emails, videoBlacklist)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
|
|
||||||
}
|
|
||||||
|
|
||||||
private async notifyVideoOwnerOfUnblacklist (video: MVideoFullLight) {
|
|
||||||
const user = await UserModel.loadByVideoId(video.id)
|
|
||||||
if (!user) return
|
|
||||||
|
|
||||||
logger.info('Notifying user %s that its video %s has been unblacklisted.', user.username, video.url)
|
|
||||||
|
|
||||||
function settingGetter (user: MUserWithNotificationSetting) {
|
|
||||||
return user.NotificationSetting.blacklistOnMyVideo
|
|
||||||
}
|
|
||||||
|
|
||||||
async function notificationCreator (user: MUserWithNotificationSetting) {
|
|
||||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
|
||||||
type: UserNotificationType.UNBLACKLIST_ON_MY_VIDEO,
|
|
||||||
userId: user.id,
|
|
||||||
videoId: video.id
|
|
||||||
})
|
|
||||||
notification.Video = video
|
|
||||||
|
|
||||||
return notification
|
|
||||||
}
|
|
||||||
|
|
||||||
function emailSender (emails: string[]) {
|
|
||||||
return Emailer.Instance.addVideoUnblacklistNotification(emails, video)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
|
|
||||||
}
|
|
||||||
|
|
||||||
private async notifyOwnedVideoHasBeenPublished (video: MVideoFullLight) {
|
|
||||||
const user = await UserModel.loadByVideoId(video.id)
|
|
||||||
if (!user) return
|
|
||||||
|
|
||||||
logger.info('Notifying user %s of the publication of its video %s.', user.username, video.url)
|
|
||||||
|
|
||||||
function settingGetter (user: MUserWithNotificationSetting) {
|
|
||||||
return user.NotificationSetting.myVideoPublished
|
|
||||||
}
|
|
||||||
|
|
||||||
async function notificationCreator (user: MUserWithNotificationSetting) {
|
|
||||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
|
||||||
type: UserNotificationType.MY_VIDEO_PUBLISHED,
|
|
||||||
userId: user.id,
|
|
||||||
videoId: video.id
|
|
||||||
})
|
|
||||||
notification.Video = video
|
|
||||||
|
|
||||||
return notification
|
|
||||||
}
|
|
||||||
|
|
||||||
function emailSender (emails: string[]) {
|
|
||||||
return Emailer.Instance.myVideoPublishedNotification(emails, video)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
|
|
||||||
}
|
|
||||||
|
|
||||||
private async notifyOwnerVideoImportIsFinished (videoImport: MVideoImportVideo, success: boolean) {
|
|
||||||
const user = await UserModel.loadByVideoImportId(videoImport.id)
|
|
||||||
if (!user) return
|
|
||||||
|
|
||||||
logger.info('Notifying user %s its video import %s is finished.', user.username, videoImport.getTargetIdentifier())
|
|
||||||
|
|
||||||
function settingGetter (user: MUserWithNotificationSetting) {
|
|
||||||
return user.NotificationSetting.myVideoImportFinished
|
|
||||||
}
|
|
||||||
|
|
||||||
async function notificationCreator (user: MUserWithNotificationSetting) {
|
|
||||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
|
||||||
type: success ? UserNotificationType.MY_VIDEO_IMPORT_SUCCESS : UserNotificationType.MY_VIDEO_IMPORT_ERROR,
|
|
||||||
userId: user.id,
|
|
||||||
videoImportId: videoImport.id
|
|
||||||
})
|
|
||||||
notification.VideoImport = videoImport
|
|
||||||
|
|
||||||
return notification
|
|
||||||
}
|
|
||||||
|
|
||||||
function emailSender (emails: string[]) {
|
|
||||||
return success
|
|
||||||
? Emailer.Instance.myVideoImportSuccessNotification(emails, videoImport)
|
|
||||||
: Emailer.Instance.myVideoImportErrorNotification(emails, videoImport)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
|
|
||||||
}
|
|
||||||
|
|
||||||
private async notifyModeratorsOfNewUserRegistration (registeredUser: MUserDefault) {
|
|
||||||
const moderators = await UserModel.listWithRight(UserRight.MANAGE_USERS)
|
|
||||||
if (moderators.length === 0) return
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
'Notifying %s moderators of new user registration of %s.',
|
|
||||||
moderators.length, registeredUser.username
|
|
||||||
)
|
|
||||||
|
|
||||||
function settingGetter (user: MUserWithNotificationSetting) {
|
|
||||||
return user.NotificationSetting.newUserRegistration
|
|
||||||
}
|
|
||||||
|
|
||||||
async function notificationCreator (user: MUserWithNotificationSetting) {
|
|
||||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
|
||||||
type: UserNotificationType.NEW_USER_REGISTRATION,
|
|
||||||
userId: user.id,
|
|
||||||
accountId: registeredUser.Account.id
|
|
||||||
})
|
|
||||||
notification.Account = registeredUser.Account
|
|
||||||
|
|
||||||
return notification
|
|
||||||
}
|
|
||||||
|
|
||||||
function emailSender (emails: string[]) {
|
|
||||||
return Emailer.Instance.addNewUserRegistrationNotification(emails, registeredUser)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
|
|
||||||
}
|
|
||||||
|
|
||||||
private async notifyAdminsOfNewPeerTubeVersion (application: MApplication, latestVersion: string) {
|
|
||||||
// Use the debug right to know who is an administrator
|
|
||||||
const admins = await UserModel.listWithRight(UserRight.MANAGE_DEBUG)
|
|
||||||
if (admins.length === 0) return
|
|
||||||
|
|
||||||
logger.info('Notifying %s admins of new PeerTube version %s.', admins.length, latestVersion)
|
|
||||||
|
|
||||||
function settingGetter (user: MUserWithNotificationSetting) {
|
|
||||||
return user.NotificationSetting.newPeerTubeVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
async function notificationCreator (user: MUserWithNotificationSetting) {
|
|
||||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
|
||||||
type: UserNotificationType.NEW_PEERTUBE_VERSION,
|
|
||||||
userId: user.id,
|
|
||||||
applicationId: application.id
|
|
||||||
})
|
|
||||||
notification.Application = application
|
|
||||||
|
|
||||||
return notification
|
|
||||||
}
|
|
||||||
|
|
||||||
function emailSender (emails: string[]) {
|
|
||||||
return Emailer.Instance.addNewPeerTubeVersionNotification(emails, latestVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.notify({ users: admins, settingGetter, notificationCreator, emailSender })
|
|
||||||
}
|
|
||||||
|
|
||||||
private async notifyAdminsOfNewPluginVersion (plugin: MPlugin) {
|
|
||||||
// Use the debug right to know who is an administrator
|
|
||||||
const admins = await UserModel.listWithRight(UserRight.MANAGE_DEBUG)
|
|
||||||
if (admins.length === 0) return
|
|
||||||
|
|
||||||
logger.info('Notifying %s admins of new plugin version %s@%s.', admins.length, plugin.name, plugin.latestVersion)
|
|
||||||
|
|
||||||
function settingGetter (user: MUserWithNotificationSetting) {
|
|
||||||
return user.NotificationSetting.newPluginVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
async function notificationCreator (user: MUserWithNotificationSetting) {
|
|
||||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
|
||||||
type: UserNotificationType.NEW_PLUGIN_VERSION,
|
|
||||||
userId: user.id,
|
|
||||||
pluginId: plugin.id
|
|
||||||
})
|
|
||||||
notification.Plugin = plugin
|
|
||||||
|
|
||||||
return notification
|
|
||||||
}
|
|
||||||
|
|
||||||
function emailSender (emails: string[]) {
|
|
||||||
return Emailer.Instance.addNewPlugionVersionNotification(emails, plugin)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.notify({ users: admins, settingGetter, notificationCreator, emailSender })
|
|
||||||
}
|
|
||||||
|
|
||||||
private async notify<T extends MUserWithNotificationSetting> (options: {
|
|
||||||
users: T[]
|
|
||||||
notificationCreator: (user: T) => Promise<UserNotificationModelForApi>
|
|
||||||
emailSender: (emails: string[]) => void
|
|
||||||
settingGetter: (user: T) => UserNotificationSettingValue
|
|
||||||
}) {
|
|
||||||
const emails: string[] = []
|
|
||||||
|
|
||||||
for (const user of options.users) {
|
|
||||||
if (this.isWebNotificationEnabled(options.settingGetter(user))) {
|
|
||||||
const notification = await options.notificationCreator(user)
|
|
||||||
|
|
||||||
PeerTubeSocket.Instance.sendNotification(user.id, notification)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isEmailEnabled(user, options.settingGetter(user))) {
|
|
||||||
emails.push(user.email)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (emails.length !== 0) {
|
|
||||||
options.emailSender(emails)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private isEmailEnabled (user: MUser, value: UserNotificationSettingValue) {
|
|
||||||
if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION === true && user.emailVerified === false) return false
|
|
||||||
|
|
||||||
return value & UserNotificationSettingValue.EMAIL
|
|
||||||
}
|
|
||||||
|
|
||||||
private isWebNotificationEnabled (value: UserNotificationSettingValue) {
|
|
||||||
return value & UserNotificationSettingValue.WEB
|
|
||||||
}
|
|
||||||
|
|
||||||
private isBlockedByServerOrUser (targetAccount: MAccountServer, user?: MUserAccount) {
|
|
||||||
return isBlockedByServerOrAccount(targetAccount, user?.Account)
|
|
||||||
}
|
|
||||||
|
|
||||||
private getAbuseUrl (abuse: MAbuseFull) {
|
|
||||||
return abuse.VideoAbuse?.Video?.url ||
|
|
||||||
abuse.VideoCommentAbuse?.VideoComment?.url ||
|
|
||||||
abuse.FlaggedAccount.Actor.url
|
|
||||||
}
|
|
||||||
|
|
||||||
static get Instance () {
|
|
||||||
return this.instance || (this.instance = new this())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
export {
|
|
||||||
Notifier
|
|
||||||
}
|
|
1
server/lib/notifier/index.ts
Normal file
1
server/lib/notifier/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from './notifier'
|
259
server/lib/notifier/notifier.ts
Normal file
259
server/lib/notifier/notifier.ts
Normal file
|
@ -0,0 +1,259 @@
|
||||||
|
import { MUser, MUserDefault } from '@server/types/models/user'
|
||||||
|
import { MVideoBlacklistLightVideo, MVideoBlacklistVideo } from '@server/types/models/video/video-blacklist'
|
||||||
|
import { UserNotificationSettingValue } from '../../../shared/models/users'
|
||||||
|
import { logger } from '../../helpers/logger'
|
||||||
|
import { CONFIG } from '../../initializers/config'
|
||||||
|
import { MAbuseFull, MAbuseMessage, MActorFollowFull, MApplication, MPlugin } from '../../types/models'
|
||||||
|
import { MCommentOwnerVideo, MVideoAccountLight, MVideoFullLight } from '../../types/models/video'
|
||||||
|
import { JobQueue } from '../job-queue'
|
||||||
|
import { PeerTubeSocket } from '../peertube-socket'
|
||||||
|
import {
|
||||||
|
AbstractNotification,
|
||||||
|
AbuseStateChangeForReporter,
|
||||||
|
AutoFollowForInstance,
|
||||||
|
CommentMention,
|
||||||
|
FollowForInstance,
|
||||||
|
FollowForUser,
|
||||||
|
ImportFinishedForOwner,
|
||||||
|
ImportFinishedForOwnerPayload,
|
||||||
|
NewAbuseForModerators,
|
||||||
|
NewAbuseMessageForModerators,
|
||||||
|
NewAbuseMessageForReporter,
|
||||||
|
NewAbusePayload,
|
||||||
|
NewAutoBlacklistForModerators,
|
||||||
|
NewBlacklistForOwner,
|
||||||
|
NewCommentForVideoOwner,
|
||||||
|
NewPeerTubeVersionForAdmins,
|
||||||
|
NewPluginVersionForAdmins,
|
||||||
|
NewVideoForSubscribers,
|
||||||
|
OwnedPublicationAfterAutoUnblacklist,
|
||||||
|
OwnedPublicationAfterScheduleUpdate,
|
||||||
|
OwnedPublicationAfterTranscoding,
|
||||||
|
RegistrationForModerators,
|
||||||
|
UnblacklistForOwner
|
||||||
|
} from './shared'
|
||||||
|
|
||||||
|
class Notifier {
|
||||||
|
|
||||||
|
private readonly notificationModels = {
|
||||||
|
newVideo: [ NewVideoForSubscribers ],
|
||||||
|
publicationAfterTranscoding: [ OwnedPublicationAfterTranscoding ],
|
||||||
|
publicationAfterScheduleUpdate: [ OwnedPublicationAfterScheduleUpdate ],
|
||||||
|
publicationAfterAutoUnblacklist: [ OwnedPublicationAfterAutoUnblacklist ],
|
||||||
|
newComment: [ CommentMention, NewCommentForVideoOwner ],
|
||||||
|
newAbuse: [ NewAbuseForModerators ],
|
||||||
|
newBlacklist: [ NewBlacklistForOwner ],
|
||||||
|
unblacklist: [ UnblacklistForOwner ],
|
||||||
|
importFinished: [ ImportFinishedForOwner ],
|
||||||
|
userRegistration: [ RegistrationForModerators ],
|
||||||
|
userFollow: [ FollowForUser ],
|
||||||
|
instanceFollow: [ FollowForInstance ],
|
||||||
|
autoInstanceFollow: [ AutoFollowForInstance ],
|
||||||
|
newAutoBlacklist: [ NewAutoBlacklistForModerators ],
|
||||||
|
abuseStateChange: [ AbuseStateChangeForReporter ],
|
||||||
|
newAbuseMessage: [ NewAbuseMessageForReporter, NewAbuseMessageForModerators ],
|
||||||
|
newPeertubeVersion: [ NewPeerTubeVersionForAdmins ],
|
||||||
|
newPluginVersion: [ NewPluginVersionForAdmins ]
|
||||||
|
}
|
||||||
|
|
||||||
|
private static instance: Notifier
|
||||||
|
|
||||||
|
private constructor () {
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyOnNewVideoIfNeeded (video: MVideoAccountLight): void {
|
||||||
|
const models = this.notificationModels.newVideo
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
this.sendNotifications(models, comment)
|
||||||
|
.catch(err => logger.error('Cannot notify of new comment.', comment.url, { err }))
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyOnNewAbuse (payload: NewAbusePayload): void {
|
||||||
|
const models = this.notificationModels.newAbuse
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
this.sendNotifications(models, payload)
|
||||||
|
.catch(err => {
|
||||||
|
logger.error('Cannot notify owner that its video import %s is finished.', payload.videoImport.getTargetIdentifier(), { err })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyOnNewUserRegistration (user: MUserDefault): void {
|
||||||
|
const models = this.notificationModels.userRegistration
|
||||||
|
|
||||||
|
this.sendNotifications(models, user)
|
||||||
|
.catch(err => logger.error('Cannot notify moderators of new user registration (%s).', user.username, { err }))
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyOfNewUserFollow (actorFollow: MActorFollowFull): void {
|
||||||
|
const models = this.notificationModels.userFollow
|
||||||
|
|
||||||
|
this.sendNotifications(models, actorFollow)
|
||||||
|
.catch(err => {
|
||||||
|
logger.error(
|
||||||
|
'Cannot notify owner of channel %s of a new follow by %s.',
|
||||||
|
actorFollow.ActorFollowing.VideoChannel.getDisplayName(),
|
||||||
|
actorFollow.ActorFollower.Account.getDisplayName(),
|
||||||
|
{ err }
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyOfNewInstanceFollow (actorFollow: MActorFollowFull): void {
|
||||||
|
const models = this.notificationModels.instanceFollow
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
this.sendNotifications(models, { application, latestVersion })
|
||||||
|
.catch(err => logger.error('Cannot notify on new PeerTubeb version %s.', latestVersion, { err }))
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyOfNewPluginVersion (plugin: MPlugin) {
|
||||||
|
const models = this.notificationModels.newPluginVersion
|
||||||
|
|
||||||
|
this.sendNotifications(models, plugin)
|
||||||
|
.catch(err => logger.error('Cannot notify on new plugin version %s.', plugin.name, { 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)
|
||||||
|
|
||||||
|
if (this.isWebNotificationEnabled(setting)) {
|
||||||
|
const notification = await object.createNotification(user)
|
||||||
|
|
||||||
|
PeerTubeSocket.Instance.sendNotification(user.id, notification)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isEmailEnabled(user, setting)) {
|
||||||
|
toEmails.push(user.email)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const to of toEmails) {
|
||||||
|
const payload = await object.createEmail(to)
|
||||||
|
JobQueue.Instance.createJob({ type: 'email', payload })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private isEmailEnabled (user: MUser, value: UserNotificationSettingValue) {
|
||||||
|
if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION === true && user.emailVerified === false) return false
|
||||||
|
|
||||||
|
return value & UserNotificationSettingValue.EMAIL
|
||||||
|
}
|
||||||
|
|
||||||
|
private isWebNotificationEnabled (value: UserNotificationSettingValue) {
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { WEBSERVER } from '@server/initializers/constants'
|
||||||
|
import { AccountModel } from '@server/models/account/account'
|
||||||
|
import { UserNotificationModel } from '@server/models/user/user-notification'
|
||||||
|
import { MAbuseFull, MAbuseMessage, MAccountDefault, MUserWithNotificationSetting, UserNotificationModelForApi } from '@server/types/models'
|
||||||
|
import { UserNotificationType } from '@shared/models'
|
||||||
|
import { AbstractNotification } from '../common/abstract-notification'
|
||||||
|
|
||||||
|
export 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
|
||||||
|
}
|
||||||
|
|
||||||
|
async createNotification (user: MUserWithNotificationSetting) {
|
||||||
|
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||||
|
type: UserNotificationType.ABUSE_NEW_MESSAGE,
|
||||||
|
userId: user.id,
|
||||||
|
abuseId: this.abuse.id
|
||||||
|
})
|
||||||
|
notification.Abuse = this.abuse
|
||||||
|
|
||||||
|
return notification
|
||||||
|
}
|
||||||
|
|
||||||
|
protected createEmailFor (to: string, target: 'moderator' | 'reporter') {
|
||||||
|
const text = 'New message on report #' + this.abuse.id
|
||||||
|
const abuseUrl = target === 'moderator'
|
||||||
|
? WEBSERVER.URL + '/admin/moderation/abuses/list?search=%23' + this.abuse.id
|
||||||
|
: WEBSERVER.URL + '/my-account/abuses?search=%23' + this.abuse.id
|
||||||
|
|
||||||
|
const action = {
|
||||||
|
text,
|
||||||
|
url: abuseUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
template: 'abuse-new-message',
|
||||||
|
to,
|
||||||
|
subject: text,
|
||||||
|
locals: {
|
||||||
|
abuseId: this.abuse.id,
|
||||||
|
abuseUrl: action.url,
|
||||||
|
messageAccountName: this.messageAccount.getDisplayName(),
|
||||||
|
messageText: this.message.message,
|
||||||
|
action
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get abuse () {
|
||||||
|
return this.payload.abuse
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get message () {
|
||||||
|
return this.payload.message
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
import { logger } from '@server/helpers/logger'
|
||||||
|
import { WEBSERVER } from '@server/initializers/constants'
|
||||||
|
import { getAbuseTargetUrl } from '@server/lib/activitypub/url'
|
||||||
|
import { UserModel } from '@server/models/user/user'
|
||||||
|
import { UserNotificationModel } from '@server/models/user/user-notification'
|
||||||
|
import { MAbuseFull, MUserDefault, MUserWithNotificationSetting, UserNotificationModelForApi } from '@server/types/models'
|
||||||
|
import { AbuseState, UserNotificationType } from '@shared/models'
|
||||||
|
import { AbstractNotification } from '../common/abstract-notification'
|
||||||
|
|
||||||
|
export class AbuseStateChangeForReporter extends AbstractNotification <MAbuseFull> {
|
||||||
|
|
||||||
|
private user: MUserDefault
|
||||||
|
|
||||||
|
async prepare () {
|
||||||
|
const reporter = this.abuse.ReporterAccount
|
||||||
|
if (reporter.isOwned() !== true) return
|
||||||
|
|
||||||
|
this.user = await UserModel.loadByAccountActorId(this.abuse.ReporterAccount.actorId)
|
||||||
|
}
|
||||||
|
|
||||||
|
log () {
|
||||||
|
logger.info('Notifying reporter of abuse % of state change.', getAbuseTargetUrl(this.abuse))
|
||||||
|
}
|
||||||
|
|
||||||
|
getSetting (user: MUserWithNotificationSetting) {
|
||||||
|
return user.NotificationSetting.abuseStateChange
|
||||||
|
}
|
||||||
|
|
||||||
|
getTargetUsers () {
|
||||||
|
if (!this.user) return []
|
||||||
|
|
||||||
|
return [ this.user ]
|
||||||
|
}
|
||||||
|
|
||||||
|
async createNotification (user: MUserWithNotificationSetting) {
|
||||||
|
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||||
|
type: UserNotificationType.ABUSE_STATE_CHANGE,
|
||||||
|
userId: user.id,
|
||||||
|
abuseId: this.abuse.id
|
||||||
|
})
|
||||||
|
notification.Abuse = this.abuse
|
||||||
|
|
||||||
|
return notification
|
||||||
|
}
|
||||||
|
|
||||||
|
createEmail (to: string) {
|
||||||
|
const text = this.abuse.state === AbuseState.ACCEPTED
|
||||||
|
? 'Report #' + this.abuse.id + ' has been accepted'
|
||||||
|
: 'Report #' + this.abuse.id + ' has been rejected'
|
||||||
|
|
||||||
|
const abuseUrl = WEBSERVER.URL + '/my-account/abuses?search=%23' + this.abuse.id
|
||||||
|
|
||||||
|
const action = {
|
||||||
|
text,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
4
server/lib/notifier/shared/abuse/index.ts
Normal file
4
server/lib/notifier/shared/abuse/index.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export * from './abuse-state-change-for-reporter'
|
||||||
|
export * from './new-abuse-for-moderators'
|
||||||
|
export * from './new-abuse-message-for-reporter'
|
||||||
|
export * from './new-abuse-message-for-moderators'
|
119
server/lib/notifier/shared/abuse/new-abuse-for-moderators.ts
Normal file
119
server/lib/notifier/shared/abuse/new-abuse-for-moderators.ts
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
import { logger } from '@server/helpers/logger'
|
||||||
|
import { WEBSERVER } from '@server/initializers/constants'
|
||||||
|
import { getAbuseTargetUrl } from '@server/lib/activitypub/url'
|
||||||
|
import { UserModel } from '@server/models/user/user'
|
||||||
|
import { UserNotificationModel } from '@server/models/user/user-notification'
|
||||||
|
import { MAbuseFull, MUserDefault, MUserWithNotificationSetting, UserNotificationModelForApi } from '@server/types/models'
|
||||||
|
import { UserAbuse, UserNotificationType, UserRight } from '@shared/models'
|
||||||
|
import { AbstractNotification } from '../common/abstract-notification'
|
||||||
|
|
||||||
|
export type NewAbusePayload = { abuse: UserAbuse, 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, getAbuseTargetUrl(this.payload.abuseInstance))
|
||||||
|
}
|
||||||
|
|
||||||
|
getSetting (user: MUserWithNotificationSetting) {
|
||||||
|
return user.NotificationSetting.abuseAsModerator
|
||||||
|
}
|
||||||
|
|
||||||
|
getTargetUsers () {
|
||||||
|
return this.moderators
|
||||||
|
}
|
||||||
|
|
||||||
|
async createNotification (user: MUserWithNotificationSetting) {
|
||||||
|
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||||
|
type: UserNotificationType.NEW_ABUSE_FOR_MODERATORS,
|
||||||
|
userId: user.id,
|
||||||
|
abuseId: this.payload.abuseInstance.id
|
||||||
|
})
|
||||||
|
notification.Abuse = this.payload.abuseInstance
|
||||||
|
|
||||||
|
return notification
|
||||||
|
}
|
||||||
|
|
||||||
|
createEmail (to: string) {
|
||||||
|
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: string) {
|
||||||
|
const video = this.payload.abuseInstance.VideoAbuse.Video
|
||||||
|
const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
|
||||||
|
|
||||||
|
return {
|
||||||
|
template: 'video-abuse-new',
|
||||||
|
to,
|
||||||
|
subject: `New video abuse report from ${this.payload.reporter}`,
|
||||||
|
locals: {
|
||||||
|
videoUrl,
|
||||||
|
isLocal: video.remote === false,
|
||||||
|
videoCreatedAt: new Date(video.createdAt).toLocaleString(),
|
||||||
|
videoPublishedAt: new Date(video.publishedAt).toLocaleString(),
|
||||||
|
videoName: video.name,
|
||||||
|
reason: this.payload.abuse.reason,
|
||||||
|
videoChannel: this.payload.abuse.video.channel,
|
||||||
|
reporter: this.payload.reporter,
|
||||||
|
action: this.buildEmailAction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private createCommentAbuseEmail (to: string) {
|
||||||
|
const comment = this.payload.abuseInstance.VideoCommentAbuse.VideoComment
|
||||||
|
const commentUrl = WEBSERVER.URL + comment.Video.getWatchStaticPath() + ';threadId=' + comment.getThreadId()
|
||||||
|
|
||||||
|
return {
|
||||||
|
template: 'video-comment-abuse-new',
|
||||||
|
to,
|
||||||
|
subject: `New comment abuse report from ${this.payload.reporter}`,
|
||||||
|
locals: {
|
||||||
|
commentUrl,
|
||||||
|
videoName: comment.Video.name,
|
||||||
|
isLocal: comment.isOwned(),
|
||||||
|
commentCreatedAt: new Date(comment.createdAt).toLocaleString(),
|
||||||
|
reason: this.payload.abuse.reason,
|
||||||
|
flaggedAccount: this.payload.abuseInstance.FlaggedAccount.getDisplayName(),
|
||||||
|
reporter: this.payload.reporter,
|
||||||
|
action: this.buildEmailAction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private createAccountAbuseEmail (to: string) {
|
||||||
|
const account = this.payload.abuseInstance.FlaggedAccount
|
||||||
|
const accountUrl = account.getClientUrl()
|
||||||
|
|
||||||
|
return {
|
||||||
|
template: 'account-abuse-new',
|
||||||
|
to,
|
||||||
|
subject: `New account abuse report from ${this.payload.reporter}`,
|
||||||
|
locals: {
|
||||||
|
accountUrl,
|
||||||
|
accountDisplayName: account.getDisplayName(),
|
||||||
|
isLocal: account.isOwned(),
|
||||||
|
reason: this.payload.abuse.reason,
|
||||||
|
reporter: this.payload.reporter,
|
||||||
|
action: this.buildEmailAction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildEmailAction () {
|
||||||
|
return {
|
||||||
|
text: 'View report #' + this.payload.abuseInstance.id,
|
||||||
|
url: WEBSERVER.URL + '/admin/moderation/abuses/list?search=%23' + this.payload.abuseInstance.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { logger } from '@server/helpers/logger'
|
||||||
|
import { getAbuseTargetUrl } from '@server/lib/activitypub/url'
|
||||||
|
import { UserModel } from '@server/models/user/user'
|
||||||
|
import { MUserDefault } from '@server/types/models'
|
||||||
|
import { UserRight } from '@shared/models'
|
||||||
|
import { AbstractNewAbuseMessage } from './abstract-new-abuse-message'
|
||||||
|
|
||||||
|
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.', getAbuseTargetUrl(this.abuse))
|
||||||
|
}
|
||||||
|
|
||||||
|
getTargetUsers () {
|
||||||
|
return this.moderators
|
||||||
|
}
|
||||||
|
|
||||||
|
createEmail (to: string) {
|
||||||
|
return this.createEmailFor(to, 'moderator')
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { logger } from '@server/helpers/logger'
|
||||||
|
import { getAbuseTargetUrl } from '@server/lib/activitypub/url'
|
||||||
|
import { UserModel } from '@server/models/user/user'
|
||||||
|
import { MUserDefault } from '@server/types/models'
|
||||||
|
import { AbstractNewAbuseMessage } from './abstract-new-abuse-message'
|
||||||
|
|
||||||
|
export class NewAbuseMessageForReporter extends AbstractNewAbuseMessage {
|
||||||
|
private reporter: MUserDefault
|
||||||
|
|
||||||
|
async prepare () {
|
||||||
|
// Only notify our users
|
||||||
|
if (this.abuse.ReporterAccount.isOwned() !== true) return
|
||||||
|
|
||||||
|
await this.loadMessageAccount()
|
||||||
|
|
||||||
|
const reporter = await UserModel.loadByAccountActorId(this.abuse.ReporterAccount.actorId)
|
||||||
|
// 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.', getAbuseTargetUrl(this.abuse))
|
||||||
|
}
|
||||||
|
|
||||||
|
getTargetUsers () {
|
||||||
|
if (!this.reporter) return []
|
||||||
|
|
||||||
|
return [ this.reporter ]
|
||||||
|
}
|
||||||
|
|
||||||
|
createEmail (to: string) {
|
||||||
|
return this.createEmailFor(to, 'reporter')
|
||||||
|
}
|
||||||
|
}
|
3
server/lib/notifier/shared/blacklist/index.ts
Normal file
3
server/lib/notifier/shared/blacklist/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export * from './new-auto-blacklist-for-moderators'
|
||||||
|
export * from './new-blacklist-for-owner'
|
||||||
|
export * from './unblacklist-for-owner'
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { logger } from '@server/helpers/logger'
|
||||||
|
import { WEBSERVER } from '@server/initializers/constants'
|
||||||
|
import { UserModel } from '@server/models/user/user'
|
||||||
|
import { UserNotificationModel } from '@server/models/user/user-notification'
|
||||||
|
import { VideoChannelModel } from '@server/models/video/video-channel'
|
||||||
|
import { MUserDefault, MUserWithNotificationSetting, MVideoBlacklistLightVideo, UserNotificationModelForApi } from '@server/types/models'
|
||||||
|
import { UserNotificationType, UserRight } from '@shared/models'
|
||||||
|
import { AbstractNotification } from '../common/abstract-notification'
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
async createNotification (user: MUserWithNotificationSetting) {
|
||||||
|
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||||
|
type: UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS,
|
||||||
|
userId: user.id,
|
||||||
|
videoBlacklistId: this.payload.id
|
||||||
|
})
|
||||||
|
notification.VideoBlacklist = this.payload
|
||||||
|
|
||||||
|
return notification
|
||||||
|
}
|
||||||
|
|
||||||
|
async createEmail (to: string) {
|
||||||
|
const videoAutoBlacklistUrl = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list'
|
||||||
|
const videoUrl = WEBSERVER.URL + this.payload.Video.getWatchStaticPath()
|
||||||
|
const channel = await VideoChannelModel.loadAndPopulateAccount(this.payload.Video.channelId)
|
||||||
|
|
||||||
|
return {
|
||||||
|
template: 'video-auto-blacklist-new',
|
||||||
|
to,
|
||||||
|
subject: 'A new video is pending moderation',
|
||||||
|
locals: {
|
||||||
|
channel: channel.toFormattedSummaryJSON(),
|
||||||
|
videoUrl,
|
||||||
|
videoName: this.payload.Video.name,
|
||||||
|
action: {
|
||||||
|
text: 'Review autoblacklist',
|
||||||
|
url: videoAutoBlacklistUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { logger } from '@server/helpers/logger'
|
||||||
|
import { CONFIG } from '@server/initializers/config'
|
||||||
|
import { WEBSERVER } from '@server/initializers/constants'
|
||||||
|
import { UserModel } from '@server/models/user/user'
|
||||||
|
import { UserNotificationModel } from '@server/models/user/user-notification'
|
||||||
|
import { MUserDefault, MUserWithNotificationSetting, MVideoBlacklistVideo, UserNotificationModelForApi } from '@server/types/models'
|
||||||
|
import { UserNotificationType } from '@shared/models'
|
||||||
|
import { AbstractNotification } from '../common/abstract-notification'
|
||||||
|
|
||||||
|
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 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
async createNotification (user: MUserWithNotificationSetting) {
|
||||||
|
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||||
|
type: UserNotificationType.BLACKLIST_ON_MY_VIDEO,
|
||||||
|
userId: user.id,
|
||||||
|
videoBlacklistId: this.payload.id
|
||||||
|
})
|
||||||
|
notification.VideoBlacklist = this.payload
|
||||||
|
|
||||||
|
return notification
|
||||||
|
}
|
||||||
|
|
||||||
|
createEmail (to: string) {
|
||||||
|
const videoName = this.payload.Video.name
|
||||||
|
const videoUrl = WEBSERVER.URL + this.payload.Video.getWatchStaticPath()
|
||||||
|
|
||||||
|
const reasonString = this.payload.reason ? ` for the following reason: ${this.payload.reason}` : ''
|
||||||
|
const blockedString = `Your video ${videoName} (${videoUrl} on ${CONFIG.INSTANCE.NAME} has been blacklisted${reasonString}.`
|
||||||
|
|
||||||
|
return {
|
||||||
|
to,
|
||||||
|
subject: `Video ${videoName} blacklisted`,
|
||||||
|
text: blockedString,
|
||||||
|
locals: {
|
||||||
|
title: 'Your video was blacklisted'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { logger } from '@server/helpers/logger'
|
||||||
|
import { CONFIG } from '@server/initializers/config'
|
||||||
|
import { WEBSERVER } from '@server/initializers/constants'
|
||||||
|
import { UserModel } from '@server/models/user/user'
|
||||||
|
import { UserNotificationModel } from '@server/models/user/user-notification'
|
||||||
|
import { MUserDefault, MUserWithNotificationSetting, MVideoFullLight, UserNotificationModelForApi } from '@server/types/models'
|
||||||
|
import { UserNotificationType } from '@shared/models'
|
||||||
|
import { AbstractNotification } from '../common/abstract-notification'
|
||||||
|
|
||||||
|
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 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
async createNotification (user: MUserWithNotificationSetting) {
|
||||||
|
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||||
|
type: UserNotificationType.UNBLACKLIST_ON_MY_VIDEO,
|
||||||
|
userId: user.id,
|
||||||
|
videoId: this.payload.id
|
||||||
|
})
|
||||||
|
notification.Video = this.payload
|
||||||
|
|
||||||
|
return notification
|
||||||
|
}
|
||||||
|
|
||||||
|
createEmail (to: string) {
|
||||||
|
const video = this.payload
|
||||||
|
const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
|
||||||
|
|
||||||
|
return {
|
||||||
|
to,
|
||||||
|
subject: `Video ${video.name} unblacklisted`,
|
||||||
|
text: `Your video "${video.name}" (${videoUrl}) on ${CONFIG.INSTANCE.NAME} has been unblacklisted.`,
|
||||||
|
locals: {
|
||||||
|
title: 'Your video was unblacklisted'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
111
server/lib/notifier/shared/comment/comment-mention.ts
Normal file
111
server/lib/notifier/shared/comment/comment-mention.ts
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
import { logger } from '@server/helpers/logger'
|
||||||
|
import { toSafeHtml } from '@server/helpers/markdown'
|
||||||
|
import { WEBSERVER } from '@server/initializers/constants'
|
||||||
|
import { AccountBlocklistModel } from '@server/models/account/account-blocklist'
|
||||||
|
import { getServerActor } from '@server/models/application/application'
|
||||||
|
import { ServerBlocklistModel } from '@server/models/server/server-blocklist'
|
||||||
|
import { UserModel } from '@server/models/user/user'
|
||||||
|
import { UserNotificationModel } from '@server/models/user/user-notification'
|
||||||
|
import {
|
||||||
|
MCommentOwnerVideo,
|
||||||
|
MUserDefault,
|
||||||
|
MUserNotifSettingAccount,
|
||||||
|
MUserWithNotificationSetting,
|
||||||
|
UserNotificationModelForApi
|
||||||
|
} from '@server/types/models'
|
||||||
|
import { UserNotificationSettingValue, UserNotificationType } from '@shared/models'
|
||||||
|
import { AbstractNotification } from '../common'
|
||||||
|
|
||||||
|
export class CommentMention extends AbstractNotification <MCommentOwnerVideo, MUserNotifSettingAccount> {
|
||||||
|
private users: MUserDefault[]
|
||||||
|
|
||||||
|
private serverAccountId: number
|
||||||
|
|
||||||
|
private accountMutedHash: { [ id: number ]: boolean }
|
||||||
|
private instanceMutedHash: { [ id: number ]: boolean }
|
||||||
|
|
||||||
|
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.isOwned()) {
|
||||||
|
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.isAccountMutedByMulti(sourceAccounts, this.payload.accountId)
|
||||||
|
this.instanceMutedHash = await ServerBlocklistModel.isServerMutedByMulti(sourceAccounts, this.payload.Account.Actor.serverId)
|
||||||
|
}
|
||||||
|
|
||||||
|
log () {
|
||||||
|
logger.info('Notifying %d users of new comment %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
|
||||||
|
}
|
||||||
|
|
||||||
|
async createNotification (user: MUserWithNotificationSetting) {
|
||||||
|
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||||
|
type: UserNotificationType.COMMENT_MENTION,
|
||||||
|
userId: user.id,
|
||||||
|
commentId: this.payload.id
|
||||||
|
})
|
||||||
|
notification.Comment = this.payload
|
||||||
|
|
||||||
|
return notification
|
||||||
|
}
|
||||||
|
|
||||||
|
createEmail (to: string) {
|
||||||
|
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: 'Mention on video ' + video.name,
|
||||||
|
locals: {
|
||||||
|
comment,
|
||||||
|
commentHtml,
|
||||||
|
video,
|
||||||
|
videoUrl,
|
||||||
|
accountName,
|
||||||
|
action: {
|
||||||
|
text: 'View comment',
|
||||||
|
url: commentUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2
server/lib/notifier/shared/comment/index.ts
Normal file
2
server/lib/notifier/shared/comment/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './comment-mention'
|
||||||
|
export * from './new-comment-for-video-owner'
|
|
@ -0,0 +1,76 @@
|
||||||
|
import { logger } from '@server/helpers/logger'
|
||||||
|
import { toSafeHtml } from '@server/helpers/markdown'
|
||||||
|
import { WEBSERVER } from '@server/initializers/constants'
|
||||||
|
import { isBlockedByServerOrAccount } from '@server/lib/blocklist'
|
||||||
|
import { UserModel } from '@server/models/user/user'
|
||||||
|
import { UserNotificationModel } from '@server/models/user/user-notification'
|
||||||
|
import { MCommentOwnerVideo, MUserDefault, MUserWithNotificationSetting, UserNotificationModelForApi } from '@server/types/models'
|
||||||
|
import { UserNotificationType } from '@shared/models'
|
||||||
|
import { AbstractNotification } from '../common/abstract-notification'
|
||||||
|
|
||||||
|
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.isOwned() === 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 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
async createNotification (user: MUserWithNotificationSetting) {
|
||||||
|
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||||
|
type: UserNotificationType.NEW_COMMENT_ON_MY_VIDEO,
|
||||||
|
userId: user.id,
|
||||||
|
commentId: this.payload.id
|
||||||
|
})
|
||||||
|
notification.Comment = this.payload
|
||||||
|
|
||||||
|
return notification
|
||||||
|
}
|
||||||
|
|
||||||
|
createEmail (to: string) {
|
||||||
|
const video = this.payload.Video
|
||||||
|
const videoUrl = WEBSERVER.URL + this.payload.Video.getWatchStaticPath()
|
||||||
|
const commentUrl = WEBSERVER.URL + this.payload.getCommentStaticPath()
|
||||||
|
const commentHtml = toSafeHtml(this.payload.text)
|
||||||
|
|
||||||
|
return {
|
||||||
|
template: 'video-comment-new',
|
||||||
|
to,
|
||||||
|
subject: 'New comment on your video ' + video.name,
|
||||||
|
locals: {
|
||||||
|
accountName: this.payload.Account.getDisplayName(),
|
||||||
|
accountUrl: this.payload.Account.Actor.url,
|
||||||
|
comment: this.payload,
|
||||||
|
commentHtml,
|
||||||
|
video,
|
||||||
|
videoUrl,
|
||||||
|
action: {
|
||||||
|
text: 'View comment',
|
||||||
|
url: commentUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
server/lib/notifier/shared/common/abstract-notification.ts
Normal file
23
server/lib/notifier/shared/common/abstract-notification.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { MUserWithNotificationSetting, UserNotificationModelForApi } from '@server/types/models'
|
||||||
|
import { EmailPayload, UserNotificationSettingValue } from '@shared/models'
|
||||||
|
|
||||||
|
export abstract class AbstractNotification <T, U = MUserWithNotificationSetting> {
|
||||||
|
|
||||||
|
constructor (protected readonly payload: T) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract prepare (): Promise<void>
|
||||||
|
abstract log (): void
|
||||||
|
|
||||||
|
abstract getSetting (user: U): UserNotificationSettingValue
|
||||||
|
abstract getTargetUsers (): U[]
|
||||||
|
|
||||||
|
abstract createNotification (user: U): Promise<UserNotificationModelForApi>
|
||||||
|
abstract createEmail (to: string): EmailPayload | Promise<EmailPayload>
|
||||||
|
|
||||||
|
isDisabled (): boolean | Promise<boolean> {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
1
server/lib/notifier/shared/common/index.ts
Normal file
1
server/lib/notifier/shared/common/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from './abstract-notification'
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { logger } from '@server/helpers/logger'
|
||||||
|
import { UserModel } from '@server/models/user/user'
|
||||||
|
import { UserNotificationModel } from '@server/models/user/user-notification'
|
||||||
|
import { MActorFollowFull, MUserDefault, MUserWithNotificationSetting, UserNotificationModelForApi } from '@server/types/models'
|
||||||
|
import { UserNotificationType, UserRight } from '@shared/models'
|
||||||
|
import { AbstractNotification } from '../common/abstract-notification'
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
async createNotification (user: MUserWithNotificationSetting) {
|
||||||
|
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||||
|
type: UserNotificationType.AUTO_INSTANCE_FOLLOWING,
|
||||||
|
userId: user.id,
|
||||||
|
actorFollowId: this.actorFollow.id
|
||||||
|
})
|
||||||
|
notification.ActorFollow = this.actorFollow
|
||||||
|
|
||||||
|
return notification
|
||||||
|
}
|
||||||
|
|
||||||
|
async createEmail (to: string) {
|
||||||
|
const instanceUrl = this.actorFollow.ActorFollowing.url
|
||||||
|
|
||||||
|
return {
|
||||||
|
to,
|
||||||
|
subject: 'Auto instance following',
|
||||||
|
text: `Your instance automatically followed a new instance: <a href="${instanceUrl}">${instanceUrl}</a>.`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private get actorFollow () {
|
||||||
|
return this.payload
|
||||||
|
}
|
||||||
|
}
|
68
server/lib/notifier/shared/follow/follow-for-instance.ts
Normal file
68
server/lib/notifier/shared/follow/follow-for-instance.ts
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
import { logger } from '@server/helpers/logger'
|
||||||
|
import { WEBSERVER } from '@server/initializers/constants'
|
||||||
|
import { isBlockedByServerOrAccount } from '@server/lib/blocklist'
|
||||||
|
import { UserModel } from '@server/models/user/user'
|
||||||
|
import { UserNotificationModel } from '@server/models/user/user-notification'
|
||||||
|
import { MActorFollowFull, MUserDefault, MUserWithNotificationSetting, UserNotificationModelForApi } from '@server/types/models'
|
||||||
|
import { UserNotificationType, UserRight } from '@shared/models'
|
||||||
|
import { AbstractNotification } from '../common/abstract-notification'
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
async createNotification (user: MUserWithNotificationSetting) {
|
||||||
|
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||||
|
type: UserNotificationType.NEW_INSTANCE_FOLLOWER,
|
||||||
|
userId: user.id,
|
||||||
|
actorFollowId: this.actorFollow.id
|
||||||
|
})
|
||||||
|
notification.ActorFollow = this.actorFollow
|
||||||
|
|
||||||
|
return notification
|
||||||
|
}
|
||||||
|
|
||||||
|
async createEmail (to: string) {
|
||||||
|
const awaitingApproval = this.actorFollow.state === 'pending'
|
||||||
|
? ' awaiting manual approval.'
|
||||||
|
: ''
|
||||||
|
|
||||||
|
return {
|
||||||
|
to,
|
||||||
|
subject: 'New instance follower',
|
||||||
|
text: `Your instance has a new follower: ${this.actorFollow.ActorFollower.url}${awaitingApproval}.`,
|
||||||
|
locals: {
|
||||||
|
title: 'New instance follower',
|
||||||
|
action: {
|
||||||
|
text: 'Review followers',
|
||||||
|
url: WEBSERVER.URL + '/admin/follows/followers-list'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private get actorFollow () {
|
||||||
|
return this.payload
|
||||||
|
}
|
||||||
|
}
|
82
server/lib/notifier/shared/follow/follow-for-user.ts
Normal file
82
server/lib/notifier/shared/follow/follow-for-user.ts
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
import { logger } from '@server/helpers/logger'
|
||||||
|
import { isBlockedByServerOrAccount } from '@server/lib/blocklist'
|
||||||
|
import { UserModel } from '@server/models/user/user'
|
||||||
|
import { UserNotificationModel } from '@server/models/user/user-notification'
|
||||||
|
import { MActorFollowFull, MUserDefault, MUserWithNotificationSetting, UserNotificationModelForApi } from '@server/types/models'
|
||||||
|
import { UserNotificationType } from '@shared/models'
|
||||||
|
import { AbstractNotification } from '../common/abstract-notification'
|
||||||
|
|
||||||
|
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.isOwned() === 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 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
async createNotification (user: MUserWithNotificationSetting) {
|
||||||
|
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||||
|
type: UserNotificationType.NEW_FOLLOW,
|
||||||
|
userId: user.id,
|
||||||
|
actorFollowId: this.actorFollow.id
|
||||||
|
})
|
||||||
|
notification.ActorFollow = this.actorFollow
|
||||||
|
|
||||||
|
return notification
|
||||||
|
}
|
||||||
|
|
||||||
|
async createEmail (to: string) {
|
||||||
|
const following = this.actorFollow.ActorFollowing
|
||||||
|
const follower = this.actorFollow.ActorFollower
|
||||||
|
|
||||||
|
const followingName = (following.VideoChannel || following.Account).getDisplayName()
|
||||||
|
|
||||||
|
return {
|
||||||
|
template: 'follower-on-channel',
|
||||||
|
to,
|
||||||
|
subject: `New follower on your channel ${followingName}`,
|
||||||
|
locals: {
|
||||||
|
followerName: follower.Account.getDisplayName(),
|
||||||
|
followerUrl: follower.url,
|
||||||
|
followingName,
|
||||||
|
followingUrl: following.url,
|
||||||
|
followType: this.followType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private get actorFollow () {
|
||||||
|
return this.payload
|
||||||
|
}
|
||||||
|
}
|
3
server/lib/notifier/shared/follow/index.ts
Normal file
3
server/lib/notifier/shared/follow/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export * from './auto-follow-for-instance'
|
||||||
|
export * from './follow-for-instance'
|
||||||
|
export * from './follow-for-user'
|
7
server/lib/notifier/shared/index.ts
Normal file
7
server/lib/notifier/shared/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export * from './abuse'
|
||||||
|
export * from './blacklist'
|
||||||
|
export * from './comment'
|
||||||
|
export * from './common'
|
||||||
|
export * from './follow'
|
||||||
|
export * from './instance'
|
||||||
|
export * from './video-publication'
|
3
server/lib/notifier/shared/instance/index.ts
Normal file
3
server/lib/notifier/shared/instance/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export * from './new-peertube-version-for-admins'
|
||||||
|
export * from './new-plugin-version-for-admins'
|
||||||
|
export * from './registration-for-moderators'
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { logger } from '@server/helpers/logger'
|
||||||
|
import { UserModel } from '@server/models/user/user'
|
||||||
|
import { UserNotificationModel } from '@server/models/user/user-notification'
|
||||||
|
import { MApplication, MUserDefault, MUserWithNotificationSetting, UserNotificationModelForApi } from '@server/types/models'
|
||||||
|
import { UserNotificationType, UserRight } from '@shared/models'
|
||||||
|
import { AbstractNotification } from '../common/abstract-notification'
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
async createNotification (user: MUserWithNotificationSetting) {
|
||||||
|
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||||
|
type: UserNotificationType.NEW_PEERTUBE_VERSION,
|
||||||
|
userId: user.id,
|
||||||
|
applicationId: this.payload.application.id
|
||||||
|
})
|
||||||
|
notification.Application = this.payload.application
|
||||||
|
|
||||||
|
return notification
|
||||||
|
}
|
||||||
|
|
||||||
|
async createEmail (to: string) {
|
||||||
|
return {
|
||||||
|
to,
|
||||||
|
template: 'peertube-version-new',
|
||||||
|
subject: `A new PeerTube version is available: ${this.payload.latestVersion}`,
|
||||||
|
locals: {
|
||||||
|
latestVersion: this.payload.latestVersion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { logger } from '@server/helpers/logger'
|
||||||
|
import { WEBSERVER } from '@server/initializers/constants'
|
||||||
|
import { UserModel } from '@server/models/user/user'
|
||||||
|
import { UserNotificationModel } from '@server/models/user/user-notification'
|
||||||
|
import { MPlugin, MUserDefault, MUserWithNotificationSetting, UserNotificationModelForApi } from '@server/types/models'
|
||||||
|
import { UserNotificationType, UserRight } from '@shared/models'
|
||||||
|
import { AbstractNotification } from '../common/abstract-notification'
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
async createNotification (user: MUserWithNotificationSetting) {
|
||||||
|
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||||
|
type: UserNotificationType.NEW_PLUGIN_VERSION,
|
||||||
|
userId: user.id,
|
||||||
|
pluginId: this.plugin.id
|
||||||
|
})
|
||||||
|
notification.Plugin = this.plugin
|
||||||
|
|
||||||
|
return notification
|
||||||
|
}
|
||||||
|
|
||||||
|
async createEmail (to: string) {
|
||||||
|
const pluginUrl = WEBSERVER.URL + '/admin/plugins/list-installed?pluginType=' + this.plugin.type
|
||||||
|
|
||||||
|
return {
|
||||||
|
to,
|
||||||
|
template: 'plugin-version-new',
|
||||||
|
subject: `A new plugin/theme version is available: ${this.plugin.name}@${this.plugin.latestVersion}`,
|
||||||
|
locals: {
|
||||||
|
pluginName: this.plugin.name,
|
||||||
|
latestVersion: this.plugin.latestVersion,
|
||||||
|
pluginUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private get plugin () {
|
||||||
|
return this.payload
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { logger } from '@server/helpers/logger'
|
||||||
|
import { CONFIG } from '@server/initializers/config'
|
||||||
|
import { UserModel } from '@server/models/user/user'
|
||||||
|
import { UserNotificationModel } from '@server/models/user/user-notification'
|
||||||
|
import { MUserDefault, MUserWithNotificationSetting, UserNotificationModelForApi } from '@server/types/models'
|
||||||
|
import { UserNotificationType, UserRight } from '@shared/models'
|
||||||
|
import { AbstractNotification } from '../common/abstract-notification'
|
||||||
|
|
||||||
|
export class RegistrationForModerators 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
|
||||||
|
}
|
||||||
|
|
||||||
|
async createNotification (user: MUserWithNotificationSetting) {
|
||||||
|
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||||
|
type: UserNotificationType.NEW_USER_REGISTRATION,
|
||||||
|
userId: user.id,
|
||||||
|
accountId: this.payload.Account.id
|
||||||
|
})
|
||||||
|
notification.Account = this.payload.Account
|
||||||
|
|
||||||
|
return notification
|
||||||
|
}
|
||||||
|
|
||||||
|
async createEmail (to: string) {
|
||||||
|
return {
|
||||||
|
template: 'user-registered',
|
||||||
|
to,
|
||||||
|
subject: `a new user registered on ${CONFIG.INSTANCE.NAME}: ${this.payload.username}`,
|
||||||
|
locals: {
|
||||||
|
user: this.payload
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { logger } from '@server/helpers/logger'
|
||||||
|
import { WEBSERVER } from '@server/initializers/constants'
|
||||||
|
import { UserModel } from '@server/models/user/user'
|
||||||
|
import { UserNotificationModel } from '@server/models/user/user-notification'
|
||||||
|
import { MUserDefault, MUserWithNotificationSetting, MVideoFullLight, UserNotificationModelForApi } from '@server/types/models'
|
||||||
|
import { UserNotificationType } from '@shared/models'
|
||||||
|
import { AbstractNotification } from '../common/abstract-notification'
|
||||||
|
|
||||||
|
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 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
async createNotification (user: MUserWithNotificationSetting) {
|
||||||
|
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||||
|
type: UserNotificationType.MY_VIDEO_PUBLISHED,
|
||||||
|
userId: user.id,
|
||||||
|
videoId: this.payload.id
|
||||||
|
})
|
||||||
|
notification.Video = this.payload
|
||||||
|
|
||||||
|
return notification
|
||||||
|
}
|
||||||
|
|
||||||
|
createEmail (to: string) {
|
||||||
|
const videoUrl = WEBSERVER.URL + this.payload.getWatchStaticPath()
|
||||||
|
|
||||||
|
return {
|
||||||
|
to,
|
||||||
|
subject: `Your video ${this.payload.name} has been published`,
|
||||||
|
text: `Your video "${this.payload.name}" has been published.`,
|
||||||
|
locals: {
|
||||||
|
title: 'You video is live',
|
||||||
|
action: {
|
||||||
|
text: 'View video',
|
||||||
|
url: videoUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
import { logger } from '@server/helpers/logger'
|
||||||
|
import { WEBSERVER } from '@server/initializers/constants'
|
||||||
|
import { UserModel } from '@server/models/user/user'
|
||||||
|
import { UserNotificationModel } from '@server/models/user/user-notification'
|
||||||
|
import { MUserDefault, MUserWithNotificationSetting, MVideoImportVideo, UserNotificationModelForApi } from '@server/types/models'
|
||||||
|
import { UserNotificationType } from '@shared/models'
|
||||||
|
import { AbstractNotification } from '../common/abstract-notification'
|
||||||
|
|
||||||
|
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 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
async createNotification (user: MUserWithNotificationSetting) {
|
||||||
|
const notification = await UserNotificationModel.create<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 (to: string) {
|
||||||
|
if (this.payload.success) return this.createSuccessEmail(to)
|
||||||
|
|
||||||
|
return this.createFailEmail(to)
|
||||||
|
}
|
||||||
|
|
||||||
|
private createSuccessEmail (to: string) {
|
||||||
|
const videoUrl = WEBSERVER.URL + this.videoImport.Video.getWatchStaticPath()
|
||||||
|
|
||||||
|
return {
|
||||||
|
to,
|
||||||
|
subject: `Your video import ${this.videoImport.getTargetIdentifier()} is complete`,
|
||||||
|
text: `Your video "${this.videoImport.getTargetIdentifier()}" just finished importing.`,
|
||||||
|
locals: {
|
||||||
|
title: 'Import complete',
|
||||||
|
action: {
|
||||||
|
text: 'View video',
|
||||||
|
url: videoUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private createFailEmail (to: string) {
|
||||||
|
const importUrl = WEBSERVER.URL + '/my-library/video-imports'
|
||||||
|
|
||||||
|
const text =
|
||||||
|
`Your video import "${this.videoImport.getTargetIdentifier()}" encountered an error.` +
|
||||||
|
'\n\n' +
|
||||||
|
`See your videos import dashboard for more information: <a href="${importUrl}">${importUrl}</a>.`
|
||||||
|
|
||||||
|
return {
|
||||||
|
to,
|
||||||
|
subject: `Your video import "${this.videoImport.getTargetIdentifier()}" encountered an error`,
|
||||||
|
text,
|
||||||
|
locals: {
|
||||||
|
title: 'Import failed',
|
||||||
|
action: {
|
||||||
|
text: 'Review imports',
|
||||||
|
url: importUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private get videoImport () {
|
||||||
|
return this.payload.videoImport
|
||||||
|
}
|
||||||
|
}
|
5
server/lib/notifier/shared/video-publication/index.ts
Normal file
5
server/lib/notifier/shared/video-publication/index.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
export * from './new-video-for-subscribers'
|
||||||
|
export * from './import-finished-for-owner'
|
||||||
|
export * from './owned-publication-after-auto-unblacklist'
|
||||||
|
export * from './owned-publication-after-schedule-update'
|
||||||
|
export * from './owned-publication-after-transcoding'
|
|
@ -0,0 +1,61 @@
|
||||||
|
import { logger } from '@server/helpers/logger'
|
||||||
|
import { WEBSERVER } from '@server/initializers/constants'
|
||||||
|
import { UserModel } from '@server/models/user/user'
|
||||||
|
import { UserNotificationModel } from '@server/models/user/user-notification'
|
||||||
|
import { MUserWithNotificationSetting, MVideoAccountLight, UserNotificationModelForApi } from '@server/types/models'
|
||||||
|
import { UserNotificationType, VideoPrivacy, VideoState } from '@shared/models'
|
||||||
|
import { AbstractNotification } from '../common/abstract-notification'
|
||||||
|
|
||||||
|
export class NewVideoForSubscribers extends AbstractNotification <MVideoAccountLight> {
|
||||||
|
private users: MUserWithNotificationSetting[]
|
||||||
|
|
||||||
|
async prepare () {
|
||||||
|
// List all followers that are users
|
||||||
|
this.users = await UserModel.listUserSubscribersOf(this.payload.VideoChannel.actorId)
|
||||||
|
}
|
||||||
|
|
||||||
|
log () {
|
||||||
|
logger.info('Notifying %d users of new video %s.', this.users.length, this.payload.url)
|
||||||
|
}
|
||||||
|
|
||||||
|
isDisabled () {
|
||||||
|
return this.payload.privacy !== VideoPrivacy.PUBLIC || this.payload.state !== VideoState.PUBLISHED || this.payload.isBlacklisted()
|
||||||
|
}
|
||||||
|
|
||||||
|
getSetting (user: MUserWithNotificationSetting) {
|
||||||
|
return user.NotificationSetting.newVideoFromSubscription
|
||||||
|
}
|
||||||
|
|
||||||
|
getTargetUsers () {
|
||||||
|
return this.users
|
||||||
|
}
|
||||||
|
|
||||||
|
async createNotification (user: MUserWithNotificationSetting) {
|
||||||
|
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||||
|
type: UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION,
|
||||||
|
userId: user.id,
|
||||||
|
videoId: this.payload.id
|
||||||
|
})
|
||||||
|
notification.Video = this.payload
|
||||||
|
|
||||||
|
return notification
|
||||||
|
}
|
||||||
|
|
||||||
|
createEmail (to: string) {
|
||||||
|
const channelName = this.payload.VideoChannel.getDisplayName()
|
||||||
|
const videoUrl = WEBSERVER.URL + this.payload.getWatchStaticPath()
|
||||||
|
|
||||||
|
return {
|
||||||
|
to,
|
||||||
|
subject: channelName + ' just published a new video',
|
||||||
|
text: `Your subscription ${channelName} just published a new video: "${this.payload.name}".`,
|
||||||
|
locals: {
|
||||||
|
title: 'New content ',
|
||||||
|
action: {
|
||||||
|
text: 'View video',
|
||||||
|
url: videoUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
|
||||||
|
import { VideoState } from '@shared/models'
|
||||||
|
import { AbstractOwnedVideoPublication } from './abstract-owned-video-publication'
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { VideoState } from '@shared/models'
|
||||||
|
import { AbstractOwnedVideoPublication } from './abstract-owned-video-publication'
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { AbstractOwnedVideoPublication } from './abstract-owned-video-publication'
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue