]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - server/lib/emailer.ts
Export encoders options in a dedicated struct
[github/Chocobozzz/PeerTube.git] / server / lib / emailer.ts
index c08732b4833de362798a3afeedb9a7dbeebea050..650a3c090948b2a19e533b4ea89b550ddaf7a56b 100644 (file)
@@ -1,26 +1,41 @@
+import { readFileSync } from 'fs-extra'
+import { merge } from 'lodash'
 import { createTransport, Transporter } from 'nodemailer'
+import { join } from 'path'
+import { VideoChannelModel } from '@server/models/video/video-channel'
+import { MVideoBlacklistLightVideo, MVideoBlacklistVideo } from '@server/types/models/video/video-blacklist'
+import { MVideoImport, MVideoImportVideo } from '@server/types/models/video/video-import'
+import { SANITIZE_OPTIONS, TEXT_WITH_HTML_RULES } from '@shared/core-utils'
+import { AbuseState, EmailPayload, UserAbuse } from '@shared/models'
+import { SendEmailOptions } from '../../shared/models/server/emailer.model'
 import { isTestInstance, root } from '../helpers/core-utils'
 import { bunyanLogger, logger } from '../helpers/logger'
 import { CONFIG, isEmailEnabled } from '../initializers/config'
-import { JobQueue } from './job-queue'
-import { readFileSync } from 'fs-extra'
 import { WEBSERVER } from '../initializers/constants'
-import {
-  MCommentOwnerVideo,
-  MVideo,
-  MVideoAbuseVideo,
-  MVideoAccountLight,
-  MVideoBlacklistLightVideo,
-  MVideoBlacklistVideo
-} from '../types/models/video'
-import { MActorFollowActors, MActorFollowFull, MUser } from '../types/models'
-import { MVideoImport, MVideoImportVideo } from '@server/types/models/video/video-import'
-import { EmailPayload } from '@shared/models'
-import { join } from 'path'
-import { VideoAbuse } from '../../shared/models/videos'
-import { SendEmailOptions } from '../../shared/models/server/emailer.model'
-import { merge } from 'lodash'
-import { VideoChannelModel } from '@server/models/video/video-channel'
+import { MAbuseFull, MAbuseMessage, MAccountDefault, MActorFollowActors, MActorFollowFull, MUser } from '../types/models'
+import { MCommentOwnerVideo, MVideo, MVideoAccountLight } from '../types/models/video'
+import { JobQueue } from './job-queue'
+
+const sanitizeHtml = require('sanitize-html')
+const markdownItEmoji = require('markdown-it-emoji/light')
+const MarkdownItClass = require('markdown-it')
+const markdownIt = new MarkdownItClass('default', { linkify: true, breaks: true, html: true })
+
+markdownIt.enable(TEXT_WITH_HTML_RULES)
+
+markdownIt.use(markdownItEmoji)
+
+const toSafeHtml = text => {
+  // Restore line feed
+  const textWithLineFeed = text.replace(/<br.?\/?>/g, '\r\n')
+
+  // Convert possible markdown (emojis, emphasis and lists) to html
+  const html = markdownIt.render(textWithLineFeed)
+
+  // Convert to safe Html
+  return sanitizeHtml(html, SANITIZE_OPTIONS)
+}
+
 const Email = require('email-templates')
 
 class Emailer {
@@ -215,7 +230,7 @@ class Emailer {
   }
 
   myVideoImportErrorNotification (to: string[], videoImport: MVideoImport) {
-    const importUrl = WEBSERVER.URL + '/my-account/video-imports'
+    const importUrl = WEBSERVER.URL + '/my-library/video-imports'
 
     const text =
       `Your video import "${videoImport.getTargetIdentifier()}" encountered an error.` +
@@ -242,6 +257,7 @@ class Emailer {
     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',
@@ -251,6 +267,7 @@ class Emailer {
         accountName: comment.Account.getDisplayName(),
         accountUrl: comment.Account.Actor.url,
         comment,
+        commentHtml,
         video,
         videoUrl,
         action: {
@@ -268,6 +285,7 @@ class Emailer {
     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',
@@ -275,6 +293,7 @@ class Emailer {
       subject: 'Mention on video ' + video.name,
       locals: {
         comment,
+        commentHtml,
         video,
         videoUrl,
         accountName,
@@ -288,29 +307,138 @@ class Emailer {
     return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
   }
 
-  addVideoAbuseModeratorsNotification (to: string[], parameters: {
-    videoAbuse: VideoAbuse
-    videoAbuseInstance: MVideoAbuseVideo
+  addAbuseModeratorsNotification (to: string[], parameters: {
+    abuse: UserAbuse
+    abuseInstance: MAbuseFull
     reporter: string
   }) {
-    const videoAbuseUrl = WEBSERVER.URL + '/admin/moderation/video-abuses/list?search=%23' + parameters.videoAbuse.id
-    const videoUrl = WEBSERVER.URL + parameters.videoAbuseInstance.Video.getWatchStaticPath()
+    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: 'video-abuse-new',
+      template: 'abuse-state-change',
       to,
-      subject: `New video abuse report from ${parameters.reporter}`,
+      subject: text,
       locals: {
-        videoUrl,
-        videoAbuseUrl,
-        videoCreatedAt: new Date(parameters.videoAbuseInstance.Video.createdAt).toLocaleString(),
-        videoPublishedAt: new Date(parameters.videoAbuseInstance.Video.publishedAt).toLocaleString(),
-        videoAbuse: parameters.videoAbuse,
-        reporter: parameters.reporter,
-        action: {
-          text: 'View report #' + parameters.videoAbuse.id,
-          url: videoAbuseUrl
-        }
+        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
       }
     }
 
@@ -387,12 +515,13 @@ class Emailer {
     return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
   }
 
-  addPasswordResetEmailJob (to: string, resetPasswordUrl: string) {
+  addPasswordResetEmailJob (username: string, to: string, resetPasswordUrl: string) {
     const emailPayload: EmailPayload = {
       template: 'password-reset',
       to: [ to ],
       subject: 'Reset your account password',
       locals: {
+        username,
         resetPasswordUrl
       }
     }
@@ -414,12 +543,13 @@ class Emailer {
     return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
   }
 
-  addVerifyEmailJob (to: string, verifyEmailUrl: string) {
+  addVerifyEmailJob (username: string, to: string, verifyEmailUrl: string) {
     const emailPayload: EmailPayload = {
       template: 'verify-email',
       to: [ to ],
       subject: `Verify your email on ${WEBSERVER.HOST}`,
       locals: {
+        username,
         verifyEmailUrl
       }
     }
@@ -450,7 +580,10 @@ class Emailer {
       locals: {
         fromName,
         fromEmail,
-        body
+        body,
+
+        // There are not notification preferences for the contact form
+        hideNotificationPreferences: true
       }
     }