mirror of
https://github.com/Chocobozzz/PeerTube.git
synced 2024-11-22 08:46:54 -06:00
Add notification on subscription live stream
This commit is contained in:
parent
4300cc1ee1
commit
a012d6c2a9
@ -30,7 +30,7 @@ export class MyAccountNotificationPreferencesComponent implements OnInit {
|
||||
private notifier: Notifier
|
||||
) {
|
||||
this.labelNotifications = {
|
||||
newVideoFromSubscription: $localize`New video from your subscriptions`,
|
||||
newVideoFromSubscription: $localize`New video or live from your subscriptions`,
|
||||
newCommentOnMyVideo: $localize`New comment on your video`,
|
||||
abuseAsModerator: $localize`New abuse`,
|
||||
videoAutoBlacklistAsModerator: $localize`An automatically blocked video is awaiting review`,
|
||||
|
@ -150,6 +150,7 @@ export class UserNotification implements UserNotificationServer {
|
||||
|
||||
switch (this.type) {
|
||||
case UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION:
|
||||
case UserNotificationType.NEW_LIVE_FROM_SUBSCRIPTION:
|
||||
this.videoUrl = this.buildVideoUrl(this.video)
|
||||
break
|
||||
|
||||
|
@ -6,9 +6,7 @@
|
||||
|
||||
<ng-container [ngSwitch]="notification.type">
|
||||
<ng-container *ngSwitchCase="1"> <!-- UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION -->
|
||||
<ng-container *ngIf="notification.video; then hasVideo; else noVideo"></ng-container>
|
||||
|
||||
<ng-template #hasVideo>
|
||||
@if (notification.video) {
|
||||
<a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">
|
||||
<img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.video.channel.avatarUrl" />
|
||||
</a>
|
||||
@ -16,15 +14,13 @@
|
||||
<div class="message" i18n>
|
||||
{{ notification.video.channel.displayName }} published a new video: <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.video.name }}</a>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #noVideo>
|
||||
} @else {
|
||||
<my-global-icon iconName="alert" aria-hidden="true"></my-global-icon>
|
||||
|
||||
<div class="message" i18n>
|
||||
The notification concerns a video now unavailable
|
||||
</div>
|
||||
</ng-template>
|
||||
}
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchCase="5"> <!-- UserNotificationType.UNBLACKLIST_ON_MY_VIDEO -->
|
||||
@ -224,6 +220,24 @@
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchCase="21"> <!-- UserNotificationType.NEW_LIVE_FROM_SUBSCRIPTION -->
|
||||
@if (notification.video) {
|
||||
<a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">
|
||||
<img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.video.channel.avatarUrl" />
|
||||
</a>
|
||||
|
||||
<div class="message" i18n>
|
||||
{{ notification.video.channel.displayName }} is live streaming in <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.video.name }}</a>
|
||||
</div>
|
||||
} @else {
|
||||
<my-global-icon iconName="alert" aria-hidden="true"></my-global-icon>
|
||||
|
||||
<div class="message" i18n>
|
||||
The notification concerns a video now unavailable
|
||||
</div>
|
||||
}
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchDefault>
|
||||
<my-global-icon iconName="alert" aria-hidden="true"></my-global-icon>
|
||||
|
||||
|
@ -34,7 +34,9 @@ export const UserNotificationType = {
|
||||
|
||||
MY_VIDEO_STUDIO_EDITION_FINISHED: 19,
|
||||
|
||||
NEW_USER_REGISTRATION_REQUEST: 20
|
||||
NEW_USER_REGISTRATION_REQUEST: 20,
|
||||
|
||||
NEW_LIVE_FROM_SUBSCRIPTION: 21
|
||||
} as const
|
||||
|
||||
export type UserNotificationType_Type = typeof UserNotificationType[keyof typeof UserNotificationType]
|
||||
|
@ -119,12 +119,13 @@ export class LiveCommand extends AbstractCommand {
|
||||
}
|
||||
|
||||
async quickCreate (options: OverrideCommandOptions & {
|
||||
name: string
|
||||
saveReplay: boolean
|
||||
permanentLive: boolean
|
||||
privacy?: VideoPrivacyType
|
||||
videoPasswords?: string[]
|
||||
}) {
|
||||
const { saveReplay, permanentLive, privacy = VideoPrivacy.PUBLIC, videoPasswords } = options
|
||||
const { name = 'live', saveReplay, permanentLive, privacy = VideoPrivacy.PUBLIC, videoPasswords } = options
|
||||
|
||||
const replaySettings = privacy === VideoPrivacy.PASSWORD_PROTECTED
|
||||
? { privacy: VideoPrivacy.PRIVATE }
|
||||
@ -134,7 +135,7 @@ export class LiveCommand extends AbstractCommand {
|
||||
...options,
|
||||
|
||||
fields: {
|
||||
name: 'live',
|
||||
name,
|
||||
permanentLive,
|
||||
saveReplay,
|
||||
replaySettings,
|
||||
|
@ -18,7 +18,7 @@ import {
|
||||
checkNewInstanceFollower,
|
||||
checkAutoInstanceFollowing,
|
||||
checkVideoAutoBlacklistForModerators,
|
||||
checkVideoIsPublished,
|
||||
checkMyVideoIsPublished,
|
||||
checkNewVideoFromSubscription
|
||||
} from '@tests/shared/notifications.js'
|
||||
|
||||
@ -487,7 +487,7 @@ describe('Test moderation notifications', function () {
|
||||
it('Should not send video publish notification if auto-blacklisted', async function () {
|
||||
this.timeout(120000)
|
||||
|
||||
await checkVideoIsPublished({ ...userBaseParams, videoName, shortUUID, checkType: 'absence' })
|
||||
await checkMyVideoIsPublished({ ...userBaseParams, videoName, shortUUID, checkType: 'absence' })
|
||||
})
|
||||
|
||||
it('Should not send a local user subscription notification if auto-blacklisted', async function () {
|
||||
@ -576,7 +576,7 @@ describe('Test moderation notifications', function () {
|
||||
const { shortUUID } = await servers[0].videos.upload({ token: userToken1, attributes })
|
||||
|
||||
await wait(6000)
|
||||
await checkVideoIsPublished({ ...userBaseParams, videoName: name, shortUUID, checkType: 'absence' })
|
||||
await checkMyVideoIsPublished({ ...userBaseParams, videoName: name, shortUUID, checkType: 'absence' })
|
||||
await checkNewVideoFromSubscription({ ...adminBaseParamsServer1, videoName: name, shortUUID, checkType: 'absence' })
|
||||
await checkNewVideoFromSubscription({ ...adminBaseParamsServer2, videoName: name, shortUUID, checkType: 'absence' })
|
||||
})
|
||||
|
@ -10,10 +10,12 @@ import {
|
||||
prepareNotificationsTest,
|
||||
CheckerBaseParams,
|
||||
checkNewVideoFromSubscription,
|
||||
checkVideoIsPublished,
|
||||
checkMyVideoIsPublished,
|
||||
checkVideoStudioEditionIsFinished,
|
||||
checkMyVideoImportIsFinished,
|
||||
checkNewActorFollow
|
||||
checkNewActorFollow,
|
||||
checkNewLiveFromSubscription,
|
||||
waitUntilNotification
|
||||
} from '@tests/shared/notifications.js'
|
||||
import { FIXTURE_URLS } from '@tests/shared/tests.js'
|
||||
import { uploadRandomVideoOnServers } from '@tests/shared/videos.js'
|
||||
@ -209,6 +211,82 @@ describe('Test user notifications', function () {
|
||||
|
||||
await checkNewVideoFromSubscription({ ...baseParams, videoName: name, shortUUID: video.shortUUID, checkType: 'presence' })
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('New live from my subscription notification', function () {
|
||||
let baseParams: CheckerBaseParams
|
||||
|
||||
async function createAndStreamLive (server: PeerTubeServer) {
|
||||
const name = 'video live ' + buildUUID()
|
||||
|
||||
const streamDate = new Date()
|
||||
const { video } = await server.live.quickCreate({ name, permanentLive: true, saveReplay: false })
|
||||
await waitJobs(servers)
|
||||
|
||||
const ffmpegCommand = await server.live.sendRTMPStreamInVideo({ videoId: video.uuid })
|
||||
|
||||
return { name, video, ffmpegCommand, streamDate }
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
baseParams = {
|
||||
server: servers[0],
|
||||
emails,
|
||||
socketNotifications: userNotifications,
|
||||
token: userAccessToken
|
||||
}
|
||||
|
||||
await servers[0].config.enableLive({ allowReplay: false })
|
||||
|
||||
await servers[0].subscriptions.add({ token: userAccessToken, targetUri: 'root_channel@' + servers[0].host })
|
||||
await waitJobs(servers)
|
||||
})
|
||||
|
||||
it('Should not send a notification when a live is created', async function () {
|
||||
this.timeout(100000)
|
||||
|
||||
const name = 'video live ' + buildUUID()
|
||||
|
||||
const { video } = await servers[0].live.quickCreate({ name, permanentLive: true, saveReplay: false })
|
||||
await waitJobs(servers)
|
||||
await checkNewLiveFromSubscription({ ...baseParams, videoName: name, shortUUID: video.shortUUID, checkType: 'absence' })
|
||||
})
|
||||
|
||||
it('Should send a local notification when streaming in the live', async function () {
|
||||
this.timeout(100000)
|
||||
|
||||
const { name, video, ffmpegCommand, streamDate } = await createAndStreamLive(servers[0])
|
||||
|
||||
await waitUntilNotification({
|
||||
server: servers[0],
|
||||
token: userAccessToken,
|
||||
notificationType: UserNotificationType.NEW_LIVE_FROM_SUBSCRIPTION,
|
||||
fromDate: streamDate
|
||||
})
|
||||
|
||||
await checkNewLiveFromSubscription({ ...baseParams, videoName: name, shortUUID: video.shortUUID, checkType: 'presence' })
|
||||
|
||||
await stopFfmpeg(ffmpegCommand)
|
||||
await waitJobs(servers)
|
||||
})
|
||||
|
||||
it('Should send a remote notification when streaming in the live ', async function () {
|
||||
this.timeout(100000)
|
||||
|
||||
const { name, video, ffmpegCommand, streamDate } = await createAndStreamLive(servers[1])
|
||||
|
||||
await waitUntilNotification({
|
||||
server: servers[0],
|
||||
token: userAccessToken,
|
||||
notificationType: UserNotificationType.NEW_LIVE_FROM_SUBSCRIPTION,
|
||||
fromDate: streamDate
|
||||
})
|
||||
await checkNewLiveFromSubscription({ ...baseParams, videoName: name, shortUUID: video.shortUUID, checkType: 'presence' })
|
||||
|
||||
await stopFfmpeg(ffmpegCommand)
|
||||
await waitJobs(servers)
|
||||
})
|
||||
})
|
||||
|
||||
describe('My video is published', function () {
|
||||
@ -229,7 +307,7 @@ describe('Test user notifications', function () {
|
||||
const { name, shortUUID } = await uploadRandomVideoOnServers(servers, 1)
|
||||
await waitJobs(servers)
|
||||
|
||||
await checkVideoIsPublished({ ...baseParams, videoName: name, shortUUID, checkType: 'absence' })
|
||||
await checkMyVideoIsPublished({ ...baseParams, videoName: name, shortUUID, checkType: 'absence' })
|
||||
})
|
||||
|
||||
it('Should not send a notification if the wait transcoding is false', async function () {
|
||||
@ -250,7 +328,7 @@ describe('Test user notifications', function () {
|
||||
const { name, shortUUID } = await uploadRandomVideoOnServers(servers, 2, { waitTranscoding: true, fixture: 'video_short_240p.mp4' })
|
||||
await waitJobs(servers)
|
||||
|
||||
await checkVideoIsPublished({ ...baseParams, videoName: name, shortUUID, checkType: 'presence' })
|
||||
await checkMyVideoIsPublished({ ...baseParams, videoName: name, shortUUID, checkType: 'presence' })
|
||||
})
|
||||
|
||||
it('Should send a notification with a transcoded video', async function () {
|
||||
@ -259,7 +337,7 @@ describe('Test user notifications', function () {
|
||||
const { name, shortUUID } = await uploadRandomVideoOnServers(servers, 2, { waitTranscoding: true })
|
||||
await waitJobs(servers)
|
||||
|
||||
await checkVideoIsPublished({ ...baseParams, videoName: name, shortUUID, checkType: 'presence' })
|
||||
await checkMyVideoIsPublished({ ...baseParams, videoName: name, shortUUID, checkType: 'presence' })
|
||||
})
|
||||
|
||||
it('Should send a notification when an imported video is transcoded', async function () {
|
||||
@ -277,7 +355,7 @@ describe('Test user notifications', function () {
|
||||
const { video } = await servers[1].imports.importVideo({ attributes })
|
||||
|
||||
await waitJobs(servers)
|
||||
await checkVideoIsPublished({ ...baseParams, videoName: name, shortUUID: video.shortUUID, checkType: 'presence' })
|
||||
await checkMyVideoIsPublished({ ...baseParams, videoName: name, shortUUID: video.shortUUID, checkType: 'presence' })
|
||||
})
|
||||
|
||||
it('Should send a notification when the scheduled update has been proceeded', async function () {
|
||||
@ -296,7 +374,7 @@ describe('Test user notifications', function () {
|
||||
const { name, shortUUID } = await uploadRandomVideoOnServers(servers, 2, data)
|
||||
|
||||
await wait(6000)
|
||||
await checkVideoIsPublished({ ...baseParams, videoName: name, shortUUID, checkType: 'presence' })
|
||||
await checkMyVideoIsPublished({ ...baseParams, videoName: name, shortUUID, checkType: 'presence' })
|
||||
})
|
||||
|
||||
it('Should not send a notification before the video is published', async function () {
|
||||
@ -314,7 +392,7 @@ describe('Test user notifications', function () {
|
||||
const { name, shortUUID } = await uploadRandomVideoOnServers(servers, 2, data)
|
||||
|
||||
await wait(6000)
|
||||
await checkVideoIsPublished({ ...baseParams, videoName: name, shortUUID, checkType: 'absence' })
|
||||
await checkMyVideoIsPublished({ ...baseParams, videoName: name, shortUUID, checkType: 'absence' })
|
||||
})
|
||||
})
|
||||
|
||||
@ -354,7 +432,7 @@ describe('Test user notifications', function () {
|
||||
await servers[1].live.waitUntilReplacedByReplay({ videoId: shortUUID })
|
||||
|
||||
await waitJobs(servers)
|
||||
await checkVideoIsPublished({ ...baseParams, videoName: 'non permanent live', shortUUID, checkType: 'presence' })
|
||||
await checkMyVideoIsPublished({ ...baseParams, videoName: 'non permanent live', shortUUID, checkType: 'presence' })
|
||||
})
|
||||
|
||||
it('Should send a notification is a live replay of a permanent live is published', async function () {
|
||||
@ -386,7 +464,7 @@ describe('Test user notifications', function () {
|
||||
const video = await findExternalSavedVideo(servers[1], liveDetails)
|
||||
expect(video).to.exist
|
||||
|
||||
await checkVideoIsPublished({ ...baseParams, videoName: video.name, shortUUID: video.shortUUID, checkType: 'presence' })
|
||||
await checkMyVideoIsPublished({ ...baseParams, videoName: video.name, shortUUID: video.shortUUID, checkType: 'presence' })
|
||||
})
|
||||
})
|
||||
|
||||
@ -408,7 +486,7 @@ describe('Test user notifications', function () {
|
||||
const { name, shortUUID, id } = await uploadRandomVideoOnServers(servers, 2, { waitTranscoding: true })
|
||||
|
||||
await waitJobs(servers)
|
||||
await checkVideoIsPublished({ ...baseParams, videoName: name, shortUUID, checkType: 'presence' })
|
||||
await checkMyVideoIsPublished({ ...baseParams, videoName: name, shortUUID, checkType: 'presence' })
|
||||
|
||||
const tasks: VideoStudioTask[] = [
|
||||
{
|
||||
|
@ -7,7 +7,8 @@ import {
|
||||
UserNotification,
|
||||
UserNotificationSetting,
|
||||
UserNotificationSettingValue,
|
||||
UserNotificationType
|
||||
UserNotificationType,
|
||||
UserNotificationType_Type
|
||||
} from '@peertube/peertube-models'
|
||||
import {
|
||||
ConfigCommand,
|
||||
@ -17,11 +18,13 @@ import {
|
||||
setAccessTokensToServers,
|
||||
setDefaultAccountAvatar,
|
||||
setDefaultChannelAvatar,
|
||||
setDefaultVideoChannel
|
||||
setDefaultVideoChannel,
|
||||
waitJobs
|
||||
} from '@peertube/peertube-server-commands'
|
||||
import { expect } from 'chai'
|
||||
import { inspect } from 'util'
|
||||
import { MockSmtpServer } from './mock-servers/index.js'
|
||||
import { wait } from '@peertube/peertube-core-utils'
|
||||
|
||||
type CheckerBaseParams = {
|
||||
server: PeerTubeServer
|
||||
@ -55,6 +58,24 @@ function getAllNotificationsSettings (): UserNotificationSetting {
|
||||
}
|
||||
}
|
||||
|
||||
async function waitUntilNotification (options: {
|
||||
server: PeerTubeServer
|
||||
notificationType: UserNotificationType_Type
|
||||
token: string
|
||||
fromDate: Date
|
||||
}) {
|
||||
const { server, fromDate, notificationType, token } = options
|
||||
|
||||
do {
|
||||
const { data } = await server.notifications.list({ start: 0, count: 5, token })
|
||||
if (data.some(n => n.type === notificationType && new Date(n.createdAt) >= fromDate)) break
|
||||
|
||||
await wait(500)
|
||||
} while (true)
|
||||
|
||||
await waitJobs([ server ])
|
||||
}
|
||||
|
||||
async function checkNewVideoFromSubscription (options: CheckerBaseParams & {
|
||||
videoName: string
|
||||
shortUUID: string
|
||||
@ -85,7 +106,37 @@ async function checkNewVideoFromSubscription (options: CheckerBaseParams & {
|
||||
await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
|
||||
}
|
||||
|
||||
async function checkVideoIsPublished (options: CheckerBaseParams & {
|
||||
async function checkNewLiveFromSubscription (options: CheckerBaseParams & {
|
||||
videoName: string
|
||||
shortUUID: string
|
||||
checkType: CheckerType
|
||||
}) {
|
||||
const { videoName, shortUUID } = options
|
||||
const notificationType = UserNotificationType.NEW_LIVE_FROM_SUBSCRIPTION
|
||||
|
||||
function notificationChecker (notification: UserNotification, checkType: CheckerType) {
|
||||
if (checkType === 'presence') {
|
||||
expect(notification).to.not.be.undefined
|
||||
expect(notification.type).to.equal(notificationType)
|
||||
|
||||
checkVideo(notification.video, videoName, shortUUID)
|
||||
checkActor(notification.video.channel)
|
||||
} else {
|
||||
expect(notification).to.satisfy((n: UserNotification) => {
|
||||
return n === undefined || n.type !== UserNotificationType.NEW_LIVE_FROM_SUBSCRIPTION || n.video.name !== videoName
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function emailNotificationFinder (email: object) {
|
||||
const text = email['text']
|
||||
return text.indexOf(shortUUID) !== -1 && text.indexOf('Your subscription') !== -1
|
||||
}
|
||||
|
||||
await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
|
||||
}
|
||||
|
||||
async function checkMyVideoIsPublished (options: CheckerBaseParams & {
|
||||
videoName: string
|
||||
shortUUID: string
|
||||
checkType: CheckerType
|
||||
@ -780,10 +831,13 @@ export {
|
||||
|
||||
getAllNotificationsSettings,
|
||||
|
||||
waitUntilNotification,
|
||||
|
||||
checkMyVideoImportIsFinished,
|
||||
checkUserRegistered,
|
||||
checkAutoInstanceFollowing,
|
||||
checkVideoIsPublished,
|
||||
checkMyVideoIsPublished,
|
||||
checkNewLiveFromSubscription,
|
||||
checkNewVideoFromSubscription,
|
||||
checkNewActorFollow,
|
||||
checkNewCommentOnMyVideo,
|
||||
@ -841,10 +895,9 @@ async function checkNotification (options: CheckerBaseParams & {
|
||||
|
||||
if (check.mail) {
|
||||
// Last email
|
||||
const email = emails
|
||||
.slice()
|
||||
.reverse()
|
||||
.find(e => emailNotificationFinder(e))
|
||||
const email = emails.slice()
|
||||
.reverse()
|
||||
.find(e => emailNotificationFinder(e))
|
||||
|
||||
if (checkType === 'presence') {
|
||||
const texts = emails.map(e => e.text)
|
||||
|
@ -61,5 +61,5 @@ async function processVideoShare (actorAnnouncer: MActorSignature, activity: Act
|
||||
return undefined
|
||||
})
|
||||
|
||||
if (videoCreated && notify) Notifier.Instance.notifyOnNewVideoIfNeeded(video)
|
||||
if (videoCreated && notify) Notifier.Instance.notifyOnNewVideoOrLiveIfNeeded(video)
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ async function processCreateVideo (videoToCreateData: VideoObject, notify: boole
|
||||
const syncParam = { rates: false, shares: false, comments: false, refreshVideo: false }
|
||||
const { video, created } = await getOrCreateAPVideo({ videoObject: videoToCreateData, syncParam })
|
||||
|
||||
if (created && notify) Notifier.Instance.notifyOnNewVideoIfNeeded(video)
|
||||
if (created && notify) Notifier.Instance.notifyOnNewVideoOrLiveIfNeeded(video)
|
||||
|
||||
return video
|
||||
}
|
||||
|
@ -93,11 +93,12 @@ export class APVideoUpdater extends APVideoAbstractBuilder {
|
||||
|
||||
// Notify our users?
|
||||
if (this.wasPrivateVideo || this.wasUnlistedVideo) {
|
||||
Notifier.Instance.notifyOnNewVideoIfNeeded(videoUpdated)
|
||||
Notifier.Instance.notifyOnNewVideoOrLiveIfNeeded(videoUpdated)
|
||||
}
|
||||
|
||||
if (videoUpdated.isLive && oldState !== videoUpdated.state) {
|
||||
PeerTubeSocket.Instance.sendVideoLiveNewState(videoUpdated)
|
||||
Notifier.Instance.notifyOnNewVideoOrLiveIfNeeded(videoUpdated)
|
||||
}
|
||||
|
||||
Hooks.runAction('action:activity-pub.remote-video.updated', { video: videoUpdated, videoAPObject: this.videoObject })
|
||||
|
@ -23,5 +23,5 @@ async function doNotifyNewVideo (payload: NotifyPayload & { action: 'new-video'
|
||||
const refreshedVideo = await VideoModel.loadFull(payload.videoUUID)
|
||||
if (!refreshedVideo) return
|
||||
|
||||
Notifier.Instance.notifyOnNewVideoIfNeeded(refreshedVideo)
|
||||
Notifier.Instance.notifyOnNewVideoOrLiveIfNeeded(refreshedVideo)
|
||||
}
|
||||
|
@ -303,7 +303,7 @@ async function afterImportSuccess (options: {
|
||||
|
||||
Notifier.Instance.notifyOnVideoAutoBlacklist(videoBlacklist)
|
||||
} else {
|
||||
Notifier.Instance.notifyOnNewVideoIfNeeded(video)
|
||||
Notifier.Instance.notifyOnNewVideoOrLiveIfNeeded(video)
|
||||
}
|
||||
|
||||
// Generate the storyboard in the job queue, and don't forget to federate an update after
|
||||
|
@ -35,6 +35,7 @@ import { computeResolutionsToTranscode } from '../transcoding/transcoding-resolu
|
||||
import { LiveQuotaStore } from './live-quota-store.js'
|
||||
import { cleanupAndDestroyPermanentLive, getLiveSegmentTime } from './live-utils.js'
|
||||
import { MuxingSession } from './shared/index.js'
|
||||
import { Notifier } from '../notifier/notifier.js'
|
||||
|
||||
// Disable node media server logs
|
||||
nodeMediaServerLogger.setLogType(0)
|
||||
@ -417,6 +418,7 @@ class LiveManager {
|
||||
logger.error('Cannot federate live video %s.', video.url, { err, ...localLTags })
|
||||
}
|
||||
|
||||
Notifier.Instance.notifyOnNewVideoOrLiveIfNeeded(video)
|
||||
PeerTubeSocket.Instance.sendVideoLiveNewState(video)
|
||||
|
||||
Hooks.runAction('action:live.video.state.updated', { video })
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { UserNotificationSettingValue, UserNotificationSettingValueType } from '@peertube/peertube-models'
|
||||
import { MRegistration, MUser, MUserDefault } from '@server/types/models/user/index.js'
|
||||
import { MVideoBlacklistLightVideo, MVideoBlacklistVideo } from '@server/types/models/video/video-blacklist.js'
|
||||
import { logger } from '../../helpers/logger.js'
|
||||
import { logger, loggerTagsFactory } from '../../helpers/logger.js'
|
||||
import { CONFIG } from '../../initializers/config.js'
|
||||
import {
|
||||
MAbuseFull,
|
||||
@ -35,7 +35,7 @@ import {
|
||||
NewCommentForVideoOwner,
|
||||
NewPeerTubeVersionForAdmins,
|
||||
NewPluginVersionForAdmins,
|
||||
NewVideoForSubscribers,
|
||||
NewVideoOrLiveForSubscribers,
|
||||
OwnedPublicationAfterAutoUnblacklist,
|
||||
OwnedPublicationAfterScheduleUpdate,
|
||||
OwnedPublicationAfterTranscoding,
|
||||
@ -44,10 +44,12 @@ import {
|
||||
UnblacklistForOwner
|
||||
} from './shared/index.js'
|
||||
|
||||
const lTags = loggerTagsFactory('notifier')
|
||||
|
||||
class Notifier {
|
||||
|
||||
private readonly notificationModels = {
|
||||
newVideo: [ NewVideoForSubscribers ],
|
||||
newVideoOrLive: [ NewVideoOrLiveForSubscribers ],
|
||||
publicationAfterTranscoding: [ OwnedPublicationAfterTranscoding ],
|
||||
publicationAfterScheduleUpdate: [ OwnedPublicationAfterScheduleUpdate ],
|
||||
publicationAfterAutoUnblacklist: [ OwnedPublicationAfterAutoUnblacklist ],
|
||||
@ -74,8 +76,10 @@ class Notifier {
|
||||
private constructor () {
|
||||
}
|
||||
|
||||
notifyOnNewVideoIfNeeded (video: MVideoAccountLight): void {
|
||||
const models = this.notificationModels.newVideo
|
||||
notifyOnNewVideoOrLiveIfNeeded (video: MVideoAccountLight): void {
|
||||
const models = this.notificationModels.newVideoOrLive
|
||||
|
||||
logger.debug('Notify on new video or live if needed', { video: video.url, ...lTags() })
|
||||
|
||||
this.sendNotifications(models, video)
|
||||
.catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err }))
|
||||
@ -84,6 +88,8 @@ class Notifier {
|
||||
notifyOnVideoPublishedAfterTranscoding (video: MVideoFullLight): void {
|
||||
const models = this.notificationModels.publicationAfterTranscoding
|
||||
|
||||
logger.debug('Notify on published video after transcoding', { video: video.url, ...lTags() })
|
||||
|
||||
this.sendNotifications(models, video)
|
||||
.catch(err => logger.error('Cannot notify owner that its video %s has been published after transcoding.', video.url, { err }))
|
||||
}
|
||||
@ -91,6 +97,8 @@ class Notifier {
|
||||
notifyOnVideoPublishedAfterScheduledUpdate (video: MVideoFullLight): void {
|
||||
const models = this.notificationModels.publicationAfterScheduleUpdate
|
||||
|
||||
logger.debug('Notify on published video after scheduled update', { video: video.url, ...lTags() })
|
||||
|
||||
this.sendNotifications(models, video)
|
||||
.catch(err => logger.error('Cannot notify owner that its video %s has been published after scheduled update.', video.url, { err }))
|
||||
}
|
||||
@ -98,6 +106,8 @@ class Notifier {
|
||||
notifyOnVideoPublishedAfterRemovedFromAutoBlacklist (video: MVideoFullLight): void {
|
||||
const models = this.notificationModels.publicationAfterAutoUnblacklist
|
||||
|
||||
logger.debug('Notify on published video after being removed from auto blacklist', { video: video.url, ...lTags() })
|
||||
|
||||
this.sendNotifications(models, video)
|
||||
.catch(err => {
|
||||
logger.error('Cannot notify owner that its video %s has been published after removed from auto-blacklist.', video.url, { err })
|
||||
@ -107,6 +117,8 @@ class Notifier {
|
||||
notifyOnNewComment (comment: MCommentOwnerVideo): void {
|
||||
const models = this.notificationModels.newComment
|
||||
|
||||
logger.debug('Notify on new comment', { comment: comment.url, ...lTags() })
|
||||
|
||||
this.sendNotifications(models, comment)
|
||||
.catch(err => logger.error('Cannot notify of new comment %s.', comment.url, { err }))
|
||||
}
|
||||
@ -114,6 +126,8 @@ class Notifier {
|
||||
notifyOnNewAbuse (payload: NewAbusePayload): void {
|
||||
const models = this.notificationModels.newAbuse
|
||||
|
||||
logger.debug('Notify on new abuse', { abuse: payload.abuseInstance.id, ...lTags() })
|
||||
|
||||
this.sendNotifications(models, payload)
|
||||
.catch(err => logger.error('Cannot notify of new abuse %d.', payload.abuseInstance.id, { err }))
|
||||
}
|
||||
@ -121,6 +135,8 @@ class Notifier {
|
||||
notifyOnVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo): void {
|
||||
const models = this.notificationModels.newAutoBlacklist
|
||||
|
||||
logger.debug('Notify on video auto blacklist', { video: videoBlacklist?.Video?.url, ...lTags() })
|
||||
|
||||
this.sendNotifications(models, videoBlacklist)
|
||||
.catch(err => logger.error('Cannot notify of auto-blacklist of video %s.', videoBlacklist.Video.url, { err }))
|
||||
}
|
||||
@ -128,6 +144,8 @@ class Notifier {
|
||||
notifyOnVideoBlacklist (videoBlacklist: MVideoBlacklistVideo): void {
|
||||
const models = this.notificationModels.newBlacklist
|
||||
|
||||
logger.debug('Notify on video manual blacklist', { video: videoBlacklist?.Video?.url, ...lTags() })
|
||||
|
||||
this.sendNotifications(models, videoBlacklist)
|
||||
.catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err }))
|
||||
}
|
||||
@ -135,6 +153,8 @@ class Notifier {
|
||||
notifyOnVideoUnblacklist (video: MVideoFullLight): void {
|
||||
const models = this.notificationModels.unblacklist
|
||||
|
||||
logger.debug('Notify on video unblacklist', { video: video.url, ...lTags() })
|
||||
|
||||
this.sendNotifications(models, video)
|
||||
.catch(err => logger.error('Cannot notify video owner of unblacklist of %s.', video.url, { err }))
|
||||
}
|
||||
@ -142,6 +162,8 @@ class Notifier {
|
||||
notifyOnFinishedVideoImport (payload: ImportFinishedForOwnerPayload): void {
|
||||
const models = this.notificationModels.importFinished
|
||||
|
||||
logger.debug('Notify on finished video import', { import: payload.videoImport.getTargetIdentifier(), ...lTags() })
|
||||
|
||||
this.sendNotifications(models, payload)
|
||||
.catch(err => {
|
||||
logger.error('Cannot notify owner that its video import %s is finished.', payload.videoImport.getTargetIdentifier(), { err })
|
||||
@ -151,6 +173,8 @@ class Notifier {
|
||||
notifyOnNewDirectRegistration (user: MUserDefault): void {
|
||||
const models = this.notificationModels.directRegistration
|
||||
|
||||
logger.debug('Notify on new direct registration', { user: user.username, ...lTags() })
|
||||
|
||||
this.sendNotifications(models, user)
|
||||
.catch(err => logger.error('Cannot notify moderators of new user registration (%s).', user.username, { err }))
|
||||
}
|
||||
@ -158,6 +182,8 @@ class Notifier {
|
||||
notifyOnNewRegistrationRequest (registration: MRegistration): void {
|
||||
const models = this.notificationModels.registrationRequest
|
||||
|
||||
logger.debug('Notify on new registration request', { registration: registration.username, ...lTags() })
|
||||
|
||||
this.sendNotifications(models, registration)
|
||||
.catch(err => logger.error('Cannot notify moderators of new registration request (%s).', registration.username, { err }))
|
||||
}
|
||||
@ -165,20 +191,22 @@ class Notifier {
|
||||
notifyOfNewUserFollow (actorFollow: MActorFollowFull): void {
|
||||
const models = this.notificationModels.userFollow
|
||||
|
||||
const following = actorFollow?.ActorFollowing?.VideoChannel?.getDisplayName()
|
||||
const follower = actorFollow?.ActorFollower?.Account?.getDisplayName()
|
||||
|
||||
logger.debug('Notify on new user follow', { following, follower, ...lTags() })
|
||||
|
||||
this.sendNotifications(models, actorFollow)
|
||||
.catch(err => {
|
||||
logger.error(
|
||||
'Cannot notify owner of channel %s of a new follow by %s.',
|
||||
actorFollow.ActorFollowing.VideoChannel.getDisplayName(),
|
||||
actorFollow.ActorFollower.Account.getDisplayName(),
|
||||
{ err }
|
||||
)
|
||||
logger.error('Cannot notify owner of channel %s of a new follow by %s.', following, follower, { err })
|
||||
})
|
||||
}
|
||||
|
||||
notifyOfNewInstanceFollow (actorFollow: MActorFollowFull): void {
|
||||
const models = this.notificationModels.instanceFollow
|
||||
|
||||
logger.debug('Notify on new instance follow', { follower: actorFollow.ActorFollower.url, ...lTags() })
|
||||
|
||||
this.sendNotifications(models, actorFollow)
|
||||
.catch(err => logger.error('Cannot notify administrators of new follower %s.', actorFollow.ActorFollower.url, { err }))
|
||||
}
|
||||
@ -186,6 +214,8 @@ class Notifier {
|
||||
notifyOfAutoInstanceFollowing (actorFollow: MActorFollowFull): void {
|
||||
const models = this.notificationModels.autoInstanceFollow
|
||||
|
||||
logger.debug('Notify on new instance auto following', { following: actorFollow.ActorFollowing.url, ...lTags() })
|
||||
|
||||
this.sendNotifications(models, actorFollow)
|
||||
.catch(err => logger.error('Cannot notify administrators of auto instance following %s.', actorFollow.ActorFollowing.url, { err }))
|
||||
}
|
||||
@ -193,6 +223,8 @@ class Notifier {
|
||||
notifyOnAbuseStateChange (abuse: MAbuseFull): void {
|
||||
const models = this.notificationModels.abuseStateChange
|
||||
|
||||
logger.debug('Notify on abuse state change', { abuse: abuse.id, ...lTags() })
|
||||
|
||||
this.sendNotifications(models, abuse)
|
||||
.catch(err => logger.error('Cannot notify of abuse %d state change.', abuse.id, { err }))
|
||||
}
|
||||
@ -200,6 +232,8 @@ class Notifier {
|
||||
notifyOnAbuseMessage (abuse: MAbuseFull, message: MAbuseMessage): void {
|
||||
const models = this.notificationModels.newAbuseMessage
|
||||
|
||||
logger.debug('Notify on abuse message', { abuse: abuse.id, message, ...lTags() })
|
||||
|
||||
this.sendNotifications(models, { abuse, message })
|
||||
.catch(err => logger.error('Cannot notify on new abuse %d message.', abuse.id, { err }))
|
||||
}
|
||||
@ -207,13 +241,17 @@ class Notifier {
|
||||
notifyOfNewPeerTubeVersion (application: MApplication, latestVersion: string) {
|
||||
const models = this.notificationModels.newPeertubeVersion
|
||||
|
||||
logger.debug('Notify on new peertube version', { currentVersion: application.version, latestVersion, ...lTags() })
|
||||
|
||||
this.sendNotifications(models, { application, latestVersion })
|
||||
.catch(err => logger.error('Cannot notify on new PeerTubeb version %s.', latestVersion, { err }))
|
||||
.catch(err => logger.error('Cannot notify on new PeerTube version %s.', latestVersion, { err }))
|
||||
}
|
||||
|
||||
notifyOfNewPluginVersion (plugin: MPlugin) {
|
||||
const models = this.notificationModels.newPluginVersion
|
||||
|
||||
logger.debug('Notify on new plugin version', { plugin: plugin.name, ...lTags() })
|
||||
|
||||
this.sendNotifications(models, plugin)
|
||||
.catch(err => logger.error('Cannot notify on new plugin version %s.', plugin.name, { err }))
|
||||
}
|
||||
@ -221,6 +259,8 @@ class Notifier {
|
||||
notifyOfFinishedVideoStudioEdition (video: MVideoFullLight) {
|
||||
const models = this.notificationModels.videoStudioEditionFinished
|
||||
|
||||
logger.debug('Notify on finished video studio edition', { video: video.url, ...lTags() })
|
||||
|
||||
this.sendNotifications(models, video)
|
||||
.catch(err => logger.error('Cannot notify on finished studio edition %s.', video.url, { err }))
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
export * from './new-video-for-subscribers.js'
|
||||
export * from './new-video-or-live-for-subscribers.js'
|
||||
export * from './import-finished-for-owner.js'
|
||||
export * from './owned-publication-after-auto-unblacklist.js'
|
||||
export * from './owned-publication-after-schedule-update.js'
|
||||
|
@ -6,7 +6,7 @@ import { MUserWithNotificationSetting, MVideoAccountLight, UserNotificationModel
|
||||
import { UserNotificationType, VideoPrivacy, VideoState } from '@peertube/peertube-models'
|
||||
import { AbstractNotification } from '../common/abstract-notification.js'
|
||||
|
||||
export class NewVideoForSubscribers extends AbstractNotification <MVideoAccountLight> {
|
||||
export class NewVideoOrLiveForSubscribers extends AbstractNotification <MVideoAccountLight> {
|
||||
private users: MUserWithNotificationSetting[]
|
||||
|
||||
async prepare () {
|
||||
@ -32,7 +32,10 @@ export class NewVideoForSubscribers extends AbstractNotification <MVideoAccountL
|
||||
|
||||
createNotification (user: MUserWithNotificationSetting) {
|
||||
const notification = UserNotificationModel.build<UserNotificationModelForApi>({
|
||||
type: UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION,
|
||||
type: this.payload.isLive
|
||||
? UserNotificationType.NEW_LIVE_FROM_SUBSCRIPTION
|
||||
: UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION,
|
||||
|
||||
userId: user.id,
|
||||
videoId: this.payload.id
|
||||
})
|
||||
@ -41,10 +44,18 @@ export class NewVideoForSubscribers extends AbstractNotification <MVideoAccountL
|
||||
return notification
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
createEmail (to: string) {
|
||||
const channelName = this.payload.VideoChannel.getDisplayName()
|
||||
const videoUrl = WEBSERVER.URL + this.payload.getWatchStaticPath()
|
||||
|
||||
if (this.payload.isLive) return this.createLiveEmail(to, channelName, videoUrl)
|
||||
|
||||
return this.createVideoEmail(to, channelName, videoUrl)
|
||||
}
|
||||
|
||||
private createVideoEmail (to: string, channelName: string, videoUrl: string) {
|
||||
return {
|
||||
to,
|
||||
subject: channelName + ' just published a new video',
|
||||
@ -58,4 +69,19 @@ export class NewVideoForSubscribers extends AbstractNotification <MVideoAccountL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private createLiveEmail (to: string, channelName: string, videoUrl: string) {
|
||||
return {
|
||||
to,
|
||||
subject: channelName + ' is live streaming',
|
||||
text: `Your subscription ${channelName} is live streaming in "${this.payload.name}".`,
|
||||
locals: {
|
||||
title: 'New content ',
|
||||
action: {
|
||||
text: 'View video',
|
||||
url: videoUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -110,7 +110,7 @@ async function unblacklistVideo (videoBlacklist: MVideoBlacklist, video: MVideoF
|
||||
|
||||
// Delete on object so new video notifications will send
|
||||
delete video.VideoBlacklist
|
||||
Notifier.Instance.notifyOnNewVideoIfNeeded(video)
|
||||
Notifier.Instance.notifyOnNewVideoOrLiveIfNeeded(video)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,7 +185,7 @@ async function moveToPublishedState (options: {
|
||||
}
|
||||
|
||||
if (isNewVideo) {
|
||||
Notifier.Instance.notifyOnNewVideoIfNeeded(video)
|
||||
Notifier.Instance.notifyOnNewVideoOrLiveIfNeeded(video)
|
||||
|
||||
if (previousState === VideoState.TO_TRANSCODE) {
|
||||
Notifier.Instance.notifyOnVideoPublishedAfterTranscoding(video)
|
||||
|
Loading…
Reference in New Issue
Block a user