diff options
Diffstat (limited to 'server/lib')
-rw-r--r-- | server/lib/emailer.ts | 53 | ||||
-rw-r--r-- | server/lib/emails/abuse-new-message/html.pug | 11 | ||||
-rw-r--r-- | server/lib/emails/abuse-state-change/html.pug | 9 | ||||
-rw-r--r-- | server/lib/notifier.ts | 118 | ||||
-rw-r--r-- | server/lib/user.ts | 2 |
5 files changed, 187 insertions, 6 deletions
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts index c6ad03328..9c49aa2f6 100644 --- a/server/lib/emailer.ts +++ b/server/lib/emailer.ts | |||
@@ -5,13 +5,13 @@ import { join } from 'path' | |||
5 | import { VideoChannelModel } from '@server/models/video/video-channel' | 5 | import { VideoChannelModel } from '@server/models/video/video-channel' |
6 | import { MVideoBlacklistLightVideo, MVideoBlacklistVideo } from '@server/types/models/video/video-blacklist' | 6 | import { MVideoBlacklistLightVideo, MVideoBlacklistVideo } from '@server/types/models/video/video-blacklist' |
7 | import { MVideoImport, MVideoImportVideo } from '@server/types/models/video/video-import' | 7 | import { MVideoImport, MVideoImportVideo } from '@server/types/models/video/video-import' |
8 | import { UserAbuse, EmailPayload } from '@shared/models' | 8 | import { AbuseState, EmailPayload, UserAbuse } from '@shared/models' |
9 | import { SendEmailOptions } from '../../shared/models/server/emailer.model' | 9 | import { SendEmailOptions } from '../../shared/models/server/emailer.model' |
10 | import { isTestInstance, root } from '../helpers/core-utils' | 10 | import { isTestInstance, root } from '../helpers/core-utils' |
11 | import { bunyanLogger, logger } from '../helpers/logger' | 11 | import { bunyanLogger, logger } from '../helpers/logger' |
12 | import { CONFIG, isEmailEnabled } from '../initializers/config' | 12 | import { CONFIG, isEmailEnabled } from '../initializers/config' |
13 | import { WEBSERVER } from '../initializers/constants' | 13 | import { WEBSERVER } from '../initializers/constants' |
14 | import { MAbuseFull, MActorFollowActors, MActorFollowFull, MUser } from '../types/models' | 14 | import { MAbuseFull, MAbuseMessage, MActorFollowActors, MActorFollowFull, MUser } from '../types/models' |
15 | import { MCommentOwnerVideo, MVideo, MVideoAccountLight } from '../types/models/video' | 15 | import { MCommentOwnerVideo, MVideo, MVideoAccountLight } from '../types/models/video' |
16 | import { JobQueue } from './job-queue' | 16 | import { JobQueue } from './job-queue' |
17 | 17 | ||
@@ -357,6 +357,55 @@ class Emailer { | |||
357 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 357 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
358 | } | 358 | } |
359 | 359 | ||
360 | addAbuseStateChangeNotification (to: string[], abuse: MAbuseFull) { | ||
361 | const text = abuse.state === AbuseState.ACCEPTED | ||
362 | ? 'Report #' + abuse.id + ' has been accepted' | ||
363 | : 'Report #' + abuse.id + ' has been rejected' | ||
364 | |||
365 | const action = { | ||
366 | text, | ||
367 | url: WEBSERVER.URL + '/my-account/abuses?search=%23' + abuse.id | ||
368 | } | ||
369 | |||
370 | const emailPayload: EmailPayload = { | ||
371 | template: 'abuse-state-change', | ||
372 | to, | ||
373 | subject: text, | ||
374 | locals: { | ||
375 | action, | ||
376 | abuseId: abuse.id, | ||
377 | isAccepted: abuse.state === AbuseState.ACCEPTED | ||
378 | } | ||
379 | } | ||
380 | |||
381 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | ||
382 | } | ||
383 | |||
384 | addAbuseNewMessageNotification (to: string[], options: { target: 'moderator' | 'reporter', abuse: MAbuseFull, message: MAbuseMessage }) { | ||
385 | const { abuse, target, message } = options | ||
386 | |||
387 | const text = 'New message on abuse #' + abuse.id | ||
388 | const action = { | ||
389 | text, | ||
390 | url: target === 'moderator' | ||
391 | ? WEBSERVER.URL + '/admin/moderation/abuses/list?search=%23' + abuse.id | ||
392 | : WEBSERVER.URL + '/my-account/abuses?search=%23' + abuse.id | ||
393 | } | ||
394 | |||
395 | const emailPayload: EmailPayload = { | ||
396 | template: 'abuse-new-message', | ||
397 | to, | ||
398 | subject: text, | ||
399 | locals: { | ||
400 | abuseUrl: action.url, | ||
401 | messageText: message.message, | ||
402 | action | ||
403 | } | ||
404 | } | ||
405 | |||
406 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | ||
407 | } | ||
408 | |||
360 | async addVideoAutoBlacklistModeratorsNotification (to: string[], videoBlacklist: MVideoBlacklistLightVideo) { | 409 | async addVideoAutoBlacklistModeratorsNotification (to: string[], videoBlacklist: MVideoBlacklistLightVideo) { |
361 | const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list' | 410 | const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list' |
362 | const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath() | 411 | const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath() |
diff --git a/server/lib/emails/abuse-new-message/html.pug b/server/lib/emails/abuse-new-message/html.pug new file mode 100644 index 000000000..a4180aba1 --- /dev/null +++ b/server/lib/emails/abuse-new-message/html.pug | |||
@@ -0,0 +1,11 @@ | |||
1 | extends ../common/greetings | ||
2 | include ../common/mixins.pug | ||
3 | |||
4 | block title | ||
5 | | New abuse message | ||
6 | |||
7 | block content | ||
8 | p | ||
9 | | A new message was created on #[a(href=WEBSERVER.URL) abuse ##{abuseId} on #{WEBSERVER.HOST}] | ||
10 | blockquote #{messageText} | ||
11 | br(style="display: none;") | ||
diff --git a/server/lib/emails/abuse-state-change/html.pug b/server/lib/emails/abuse-state-change/html.pug new file mode 100644 index 000000000..a94c8521d --- /dev/null +++ b/server/lib/emails/abuse-state-change/html.pug | |||
@@ -0,0 +1,9 @@ | |||
1 | extends ../common/greetings | ||
2 | include ../common/mixins.pug | ||
3 | |||
4 | block title | ||
5 | | Abuse state changed | ||
6 | |||
7 | block content | ||
8 | p | ||
9 | | #[a(href=abuseUrl) Your abuse ##{abuseId} on #{WEBSERVER.HOST}] has been #{isAccepted ? 'accepted' : 'rejected'} | ||
diff --git a/server/lib/notifier.ts b/server/lib/notifier.ts index 8f165d2fd..5c50fcf01 100644 --- a/server/lib/notifier.ts +++ b/server/lib/notifier.ts | |||
@@ -1,3 +1,4 @@ | |||
1 | import { AbuseMessageModel } from '@server/models/abuse/abuse-message' | ||
1 | import { getServerActor } from '@server/models/application/application' | 2 | import { getServerActor } from '@server/models/application/application' |
2 | import { ServerBlocklistModel } from '@server/models/server/server-blocklist' | 3 | import { ServerBlocklistModel } from '@server/models/server/server-blocklist' |
3 | import { | 4 | import { |
@@ -18,7 +19,7 @@ import { CONFIG } from '../initializers/config' | |||
18 | import { AccountBlocklistModel } from '../models/account/account-blocklist' | 19 | import { AccountBlocklistModel } from '../models/account/account-blocklist' |
19 | import { UserModel } from '../models/account/user' | 20 | import { UserModel } from '../models/account/user' |
20 | import { UserNotificationModel } from '../models/account/user-notification' | 21 | import { UserNotificationModel } from '../models/account/user-notification' |
21 | import { MAbuseFull, MAccountServer, MActorFollowFull } from '../types/models' | 22 | import { MAbuseFull, MAbuseMessage, MAccountServer, MActorFollowFull } from '../types/models' |
22 | import { MCommentOwnerVideo, MVideoAccountLight, MVideoFullLight } from '../types/models/video' | 23 | import { MCommentOwnerVideo, MVideoAccountLight, MVideoFullLight } from '../types/models/video' |
23 | import { isBlockedByServerOrAccount } from './blocklist' | 24 | import { isBlockedByServerOrAccount } from './blocklist' |
24 | import { Emailer } from './emailer' | 25 | import { Emailer } from './emailer' |
@@ -129,6 +130,20 @@ class Notifier { | |||
129 | }) | 130 | }) |
130 | } | 131 | } |
131 | 132 | ||
133 | notifyOnAbuseStateChange (abuse: MAbuseFull): void { | ||
134 | this.notifyReporterOfAbuseStateChange(abuse) | ||
135 | .catch(err => { | ||
136 | logger.error('Cannot notify reporter of abuse %d state change.', abuse.id, { err }) | ||
137 | }) | ||
138 | } | ||
139 | |||
140 | notifyOnAbuseMessage (abuse: MAbuseFull, message: AbuseMessageModel): void { | ||
141 | this.notifyOfNewAbuseMessage(abuse, message) | ||
142 | .catch(err => { | ||
143 | logger.error('Cannot notify on new abuse %d message.', abuse.id, { err }) | ||
144 | }) | ||
145 | } | ||
146 | |||
132 | private async notifySubscribersOfNewVideo (video: MVideoAccountLight) { | 147 | private async notifySubscribersOfNewVideo (video: MVideoAccountLight) { |
133 | // List all followers that are users | 148 | // List all followers that are users |
134 | const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId) | 149 | const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId) |
@@ -359,9 +374,7 @@ class Notifier { | |||
359 | const moderators = await UserModel.listWithRight(UserRight.MANAGE_ABUSES) | 374 | const moderators = await UserModel.listWithRight(UserRight.MANAGE_ABUSES) |
360 | if (moderators.length === 0) return | 375 | if (moderators.length === 0) return |
361 | 376 | ||
362 | const url = abuseInstance.VideoAbuse?.Video?.url || | 377 | const url = this.getAbuseUrl(abuseInstance) |
363 | abuseInstance.VideoCommentAbuse?.VideoComment?.url || | ||
364 | abuseInstance.FlaggedAccount.Actor.url | ||
365 | 378 | ||
366 | logger.info('Notifying %s user/moderators of new abuse %s.', moderators.length, url) | 379 | logger.info('Notifying %s user/moderators of new abuse %s.', moderators.length, url) |
367 | 380 | ||
@@ -387,6 +400,97 @@ class Notifier { | |||
387 | return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) | 400 | return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) |
388 | } | 401 | } |
389 | 402 | ||
403 | private async notifyReporterOfAbuseStateChange (abuse: MAbuseFull) { | ||
404 | // Only notify our users | ||
405 | if (abuse.ReporterAccount.isOwned() !== true) return | ||
406 | |||
407 | const url = this.getAbuseUrl(abuse) | ||
408 | |||
409 | logger.info('Notifying reporter of abuse % of state change.', url) | ||
410 | |||
411 | const reporter = await UserModel.loadByAccountActorId(abuse.ReporterAccount.actorId) | ||
412 | |||
413 | function settingGetter (user: MUserWithNotificationSetting) { | ||
414 | return user.NotificationSetting.abuseStateChange | ||
415 | } | ||
416 | |||
417 | async function notificationCreator (user: MUserWithNotificationSetting) { | ||
418 | const notification = await UserNotificationModel.create<UserNotificationModelForApi>({ | ||
419 | type: UserNotificationType.ABUSE_STATE_CHANGE, | ||
420 | userId: user.id, | ||
421 | abuseId: abuse.id | ||
422 | }) | ||
423 | notification.Abuse = abuse | ||
424 | |||
425 | return notification | ||
426 | } | ||
427 | |||
428 | function emailSender (emails: string[]) { | ||
429 | return Emailer.Instance.addAbuseStateChangeNotification(emails, abuse) | ||
430 | } | ||
431 | |||
432 | return this.notify({ users: [ reporter ], settingGetter, notificationCreator, emailSender }) | ||
433 | } | ||
434 | |||
435 | private async notifyOfNewAbuseMessage (abuse: MAbuseFull, message: MAbuseMessage) { | ||
436 | const url = this.getAbuseUrl(abuse) | ||
437 | logger.info('Notifying reporter and moderators of new abuse message on %s.', url) | ||
438 | |||
439 | function settingGetter (user: MUserWithNotificationSetting) { | ||
440 | return user.NotificationSetting.abuseNewMessage | ||
441 | } | ||
442 | |||
443 | async function notificationCreator (user: MUserWithNotificationSetting) { | ||
444 | const notification = await UserNotificationModel.create<UserNotificationModelForApi>({ | ||
445 | type: UserNotificationType.ABUSE_NEW_MESSAGE, | ||
446 | userId: user.id, | ||
447 | abuseId: abuse.id | ||
448 | }) | ||
449 | notification.Abuse = abuse | ||
450 | |||
451 | return notification | ||
452 | } | ||
453 | |||
454 | function emailSenderReporter (emails: string[]) { | ||
455 | return Emailer.Instance.addAbuseNewMessageNotification(emails, { target: 'reporter', abuse, message }) | ||
456 | } | ||
457 | |||
458 | function emailSenderModerators (emails: string[]) { | ||
459 | return Emailer.Instance.addAbuseNewMessageNotification(emails, { target: 'moderator', abuse, message }) | ||
460 | } | ||
461 | |||
462 | async function buildReporterOptions () { | ||
463 | // Only notify our users | ||
464 | if (abuse.ReporterAccount.isOwned() !== true) return | ||
465 | |||
466 | const reporter = await UserModel.loadByAccountActorId(abuse.ReporterAccount.actorId) | ||
467 | // Don't notify my own message | ||
468 | if (reporter.Account.id === message.accountId) return | ||
469 | |||
470 | return { users: [ reporter ], settingGetter, notificationCreator, emailSender: emailSenderReporter } | ||
471 | } | ||
472 | |||
473 | async function buildModeratorsOptions () { | ||
474 | let moderators = await UserModel.listWithRight(UserRight.MANAGE_ABUSES) | ||
475 | // Don't notify my own message | ||
476 | moderators = moderators.filter(m => m.Account.id !== message.accountId) | ||
477 | |||
478 | if (moderators.length === 0) return | ||
479 | |||
480 | return { users: moderators, settingGetter, notificationCreator, emailSender: emailSenderModerators } | ||
481 | } | ||
482 | |||
483 | const [ reporterOptions, moderatorsOptions ] = await Promise.all([ | ||
484 | buildReporterOptions(), | ||
485 | buildModeratorsOptions() | ||
486 | ]) | ||
487 | |||
488 | return Promise.all([ | ||
489 | this.notify(reporterOptions), | ||
490 | this.notify(moderatorsOptions) | ||
491 | ]) | ||
492 | } | ||
493 | |||
390 | private async notifyModeratorsOfVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo) { | 494 | private async notifyModeratorsOfVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo) { |
391 | const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_BLACKLIST) | 495 | const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_BLACKLIST) |
392 | if (moderators.length === 0) return | 496 | if (moderators.length === 0) return |
@@ -599,6 +703,12 @@ class Notifier { | |||
599 | return isBlockedByServerOrAccount(targetAccount, user?.Account) | 703 | return isBlockedByServerOrAccount(targetAccount, user?.Account) |
600 | } | 704 | } |
601 | 705 | ||
706 | private getAbuseUrl (abuse: MAbuseFull) { | ||
707 | return abuse.VideoAbuse?.Video?.url || | ||
708 | abuse.VideoCommentAbuse?.VideoComment?.url || | ||
709 | abuse.FlaggedAccount.Actor.url | ||
710 | } | ||
711 | |||
602 | static get Instance () { | 712 | static get Instance () { |
603 | return this.instance || (this.instance = new this()) | 713 | return this.instance || (this.instance = new this()) |
604 | } | 714 | } |
diff --git a/server/lib/user.ts b/server/lib/user.ts index 6e7a738ee..aa14f0b54 100644 --- a/server/lib/user.ts +++ b/server/lib/user.ts | |||
@@ -141,6 +141,8 @@ function createDefaultUserNotificationSettings (user: MUserId, t: Transaction | | |||
141 | commentMention: UserNotificationSettingValue.WEB, | 141 | commentMention: UserNotificationSettingValue.WEB, |
142 | newFollow: UserNotificationSettingValue.WEB, | 142 | newFollow: UserNotificationSettingValue.WEB, |
143 | newInstanceFollower: UserNotificationSettingValue.WEB, | 143 | newInstanceFollower: UserNotificationSettingValue.WEB, |
144 | abuseNewMessage: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, | ||
145 | abuseStateChange: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, | ||
144 | autoInstanceFollowing: UserNotificationSettingValue.WEB | 146 | autoInstanceFollowing: UserNotificationSettingValue.WEB |
145 | } | 147 | } |
146 | 148 | ||