Switch emails to pug templates and provide richer html/text-only versions
This commit is contained in:
parent
91b8e675e2
commit
df4c603dea
|
@ -22,7 +22,7 @@
|
||||||
<th i18n>Follower handle</th>
|
<th i18n>Follower handle</th>
|
||||||
<th style="width: 100px;" i18n pSortableColumn="state">State <p-sortIcon field="state"></p-sortIcon></th>
|
<th style="width: 100px;" i18n pSortableColumn="state">State <p-sortIcon field="state"></p-sortIcon></th>
|
||||||
<th style="width: 100px;" i18n pSortableColumn="score">Score <p-sortIcon field="score"></p-sortIcon></th>
|
<th style="width: 100px;" i18n pSortableColumn="score">Score <p-sortIcon field="score"></p-sortIcon></th>
|
||||||
<th style="width: 140px;" i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
|
<th style="width: 150px;" i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
|
||||||
<th style="width: 100px;"></th>
|
<th style="width: 100px;"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<th i18n>Host</th>
|
<th i18n>Host</th>
|
||||||
<th style="width: 100px;" i18n pSortableColumn="state">State <p-sortIcon field="state"></p-sortIcon></th>
|
<th style="width: 100px;" i18n pSortableColumn="state">State <p-sortIcon field="state"></p-sortIcon></th>
|
||||||
<th style="width: 140px;" i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
|
<th style="width: 150px;" i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
|
||||||
<th style="width: 160px;" i18n pSortableColumn="redundancyAllowed">Redundancy allowed <p-sortIcon field="redundancyAllowed"></p-sortIcon></th>
|
<th style="width: 160px;" i18n pSortableColumn="redundancyAllowed">Redundancy allowed <p-sortIcon field="redundancyAllowed"></p-sortIcon></th>
|
||||||
<th style="width: 100px;"></th>
|
<th style="width: 100px;"></th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
<ng-template pTemplate="header">
|
<ng-template pTemplate="header">
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width: 100%;" i18n>Account</th>
|
<th style="width: 100%;" i18n>Account</th>
|
||||||
<th style="width: 140px;" i18n pSortableColumn="createdAt">Muted at <p-sortIcon field="createdAt"></p-sortIcon></th>
|
<th style="width: 150px;" i18n pSortableColumn="createdAt">Muted at <p-sortIcon field="createdAt"></p-sortIcon></th>
|
||||||
<th style="width: 100px;"></th> <!-- column for action buttons -->
|
<th style="width: 100px;"></th> <!-- column for action buttons -->
|
||||||
</tr>
|
</tr>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
<ng-template pTemplate="header">
|
<ng-template pTemplate="header">
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width: 100%;" i18n>Instance</th>
|
<th style="width: 100%;" i18n>Instance</th>
|
||||||
<th style="width: 140px;" i18n pSortableColumn="createdAt">Muted at <p-sortIcon field="createdAt"></p-sortIcon></th>
|
<th style="width: 150px;" i18n pSortableColumn="createdAt">Muted at <p-sortIcon field="createdAt"></p-sortIcon></th>
|
||||||
<th style="width: 100px;"></th> <!-- column for action buttons -->
|
<th style="width: 100px;"></th> <!-- column for action buttons -->
|
||||||
</tr>
|
</tr>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
<th style="width: 40px;"></th>
|
<th style="width: 40px;"></th>
|
||||||
<th style="width: 20%;" pResizableColumn i18n>Reporter</th>
|
<th style="width: 20%;" pResizableColumn i18n>Reporter</th>
|
||||||
<th i18n>Video</th>
|
<th i18n>Video</th>
|
||||||
<th style="width: 140px;" i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
|
<th style="width: 150px;" i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
|
||||||
<th i18n pSortableColumn="state" style="width: 80px;">State <p-sortIcon field="state"></p-sortIcon></th>
|
<th i18n pSortableColumn="state" style="width: 80px;">State <p-sortIcon field="state"></p-sortIcon></th>
|
||||||
<th style="width: 120px;"></th>
|
<th style="width: 120px;"></th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
<th i18n pSortableColumn="name">Video <p-sortIcon field="name"></p-sortIcon></th>
|
<th i18n pSortableColumn="name">Video <p-sortIcon field="name"></p-sortIcon></th>
|
||||||
<th style="width: 100px;" i18n>Sensitive</th>
|
<th style="width: 100px;" i18n>Sensitive</th>
|
||||||
<th style="width: 120px;" i18n>Unfederated</th>
|
<th style="width: 120px;" i18n>Unfederated</th>
|
||||||
<th style="width: 140px;" i18n pSortableColumn="createdAt">Date <p-sortIcon field="createdAt"></p-sortIcon></th>
|
<th style="width: 150px;" i18n pSortableColumn="createdAt">Date <p-sortIcon field="createdAt"></p-sortIcon></th>
|
||||||
<th style="width: 120px;"></th>
|
<th style="width: 120px;"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
<p-table
|
<p-table
|
||||||
[value]="users" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
|
[value]="users" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
|
||||||
[sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id"
|
[sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id" [resizableColumns]="true"
|
||||||
[(selection)]="selectedUsers"
|
[(selection)]="selectedUsers"
|
||||||
[showCurrentPageReport]="true" i18n-currentPageReportTemplate
|
[showCurrentPageReport]="true" i18n-currentPageReportTemplate
|
||||||
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} users"
|
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} users"
|
||||||
|
@ -42,12 +42,12 @@
|
||||||
<p-tableHeaderCheckbox></p-tableHeaderCheckbox>
|
<p-tableHeaderCheckbox></p-tableHeaderCheckbox>
|
||||||
</th>
|
</th>
|
||||||
<th style="width: 40px"></th>
|
<th style="width: 40px"></th>
|
||||||
<th i18n pSortableColumn="username">Username <p-sortIcon field="username"></p-sortIcon></th>
|
<th pResizableColumn i18n pSortableColumn="username">Username <p-sortIcon field="username"></p-sortIcon></th>
|
||||||
<th i18n>Email</th>
|
<th i18n>Email</th>
|
||||||
<th i18n pSortableColumn="videoQuotaUsed">Video quota <p-sortIcon field="videoQuotaUsed"></p-sortIcon></th>
|
<th style="width: 140px;" i18n pSortableColumn="videoQuotaUsed">Video quota <p-sortIcon field="videoQuotaUsed"></p-sortIcon></th>
|
||||||
<th i18n>Role</th>
|
<th style="width: 120px;" i18n>Role</th>
|
||||||
<th i18n>Auth plugin</th>
|
<th style="width: 140px;" pResizableColumn i18n>Auth plugin</th>
|
||||||
<th i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
|
<th style="width: 150px;" i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
|
||||||
<th style="width: 50px;"></th>
|
<th style="width: 50px;"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
@ -103,7 +103,7 @@
|
||||||
<ng-container *ngIf="user.pluginAuth">{{ user.pluginAuth }}</ng-container>
|
<ng-container *ngIf="user.pluginAuth">{{ user.pluginAuth }}</ng-container>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td [title]="user.createdAt">{{ user.createdAt }}</td>
|
<td [title]="user.createdAt">{{ user.createdAt | date: 'short' }}</td>
|
||||||
|
|
||||||
<td class="action-cell">
|
<td class="action-cell">
|
||||||
<my-user-moderation-dropdown *ngIf="!isInSelectionMode()" [user]="user" (userChanged)="onUserChanged()" (userDeleted)="onUserChanged()">
|
<my-user-moderation-dropdown *ngIf="!isInSelectionMode()" [user]="user" (userChanged)="onUserChanged()" (userDeleted)="onUserChanged()">
|
||||||
|
|
|
@ -98,6 +98,7 @@
|
||||||
"cors": "^2.8.1",
|
"cors": "^2.8.1",
|
||||||
"create-torrent": "^4.0.0",
|
"create-torrent": "^4.0.0",
|
||||||
"deep-object-diff": "^1.1.0",
|
"deep-object-diff": "^1.1.0",
|
||||||
|
"email-templates": "^7.0.4",
|
||||||
"express": "^4.12.4",
|
"express": "^4.12.4",
|
||||||
"express-oauth-server": "^2.0.0",
|
"express-oauth-server": "^2.0.0",
|
||||||
"express-rate-limit": "^5.0.0",
|
"express-rate-limit": "^5.0.0",
|
||||||
|
@ -127,6 +128,7 @@
|
||||||
"pfeed": "1.1.11",
|
"pfeed": "1.1.11",
|
||||||
"pg": "^7.4.1",
|
"pg": "^7.4.1",
|
||||||
"prompt": "^1.0.0",
|
"prompt": "^1.0.0",
|
||||||
|
"pug": "^2.0.4",
|
||||||
"redis": "^3.0.2",
|
"redis": "^3.0.2",
|
||||||
"reflect-metadata": "^0.1.12",
|
"reflect-metadata": "^0.1.12",
|
||||||
"request": "^2.81.0",
|
"request": "^2.81.0",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import * as express from 'express'
|
import * as express from 'express'
|
||||||
import { UserRight, VideoAbuseCreate, VideoAbuseState } from '../../../../shared'
|
import { UserRight, VideoAbuseCreate, VideoAbuseState, VideoAbuse } from '../../../../shared'
|
||||||
import { logger } from '../../../helpers/logger'
|
import { logger } from '../../../helpers/logger'
|
||||||
import { getFormattedObjects } from '../../../helpers/utils'
|
import { getFormattedObjects } from '../../../helpers/utils'
|
||||||
import { sequelizeTypescript } from '../../../initializers/database'
|
import { sequelizeTypescript } from '../../../initializers/database'
|
||||||
|
@ -24,6 +24,7 @@ import { Notifier } from '../../../lib/notifier'
|
||||||
import { sendVideoAbuse } from '../../../lib/activitypub/send/send-flag'
|
import { sendVideoAbuse } from '../../../lib/activitypub/send/send-flag'
|
||||||
import { MVideoAbuseAccountVideo } from '../../../typings/models/video'
|
import { MVideoAbuseAccountVideo } from '../../../typings/models/video'
|
||||||
import { getServerActor } from '@server/models/application/application'
|
import { getServerActor } from '@server/models/application/application'
|
||||||
|
import { MAccountDefault } from '@server/typings/models'
|
||||||
|
|
||||||
const auditLogger = auditLoggerFactory('abuse')
|
const auditLogger = auditLoggerFactory('abuse')
|
||||||
const abuseVideoRouter = express.Router()
|
const abuseVideoRouter = express.Router()
|
||||||
|
@ -117,9 +118,11 @@ async function deleteVideoAbuse (req: express.Request, res: express.Response) {
|
||||||
async function reportVideoAbuse (req: express.Request, res: express.Response) {
|
async function reportVideoAbuse (req: express.Request, res: express.Response) {
|
||||||
const videoInstance = res.locals.videoAll
|
const videoInstance = res.locals.videoAll
|
||||||
const body: VideoAbuseCreate = req.body
|
const body: VideoAbuseCreate = req.body
|
||||||
|
let reporterAccount: MAccountDefault
|
||||||
|
let videoAbuseJSON: VideoAbuse
|
||||||
|
|
||||||
const videoAbuse = await sequelizeTypescript.transaction(async t => {
|
const videoAbuseInstance = await sequelizeTypescript.transaction(async t => {
|
||||||
const reporterAccount = await AccountModel.load(res.locals.oauth.token.User.Account.id, t)
|
reporterAccount = await AccountModel.load(res.locals.oauth.token.User.Account.id, t)
|
||||||
|
|
||||||
const abuseToCreate = {
|
const abuseToCreate = {
|
||||||
reporterAccountId: reporterAccount.id,
|
reporterAccountId: reporterAccount.id,
|
||||||
|
@ -137,14 +140,19 @@ async function reportVideoAbuse (req: express.Request, res: express.Response) {
|
||||||
await sendVideoAbuse(reporterAccount.Actor, videoAbuseInstance, videoInstance, t)
|
await sendVideoAbuse(reporterAccount.Actor, videoAbuseInstance, videoInstance, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
auditLogger.create(reporterAccount.Actor.getIdentifier(), new VideoAbuseAuditView(videoAbuseInstance.toFormattedJSON()))
|
videoAbuseJSON = videoAbuseInstance.toFormattedJSON()
|
||||||
|
auditLogger.create(reporterAccount.Actor.getIdentifier(), new VideoAbuseAuditView(videoAbuseJSON))
|
||||||
|
|
||||||
return videoAbuseInstance
|
return videoAbuseInstance
|
||||||
})
|
})
|
||||||
|
|
||||||
Notifier.Instance.notifyOnNewVideoAbuse(videoAbuse)
|
Notifier.Instance.notifyOnNewVideoAbuse({
|
||||||
|
videoAbuse: videoAbuseJSON,
|
||||||
|
videoAbuseInstance,
|
||||||
|
reporter: reporterAccount.Actor.getIdentifier()
|
||||||
|
})
|
||||||
|
|
||||||
logger.info('Abuse report for video %s created.', videoInstance.name)
|
logger.info('Abuse report for video %s created.', videoInstance.name)
|
||||||
|
|
||||||
return res.json({ videoAbuse: videoAbuse.toFormattedJSON() }).end()
|
return res.json({ videoAbuseJSON }).end()
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,8 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos'
|
||||||
import { Notifier } from '../../notifier'
|
import { Notifier } from '../../notifier'
|
||||||
import { getAPId } from '../../../helpers/activitypub'
|
import { getAPId } from '../../../helpers/activitypub'
|
||||||
import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
|
import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
|
||||||
import { MActorSignature, MVideoAbuseVideo } from '../../../typings/models'
|
import { MActorSignature, MVideoAbuseAccountVideo } from '../../../typings/models'
|
||||||
|
import { AccountModel } from '@server/models/account/account'
|
||||||
|
|
||||||
async function processFlagActivity (options: APProcessorOptions<ActivityCreate | ActivityFlag>) {
|
async function processFlagActivity (options: APProcessorOptions<ActivityCreate | ActivityFlag>) {
|
||||||
const { activity, byActor } = options
|
const { activity, byActor } = options
|
||||||
|
@ -36,8 +37,9 @@ async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag,
|
||||||
logger.debug('Reporting remote abuse for video %s.', getAPId(object))
|
logger.debug('Reporting remote abuse for video %s.', getAPId(object))
|
||||||
|
|
||||||
const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: object })
|
const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: object })
|
||||||
|
const reporterAccount = await sequelizeTypescript.transaction(async t => AccountModel.load(account.id, t))
|
||||||
|
|
||||||
const videoAbuse = await sequelizeTypescript.transaction(async t => {
|
const videoAbuseInstance = await sequelizeTypescript.transaction(async t => {
|
||||||
const videoAbuseData = {
|
const videoAbuseData = {
|
||||||
reporterAccountId: account.id,
|
reporterAccountId: account.id,
|
||||||
reason: flag.content,
|
reason: flag.content,
|
||||||
|
@ -45,15 +47,22 @@ async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag,
|
||||||
state: VideoAbuseState.PENDING
|
state: VideoAbuseState.PENDING
|
||||||
}
|
}
|
||||||
|
|
||||||
const videoAbuseInstance = await VideoAbuseModel.create(videoAbuseData, { transaction: t }) as MVideoAbuseVideo
|
const videoAbuseInstance: MVideoAbuseAccountVideo = await VideoAbuseModel.create(videoAbuseData, { transaction: t })
|
||||||
videoAbuseInstance.Video = video
|
videoAbuseInstance.Video = video
|
||||||
|
videoAbuseInstance.Account = reporterAccount
|
||||||
|
|
||||||
logger.info('Remote abuse for video uuid %s created', flag.object)
|
logger.info('Remote abuse for video uuid %s created', flag.object)
|
||||||
|
|
||||||
return videoAbuseInstance
|
return videoAbuseInstance
|
||||||
})
|
})
|
||||||
|
|
||||||
Notifier.Instance.notifyOnNewVideoAbuse(videoAbuse)
|
const videoAbuseJSON = videoAbuseInstance.toFormattedJSON()
|
||||||
|
|
||||||
|
Notifier.Instance.notifyOnNewVideoAbuse({
|
||||||
|
videoAbuse: videoAbuseJSON,
|
||||||
|
videoAbuseInstance,
|
||||||
|
reporter: reporterAccount.Actor.getIdentifier()
|
||||||
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.debug('Cannot process report of %s. (Maybe not a video abuse).', getAPId(object), { err })
|
logger.debug('Cannot process report of %s. (Maybe not a video abuse).', getAPId(object), { err })
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { createTransport, Transporter } from 'nodemailer'
|
import { createTransport, Transporter } from 'nodemailer'
|
||||||
import { isTestInstance } 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 { JobQueue } from './job-queue'
|
import { JobQueue } from './job-queue'
|
||||||
|
@ -16,6 +16,12 @@ import {
|
||||||
import { MActorFollowActors, MActorFollowFull, MUser } from '../typings/models'
|
import { MActorFollowActors, MActorFollowFull, MUser } from '../typings/models'
|
||||||
import { MVideoImport, MVideoImportVideo } from '@server/typings/models/video/video-import'
|
import { MVideoImport, MVideoImportVideo } from '@server/typings/models/video/video-import'
|
||||||
import { EmailPayload } from '@shared/models'
|
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'
|
||||||
|
const Email = require('email-templates')
|
||||||
|
|
||||||
class Emailer {
|
class Emailer {
|
||||||
|
|
||||||
|
@ -105,37 +111,36 @@ class Emailer {
|
||||||
const channelName = video.VideoChannel.getDisplayName()
|
const channelName = video.VideoChannel.getDisplayName()
|
||||||
const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
|
const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
|
||||||
|
|
||||||
const text = 'Hi dear user,\n\n' +
|
|
||||||
`Your subscription ${channelName} just published a new video: ${video.name}` +
|
|
||||||
'\n\n' +
|
|
||||||
`You can view it on ${videoUrl} ` +
|
|
||||||
'\n\n' +
|
|
||||||
'Cheers,\n' +
|
|
||||||
`${CONFIG.EMAIL.BODY.SIGNATURE}`
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
const emailPayload: EmailPayload = {
|
||||||
to,
|
to,
|
||||||
subject: CONFIG.EMAIL.SUBJECT.PREFIX + channelName + ' just published a new video',
|
subject: channelName + ' just published a new video',
|
||||||
text
|
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 })
|
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||||
}
|
}
|
||||||
|
|
||||||
addNewFollowNotification (to: string[], actorFollow: MActorFollowFull, followType: 'account' | 'channel') {
|
addNewFollowNotification (to: string[], actorFollow: MActorFollowFull, followType: 'account' | 'channel') {
|
||||||
const followerName = actorFollow.ActorFollower.Account.getDisplayName()
|
|
||||||
const followingName = (actorFollow.ActorFollowing.VideoChannel || actorFollow.ActorFollowing.Account).getDisplayName()
|
const followingName = (actorFollow.ActorFollowing.VideoChannel || actorFollow.ActorFollowing.Account).getDisplayName()
|
||||||
|
|
||||||
const text = 'Hi dear user,\n\n' +
|
|
||||||
`Your ${followType} ${followingName} has a new subscriber: ${followerName}` +
|
|
||||||
'\n\n' +
|
|
||||||
'Cheers,\n' +
|
|
||||||
`${CONFIG.EMAIL.BODY.SIGNATURE}`
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
const emailPayload: EmailPayload = {
|
||||||
|
template: 'follower-on-channel',
|
||||||
to,
|
to,
|
||||||
subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New follower on your channel ' + followingName,
|
subject: `New follower on your channel ${followingName}`,
|
||||||
text
|
locals: {
|
||||||
|
followerName: actorFollow.ActorFollower.Account.getDisplayName(),
|
||||||
|
followerUrl: actorFollow.ActorFollower.url,
|
||||||
|
followingName,
|
||||||
|
followingUrl: actorFollow.ActorFollowing.url,
|
||||||
|
followType
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||||
|
@ -144,32 +149,28 @@ class Emailer {
|
||||||
addNewInstanceFollowerNotification (to: string[], actorFollow: MActorFollowActors) {
|
addNewInstanceFollowerNotification (to: string[], actorFollow: MActorFollowActors) {
|
||||||
const awaitingApproval = actorFollow.state === 'pending' ? ' awaiting manual approval.' : ''
|
const awaitingApproval = actorFollow.state === 'pending' ? ' awaiting manual approval.' : ''
|
||||||
|
|
||||||
const text = 'Hi dear admin,\n\n' +
|
|
||||||
`Your instance has a new follower: ${actorFollow.ActorFollower.url}${awaitingApproval}` +
|
|
||||||
'\n\n' +
|
|
||||||
'Cheers,\n' +
|
|
||||||
`${CONFIG.EMAIL.BODY.SIGNATURE}`
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
const emailPayload: EmailPayload = {
|
||||||
to,
|
to,
|
||||||
subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New instance follower',
|
subject: 'New instance follower',
|
||||||
text
|
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 })
|
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||||
}
|
}
|
||||||
|
|
||||||
addAutoInstanceFollowingNotification (to: string[], actorFollow: MActorFollowActors) {
|
addAutoInstanceFollowingNotification (to: string[], actorFollow: MActorFollowActors) {
|
||||||
const text = 'Hi dear admin,\n\n' +
|
const instanceUrl = actorFollow.ActorFollowing.url
|
||||||
`Your instance automatically followed a new instance: ${actorFollow.ActorFollowing.url}` +
|
|
||||||
'\n\n' +
|
|
||||||
'Cheers,\n' +
|
|
||||||
`${CONFIG.EMAIL.BODY.SIGNATURE}`
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
const emailPayload: EmailPayload = {
|
||||||
to,
|
to,
|
||||||
subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Auto instance following',
|
subject: 'Auto instance following',
|
||||||
text
|
text: `Your instance automatically followed a new instance: <a href="${instanceUrl}">${instanceUrl}</a>.`
|
||||||
}
|
}
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||||
|
@ -178,18 +179,17 @@ class Emailer {
|
||||||
myVideoPublishedNotification (to: string[], video: MVideo) {
|
myVideoPublishedNotification (to: string[], video: MVideo) {
|
||||||
const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
|
const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
|
||||||
|
|
||||||
const text = 'Hi dear user,\n\n' +
|
|
||||||
`Your video ${video.name} has been published.` +
|
|
||||||
'\n\n' +
|
|
||||||
`You can view it on ${videoUrl} ` +
|
|
||||||
'\n\n' +
|
|
||||||
'Cheers,\n' +
|
|
||||||
`${CONFIG.EMAIL.BODY.SIGNATURE}`
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
const emailPayload: EmailPayload = {
|
||||||
to,
|
to,
|
||||||
subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Your video ${video.name} is published`,
|
subject: `Your video ${video.name} has been published`,
|
||||||
text
|
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 })
|
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||||
|
@ -198,18 +198,17 @@ class Emailer {
|
||||||
myVideoImportSuccessNotification (to: string[], videoImport: MVideoImportVideo) {
|
myVideoImportSuccessNotification (to: string[], videoImport: MVideoImportVideo) {
|
||||||
const videoUrl = WEBSERVER.URL + videoImport.Video.getWatchStaticPath()
|
const videoUrl = WEBSERVER.URL + videoImport.Video.getWatchStaticPath()
|
||||||
|
|
||||||
const text = 'Hi dear user,\n\n' +
|
|
||||||
`Your video import ${videoImport.getTargetIdentifier()} is finished.` +
|
|
||||||
'\n\n' +
|
|
||||||
`You can view the imported video on ${videoUrl} ` +
|
|
||||||
'\n\n' +
|
|
||||||
'Cheers,\n' +
|
|
||||||
`${CONFIG.EMAIL.BODY.SIGNATURE}`
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
const emailPayload: EmailPayload = {
|
||||||
to,
|
to,
|
||||||
subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Your video import ${videoImport.getTargetIdentifier()} is finished`,
|
subject: `Your video import ${videoImport.getTargetIdentifier()} is complete`,
|
||||||
text
|
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 })
|
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||||
|
@ -218,40 +217,47 @@ class Emailer {
|
||||||
myVideoImportErrorNotification (to: string[], videoImport: MVideoImport) {
|
myVideoImportErrorNotification (to: string[], videoImport: MVideoImport) {
|
||||||
const importUrl = WEBSERVER.URL + '/my-account/video-imports'
|
const importUrl = WEBSERVER.URL + '/my-account/video-imports'
|
||||||
|
|
||||||
const text = 'Hi dear user,\n\n' +
|
const text =
|
||||||
`Your video import ${videoImport.getTargetIdentifier()} encountered an error.` +
|
`Your video import "${videoImport.getTargetIdentifier()}" encountered an error.` +
|
||||||
'\n\n' +
|
'\n\n' +
|
||||||
`See your videos import dashboard for more information: ${importUrl}` +
|
`See your videos import dashboard for more information: <a href="${importUrl}">${importUrl}</a>.`
|
||||||
'\n\n' +
|
|
||||||
'Cheers,\n' +
|
|
||||||
`${CONFIG.EMAIL.BODY.SIGNATURE}`
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
const emailPayload: EmailPayload = {
|
||||||
to,
|
to,
|
||||||
subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Your video import ${videoImport.getTargetIdentifier()} encountered an error`,
|
subject: `Your video import "${videoImport.getTargetIdentifier()}" encountered an error`,
|
||||||
text
|
text,
|
||||||
|
locals: {
|
||||||
|
title: 'Import failed',
|
||||||
|
action: {
|
||||||
|
text: 'Review imports',
|
||||||
|
url: importUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||||
}
|
}
|
||||||
|
|
||||||
addNewCommentOnMyVideoNotification (to: string[], comment: MCommentOwnerVideo) {
|
addNewCommentOnMyVideoNotification (to: string[], comment: MCommentOwnerVideo) {
|
||||||
const accountName = comment.Account.getDisplayName()
|
|
||||||
const video = comment.Video
|
const video = comment.Video
|
||||||
|
const videoUrl = WEBSERVER.URL + comment.Video.getWatchStaticPath()
|
||||||
const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath()
|
const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath()
|
||||||
|
|
||||||
const text = 'Hi dear user,\n\n' +
|
|
||||||
`A new comment has been posted by ${accountName} on your video ${video.name}` +
|
|
||||||
'\n\n' +
|
|
||||||
`You can view it on ${commentUrl} ` +
|
|
||||||
'\n\n' +
|
|
||||||
'Cheers,\n' +
|
|
||||||
`${CONFIG.EMAIL.BODY.SIGNATURE}`
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
const emailPayload: EmailPayload = {
|
||||||
|
template: 'video-comment-new',
|
||||||
to,
|
to,
|
||||||
subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New comment on your video ' + video.name,
|
subject: 'New comment on your video ' + video.name,
|
||||||
text
|
locals: {
|
||||||
|
accountName: comment.Account.getDisplayName(),
|
||||||
|
accountUrl: comment.Account.Actor.url,
|
||||||
|
comment,
|
||||||
|
video,
|
||||||
|
videoUrl,
|
||||||
|
action: {
|
||||||
|
text: 'View comment',
|
||||||
|
url: commentUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||||
|
@ -260,75 +266,88 @@ class Emailer {
|
||||||
addNewCommentMentionNotification (to: string[], comment: MCommentOwnerVideo) {
|
addNewCommentMentionNotification (to: string[], comment: MCommentOwnerVideo) {
|
||||||
const accountName = comment.Account.getDisplayName()
|
const accountName = comment.Account.getDisplayName()
|
||||||
const video = comment.Video
|
const video = comment.Video
|
||||||
|
const videoUrl = WEBSERVER.URL + comment.Video.getWatchStaticPath()
|
||||||
const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath()
|
const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath()
|
||||||
|
|
||||||
const text = 'Hi dear user,\n\n' +
|
|
||||||
`${accountName} mentioned you on video ${video.name}` +
|
|
||||||
'\n\n' +
|
|
||||||
`You can view the comment on ${commentUrl} ` +
|
|
||||||
'\n\n' +
|
|
||||||
'Cheers,\n' +
|
|
||||||
`${CONFIG.EMAIL.BODY.SIGNATURE}`
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
const emailPayload: EmailPayload = {
|
||||||
|
template: 'video-comment-mention',
|
||||||
to,
|
to,
|
||||||
subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Mention on video ' + video.name,
|
subject: 'Mention on video ' + video.name,
|
||||||
text
|
locals: {
|
||||||
|
comment,
|
||||||
|
video,
|
||||||
|
videoUrl,
|
||||||
|
accountName,
|
||||||
|
action: {
|
||||||
|
text: 'View comment',
|
||||||
|
url: commentUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||||
}
|
}
|
||||||
|
|
||||||
addVideoAbuseModeratorsNotification (to: string[], videoAbuse: MVideoAbuseVideo) {
|
addVideoAbuseModeratorsNotification (to: string[], parameters: {
|
||||||
const videoUrl = WEBSERVER.URL + videoAbuse.Video.getWatchStaticPath()
|
videoAbuse: VideoAbuse
|
||||||
|
videoAbuseInstance: MVideoAbuseVideo
|
||||||
const text = 'Hi,\n\n' +
|
reporter: string
|
||||||
`${WEBSERVER.HOST} received an abuse for the following video: ${videoUrl}\n\n` +
|
}) {
|
||||||
'Cheers,\n' +
|
const videoAbuseUrl = WEBSERVER.URL + '/admin/moderation/video-abuses/list?search=%23' + parameters.videoAbuse.id
|
||||||
`${CONFIG.EMAIL.BODY.SIGNATURE}`
|
const videoUrl = WEBSERVER.URL + parameters.videoAbuseInstance.Video.getWatchStaticPath()
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
const emailPayload: EmailPayload = {
|
||||||
|
template: 'video-abuse-new',
|
||||||
to,
|
to,
|
||||||
subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Received a video abuse',
|
subject: `New video abuse report from ${parameters.reporter}`,
|
||||||
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||||
}
|
}
|
||||||
|
|
||||||
addVideoAutoBlacklistModeratorsNotification (to: string[], videoBlacklist: MVideoBlacklistLightVideo) {
|
async addVideoAutoBlacklistModeratorsNotification (to: string[], videoBlacklist: MVideoBlacklistLightVideo) {
|
||||||
const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list'
|
const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list'
|
||||||
const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath()
|
const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath()
|
||||||
|
const channel = (await VideoChannelModel.loadByIdAndPopulateAccount(videoBlacklist.Video.channelId)).toFormattedSummaryJSON()
|
||||||
const text = 'Hi,\n\n' +
|
|
||||||
'A recently added video was auto-blacklisted and requires moderator review before publishing.' +
|
|
||||||
'\n\n' +
|
|
||||||
`You can view it and take appropriate action on ${videoUrl}` +
|
|
||||||
'\n\n' +
|
|
||||||
`A full list of auto-blacklisted videos can be reviewed here: ${VIDEO_AUTO_BLACKLIST_URL}` +
|
|
||||||
'\n\n' +
|
|
||||||
'Cheers,\n' +
|
|
||||||
`${CONFIG.EMAIL.BODY.SIGNATURE}`
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
const emailPayload: EmailPayload = {
|
||||||
|
template: 'video-auto-blacklist-new',
|
||||||
to,
|
to,
|
||||||
subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'An auto-blacklisted video is awaiting review',
|
subject: 'A new video is pending moderation',
|
||||||
text
|
locals: {
|
||||||
|
channel,
|
||||||
|
videoUrl,
|
||||||
|
videoName: videoBlacklist.Video.name,
|
||||||
|
action: {
|
||||||
|
text: 'Review autoblacklist',
|
||||||
|
url: VIDEO_AUTO_BLACKLIST_URL
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||||
}
|
}
|
||||||
|
|
||||||
addNewUserRegistrationNotification (to: string[], user: MUser) {
|
addNewUserRegistrationNotification (to: string[], user: MUser) {
|
||||||
const text = 'Hi,\n\n' +
|
|
||||||
`User ${user.username} just registered on ${WEBSERVER.HOST} PeerTube instance.\n\n` +
|
|
||||||
'Cheers,\n' +
|
|
||||||
`${CONFIG.EMAIL.BODY.SIGNATURE}`
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
const emailPayload: EmailPayload = {
|
||||||
|
template: 'user-registered',
|
||||||
to,
|
to,
|
||||||
subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New user registration on ' + WEBSERVER.HOST,
|
subject: `a new user registered on ${WEBSERVER.HOST}: ${user.username}`,
|
||||||
text
|
locals: {
|
||||||
|
user
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||||
|
@ -341,16 +360,13 @@ class Emailer {
|
||||||
const reasonString = videoBlacklist.reason ? ` for the following reason: ${videoBlacklist.reason}` : ''
|
const reasonString = videoBlacklist.reason ? ` for the following reason: ${videoBlacklist.reason}` : ''
|
||||||
const blockedString = `Your video ${videoName} (${videoUrl} on ${WEBSERVER.HOST} has been blacklisted${reasonString}.`
|
const blockedString = `Your video ${videoName} (${videoUrl} on ${WEBSERVER.HOST} has been blacklisted${reasonString}.`
|
||||||
|
|
||||||
const text = 'Hi,\n\n' +
|
|
||||||
blockedString +
|
|
||||||
'\n\n' +
|
|
||||||
'Cheers,\n' +
|
|
||||||
`${CONFIG.EMAIL.BODY.SIGNATURE}`
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
const emailPayload: EmailPayload = {
|
||||||
to,
|
to,
|
||||||
subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Video ${videoName} blacklisted`,
|
subject: `Video ${videoName} blacklisted`,
|
||||||
text
|
text: blockedString,
|
||||||
|
locals: {
|
||||||
|
title: 'Your video was blacklisted'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||||
|
@ -359,66 +375,53 @@ class Emailer {
|
||||||
addVideoUnblacklistNotification (to: string[], video: MVideo) {
|
addVideoUnblacklistNotification (to: string[], video: MVideo) {
|
||||||
const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
|
const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
|
||||||
|
|
||||||
const text = 'Hi,\n\n' +
|
|
||||||
`Your video ${video.name} (${videoUrl}) on ${WEBSERVER.HOST} has been unblacklisted.` +
|
|
||||||
'\n\n' +
|
|
||||||
'Cheers,\n' +
|
|
||||||
`${CONFIG.EMAIL.BODY.SIGNATURE}`
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
const emailPayload: EmailPayload = {
|
||||||
to,
|
to,
|
||||||
subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Video ${video.name} unblacklisted`,
|
subject: `Video ${video.name} unblacklisted`,
|
||||||
text
|
text: `Your video "${video.name}" (${videoUrl}) on ${WEBSERVER.HOST} has been unblacklisted.`,
|
||||||
|
locals: {
|
||||||
|
title: 'Your video was unblacklisted'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||||
}
|
}
|
||||||
|
|
||||||
addPasswordResetEmailJob (to: string, resetPasswordUrl: string) {
|
addPasswordResetEmailJob (to: string, resetPasswordUrl: string) {
|
||||||
const text = 'Hi dear user,\n\n' +
|
|
||||||
`A reset password procedure for your account ${to} has been requested on ${WEBSERVER.HOST} ` +
|
|
||||||
`Please follow this link to reset it: ${resetPasswordUrl} (the link will expire within 1 hour)\n\n` +
|
|
||||||
'If you are not the person who initiated this request, please ignore this email.\n\n' +
|
|
||||||
'Cheers,\n' +
|
|
||||||
`${CONFIG.EMAIL.BODY.SIGNATURE}`
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
const emailPayload: EmailPayload = {
|
||||||
|
template: 'password-reset',
|
||||||
to: [ to ],
|
to: [ to ],
|
||||||
subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Reset your password',
|
subject: 'Reset your account password',
|
||||||
text
|
locals: {
|
||||||
|
resetPasswordUrl
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||||
}
|
}
|
||||||
|
|
||||||
addPasswordCreateEmailJob (username: string, to: string, resetPasswordUrl: string) {
|
addPasswordCreateEmailJob (username: string, to: string, createPasswordUrl: string) {
|
||||||
const text = 'Hi,\n\n' +
|
|
||||||
`Welcome to your ${WEBSERVER.HOST} PeerTube instance. Your username is: ${username}.\n\n` +
|
|
||||||
`Please set your password by following this link: ${resetPasswordUrl} (this link will expire within seven days).\n\n` +
|
|
||||||
'Cheers,\n' +
|
|
||||||
`${CONFIG.EMAIL.BODY.SIGNATURE}`
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
const emailPayload: EmailPayload = {
|
||||||
|
template: 'password-create',
|
||||||
to: [ to ],
|
to: [ to ],
|
||||||
subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New PeerTube account password',
|
subject: 'Create your account password',
|
||||||
text
|
locals: {
|
||||||
|
username,
|
||||||
|
createPasswordUrl
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||||
}
|
}
|
||||||
|
|
||||||
addVerifyEmailJob (to: string, verifyEmailUrl: string) {
|
addVerifyEmailJob (to: string, verifyEmailUrl: string) {
|
||||||
const text = 'Welcome to PeerTube,\n\n' +
|
|
||||||
`To start using PeerTube on ${WEBSERVER.HOST} you must verify your email! ` +
|
|
||||||
`Please follow this link to verify this email belongs to you: ${verifyEmailUrl}\n\n` +
|
|
||||||
'If you are not the person who initiated this request, please ignore this email.\n\n' +
|
|
||||||
'Cheers,\n' +
|
|
||||||
`${CONFIG.EMAIL.BODY.SIGNATURE}`
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
const emailPayload: EmailPayload = {
|
||||||
|
template: 'verify-email',
|
||||||
to: [ to ],
|
to: [ to ],
|
||||||
subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Verify your email',
|
subject: `Verify your email on ${WEBSERVER.HOST}`,
|
||||||
text
|
locals: {
|
||||||
|
verifyEmailUrl
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||||
|
@ -427,39 +430,28 @@ class Emailer {
|
||||||
addUserBlockJob (user: MUser, blocked: boolean, reason?: string) {
|
addUserBlockJob (user: MUser, blocked: boolean, reason?: string) {
|
||||||
const reasonString = reason ? ` for the following reason: ${reason}` : ''
|
const reasonString = reason ? ` for the following reason: ${reason}` : ''
|
||||||
const blockedWord = blocked ? 'blocked' : 'unblocked'
|
const blockedWord = blocked ? 'blocked' : 'unblocked'
|
||||||
const blockedString = `Your account ${user.username} on ${WEBSERVER.HOST} has been ${blockedWord}${reasonString}.`
|
|
||||||
|
|
||||||
const text = 'Hi,\n\n' +
|
|
||||||
blockedString +
|
|
||||||
'\n\n' +
|
|
||||||
'Cheers,\n' +
|
|
||||||
`${CONFIG.EMAIL.BODY.SIGNATURE}`
|
|
||||||
|
|
||||||
const to = user.email
|
const to = user.email
|
||||||
const emailPayload: EmailPayload = {
|
const emailPayload: EmailPayload = {
|
||||||
to: [ to ],
|
to: [ to ],
|
||||||
subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Account ' + blockedWord,
|
subject: 'Account ' + blockedWord,
|
||||||
text
|
text: `Your account ${user.username} on ${WEBSERVER.HOST} has been ${blockedWord}${reasonString}.`
|
||||||
}
|
}
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||||
}
|
}
|
||||||
|
|
||||||
addContactFormJob (fromEmail: string, fromName: string, subject: string, body: string) {
|
addContactFormJob (fromEmail: string, fromName: string, subject: string, body: string) {
|
||||||
const text = 'Hello dear admin,\n\n' +
|
|
||||||
fromName + ' sent you a message' +
|
|
||||||
'\n\n---------------------------------------\n\n' +
|
|
||||||
body +
|
|
||||||
'\n\n---------------------------------------\n\n' +
|
|
||||||
'Cheers,\n' +
|
|
||||||
'PeerTube.'
|
|
||||||
|
|
||||||
const emailPayload: EmailPayload = {
|
const emailPayload: EmailPayload = {
|
||||||
fromDisplayName: fromEmail,
|
template: 'contact-form',
|
||||||
replyTo: fromEmail,
|
|
||||||
to: [ CONFIG.ADMIN.EMAIL ],
|
to: [ CONFIG.ADMIN.EMAIL ],
|
||||||
subject: CONFIG.EMAIL.SUBJECT.PREFIX + subject,
|
replyTo: `"${fromName}" <${fromEmail}>`,
|
||||||
text
|
subject: `(contact form) ${subject}`,
|
||||||
|
locals: {
|
||||||
|
fromName,
|
||||||
|
fromEmail,
|
||||||
|
body
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||||
|
@ -470,18 +462,44 @@ class Emailer {
|
||||||
throw new Error('Cannot send mail because SMTP is not configured.')
|
throw new Error('Cannot send mail because SMTP is not configured.')
|
||||||
}
|
}
|
||||||
|
|
||||||
const fromDisplayName = options.fromDisplayName
|
const fromDisplayName = options.from
|
||||||
? options.fromDisplayName
|
? options.from
|
||||||
: WEBSERVER.HOST
|
: WEBSERVER.HOST
|
||||||
|
|
||||||
|
const email = new Email({
|
||||||
|
send: true,
|
||||||
|
message: {
|
||||||
|
from: `"${fromDisplayName}" <${CONFIG.SMTP.FROM_ADDRESS}>`
|
||||||
|
},
|
||||||
|
transport: this.transporter,
|
||||||
|
views: {
|
||||||
|
root: join(root(), 'server', 'lib', 'emails')
|
||||||
|
},
|
||||||
|
subjectPrefix: CONFIG.EMAIL.SUBJECT.PREFIX
|
||||||
|
})
|
||||||
|
|
||||||
for (const to of options.to) {
|
for (const to of options.to) {
|
||||||
await this.transporter.sendMail({
|
await email
|
||||||
from: `"${fromDisplayName}" <${CONFIG.SMTP.FROM_ADDRESS}>`,
|
.send(merge(
|
||||||
replyTo: options.replyTo,
|
{
|
||||||
to,
|
template: 'common',
|
||||||
subject: options.subject,
|
message: {
|
||||||
text: options.text
|
to,
|
||||||
})
|
from: options.from,
|
||||||
|
subject: options.subject,
|
||||||
|
replyTo: options.replyTo
|
||||||
|
},
|
||||||
|
locals: { // default variables available in all templates
|
||||||
|
WEBSERVER,
|
||||||
|
EMAIL: CONFIG.EMAIL,
|
||||||
|
text: options.text,
|
||||||
|
subject: options.subject
|
||||||
|
}
|
||||||
|
},
|
||||||
|
options // overriden/new variables given for a specific template in the payload
|
||||||
|
) as SendEmailOptions)
|
||||||
|
.then(logger.info)
|
||||||
|
.catch(logger.error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,267 @@
|
||||||
|
//-
|
||||||
|
The email background color is defined in three places:
|
||||||
|
1. body tag: for most email clients
|
||||||
|
2. center tag: for Gmail and Inbox mobile apps and web versions of Gmail, GSuite, Inbox, Yahoo, AOL, Libero, Comcast, freenet, Mail.ru, Orange.fr
|
||||||
|
3. mso conditional: For Windows 10 Mail
|
||||||
|
- var backgroundColor = "#fff";
|
||||||
|
- var mainColor = "#f2690d";
|
||||||
|
doctype html
|
||||||
|
head
|
||||||
|
// This template is heavily adapted from the Cerberus Fluid template. Kudos to them!
|
||||||
|
meta(charset='utf-8')
|
||||||
|
//- utf-8 works for most cases
|
||||||
|
meta(name='viewport' content='width=device-width')
|
||||||
|
//- Forcing initial-scale shouldn't be necessary
|
||||||
|
meta(http-equiv='X-UA-Compatible' content='IE=edge')
|
||||||
|
//- Use the latest (edge) version of IE rendering engine
|
||||||
|
meta(name='x-apple-disable-message-reformatting')
|
||||||
|
//- Disable auto-scale in iOS 10 Mail entirely
|
||||||
|
meta(name='format-detection' content='telephone=no,address=no,email=no,date=no,url=no')
|
||||||
|
//- Tell iOS not to automatically link certain text strings.
|
||||||
|
meta(name='color-scheme' content='light')
|
||||||
|
meta(name='supported-color-schemes' content='light')
|
||||||
|
//- The title tag shows in email notifications, like Android 4.4.
|
||||||
|
title #{subject}
|
||||||
|
//- What it does: Makes background images in 72ppi Outlook render at correct size.
|
||||||
|
//if gte mso 9
|
||||||
|
xml
|
||||||
|
o:officedocumentsettings
|
||||||
|
o:allowpng
|
||||||
|
o:pixelsperinch 96
|
||||||
|
//- CSS Reset : BEGIN
|
||||||
|
style.
|
||||||
|
/* What it does: Tells the email client that only light styles are provided but the client can transform them to dark. A duplicate of meta color-scheme meta tag above. */
|
||||||
|
:root {
|
||||||
|
color-scheme: light;
|
||||||
|
supported-color-schemes: light;
|
||||||
|
}
|
||||||
|
/* What it does: Remove spaces around the email design added by some email clients. */
|
||||||
|
/* Beware: It can remove the padding / margin and add a background color to the compose a reply window. */
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0 auto !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
height: 100% !important;
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
/* What it does: Stops email clients resizing small text. */
|
||||||
|
* {
|
||||||
|
-ms-text-size-adjust: 100%;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
}
|
||||||
|
/* What it does: Centers email on Android 4.4 */
|
||||||
|
div[style*="margin: 16px 0"] {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
/* What it does: forces Samsung Android mail clients to use the entire viewport */
|
||||||
|
#MessageViewBody, #MessageWebViewDiv{
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
/* What it does: Stops Outlook from adding extra spacing to tables. */
|
||||||
|
table,
|
||||||
|
td {
|
||||||
|
mso-table-lspace: 0pt !important;
|
||||||
|
mso-table-rspace: 0pt !important;
|
||||||
|
}
|
||||||
|
/* What it does: Fixes webkit padding issue. */
|
||||||
|
table {
|
||||||
|
border-spacing: 0 !important;
|
||||||
|
border-collapse: collapse !important;
|
||||||
|
table-layout: fixed !important;
|
||||||
|
margin: 0 auto !important;
|
||||||
|
}
|
||||||
|
/* What it does: Uses a better rendering method when resizing images in IE. */
|
||||||
|
img {
|
||||||
|
-ms-interpolation-mode:bicubic;
|
||||||
|
}
|
||||||
|
/* What it does: Prevents Windows 10 Mail from underlining links despite inline CSS. Styles for underlined links should be inline. */
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a:not(.nocolor) {
|
||||||
|
color: #{mainColor};
|
||||||
|
}
|
||||||
|
a.nocolor {
|
||||||
|
color: inherit !important;
|
||||||
|
}
|
||||||
|
/* What it does: A work-around for email clients meddling in triggered links. */
|
||||||
|
a[x-apple-data-detectors], /* iOS */
|
||||||
|
.unstyle-auto-detected-links a,
|
||||||
|
.aBn {
|
||||||
|
border-bottom: 0 !important;
|
||||||
|
cursor: default !important;
|
||||||
|
color: inherit !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
font-size: inherit !important;
|
||||||
|
font-family: inherit !important;
|
||||||
|
font-weight: inherit !important;
|
||||||
|
line-height: inherit !important;
|
||||||
|
}
|
||||||
|
/* What it does: Prevents Gmail from displaying a download button on large, non-linked images. */
|
||||||
|
.a6S {
|
||||||
|
display: none !important;
|
||||||
|
opacity: 0.01 !important;
|
||||||
|
}
|
||||||
|
/* What it does: Prevents Gmail from changing the text color in conversation threads. */
|
||||||
|
.im {
|
||||||
|
color: inherit !important;
|
||||||
|
}
|
||||||
|
/* If the above doesn't work, add a .g-img class to any image in question. */
|
||||||
|
img.g-img + div {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
/* What it does: Removes right gutter in Gmail iOS app: https://github.com/TedGoas/Cerberus/issues/89 */
|
||||||
|
/* Create one of these media queries for each additional viewport size you'd like to fix */
|
||||||
|
/* iPhone 4, 4S, 5, 5S, 5C, and 5SE */
|
||||||
|
@media only screen and (min-device-width: 320px) and (max-device-width: 374px) {
|
||||||
|
u ~ div .email-container {
|
||||||
|
min-width: 320px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* iPhone 6, 6S, 7, 8, and X */
|
||||||
|
@media only screen and (min-device-width: 375px) and (max-device-width: 413px) {
|
||||||
|
u ~ div .email-container {
|
||||||
|
min-width: 375px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* iPhone 6+, 7+, and 8+ */
|
||||||
|
@media only screen and (min-device-width: 414px) {
|
||||||
|
u ~ div .email-container {
|
||||||
|
min-width: 414px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//- CSS Reset : END
|
||||||
|
//- CSS for PeerTube : START
|
||||||
|
style.
|
||||||
|
blockquote {
|
||||||
|
margin-left: 0;
|
||||||
|
padding-left: 20px;
|
||||||
|
border-left: 2px solid #f2690d;
|
||||||
|
}
|
||||||
|
//- CSS for PeerTube : END
|
||||||
|
//- Progressive Enhancements : BEGIN
|
||||||
|
style.
|
||||||
|
/* What it does: Hover styles for buttons */
|
||||||
|
.button-td,
|
||||||
|
.button-a {
|
||||||
|
transition: all 100ms ease-in;
|
||||||
|
}
|
||||||
|
.button-td-primary:hover,
|
||||||
|
.button-a-primary:hover {
|
||||||
|
background: #555555 !important;
|
||||||
|
border-color: #555555 !important;
|
||||||
|
}
|
||||||
|
/* Media Queries */
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
/* What it does: Adjust typography on small screens to improve readability */
|
||||||
|
.email-container p {
|
||||||
|
font-size: 17px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//- Progressive Enhancements : END
|
||||||
|
|
||||||
|
body(width="100%" style="margin: 0; padding: 0 !important; mso-line-height-rule: exactly; background-color: #{backgroundColor};")
|
||||||
|
center(role='article' aria-roledescription='email' lang='en' style='width: 100%; background-color: #{backgroundColor};')
|
||||||
|
//if mso | IE
|
||||||
|
table(role='presentation' border='0' cellpadding='0' cellspacing='0' width='100%' style='background-color: #fff;')
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
//- Visually Hidden Preheader Text : BEGIN
|
||||||
|
div(style='max-height:0; overflow:hidden; mso-hide:all;' aria-hidden='true')
|
||||||
|
block preheader
|
||||||
|
//- Visually Hidden Preheader Text : END
|
||||||
|
|
||||||
|
//- Create white space after the desired preview text so email clients don’t pull other distracting text into the inbox preview. Extend as necessary.
|
||||||
|
//- Preview Text Spacing Hack : BEGIN
|
||||||
|
div(style='display: none; font-size: 1px; line-height: 1px; max-height: 0px; max-width: 0px; opacity: 0; overflow: hidden; mso-hide: all; font-family: sans-serif;')
|
||||||
|
| ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌
|
||||||
|
//- Preview Text Spacing Hack : END
|
||||||
|
|
||||||
|
//-
|
||||||
|
Set the email width. Defined in two places:
|
||||||
|
1. max-width for all clients except Desktop Windows Outlook, allowing the email to squish on narrow but never go wider than 600px.
|
||||||
|
2. MSO tags for Desktop Windows Outlook enforce a 600px width.
|
||||||
|
.email-container(style='max-width: 600px; margin: 0 auto;')
|
||||||
|
//if mso
|
||||||
|
table(align='center' role='presentation' cellspacing='0' cellpadding='0' border='0' width='600')
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
//- Email Body : BEGIN
|
||||||
|
table(align='center' role='presentation' cellspacing='0' cellpadding='0' border='0' width='100%' style='margin: auto;')
|
||||||
|
//- 1 Column Text + Button : BEGIN
|
||||||
|
tr
|
||||||
|
td(style='background-color: #ffffff;')
|
||||||
|
table(role='presentation' cellspacing='0' cellpadding='0' border='0' width='100%')
|
||||||
|
tr
|
||||||
|
td(style='padding: 20px; font-family: sans-serif; font-size: 15px; line-height: 20px; color: #555555;')
|
||||||
|
table(role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%")
|
||||||
|
tr
|
||||||
|
td(width="40px")
|
||||||
|
img(src=`${WEBSERVER.URL}/client/assets/images/icons/icon-192x192.png` width="auto" height="30px" alt="icon" border="0" style="height: 30px; background: #ffffff; font-family: sans-serif; font-size: 15px; line-height: 15px; color: #555555;")
|
||||||
|
td
|
||||||
|
h1(style='margin: 10px 0 10px 0; font-family: sans-serif; font-size: 25px; line-height: 30px; color: #333333; font-weight: normal;')
|
||||||
|
block title
|
||||||
|
if title
|
||||||
|
| #{title}
|
||||||
|
else
|
||||||
|
| Something requires your attention
|
||||||
|
p(style='margin: 0;')
|
||||||
|
block body
|
||||||
|
if action
|
||||||
|
tr
|
||||||
|
td(style='padding: 0 20px;')
|
||||||
|
//- Button : BEGIN
|
||||||
|
table(align='center' role='presentation' cellspacing='0' cellpadding='0' border='0' style='margin: auto;')
|
||||||
|
tr
|
||||||
|
td.button-td.button-td-primary(style='border-radius: 4px; background: #222222;')
|
||||||
|
a.button-a.button-a-primary(href=action.url style='background: #222222; border: 1px solid #000000; font-family: sans-serif; font-size: 15px; line-height: 15px; text-decoration: none; padding: 13px 17px; color: #ffffff; display: block; border-radius: 4px;') #{action.text}
|
||||||
|
//- Button : END
|
||||||
|
//- 1 Column Text + Button : END
|
||||||
|
//- Clear Spacer : BEGIN
|
||||||
|
tr
|
||||||
|
td(aria-hidden='true' height='20' style='font-size: 0px; line-height: 0px;')
|
||||||
|
br
|
||||||
|
//- Clear Spacer : END
|
||||||
|
//- 1 Column Text : BEGIN
|
||||||
|
if username
|
||||||
|
tr
|
||||||
|
td(style='background-color: #cccccc;')
|
||||||
|
table(role='presentation' cellspacing='0' cellpadding='0' border='0' width='100%')
|
||||||
|
tr
|
||||||
|
td(style='padding: 20px; font-family: sans-serif; font-size: 15px; line-height: 20px; color: #555555;')
|
||||||
|
p(style='margin: 0;')
|
||||||
|
| You are receiving this email as part of your notification settings on #{WEBSERVER.HOST} for your account #{username}.
|
||||||
|
//- 1 Column Text : END
|
||||||
|
//- Email Body : END
|
||||||
|
//- Email Footer : BEGIN
|
||||||
|
table(align='center' role='presentation' cellspacing='0' cellpadding='0' border='0' width='100%' style='margin: auto;')
|
||||||
|
tr
|
||||||
|
td(style='padding: 20px; padding-bottom: 0px; font-family: sans-serif; font-size: 12px; line-height: 15px; text-align: center; color: #888888;')
|
||||||
|
webversion
|
||||||
|
a.nocolor(href=`${WEBSERVER.URL}/my-account/notifications` style='color: #cccccc; font-weight: bold;') View in your notifications
|
||||||
|
br
|
||||||
|
tr
|
||||||
|
td(style='padding: 20px; padding-top: 10px; font-family: sans-serif; font-size: 12px; line-height: 15px; text-align: center; color: #888888;')
|
||||||
|
unsubscribe
|
||||||
|
a.nocolor(href=`${WEBSERVER.URL}/my-account/settings#notifications` style='color: #888888;') Manage your notification preferences in your profile
|
||||||
|
br
|
||||||
|
//- Email Footer : END
|
||||||
|
//if mso
|
||||||
|
//- Full Bleed Background Section : BEGIN
|
||||||
|
table(role='presentation' cellspacing='0' cellpadding='0' border='0' width='100%' style=`background-color: ${mainColor};`)
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
.email-container(align='center' style='max-width: 600px; margin: auto;')
|
||||||
|
//if mso
|
||||||
|
table(role='presentation' cellspacing='0' cellpadding='0' border='0' width='600' align='center')
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
table(role='presentation' cellspacing='0' cellpadding='0' border='0' width='100%')
|
||||||
|
tr
|
||||||
|
td(style='padding: 20px; text-align: left; font-family: sans-serif; font-size: 12px; line-height: 20px; color: #ffffff;')
|
||||||
|
table(role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%")
|
||||||
|
tr
|
||||||
|
td(valign="top") #[a(href="https://github.com/Chocobozzz/PeerTube" style="color: white !important") PeerTube © 2015-#{new Date().getFullYear()}] #[a(href="https://github.com/Chocobozzz/PeerTube/blob/master/CREDITS.md" style="color: white !important") PeerTube Contributors]
|
||||||
|
//if mso
|
||||||
|
//- Full Bleed Background Section : END
|
||||||
|
//if mso | IE
|
|
@ -0,0 +1,11 @@
|
||||||
|
extends base
|
||||||
|
|
||||||
|
block body
|
||||||
|
if username
|
||||||
|
p Hi #{username},
|
||||||
|
else
|
||||||
|
p Hi,
|
||||||
|
block content
|
||||||
|
p
|
||||||
|
| Cheers,#[br]
|
||||||
|
| #{EMAIL.BODY.SIGNATURE}
|
|
@ -0,0 +1,4 @@
|
||||||
|
extends greetings
|
||||||
|
|
||||||
|
block content
|
||||||
|
p !{text}
|
|
@ -0,0 +1,3 @@
|
||||||
|
mixin channel(channel)
|
||||||
|
- var handle = `${channel.name}@${channel.host}`
|
||||||
|
| #[a(href=`${WEBSERVER.URL}/video-channels/${handle}` title=handle) #{channel.displayName}]
|
|
@ -0,0 +1,9 @@
|
||||||
|
extends ../common/greetings
|
||||||
|
|
||||||
|
block title
|
||||||
|
| Someone just used the contact form
|
||||||
|
|
||||||
|
block content
|
||||||
|
p #{fromName} sent you a message via the contact form on #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}]:
|
||||||
|
blockquote(style='white-space: pre-wrap') #{body}
|
||||||
|
p You can contact them at #[a(href=`mailto:${fromEmail}`) #{fromEmail}], or simply reply to this email to get in touch.
|
|
@ -0,0 +1,9 @@
|
||||||
|
extends ../common/greetings
|
||||||
|
|
||||||
|
block title
|
||||||
|
| New follower on your channel
|
||||||
|
|
||||||
|
block content
|
||||||
|
p.
|
||||||
|
Your #{followType} #[a(href=followingUrl) #{followingName}] has a new subscriber:
|
||||||
|
#[a(href=followerUrl) #{followerName}].
|
|
@ -0,0 +1,10 @@
|
||||||
|
extends ../common/greetings
|
||||||
|
|
||||||
|
block title
|
||||||
|
| Password creation for your account
|
||||||
|
|
||||||
|
block content
|
||||||
|
p.
|
||||||
|
Welcome to #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}], your PeerTube instance. Your username is: #{username}.
|
||||||
|
Please set your password by following #[a(href=createPasswordUrl) this link]: #[a(href=createPasswordUrl) #{createPasswordUrl}]
|
||||||
|
(this link will expire within seven days).
|
|
@ -0,0 +1,12 @@
|
||||||
|
extends ../common/greetings
|
||||||
|
|
||||||
|
block title
|
||||||
|
| Password reset for your account
|
||||||
|
|
||||||
|
block content
|
||||||
|
p.
|
||||||
|
A reset password procedure for your account ${to} has been requested on #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}].
|
||||||
|
Please follow #[a(href=resetPasswordUrl) this link] to reset it: #[a(href=resetPasswordUrl) #{resetPasswordUrl}]
|
||||||
|
(the link will expire within 1 hour)
|
||||||
|
p.
|
||||||
|
If you are not the person who initiated this request, please ignore this email.
|
|
@ -0,0 +1,10 @@
|
||||||
|
extends ../common/greetings
|
||||||
|
|
||||||
|
block title
|
||||||
|
| A new user registered
|
||||||
|
|
||||||
|
block content
|
||||||
|
- var mail = user.email || user.pendingEmail;
|
||||||
|
p
|
||||||
|
| User #[a(href=`${WEBSERVER.URL}/accounts/${user.username}`) #{user.username}] just registered.
|
||||||
|
| You might want to contact them at #[a(href=`mailto:${mail}`) #{mail}].
|
|
@ -0,0 +1,14 @@
|
||||||
|
extends ../common/greetings
|
||||||
|
|
||||||
|
block title
|
||||||
|
| Account verification
|
||||||
|
|
||||||
|
block content
|
||||||
|
p Welcome to PeerTube!
|
||||||
|
p.
|
||||||
|
You just created an account #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}], your new PeerTube instance.
|
||||||
|
Your username there is: #{username}.
|
||||||
|
p.
|
||||||
|
To start using PeerTube on #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] you must verify your email first!
|
||||||
|
Please follow #[a(href=verifyEmailUrl) this link] to verify this email belongs to you: #[a(href=verifyEmailUrl) #{verifyEmailUrl}]
|
||||||
|
If you are not the person who initiated this request, please ignore this email.
|
|
@ -0,0 +1,18 @@
|
||||||
|
extends ../common/greetings
|
||||||
|
include ../common/mixins.pug
|
||||||
|
|
||||||
|
block title
|
||||||
|
| A video is pending moderation
|
||||||
|
|
||||||
|
block content
|
||||||
|
p
|
||||||
|
| #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] received an abuse report for the #{videoAbuse.video.channel.isLocal ? '' : 'remote '}video "
|
||||||
|
a(href=videoUrl) #{videoAbuse.video.name}
|
||||||
|
| " by #[+channel(videoAbuse.video.channel)]
|
||||||
|
if videoPublishedAt
|
||||||
|
| , published the #{videoPublishedAt}.
|
||||||
|
else
|
||||||
|
| , uploaded the #{videoCreatedAt} but not yet published.
|
||||||
|
p The reporter, #{reporter}, cited the following reason(s):
|
||||||
|
blockquote #{videoAbuse.reason}
|
||||||
|
br(style="display: none;")
|
|
@ -0,0 +1,17 @@
|
||||||
|
extends ../common/greetings
|
||||||
|
include ../common/mixins
|
||||||
|
|
||||||
|
block title
|
||||||
|
| A video is pending moderation
|
||||||
|
|
||||||
|
block content
|
||||||
|
p
|
||||||
|
| A recently added video was auto-blacklisted and requires moderator review before going public:
|
||||||
|
|
|
||||||
|
a(href=videoUrl) #{videoName}
|
||||||
|
|
|
||||||
|
| by #[+channel(channel)].
|
||||||
|
p.
|
||||||
|
Apart from the publisher and the moderation team, no one will be able to see the video until you
|
||||||
|
unblacklist it. If you trust the publisher, any admin can whitelist the user for later videos so
|
||||||
|
that they don't require approval before going public.
|
|
@ -0,0 +1,11 @@
|
||||||
|
extends ../common/greetings
|
||||||
|
|
||||||
|
block title
|
||||||
|
| Someone mentioned you
|
||||||
|
|
||||||
|
block content
|
||||||
|
p.
|
||||||
|
#[a(href=accountUrl title=handle) #{accountName}] mentioned you in a comment on video
|
||||||
|
"#[a(href=videoUrl) #{video.name}]":
|
||||||
|
blockquote #{comment.text}
|
||||||
|
br(style="display: none;")
|
|
@ -0,0 +1,11 @@
|
||||||
|
extends ../common/greetings
|
||||||
|
|
||||||
|
block title
|
||||||
|
| Someone commented your video
|
||||||
|
|
||||||
|
block content
|
||||||
|
p.
|
||||||
|
#[a(href=accountUrl title=handle) #{accountName}] added a comment on your video
|
||||||
|
"#[a(href=videoUrl) #{video.name}]":
|
||||||
|
blockquote #{comment.text}
|
||||||
|
br(style="display: none;")
|
|
@ -5,7 +5,7 @@ import { UserNotificationModel } from '../models/account/user-notification'
|
||||||
import { UserModel } from '../models/account/user'
|
import { UserModel } from '../models/account/user'
|
||||||
import { PeerTubeSocket } from './peertube-socket'
|
import { PeerTubeSocket } from './peertube-socket'
|
||||||
import { CONFIG } from '../initializers/config'
|
import { CONFIG } from '../initializers/config'
|
||||||
import { VideoPrivacy, VideoState } from '../../shared/models/videos'
|
import { VideoPrivacy, VideoState, VideoAbuse } from '../../shared/models/videos'
|
||||||
import { AccountBlocklistModel } from '../models/account/account-blocklist'
|
import { AccountBlocklistModel } from '../models/account/account-blocklist'
|
||||||
import {
|
import {
|
||||||
MCommentOwnerVideo,
|
MCommentOwnerVideo,
|
||||||
|
@ -77,9 +77,9 @@ class Notifier {
|
||||||
.catch(err => logger.error('Cannot notify mentions of comment %s.', comment.url, { err }))
|
.catch(err => logger.error('Cannot notify mentions of comment %s.', comment.url, { err }))
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyOnNewVideoAbuse (videoAbuse: MVideoAbuseVideo): void {
|
notifyOnNewVideoAbuse (parameters: { videoAbuse: VideoAbuse, videoAbuseInstance: MVideoAbuseVideo, reporter: string }): void {
|
||||||
this.notifyModeratorsOfNewVideoAbuse(videoAbuse)
|
this.notifyModeratorsOfNewVideoAbuse(parameters)
|
||||||
.catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err }))
|
.catch(err => logger.error('Cannot notify of new video abuse of video %s.', parameters.videoAbuseInstance.Video.url, { err }))
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyOnVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo): void {
|
notifyOnVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo): void {
|
||||||
|
@ -350,11 +350,15 @@ class Notifier {
|
||||||
return this.notify({ users: admins, settingGetter, notificationCreator, emailSender })
|
return this.notify({ users: admins, settingGetter, notificationCreator, emailSender })
|
||||||
}
|
}
|
||||||
|
|
||||||
private async notifyModeratorsOfNewVideoAbuse (videoAbuse: MVideoAbuseVideo) {
|
private async notifyModeratorsOfNewVideoAbuse (parameters: {
|
||||||
|
videoAbuse: VideoAbuse
|
||||||
|
videoAbuseInstance: MVideoAbuseVideo
|
||||||
|
reporter: string
|
||||||
|
}) {
|
||||||
const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_ABUSES)
|
const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_ABUSES)
|
||||||
if (moderators.length === 0) return
|
if (moderators.length === 0) return
|
||||||
|
|
||||||
logger.info('Notifying %s user/moderators of new video abuse %s.', moderators.length, videoAbuse.Video.url)
|
logger.info('Notifying %s user/moderators of new video abuse %s.', moderators.length, parameters.videoAbuseInstance.Video.url)
|
||||||
|
|
||||||
function settingGetter (user: MUserWithNotificationSetting) {
|
function settingGetter (user: MUserWithNotificationSetting) {
|
||||||
return user.NotificationSetting.videoAbuseAsModerator
|
return user.NotificationSetting.videoAbuseAsModerator
|
||||||
|
@ -364,15 +368,15 @@ class Notifier {
|
||||||
const notification: UserNotificationModelForApi = await UserNotificationModel.create<UserNotificationModelForApi>({
|
const notification: UserNotificationModelForApi = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||||
type: UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS,
|
type: UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
videoAbuseId: videoAbuse.id
|
videoAbuseId: parameters.videoAbuse.id
|
||||||
})
|
})
|
||||||
notification.VideoAbuse = videoAbuse
|
notification.VideoAbuse = parameters.videoAbuseInstance
|
||||||
|
|
||||||
return notification
|
return notification
|
||||||
}
|
}
|
||||||
|
|
||||||
function emailSender (emails: string[]) {
|
function emailSender (emails: string[]) {
|
||||||
return Emailer.Instance.addVideoAbuseModeratorsNotification(emails, videoAbuse)
|
return Emailer.Instance.addVideoAbuseModeratorsNotification(emails, parameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
|
return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
|
||||||
|
|
|
@ -46,7 +46,7 @@ describe('Test contact form', function () {
|
||||||
const email = emails[0]
|
const email = emails[0]
|
||||||
|
|
||||||
expect(email['from'][0]['address']).equal('test-admin@localhost')
|
expect(email['from'][0]['address']).equal('test-admin@localhost')
|
||||||
expect(email['from'][0]['name']).equal('toto@example.com')
|
expect(email['replyTo'][0]['address']).equal('toto@example.com')
|
||||||
expect(email['to'][0]['address']).equal('admin' + server.internalServerNumber + '@example.com')
|
expect(email['to'][0]['address']).equal('admin' + server.internalServerNumber + '@example.com')
|
||||||
expect(email['subject']).contains('my subject')
|
expect(email['subject']).contains('my subject')
|
||||||
expect(email['text']).contains('my super message')
|
expect(email['text']).contains('my super message')
|
||||||
|
|
|
@ -110,10 +110,10 @@ async function checkNotification (
|
||||||
|
|
||||||
if (checkType === 'presence') {
|
if (checkType === 'presence') {
|
||||||
const obj = inspect(base.socketNotifications, { depth: 5 })
|
const obj = inspect(base.socketNotifications, { depth: 5 })
|
||||||
expect(socketNotification, 'The socket notification is absent. ' + obj).to.not.be.undefined
|
expect(socketNotification, 'The socket notification is absent when is should be present. ' + obj).to.not.be.undefined
|
||||||
} else {
|
} else {
|
||||||
const obj = inspect(socketNotification, { depth: 5 })
|
const obj = inspect(socketNotification, { depth: 5 })
|
||||||
expect(socketNotification, 'The socket notification is present. ' + obj).to.be.undefined
|
expect(socketNotification, 'The socket notification is present when is should not be present. ' + obj).to.be.undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,9 +125,9 @@ async function checkNotification (
|
||||||
.find(e => emailNotificationFinder(e))
|
.find(e => emailNotificationFinder(e))
|
||||||
|
|
||||||
if (checkType === 'presence') {
|
if (checkType === 'presence') {
|
||||||
expect(email, 'The email is absent. ' + inspect(base.emails)).to.not.be.undefined
|
expect(email, 'The email is absent when is should be present. ' + inspect(base.emails)).to.not.be.undefined
|
||||||
} else {
|
} else {
|
||||||
expect(email, 'The email is present. ' + inspect(email)).to.be.undefined
|
expect(email, 'The email is present when is should not be present. ' + inspect(email)).to.be.undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,12 +172,12 @@ async function checkNewVideoFromSubscription (base: CheckerBaseParams, videoName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function emailFinder (email: object) {
|
function emailNotificationFinder (email: object) {
|
||||||
const text = email['text']
|
const text = email['text']
|
||||||
return text.indexOf(videoUUID) !== -1 && text.indexOf('Your subscription') !== -1
|
return text.indexOf(videoUUID) !== -1 && text.indexOf('Your subscription') !== -1
|
||||||
}
|
}
|
||||||
|
|
||||||
await checkNotification(base, notificationChecker, emailFinder, type)
|
await checkNotification(base, notificationChecker, emailNotificationFinder, type)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkVideoIsPublished (base: CheckerBaseParams, videoName: string, videoUUID: string, type: CheckerType) {
|
async function checkVideoIsPublished (base: CheckerBaseParams, videoName: string, videoUUID: string, type: CheckerType) {
|
||||||
|
@ -195,12 +195,12 @@ async function checkVideoIsPublished (base: CheckerBaseParams, videoName: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function emailFinder (email: object) {
|
function emailNotificationFinder (email: object) {
|
||||||
const text: string = email['text']
|
const text: string = email['text']
|
||||||
return text.includes(videoUUID) && text.includes('Your video')
|
return text.includes(videoUUID) && text.includes('Your video')
|
||||||
}
|
}
|
||||||
|
|
||||||
await checkNotification(base, notificationChecker, emailFinder, type)
|
await checkNotification(base, notificationChecker, emailNotificationFinder, type)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkMyVideoImportIsFinished (
|
async function checkMyVideoImportIsFinished (
|
||||||
|
@ -226,14 +226,14 @@ async function checkMyVideoImportIsFinished (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function emailFinder (email: object) {
|
function emailNotificationFinder (email: object) {
|
||||||
const text: string = email['text']
|
const text: string = email['text']
|
||||||
const toFind = success ? ' finished' : ' error'
|
const toFind = success ? ' finished' : ' error'
|
||||||
|
|
||||||
return text.includes(url) && text.includes(toFind)
|
return text.includes(url) && text.includes(toFind)
|
||||||
}
|
}
|
||||||
|
|
||||||
await checkNotification(base, notificationChecker, emailFinder, type)
|
await checkNotification(base, notificationChecker, emailNotificationFinder, type)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkUserRegistered (base: CheckerBaseParams, username: string, type: CheckerType) {
|
async function checkUserRegistered (base: CheckerBaseParams, username: string, type: CheckerType) {
|
||||||
|
@ -251,13 +251,13 @@ async function checkUserRegistered (base: CheckerBaseParams, username: string, t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function emailFinder (email: object) {
|
function emailNotificationFinder (email: object) {
|
||||||
const text: string = email['text']
|
const text: string = email['text']
|
||||||
|
|
||||||
return text.includes(' registered ') && text.includes(username)
|
return text.includes(' registered.') && text.includes(username)
|
||||||
}
|
}
|
||||||
|
|
||||||
await checkNotification(base, notificationChecker, emailFinder, type)
|
await checkNotification(base, notificationChecker, emailNotificationFinder, type)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkNewActorFollow (
|
async function checkNewActorFollow (
|
||||||
|
@ -291,13 +291,13 @@ async function checkNewActorFollow (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function emailFinder (email: object) {
|
function emailNotificationFinder (email: object) {
|
||||||
const text: string = email['text']
|
const text: string = email['text']
|
||||||
|
|
||||||
return text.includes('Your ' + followType) && text.includes(followingDisplayName) && text.includes(followerDisplayName)
|
return text.includes(followType) && text.includes(followingDisplayName) && text.includes(followerDisplayName)
|
||||||
}
|
}
|
||||||
|
|
||||||
await checkNotification(base, notificationChecker, emailFinder, type)
|
await checkNotification(base, notificationChecker, emailNotificationFinder, type)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkNewInstanceFollower (base: CheckerBaseParams, followerHost: string, type: CheckerType) {
|
async function checkNewInstanceFollower (base: CheckerBaseParams, followerHost: string, type: CheckerType) {
|
||||||
|
@ -320,13 +320,13 @@ async function checkNewInstanceFollower (base: CheckerBaseParams, followerHost:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function emailFinder (email: object) {
|
function emailNotificationFinder (email: object) {
|
||||||
const text: string = email['text']
|
const text: string = email['text']
|
||||||
|
|
||||||
return text.includes('instance has a new follower') && text.includes(followerHost)
|
return text.includes('instance has a new follower') && text.includes(followerHost)
|
||||||
}
|
}
|
||||||
|
|
||||||
await checkNotification(base, notificationChecker, emailFinder, type)
|
await checkNotification(base, notificationChecker, emailNotificationFinder, type)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkAutoInstanceFollowing (base: CheckerBaseParams, followerHost: string, followingHost: string, type: CheckerType) {
|
async function checkAutoInstanceFollowing (base: CheckerBaseParams, followerHost: string, followingHost: string, type: CheckerType) {
|
||||||
|
@ -351,13 +351,13 @@ async function checkAutoInstanceFollowing (base: CheckerBaseParams, followerHost
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function emailFinder (email: object) {
|
function emailNotificationFinder (email: object) {
|
||||||
const text: string = email['text']
|
const text: string = email['text']
|
||||||
|
|
||||||
return text.includes(' automatically followed a new instance') && text.includes(followingHost)
|
return text.includes(' automatically followed a new instance') && text.includes(followingHost)
|
||||||
}
|
}
|
||||||
|
|
||||||
await checkNotification(base, notificationChecker, emailFinder, type)
|
await checkNotification(base, notificationChecker, emailNotificationFinder, type)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkCommentMention (
|
async function checkCommentMention (
|
||||||
|
@ -385,13 +385,13 @@ async function checkCommentMention (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function emailFinder (email: object) {
|
function emailNotificationFinder (email: object) {
|
||||||
const text: string = email['text']
|
const text: string = email['text']
|
||||||
|
|
||||||
return text.includes(' mentioned ') && text.includes(uuid) && text.includes(byAccountDisplayName)
|
return text.includes(' mentioned ') && text.includes(uuid) && text.includes(byAccountDisplayName)
|
||||||
}
|
}
|
||||||
|
|
||||||
await checkNotification(base, notificationChecker, emailFinder, type)
|
await checkNotification(base, notificationChecker, emailNotificationFinder, type)
|
||||||
}
|
}
|
||||||
|
|
||||||
let lastEmailCount = 0
|
let lastEmailCount = 0
|
||||||
|
@ -416,11 +416,11 @@ async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string,
|
||||||
|
|
||||||
const commentUrl = `http://localhost:${base.server.port}/videos/watch/${uuid};threadId=${threadId}`
|
const commentUrl = `http://localhost:${base.server.port}/videos/watch/${uuid};threadId=${threadId}`
|
||||||
|
|
||||||
function emailFinder (email: object) {
|
function emailNotificationFinder (email: object) {
|
||||||
return email['text'].indexOf(commentUrl) !== -1
|
return email['text'].indexOf(commentUrl) !== -1
|
||||||
}
|
}
|
||||||
|
|
||||||
await checkNotification(base, notificationChecker, emailFinder, type)
|
await checkNotification(base, notificationChecker, emailNotificationFinder, type)
|
||||||
|
|
||||||
if (type === 'presence') {
|
if (type === 'presence') {
|
||||||
// We cannot detect email duplicates, so check we received another email
|
// We cannot detect email duplicates, so check we received another email
|
||||||
|
@ -446,12 +446,12 @@ async function checkNewVideoAbuseForModerators (base: CheckerBaseParams, videoUU
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function emailFinder (email: object) {
|
function emailNotificationFinder (email: object) {
|
||||||
const text = email['text']
|
const text = email['text']
|
||||||
return text.indexOf(videoUUID) !== -1 && text.indexOf('abuse') !== -1
|
return text.indexOf(videoUUID) !== -1 && text.indexOf('abuse') !== -1
|
||||||
}
|
}
|
||||||
|
|
||||||
await checkNotification(base, notificationChecker, emailFinder, type)
|
await checkNotification(base, notificationChecker, emailNotificationFinder, type)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkVideoAutoBlacklistForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
|
async function checkVideoAutoBlacklistForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
|
||||||
|
@ -471,12 +471,12 @@ async function checkVideoAutoBlacklistForModerators (base: CheckerBaseParams, vi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function emailFinder (email: object) {
|
function emailNotificationFinder (email: object) {
|
||||||
const text = email['text']
|
const text = email['text']
|
||||||
return text.indexOf(videoUUID) !== -1 && email['text'].indexOf('video-auto-blacklist/list') !== -1
|
return text.indexOf(videoUUID) !== -1 && email['text'].indexOf('video-auto-blacklist/list') !== -1
|
||||||
}
|
}
|
||||||
|
|
||||||
await checkNotification(base, notificationChecker, emailFinder, type)
|
await checkNotification(base, notificationChecker, emailNotificationFinder, type)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkNewBlacklistOnMyVideo (
|
async function checkNewBlacklistOnMyVideo (
|
||||||
|
@ -498,12 +498,12 @@ async function checkNewBlacklistOnMyVideo (
|
||||||
checkVideo(video, videoName, videoUUID)
|
checkVideo(video, videoName, videoUUID)
|
||||||
}
|
}
|
||||||
|
|
||||||
function emailFinder (email: object) {
|
function emailNotificationFinder (email: object) {
|
||||||
const text = email['text']
|
const text = email['text']
|
||||||
return text.indexOf(videoUUID) !== -1 && text.indexOf(' ' + blacklistType) !== -1
|
return text.indexOf(videoUUID) !== -1 && text.indexOf(' ' + blacklistType) !== -1
|
||||||
}
|
}
|
||||||
|
|
||||||
await checkNotification(base, notificationChecker, emailFinder, 'presence')
|
await checkNotification(base, notificationChecker, emailNotificationFinder, 'presence')
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
export type SendEmailOptions = {
|
export type SendEmailOptions = {
|
||||||
to: string[]
|
to: string[]
|
||||||
subject: string
|
|
||||||
text: string
|
|
||||||
|
|
||||||
fromDisplayName?: string
|
template?: string
|
||||||
|
locals?: { [key: string]: any }
|
||||||
|
|
||||||
|
// override defaults
|
||||||
|
subject?: string
|
||||||
|
text?: string
|
||||||
|
from?: string | { name?: string, address: string }
|
||||||
replyTo?: string
|
replyTo?: string
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue