aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/lib
diff options
context:
space:
mode:
Diffstat (limited to 'server/lib')
-rw-r--r--server/lib/activitypub/actor.ts82
-rw-r--r--server/lib/activitypub/audience.ts27
-rw-r--r--server/lib/activitypub/cache-file.ts14
-rw-r--r--server/lib/activitypub/follow.ts36
-rw-r--r--server/lib/activitypub/playlist.ts19
-rw-r--r--server/lib/activitypub/process/process-accept.ts7
-rw-r--r--server/lib/activitypub/process/process-announce.ts7
-rw-r--r--server/lib/activitypub/process/process-create.ts14
-rw-r--r--server/lib/activitypub/process/process-delete.ts26
-rw-r--r--server/lib/activitypub/process/process-dislike.ts4
-rw-r--r--server/lib/activitypub/process/process-flag.ts44
-rw-r--r--server/lib/activitypub/process/process-follow.ts31
-rw-r--r--server/lib/activitypub/process/process-like.ts4
-rw-r--r--server/lib/activitypub/process/process-reject.ts4
-rw-r--r--server/lib/activitypub/process/process-undo.ts12
-rw-r--r--server/lib/activitypub/process/process-update.ts15
-rw-r--r--server/lib/activitypub/process/process-view.ts6
-rw-r--r--server/lib/activitypub/process/process.ts11
-rw-r--r--server/lib/activitypub/send/send-accept.ts7
-rw-r--r--server/lib/activitypub/send/send-announce.ts15
-rw-r--r--server/lib/activitypub/send/send-create.ts34
-rw-r--r--server/lib/activitypub/send/send-delete.ts12
-rw-r--r--server/lib/activitypub/send/send-dislike.ts7
-rw-r--r--server/lib/activitypub/send/send-flag.ts9
-rw-r--r--server/lib/activitypub/send/send-follow.ts7
-rw-r--r--server/lib/activitypub/send/send-like.ts7
-rw-r--r--server/lib/activitypub/send/send-reject.ts7
-rw-r--r--server/lib/activitypub/send/send-undo.ts34
-rw-r--r--server/lib/activitypub/send/send-update.ts31
-rw-r--r--server/lib/activitypub/send/send-view.ts6
-rw-r--r--server/lib/activitypub/send/utils.ts35
-rw-r--r--server/lib/activitypub/share.ts21
-rw-r--r--server/lib/activitypub/url.ts60
-rw-r--r--server/lib/activitypub/video-comments.ts16
-rw-r--r--server/lib/activitypub/video-rates.ts24
-rw-r--r--server/lib/activitypub/videos.ts141
-rw-r--r--server/lib/avatar.ts8
-rw-r--r--server/lib/blocklist.ts5
-rw-r--r--server/lib/client-html.ts11
-rw-r--r--server/lib/emailer.ts63
-rw-r--r--server/lib/hls.ts6
-rw-r--r--server/lib/job-queue/handlers/activitypub-follow.ts24
-rw-r--r--server/lib/job-queue/handlers/activitypub-http-fetcher.ts5
-rw-r--r--server/lib/job-queue/handlers/utils/activitypub-http-utils.ts4
-rw-r--r--server/lib/job-queue/handlers/video-file-import.ts11
-rw-r--r--server/lib/job-queue/handlers/video-import.ts55
-rw-r--r--server/lib/job-queue/handlers/video-transcoding.ts7
-rw-r--r--server/lib/notifier.ts207
-rw-r--r--server/lib/oauth-model.ts3
-rw-r--r--server/lib/peertube-socket.ts7
-rw-r--r--server/lib/redundancy.ts3
-rw-r--r--server/lib/schedulers/auto-follow-index-instances.ts72
-rw-r--r--server/lib/schedulers/videos-redundancy-scheduler.ts57
-rw-r--r--server/lib/thumbnail.ts26
-rw-r--r--server/lib/user.ts36
-rw-r--r--server/lib/video-blacklist.ts17
-rw-r--r--server/lib/video-channel.ts19
-rw-r--r--server/lib/video-comment.ts11
-rw-r--r--server/lib/video-playlist.ts7
-rw-r--r--server/lib/video-transcoding.ts16
60 files changed, 920 insertions, 596 deletions
diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts
index 9f5d12eb4..13b73077e 100644
--- a/server/lib/activitypub/actor.ts
+++ b/server/lib/activitypub/actor.ts
@@ -22,13 +22,27 @@ import { JobQueue } from '../job-queue'
22import { getServerActor } from '../../helpers/utils' 22import { getServerActor } from '../../helpers/utils'
23import { ActorFetchByUrlType, fetchActorByUrl } from '../../helpers/actor' 23import { ActorFetchByUrlType, fetchActorByUrl } from '../../helpers/actor'
24import { sequelizeTypescript } from '../../initializers/database' 24import { sequelizeTypescript } from '../../initializers/database'
25import {
26 MAccount,
27 MAccountDefault,
28 MActor,
29 MActorAccountChannelId,
30 MActorAccountChannelIdActor,
31 MActorAccountId,
32 MActorDefault,
33 MActorFull,
34 MActorFullActor,
35 MActorId,
36 MChannel,
37 MChannelAccountDefault
38} from '../../typings/models'
25 39
26// Set account keys, this could be long so process after the account creation and do not block the client 40// Set account keys, this could be long so process after the account creation and do not block the client
27function setAsyncActorKeys (actor: ActorModel) { 41function setAsyncActorKeys <T extends MActor> (actor: T) {
28 return createPrivateAndPublicKeys() 42 return createPrivateAndPublicKeys()
29 .then(({ publicKey, privateKey }) => { 43 .then(({ publicKey, privateKey }) => {
30 actor.set('publicKey', publicKey) 44 actor.publicKey = publicKey
31 actor.set('privateKey', privateKey) 45 actor.privateKey = privateKey
32 return actor.save() 46 return actor.save()
33 }) 47 })
34 .catch(err => { 48 .catch(err => {
@@ -37,12 +51,26 @@ function setAsyncActorKeys (actor: ActorModel) {
37 }) 51 })
38} 52}
39 53
54function getOrCreateActorAndServerAndModel (
55 activityActor: string | ActivityPubActor,
56 fetchType: 'all',
57 recurseIfNeeded?: boolean,
58 updateCollections?: boolean
59): Promise<MActorFullActor>
60
61function getOrCreateActorAndServerAndModel (
62 activityActor: string | ActivityPubActor,
63 fetchType?: 'association-ids',
64 recurseIfNeeded?: boolean,
65 updateCollections?: boolean
66): Promise<MActorAccountChannelId>
67
40async function getOrCreateActorAndServerAndModel ( 68async function getOrCreateActorAndServerAndModel (
41 activityActor: string | ActivityPubActor, 69 activityActor: string | ActivityPubActor,
42 fetchType: ActorFetchByUrlType = 'actor-and-association-ids', 70 fetchType: ActorFetchByUrlType = 'association-ids',
43 recurseIfNeeded = true, 71 recurseIfNeeded = true,
44 updateCollections = false 72 updateCollections = false
45) { 73): Promise<MActorFullActor | MActorAccountChannelId> {
46 const actorUrl = getAPId(activityActor) 74 const actorUrl = getAPId(activityActor)
47 let created = false 75 let created = false
48 let accountPlaylistsUrl: string 76 let accountPlaylistsUrl: string
@@ -61,7 +89,7 @@ async function getOrCreateActorAndServerAndModel (
61 89
62 // Create the attributed to actor 90 // Create the attributed to actor
63 // In PeerTube a video channel is owned by an account 91 // In PeerTube a video channel is owned by an account
64 let ownerActor: ActorModel = undefined 92 let ownerActor: MActorFullActor
65 if (recurseIfNeeded === true && result.actor.type === 'Group') { 93 if (recurseIfNeeded === true && result.actor.type === 'Group') {
66 const accountAttributedTo = result.attributedTo.find(a => a.type === 'Person') 94 const accountAttributedTo = result.attributedTo.find(a => a.type === 'Person')
67 if (!accountAttributedTo) throw new Error('Cannot find account attributed to video channel ' + actor.url) 95 if (!accountAttributedTo) throw new Error('Cannot find account attributed to video channel ' + actor.url)
@@ -85,8 +113,8 @@ async function getOrCreateActorAndServerAndModel (
85 accountPlaylistsUrl = result.playlists 113 accountPlaylistsUrl = result.playlists
86 } 114 }
87 115
88 if (actor.Account) actor.Account.Actor = actor 116 if (actor.Account) (actor as MActorAccountChannelIdActor).Account.Actor = actor
89 if (actor.VideoChannel) actor.VideoChannel.Actor = actor 117 if (actor.VideoChannel) (actor as MActorAccountChannelIdActor).VideoChannel.Actor = actor
90 118
91 const { actor: actorRefreshed, refreshed } = await retryTransactionWrapper(refreshActorIfNeeded, actor, fetchType) 119 const { actor: actorRefreshed, refreshed } = await retryTransactionWrapper(refreshActorIfNeeded, actor, fetchType)
92 if (!actorRefreshed) throw new Error('Actor ' + actorRefreshed.url + ' does not exist anymore.') 120 if (!actorRefreshed) throw new Error('Actor ' + actorRefreshed.url + ' does not exist anymore.')
@@ -120,7 +148,7 @@ function buildActorInstance (type: ActivityPubActorType, url: string, preferredU
120 sharedInboxUrl: WEBSERVER.URL + '/inbox', 148 sharedInboxUrl: WEBSERVER.URL + '/inbox',
121 followersUrl: url + '/followers', 149 followersUrl: url + '/followers',
122 followingUrl: url + '/following' 150 followingUrl: url + '/following'
123 }) 151 }) as MActor
124} 152}
125 153
126async function updateActorInstance (actorInstance: ActorModel, attributes: ActivityPubActor) { 154async function updateActorInstance (actorInstance: ActorModel, attributes: ActivityPubActor) {
@@ -140,7 +168,8 @@ async function updateActorInstance (actorInstance: ActorModel, attributes: Activ
140 actorInstance.followingUrl = attributes.following 168 actorInstance.followingUrl = attributes.following
141} 169}
142 170
143async function updateActorAvatarInstance (actor: ActorModel, info: { name: string, onDisk: boolean, fileUrl: string }, t: Transaction) { 171type AvatarInfo = { name: string, onDisk: boolean, fileUrl: string }
172async function updateActorAvatarInstance (actor: MActorDefault, info: AvatarInfo, t: Transaction) {
144 if (info.name !== undefined) { 173 if (info.name !== undefined) {
145 if (actor.avatarId) { 174 if (actor.avatarId) {
146 try { 175 try {
@@ -212,14 +241,16 @@ async function addFetchOutboxJob (actor: Pick<ActorModel, 'id' | 'outboxUrl'>) {
212 return JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload }) 241 return JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload })
213} 242}
214 243
215async function refreshActorIfNeeded ( 244async function refreshActorIfNeeded <T extends MActorFull | MActorAccountChannelId> (
216 actorArg: ActorModel, 245 actorArg: T,
217 fetchedType: ActorFetchByUrlType 246 fetchedType: ActorFetchByUrlType
218): Promise<{ actor: ActorModel, refreshed: boolean }> { 247): Promise<{ actor: T | MActorFull, refreshed: boolean }> {
219 if (!actorArg.isOutdated()) return { actor: actorArg, refreshed: false } 248 if (!actorArg.isOutdated()) return { actor: actorArg, refreshed: false }
220 249
221 // We need more attributes 250 // We need more attributes
222 const actor = fetchedType === 'all' ? actorArg : await ActorModel.loadByUrlAndPopulateAccountAndChannel(actorArg.url) 251 const actor = fetchedType === 'all'
252 ? actorArg as MActorFull
253 : await ActorModel.loadByUrlAndPopulateAccountAndChannel(actorArg.url)
223 254
224 try { 255 try {
225 let actorUrl: string 256 let actorUrl: string
@@ -297,9 +328,9 @@ export {
297 328
298function saveActorAndServerAndModelIfNotExist ( 329function saveActorAndServerAndModelIfNotExist (
299 result: FetchRemoteActorResult, 330 result: FetchRemoteActorResult,
300 ownerActor?: ActorModel, 331 ownerActor?: MActorFullActor,
301 t?: Transaction 332 t?: Transaction
302): Bluebird<ActorModel> | Promise<ActorModel> { 333): Bluebird<MActorFullActor> | Promise<MActorFullActor> {
303 let actor = result.actor 334 let actor = result.actor
304 335
305 if (t !== undefined) return save(t) 336 if (t !== undefined) return save(t)
@@ -336,7 +367,7 @@ function saveActorAndServerAndModelIfNotExist (
336 367
337 // Force the actor creation, sometimes Sequelize skips the save() when it thinks the instance already exists 368 // Force the actor creation, sometimes Sequelize skips the save() when it thinks the instance already exists
338 // (which could be false in a retried query) 369 // (which could be false in a retried query)
339 const [ actorCreated ] = await ActorModel.findOrCreate({ 370 const [ actorCreated ] = await ActorModel.findOrCreate<MActorFullActor>({
340 defaults: actor.toJSON(), 371 defaults: actor.toJSON(),
341 where: { 372 where: {
342 url: actor.url 373 url: actor.url
@@ -345,12 +376,11 @@ function saveActorAndServerAndModelIfNotExist (
345 }) 376 })
346 377
347 if (actorCreated.type === 'Person' || actorCreated.type === 'Application') { 378 if (actorCreated.type === 'Person' || actorCreated.type === 'Application') {
348 actorCreated.Account = await saveAccount(actorCreated, result, t) 379 actorCreated.Account = await saveAccount(actorCreated, result, t) as MAccountDefault
349 actorCreated.Account.Actor = actorCreated 380 actorCreated.Account.Actor = actorCreated
350 } else if (actorCreated.type === 'Group') { // Video channel 381 } else if (actorCreated.type === 'Group') { // Video channel
351 actorCreated.VideoChannel = await saveVideoChannel(actorCreated, result, ownerActor, t) 382 const channel = await saveVideoChannel(actorCreated, result, ownerActor, t)
352 actorCreated.VideoChannel.Actor = actorCreated 383 actorCreated.VideoChannel = Object.assign(channel, { Actor: actorCreated, Account: ownerActor.Account })
353 actorCreated.VideoChannel.Account = ownerActor.Account
354 } 384 }
355 385
356 actorCreated.Server = server 386 actorCreated.Server = server
@@ -360,7 +390,7 @@ function saveActorAndServerAndModelIfNotExist (
360} 390}
361 391
362type FetchRemoteActorResult = { 392type FetchRemoteActorResult = {
363 actor: ActorModel 393 actor: MActor
364 name: string 394 name: string
365 summary: string 395 summary: string
366 support?: string 396 support?: string
@@ -429,7 +459,7 @@ async function fetchRemoteActor (actorUrl: string): Promise<{ statusCode?: numbe
429 } 459 }
430} 460}
431 461
432async function saveAccount (actor: ActorModel, result: FetchRemoteActorResult, t: Transaction) { 462async function saveAccount (actor: MActorId, result: FetchRemoteActorResult, t: Transaction) {
433 const [ accountCreated ] = await AccountModel.findOrCreate({ 463 const [ accountCreated ] = await AccountModel.findOrCreate({
434 defaults: { 464 defaults: {
435 name: result.name, 465 name: result.name,
@@ -442,10 +472,10 @@ async function saveAccount (actor: ActorModel, result: FetchRemoteActorResult, t
442 transaction: t 472 transaction: t
443 }) 473 })
444 474
445 return accountCreated 475 return accountCreated as MAccount
446} 476}
447 477
448async function saveVideoChannel (actor: ActorModel, result: FetchRemoteActorResult, ownerActor: ActorModel, t: Transaction) { 478async function saveVideoChannel (actor: MActorId, result: FetchRemoteActorResult, ownerActor: MActorAccountId, t: Transaction) {
449 const [ videoChannelCreated ] = await VideoChannelModel.findOrCreate({ 479 const [ videoChannelCreated ] = await VideoChannelModel.findOrCreate({
450 defaults: { 480 defaults: {
451 name: result.name, 481 name: result.name,
@@ -460,5 +490,5 @@ async function saveVideoChannel (actor: ActorModel, result: FetchRemoteActorResu
460 transaction: t 490 transaction: t
461 }) 491 })
462 492
463 return videoChannelCreated 493 return videoChannelCreated as MChannel
464} 494}
diff --git a/server/lib/activitypub/audience.ts b/server/lib/activitypub/audience.ts
index 0e3d78590..f2ab54cf7 100644
--- a/server/lib/activitypub/audience.ts
+++ b/server/lib/activitypub/audience.ts
@@ -3,11 +3,10 @@ import { ActivityAudience } from '../../../shared/models/activitypub'
3import { ACTIVITY_PUB } from '../../initializers/constants' 3import { ACTIVITY_PUB } from '../../initializers/constants'
4import { ActorModel } from '../../models/activitypub/actor' 4import { ActorModel } from '../../models/activitypub/actor'
5import { VideoModel } from '../../models/video/video' 5import { VideoModel } from '../../models/video/video'
6import { VideoCommentModel } from '../../models/video/video-comment'
7import { VideoShareModel } from '../../models/video/video-share' 6import { VideoShareModel } from '../../models/video/video-share'
8import { ActorModelOnly } from '../../typings/models' 7import { MActorFollowersUrl, MActorLight, MCommentOwner, MCommentOwnerVideo, MVideo, MVideoAccountLight } from '../../typings/models'
9 8
10function getRemoteVideoAudience (video: VideoModel, actorsInvolvedInVideo: ActorModel[]): ActivityAudience { 9function getRemoteVideoAudience (video: MVideoAccountLight, actorsInvolvedInVideo: MActorFollowersUrl[]): ActivityAudience {
11 return { 10 return {
12 to: [ video.VideoChannel.Account.Actor.url ], 11 to: [ video.VideoChannel.Account.Actor.url ],
13 cc: actorsInvolvedInVideo.map(a => a.followersUrl) 12 cc: actorsInvolvedInVideo.map(a => a.followersUrl)
@@ -15,9 +14,9 @@ function getRemoteVideoAudience (video: VideoModel, actorsInvolvedInVideo: Actor
15} 14}
16 15
17function getVideoCommentAudience ( 16function getVideoCommentAudience (
18 videoComment: VideoCommentModel, 17 videoComment: MCommentOwnerVideo,
19 threadParentComments: VideoCommentModel[], 18 threadParentComments: MCommentOwner[],
20 actorsInvolvedInVideo: ActorModel[], 19 actorsInvolvedInVideo: MActorFollowersUrl[],
21 isOrigin = false 20 isOrigin = false
22): ActivityAudience { 21): ActivityAudience {
23 const to = [ ACTIVITY_PUB.PUBLIC ] 22 const to = [ ACTIVITY_PUB.PUBLIC ]
@@ -42,26 +41,28 @@ function getVideoCommentAudience (
42 } 41 }
43} 42}
44 43
45function getAudienceFromFollowersOf (actorsInvolvedInObject: ActorModel[]): ActivityAudience { 44function getAudienceFromFollowersOf (actorsInvolvedInObject: MActorFollowersUrl[]): ActivityAudience {
46 return { 45 return {
47 to: [ ACTIVITY_PUB.PUBLIC ].concat(actorsInvolvedInObject.map(a => a.followersUrl)), 46 to: [ ACTIVITY_PUB.PUBLIC ].concat(actorsInvolvedInObject.map(a => a.followersUrl)),
48 cc: [] 47 cc: []
49 } 48 }
50} 49}
51 50
52async function getActorsInvolvedInVideo (video: VideoModel, t: Transaction) { 51async function getActorsInvolvedInVideo (video: MVideo, t: Transaction) {
53 const actors = await VideoShareModel.loadActorsByShare(video.id, t) 52 const actors: MActorLight[] = await VideoShareModel.loadActorsByShare(video.id, t)
54 53
55 const videoActor = video.VideoChannel && video.VideoChannel.Account 54 const videoAll = video as VideoModel
56 ? video.VideoChannel.Account.Actor 55
57 : await ActorModel.loadAccountActorByVideoId(video.id, t) 56 const videoActor = videoAll.VideoChannel && videoAll.VideoChannel.Account
57 ? videoAll.VideoChannel.Account.Actor
58 : await ActorModel.loadFromAccountByVideoId(video.id, t)
58 59
59 actors.push(videoActor) 60 actors.push(videoActor)
60 61
61 return actors 62 return actors
62} 63}
63 64
64function getAudience (actorSender: ActorModelOnly, isPublic = true) { 65function getAudience (actorSender: MActorFollowersUrl, isPublic = true) {
65 return buildAudience([ actorSender.followersUrl ], isPublic) 66 return buildAudience([ actorSender.followersUrl ], isPublic)
66} 67}
67 68
diff --git a/server/lib/activitypub/cache-file.ts b/server/lib/activitypub/cache-file.ts
index de5cc54ac..65b2dcb49 100644
--- a/server/lib/activitypub/cache-file.ts
+++ b/server/lib/activitypub/cache-file.ts
@@ -1,10 +1,10 @@
1import { CacheFileObject } from '../../../shared/index' 1import { CacheFileObject } from '../../../shared/index'
2import { VideoModel } from '../../models/video/video'
3import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' 2import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy'
4import { Transaction } from 'sequelize' 3import { Transaction } from 'sequelize'
5import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' 4import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type'
5import { MActorId, MVideoRedundancy, MVideoWithAllFiles } from '@server/typings/models'
6 6
7function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject, video: VideoModel, byActor: { id?: number }) { 7function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject, video: MVideoWithAllFiles, byActor: MActorId) {
8 8
9 if (cacheFileObject.url.mediaType === 'application/x-mpegURL') { 9 if (cacheFileObject.url.mediaType === 'application/x-mpegURL') {
10 const url = cacheFileObject.url 10 const url = cacheFileObject.url
@@ -39,7 +39,7 @@ function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject
39 } 39 }
40} 40}
41 41
42async function createOrUpdateCacheFile (cacheFileObject: CacheFileObject, video: VideoModel, byActor: { id?: number }, t: Transaction) { 42async function createOrUpdateCacheFile (cacheFileObject: CacheFileObject, video: MVideoWithAllFiles, byActor: MActorId, t: Transaction) {
43 const redundancyModel = await VideoRedundancyModel.loadByUrl(cacheFileObject.id, t) 43 const redundancyModel = await VideoRedundancyModel.loadByUrl(cacheFileObject.id, t)
44 44
45 if (!redundancyModel) { 45 if (!redundancyModel) {
@@ -49,7 +49,7 @@ async function createOrUpdateCacheFile (cacheFileObject: CacheFileObject, video:
49 } 49 }
50} 50}
51 51
52function createCacheFile (cacheFileObject: CacheFileObject, video: VideoModel, byActor: { id?: number }, t: Transaction) { 52function createCacheFile (cacheFileObject: CacheFileObject, video: MVideoWithAllFiles, byActor: MActorId, t: Transaction) {
53 const attributes = cacheFileActivityObjectToDBAttributes(cacheFileObject, video, byActor) 53 const attributes = cacheFileActivityObjectToDBAttributes(cacheFileObject, video, byActor)
54 54
55 return VideoRedundancyModel.create(attributes, { transaction: t }) 55 return VideoRedundancyModel.create(attributes, { transaction: t })
@@ -57,9 +57,9 @@ function createCacheFile (cacheFileObject: CacheFileObject, video: VideoModel, b
57 57
58function updateCacheFile ( 58function updateCacheFile (
59 cacheFileObject: CacheFileObject, 59 cacheFileObject: CacheFileObject,
60 redundancyModel: VideoRedundancyModel, 60 redundancyModel: MVideoRedundancy,
61 video: VideoModel, 61 video: MVideoWithAllFiles,
62 byActor: { id?: number }, 62 byActor: MActorId,
63 t: Transaction 63 t: Transaction
64) { 64) {
65 if (redundancyModel.actorId !== byActor.id) { 65 if (redundancyModel.actorId !== byActor.id) {
diff --git a/server/lib/activitypub/follow.ts b/server/lib/activitypub/follow.ts
new file mode 100644
index 000000000..1abf43cd4
--- /dev/null
+++ b/server/lib/activitypub/follow.ts
@@ -0,0 +1,36 @@
1import { MActorFollowActors } from '../../typings/models'
2import { CONFIG } from '../../initializers/config'
3import { SERVER_ACTOR_NAME } from '../../initializers/constants'
4import { JobQueue } from '../job-queue'
5import { logger } from '../../helpers/logger'
6import { getServerActor } from '../../helpers/utils'
7import { ServerModel } from '../../models/server/server'
8
9async function autoFollowBackIfNeeded (actorFollow: MActorFollowActors) {
10 if (!CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_BACK.ENABLED) return
11
12 const follower = actorFollow.ActorFollower
13
14 if (follower.type === 'Application' && follower.preferredUsername === SERVER_ACTOR_NAME) {
15 logger.info('Auto follow back %s.', follower.url)
16
17 const me = await getServerActor()
18
19 const server = await ServerModel.load(follower.serverId)
20 const host = server.host
21
22 const payload = {
23 host,
24 name: SERVER_ACTOR_NAME,
25 followerActorId: me.id,
26 isAutoFollow: true
27 }
28
29 JobQueue.Instance.createJob({ type: 'activitypub-follow', payload })
30 .catch(err => logger.error('Cannot create auto follow back job for %s.', host, err))
31 }
32}
33
34export {
35 autoFollowBackIfNeeded
36}
diff --git a/server/lib/activitypub/playlist.ts b/server/lib/activitypub/playlist.ts
index c2e2a3283..c52b715ef 100644
--- a/server/lib/activitypub/playlist.ts
+++ b/server/lib/activitypub/playlist.ts
@@ -1,7 +1,6 @@
1import { PlaylistObject } from '../../../shared/models/activitypub/objects/playlist-object' 1import { PlaylistObject } from '../../../shared/models/activitypub/objects/playlist-object'
2import { crawlCollectionPage } from './crawl' 2import { crawlCollectionPage } from './crawl'
3import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' 3import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants'
4import { AccountModel } from '../../models/account/account'
5import { isArray } from '../../helpers/custom-validators/misc' 4import { isArray } from '../../helpers/custom-validators/misc'
6import { getOrCreateActorAndServerAndModel } from './actor' 5import { getOrCreateActorAndServerAndModel } from './actor'
7import { logger } from '../../helpers/logger' 6import { logger } from '../../helpers/logger'
@@ -13,14 +12,14 @@ import { PlaylistElementObject } from '../../../shared/models/activitypub/object
13import { getOrCreateVideoAndAccountAndChannel } from './videos' 12import { getOrCreateVideoAndAccountAndChannel } from './videos'
14import { isPlaylistElementObjectValid, isPlaylistObjectValid } from '../../helpers/custom-validators/activitypub/playlist' 13import { isPlaylistElementObjectValid, isPlaylistObjectValid } from '../../helpers/custom-validators/activitypub/playlist'
15import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element' 14import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element'
16import { VideoModel } from '../../models/video/video'
17import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' 15import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
18import { sequelizeTypescript } from '../../initializers/database' 16import { sequelizeTypescript } from '../../initializers/database'
19import { createPlaylistMiniatureFromUrl } from '../thumbnail' 17import { createPlaylistMiniatureFromUrl } from '../thumbnail'
20import { FilteredModelAttributes } from '../../typings/sequelize' 18import { FilteredModelAttributes } from '../../typings/sequelize'
21import { AccountModelId } from '../../typings/models' 19import { MAccountDefault, MAccountId, MVideoId } from '../../typings/models'
20import { MVideoPlaylist, MVideoPlaylistId, MVideoPlaylistOwner } from '../../typings/models/video/video-playlist'
22 21
23function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount: AccountModelId, to: string[]) { 22function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount: MAccountId, to: string[]) {
24 const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPlaylistPrivacy.PUBLIC : VideoPlaylistPrivacy.UNLISTED 23 const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPlaylistPrivacy.PUBLIC : VideoPlaylistPrivacy.UNLISTED
25 24
26 return { 25 return {
@@ -36,7 +35,7 @@ function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount
36 } 35 }
37} 36}
38 37
39function playlistElementObjectToDBAttributes (elementObject: PlaylistElementObject, videoPlaylist: VideoPlaylistModel, video: VideoModel) { 38function playlistElementObjectToDBAttributes (elementObject: PlaylistElementObject, videoPlaylist: MVideoPlaylistId, video: MVideoId) {
40 return { 39 return {
41 position: elementObject.position, 40 position: elementObject.position,
42 url: elementObject.id, 41 url: elementObject.id,
@@ -47,7 +46,7 @@ function playlistElementObjectToDBAttributes (elementObject: PlaylistElementObje
47 } 46 }
48} 47}
49 48
50async function createAccountPlaylists (playlistUrls: string[], account: AccountModel) { 49async function createAccountPlaylists (playlistUrls: string[], account: MAccountDefault) {
51 await Bluebird.map(playlistUrls, async playlistUrl => { 50 await Bluebird.map(playlistUrls, async playlistUrl => {
52 try { 51 try {
53 const exists = await VideoPlaylistModel.doesPlaylistExist(playlistUrl) 52 const exists = await VideoPlaylistModel.doesPlaylistExist(playlistUrl)
@@ -75,7 +74,7 @@ async function createAccountPlaylists (playlistUrls: string[], account: AccountM
75 }, { concurrency: CRAWL_REQUEST_CONCURRENCY }) 74 }, { concurrency: CRAWL_REQUEST_CONCURRENCY })
76} 75}
77 76
78async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAccount: AccountModelId, to: string[]) { 77async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAccount: MAccountId, to: string[]) {
79 const playlistAttributes = playlistObjectToDBAttributes(playlistObject, byAccount, to) 78 const playlistAttributes = playlistObjectToDBAttributes(playlistObject, byAccount, to)
80 79
81 if (isArray(playlistObject.attributedTo) && playlistObject.attributedTo.length === 1) { 80 if (isArray(playlistObject.attributedTo) && playlistObject.attributedTo.length === 1) {
@@ -88,7 +87,7 @@ async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAc
88 } 87 }
89 } 88 }
90 89
91 const [ playlist ] = await VideoPlaylistModel.upsert<VideoPlaylistModel>(playlistAttributes, { returning: true }) 90 const [ playlist ] = await VideoPlaylistModel.upsert<MVideoPlaylist>(playlistAttributes, { returning: true })
92 91
93 let accItems: string[] = [] 92 let accItems: string[] = []
94 await crawlCollectionPage<string>(playlistObject.id, items => { 93 await crawlCollectionPage<string>(playlistObject.id, items => {
@@ -114,7 +113,7 @@ async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAc
114 return resetVideoPlaylistElements(accItems, refreshedPlaylist) 113 return resetVideoPlaylistElements(accItems, refreshedPlaylist)
115} 114}
116 115
117async function refreshVideoPlaylistIfNeeded (videoPlaylist: VideoPlaylistModel): Promise<VideoPlaylistModel> { 116async function refreshVideoPlaylistIfNeeded (videoPlaylist: MVideoPlaylistOwner): Promise<MVideoPlaylistOwner> {
118 if (!videoPlaylist.isOutdated()) return videoPlaylist 117 if (!videoPlaylist.isOutdated()) return videoPlaylist
119 118
120 try { 119 try {
@@ -157,7 +156,7 @@ export {
157 156
158// --------------------------------------------------------------------------- 157// ---------------------------------------------------------------------------
159 158
160async function resetVideoPlaylistElements (elementUrls: string[], playlist: VideoPlaylistModel) { 159async function resetVideoPlaylistElements (elementUrls: string[], playlist: MVideoPlaylist) {
161 const elementsToCreate: FilteredModelAttributes<VideoPlaylistElementModel>[] = [] 160 const elementsToCreate: FilteredModelAttributes<VideoPlaylistElementModel>[] = []
162 161
163 await Bluebird.map(elementUrls, async elementUrl => { 162 await Bluebird.map(elementUrls, async elementUrl => {
diff --git a/server/lib/activitypub/process/process-accept.ts b/server/lib/activitypub/process/process-accept.ts
index cf27e6c32..dcfbb2c84 100644
--- a/server/lib/activitypub/process/process-accept.ts
+++ b/server/lib/activitypub/process/process-accept.ts
@@ -1,9 +1,8 @@
1import { ActivityAccept } from '../../../../shared/models/activitypub' 1import { ActivityAccept } from '../../../../shared/models/activitypub'
2import { ActorModel } from '../../../models/activitypub/actor'
3import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 2import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
4import { addFetchOutboxJob } from '../actor' 3import { addFetchOutboxJob } from '../actor'
5import { APProcessorOptions } from '../../../typings/activitypub-processor.model' 4import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
6import { SignatureActorModel } from '../../../typings/models' 5import { MActorDefault, MActorSignature } from '../../../typings/models'
7 6
8async function processAcceptActivity (options: APProcessorOptions<ActivityAccept>) { 7async function processAcceptActivity (options: APProcessorOptions<ActivityAccept>) {
9 const { byActor: targetActor, inboxActor } = options 8 const { byActor: targetActor, inboxActor } = options
@@ -20,12 +19,12 @@ export {
20 19
21// --------------------------------------------------------------------------- 20// ---------------------------------------------------------------------------
22 21
23async function processAccept (actor: ActorModel, targetActor: SignatureActorModel) { 22async function processAccept (actor: MActorDefault, targetActor: MActorSignature) {
24 const follow = await ActorFollowModel.loadByActorAndTarget(actor.id, targetActor.id) 23 const follow = await ActorFollowModel.loadByActorAndTarget(actor.id, targetActor.id)
25 if (!follow) throw new Error('Cannot find associated follow.') 24 if (!follow) throw new Error('Cannot find associated follow.')
26 25
27 if (follow.state !== 'accepted') { 26 if (follow.state !== 'accepted') {
28 follow.set('state', 'accepted') 27 follow.state = 'accepted'
29 await follow.save() 28 await follow.save()
30 29
31 await addFetchOutboxJob(targetActor) 30 await addFetchOutboxJob(targetActor)
diff --git a/server/lib/activitypub/process/process-announce.ts b/server/lib/activitypub/process/process-announce.ts
index b3cdc4441..7e22125d5 100644
--- a/server/lib/activitypub/process/process-announce.ts
+++ b/server/lib/activitypub/process/process-announce.ts
@@ -5,10 +5,9 @@ import { VideoShareModel } from '../../../models/video/video-share'
5import { forwardVideoRelatedActivity } from '../send/utils' 5import { forwardVideoRelatedActivity } from '../send/utils'
6import { getOrCreateVideoAndAccountAndChannel } from '../videos' 6import { getOrCreateVideoAndAccountAndChannel } from '../videos'
7import { Notifier } from '../../notifier' 7import { Notifier } from '../../notifier'
8import { VideoModel } from '../../../models/video/video'
9import { logger } from '../../../helpers/logger' 8import { logger } from '../../../helpers/logger'
10import { APProcessorOptions } from '../../../typings/activitypub-processor.model' 9import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
11import { SignatureActorModel } from '../../../typings/models' 10import { MActorSignature, MVideoAccountLightBlacklistAllFiles } from '../../../typings/models'
12 11
13async function processAnnounceActivity (options: APProcessorOptions<ActivityAnnounce>) { 12async function processAnnounceActivity (options: APProcessorOptions<ActivityAnnounce>) {
14 const { activity, byActor: actorAnnouncer } = options 13 const { activity, byActor: actorAnnouncer } = options
@@ -26,10 +25,10 @@ export {
26 25
27// --------------------------------------------------------------------------- 26// ---------------------------------------------------------------------------
28 27
29async function processVideoShare (actorAnnouncer: SignatureActorModel, activity: ActivityAnnounce, notify: boolean) { 28async function processVideoShare (actorAnnouncer: MActorSignature, activity: ActivityAnnounce, notify: boolean) {
30 const objectUri = typeof activity.object === 'string' ? activity.object : activity.object.id 29 const objectUri = typeof activity.object === 'string' ? activity.object : activity.object.id
31 30
32 let video: VideoModel 31 let video: MVideoAccountLightBlacklistAllFiles
33 let videoCreated: boolean 32 let videoCreated: boolean
34 33
35 try { 34 try {
diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts
index 6815c6997..bee853721 100644
--- a/server/lib/activitypub/process/process-create.ts
+++ b/server/lib/activitypub/process/process-create.ts
@@ -10,10 +10,8 @@ import { createOrUpdateCacheFile } from '../cache-file'
10import { Notifier } from '../../notifier' 10import { Notifier } from '../../notifier'
11import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object' 11import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object'
12import { createOrUpdateVideoPlaylist } from '../playlist' 12import { createOrUpdateVideoPlaylist } from '../playlist'
13import { VideoModel } from '../../../models/video/video'
14import { APProcessorOptions } from '../../../typings/activitypub-processor.model' 13import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
15import { VideoCommentModel } from '../../../models/video/video-comment' 14import { MActorSignature, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../../typings/models'
16import { SignatureActorModel } from '../../../typings/models'
17 15
18async function processCreateActivity (options: APProcessorOptions<ActivityCreate>) { 16async function processCreateActivity (options: APProcessorOptions<ActivityCreate>) {
19 const { activity, byActor } = options 17 const { activity, byActor } = options
@@ -61,7 +59,7 @@ async function processCreateVideo (activity: ActivityCreate, notify: boolean) {
61 return video 59 return video
62} 60}
63 61
64async function processCreateCacheFile (activity: ActivityCreate, byActor: SignatureActorModel) { 62async function processCreateCacheFile (activity: ActivityCreate, byActor: MActorSignature) {
65 const cacheFile = activity.object as CacheFileObject 63 const cacheFile = activity.object as CacheFileObject
66 64
67 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFile.object }) 65 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFile.object })
@@ -77,15 +75,15 @@ async function processCreateCacheFile (activity: ActivityCreate, byActor: Signat
77 } 75 }
78} 76}
79 77
80async function processCreateVideoComment (activity: ActivityCreate, byActor: SignatureActorModel, notify: boolean) { 78async function processCreateVideoComment (activity: ActivityCreate, byActor: MActorSignature, notify: boolean) {
81 const commentObject = activity.object as VideoCommentObject 79 const commentObject = activity.object as VideoCommentObject
82 const byAccount = byActor.Account 80 const byAccount = byActor.Account
83 81
84 if (!byAccount) throw new Error('Cannot create video comment with the non account actor ' + byActor.url) 82 if (!byAccount) throw new Error('Cannot create video comment with the non account actor ' + byActor.url)
85 83
86 let video: VideoModel 84 let video: MVideoAccountLightBlacklistAllFiles
87 let created: boolean 85 let created: boolean
88 let comment: VideoCommentModel 86 let comment: MCommentOwnerVideo
89 try { 87 try {
90 const resolveThreadResult = await resolveThread({ url: commentObject.id, isVideo: false }) 88 const resolveThreadResult = await resolveThread({ url: commentObject.id, isVideo: false })
91 video = resolveThreadResult.video 89 video = resolveThreadResult.video
@@ -110,7 +108,7 @@ async function processCreateVideoComment (activity: ActivityCreate, byActor: Sig
110 if (created && notify) Notifier.Instance.notifyOnNewComment(comment) 108 if (created && notify) Notifier.Instance.notifyOnNewComment(comment)
111} 109}
112 110
113async function processCreatePlaylist (activity: ActivityCreate, byActor: SignatureActorModel) { 111async function processCreatePlaylist (activity: ActivityCreate, byActor: MActorSignature) {
114 const playlistObject = activity.object as PlaylistObject 112 const playlistObject = activity.object as PlaylistObject
115 const byAccount = byActor.Account 113 const byAccount = byActor.Account
116 114
diff --git a/server/lib/activitypub/process/process-delete.ts b/server/lib/activitypub/process/process-delete.ts
index 344d14322..79d0e0d79 100644
--- a/server/lib/activitypub/process/process-delete.ts
+++ b/server/lib/activitypub/process/process-delete.ts
@@ -2,15 +2,13 @@ import { ActivityDelete } from '../../../../shared/models/activitypub'
2import { retryTransactionWrapper } from '../../../helpers/database-utils' 2import { retryTransactionWrapper } from '../../../helpers/database-utils'
3import { logger } from '../../../helpers/logger' 3import { logger } from '../../../helpers/logger'
4import { sequelizeTypescript } from '../../../initializers' 4import { sequelizeTypescript } from '../../../initializers'
5import { AccountModel } from '../../../models/account/account'
6import { ActorModel } from '../../../models/activitypub/actor' 5import { ActorModel } from '../../../models/activitypub/actor'
7import { VideoModel } from '../../../models/video/video' 6import { VideoModel } from '../../../models/video/video'
8import { VideoChannelModel } from '../../../models/video/video-channel'
9import { VideoCommentModel } from '../../../models/video/video-comment' 7import { VideoCommentModel } from '../../../models/video/video-comment'
10import { forwardVideoRelatedActivity } from '../send/utils' 8import { forwardVideoRelatedActivity } from '../send/utils'
11import { VideoPlaylistModel } from '../../../models/video/video-playlist' 9import { VideoPlaylistModel } from '../../../models/video/video-playlist'
12import { APProcessorOptions } from '../../../typings/activitypub-processor.model' 10import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
13import { SignatureActorModel } from '../../../typings/models' 11import { MAccountActor, MActor, MActorSignature, MChannelActor, MChannelActorAccountActor } from '../../../typings/models'
14 12
15async function processDeleteActivity (options: APProcessorOptions<ActivityDelete>) { 13async function processDeleteActivity (options: APProcessorOptions<ActivityDelete>) {
16 const { activity, byActor } = options 14 const { activity, byActor } = options
@@ -24,13 +22,17 @@ async function processDeleteActivity (options: APProcessorOptions<ActivityDelete
24 if (byActorFull.type === 'Person') { 22 if (byActorFull.type === 'Person') {
25 if (!byActorFull.Account) throw new Error('Actor ' + byActorFull.url + ' is a person but we cannot find it in database.') 23 if (!byActorFull.Account) throw new Error('Actor ' + byActorFull.url + ' is a person but we cannot find it in database.')
26 24
27 byActorFull.Account.Actor = await byActorFull.Account.$get('Actor') as ActorModel 25 const accountToDelete = byActorFull.Account as MAccountActor
28 return retryTransactionWrapper(processDeleteAccount, byActorFull.Account) 26 accountToDelete.Actor = byActorFull
27
28 return retryTransactionWrapper(processDeleteAccount, accountToDelete)
29 } else if (byActorFull.type === 'Group') { 29 } else if (byActorFull.type === 'Group') {
30 if (!byActorFull.VideoChannel) throw new Error('Actor ' + byActorFull.url + ' is a group but we cannot find it in database.') 30 if (!byActorFull.VideoChannel) throw new Error('Actor ' + byActorFull.url + ' is a group but we cannot find it in database.')
31 31
32 byActorFull.VideoChannel.Actor = await byActorFull.VideoChannel.$get('Actor') as ActorModel 32 const channelToDelete = byActorFull.VideoChannel as MChannelActorAccountActor
33 return retryTransactionWrapper(processDeleteVideoChannel, byActorFull.VideoChannel) 33 channelToDelete.Actor = byActorFull
34
35 return retryTransactionWrapper(processDeleteVideoChannel, channelToDelete)
34 } 36 }
35 } 37 }
36 38
@@ -70,7 +72,7 @@ export {
70 72
71// --------------------------------------------------------------------------- 73// ---------------------------------------------------------------------------
72 74
73async function processDeleteVideo (actor: ActorModel, videoToDelete: VideoModel) { 75async function processDeleteVideo (actor: MActor, videoToDelete: VideoModel) {
74 logger.debug('Removing remote video "%s".', videoToDelete.uuid) 76 logger.debug('Removing remote video "%s".', videoToDelete.uuid)
75 77
76 await sequelizeTypescript.transaction(async t => { 78 await sequelizeTypescript.transaction(async t => {
@@ -84,7 +86,7 @@ async function processDeleteVideo (actor: ActorModel, videoToDelete: VideoModel)
84 logger.info('Remote video with uuid %s removed.', videoToDelete.uuid) 86 logger.info('Remote video with uuid %s removed.', videoToDelete.uuid)
85} 87}
86 88
87async function processDeleteVideoPlaylist (actor: ActorModel, playlistToDelete: VideoPlaylistModel) { 89async function processDeleteVideoPlaylist (actor: MActor, playlistToDelete: VideoPlaylistModel) {
88 logger.debug('Removing remote video playlist "%s".', playlistToDelete.uuid) 90 logger.debug('Removing remote video playlist "%s".', playlistToDelete.uuid)
89 91
90 await sequelizeTypescript.transaction(async t => { 92 await sequelizeTypescript.transaction(async t => {
@@ -98,7 +100,7 @@ async function processDeleteVideoPlaylist (actor: ActorModel, playlistToDelete:
98 logger.info('Remote video playlist with uuid %s removed.', playlistToDelete.uuid) 100 logger.info('Remote video playlist with uuid %s removed.', playlistToDelete.uuid)
99} 101}
100 102
101async function processDeleteAccount (accountToRemove: AccountModel) { 103async function processDeleteAccount (accountToRemove: MAccountActor) {
102 logger.debug('Removing remote account "%s".', accountToRemove.Actor.url) 104 logger.debug('Removing remote account "%s".', accountToRemove.Actor.url)
103 105
104 await sequelizeTypescript.transaction(async t => { 106 await sequelizeTypescript.transaction(async t => {
@@ -108,7 +110,7 @@ async function processDeleteAccount (accountToRemove: AccountModel) {
108 logger.info('Remote account %s removed.', accountToRemove.Actor.url) 110 logger.info('Remote account %s removed.', accountToRemove.Actor.url)
109} 111}
110 112
111async function processDeleteVideoChannel (videoChannelToRemove: VideoChannelModel) { 113async function processDeleteVideoChannel (videoChannelToRemove: MChannelActor) {
112 logger.debug('Removing remote video channel "%s".', videoChannelToRemove.Actor.url) 114 logger.debug('Removing remote video channel "%s".', videoChannelToRemove.Actor.url)
113 115
114 await sequelizeTypescript.transaction(async t => { 116 await sequelizeTypescript.transaction(async t => {
@@ -118,7 +120,7 @@ async function processDeleteVideoChannel (videoChannelToRemove: VideoChannelMode
118 logger.info('Remote video channel %s removed.', videoChannelToRemove.Actor.url) 120 logger.info('Remote video channel %s removed.', videoChannelToRemove.Actor.url)
119} 121}
120 122
121function processDeleteVideoComment (byActor: SignatureActorModel, videoComment: VideoCommentModel, activity: ActivityDelete) { 123function processDeleteVideoComment (byActor: MActorSignature, videoComment: VideoCommentModel, activity: ActivityDelete) {
122 logger.debug('Removing remote video comment "%s".', videoComment.url) 124 logger.debug('Removing remote video comment "%s".', videoComment.url)
123 125
124 return sequelizeTypescript.transaction(async t => { 126 return sequelizeTypescript.transaction(async t => {
diff --git a/server/lib/activitypub/process/process-dislike.ts b/server/lib/activitypub/process/process-dislike.ts
index 727fcfee0..debd8a67c 100644
--- a/server/lib/activitypub/process/process-dislike.ts
+++ b/server/lib/activitypub/process/process-dislike.ts
@@ -7,7 +7,7 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos'
7import { forwardVideoRelatedActivity } from '../send/utils' 7import { forwardVideoRelatedActivity } from '../send/utils'
8import { getVideoDislikeActivityPubUrl } from '../url' 8import { getVideoDislikeActivityPubUrl } from '../url'
9import { APProcessorOptions } from '../../../typings/activitypub-processor.model' 9import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
10import { SignatureActorModel } from '../../../typings/models' 10import { MActorSignature } from '../../../typings/models'
11 11
12async function processDislikeActivity (options: APProcessorOptions<ActivityCreate | ActivityDislike>) { 12async function processDislikeActivity (options: APProcessorOptions<ActivityCreate | ActivityDislike>) {
13 const { activity, byActor } = options 13 const { activity, byActor } = options
@@ -22,7 +22,7 @@ export {
22 22
23// --------------------------------------------------------------------------- 23// ---------------------------------------------------------------------------
24 24
25async function processDislike (activity: ActivityCreate | ActivityDislike, byActor: SignatureActorModel) { 25async function processDislike (activity: ActivityCreate | ActivityDislike, byActor: MActorSignature) {
26 const dislikeObject = activity.type === 'Dislike' ? activity.object : (activity.object as DislikeObject).object 26 const dislikeObject = activity.type === 'Dislike' ? activity.object : (activity.object as DislikeObject).object
27 const byAccount = byActor.Account 27 const byAccount = byActor.Account
28 28
diff --git a/server/lib/activitypub/process/process-flag.ts b/server/lib/activitypub/process/process-flag.ts
index 1f8a80c14..e6e9084de 100644
--- a/server/lib/activitypub/process/process-flag.ts
+++ b/server/lib/activitypub/process/process-flag.ts
@@ -8,7 +8,7 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos'
8import { Notifier } from '../../notifier' 8import { Notifier } from '../../notifier'
9import { getAPId } from '../../../helpers/activitypub' 9import { getAPId } from '../../../helpers/activitypub'
10import { APProcessorOptions } from '../../../typings/activitypub-processor.model' 10import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
11import { SignatureActorModel } from '../../../typings/models' 11import { MActorSignature, MVideoAbuseVideo } from '../../../typings/models'
12 12
13async function processFlagActivity (options: APProcessorOptions<ActivityCreate | ActivityFlag>) { 13async function processFlagActivity (options: APProcessorOptions<ActivityCreate | ActivityFlag>) {
14 const { activity, byActor } = options 14 const { activity, byActor } = options
@@ -23,31 +23,39 @@ export {
23 23
24// --------------------------------------------------------------------------- 24// ---------------------------------------------------------------------------
25 25
26async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag, byActor: SignatureActorModel) { 26async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag, byActor: MActorSignature) {
27 const flag = activity.type === 'Flag' ? activity : (activity.object as VideoAbuseObject) 27 const flag = activity.type === 'Flag' ? activity : (activity.object as VideoAbuseObject)
28 28
29 logger.debug('Reporting remote abuse for video %s.', getAPId(flag.object))
30
31 const account = byActor.Account 29 const account = byActor.Account
32 if (!account) throw new Error('Cannot create video abuse with the non account actor ' + byActor.url) 30 if (!account) throw new Error('Cannot create video abuse with the non account actor ' + byActor.url)
33 31
34 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: flag.object }) 32 const objects = Array.isArray(flag.object) ? flag.object : [ flag.object ]
35 33
36 const videoAbuse = await sequelizeTypescript.transaction(async t => { 34 for (const object of objects) {
37 const videoAbuseData = { 35 try {
38 reporterAccountId: account.id, 36 logger.debug('Reporting remote abuse for video %s.', getAPId(object))
39 reason: flag.content, 37
40 videoId: video.id, 38 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: object })
41 state: VideoAbuseState.PENDING
42 }
43 39
44 const videoAbuseInstance = await VideoAbuseModel.create(videoAbuseData, { transaction: t }) 40 const videoAbuse = await sequelizeTypescript.transaction(async t => {
45 videoAbuseInstance.Video = video 41 const videoAbuseData = {
42 reporterAccountId: account.id,
43 reason: flag.content,
44 videoId: video.id,
45 state: VideoAbuseState.PENDING
46 }
46 47
47 logger.info('Remote abuse for video uuid %s created', flag.object) 48 const videoAbuseInstance = await VideoAbuseModel.create(videoAbuseData, { transaction: t }) as MVideoAbuseVideo
49 videoAbuseInstance.Video = video
48 50
49 return videoAbuseInstance 51 logger.info('Remote abuse for video uuid %s created', flag.object)
50 })
51 52
52 Notifier.Instance.notifyOnNewVideoAbuse(videoAbuse) 53 return videoAbuseInstance
54 })
55
56 Notifier.Instance.notifyOnNewVideoAbuse(videoAbuse)
57 } catch (err) {
58 logger.debug('Cannot process report of %s. (Maybe not a video abuse).', getAPId(object), { err })
59 }
60 }
53} 61}
diff --git a/server/lib/activitypub/process/process-follow.ts b/server/lib/activitypub/process/process-follow.ts
index 240aa5799..85f22d654 100644
--- a/server/lib/activitypub/process/process-follow.ts
+++ b/server/lib/activitypub/process/process-follow.ts
@@ -10,8 +10,8 @@ import { getAPId } from '../../../helpers/activitypub'
10import { getServerActor } from '../../../helpers/utils' 10import { getServerActor } from '../../../helpers/utils'
11import { CONFIG } from '../../../initializers/config' 11import { CONFIG } from '../../../initializers/config'
12import { APProcessorOptions } from '../../../typings/activitypub-processor.model' 12import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
13import { SignatureActorModel } from '../../../typings/models' 13import { MActorFollowActors, MActorSignature } from '../../../typings/models'
14import { ActorFollowModelLight } from '../../../typings/models/actor-follow' 14import { autoFollowBackIfNeeded } from '../follow'
15 15
16async function processFollowActivity (options: APProcessorOptions<ActivityFollow>) { 16async function processFollowActivity (options: APProcessorOptions<ActivityFollow>) {
17 const { activity, byActor } = options 17 const { activity, byActor } = options
@@ -28,8 +28,8 @@ export {
28 28
29// --------------------------------------------------------------------------- 29// ---------------------------------------------------------------------------
30 30
31async function processFollow (byActor: SignatureActorModel, targetActorURL: string) { 31async function processFollow (byActor: MActorSignature, targetActorURL: string) {
32 const { actorFollow, created, isFollowingInstance } = await sequelizeTypescript.transaction(async t => { 32 const { actorFollow, created, isFollowingInstance, targetActor } = await sequelizeTypescript.transaction(async t => {
33 const targetActor = await ActorModel.loadByUrlAndPopulateAccountAndChannel(targetActorURL, t) 33 const targetActor = await ActorModel.loadByUrlAndPopulateAccountAndChannel(targetActorURL, t)
34 34
35 if (!targetActor) throw new Error('Unknown actor') 35 if (!targetActor) throw new Error('Unknown actor')
@@ -43,10 +43,10 @@ async function processFollow (byActor: SignatureActorModel, targetActorURL: stri
43 43
44 await sendReject(byActor, targetActor) 44 await sendReject(byActor, targetActor)
45 45
46 return { actorFollow: undefined } 46 return { actorFollow: undefined as MActorFollowActors }
47 } 47 }
48 48
49 const [ actorFollow, created ] = await ActorFollowModel.findOrCreate({ 49 const [ actorFollow, created ] = await ActorFollowModel.findOrCreate<MActorFollowActors>({
50 where: { 50 where: {
51 actorId: byActor.id, 51 actorId: byActor.id,
52 targetActorId: targetActor.id 52 targetActorId: targetActor.id
@@ -57,7 +57,7 @@ async function processFollow (byActor: SignatureActorModel, targetActorURL: stri
57 state: CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL ? 'pending' : 'accepted' 57 state: CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL ? 'pending' : 'accepted'
58 }, 58 },
59 transaction: t 59 transaction: t
60 }) as [ ActorFollowModelLight, boolean ] 60 })
61 61
62 if (actorFollow.state !== 'accepted' && CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL === false) { 62 if (actorFollow.state !== 'accepted' && CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL === false) {
63 actorFollow.state = 'accepted' 63 actorFollow.state = 'accepted'
@@ -68,17 +68,26 @@ async function processFollow (byActor: SignatureActorModel, targetActorURL: stri
68 actorFollow.ActorFollowing = targetActor 68 actorFollow.ActorFollowing = targetActor
69 69
70 // Target sends to actor he accepted the follow request 70 // Target sends to actor he accepted the follow request
71 if (actorFollow.state === 'accepted') await sendAccept(actorFollow) 71 if (actorFollow.state === 'accepted') {
72 await sendAccept(actorFollow)
73 await autoFollowBackIfNeeded(actorFollow)
74 }
72 75
73 return { actorFollow, created, isFollowingInstance } 76 return { actorFollow, created, isFollowingInstance, targetActor }
74 }) 77 })
75 78
76 // Rejected 79 // Rejected
77 if (!actorFollow) return 80 if (!actorFollow) return
78 81
79 if (created) { 82 if (created) {
80 if (isFollowingInstance) Notifier.Instance.notifyOfNewInstanceFollow(actorFollow) 83 const follower = await ActorModel.loadFull(byActor.id)
81 else Notifier.Instance.notifyOfNewUserFollow(actorFollow) 84 const actorFollowFull = Object.assign(actorFollow, { ActorFollowing: targetActor, ActorFollower: follower })
85
86 if (isFollowingInstance) {
87 Notifier.Instance.notifyOfNewInstanceFollow(actorFollowFull)
88 } else {
89 Notifier.Instance.notifyOfNewUserFollow(actorFollowFull)
90 }
82 } 91 }
83 92
84 logger.info('Actor %s is followed by actor %s.', targetActorURL, byActor.url) 93 logger.info('Actor %s is followed by actor %s.', targetActorURL, byActor.url)
diff --git a/server/lib/activitypub/process/process-like.ts b/server/lib/activitypub/process/process-like.ts
index cf559af72..62be0de42 100644
--- a/server/lib/activitypub/process/process-like.ts
+++ b/server/lib/activitypub/process/process-like.ts
@@ -7,7 +7,7 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos'
7import { getVideoLikeActivityPubUrl } from '../url' 7import { getVideoLikeActivityPubUrl } from '../url'
8import { getAPId } from '../../../helpers/activitypub' 8import { getAPId } from '../../../helpers/activitypub'
9import { APProcessorOptions } from '../../../typings/activitypub-processor.model' 9import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
10import { SignatureActorModel } from '../../../typings/models' 10import { MActorSignature } from '../../../typings/models'
11 11
12async function processLikeActivity (options: APProcessorOptions<ActivityLike>) { 12async function processLikeActivity (options: APProcessorOptions<ActivityLike>) {
13 const { activity, byActor } = options 13 const { activity, byActor } = options
@@ -22,7 +22,7 @@ export {
22 22
23// --------------------------------------------------------------------------- 23// ---------------------------------------------------------------------------
24 24
25async function processLikeVideo (byActor: SignatureActorModel, activity: ActivityLike) { 25async function processLikeVideo (byActor: MActorSignature, activity: ActivityLike) {
26 const videoUrl = getAPId(activity.object) 26 const videoUrl = getAPId(activity.object)
27 27
28 const byAccount = byActor.Account 28 const byAccount = byActor.Account
diff --git a/server/lib/activitypub/process/process-reject.ts b/server/lib/activitypub/process/process-reject.ts
index 22e311ceb..00e9afa10 100644
--- a/server/lib/activitypub/process/process-reject.ts
+++ b/server/lib/activitypub/process/process-reject.ts
@@ -2,7 +2,7 @@ import { ActivityReject } from '../../../../shared/models/activitypub/activity'
2import { sequelizeTypescript } from '../../../initializers' 2import { sequelizeTypescript } from '../../../initializers'
3import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 3import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
4import { APProcessorOptions } from '../../../typings/activitypub-processor.model' 4import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
5import { ActorModelOnly } from '../../../typings/models' 5import { MActor } from '../../../typings/models'
6 6
7async function processRejectActivity (options: APProcessorOptions<ActivityReject>) { 7async function processRejectActivity (options: APProcessorOptions<ActivityReject>) {
8 const { byActor: targetActor, inboxActor } = options 8 const { byActor: targetActor, inboxActor } = options
@@ -19,7 +19,7 @@ export {
19 19
20// --------------------------------------------------------------------------- 20// ---------------------------------------------------------------------------
21 21
22async function processReject (follower: ActorModelOnly, targetActor: ActorModelOnly) { 22async function processReject (follower: MActor, targetActor: MActor) {
23 return sequelizeTypescript.transaction(async t => { 23 return sequelizeTypescript.transaction(async t => {
24 const actorFollow = await ActorFollowModel.loadByActorAndTarget(follower.id, targetActor.id, t) 24 const actorFollow = await ActorFollowModel.loadByActorAndTarget(follower.id, targetActor.id, t)
25 25
diff --git a/server/lib/activitypub/process/process-undo.ts b/server/lib/activitypub/process/process-undo.ts
index c37ee38bb..10643b2e9 100644
--- a/server/lib/activitypub/process/process-undo.ts
+++ b/server/lib/activitypub/process/process-undo.ts
@@ -11,7 +11,7 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos'
11import { VideoShareModel } from '../../../models/video/video-share' 11import { VideoShareModel } from '../../../models/video/video-share'
12import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' 12import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
13import { APProcessorOptions } from '../../../typings/activitypub-processor.model' 13import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
14import { SignatureActorModel } from '../../../typings/models' 14import { MActorSignature } from '../../../typings/models'
15 15
16async function processUndoActivity (options: APProcessorOptions<ActivityUndo>) { 16async function processUndoActivity (options: APProcessorOptions<ActivityUndo>) {
17 const { activity, byActor } = options 17 const { activity, byActor } = options
@@ -54,7 +54,7 @@ export {
54 54
55// --------------------------------------------------------------------------- 55// ---------------------------------------------------------------------------
56 56
57async function processUndoLike (byActor: SignatureActorModel, activity: ActivityUndo) { 57async function processUndoLike (byActor: MActorSignature, activity: ActivityUndo) {
58 const likeActivity = activity.object as ActivityLike 58 const likeActivity = activity.object as ActivityLike
59 59
60 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: likeActivity.object }) 60 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: likeActivity.object })
@@ -77,7 +77,7 @@ async function processUndoLike (byActor: SignatureActorModel, activity: Activity
77 }) 77 })
78} 78}
79 79
80async function processUndoDislike (byActor: SignatureActorModel, activity: ActivityUndo) { 80async function processUndoDislike (byActor: MActorSignature, activity: ActivityUndo) {
81 const dislike = activity.object.type === 'Dislike' 81 const dislike = activity.object.type === 'Dislike'
82 ? activity.object 82 ? activity.object
83 : activity.object.object as DislikeObject 83 : activity.object.object as DislikeObject
@@ -102,7 +102,7 @@ async function processUndoDislike (byActor: SignatureActorModel, activity: Activ
102 }) 102 })
103} 103}
104 104
105async function processUndoCacheFile (byActor: SignatureActorModel, activity: ActivityUndo) { 105async function processUndoCacheFile (byActor: MActorSignature, activity: ActivityUndo) {
106 const cacheFileObject = activity.object.object as CacheFileObject 106 const cacheFileObject = activity.object.object as CacheFileObject
107 107
108 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFileObject.object }) 108 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFileObject.object })
@@ -127,7 +127,7 @@ async function processUndoCacheFile (byActor: SignatureActorModel, activity: Act
127 }) 127 })
128} 128}
129 129
130function processUndoFollow (follower: SignatureActorModel, followActivity: ActivityFollow) { 130function processUndoFollow (follower: MActorSignature, followActivity: ActivityFollow) {
131 return sequelizeTypescript.transaction(async t => { 131 return sequelizeTypescript.transaction(async t => {
132 const following = await ActorModel.loadByUrlAndPopulateAccountAndChannel(followActivity.object, t) 132 const following = await ActorModel.loadByUrlAndPopulateAccountAndChannel(followActivity.object, t)
133 const actorFollow = await ActorFollowModel.loadByActorAndTarget(follower.id, following.id, t) 133 const actorFollow = await ActorFollowModel.loadByActorAndTarget(follower.id, following.id, t)
@@ -140,7 +140,7 @@ function processUndoFollow (follower: SignatureActorModel, followActivity: Activ
140 }) 140 })
141} 141}
142 142
143function processUndoAnnounce (byActor: SignatureActorModel, announceActivity: ActivityAnnounce) { 143function processUndoAnnounce (byActor: MActorSignature, announceActivity: ActivityAnnounce) {
144 return sequelizeTypescript.transaction(async t => { 144 return sequelizeTypescript.transaction(async t => {
145 const share = await VideoShareModel.loadByUrl(announceActivity.id, t) 145 const share = await VideoShareModel.loadByUrl(announceActivity.id, t)
146 if (!share) throw new Error(`Unknown video share ${announceActivity.id}.`) 146 if (!share) throw new Error(`Unknown video share ${announceActivity.id}.`)
diff --git a/server/lib/activitypub/process/process-update.ts b/server/lib/activitypub/process/process-update.ts
index 414f9e375..a47d605d8 100644
--- a/server/lib/activitypub/process/process-update.ts
+++ b/server/lib/activitypub/process/process-update.ts
@@ -15,7 +15,7 @@ import { forwardVideoRelatedActivity } from '../send/utils'
15import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object' 15import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object'
16import { createOrUpdateVideoPlaylist } from '../playlist' 16import { createOrUpdateVideoPlaylist } from '../playlist'
17import { APProcessorOptions } from '../../../typings/activitypub-processor.model' 17import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
18import { SignatureActorModel } from '../../../typings/models' 18import { MActorSignature, MAccountIdActor } from '../../../typings/models'
19 19
20async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate>) { 20async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate>) {
21 const { activity, byActor } = options 21 const { activity, byActor } = options
@@ -53,7 +53,7 @@ export {
53 53
54// --------------------------------------------------------------------------- 54// ---------------------------------------------------------------------------
55 55
56async function processUpdateVideo (actor: SignatureActorModel, activity: ActivityUpdate) { 56async function processUpdateVideo (actor: MActorSignature, activity: ActivityUpdate) {
57 const videoObject = activity.object as VideoTorrentObject 57 const videoObject = activity.object as VideoTorrentObject
58 58
59 if (sanitizeAndCheckVideoTorrentObject(videoObject) === false) { 59 if (sanitizeAndCheckVideoTorrentObject(videoObject) === false) {
@@ -61,20 +61,23 @@ async function processUpdateVideo (actor: SignatureActorModel, activity: Activit
61 return undefined 61 return undefined
62 } 62 }
63 63
64 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoObject.id, allowRefresh: false }) 64 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoObject.id, allowRefresh: false, fetchType: 'all' })
65 const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject) 65 const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject)
66 66
67 const account = actor.Account as MAccountIdActor
68 account.Actor = actor
69
67 const updateOptions = { 70 const updateOptions = {
68 video, 71 video,
69 videoObject, 72 videoObject,
70 account: actor.Account, 73 account,
71 channel: channelActor.VideoChannel, 74 channel: channelActor.VideoChannel,
72 overrideTo: activity.to 75 overrideTo: activity.to
73 } 76 }
74 return updateVideoFromAP(updateOptions) 77 return updateVideoFromAP(updateOptions)
75} 78}
76 79
77async function processUpdateCacheFile (byActor: SignatureActorModel, activity: ActivityUpdate) { 80async function processUpdateCacheFile (byActor: MActorSignature, activity: ActivityUpdate) {
78 const cacheFileObject = activity.object as CacheFileObject 81 const cacheFileObject = activity.object as CacheFileObject
79 82
80 if (!isCacheFileObjectValid(cacheFileObject)) { 83 if (!isCacheFileObjectValid(cacheFileObject)) {
@@ -150,7 +153,7 @@ async function processUpdateActor (actor: ActorModel, activity: ActivityUpdate)
150 } 153 }
151} 154}
152 155
153async function processUpdatePlaylist (byActor: SignatureActorModel, activity: ActivityUpdate) { 156async function processUpdatePlaylist (byActor: MActorSignature, activity: ActivityUpdate) {
154 const playlistObject = activity.object as PlaylistObject 157 const playlistObject = activity.object as PlaylistObject
155 const byAccount = byActor.Account 158 const byAccount = byActor.Account
156 159
diff --git a/server/lib/activitypub/process/process-view.ts b/server/lib/activitypub/process/process-view.ts
index e4997b828..df29ee968 100644
--- a/server/lib/activitypub/process/process-view.ts
+++ b/server/lib/activitypub/process/process-view.ts
@@ -3,7 +3,7 @@ import { forwardVideoRelatedActivity } from '../send/utils'
3import { Redis } from '../../redis' 3import { Redis } from '../../redis'
4import { ActivityCreate, ActivityView, ViewObject } from '../../../../shared/models/activitypub' 4import { ActivityCreate, ActivityView, ViewObject } from '../../../../shared/models/activitypub'
5import { APProcessorOptions } from '../../../typings/activitypub-processor.model' 5import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
6import { SignatureActorModel } from '../../../typings/models' 6import { MActorSignature } from '../../../typings/models'
7 7
8async function processViewActivity (options: APProcessorOptions<ActivityCreate | ActivityView>) { 8async function processViewActivity (options: APProcessorOptions<ActivityCreate | ActivityView>) {
9 const { activity, byActor } = options 9 const { activity, byActor } = options
@@ -18,11 +18,11 @@ export {
18 18
19// --------------------------------------------------------------------------- 19// ---------------------------------------------------------------------------
20 20
21async function processCreateView (activity: ActivityView | ActivityCreate, byActor: SignatureActorModel) { 21async function processCreateView (activity: ActivityView | ActivityCreate, byActor: MActorSignature) {
22 const videoObject = activity.type === 'View' ? activity.object : (activity.object as ViewObject).object 22 const videoObject = activity.type === 'View' ? activity.object : (activity.object as ViewObject).object
23 23
24 const options = { 24 const options = {
25 videoObject: videoObject, 25 videoObject,
26 fetchType: 'only-video' as 'only-video' 26 fetchType: 'only-video' as 'only-video'
27 } 27 }
28 const { video } = await getOrCreateVideoAndAccountAndChannel(options) 28 const { video } = await getOrCreateVideoAndAccountAndChannel(options)
diff --git a/server/lib/activitypub/process/process.ts b/server/lib/activitypub/process/process.ts
index d108fe321..c602bf218 100644
--- a/server/lib/activitypub/process/process.ts
+++ b/server/lib/activitypub/process/process.ts
@@ -1,7 +1,6 @@
1import { Activity, ActivityType } from '../../../../shared/models/activitypub' 1import { Activity, ActivityType } from '../../../../shared/models/activitypub'
2import { checkUrlsSameHost, getAPId } from '../../../helpers/activitypub' 2import { checkUrlsSameHost, getAPId } from '../../../helpers/activitypub'
3import { logger } from '../../../helpers/logger' 3import { logger } from '../../../helpers/logger'
4import { ActorModel } from '../../../models/activitypub/actor'
5import { processAcceptActivity } from './process-accept' 4import { processAcceptActivity } from './process-accept'
6import { processAnnounceActivity } from './process-announce' 5import { processAnnounceActivity } from './process-announce'
7import { processCreateActivity } from './process-create' 6import { processCreateActivity } from './process-create'
@@ -16,7 +15,7 @@ import { processDislikeActivity } from './process-dislike'
16import { processFlagActivity } from './process-flag' 15import { processFlagActivity } from './process-flag'
17import { processViewActivity } from './process-view' 16import { processViewActivity } from './process-view'
18import { APProcessorOptions } from '../../../typings/activitypub-processor.model' 17import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
19import { SignatureActorModel } from '../../../typings/models' 18import { MActorDefault, MActorSignature } from '../../../typings/models'
20 19
21const processActivity: { [ P in ActivityType ]: (options: APProcessorOptions<Activity>) => Promise<any> } = { 20const processActivity: { [ P in ActivityType ]: (options: APProcessorOptions<Activity>) => Promise<any> } = {
22 Create: processCreateActivity, 21 Create: processCreateActivity,
@@ -36,15 +35,15 @@ const processActivity: { [ P in ActivityType ]: (options: APProcessorOptions<Act
36async function processActivities ( 35async function processActivities (
37 activities: Activity[], 36 activities: Activity[],
38 options: { 37 options: {
39 signatureActor?: SignatureActorModel 38 signatureActor?: MActorSignature
40 inboxActor?: ActorModel 39 inboxActor?: MActorDefault
41 outboxUrl?: string 40 outboxUrl?: string
42 fromFetch?: boolean 41 fromFetch?: boolean
43 } = {} 42 } = {}
44) { 43) {
45 const { outboxUrl, signatureActor, inboxActor, fromFetch = false } = options 44 const { outboxUrl, signatureActor, inboxActor, fromFetch = false } = options
46 45
47 const actorsCache: { [ url: string ]: SignatureActorModel } = {} 46 const actorsCache: { [ url: string ]: MActorSignature } = {}
48 47
49 for (const activity of activities) { 48 for (const activity of activities) {
50 if (!signatureActor && [ 'Create', 'Announce', 'Like' ].includes(activity.type) === false) { 49 if (!signatureActor && [ 'Create', 'Announce', 'Like' ].includes(activity.type) === false) {
@@ -75,7 +74,7 @@ async function processActivities (
75 } 74 }
76 75
77 try { 76 try {
78 await activityProcessor({ activity, byActor, inboxActor: inboxActor, fromFetch }) 77 await activityProcessor({ activity, byActor, inboxActor, fromFetch })
79 } catch (err) { 78 } catch (err) {
80 logger.warn('Cannot process activity %s.', activity.type, { err }) 79 logger.warn('Cannot process activity %s.', activity.type, { err })
81 } 80 }
diff --git a/server/lib/activitypub/send/send-accept.ts b/server/lib/activitypub/send/send-accept.ts
index 813c42e15..9f0225b64 100644
--- a/server/lib/activitypub/send/send-accept.ts
+++ b/server/lib/activitypub/send/send-accept.ts
@@ -3,10 +3,9 @@ import { getActorFollowAcceptActivityPubUrl, getActorFollowActivityPubUrl } from
3import { unicastTo } from './utils' 3import { unicastTo } from './utils'
4import { buildFollowActivity } from './send-follow' 4import { buildFollowActivity } from './send-follow'
5import { logger } from '../../../helpers/logger' 5import { logger } from '../../../helpers/logger'
6import { ActorFollowModelLight } from '../../../typings/models/actor-follow' 6import { MActor, MActorFollowActors } from '../../../typings/models'
7import { ActorModelOnly } from '../../../typings/models'
8 7
9async function sendAccept (actorFollow: ActorFollowModelLight) { 8async function sendAccept (actorFollow: MActorFollowActors) {
10 const follower = actorFollow.ActorFollower 9 const follower = actorFollow.ActorFollower
11 const me = actorFollow.ActorFollowing 10 const me = actorFollow.ActorFollowing
12 11
@@ -34,7 +33,7 @@ export {
34 33
35// --------------------------------------------------------------------------- 34// ---------------------------------------------------------------------------
36 35
37function buildAcceptActivity (url: string, byActor: ActorModelOnly, followActivityData: ActivityFollow): ActivityAccept { 36function buildAcceptActivity (url: string, byActor: MActor, followActivityData: ActivityFollow): ActivityAccept {
38 return { 37 return {
39 type: 'Accept', 38 type: 'Accept',
40 id: url, 39 id: url,
diff --git a/server/lib/activitypub/send/send-announce.ts b/server/lib/activitypub/send/send-announce.ts
index 7fe4ca180..a0f33852c 100644
--- a/server/lib/activitypub/send/send-announce.ts
+++ b/server/lib/activitypub/send/send-announce.ts
@@ -1,16 +1,15 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityAnnounce, ActivityAudience } from '../../../../shared/models/activitypub' 2import { ActivityAnnounce, ActivityAudience } from '../../../../shared/models/activitypub'
3import { VideoModel } from '../../../models/video/video'
4import { broadcastToFollowers } from './utils' 3import { broadcastToFollowers } from './utils'
5import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf } from '../audience' 4import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf } from '../audience'
6import { logger } from '../../../helpers/logger' 5import { logger } from '../../../helpers/logger'
7import { ActorModelOnly } from '../../../typings/models' 6import { MActorLight, MVideo } from '../../../typings/models'
8import { VideoShareModelOnly } from '../../../typings/models/video-share' 7import { MVideoShare } from '../../../typings/models/video'
9 8
10async function buildAnnounceWithVideoAudience ( 9async function buildAnnounceWithVideoAudience (
11 byActor: ActorModelOnly, 10 byActor: MActorLight,
12 videoShare: VideoShareModelOnly, 11 videoShare: MVideoShare,
13 video: VideoModel, 12 video: MVideo,
14 t: Transaction 13 t: Transaction
15) { 14) {
16 const announcedObject = video.url 15 const announcedObject = video.url
@@ -23,7 +22,7 @@ async function buildAnnounceWithVideoAudience (
23 return { activity, actorsInvolvedInVideo } 22 return { activity, actorsInvolvedInVideo }
24} 23}
25 24
26async function sendVideoAnnounce (byActor: ActorModelOnly, videoShare: VideoShareModelOnly, video: VideoModel, t: Transaction) { 25async function sendVideoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, t: Transaction) {
27 const { activity, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, t) 26 const { activity, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, t)
28 27
29 logger.info('Creating job to send announce %s.', videoShare.url) 28 logger.info('Creating job to send announce %s.', videoShare.url)
@@ -32,7 +31,7 @@ async function sendVideoAnnounce (byActor: ActorModelOnly, videoShare: VideoShar
32 return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, t, followersException) 31 return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, t, followersException)
33} 32}
34 33
35function buildAnnounceActivity (url: string, byActor: ActorModelOnly, object: string, audience?: ActivityAudience): ActivityAnnounce { 34function buildAnnounceActivity (url: string, byActor: MActorLight, object: string, audience?: ActivityAudience): ActivityAnnounce {
36 if (!audience) audience = getAudience(byActor) 35 if (!audience) audience = getAudience(byActor)
37 36
38 return audiencify({ 37 return audiencify({
diff --git a/server/lib/activitypub/send/send-create.ts b/server/lib/activitypub/send/send-create.ts
index 9c21149f2..26ec3e948 100644
--- a/server/lib/activitypub/send/send-create.ts
+++ b/server/lib/activitypub/send/send-create.ts
@@ -1,19 +1,23 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub' 2import { ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub'
3import { VideoPrivacy } from '../../../../shared/models/videos' 3import { VideoPrivacy } from '../../../../shared/models/videos'
4import { ActorModel } from '../../../models/activitypub/actor'
5import { VideoModel } from '../../../models/video/video'
6import { VideoCommentModel } from '../../../models/video/video-comment' 4import { VideoCommentModel } from '../../../models/video/video-comment'
7import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils' 5import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils'
8import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf, getVideoCommentAudience } from '../audience' 6import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf, getVideoCommentAudience } from '../audience'
9import { logger } from '../../../helpers/logger' 7import { logger } from '../../../helpers/logger'
10import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
11import { VideoPlaylistModel } from '../../../models/video/video-playlist'
12import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' 8import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
13import { getServerActor } from '../../../helpers/utils' 9import { getServerActor } from '../../../helpers/utils'
14import * as Bluebird from 'bluebird' 10import {
15 11 MActorLight,
16async function sendCreateVideo (video: VideoModel, t: Transaction) { 12 MCommentOwnerVideo,
13 MVideoAccountLight,
14 MVideoAP,
15 MVideoPlaylistFull,
16 MVideoRedundancyFileVideo,
17 MVideoRedundancyStreamingPlaylistVideo
18} from '../../../typings/models'
19
20async function sendCreateVideo (video: MVideoAP, t: Transaction) {
17 if (video.privacy === VideoPrivacy.PRIVATE) return undefined 21 if (video.privacy === VideoPrivacy.PRIVATE) return undefined
18 22
19 logger.info('Creating job to send video creation of %s.', video.url) 23 logger.info('Creating job to send video creation of %s.', video.url)
@@ -27,7 +31,11 @@ async function sendCreateVideo (video: VideoModel, t: Transaction) {
27 return broadcastToFollowers(createActivity, byActor, [ byActor ], t) 31 return broadcastToFollowers(createActivity, byActor, [ byActor ], t)
28} 32}
29 33
30async function sendCreateCacheFile (byActor: ActorModel, video: VideoModel, fileRedundancy: VideoRedundancyModel) { 34async function sendCreateCacheFile (
35 byActor: MActorLight,
36 video: MVideoAccountLight,
37 fileRedundancy: MVideoRedundancyStreamingPlaylistVideo | MVideoRedundancyFileVideo
38) {
31 logger.info('Creating job to send file cache of %s.', fileRedundancy.url) 39 logger.info('Creating job to send file cache of %s.', fileRedundancy.url)
32 40
33 return sendVideoRelatedCreateActivity({ 41 return sendVideoRelatedCreateActivity({
@@ -38,7 +46,7 @@ async function sendCreateCacheFile (byActor: ActorModel, video: VideoModel, file
38 }) 46 })
39} 47}
40 48
41async function sendCreateVideoPlaylist (playlist: VideoPlaylistModel, t: Transaction) { 49async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, t: Transaction) {
42 if (playlist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined 50 if (playlist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined
43 51
44 logger.info('Creating job to send create video playlist of %s.', playlist.url) 52 logger.info('Creating job to send create video playlist of %s.', playlist.url)
@@ -57,7 +65,7 @@ async function sendCreateVideoPlaylist (playlist: VideoPlaylistModel, t: Transac
57 return broadcastToFollowers(createActivity, byActor, toFollowersOf, t) 65 return broadcastToFollowers(createActivity, byActor, toFollowersOf, t)
58} 66}
59 67
60async function sendCreateVideoComment (comment: VideoCommentModel, t: Transaction) { 68async function sendCreateVideoComment (comment: MCommentOwnerVideo, t: Transaction) {
61 logger.info('Creating job to send comment %s.', comment.url) 69 logger.info('Creating job to send comment %s.', comment.url)
62 70
63 const isOrigin = comment.Video.isOwned() 71 const isOrigin = comment.Video.isOwned()
@@ -95,7 +103,7 @@ async function sendCreateVideoComment (comment: VideoCommentModel, t: Transactio
95 t.afterCommit(() => unicastTo(createActivity, byActor, comment.Video.VideoChannel.Account.Actor.sharedInboxUrl)) 103 t.afterCommit(() => unicastTo(createActivity, byActor, comment.Video.VideoChannel.Account.Actor.sharedInboxUrl))
96} 104}
97 105
98function buildCreateActivity (url: string, byActor: ActorModel, object: any, audience?: ActivityAudience): ActivityCreate { 106function buildCreateActivity (url: string, byActor: MActorLight, object: any, audience?: ActivityAudience): ActivityCreate {
99 if (!audience) audience = getAudience(byActor) 107 if (!audience) audience = getAudience(byActor)
100 108
101 return audiencify( 109 return audiencify(
@@ -122,8 +130,8 @@ export {
122// --------------------------------------------------------------------------- 130// ---------------------------------------------------------------------------
123 131
124async function sendVideoRelatedCreateActivity (options: { 132async function sendVideoRelatedCreateActivity (options: {
125 byActor: ActorModel, 133 byActor: MActorLight,
126 video: VideoModel, 134 video: MVideoAccountLight,
127 url: string, 135 url: string,
128 object: any, 136 object: any,
129 transaction?: Transaction 137 transaction?: Transaction
diff --git a/server/lib/activitypub/send/send-delete.ts b/server/lib/activitypub/send/send-delete.ts
index 6c7fb8449..4b1ff8dc5 100644
--- a/server/lib/activitypub/send/send-delete.ts
+++ b/server/lib/activitypub/send/send-delete.ts
@@ -1,17 +1,17 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityAudience, ActivityDelete } from '../../../../shared/models/activitypub' 2import { ActivityAudience, ActivityDelete } from '../../../../shared/models/activitypub'
3import { ActorModel } from '../../../models/activitypub/actor' 3import { ActorModel } from '../../../models/activitypub/actor'
4import { VideoModel } from '../../../models/video/video'
5import { VideoCommentModel } from '../../../models/video/video-comment' 4import { VideoCommentModel } from '../../../models/video/video-comment'
6import { VideoShareModel } from '../../../models/video/video-share' 5import { VideoShareModel } from '../../../models/video/video-share'
7import { getDeleteActivityPubUrl } from '../url' 6import { getDeleteActivityPubUrl } from '../url'
8import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils' 7import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils'
9import { audiencify, getActorsInvolvedInVideo, getVideoCommentAudience } from '../audience' 8import { audiencify, getActorsInvolvedInVideo, getVideoCommentAudience } from '../audience'
10import { logger } from '../../../helpers/logger' 9import { logger } from '../../../helpers/logger'
11import { VideoPlaylistModel } from '../../../models/video/video-playlist'
12import { getServerActor } from '../../../helpers/utils' 10import { getServerActor } from '../../../helpers/utils'
11import { MCommentOwnerVideoReply, MVideoAccountLight, MVideoPlaylistFullSummary } from '../../../typings/models/video'
12import { MActorUrl } from '../../../typings/models'
13 13
14async function sendDeleteVideo (video: VideoModel, transaction: Transaction) { 14async function sendDeleteVideo (video: MVideoAccountLight, transaction: Transaction) {
15 logger.info('Creating job to broadcast delete of video %s.', video.url) 15 logger.info('Creating job to broadcast delete of video %s.', video.url)
16 16
17 const byActor = video.VideoChannel.Account.Actor 17 const byActor = video.VideoChannel.Account.Actor
@@ -42,7 +42,7 @@ async function sendDeleteActor (byActor: ActorModel, t: Transaction) {
42 return broadcastToFollowers(activity, byActor, actorsInvolved, t) 42 return broadcastToFollowers(activity, byActor, actorsInvolved, t)
43} 43}
44 44
45async function sendDeleteVideoComment (videoComment: VideoCommentModel, t: Transaction) { 45async function sendDeleteVideoComment (videoComment: MCommentOwnerVideoReply, t: Transaction) {
46 logger.info('Creating job to send delete of comment %s.', videoComment.url) 46 logger.info('Creating job to send delete of comment %s.', videoComment.url)
47 47
48 const isVideoOrigin = videoComment.Video.isOwned() 48 const isVideoOrigin = videoComment.Video.isOwned()
@@ -74,7 +74,7 @@ async function sendDeleteVideoComment (videoComment: VideoCommentModel, t: Trans
74 t.afterCommit(() => unicastTo(activity, byActor, videoComment.Video.VideoChannel.Account.Actor.sharedInboxUrl)) 74 t.afterCommit(() => unicastTo(activity, byActor, videoComment.Video.VideoChannel.Account.Actor.sharedInboxUrl))
75} 75}
76 76
77async function sendDeleteVideoPlaylist (videoPlaylist: VideoPlaylistModel, t: Transaction) { 77async function sendDeleteVideoPlaylist (videoPlaylist: MVideoPlaylistFullSummary, t: Transaction) {
78 logger.info('Creating job to send delete of playlist %s.', videoPlaylist.url) 78 logger.info('Creating job to send delete of playlist %s.', videoPlaylist.url)
79 79
80 const byActor = videoPlaylist.OwnerAccount.Actor 80 const byActor = videoPlaylist.OwnerAccount.Actor
@@ -101,7 +101,7 @@ export {
101 101
102// --------------------------------------------------------------------------- 102// ---------------------------------------------------------------------------
103 103
104function buildDeleteActivity (url: string, object: string, byActor: ActorModel, audience?: ActivityAudience): ActivityDelete { 104function buildDeleteActivity (url: string, object: string, byActor: MActorUrl, audience?: ActivityAudience): ActivityDelete {
105 const activity = { 105 const activity = {
106 type: 'Delete' as 'Delete', 106 type: 'Delete' as 'Delete',
107 id: url, 107 id: url,
diff --git a/server/lib/activitypub/send/send-dislike.ts b/server/lib/activitypub/send/send-dislike.ts
index a88436f2c..6e41f241f 100644
--- a/server/lib/activitypub/send/send-dislike.ts
+++ b/server/lib/activitypub/send/send-dislike.ts
@@ -1,13 +1,12 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActorModel } from '../../../models/activitypub/actor'
3import { VideoModel } from '../../../models/video/video'
4import { getVideoDislikeActivityPubUrl } from '../url' 2import { getVideoDislikeActivityPubUrl } from '../url'
5import { logger } from '../../../helpers/logger' 3import { logger } from '../../../helpers/logger'
6import { ActivityAudience, ActivityDislike } from '../../../../shared/models/activitypub' 4import { ActivityAudience, ActivityDislike } from '../../../../shared/models/activitypub'
7import { sendVideoRelatedActivity } from './utils' 5import { sendVideoRelatedActivity } from './utils'
8import { audiencify, getAudience } from '../audience' 6import { audiencify, getAudience } from '../audience'
7import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../typings/models'
9 8
10async function sendDislike (byActor: ActorModel, video: VideoModel, t: Transaction) { 9async function sendDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) {
11 logger.info('Creating job to dislike %s.', video.url) 10 logger.info('Creating job to dislike %s.', video.url)
12 11
13 const activityBuilder = (audience: ActivityAudience) => { 12 const activityBuilder = (audience: ActivityAudience) => {
@@ -19,7 +18,7 @@ async function sendDislike (byActor: ActorModel, video: VideoModel, t: Transacti
19 return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t }) 18 return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t })
20} 19}
21 20
22function buildDislikeActivity (url: string, byActor: ActorModel, video: VideoModel, audience?: ActivityAudience): ActivityDislike { 21function buildDislikeActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityDislike {
23 if (!audience) audience = getAudience(byActor) 22 if (!audience) audience = getAudience(byActor)
24 23
25 return audiencify( 24 return audiencify(
diff --git a/server/lib/activitypub/send/send-flag.ts b/server/lib/activitypub/send/send-flag.ts
index 61ee389a6..5ae1614ab 100644
--- a/server/lib/activitypub/send/send-flag.ts
+++ b/server/lib/activitypub/send/send-flag.ts
@@ -1,14 +1,13 @@
1import { ActorModel } from '../../../models/activitypub/actor'
2import { VideoModel } from '../../../models/video/video'
3import { VideoAbuseModel } from '../../../models/video/video-abuse'
4import { getVideoAbuseActivityPubUrl } from '../url' 1import { getVideoAbuseActivityPubUrl } from '../url'
5import { unicastTo } from './utils' 2import { unicastTo } from './utils'
6import { logger } from '../../../helpers/logger' 3import { logger } from '../../../helpers/logger'
7import { ActivityAudience, ActivityFlag } from '../../../../shared/models/activitypub' 4import { ActivityAudience, ActivityFlag } from '../../../../shared/models/activitypub'
8import { audiencify, getAudience } from '../audience' 5import { audiencify, getAudience } from '../audience'
9import { Transaction } from 'sequelize' 6import { Transaction } from 'sequelize'
7import { MActor, MVideoFullLight } from '../../../typings/models'
8import { MVideoAbuseVideo } from '../../../typings/models/video'
10 9
11async function sendVideoAbuse (byActor: ActorModel, videoAbuse: VideoAbuseModel, video: VideoModel, t: Transaction) { 10async function sendVideoAbuse (byActor: MActor, videoAbuse: MVideoAbuseVideo, video: MVideoFullLight, t: Transaction) {
12 if (!video.VideoChannel.Account.Actor.serverId) return // Local user 11 if (!video.VideoChannel.Account.Actor.serverId) return // Local user
13 12
14 const url = getVideoAbuseActivityPubUrl(videoAbuse) 13 const url = getVideoAbuseActivityPubUrl(videoAbuse)
@@ -22,7 +21,7 @@ async function sendVideoAbuse (byActor: ActorModel, videoAbuse: VideoAbuseModel,
22 t.afterCommit(() => unicastTo(flagActivity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)) 21 t.afterCommit(() => unicastTo(flagActivity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl))
23} 22}
24 23
25function buildFlagActivity (url: string, byActor: ActorModel, videoAbuse: VideoAbuseModel, audience: ActivityAudience): ActivityFlag { 24function buildFlagActivity (url: string, byActor: MActor, videoAbuse: MVideoAbuseVideo, audience: ActivityAudience): ActivityFlag {
26 if (!audience) audience = getAudience(byActor) 25 if (!audience) audience = getAudience(byActor)
27 26
28 const activity = Object.assign( 27 const activity = Object.assign(
diff --git a/server/lib/activitypub/send/send-follow.ts b/server/lib/activitypub/send/send-follow.ts
index a59ed50cf..ce400d8ff 100644
--- a/server/lib/activitypub/send/send-follow.ts
+++ b/server/lib/activitypub/send/send-follow.ts
@@ -1,12 +1,11 @@
1import { ActivityFollow } from '../../../../shared/models/activitypub' 1import { ActivityFollow } from '../../../../shared/models/activitypub'
2import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
3import { getActorFollowActivityPubUrl } from '../url' 2import { getActorFollowActivityPubUrl } from '../url'
4import { unicastTo } from './utils' 3import { unicastTo } from './utils'
5import { logger } from '../../../helpers/logger' 4import { logger } from '../../../helpers/logger'
6import { Transaction } from 'sequelize' 5import { Transaction } from 'sequelize'
7import { ActorModelOnly } from '../../../typings/models' 6import { MActor, MActorFollowActors } from '../../../typings/models'
8 7
9function sendFollow (actorFollow: ActorFollowModel, t: Transaction) { 8function sendFollow (actorFollow: MActorFollowActors, t: Transaction) {
10 const me = actorFollow.ActorFollower 9 const me = actorFollow.ActorFollower
11 const following = actorFollow.ActorFollowing 10 const following = actorFollow.ActorFollowing
12 11
@@ -21,7 +20,7 @@ function sendFollow (actorFollow: ActorFollowModel, t: Transaction) {
21 t.afterCommit(() => unicastTo(data, me, following.inboxUrl)) 20 t.afterCommit(() => unicastTo(data, me, following.inboxUrl))
22} 21}
23 22
24function buildFollowActivity (url: string, byActor: ActorModelOnly, targetActor: ActorModelOnly): ActivityFollow { 23function buildFollowActivity (url: string, byActor: MActor, targetActor: MActor): ActivityFollow {
25 return { 24 return {
26 type: 'Follow', 25 type: 'Follow',
27 id: url, 26 id: url,
diff --git a/server/lib/activitypub/send/send-like.ts b/server/lib/activitypub/send/send-like.ts
index 35227887a..e84a6f98b 100644
--- a/server/lib/activitypub/send/send-like.ts
+++ b/server/lib/activitypub/send/send-like.ts
@@ -1,13 +1,12 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityAudience, ActivityLike } from '../../../../shared/models/activitypub' 2import { ActivityAudience, ActivityLike } from '../../../../shared/models/activitypub'
3import { ActorModel } from '../../../models/activitypub/actor'
4import { VideoModel } from '../../../models/video/video'
5import { getVideoLikeActivityPubUrl } from '../url' 3import { getVideoLikeActivityPubUrl } from '../url'
6import { sendVideoRelatedActivity } from './utils' 4import { sendVideoRelatedActivity } from './utils'
7import { audiencify, getAudience } from '../audience' 5import { audiencify, getAudience } from '../audience'
8import { logger } from '../../../helpers/logger' 6import { logger } from '../../../helpers/logger'
7import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../typings/models'
9 8
10async function sendLike (byActor: ActorModel, video: VideoModel, t: Transaction) { 9async function sendLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) {
11 logger.info('Creating job to like %s.', video.url) 10 logger.info('Creating job to like %s.', video.url)
12 11
13 const activityBuilder = (audience: ActivityAudience) => { 12 const activityBuilder = (audience: ActivityAudience) => {
@@ -19,7 +18,7 @@ async function sendLike (byActor: ActorModel, video: VideoModel, t: Transaction)
19 return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t }) 18 return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t })
20} 19}
21 20
22function buildLikeActivity (url: string, byActor: ActorModel, video: VideoModel, audience?: ActivityAudience): ActivityLike { 21function buildLikeActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityLike {
23 if (!audience) audience = getAudience(byActor) 22 if (!audience) audience = getAudience(byActor)
24 23
25 return audiencify( 24 return audiencify(
diff --git a/server/lib/activitypub/send/send-reject.ts b/server/lib/activitypub/send/send-reject.ts
index 63110b433..4258a3c36 100644
--- a/server/lib/activitypub/send/send-reject.ts
+++ b/server/lib/activitypub/send/send-reject.ts
@@ -1,12 +1,11 @@
1import { ActivityFollow, ActivityReject } from '../../../../shared/models/activitypub' 1import { ActivityFollow, ActivityReject } from '../../../../shared/models/activitypub'
2import { ActorModel } from '../../../models/activitypub/actor'
3import { getActorFollowActivityPubUrl, getActorFollowRejectActivityPubUrl } from '../url' 2import { getActorFollowActivityPubUrl, getActorFollowRejectActivityPubUrl } from '../url'
4import { unicastTo } from './utils' 3import { unicastTo } from './utils'
5import { buildFollowActivity } from './send-follow' 4import { buildFollowActivity } from './send-follow'
6import { logger } from '../../../helpers/logger' 5import { logger } from '../../../helpers/logger'
7import { SignatureActorModel } from '../../../typings/models' 6import { MActor } from '../../../typings/models'
8 7
9async function sendReject (follower: SignatureActorModel, following: ActorModel) { 8async function sendReject (follower: MActor, following: MActor) {
10 if (!follower.serverId) { // This should never happen 9 if (!follower.serverId) { // This should never happen
11 logger.warn('Do not sending reject to local follower.') 10 logger.warn('Do not sending reject to local follower.')
12 return 11 return
@@ -31,7 +30,7 @@ export {
31 30
32// --------------------------------------------------------------------------- 31// ---------------------------------------------------------------------------
33 32
34function buildRejectActivity (url: string, byActor: ActorModel, followActivityData: ActivityFollow): ActivityReject { 33function buildRejectActivity (url: string, byActor: MActor, followActivityData: ActivityFollow): ActivityReject {
35 return { 34 return {
36 type: 'Reject', 35 type: 'Reject',
37 id: url, 36 id: url,
diff --git a/server/lib/activitypub/send/send-undo.ts b/server/lib/activitypub/send/send-undo.ts
index 8fcbbac5c..e9ab5b3c5 100644
--- a/server/lib/activitypub/send/send-undo.ts
+++ b/server/lib/activitypub/send/send-undo.ts
@@ -2,13 +2,12 @@ import { Transaction } from 'sequelize'
2import { 2import {
3 ActivityAnnounce, 3 ActivityAnnounce,
4 ActivityAudience, 4 ActivityAudience,
5 ActivityCreate, ActivityDislike, 5 ActivityCreate,
6 ActivityDislike,
6 ActivityFollow, 7 ActivityFollow,
7 ActivityLike, 8 ActivityLike,
8 ActivityUndo 9 ActivityUndo
9} from '../../../../shared/models/activitypub' 10} from '../../../../shared/models/activitypub'
10import { ActorModel } from '../../../models/activitypub/actor'
11import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
12import { VideoModel } from '../../../models/video/video' 11import { VideoModel } from '../../../models/video/video'
13import { getActorFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url' 12import { getActorFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url'
14import { broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils' 13import { broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils'
@@ -16,13 +15,20 @@ import { audiencify, getAudience } from '../audience'
16import { buildCreateActivity } from './send-create' 15import { buildCreateActivity } from './send-create'
17import { buildFollowActivity } from './send-follow' 16import { buildFollowActivity } from './send-follow'
18import { buildLikeActivity } from './send-like' 17import { buildLikeActivity } from './send-like'
19import { VideoShareModel } from '../../../models/video/video-share'
20import { buildAnnounceWithVideoAudience } from './send-announce' 18import { buildAnnounceWithVideoAudience } from './send-announce'
21import { logger } from '../../../helpers/logger' 19import { logger } from '../../../helpers/logger'
22import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
23import { buildDislikeActivity } from './send-dislike' 20import { buildDislikeActivity } from './send-dislike'
24 21import {
25async function sendUndoFollow (actorFollow: ActorFollowModel, t: Transaction) { 22 MActor, MActorAudience,
23 MActorFollowActors,
24 MActorLight,
25 MVideo,
26 MVideoAccountLight,
27 MVideoRedundancyVideo,
28 MVideoShare
29} from '../../../typings/models'
30
31async function sendUndoFollow (actorFollow: MActorFollowActors, t: Transaction) {
26 const me = actorFollow.ActorFollower 32 const me = actorFollow.ActorFollower
27 const following = actorFollow.ActorFollowing 33 const following = actorFollow.ActorFollowing
28 34
@@ -40,7 +46,7 @@ async function sendUndoFollow (actorFollow: ActorFollowModel, t: Transaction) {
40 t.afterCommit(() => unicastTo(undoActivity, me, following.inboxUrl)) 46 t.afterCommit(() => unicastTo(undoActivity, me, following.inboxUrl))
41} 47}
42 48
43async function sendUndoAnnounce (byActor: ActorModel, videoShare: VideoShareModel, video: VideoModel, t: Transaction) { 49async function sendUndoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, t: Transaction) {
44 logger.info('Creating job to undo announce %s.', videoShare.url) 50 logger.info('Creating job to undo announce %s.', videoShare.url)
45 51
46 const undoUrl = getUndoActivityPubUrl(videoShare.url) 52 const undoUrl = getUndoActivityPubUrl(videoShare.url)
@@ -52,7 +58,7 @@ async function sendUndoAnnounce (byActor: ActorModel, videoShare: VideoShareMode
52 return broadcastToFollowers(undoActivity, byActor, actorsInvolvedInVideo, t, followersException) 58 return broadcastToFollowers(undoActivity, byActor, actorsInvolvedInVideo, t, followersException)
53} 59}
54 60
55async function sendUndoLike (byActor: ActorModel, video: VideoModel, t: Transaction) { 61async function sendUndoLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) {
56 logger.info('Creating job to undo a like of video %s.', video.url) 62 logger.info('Creating job to undo a like of video %s.', video.url)
57 63
58 const likeUrl = getVideoLikeActivityPubUrl(byActor, video) 64 const likeUrl = getVideoLikeActivityPubUrl(byActor, video)
@@ -61,7 +67,7 @@ async function sendUndoLike (byActor: ActorModel, video: VideoModel, t: Transact
61 return sendUndoVideoRelatedActivity({ byActor, video, url: likeUrl, activity: likeActivity, transaction: t }) 67 return sendUndoVideoRelatedActivity({ byActor, video, url: likeUrl, activity: likeActivity, transaction: t })
62} 68}
63 69
64async function sendUndoDislike (byActor: ActorModel, video: VideoModel, t: Transaction) { 70async function sendUndoDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) {
65 logger.info('Creating job to undo a dislike of video %s.', video.url) 71 logger.info('Creating job to undo a dislike of video %s.', video.url)
66 72
67 const dislikeUrl = getVideoDislikeActivityPubUrl(byActor, video) 73 const dislikeUrl = getVideoDislikeActivityPubUrl(byActor, video)
@@ -70,7 +76,7 @@ async function sendUndoDislike (byActor: ActorModel, video: VideoModel, t: Trans
70 return sendUndoVideoRelatedActivity({ byActor, video, url: dislikeUrl, activity: dislikeActivity, transaction: t }) 76 return sendUndoVideoRelatedActivity({ byActor, video, url: dislikeUrl, activity: dislikeActivity, transaction: t })
71} 77}
72 78
73async function sendUndoCacheFile (byActor: ActorModel, redundancyModel: VideoRedundancyModel, t: Transaction) { 79async function sendUndoCacheFile (byActor: MActor, redundancyModel: MVideoRedundancyVideo, t: Transaction) {
74 logger.info('Creating job to undo cache file %s.', redundancyModel.url) 80 logger.info('Creating job to undo cache file %s.', redundancyModel.url)
75 81
76 const videoId = redundancyModel.getVideo().id 82 const videoId = redundancyModel.getVideo().id
@@ -94,7 +100,7 @@ export {
94 100
95function undoActivityData ( 101function undoActivityData (
96 url: string, 102 url: string,
97 byActor: ActorModel, 103 byActor: MActorAudience,
98 object: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce, 104 object: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce,
99 audience?: ActivityAudience 105 audience?: ActivityAudience
100): ActivityUndo { 106): ActivityUndo {
@@ -112,8 +118,8 @@ function undoActivityData (
112} 118}
113 119
114async function sendUndoVideoRelatedActivity (options: { 120async function sendUndoVideoRelatedActivity (options: {
115 byActor: ActorModel, 121 byActor: MActor,
116 video: VideoModel, 122 video: MVideoAccountLight,
117 url: string, 123 url: string,
118 activity: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce, 124 activity: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce,
119 transaction: Transaction 125 transaction: Transaction
diff --git a/server/lib/activitypub/send/send-update.ts b/server/lib/activitypub/send/send-update.ts
index 5bf092894..37517c2be 100644
--- a/server/lib/activitypub/send/send-update.ts
+++ b/server/lib/activitypub/send/send-update.ts
@@ -2,21 +2,29 @@ import { Transaction } from 'sequelize'
2import { ActivityAudience, ActivityUpdate } from '../../../../shared/models/activitypub' 2import { ActivityAudience, ActivityUpdate } from '../../../../shared/models/activitypub'
3import { VideoPrivacy } from '../../../../shared/models/videos' 3import { VideoPrivacy } from '../../../../shared/models/videos'
4import { AccountModel } from '../../../models/account/account' 4import { AccountModel } from '../../../models/account/account'
5import { ActorModel } from '../../../models/activitypub/actor'
6import { VideoModel } from '../../../models/video/video' 5import { VideoModel } from '../../../models/video/video'
7import { VideoChannelModel } from '../../../models/video/video-channel'
8import { VideoShareModel } from '../../../models/video/video-share' 6import { VideoShareModel } from '../../../models/video/video-share'
9import { getUpdateActivityPubUrl } from '../url' 7import { getUpdateActivityPubUrl } from '../url'
10import { broadcastToFollowers, sendVideoRelatedActivity } from './utils' 8import { broadcastToFollowers, sendVideoRelatedActivity } from './utils'
11import { audiencify, getActorsInvolvedInVideo, getAudience } from '../audience' 9import { audiencify, getActorsInvolvedInVideo, getAudience } from '../audience'
12import { logger } from '../../../helpers/logger' 10import { logger } from '../../../helpers/logger'
13import { VideoCaptionModel } from '../../../models/video/video-caption' 11import { VideoCaptionModel } from '../../../models/video/video-caption'
14import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
15import { VideoPlaylistModel } from '../../../models/video/video-playlist'
16import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' 12import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
17import { getServerActor } from '../../../helpers/utils' 13import { getServerActor } from '../../../helpers/utils'
14import {
15 MAccountDefault,
16 MActor,
17 MActorLight,
18 MChannelDefault,
19 MVideoAP,
20 MVideoAPWithoutCaption,
21 MVideoPlaylistFull,
22 MVideoRedundancyVideo
23} from '../../../typings/models'
24
25async function sendUpdateVideo (videoArg: MVideoAPWithoutCaption, t: Transaction, overrodeByActor?: MActor) {
26 const video = videoArg as MVideoAP
18 27
19async function sendUpdateVideo (video: VideoModel, t: Transaction, overrodeByActor?: ActorModel) {
20 if (video.privacy === VideoPrivacy.PRIVATE) return undefined 28 if (video.privacy === VideoPrivacy.PRIVATE) return undefined
21 29
22 logger.info('Creating job to update video %s.', video.url) 30 logger.info('Creating job to update video %s.', video.url)
@@ -41,7 +49,7 @@ async function sendUpdateVideo (video: VideoModel, t: Transaction, overrodeByAct
41 return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t) 49 return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t)
42} 50}
43 51
44async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelModel, t: Transaction) { 52async function sendUpdateActor (accountOrChannel: MChannelDefault | MAccountDefault, t: Transaction) {
45 const byActor = accountOrChannel.Actor 53 const byActor = accountOrChannel.Actor
46 54
47 logger.info('Creating job to update actor %s.', byActor.url) 55 logger.info('Creating job to update actor %s.', byActor.url)
@@ -51,7 +59,7 @@ async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelMod
51 const audience = getAudience(byActor) 59 const audience = getAudience(byActor)
52 const updateActivity = buildUpdateActivity(url, byActor, accountOrChannelObject, audience) 60 const updateActivity = buildUpdateActivity(url, byActor, accountOrChannelObject, audience)
53 61
54 let actorsInvolved: ActorModel[] 62 let actorsInvolved: MActor[]
55 if (accountOrChannel instanceof AccountModel) { 63 if (accountOrChannel instanceof AccountModel) {
56 // Actors that shared my videos are involved too 64 // Actors that shared my videos are involved too
57 actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, t) 65 actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, t)
@@ -65,7 +73,7 @@ async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelMod
65 return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t) 73 return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t)
66} 74}
67 75
68async function sendUpdateCacheFile (byActor: ActorModel, redundancyModel: VideoRedundancyModel) { 76async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVideoRedundancyVideo) {
69 logger.info('Creating job to update cache file %s.', redundancyModel.url) 77 logger.info('Creating job to update cache file %s.', redundancyModel.url)
70 78
71 const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(redundancyModel.getVideo().id) 79 const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(redundancyModel.getVideo().id)
@@ -80,7 +88,7 @@ async function sendUpdateCacheFile (byActor: ActorModel, redundancyModel: VideoR
80 return sendVideoRelatedActivity(activityBuilder, { byActor, video }) 88 return sendVideoRelatedActivity(activityBuilder, { byActor, video })
81} 89}
82 90
83async function sendUpdateVideoPlaylist (videoPlaylist: VideoPlaylistModel, t: Transaction) { 91async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, t: Transaction) {
84 if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined 92 if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined
85 93
86 const byActor = videoPlaylist.OwnerAccount.Actor 94 const byActor = videoPlaylist.OwnerAccount.Actor
@@ -113,7 +121,7 @@ export {
113 121
114// --------------------------------------------------------------------------- 122// ---------------------------------------------------------------------------
115 123
116function buildUpdateActivity (url: string, byActor: ActorModel, object: any, audience?: ActivityAudience): ActivityUpdate { 124function buildUpdateActivity (url: string, byActor: MActorLight, object: any, audience?: ActivityAudience): ActivityUpdate {
117 if (!audience) audience = getAudience(byActor) 125 if (!audience) audience = getAudience(byActor)
118 126
119 return audiencify( 127 return audiencify(
@@ -121,8 +129,7 @@ function buildUpdateActivity (url: string, byActor: ActorModel, object: any, aud
121 type: 'Update' as 'Update', 129 type: 'Update' as 'Update',
122 id: url, 130 id: url,
123 actor: byActor.url, 131 actor: byActor.url,
124 object: audiencify(object, audience 132 object: audiencify(object, audience)
125 )
126 }, 133 },
127 audience 134 audience
128 ) 135 )
diff --git a/server/lib/activitypub/send/send-view.ts b/server/lib/activitypub/send/send-view.ts
index 8ad126be0..8809417f9 100644
--- a/server/lib/activitypub/send/send-view.ts
+++ b/server/lib/activitypub/send/send-view.ts
@@ -1,13 +1,13 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityAudience, ActivityView } from '../../../../shared/models/activitypub' 2import { ActivityAudience, ActivityView } from '../../../../shared/models/activitypub'
3import { ActorModel } from '../../../models/activitypub/actor' 3import { ActorModel } from '../../../models/activitypub/actor'
4import { VideoModel } from '../../../models/video/video'
5import { getVideoLikeActivityPubUrl } from '../url' 4import { getVideoLikeActivityPubUrl } from '../url'
6import { sendVideoRelatedActivity } from './utils' 5import { sendVideoRelatedActivity } from './utils'
7import { audiencify, getAudience } from '../audience' 6import { audiencify, getAudience } from '../audience'
8import { logger } from '../../../helpers/logger' 7import { logger } from '../../../helpers/logger'
8import { MActorAudience, MVideoAccountLight, MVideoUrl } from '@server/typings/models'
9 9
10async function sendView (byActor: ActorModel, video: VideoModel, t: Transaction) { 10async function sendView (byActor: ActorModel, video: MVideoAccountLight, t: Transaction) {
11 logger.info('Creating job to send view of %s.', video.url) 11 logger.info('Creating job to send view of %s.', video.url)
12 12
13 const activityBuilder = (audience: ActivityAudience) => { 13 const activityBuilder = (audience: ActivityAudience) => {
@@ -19,7 +19,7 @@ async function sendView (byActor: ActorModel, video: VideoModel, t: Transaction)
19 return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t }) 19 return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t })
20} 20}
21 21
22function buildViewActivity (url: string, byActor: ActorModel, video: VideoModel, audience?: ActivityAudience): ActivityView { 22function buildViewActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityView {
23 if (!audience) audience = getAudience(byActor) 23 if (!audience) audience = getAudience(byActor)
24 24
25 return audiencify( 25 return audiencify(
diff --git a/server/lib/activitypub/send/utils.ts b/server/lib/activitypub/send/utils.ts
index 4f69afb00..8129ab32a 100644
--- a/server/lib/activitypub/send/utils.ts
+++ b/server/lib/activitypub/send/utils.ts
@@ -4,15 +4,14 @@ import { logger } from '../../../helpers/logger'
4import { ActorModel } from '../../../models/activitypub/actor' 4import { ActorModel } from '../../../models/activitypub/actor'
5import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 5import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
6import { JobQueue } from '../../job-queue' 6import { JobQueue } from '../../job-queue'
7import { VideoModel } from '../../../models/video/video'
8import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getRemoteVideoAudience } from '../audience' 7import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getRemoteVideoAudience } from '../audience'
9import { getServerActor } from '../../../helpers/utils' 8import { getServerActor } from '../../../helpers/utils'
10import { afterCommitIfTransaction } from '../../../helpers/database-utils' 9import { afterCommitIfTransaction } from '../../../helpers/database-utils'
11import { ActorFollowerException, ActorModelId, ActorModelOnly } from '../../../typings/models' 10import { MActorFollowerException, MActor, MActorId, MActorLight, MVideo, MVideoAccountLight } from '../../../typings/models'
12 11
13async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: { 12async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: {
14 byActor: ActorModelOnly, 13 byActor: MActorLight,
15 video: VideoModel, 14 video: MVideoAccountLight,
16 transaction?: Transaction 15 transaction?: Transaction
17}) { 16}) {
18 const { byActor, video, transaction } = options 17 const { byActor, video, transaction } = options
@@ -41,8 +40,8 @@ async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAud
41async function forwardVideoRelatedActivity ( 40async function forwardVideoRelatedActivity (
42 activity: Activity, 41 activity: Activity,
43 t: Transaction, 42 t: Transaction,
44 followersException: ActorFollowerException[] = [], 43 followersException: MActorFollowerException[] = [],
45 video: VideoModel 44 video: MVideo
46) { 45) {
47 // Mastodon does not add our announces in audience, so we forward to them manually 46 // Mastodon does not add our announces in audience, so we forward to them manually
48 const additionalActors = await getActorsInvolvedInVideo(video, t) 47 const additionalActors = await getActorsInvolvedInVideo(video, t)
@@ -54,7 +53,7 @@ async function forwardVideoRelatedActivity (
54async function forwardActivity ( 53async function forwardActivity (
55 activity: Activity, 54 activity: Activity,
56 t: Transaction, 55 t: Transaction,
57 followersException: ActorFollowerException[] = [], 56 followersException: MActorFollowerException[] = [],
58 additionalFollowerUrls: string[] = [] 57 additionalFollowerUrls: string[] = []
59) { 58) {
60 logger.info('Forwarding activity %s.', activity.id) 59 logger.info('Forwarding activity %s.', activity.id)
@@ -88,10 +87,10 @@ async function forwardActivity (
88 87
89async function broadcastToFollowers ( 88async function broadcastToFollowers (
90 data: any, 89 data: any,
91 byActor: ActorModelId, 90 byActor: MActorId,
92 toFollowersOf: ActorModelId[], 91 toFollowersOf: MActorId[],
93 t: Transaction, 92 t: Transaction,
94 actorsException: ActorFollowerException[] = [] 93 actorsException: MActorFollowerException[] = []
95) { 94) {
96 const uris = await computeFollowerUris(toFollowersOf, actorsException, t) 95 const uris = await computeFollowerUris(toFollowersOf, actorsException, t)
97 96
@@ -100,16 +99,16 @@ async function broadcastToFollowers (
100 99
101async function broadcastToActors ( 100async function broadcastToActors (
102 data: any, 101 data: any,
103 byActor: ActorModelId, 102 byActor: MActorId,
104 toActors: ActorModelOnly[], 103 toActors: MActor[],
105 t?: Transaction, 104 t?: Transaction,
106 actorsException: ActorFollowerException[] = [] 105 actorsException: MActorFollowerException[] = []
107) { 106) {
108 const uris = await computeUris(toActors, actorsException) 107 const uris = await computeUris(toActors, actorsException)
109 return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor)) 108 return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor))
110} 109}
111 110
112function broadcastTo (uris: string[], data: any, byActor: ActorModelId) { 111function broadcastTo (uris: string[], data: any, byActor: MActorId) {
113 if (uris.length === 0) return undefined 112 if (uris.length === 0) return undefined
114 113
115 logger.debug('Creating broadcast job.', { uris }) 114 logger.debug('Creating broadcast job.', { uris })
@@ -123,7 +122,7 @@ function broadcastTo (uris: string[], data: any, byActor: ActorModelId) {
123 return JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload }) 122 return JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload })
124} 123}
125 124
126function unicastTo (data: any, byActor: ActorModelId, toActorUrl: string) { 125function unicastTo (data: any, byActor: MActorId, toActorUrl: string) {
127 logger.debug('Creating unicast job.', { uri: toActorUrl }) 126 logger.debug('Creating unicast job.', { uri: toActorUrl })
128 127
129 const payload = { 128 const payload = {
@@ -148,7 +147,7 @@ export {
148 147
149// --------------------------------------------------------------------------- 148// ---------------------------------------------------------------------------
150 149
151async function computeFollowerUris (toFollowersOf: ActorModelId[], actorsException: ActorFollowerException[], t: Transaction) { 150async function computeFollowerUris (toFollowersOf: MActorId[], actorsException: MActorFollowerException[], t: Transaction) {
152 const toActorFollowerIds = toFollowersOf.map(a => a.id) 151 const toActorFollowerIds = toFollowersOf.map(a => a.id)
153 152
154 const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t) 153 const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t)
@@ -157,7 +156,7 @@ async function computeFollowerUris (toFollowersOf: ActorModelId[], actorsExcepti
157 return result.data.filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1) 156 return result.data.filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1)
158} 157}
159 158
160async function computeUris (toActors: ActorModelOnly[], actorsException: ActorFollowerException[] = []) { 159async function computeUris (toActors: MActor[], actorsException: MActorFollowerException[] = []) {
161 const serverActor = await getServerActor() 160 const serverActor = await getServerActor()
162 const targetUrls = toActors 161 const targetUrls = toActors
163 .filter(a => a.id !== serverActor.id) // Don't send to ourselves 162 .filter(a => a.id !== serverActor.id) // Don't send to ourselves
@@ -170,7 +169,7 @@ async function computeUris (toActors: ActorModelOnly[], actorsException: ActorFo
170 .filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1) 169 .filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1)
171} 170}
172 171
173async function buildSharedInboxesException (actorsException: ActorFollowerException[]) { 172async function buildSharedInboxesException (actorsException: MActorFollowerException[]) {
174 const serverActor = await getServerActor() 173 const serverActor = await getServerActor()
175 174
176 return actorsException 175 return actorsException
diff --git a/server/lib/activitypub/share.ts b/server/lib/activitypub/share.ts
index 7f38402b6..fdca9bed7 100644
--- a/server/lib/activitypub/share.ts
+++ b/server/lib/activitypub/share.ts
@@ -1,19 +1,18 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { VideoPrivacy } from '../../../shared/models/videos' 2import { VideoPrivacy } from '../../../shared/models/videos'
3import { getServerActor } from '../../helpers/utils' 3import { getServerActor } from '../../helpers/utils'
4import { VideoModel } from '../../models/video/video'
5import { VideoShareModel } from '../../models/video/video-share' 4import { VideoShareModel } from '../../models/video/video-share'
6import { sendUndoAnnounce, sendVideoAnnounce } from './send' 5import { sendUndoAnnounce, sendVideoAnnounce } from './send'
7import { getVideoAnnounceActivityPubUrl } from './url' 6import { getVideoAnnounceActivityPubUrl } from './url'
8import { VideoChannelModel } from '../../models/video/video-channel'
9import * as Bluebird from 'bluebird' 7import * as Bluebird from 'bluebird'
10import { doRequest } from '../../helpers/requests' 8import { doRequest } from '../../helpers/requests'
11import { getOrCreateActorAndServerAndModel } from './actor' 9import { getOrCreateActorAndServerAndModel } from './actor'
12import { logger } from '../../helpers/logger' 10import { logger } from '../../helpers/logger'
13import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' 11import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants'
14import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' 12import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub'
13import { MChannelActor, MChannelActorLight, MVideo, MVideoAccountLight, MVideoId } from '../../typings/models/video'
15 14
16async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction) { 15async function shareVideoByServerAndChannel (video: MVideoAccountLight, t: Transaction) {
17 if (video.privacy === VideoPrivacy.PRIVATE) return undefined 16 if (video.privacy === VideoPrivacy.PRIVATE) return undefined
18 17
19 return Promise.all([ 18 return Promise.all([
@@ -22,7 +21,11 @@ async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction)
22 ]) 21 ])
23} 22}
24 23
25async function changeVideoChannelShare (video: VideoModel, oldVideoChannel: VideoChannelModel, t: Transaction) { 24async function changeVideoChannelShare (
25 video: MVideoAccountLight,
26 oldVideoChannel: MChannelActorLight,
27 t: Transaction
28) {
26 logger.info('Updating video channel of video %s: %s -> %s.', video.uuid, oldVideoChannel.name, video.VideoChannel.name) 29 logger.info('Updating video channel of video %s: %s -> %s.', video.uuid, oldVideoChannel.name, video.VideoChannel.name)
27 30
28 await undoShareByVideoChannel(video, oldVideoChannel, t) 31 await undoShareByVideoChannel(video, oldVideoChannel, t)
@@ -30,7 +33,7 @@ async function changeVideoChannelShare (video: VideoModel, oldVideoChannel: Vide
30 await shareByVideoChannel(video, t) 33 await shareByVideoChannel(video, t)
31} 34}
32 35
33async function addVideoShares (shareUrls: string[], instance: VideoModel) { 36async function addVideoShares (shareUrls: string[], video: MVideoId) {
34 await Bluebird.map(shareUrls, async shareUrl => { 37 await Bluebird.map(shareUrls, async shareUrl => {
35 try { 38 try {
36 // Fetch url 39 // Fetch url
@@ -50,7 +53,7 @@ async function addVideoShares (shareUrls: string[], instance: VideoModel) {
50 53
51 const entry = { 54 const entry = {
52 actorId: actor.id, 55 actorId: actor.id,
53 videoId: instance.id, 56 videoId: video.id,
54 url: shareUrl 57 url: shareUrl
55 } 58 }
56 59
@@ -69,7 +72,7 @@ export {
69 72
70// --------------------------------------------------------------------------- 73// ---------------------------------------------------------------------------
71 74
72async function shareByServer (video: VideoModel, t: Transaction) { 75async function shareByServer (video: MVideo, t: Transaction) {
73 const serverActor = await getServerActor() 76 const serverActor = await getServerActor()
74 77
75 const serverShareUrl = getVideoAnnounceActivityPubUrl(serverActor, video) 78 const serverShareUrl = getVideoAnnounceActivityPubUrl(serverActor, video)
@@ -88,7 +91,7 @@ async function shareByServer (video: VideoModel, t: Transaction) {
88 return sendVideoAnnounce(serverActor, serverShare, video, t) 91 return sendVideoAnnounce(serverActor, serverShare, video, t)
89} 92}
90 93
91async function shareByVideoChannel (video: VideoModel, t: Transaction) { 94async function shareByVideoChannel (video: MVideoAccountLight, t: Transaction) {
92 const videoChannelShareUrl = getVideoAnnounceActivityPubUrl(video.VideoChannel.Actor, video) 95 const videoChannelShareUrl = getVideoAnnounceActivityPubUrl(video.VideoChannel.Actor, video)
93 const [ videoChannelShare ] = await VideoShareModel.findOrCreate({ 96 const [ videoChannelShare ] = await VideoShareModel.findOrCreate({
94 defaults: { 97 defaults: {
@@ -105,7 +108,7 @@ async function shareByVideoChannel (video: VideoModel, t: Transaction) {
105 return sendVideoAnnounce(video.VideoChannel.Actor, videoChannelShare, video, t) 108 return sendVideoAnnounce(video.VideoChannel.Actor, videoChannelShare, video, t)
106} 109}
107 110
108async function undoShareByVideoChannel (video: VideoModel, oldVideoChannel: VideoChannelModel, t: Transaction) { 111async function undoShareByVideoChannel (video: MVideo, oldVideoChannel: MChannelActorLight, t: Transaction) {
109 // Load old share 112 // Load old share
110 const oldShare = await VideoShareModel.load(oldVideoChannel.actorId, video.id, t) 113 const oldShare = await VideoShareModel.load(oldVideoChannel.actorId, video.id, t)
111 if (!oldShare) return new Error('Cannot find old video channel share ' + oldVideoChannel.actorId + ' for video ' + video.id) 114 if (!oldShare) return new Error('Cannot find old video channel share ' + oldVideoChannel.actorId + ' for video ' + video.id)
diff --git a/server/lib/activitypub/url.ts b/server/lib/activitypub/url.ts
index dfcb3c668..6290af34b 100644
--- a/server/lib/activitypub/url.ts
+++ b/server/lib/activitypub/url.ts
@@ -1,36 +1,42 @@
1import { WEBSERVER } from '../../initializers/constants' 1import { WEBSERVER } from '../../initializers/constants'
2import { VideoModel } from '../../models/video/video' 2import {
3import { VideoAbuseModel } from '../../models/video/video-abuse' 3 MActor,
4import { VideoCommentModel } from '../../models/video/video-comment' 4 MActorFollowActors,
5import { VideoFileModel } from '../../models/video/video-file' 5 MActorId,
6import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist' 6 MActorUrl,
7import { VideoPlaylistModel } from '../../models/video/video-playlist' 7 MCommentId,
8import { ActorModelOnly, ActorModelUrl } from '../../typings/models' 8 MVideoAbuseId,
9import { ActorFollowModelLight } from '../../typings/models/actor-follow' 9 MVideoId,
10 10 MVideoUrl,
11function getVideoActivityPubUrl (video: VideoModel) { 11 MVideoUUID
12} from '../../typings/models'
13import { MVideoPlaylist, MVideoPlaylistUUID } from '../../typings/models/video/video-playlist'
14import { MVideoFileVideoUUID } from '../../typings/models/video/video-file'
15import { MStreamingPlaylist } from '../../typings/models/video/video-streaming-playlist'
16
17function getVideoActivityPubUrl (video: MVideoUUID) {
12 return WEBSERVER.URL + '/videos/watch/' + video.uuid 18 return WEBSERVER.URL + '/videos/watch/' + video.uuid
13} 19}
14 20
15function getVideoPlaylistActivityPubUrl (videoPlaylist: VideoPlaylistModel) { 21function getVideoPlaylistActivityPubUrl (videoPlaylist: MVideoPlaylist) {
16 return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid 22 return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid
17} 23}
18 24
19function getVideoPlaylistElementActivityPubUrl (videoPlaylist: VideoPlaylistModel, video: VideoModel) { 25function getVideoPlaylistElementActivityPubUrl (videoPlaylist: MVideoPlaylistUUID, video: MVideoUUID) {
20 return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid + '/' + video.uuid 26 return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid + '/' + video.uuid
21} 27}
22 28
23function getVideoCacheFileActivityPubUrl (videoFile: VideoFileModel) { 29function getVideoCacheFileActivityPubUrl (videoFile: MVideoFileVideoUUID) {
24 const suffixFPS = videoFile.fps && videoFile.fps !== -1 ? '-' + videoFile.fps : '' 30 const suffixFPS = videoFile.fps && videoFile.fps !== -1 ? '-' + videoFile.fps : ''
25 31
26 return `${WEBSERVER.URL}/redundancy/videos/${videoFile.Video.uuid}/${videoFile.resolution}${suffixFPS}` 32 return `${WEBSERVER.URL}/redundancy/videos/${videoFile.Video.uuid}/${videoFile.resolution}${suffixFPS}`
27} 33}
28 34
29function getVideoCacheStreamingPlaylistActivityPubUrl (video: VideoModel, playlist: VideoStreamingPlaylistModel) { 35function getVideoCacheStreamingPlaylistActivityPubUrl (video: MVideoUUID, playlist: MStreamingPlaylist) {
30 return `${WEBSERVER.URL}/redundancy/streaming-playlists/${playlist.getStringType()}/${video.uuid}` 36 return `${WEBSERVER.URL}/redundancy/streaming-playlists/${playlist.getStringType()}/${video.uuid}`
31} 37}
32 38
33function getVideoCommentActivityPubUrl (video: VideoModel, videoComment: VideoCommentModel) { 39function getVideoCommentActivityPubUrl (video: MVideoUUID, videoComment: MCommentId) {
34 return WEBSERVER.URL + '/videos/watch/' + video.uuid + '/comments/' + videoComment.id 40 return WEBSERVER.URL + '/videos/watch/' + video.uuid + '/comments/' + videoComment.id
35} 41}
36 42
@@ -42,54 +48,54 @@ function getAccountActivityPubUrl (accountName: string) {
42 return WEBSERVER.URL + '/accounts/' + accountName 48 return WEBSERVER.URL + '/accounts/' + accountName
43} 49}
44 50
45function getVideoAbuseActivityPubUrl (videoAbuse: VideoAbuseModel) { 51function getVideoAbuseActivityPubUrl (videoAbuse: MVideoAbuseId) {
46 return WEBSERVER.URL + '/admin/video-abuses/' + videoAbuse.id 52 return WEBSERVER.URL + '/admin/video-abuses/' + videoAbuse.id
47} 53}
48 54
49function getVideoViewActivityPubUrl (byActor: ActorModelUrl, video: VideoModel) { 55function getVideoViewActivityPubUrl (byActor: MActorUrl, video: MVideoId) {
50 return byActor.url + '/views/videos/' + video.id + '/' + new Date().toISOString() 56 return byActor.url + '/views/videos/' + video.id + '/' + new Date().toISOString()
51} 57}
52 58
53function getVideoLikeActivityPubUrl (byActor: ActorModelUrl, video: VideoModel | { id: number }) { 59function getVideoLikeActivityPubUrl (byActor: MActorUrl, video: MVideoId) {
54 return byActor.url + '/likes/' + video.id 60 return byActor.url + '/likes/' + video.id
55} 61}
56 62
57function getVideoDislikeActivityPubUrl (byActor: ActorModelUrl, video: VideoModel | { id: number }) { 63function getVideoDislikeActivityPubUrl (byActor: MActorUrl, video: MVideoId) {
58 return byActor.url + '/dislikes/' + video.id 64 return byActor.url + '/dislikes/' + video.id
59} 65}
60 66
61function getVideoSharesActivityPubUrl (video: VideoModel) { 67function getVideoSharesActivityPubUrl (video: MVideoUrl) {
62 return video.url + '/announces' 68 return video.url + '/announces'
63} 69}
64 70
65function getVideoCommentsActivityPubUrl (video: VideoModel) { 71function getVideoCommentsActivityPubUrl (video: MVideoUrl) {
66 return video.url + '/comments' 72 return video.url + '/comments'
67} 73}
68 74
69function getVideoLikesActivityPubUrl (video: VideoModel) { 75function getVideoLikesActivityPubUrl (video: MVideoUrl) {
70 return video.url + '/likes' 76 return video.url + '/likes'
71} 77}
72 78
73function getVideoDislikesActivityPubUrl (video: VideoModel) { 79function getVideoDislikesActivityPubUrl (video: MVideoUrl) {
74 return video.url + '/dislikes' 80 return video.url + '/dislikes'
75} 81}
76 82
77function getActorFollowActivityPubUrl (follower: ActorModelOnly, following: ActorModelOnly) { 83function getActorFollowActivityPubUrl (follower: MActor, following: MActorId) {
78 return follower.url + '/follows/' + following.id 84 return follower.url + '/follows/' + following.id
79} 85}
80 86
81function getActorFollowAcceptActivityPubUrl (actorFollow: ActorFollowModelLight) { 87function getActorFollowAcceptActivityPubUrl (actorFollow: MActorFollowActors) {
82 const follower = actorFollow.ActorFollower 88 const follower = actorFollow.ActorFollower
83 const me = actorFollow.ActorFollowing 89 const me = actorFollow.ActorFollowing
84 90
85 return follower.url + '/accepts/follows/' + me.id 91 return follower.url + '/accepts/follows/' + me.id
86} 92}
87 93
88function getActorFollowRejectActivityPubUrl (follower: ActorModelOnly, following: ActorModelOnly) { 94function getActorFollowRejectActivityPubUrl (follower: MActorUrl, following: MActorId) {
89 return follower.url + '/rejects/follows/' + following.id 95 return follower.url + '/rejects/follows/' + following.id
90} 96}
91 97
92function getVideoAnnounceActivityPubUrl (byActor: ActorModelOnly, video: VideoModel) { 98function getVideoAnnounceActivityPubUrl (byActor: MActorId, video: MVideoUrl) {
93 return video.url + '/announces/' + byActor.id 99 return video.url + '/announces/' + byActor.id
94} 100}
95 101
diff --git a/server/lib/activitypub/video-comments.ts b/server/lib/activitypub/video-comments.ts
index 8d2c1ade3..3e8306fa4 100644
--- a/server/lib/activitypub/video-comments.ts
+++ b/server/lib/activitypub/video-comments.ts
@@ -2,20 +2,20 @@ import { sanitizeAndCheckVideoCommentObject } from '../../helpers/custom-validat
2import { logger } from '../../helpers/logger' 2import { logger } from '../../helpers/logger'
3import { doRequest } from '../../helpers/requests' 3import { doRequest } from '../../helpers/requests'
4import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' 4import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants'
5import { VideoModel } from '../../models/video/video'
6import { VideoCommentModel } from '../../models/video/video-comment' 5import { VideoCommentModel } from '../../models/video/video-comment'
7import { getOrCreateActorAndServerAndModel } from './actor' 6import { getOrCreateActorAndServerAndModel } from './actor'
8import { getOrCreateVideoAndAccountAndChannel } from './videos' 7import { getOrCreateVideoAndAccountAndChannel } from './videos'
9import * as Bluebird from 'bluebird' 8import * as Bluebird from 'bluebird'
10import { checkUrlsSameHost } from '../../helpers/activitypub' 9import { checkUrlsSameHost } from '../../helpers/activitypub'
10import { MCommentOwner, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../typings/models/video'
11 11
12type ResolveThreadParams = { 12type ResolveThreadParams = {
13 url: string, 13 url: string,
14 comments?: VideoCommentModel[], 14 comments?: MCommentOwner[],
15 isVideo?: boolean, 15 isVideo?: boolean,
16 commentCreated?: boolean 16 commentCreated?: boolean
17} 17}
18type ResolveThreadResult = Promise<{ video: VideoModel, comment: VideoCommentModel, commentCreated: boolean }> 18type ResolveThreadResult = Promise<{ video: MVideoAccountLightBlacklistAllFiles, comment: MCommentOwnerVideo, commentCreated: boolean }>
19 19
20async function addVideoComments (commentUrls: string[]) { 20async function addVideoComments (commentUrls: string[]) {
21 return Bluebird.map(commentUrls, commentUrl => { 21 return Bluebird.map(commentUrls, commentUrl => {
@@ -85,9 +85,9 @@ async function tryResolveThreadFromVideo (params: ResolveThreadParams) {
85 const syncParam = { likes: true, dislikes: true, shares: true, comments: false, thumbnail: true, refreshVideo: false } 85 const syncParam = { likes: true, dislikes: true, shares: true, comments: false, thumbnail: true, refreshVideo: false }
86 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: url, syncParam }) 86 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: url, syncParam })
87 87
88 let resultComment: VideoCommentModel 88 let resultComment: MCommentOwnerVideo
89 if (comments.length !== 0) { 89 if (comments.length !== 0) {
90 const firstReply = comments[ comments.length - 1 ] 90 const firstReply = comments[ comments.length - 1 ] as MCommentOwnerVideo
91 firstReply.inReplyToCommentId = null 91 firstReply.inReplyToCommentId = null
92 firstReply.originCommentId = null 92 firstReply.originCommentId = null
93 firstReply.videoId = video.id 93 firstReply.videoId = video.id
@@ -97,7 +97,7 @@ async function tryResolveThreadFromVideo (params: ResolveThreadParams) {
97 comments[comments.length - 1] = await firstReply.save() 97 comments[comments.length - 1] = await firstReply.save()
98 98
99 for (let i = comments.length - 2; i >= 0; i--) { 99 for (let i = comments.length - 2; i >= 0; i--) {
100 const comment = comments[ i ] 100 const comment = comments[ i ] as MCommentOwnerVideo
101 comment.originCommentId = firstReply.id 101 comment.originCommentId = firstReply.id
102 comment.inReplyToCommentId = comments[ i + 1 ].id 102 comment.inReplyToCommentId = comments[ i + 1 ].id
103 comment.videoId = video.id 103 comment.videoId = video.id
@@ -107,7 +107,7 @@ async function tryResolveThreadFromVideo (params: ResolveThreadParams) {
107 comments[i] = await comment.save() 107 comments[i] = await comment.save()
108 } 108 }
109 109
110 resultComment = comments[0] 110 resultComment = comments[0] as MCommentOwnerVideo
111 } 111 }
112 112
113 return { video, comment: resultComment, commentCreated } 113 return { video, comment: resultComment, commentCreated }
@@ -151,7 +151,7 @@ async function resolveParentComment (params: ResolveThreadParams) {
151 originCommentId: null, 151 originCommentId: null,
152 createdAt: new Date(body.published), 152 createdAt: new Date(body.published),
153 updatedAt: new Date(body.updated) 153 updatedAt: new Date(body.updated)
154 }) 154 }) as MCommentOwner
155 comment.Account = actor.Account 155 comment.Account = actor.Account
156 156
157 return resolveThread({ 157 return resolveThread({
diff --git a/server/lib/activitypub/video-rates.ts b/server/lib/activitypub/video-rates.ts
index cda5b2981..6bd46bb58 100644
--- a/server/lib/activitypub/video-rates.ts
+++ b/server/lib/activitypub/video-rates.ts
@@ -1,6 +1,4 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { AccountModel } from '../../models/account/account'
3import { VideoModel } from '../../models/video/video'
4import { sendLike, sendUndoDislike, sendUndoLike } from './send' 2import { sendLike, sendUndoDislike, sendUndoLike } from './send'
5import { VideoRateType } from '../../../shared/models/videos' 3import { VideoRateType } from '../../../shared/models/videos'
6import * as Bluebird from 'bluebird' 4import * as Bluebird from 'bluebird'
@@ -10,11 +8,11 @@ import { logger } from '../../helpers/logger'
10import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' 8import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants'
11import { doRequest } from '../../helpers/requests' 9import { doRequest } from '../../helpers/requests'
12import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' 10import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub'
13import { ActorModel } from '../../models/activitypub/actor'
14import { getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from './url' 11import { getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from './url'
15import { sendDislike } from './send/send-dislike' 12import { sendDislike } from './send/send-dislike'
13import { MAccountActor, MActorUrl, MVideo, MVideoAccountLight, MVideoId } from '../../typings/models'
16 14
17async function createRates (ratesUrl: string[], video: VideoModel, rate: VideoRateType) { 15async function createRates (ratesUrl: string[], video: MVideo, rate: VideoRateType) {
18 let rateCounts = 0 16 let rateCounts = 0
19 17
20 await Bluebird.map(ratesUrl, async rateUrl => { 18 await Bluebird.map(ratesUrl, async rateUrl => {
@@ -64,11 +62,13 @@ async function createRates (ratesUrl: string[], video: VideoModel, rate: VideoRa
64 return 62 return
65} 63}
66 64
67async function sendVideoRateChange (account: AccountModel, 65async function sendVideoRateChange (
68 video: VideoModel, 66 account: MAccountActor,
69 likes: number, 67 video: MVideoAccountLight,
70 dislikes: number, 68 likes: number,
71 t: Transaction) { 69 dislikes: number,
70 t: Transaction
71) {
72 const actor = account.Actor 72 const actor = account.Actor
73 73
74 // Keep the order: first we undo and then we create 74 // Keep the order: first we undo and then we create
@@ -84,8 +84,10 @@ async function sendVideoRateChange (account: AccountModel,
84 if (dislikes > 0) await sendDislike(actor, video, t) 84 if (dislikes > 0) await sendDislike(actor, video, t)
85} 85}
86 86
87function getRateUrl (rateType: VideoRateType, actor: ActorModel, video: VideoModel) { 87function getRateUrl (rateType: VideoRateType, actor: MActorUrl, video: MVideoId) {
88 return rateType === 'like' ? getVideoLikeActivityPubUrl(actor, video) : getVideoDislikeActivityPubUrl(actor, video) 88 return rateType === 'like'
89 ? getVideoLikeActivityPubUrl(actor, video)
90 : getVideoDislikeActivityPubUrl(actor, video)
89} 91}
90 92
91export { 93export {
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts
index 3a8451a32..c318978fd 100644
--- a/server/lib/activitypub/videos.ts
+++ b/server/lib/activitypub/videos.ts
@@ -24,7 +24,6 @@ import {
24 REMOTE_SCHEME, 24 REMOTE_SCHEME,
25 STATIC_PATHS 25 STATIC_PATHS
26} from '../../initializers/constants' 26} from '../../initializers/constants'
27import { ActorModel } from '../../models/activitypub/actor'
28import { TagModel } from '../../models/video/tag' 27import { TagModel } from '../../models/video/tag'
29import { VideoModel } from '../../models/video/video' 28import { VideoModel } from '../../models/video/video'
30import { VideoFileModel } from '../../models/video/video-file' 29import { VideoFileModel } from '../../models/video/video-file'
@@ -38,7 +37,6 @@ import { JobQueue } from '../job-queue'
38import { ActivitypubHttpFetcherPayload } from '../job-queue/handlers/activitypub-http-fetcher' 37import { ActivitypubHttpFetcherPayload } from '../job-queue/handlers/activitypub-http-fetcher'
39import { createRates } from './video-rates' 38import { createRates } from './video-rates'
40import { addVideoShares, shareVideoByServerAndChannel } from './share' 39import { addVideoShares, shareVideoByServerAndChannel } from './share'
41import { AccountModel } from '../../models/account/account'
42import { fetchVideoByUrl, VideoFetchByUrlType } from '../../helpers/video' 40import { fetchVideoByUrl, VideoFetchByUrlType } from '../../helpers/video'
43import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' 41import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub'
44import { Notifier } from '../notifier' 42import { Notifier } from '../notifier'
@@ -49,15 +47,31 @@ import { VideoShareModel } from '../../models/video/video-share'
49import { VideoCommentModel } from '../../models/video/video-comment' 47import { VideoCommentModel } from '../../models/video/video-comment'
50import { sequelizeTypescript } from '../../initializers/database' 48import { sequelizeTypescript } from '../../initializers/database'
51import { createPlaceholderThumbnail, createVideoMiniatureFromUrl } from '../thumbnail' 49import { createPlaceholderThumbnail, createVideoMiniatureFromUrl } from '../thumbnail'
52import { ThumbnailModel } from '../../models/video/thumbnail'
53import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' 50import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type'
54import { join } from 'path' 51import { join } from 'path'
55import { FilteredModelAttributes } from '../../typings/sequelize' 52import { FilteredModelAttributes } from '../../typings/sequelize'
56import { autoBlacklistVideoIfNeeded } from '../video-blacklist' 53import { autoBlacklistVideoIfNeeded } from '../video-blacklist'
57import { ActorFollowScoreCache } from '../files-cache' 54import { ActorFollowScoreCache } from '../files-cache'
58import { AccountModelIdActor, VideoChannelModelId, VideoChannelModelIdActor } from '../../typings/models' 55import {
56 MAccountIdActor,
57 MChannelAccountLight,
58 MChannelDefault,
59 MChannelId,
60 MVideo,
61 MVideoAccountLight,
62 MVideoAccountLightBlacklistAllFiles,
63 MVideoAP,
64 MVideoAPWithoutCaption,
65 MVideoFile,
66 MVideoFullLight,
67 MVideoId,
68 MVideoThumbnail
69} from '../../typings/models'
70import { MThumbnail } from '../../typings/models/video/thumbnail'
71
72async function federateVideoIfNeeded (videoArg: MVideoAPWithoutCaption, isNewVideo: boolean, transaction?: sequelize.Transaction) {
73 const video = videoArg as MVideoAP
59 74
60async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) {
61 if ( 75 if (
62 // Check this is not a blacklisted video, or unfederated blacklisted video 76 // Check this is not a blacklisted video, or unfederated blacklisted video
63 (video.isBlacklisted() === false || (isNewVideo === false && video.VideoBlacklist.unfederated === false)) && 77 (video.isBlacklisted() === false || (isNewVideo === false && video.VideoBlacklist.unfederated === false)) &&
@@ -102,7 +116,7 @@ async function fetchRemoteVideo (videoUrl: string): Promise<{ response: request.
102 return { response, videoObject: body } 116 return { response, videoObject: body }
103} 117}
104 118
105async function fetchRemoteVideoDescription (video: VideoModel) { 119async function fetchRemoteVideoDescription (video: MVideoAccountLight) {
106 const host = video.VideoChannel.Account.Actor.Server.host 120 const host = video.VideoChannel.Account.Actor.Server.host
107 const path = video.getDescriptionAPIPath() 121 const path = video.getDescriptionAPIPath()
108 const options = { 122 const options = {
@@ -114,14 +128,14 @@ async function fetchRemoteVideoDescription (video: VideoModel) {
114 return body.description ? body.description : '' 128 return body.description ? body.description : ''
115} 129}
116 130
117function fetchRemoteVideoStaticFile (video: VideoModel, path: string, destPath: string) { 131function fetchRemoteVideoStaticFile (video: MVideoAccountLight, path: string, destPath: string) {
118 const url = buildRemoteBaseUrl(video, path) 132 const url = buildRemoteBaseUrl(video, path)
119 133
120 // We need to provide a callback, if no we could have an uncaught exception 134 // We need to provide a callback, if no we could have an uncaught exception
121 return doRequestAndSaveToFile({ uri: url }, destPath) 135 return doRequestAndSaveToFile({ uri: url }, destPath)
122} 136}
123 137
124function buildRemoteBaseUrl (video: VideoModel, path: string) { 138function buildRemoteBaseUrl (video: MVideoAccountLight, path: string) {
125 const host = video.VideoChannel.Account.Actor.Server.host 139 const host = video.VideoChannel.Account.Actor.Server.host
126 140
127 return REMOTE_SCHEME.HTTP + '://' + host + path 141 return REMOTE_SCHEME.HTTP + '://' + host + path
@@ -146,7 +160,7 @@ type SyncParam = {
146 thumbnail: boolean 160 thumbnail: boolean
147 refreshVideo?: boolean 161 refreshVideo?: boolean
148} 162}
149async function syncVideoExternalAttributes (video: VideoModel, fetchedVideo: VideoTorrentObject, syncParam: SyncParam) { 163async function syncVideoExternalAttributes (video: MVideo, fetchedVideo: VideoTorrentObject, syncParam: SyncParam) {
150 logger.info('Adding likes/dislikes/shares/comments of video %s.', video.uuid) 164 logger.info('Adding likes/dislikes/shares/comments of video %s.', video.uuid)
151 165
152 const jobPayloads: ActivitypubHttpFetcherPayload[] = [] 166 const jobPayloads: ActivitypubHttpFetcherPayload[] = []
@@ -194,12 +208,24 @@ async function syncVideoExternalAttributes (video: VideoModel, fetchedVideo: Vid
194 await Bluebird.map(jobPayloads, payload => JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload })) 208 await Bluebird.map(jobPayloads, payload => JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload }))
195} 209}
196 210
211function getOrCreateVideoAndAccountAndChannel (options: {
212 videoObject: { id: string } | string,
213 syncParam?: SyncParam,
214 fetchType?: 'all',
215 allowRefresh?: boolean
216}): Promise<{ video: MVideoAccountLightBlacklistAllFiles, created: boolean, autoBlacklisted?: boolean }>
217function getOrCreateVideoAndAccountAndChannel (options: {
218 videoObject: { id: string } | string,
219 syncParam?: SyncParam,
220 fetchType?: VideoFetchByUrlType,
221 allowRefresh?: boolean
222}): Promise<{ video: MVideoAccountLightBlacklistAllFiles | MVideoThumbnail, created: boolean, autoBlacklisted?: boolean }>
197async function getOrCreateVideoAndAccountAndChannel (options: { 223async function getOrCreateVideoAndAccountAndChannel (options: {
198 videoObject: { id: string } | string, 224 videoObject: { id: string } | string,
199 syncParam?: SyncParam, 225 syncParam?: SyncParam,
200 fetchType?: VideoFetchByUrlType, 226 fetchType?: VideoFetchByUrlType,
201 allowRefresh?: boolean // true by default 227 allowRefresh?: boolean // true by default
202}) { 228}): Promise<{ video: MVideoAccountLightBlacklistAllFiles | MVideoThumbnail, created: boolean, autoBlacklisted?: boolean }> {
203 // Default params 229 // Default params
204 const syncParam = options.syncParam || { likes: true, dislikes: true, shares: true, comments: true, thumbnail: true, refreshVideo: false } 230 const syncParam = options.syncParam || { likes: true, dislikes: true, shares: true, comments: true, thumbnail: true, refreshVideo: false }
205 const fetchType = options.fetchType || 'all' 231 const fetchType = options.fetchType || 'all'
@@ -227,8 +253,9 @@ async function getOrCreateVideoAndAccountAndChannel (options: {
227 const { videoObject: fetchedVideo } = await fetchRemoteVideo(videoUrl) 253 const { videoObject: fetchedVideo } = await fetchRemoteVideo(videoUrl)
228 if (!fetchedVideo) throw new Error('Cannot fetch remote video with url: ' + videoUrl) 254 if (!fetchedVideo) throw new Error('Cannot fetch remote video with url: ' + videoUrl)
229 255
230 const channelActor = await getOrCreateVideoChannelFromVideoObject(fetchedVideo) 256 const actor = await getOrCreateVideoChannelFromVideoObject(fetchedVideo)
231 const { autoBlacklisted, videoCreated } = await retryTransactionWrapper(createVideo, fetchedVideo, channelActor, syncParam.thumbnail) 257 const videoChannel = actor.VideoChannel
258 const { autoBlacklisted, videoCreated } = await retryTransactionWrapper(createVideo, fetchedVideo, videoChannel, syncParam.thumbnail)
232 259
233 await syncVideoExternalAttributes(videoCreated, fetchedVideo, syncParam) 260 await syncVideoExternalAttributes(videoCreated, fetchedVideo, syncParam)
234 261
@@ -236,22 +263,22 @@ async function getOrCreateVideoAndAccountAndChannel (options: {
236} 263}
237 264
238async function updateVideoFromAP (options: { 265async function updateVideoFromAP (options: {
239 video: VideoModel, 266 video: MVideoAccountLightBlacklistAllFiles,
240 videoObject: VideoTorrentObject, 267 videoObject: VideoTorrentObject,
241 account: AccountModelIdActor, 268 account: MAccountIdActor,
242 channel: VideoChannelModelIdActor, 269 channel: MChannelDefault,
243 overrideTo?: string[] 270 overrideTo?: string[]
244}) { 271}) {
245 const { video, videoObject, account, channel, overrideTo } = options 272 const { video, videoObject, account, channel, overrideTo } = options
246 273
247 logger.debug('Updating remote video "%s".', options.videoObject.uuid) 274 logger.debug('Updating remote video "%s".', options.videoObject.uuid, { account, channel })
248 275
249 let videoFieldsSave: any 276 let videoFieldsSave: any
250 const wasPrivateVideo = video.privacy === VideoPrivacy.PRIVATE 277 const wasPrivateVideo = video.privacy === VideoPrivacy.PRIVATE
251 const wasUnlistedVideo = video.privacy === VideoPrivacy.UNLISTED 278 const wasUnlistedVideo = video.privacy === VideoPrivacy.UNLISTED
252 279
253 try { 280 try {
254 let thumbnailModel: ThumbnailModel 281 let thumbnailModel: MThumbnail
255 282
256 try { 283 try {
257 thumbnailModel = await createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE) 284 thumbnailModel = await createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE)
@@ -259,7 +286,7 @@ async function updateVideoFromAP (options: {
259 logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err }) 286 logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err })
260 } 287 }
261 288
262 await sequelizeTypescript.transaction(async t => { 289 const videoUpdated = await sequelizeTypescript.transaction(async t => {
263 const sequelizeOptions = { transaction: t } 290 const sequelizeOptions = { transaction: t }
264 291
265 videoFieldsSave = video.toJSON() 292 videoFieldsSave = video.toJSON()
@@ -293,21 +320,21 @@ async function updateVideoFromAP (options: {
293 video.channelId = videoData.channelId 320 video.channelId = videoData.channelId
294 video.views = videoData.views 321 video.views = videoData.views
295 322
296 await video.save(sequelizeOptions) 323 const videoUpdated = await video.save(sequelizeOptions) as MVideoFullLight
297 324
298 if (thumbnailModel) await video.addAndSaveThumbnail(thumbnailModel, t) 325 if (thumbnailModel) await videoUpdated.addAndSaveThumbnail(thumbnailModel, t)
299 326
300 // FIXME: use icon URL instead 327 // FIXME: use icon URL instead
301 const previewUrl = buildRemoteBaseUrl(video, join(STATIC_PATHS.PREVIEWS, video.getPreview().filename)) 328 const previewUrl = buildRemoteBaseUrl(videoUpdated, join(STATIC_PATHS.PREVIEWS, videoUpdated.getPreview().filename))
302 const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE) 329 const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE)
303 await video.addAndSaveThumbnail(previewModel, t) 330 await videoUpdated.addAndSaveThumbnail(previewModel, t)
304 331
305 { 332 {
306 const videoFileAttributes = videoFileActivityUrlToDBAttributes(video, videoObject) 333 const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoUpdated, videoObject)
307 const newVideoFiles = videoFileAttributes.map(a => new VideoFileModel(a)) 334 const newVideoFiles = videoFileAttributes.map(a => new VideoFileModel(a))
308 335
309 // Remove video files that do not exist anymore 336 // Remove video files that do not exist anymore
310 const destroyTasks = video.VideoFiles 337 const destroyTasks = videoUpdated.VideoFiles
311 .filter(f => !newVideoFiles.find(newFile => newFile.hasSameUniqueKeysThan(f))) 338 .filter(f => !newVideoFiles.find(newFile => newFile.hasSameUniqueKeysThan(f)))
312 .map(f => f.destroy(sequelizeOptions)) 339 .map(f => f.destroy(sequelizeOptions))
313 await Promise.all(destroyTasks) 340 await Promise.all(destroyTasks)
@@ -318,15 +345,15 @@ async function updateVideoFromAP (options: {
318 .then(([ file ]) => file) 345 .then(([ file ]) => file)
319 }) 346 })
320 347
321 video.VideoFiles = await Promise.all(upsertTasks) 348 videoUpdated.VideoFiles = await Promise.all(upsertTasks)
322 } 349 }
323 350
324 { 351 {
325 const streamingPlaylistAttributes = streamingPlaylistActivityUrlToDBAttributes(video, videoObject, video.VideoFiles) 352 const streamingPlaylistAttributes = streamingPlaylistActivityUrlToDBAttributes(videoUpdated, videoObject, videoUpdated.VideoFiles)
326 const newStreamingPlaylists = streamingPlaylistAttributes.map(a => new VideoStreamingPlaylistModel(a)) 353 const newStreamingPlaylists = streamingPlaylistAttributes.map(a => new VideoStreamingPlaylistModel(a))
327 354
328 // Remove video files that do not exist anymore 355 // Remove video files that do not exist anymore
329 const destroyTasks = video.VideoStreamingPlaylists 356 const destroyTasks = videoUpdated.VideoStreamingPlaylists
330 .filter(f => !newStreamingPlaylists.find(newPlaylist => newPlaylist.hasSameUniqueKeysThan(f))) 357 .filter(f => !newStreamingPlaylists.find(newPlaylist => newPlaylist.hasSameUniqueKeysThan(f)))
331 .map(f => f.destroy(sequelizeOptions)) 358 .map(f => f.destroy(sequelizeOptions))
332 await Promise.all(destroyTasks) 359 await Promise.all(destroyTasks)
@@ -337,38 +364,42 @@ async function updateVideoFromAP (options: {
337 .then(([ streamingPlaylist ]) => streamingPlaylist) 364 .then(([ streamingPlaylist ]) => streamingPlaylist)
338 }) 365 })
339 366
340 video.VideoStreamingPlaylists = await Promise.all(upsertTasks) 367 videoUpdated.VideoStreamingPlaylists = await Promise.all(upsertTasks)
341 } 368 }
342 369
343 { 370 {
344 // Update Tags 371 // Update Tags
345 const tags = videoObject.tag.map(tag => tag.name) 372 const tags = videoObject.tag.map(tag => tag.name)
346 const tagInstances = await TagModel.findOrCreateTags(tags, t) 373 const tagInstances = await TagModel.findOrCreateTags(tags, t)
347 await video.$set('Tags', tagInstances, sequelizeOptions) 374 await videoUpdated.$set('Tags', tagInstances, sequelizeOptions)
348 } 375 }
349 376
350 { 377 {
351 // Update captions 378 // Update captions
352 await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(video.id, t) 379 await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(videoUpdated.id, t)
353 380
354 const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { 381 const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => {
355 return VideoCaptionModel.insertOrReplaceLanguage(video.id, c.identifier, t) 382 return VideoCaptionModel.insertOrReplaceLanguage(videoUpdated.id, c.identifier, t)
356 }) 383 })
357 video.VideoCaptions = await Promise.all(videoCaptionsPromises) 384 await Promise.all(videoCaptionsPromises)
358 } 385 }
386
387 return videoUpdated
359 }) 388 })
360 389
361 await autoBlacklistVideoIfNeeded({ 390 await autoBlacklistVideoIfNeeded({
362 video, 391 video: videoUpdated,
363 user: undefined, 392 user: undefined,
364 isRemote: true, 393 isRemote: true,
365 isNew: false, 394 isNew: false,
366 transaction: undefined 395 transaction: undefined
367 }) 396 })
368 397
369 if (wasPrivateVideo || wasUnlistedVideo) Notifier.Instance.notifyOnNewVideoIfNeeded(video) // Notify our users? 398 if (wasPrivateVideo || wasUnlistedVideo) Notifier.Instance.notifyOnNewVideoIfNeeded(videoUpdated) // Notify our users?
370 399
371 logger.info('Remote video with uuid %s updated', videoObject.uuid) 400 logger.info('Remote video with uuid %s updated', videoObject.uuid)
401
402 return videoUpdated
372 } catch (err) { 403 } catch (err) {
373 if (video !== undefined && videoFieldsSave !== undefined) { 404 if (video !== undefined && videoFieldsSave !== undefined) {
374 resetSequelizeInstance(video, videoFieldsSave) 405 resetSequelizeInstance(video, videoFieldsSave)
@@ -381,15 +412,15 @@ async function updateVideoFromAP (options: {
381} 412}
382 413
383async function refreshVideoIfNeeded (options: { 414async function refreshVideoIfNeeded (options: {
384 video: VideoModel, 415 video: MVideoThumbnail,
385 fetchedType: VideoFetchByUrlType, 416 fetchedType: VideoFetchByUrlType,
386 syncParam: SyncParam 417 syncParam: SyncParam
387}): Promise<VideoModel> { 418}): Promise<MVideoThumbnail> {
388 if (!options.video.isOutdated()) return options.video 419 if (!options.video.isOutdated()) return options.video
389 420
390 // We need more attributes if the argument video was fetched with not enough joints 421 // We need more attributes if the argument video was fetched with not enough joints
391 const video = options.fetchedType === 'all' 422 const video = options.fetchedType === 'all'
392 ? options.video 423 ? options.video as MVideoAccountLightBlacklistAllFiles
393 : await VideoModel.loadByUrlAndPopulateAccount(options.video.url) 424 : await VideoModel.loadByUrlAndPopulateAccount(options.video.url)
394 425
395 try { 426 try {
@@ -410,12 +441,11 @@ async function refreshVideoIfNeeded (options: {
410 } 441 }
411 442
412 const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject) 443 const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject)
413 const account = await AccountModel.load(channelActor.VideoChannel.accountId)
414 444
415 const updateOptions = { 445 const updateOptions = {
416 video, 446 video,
417 videoObject, 447 videoObject,
418 account, 448 account: channelActor.VideoChannel.Account,
419 channel: channelActor.VideoChannel 449 channel: channelActor.VideoChannel
420 } 450 }
421 await retryTransactionWrapper(updateVideoFromAP, updateOptions) 451 await retryTransactionWrapper(updateVideoFromAP, updateOptions)
@@ -467,15 +497,15 @@ function isAPPlaylistSegmentHashesUrlObject (tag: any): tag is ActivityPlaylistS
467 return tag.name === 'sha256' && tag.type === 'Link' && urlMediaType === 'application/json' 497 return tag.name === 'sha256' && tag.type === 'Link' && urlMediaType === 'application/json'
468} 498}
469 499
470async function createVideo (videoObject: VideoTorrentObject, channelActor: ActorModel, waitThumbnail = false) { 500async function createVideo (videoObject: VideoTorrentObject, channel: MChannelAccountLight, waitThumbnail = false) {
471 logger.debug('Adding remote video %s.', videoObject.id) 501 logger.debug('Adding remote video %s.', videoObject.id)
472 502
473 const videoData = await videoActivityObjectToDBAttributes(channelActor.VideoChannel, videoObject, videoObject.to) 503 const videoData = await videoActivityObjectToDBAttributes(channel, videoObject, videoObject.to)
474 const video = VideoModel.build(videoData) 504 const video = VideoModel.build(videoData) as MVideoThumbnail
475 505
476 const promiseThumbnail = createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE) 506 const promiseThumbnail = createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE)
477 507
478 let thumbnailModel: ThumbnailModel 508 let thumbnailModel: MThumbnail
479 if (waitThumbnail === true) { 509 if (waitThumbnail === true) {
480 thumbnailModel = await promiseThumbnail 510 thumbnailModel = await promiseThumbnail
481 } 511 }
@@ -483,8 +513,8 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor
483 const { autoBlacklisted, videoCreated } = await sequelizeTypescript.transaction(async t => { 513 const { autoBlacklisted, videoCreated } = await sequelizeTypescript.transaction(async t => {
484 const sequelizeOptions = { transaction: t } 514 const sequelizeOptions = { transaction: t }
485 515
486 const videoCreated = await video.save(sequelizeOptions) 516 const videoCreated = await video.save(sequelizeOptions) as MVideoFullLight
487 videoCreated.VideoChannel = channelActor.VideoChannel 517 videoCreated.VideoChannel = channel
488 518
489 if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t) 519 if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
490 520
@@ -517,15 +547,14 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor
517 const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { 547 const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => {
518 return VideoCaptionModel.insertOrReplaceLanguage(videoCreated.id, c.identifier, t) 548 return VideoCaptionModel.insertOrReplaceLanguage(videoCreated.id, c.identifier, t)
519 }) 549 })
520 const captions = await Promise.all(videoCaptionsPromises) 550 await Promise.all(videoCaptionsPromises)
521 551
522 video.VideoFiles = videoFiles 552 videoCreated.VideoFiles = videoFiles
523 video.VideoStreamingPlaylists = streamingPlaylists 553 videoCreated.VideoStreamingPlaylists = streamingPlaylists
524 video.Tags = tagInstances 554 videoCreated.Tags = tagInstances
525 video.VideoCaptions = captions
526 555
527 const autoBlacklisted = await autoBlacklistVideoIfNeeded({ 556 const autoBlacklisted = await autoBlacklistVideoIfNeeded({
528 video, 557 video: videoCreated,
529 user: undefined, 558 user: undefined,
530 isRemote: true, 559 isRemote: true,
531 isNew: true, 560 isNew: true,
@@ -548,11 +577,7 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor
548 return { autoBlacklisted, videoCreated } 577 return { autoBlacklisted, videoCreated }
549} 578}
550 579
551async function videoActivityObjectToDBAttributes ( 580async function videoActivityObjectToDBAttributes (videoChannel: MChannelId, videoObject: VideoTorrentObject, to: string[] = []) {
552 videoChannel: VideoChannelModelId,
553 videoObject: VideoTorrentObject,
554 to: string[] = []
555) {
556 const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPrivacy.PUBLIC : VideoPrivacy.UNLISTED 581 const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPrivacy.PUBLIC : VideoPrivacy.UNLISTED
557 const duration = videoObject.duration.replace(/[^\d]+/, '') 582 const duration = videoObject.duration.replace(/[^\d]+/, '')
558 583
@@ -603,7 +628,7 @@ async function videoActivityObjectToDBAttributes (
603 } 628 }
604} 629}
605 630
606function videoFileActivityUrlToDBAttributes (video: VideoModel, videoObject: VideoTorrentObject) { 631function videoFileActivityUrlToDBAttributes (video: MVideo, videoObject: VideoTorrentObject) {
607 const fileUrls = videoObject.url.filter(u => isAPVideoUrlObject(u)) as ActivityVideoUrlObject[] 632 const fileUrls = videoObject.url.filter(u => isAPVideoUrlObject(u)) as ActivityVideoUrlObject[]
608 633
609 if (fileUrls.length === 0) { 634 if (fileUrls.length === 0) {
@@ -641,7 +666,7 @@ function videoFileActivityUrlToDBAttributes (video: VideoModel, videoObject: Vid
641 return attributes 666 return attributes
642} 667}
643 668
644function streamingPlaylistActivityUrlToDBAttributes (video: VideoModel, videoObject: VideoTorrentObject, videoFiles: VideoFileModel[]) { 669function streamingPlaylistActivityUrlToDBAttributes (video: MVideoId, videoObject: VideoTorrentObject, videoFiles: MVideoFile[]) {
645 const playlistUrls = videoObject.url.filter(u => isAPStreamingPlaylistUrlObject(u)) as ActivityPlaylistUrlObject[] 670 const playlistUrls = videoObject.url.filter(u => isAPStreamingPlaylistUrlObject(u)) as ActivityPlaylistUrlObject[]
646 if (playlistUrls.length === 0) return [] 671 if (playlistUrls.length === 0) return []
647 672
diff --git a/server/lib/avatar.ts b/server/lib/avatar.ts
index 1b38e6cb5..ad4cdd3ab 100644
--- a/server/lib/avatar.ts
+++ b/server/lib/avatar.ts
@@ -3,8 +3,6 @@ import { sendUpdateActor } from './activitypub/send'
3import { AVATARS_SIZE, LRU_CACHE, QUEUE_CONCURRENCY } from '../initializers/constants' 3import { AVATARS_SIZE, LRU_CACHE, QUEUE_CONCURRENCY } from '../initializers/constants'
4import { updateActorAvatarInstance } from './activitypub' 4import { updateActorAvatarInstance } from './activitypub'
5import { processImage } from '../helpers/image-utils' 5import { processImage } from '../helpers/image-utils'
6import { AccountModel } from '../models/account/account'
7import { VideoChannelModel } from '../models/video/video-channel'
8import { extname, join } from 'path' 6import { extname, join } from 'path'
9import { retryTransactionWrapper } from '../helpers/database-utils' 7import { retryTransactionWrapper } from '../helpers/database-utils'
10import * as uuidv4 from 'uuid/v4' 8import * as uuidv4 from 'uuid/v4'
@@ -13,8 +11,12 @@ import { sequelizeTypescript } from '../initializers/database'
13import * as LRUCache from 'lru-cache' 11import * as LRUCache from 'lru-cache'
14import { queue } from 'async' 12import { queue } from 'async'
15import { downloadImage } from '../helpers/requests' 13import { downloadImage } from '../helpers/requests'
14import { MAccountDefault, MChannelDefault } from '../typings/models'
16 15
17async function updateActorAvatarFile (avatarPhysicalFile: Express.Multer.File, accountOrChannel: AccountModel | VideoChannelModel) { 16async function updateActorAvatarFile (
17 avatarPhysicalFile: Express.Multer.File,
18 accountOrChannel: MAccountDefault | MChannelDefault
19) {
18 const extension = extname(avatarPhysicalFile.filename) 20 const extension = extname(avatarPhysicalFile.filename)
19 const avatarName = uuidv4() + extension 21 const avatarName = uuidv4() + extension
20 const destination = join(CONFIG.STORAGE.AVATARS_DIR, avatarName) 22 const destination = join(CONFIG.STORAGE.AVATARS_DIR, avatarName)
diff --git a/server/lib/blocklist.ts b/server/lib/blocklist.ts
index 1633e500c..28c69b46e 100644
--- a/server/lib/blocklist.ts
+++ b/server/lib/blocklist.ts
@@ -1,6 +1,7 @@
1import { sequelizeTypescript } from '../initializers' 1import { sequelizeTypescript } from '../initializers'
2import { AccountBlocklistModel } from '../models/account/account-blocklist' 2import { AccountBlocklistModel } from '../models/account/account-blocklist'
3import { ServerBlocklistModel } from '../models/server/server-blocklist' 3import { ServerBlocklistModel } from '../models/server/server-blocklist'
4import { MAccountBlocklist, MServerBlocklist } from '@server/typings/models'
4 5
5function addAccountInBlocklist (byAccountId: number, targetAccountId: number) { 6function addAccountInBlocklist (byAccountId: number, targetAccountId: number) {
6 return sequelizeTypescript.transaction(async t => { 7 return sequelizeTypescript.transaction(async t => {
@@ -20,13 +21,13 @@ function addServerInBlocklist (byAccountId: number, targetServerId: number) {
20 }) 21 })
21} 22}
22 23
23function removeAccountFromBlocklist (accountBlock: AccountBlocklistModel) { 24function removeAccountFromBlocklist (accountBlock: MAccountBlocklist) {
24 return sequelizeTypescript.transaction(async t => { 25 return sequelizeTypescript.transaction(async t => {
25 return accountBlock.destroy({ transaction: t }) 26 return accountBlock.destroy({ transaction: t })
26 }) 27 })
27} 28}
28 29
29function removeServerFromBlocklist (serverBlock: ServerBlocklistModel) { 30function removeServerFromBlocklist (serverBlock: MServerBlocklist) {
30 return sequelizeTypescript.transaction(async t => { 31 return sequelizeTypescript.transaction(async t => {
31 return serverBlock.destroy({ transaction: t }) 32 return serverBlock.destroy({ transaction: t })
32 }) 33 })
diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts
index 8841dd2ac..a1f4ae858 100644
--- a/server/lib/client-html.ts
+++ b/server/lib/client-html.ts
@@ -13,6 +13,7 @@ import { VideoChannelModel } from '../models/video/video-channel'
13import * as Bluebird from 'bluebird' 13import * as Bluebird from 'bluebird'
14import { CONFIG } from '../initializers/config' 14import { CONFIG } from '../initializers/config'
15import { logger } from '../helpers/logger' 15import { logger } from '../helpers/logger'
16import { MAccountActor, MChannelActor, MVideo } from '../typings/models'
16 17
17export class ClientHtml { 18export class ClientHtml {
18 19
@@ -41,11 +42,11 @@ export class ClientHtml {
41 42
42 const [ html, video ] = await Promise.all([ 43 const [ html, video ] = await Promise.all([
43 ClientHtml.getIndexHTML(req, res), 44 ClientHtml.getIndexHTML(req, res),
44 VideoModel.load(videoId) 45 VideoModel.loadWithBlacklist(videoId)
45 ]) 46 ])
46 47
47 // Let Angular application handle errors 48 // Let Angular application handle errors
48 if (!video || video.privacy === VideoPrivacy.PRIVATE) { 49 if (!video || video.privacy === VideoPrivacy.PRIVATE || video.VideoBlacklist) {
49 return ClientHtml.getIndexHTML(req, res) 50 return ClientHtml.getIndexHTML(req, res)
50 } 51 }
51 52
@@ -65,7 +66,7 @@ export class ClientHtml {
65 } 66 }
66 67
67 private static async getAccountOrChannelHTMLPage ( 68 private static async getAccountOrChannelHTMLPage (
68 loader: () => Bluebird<AccountModel | VideoChannelModel>, 69 loader: () => Bluebird<MAccountActor | MChannelActor>,
69 req: express.Request, 70 req: express.Request,
70 res: express.Response 71 res: express.Response
71 ) { 72 ) {
@@ -157,7 +158,7 @@ export class ClientHtml {
157 return htmlStringPage.replace('</head>', linkTag + '</head>') 158 return htmlStringPage.replace('</head>', linkTag + '</head>')
158 } 159 }
159 160
160 private static addVideoOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoModel) { 161 private static addVideoOpenGraphAndOEmbedTags (htmlStringPage: string, video: MVideo) {
161 const previewUrl = WEBSERVER.URL + video.getPreviewStaticPath() 162 const previewUrl = WEBSERVER.URL + video.getPreviewStaticPath()
162 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() 163 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
163 164
@@ -236,7 +237,7 @@ export class ClientHtml {
236 return this.addOpenGraphAndOEmbedTags(htmlStringPage, tagsString) 237 return this.addOpenGraphAndOEmbedTags(htmlStringPage, tagsString)
237 } 238 }
238 239
239 private static addAccountOrChannelMetaTags (htmlStringPage: string, entity: AccountModel | VideoChannelModel) { 240 private static addAccountOrChannelMetaTags (htmlStringPage: string, entity: MAccountActor | MChannelActor) {
240 // SEO, use origin account or channel URL 241 // SEO, use origin account or channel URL
241 const metaTags = `<link rel="canonical" href="${entity.Actor.url}" />` 242 const metaTags = `<link rel="canonical" href="${entity.Actor.url}" />`
242 243
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts
index 10e7d0479..bd3d4f252 100644
--- a/server/lib/emailer.ts
+++ b/server/lib/emailer.ts
@@ -2,17 +2,20 @@ import { createTransport, Transporter } from 'nodemailer'
2import { isTestInstance } from '../helpers/core-utils' 2import { isTestInstance } from '../helpers/core-utils'
3import { bunyanLogger, logger } from '../helpers/logger' 3import { bunyanLogger, logger } from '../helpers/logger'
4import { CONFIG } from '../initializers/config' 4import { CONFIG } from '../initializers/config'
5import { UserModel } from '../models/account/user'
6import { VideoModel } from '../models/video/video'
7import { JobQueue } from './job-queue' 5import { JobQueue } from './job-queue'
8import { EmailPayload } from './job-queue/handlers/email' 6import { EmailPayload } from './job-queue/handlers/email'
9import { readFileSync } from 'fs-extra' 7import { readFileSync } from 'fs-extra'
10import { VideoCommentModel } from '../models/video/video-comment'
11import { VideoAbuseModel } from '../models/video/video-abuse'
12import { VideoBlacklistModel } from '../models/video/video-blacklist'
13import { VideoImportModel } from '../models/video/video-import'
14import { ActorFollowModel } from '../models/activitypub/actor-follow'
15import { WEBSERVER } from '../initializers/constants' 8import { WEBSERVER } from '../initializers/constants'
9import {
10 MCommentOwnerVideo,
11 MVideo,
12 MVideoAbuseVideo,
13 MVideoAccountLight,
14 MVideoBlacklistLightVideo,
15 MVideoBlacklistVideo
16} from '../typings/models/video'
17import { MActorFollowActors, MActorFollowFull, MUser } from '../typings/models'
18import { MVideoImport, MVideoImportVideo } from '@server/typings/models/video/video-import'
16 19
17type SendEmailOptions = { 20type SendEmailOptions = {
18 to: string[] 21 to: string[]
@@ -90,7 +93,7 @@ class Emailer {
90 } 93 }
91 } 94 }
92 95
93 addNewVideoFromSubscriberNotification (to: string[], video: VideoModel) { 96 addNewVideoFromSubscriberNotification (to: string[], video: MVideoAccountLight) {
94 const channelName = video.VideoChannel.getDisplayName() 97 const channelName = video.VideoChannel.getDisplayName()
95 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() 98 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
96 99
@@ -111,7 +114,7 @@ class Emailer {
111 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 114 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
112 } 115 }
113 116
114 addNewFollowNotification (to: string[], actorFollow: ActorFollowModel, followType: 'account' | 'channel') { 117 addNewFollowNotification (to: string[], actorFollow: MActorFollowFull, followType: 'account' | 'channel') {
115 const followerName = actorFollow.ActorFollower.Account.getDisplayName() 118 const followerName = actorFollow.ActorFollower.Account.getDisplayName()
116 const followingName = (actorFollow.ActorFollowing.VideoChannel || actorFollow.ActorFollowing.Account).getDisplayName() 119 const followingName = (actorFollow.ActorFollowing.VideoChannel || actorFollow.ActorFollowing.Account).getDisplayName()
117 120
@@ -130,7 +133,7 @@ class Emailer {
130 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 133 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
131 } 134 }
132 135
133 addNewInstanceFollowerNotification (to: string[], actorFollow: ActorFollowModel) { 136 addNewInstanceFollowerNotification (to: string[], actorFollow: MActorFollowActors) {
134 const awaitingApproval = actorFollow.state === 'pending' ? ' awaiting manual approval.' : '' 137 const awaitingApproval = actorFollow.state === 'pending' ? ' awaiting manual approval.' : ''
135 138
136 const text = `Hi dear admin,\n\n` + 139 const text = `Hi dear admin,\n\n` +
@@ -148,7 +151,23 @@ class Emailer {
148 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 151 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
149 } 152 }
150 153
151 myVideoPublishedNotification (to: string[], video: VideoModel) { 154 addAutoInstanceFollowingNotification (to: string[], actorFollow: MActorFollowActors) {
155 const text = `Hi dear admin,\n\n` +
156 `Your instance automatically followed a new instance: ${actorFollow.ActorFollowing.url}` +
157 `\n\n` +
158 `Cheers,\n` +
159 `${CONFIG.EMAIL.BODY.SIGNATURE}`
160
161 const emailPayload: EmailPayload = {
162 to,
163 subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Auto instance following',
164 text
165 }
166
167 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
168 }
169
170 myVideoPublishedNotification (to: string[], video: MVideo) {
152 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() 171 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
153 172
154 const text = `Hi dear user,\n\n` + 173 const text = `Hi dear user,\n\n` +
@@ -168,7 +187,7 @@ class Emailer {
168 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 187 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
169 } 188 }
170 189
171 myVideoImportSuccessNotification (to: string[], videoImport: VideoImportModel) { 190 myVideoImportSuccessNotification (to: string[], videoImport: MVideoImportVideo) {
172 const videoUrl = WEBSERVER.URL + videoImport.Video.getWatchStaticPath() 191 const videoUrl = WEBSERVER.URL + videoImport.Video.getWatchStaticPath()
173 192
174 const text = `Hi dear user,\n\n` + 193 const text = `Hi dear user,\n\n` +
@@ -188,7 +207,7 @@ class Emailer {
188 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 207 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
189 } 208 }
190 209
191 myVideoImportErrorNotification (to: string[], videoImport: VideoImportModel) { 210 myVideoImportErrorNotification (to: string[], videoImport: MVideoImport) {
192 const importUrl = WEBSERVER.URL + '/my-account/video-imports' 211 const importUrl = WEBSERVER.URL + '/my-account/video-imports'
193 212
194 const text = `Hi dear user,\n\n` + 213 const text = `Hi dear user,\n\n` +
@@ -208,7 +227,7 @@ class Emailer {
208 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 227 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
209 } 228 }
210 229
211 addNewCommentOnMyVideoNotification (to: string[], comment: VideoCommentModel) { 230 addNewCommentOnMyVideoNotification (to: string[], comment: MCommentOwnerVideo) {
212 const accountName = comment.Account.getDisplayName() 231 const accountName = comment.Account.getDisplayName()
213 const video = comment.Video 232 const video = comment.Video
214 const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() 233 const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath()
@@ -230,7 +249,7 @@ class Emailer {
230 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 249 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
231 } 250 }
232 251
233 addNewCommentMentionNotification (to: string[], comment: VideoCommentModel) { 252 addNewCommentMentionNotification (to: string[], comment: MCommentOwnerVideo) {
234 const accountName = comment.Account.getDisplayName() 253 const accountName = comment.Account.getDisplayName()
235 const video = comment.Video 254 const video = comment.Video
236 const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() 255 const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath()
@@ -252,7 +271,7 @@ class Emailer {
252 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 271 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
253 } 272 }
254 273
255 addVideoAbuseModeratorsNotification (to: string[], videoAbuse: VideoAbuseModel) { 274 addVideoAbuseModeratorsNotification (to: string[], videoAbuse: MVideoAbuseVideo) {
256 const videoUrl = WEBSERVER.URL + videoAbuse.Video.getWatchStaticPath() 275 const videoUrl = WEBSERVER.URL + videoAbuse.Video.getWatchStaticPath()
257 276
258 const text = `Hi,\n\n` + 277 const text = `Hi,\n\n` +
@@ -269,9 +288,9 @@ class Emailer {
269 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 288 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
270 } 289 }
271 290
272 addVideoAutoBlacklistModeratorsNotification (to: string[], video: VideoModel) { 291 addVideoAutoBlacklistModeratorsNotification (to: string[], videoBlacklist: MVideoBlacklistLightVideo) {
273 const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list' 292 const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list'
274 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() 293 const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath()
275 294
276 const text = `Hi,\n\n` + 295 const text = `Hi,\n\n` +
277 `A recently added video was auto-blacklisted and requires moderator review before publishing.` + 296 `A recently added video was auto-blacklisted and requires moderator review before publishing.` +
@@ -292,7 +311,7 @@ class Emailer {
292 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 311 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
293 } 312 }
294 313
295 addNewUserRegistrationNotification (to: string[], user: UserModel) { 314 addNewUserRegistrationNotification (to: string[], user: MUser) {
296 const text = `Hi,\n\n` + 315 const text = `Hi,\n\n` +
297 `User ${user.username} just registered on ${WEBSERVER.HOST} PeerTube instance.\n\n` + 316 `User ${user.username} just registered on ${WEBSERVER.HOST} PeerTube instance.\n\n` +
298 `Cheers,\n` + 317 `Cheers,\n` +
@@ -307,7 +326,7 @@ class Emailer {
307 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 326 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
308 } 327 }
309 328
310 addVideoBlacklistNotification (to: string[], videoBlacklist: VideoBlacklistModel) { 329 addVideoBlacklistNotification (to: string[], videoBlacklist: MVideoBlacklistVideo) {
311 const videoName = videoBlacklist.Video.name 330 const videoName = videoBlacklist.Video.name
312 const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath() 331 const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath()
313 332
@@ -329,7 +348,7 @@ class Emailer {
329 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 348 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
330 } 349 }
331 350
332 addVideoUnblacklistNotification (to: string[], video: VideoModel) { 351 addVideoUnblacklistNotification (to: string[], video: MVideo) {
333 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() 352 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
334 353
335 const text = 'Hi,\n\n' + 354 const text = 'Hi,\n\n' +
@@ -381,7 +400,7 @@ class Emailer {
381 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 400 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
382 } 401 }
383 402
384 addUserBlockJob (user: UserModel, blocked: boolean, reason?: string) { 403 addUserBlockJob (user: MUser, blocked: boolean, reason?: string) {
385 const reasonString = reason ? ` for the following reason: ${reason}` : '' 404 const reasonString = reason ? ` for the following reason: ${reason}` : ''
386 const blockedWord = blocked ? 'blocked' : 'unblocked' 405 const blockedWord = blocked ? 'blocked' : 'unblocked'
387 const blockedString = `Your account ${user.username} on ${WEBSERVER.HOST} has been ${blockedWord}${reasonString}.` 406 const blockedString = `Your account ${user.username} on ${WEBSERVER.HOST} has been ${blockedWord}${reasonString}.`
diff --git a/server/lib/hls.ts b/server/lib/hls.ts
index 98da4dcd8..05136c21c 100644
--- a/server/lib/hls.ts
+++ b/server/lib/hls.ts
@@ -1,4 +1,3 @@
1import { VideoModel } from '../models/video/video'
2import { basename, dirname, join } from 'path' 1import { basename, dirname, join } from 'path'
3import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION } from '../initializers/constants' 2import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION } from '../initializers/constants'
4import { close, ensureDir, move, open, outputJSON, pathExists, read, readFile, remove, writeFile } from 'fs-extra' 3import { close, ensureDir, move, open, outputJSON, pathExists, read, readFile, remove, writeFile } from 'fs-extra'
@@ -12,6 +11,7 @@ import { flatten, uniq } from 'lodash'
12import { VideoFileModel } from '../models/video/video-file' 11import { VideoFileModel } from '../models/video/video-file'
13import { CONFIG } from '../initializers/config' 12import { CONFIG } from '../initializers/config'
14import { sequelizeTypescript } from '../initializers/database' 13import { sequelizeTypescript } from '../initializers/database'
14import { MVideoWithFile } from '@server/typings/models'
15 15
16async function updateStreamingPlaylistsInfohashesIfNeeded () { 16async function updateStreamingPlaylistsInfohashesIfNeeded () {
17 const playlistsToUpdate = await VideoStreamingPlaylistModel.listByIncorrectPeerVersion() 17 const playlistsToUpdate = await VideoStreamingPlaylistModel.listByIncorrectPeerVersion()
@@ -28,7 +28,7 @@ async function updateStreamingPlaylistsInfohashesIfNeeded () {
28 } 28 }
29} 29}
30 30
31async function updateMasterHLSPlaylist (video: VideoModel) { 31async function updateMasterHLSPlaylist (video: MVideoWithFile) {
32 const directory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) 32 const directory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)
33 const masterPlaylists: string[] = [ '#EXTM3U', '#EXT-X-VERSION:3' ] 33 const masterPlaylists: string[] = [ '#EXTM3U', '#EXT-X-VERSION:3' ]
34 const masterPlaylistPath = join(directory, VideoStreamingPlaylistModel.getMasterHlsPlaylistFilename()) 34 const masterPlaylistPath = join(directory, VideoStreamingPlaylistModel.getMasterHlsPlaylistFilename())
@@ -55,7 +55,7 @@ async function updateMasterHLSPlaylist (video: VideoModel) {
55 await writeFile(masterPlaylistPath, masterPlaylists.join('\n') + '\n') 55 await writeFile(masterPlaylistPath, masterPlaylists.join('\n') + '\n')
56} 56}
57 57
58async function updateSha256Segments (video: VideoModel) { 58async function updateSha256Segments (video: MVideoWithFile) {
59 const json: { [filename: string]: { [range: string]: string } } = {} 59 const json: { [filename: string]: { [range: string]: string } } = {}
60 60
61 const playlistDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) 61 const playlistDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)
diff --git a/server/lib/job-queue/handlers/activitypub-follow.ts b/server/lib/job-queue/handlers/activitypub-follow.ts
index 4ae66cd01..af7c8a838 100644
--- a/server/lib/job-queue/handlers/activitypub-follow.ts
+++ b/server/lib/job-queue/handlers/activitypub-follow.ts
@@ -10,11 +10,13 @@ import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
10import { ActorModel } from '../../../models/activitypub/actor' 10import { ActorModel } from '../../../models/activitypub/actor'
11import { Notifier } from '../../notifier' 11import { Notifier } from '../../notifier'
12import { sequelizeTypescript } from '../../../initializers/database' 12import { sequelizeTypescript } from '../../../initializers/database'
13import { MActor, MActorFollowActors, MActorFull } from '../../../typings/models'
13 14
14export type ActivitypubFollowPayload = { 15export type ActivitypubFollowPayload = {
15 followerActorId: number 16 followerActorId: number
16 name: string 17 name: string
17 host: string 18 host: string
19 isAutoFollow?: boolean
18} 20}
19 21
20async function processActivityPubFollow (job: Bull.Job) { 22async function processActivityPubFollow (job: Bull.Job) {
@@ -23,18 +25,18 @@ async function processActivityPubFollow (job: Bull.Job) {
23 25
24 logger.info('Processing ActivityPub follow in job %d.', job.id) 26 logger.info('Processing ActivityPub follow in job %d.', job.id)
25 27
26 let targetActor: ActorModel 28 let targetActor: MActorFull
27 if (!host || host === WEBSERVER.HOST) { 29 if (!host || host === WEBSERVER.HOST) {
28 targetActor = await ActorModel.loadLocalByName(payload.name) 30 targetActor = await ActorModel.loadLocalByName(payload.name)
29 } else { 31 } else {
30 const sanitizedHost = sanitizeHost(host, REMOTE_SCHEME.HTTP) 32 const sanitizedHost = sanitizeHost(host, REMOTE_SCHEME.HTTP)
31 const actorUrl = await loadActorUrlOrGetFromWebfinger(payload.name + '@' + sanitizedHost) 33 const actorUrl = await loadActorUrlOrGetFromWebfinger(payload.name + '@' + sanitizedHost)
32 targetActor = await getOrCreateActorAndServerAndModel(actorUrl) 34 targetActor = await getOrCreateActorAndServerAndModel(actorUrl, 'all')
33 } 35 }
34 36
35 const fromActor = await ActorModel.load(payload.followerActorId) 37 const fromActor = await ActorModel.load(payload.followerActorId)
36 38
37 return retryTransactionWrapper(follow, fromActor, targetActor) 39 return retryTransactionWrapper(follow, fromActor, targetActor, payload.isAutoFollow)
38} 40}
39// --------------------------------------------------------------------------- 41// ---------------------------------------------------------------------------
40 42
@@ -44,7 +46,7 @@ export {
44 46
45// --------------------------------------------------------------------------- 47// ---------------------------------------------------------------------------
46 48
47async function follow (fromActor: ActorModel, targetActor: ActorModel) { 49async function follow (fromActor: MActor, targetActor: MActorFull, isAutoFollow = false) {
48 if (fromActor.id === targetActor.id) { 50 if (fromActor.id === targetActor.id) {
49 throw new Error('Follower is the same than target actor.') 51 throw new Error('Follower is the same than target actor.')
50 } 52 }
@@ -53,7 +55,7 @@ async function follow (fromActor: ActorModel, targetActor: ActorModel) {
53 const state = !fromActor.serverId && !targetActor.serverId ? 'accepted' : 'pending' 55 const state = !fromActor.serverId && !targetActor.serverId ? 'accepted' : 'pending'
54 56
55 const actorFollow = await sequelizeTypescript.transaction(async t => { 57 const actorFollow = await sequelizeTypescript.transaction(async t => {
56 const [ actorFollow ] = await ActorFollowModel.findOrCreate({ 58 const [ actorFollow ] = await ActorFollowModel.findOrCreate<MActorFollowActors>({
57 where: { 59 where: {
58 actorId: fromActor.id, 60 actorId: fromActor.id,
59 targetActorId: targetActor.id 61 targetActorId: targetActor.id
@@ -74,5 +76,15 @@ async function follow (fromActor: ActorModel, targetActor: ActorModel) {
74 return actorFollow 76 return actorFollow
75 }) 77 })
76 78
77 if (actorFollow.state === 'accepted') Notifier.Instance.notifyOfNewUserFollow(actorFollow) 79 const followerFull = await ActorModel.loadFull(fromActor.id)
80
81 const actorFollowFull = Object.assign(actorFollow, {
82 ActorFollowing: targetActor,
83 ActorFollower: followerFull
84 })
85
86 if (actorFollow.state === 'accepted') Notifier.Instance.notifyOfNewUserFollow(actorFollowFull)
87 if (isAutoFollow === true) Notifier.Instance.notifyOfAutoInstanceFollowing(actorFollowFull)
88
89 return actorFollow
78} 90}
diff --git a/server/lib/job-queue/handlers/activitypub-http-fetcher.ts b/server/lib/job-queue/handlers/activitypub-http-fetcher.ts
index c3f59dc77..0182c5169 100644
--- a/server/lib/job-queue/handlers/activitypub-http-fetcher.ts
+++ b/server/lib/job-queue/handlers/activitypub-http-fetcher.ts
@@ -11,6 +11,7 @@ import { AccountModel } from '../../../models/account/account'
11import { AccountVideoRateModel } from '../../../models/account/account-video-rate' 11import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
12import { VideoShareModel } from '../../../models/video/video-share' 12import { VideoShareModel } from '../../../models/video/video-share'
13import { VideoCommentModel } from '../../../models/video/video-comment' 13import { VideoCommentModel } from '../../../models/video/video-comment'
14import { MAccountDefault, MVideoFullLight } from '../../../typings/models'
14 15
15type FetchType = 'activity' | 'video-likes' | 'video-dislikes' | 'video-shares' | 'video-comments' | 'account-playlists' 16type FetchType = 'activity' | 'video-likes' | 'video-dislikes' | 'video-shares' | 'video-comments' | 'account-playlists'
16 17
@@ -26,10 +27,10 @@ async function processActivityPubHttpFetcher (job: Bull.Job) {
26 27
27 const payload = job.data as ActivitypubHttpFetcherPayload 28 const payload = job.data as ActivitypubHttpFetcherPayload
28 29
29 let video: VideoModel 30 let video: MVideoFullLight
30 if (payload.videoId) video = await VideoModel.loadAndPopulateAccountAndServerAndTags(payload.videoId) 31 if (payload.videoId) video = await VideoModel.loadAndPopulateAccountAndServerAndTags(payload.videoId)
31 32
32 let account: AccountModel 33 let account: MAccountDefault
33 if (payload.accountId) account = await AccountModel.load(payload.accountId) 34 if (payload.accountId) account = await AccountModel.load(payload.accountId)
34 35
35 const fetcherType: { [ id in FetchType ]: (items: any[]) => Promise<any> } = { 36 const fetcherType: { [ id in FetchType ]: (items: any[]) => Promise<any> } = {
diff --git a/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts b/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts
index cdee1f6fd..d3bde6e6a 100644
--- a/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts
+++ b/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts
@@ -3,6 +3,7 @@ import { getServerActor } from '../../../../helpers/utils'
3import { ActorModel } from '../../../../models/activitypub/actor' 3import { ActorModel } from '../../../../models/activitypub/actor'
4import { sha256 } from '../../../../helpers/core-utils' 4import { sha256 } from '../../../../helpers/core-utils'
5import { HTTP_SIGNATURE } from '../../../../initializers/constants' 5import { HTTP_SIGNATURE } from '../../../../initializers/constants'
6import { MActor } from '../../../../typings/models'
6 7
7type Payload = { body: any, signatureActorId?: number } 8type Payload = { body: any, signatureActorId?: number }
8 9
@@ -19,7 +20,8 @@ async function computeBody (payload: Payload) {
19} 20}
20 21
21async function buildSignedRequestOptions (payload: Payload) { 22async function buildSignedRequestOptions (payload: Payload) {
22 let actor: ActorModel | null 23 let actor: MActor | null
24
23 if (payload.signatureActorId) { 25 if (payload.signatureActorId) {
24 actor = await ActorModel.load(payload.signatureActorId) 26 actor = await ActorModel.load(payload.signatureActorId)
25 if (!actor) throw new Error('Unknown signature actor id.') 27 if (!actor) throw new Error('Unknown signature actor id.')
diff --git a/server/lib/job-queue/handlers/video-file-import.ts b/server/lib/job-queue/handlers/video-file-import.ts
index 8cacb0ef3..5c5b7dccb 100644
--- a/server/lib/job-queue/handlers/video-file-import.ts
+++ b/server/lib/job-queue/handlers/video-file-import.ts
@@ -6,6 +6,7 @@ import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg
6import { copy, stat } from 'fs-extra' 6import { copy, stat } from 'fs-extra'
7import { VideoFileModel } from '../../../models/video/video-file' 7import { VideoFileModel } from '../../../models/video/video-file'
8import { extname } from 'path' 8import { extname } from 'path'
9import { MVideoFile, MVideoWithFile } from '@server/typings/models'
9 10
10export type VideoFileImportPayload = { 11export type VideoFileImportPayload = {
11 videoUUID: string, 12 videoUUID: string,
@@ -37,7 +38,7 @@ export {
37 38
38// --------------------------------------------------------------------------- 39// ---------------------------------------------------------------------------
39 40
40async function updateVideoFile (video: VideoModel, inputFilePath: string) { 41async function updateVideoFile (video: MVideoWithFile, inputFilePath: string) {
41 const { videoFileResolution } = await getVideoFileResolution(inputFilePath) 42 const { videoFileResolution } = await getVideoFileResolution(inputFilePath)
42 const { size } = await stat(inputFilePath) 43 const { size } = await stat(inputFilePath)
43 const fps = await getVideoFileFPS(inputFilePath) 44 const fps = await getVideoFileFPS(inputFilePath)
@@ -48,7 +49,7 @@ async function updateVideoFile (video: VideoModel, inputFilePath: string) {
48 size, 49 size,
49 fps, 50 fps,
50 videoId: video.id 51 videoId: video.id
51 }) 52 }) as MVideoFile
52 53
53 const currentVideoFile = video.VideoFiles.find(videoFile => videoFile.resolution === updatedVideoFile.resolution) 54 const currentVideoFile = video.VideoFiles.find(videoFile => videoFile.resolution === updatedVideoFile.resolution)
54 55
@@ -60,9 +61,9 @@ async function updateVideoFile (video: VideoModel, inputFilePath: string) {
60 video.VideoFiles = video.VideoFiles.filter(f => f !== currentVideoFile) 61 video.VideoFiles = video.VideoFiles.filter(f => f !== currentVideoFile)
61 62
62 // Update the database 63 // Update the database
63 currentVideoFile.set('extname', updatedVideoFile.extname) 64 currentVideoFile.extname = updatedVideoFile.extname
64 currentVideoFile.set('size', updatedVideoFile.size) 65 currentVideoFile.size = updatedVideoFile.size
65 currentVideoFile.set('fps', updatedVideoFile.fps) 66 currentVideoFile.fps = updatedVideoFile.fps
66 67
67 updatedVideoFile = currentVideoFile 68 updatedVideoFile = currentVideoFile
68 } 69 }
diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts
index 13b741180..93a3e9d90 100644
--- a/server/lib/job-queue/handlers/video-import.ts
+++ b/server/lib/job-queue/handlers/video-import.ts
@@ -17,9 +17,11 @@ import { move, remove, stat } from 'fs-extra'
17import { Notifier } from '../../notifier' 17import { Notifier } from '../../notifier'
18import { CONFIG } from '../../../initializers/config' 18import { CONFIG } from '../../../initializers/config'
19import { sequelizeTypescript } from '../../../initializers/database' 19import { sequelizeTypescript } from '../../../initializers/database'
20import { ThumbnailModel } from '../../../models/video/thumbnail'
21import { createVideoMiniatureFromUrl, generateVideoMiniature } from '../../thumbnail' 20import { createVideoMiniatureFromUrl, generateVideoMiniature } from '../../thumbnail'
22import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' 21import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
22import { MThumbnail } from '../../../typings/models/video/thumbnail'
23import { MVideoImportDefault, MVideoImportDefaultFiles, MVideoImportVideo } from '@server/typings/models/video/video-import'
24import { MVideoBlacklistVideo, MVideoBlacklist } from '@server/typings/models'
23 25
24type VideoImportYoutubeDLPayload = { 26type VideoImportYoutubeDLPayload = {
25 type: 'youtube-dl' 27 type: 'youtube-dl'
@@ -110,7 +112,7 @@ type ProcessFileOptions = {
110 generateThumbnail: boolean 112 generateThumbnail: boolean
111 generatePreview: boolean 113 generatePreview: boolean
112} 114}
113async function processFile (downloader: () => Promise<string>, videoImport: VideoImportModel, options: ProcessFileOptions) { 115async function processFile (downloader: () => Promise<string>, videoImport: MVideoImportDefault, options: ProcessFileOptions) {
114 let tempVideoPath: string 116 let tempVideoPath: string
115 let videoDestFile: string 117 let videoDestFile: string
116 let videoFile: VideoFileModel 118 let videoFile: VideoFileModel
@@ -139,41 +141,44 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
139 videoId: videoImport.videoId 141 videoId: videoImport.videoId
140 } 142 }
141 videoFile = new VideoFileModel(videoFileData) 143 videoFile = new VideoFileModel(videoFileData)
144
145 const videoWithFiles = Object.assign(videoImport.Video, { VideoFiles: [ videoFile ] })
142 // To clean files if the import fails 146 // To clean files if the import fails
143 videoImport.Video.VideoFiles = [ videoFile ] 147 const videoImportWithFiles: MVideoImportDefaultFiles = Object.assign(videoImport, { Video: videoWithFiles })
144 148
145 // Move file 149 // Move file
146 videoDestFile = join(CONFIG.STORAGE.VIDEOS_DIR, videoImport.Video.getVideoFilename(videoFile)) 150 videoDestFile = join(CONFIG.STORAGE.VIDEOS_DIR, videoImportWithFiles.Video.getVideoFilename(videoFile))
147 await move(tempVideoPath, videoDestFile) 151 await move(tempVideoPath, videoDestFile)
148 tempVideoPath = null // This path is not used anymore 152 tempVideoPath = null // This path is not used anymore
149 153
150 // Process thumbnail 154 // Process thumbnail
151 let thumbnailModel: ThumbnailModel 155 let thumbnailModel: MThumbnail
152 if (options.downloadThumbnail && options.thumbnailUrl) { 156 if (options.downloadThumbnail && options.thumbnailUrl) {
153 thumbnailModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImport.Video, ThumbnailType.MINIATURE) 157 thumbnailModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImportWithFiles.Video, ThumbnailType.MINIATURE)
154 } else if (options.generateThumbnail || options.downloadThumbnail) { 158 } else if (options.generateThumbnail || options.downloadThumbnail) {
155 thumbnailModel = await generateVideoMiniature(videoImport.Video, videoFile, ThumbnailType.MINIATURE) 159 thumbnailModel = await generateVideoMiniature(videoImportWithFiles.Video, videoFile, ThumbnailType.MINIATURE)
156 } 160 }
157 161
158 // Process preview 162 // Process preview
159 let previewModel: ThumbnailModel 163 let previewModel: MThumbnail
160 if (options.downloadPreview && options.thumbnailUrl) { 164 if (options.downloadPreview && options.thumbnailUrl) {
161 previewModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImport.Video, ThumbnailType.PREVIEW) 165 previewModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImportWithFiles.Video, ThumbnailType.PREVIEW)
162 } else if (options.generatePreview || options.downloadPreview) { 166 } else if (options.generatePreview || options.downloadPreview) {
163 previewModel = await generateVideoMiniature(videoImport.Video, videoFile, ThumbnailType.PREVIEW) 167 previewModel = await generateVideoMiniature(videoImportWithFiles.Video, videoFile, ThumbnailType.PREVIEW)
164 } 168 }
165 169
166 // Create torrent 170 // Create torrent
167 await videoImport.Video.createTorrentAndSetInfoHash(videoFile) 171 await videoImportWithFiles.Video.createTorrentAndSetInfoHash(videoFile)
172
173 const { videoImportUpdated, video } = await sequelizeTypescript.transaction(async t => {
174 const videoImportToUpdate = videoImportWithFiles as MVideoImportVideo
168 175
169 const videoImportUpdated: VideoImportModel = await sequelizeTypescript.transaction(async t => {
170 // Refresh video 176 // Refresh video
171 const video = await VideoModel.load(videoImport.videoId, t) 177 const video = await VideoModel.load(videoImportToUpdate.videoId, t)
172 if (!video) throw new Error('Video linked to import ' + videoImport.videoId + ' does not exist anymore.') 178 if (!video) throw new Error('Video linked to import ' + videoImportToUpdate.videoId + ' does not exist anymore.')
173 videoImport.Video = video
174 179
175 const videoFileCreated = await videoFile.save({ transaction: t }) 180 const videoFileCreated = await videoFile.save({ transaction: t })
176 video.VideoFiles = [ videoFileCreated ] 181 videoImportToUpdate.Video = Object.assign(video, { VideoFiles: [ videoFileCreated ] })
177 182
178 // Update video DB object 183 // Update video DB object
179 video.duration = duration 184 video.duration = duration
@@ -188,25 +193,27 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
188 await federateVideoIfNeeded(videoForFederation, true, t) 193 await federateVideoIfNeeded(videoForFederation, true, t)
189 194
190 // Update video import object 195 // Update video import object
191 videoImport.state = VideoImportState.SUCCESS 196 videoImportToUpdate.state = VideoImportState.SUCCESS
192 const videoImportUpdated = await videoImport.save({ transaction: t }) 197 const videoImportUpdated = await videoImportToUpdate.save({ transaction: t }) as MVideoImportVideo
198 videoImportUpdated.Video = video
193 199
194 logger.info('Video %s imported.', video.uuid) 200 logger.info('Video %s imported.', video.uuid)
195 201
196 videoImportUpdated.Video = videoForFederation 202 return { videoImportUpdated, video: videoForFederation }
197 return videoImportUpdated
198 }) 203 })
199 204
200 Notifier.Instance.notifyOnFinishedVideoImport(videoImportUpdated, true) 205 Notifier.Instance.notifyOnFinishedVideoImport(videoImportUpdated, true)
201 206
202 if (videoImportUpdated.Video.isBlacklisted()) { 207 if (video.isBlacklisted()) {
203 Notifier.Instance.notifyOnVideoAutoBlacklist(videoImportUpdated.Video) 208 const videoBlacklist = Object.assign(video.VideoBlacklist, { Video: video })
209
210 Notifier.Instance.notifyOnVideoAutoBlacklist(videoBlacklist)
204 } else { 211 } else {
205 Notifier.Instance.notifyOnNewVideoIfNeeded(videoImportUpdated.Video) 212 Notifier.Instance.notifyOnNewVideoIfNeeded(video)
206 } 213 }
207 214
208 // Create transcoding jobs? 215 // Create transcoding jobs?
209 if (videoImportUpdated.Video.state === VideoState.TO_TRANSCODE) { 216 if (video.state === VideoState.TO_TRANSCODE) {
210 // Put uuid because we don't have id auto incremented for now 217 // Put uuid because we don't have id auto incremented for now
211 const dataInput = { 218 const dataInput = {
212 type: 'optimize' as 'optimize', 219 type: 'optimize' as 'optimize',
diff --git a/server/lib/job-queue/handlers/video-transcoding.ts b/server/lib/job-queue/handlers/video-transcoding.ts
index 981daf9a1..2ebe15bcb 100644
--- a/server/lib/job-queue/handlers/video-transcoding.ts
+++ b/server/lib/job-queue/handlers/video-transcoding.ts
@@ -11,6 +11,7 @@ import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils'
11import { generateHlsPlaylist, optimizeVideofile, transcodeOriginalVideofile, mergeAudioVideofile } from '../../video-transcoding' 11import { generateHlsPlaylist, optimizeVideofile, transcodeOriginalVideofile, mergeAudioVideofile } from '../../video-transcoding'
12import { Notifier } from '../../notifier' 12import { Notifier } from '../../notifier'
13import { CONFIG } from '../../../initializers/config' 13import { CONFIG } from '../../../initializers/config'
14import { MVideoUUID, MVideoWithFile } from '@server/typings/models'
14 15
15interface BaseTranscodingPayload { 16interface BaseTranscodingPayload {
16 videoUUID: string 17 videoUUID: string
@@ -73,7 +74,7 @@ async function processVideoTranscoding (job: Bull.Job) {
73 return video 74 return video
74} 75}
75 76
76async function onHlsPlaylistGenerationSuccess (video: VideoModel) { 77async function onHlsPlaylistGenerationSuccess (video: MVideoUUID) {
77 if (video === undefined) return undefined 78 if (video === undefined) return undefined
78 79
79 await sequelizeTypescript.transaction(async t => { 80 await sequelizeTypescript.transaction(async t => {
@@ -87,7 +88,7 @@ async function onHlsPlaylistGenerationSuccess (video: VideoModel) {
87 }) 88 })
88} 89}
89 90
90async function publishNewResolutionIfNeeded (video: VideoModel, payload?: NewResolutionTranscodingPayload | MergeAudioTranscodingPayload) { 91async function publishNewResolutionIfNeeded (video: MVideoUUID, payload?: NewResolutionTranscodingPayload | MergeAudioTranscodingPayload) {
91 const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => { 92 const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => {
92 // Maybe the video changed in database, refresh it 93 // Maybe the video changed in database, refresh it
93 let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t) 94 let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t)
@@ -119,7 +120,7 @@ async function publishNewResolutionIfNeeded (video: VideoModel, payload?: NewRes
119 await createHlsJobIfEnabled(payload) 120 await createHlsJobIfEnabled(payload)
120} 121}
121 122
122async function onVideoFileOptimizerSuccess (videoArg: VideoModel, payload: OptimizeTranscodingPayload) { 123async function onVideoFileOptimizerSuccess (videoArg: MVideoWithFile, payload: OptimizeTranscodingPayload) {
123 if (videoArg === undefined) return undefined 124 if (videoArg === undefined) return undefined
124 125
125 // Outside the transaction (IO on disk) 126 // Outside the transaction (IO on disk)
diff --git a/server/lib/notifier.ts b/server/lib/notifier.ts
index a7dfb0979..b7cc2607d 100644
--- a/server/lib/notifier.ts
+++ b/server/lib/notifier.ts
@@ -1,20 +1,30 @@
1import { UserNotificationSettingValue, UserNotificationType, UserRight } from '../../shared/models/users' 1import { UserNotificationSettingValue, UserNotificationType, UserRight } from '../../shared/models/users'
2import { logger } from '../helpers/logger' 2import { logger } from '../helpers/logger'
3import { VideoModel } from '../models/video/video'
4import { Emailer } from './emailer' 3import { Emailer } from './emailer'
5import { UserNotificationModel } from '../models/account/user-notification' 4import { UserNotificationModel } from '../models/account/user-notification'
6import { VideoCommentModel } from '../models/video/video-comment'
7import { UserModel } from '../models/account/user' 5import { UserModel } from '../models/account/user'
8import { PeerTubeSocket } from './peertube-socket' 6import { PeerTubeSocket } from './peertube-socket'
9import { CONFIG } from '../initializers/config' 7import { CONFIG } from '../initializers/config'
10import { VideoPrivacy, VideoState } from '../../shared/models/videos' 8import { VideoPrivacy, VideoState } from '../../shared/models/videos'
11import { VideoAbuseModel } from '../models/video/video-abuse'
12import { VideoBlacklistModel } from '../models/video/video-blacklist'
13import * as Bluebird from 'bluebird' 9import * as Bluebird from 'bluebird'
14import { VideoImportModel } from '../models/video/video-import'
15import { AccountBlocklistModel } from '../models/account/account-blocklist' 10import { AccountBlocklistModel } from '../models/account/account-blocklist'
16import { ActorFollowModel } from '../models/activitypub/actor-follow' 11import {
17import { AccountModel } from '../models/account/account' 12 MCommentOwnerVideo,
13 MVideoAbuseVideo,
14 MVideoAccountLight,
15 MVideoBlacklistLightVideo,
16 MVideoBlacklistVideo,
17 MVideoFullLight
18} from '../typings/models/video'
19import {
20 MUser,
21 MUserDefault,
22 MUserNotifSettingAccount,
23 MUserWithNotificationSetting,
24 UserNotificationModelForApi
25} from '@server/typings/models/user'
26import { MActorFollowFull } from '../typings/models'
27import { MVideoImportVideo } from '@server/typings/models/video/video-import'
18 28
19class Notifier { 29class Notifier {
20 30
@@ -22,7 +32,7 @@ class Notifier {
22 32
23 private constructor () {} 33 private constructor () {}
24 34
25 notifyOnNewVideoIfNeeded (video: VideoModel): void { 35 notifyOnNewVideoIfNeeded (video: MVideoAccountLight): void {
26 // Only notify on public and published videos which are not blacklisted 36 // Only notify on public and published videos which are not blacklisted
27 if (video.privacy !== VideoPrivacy.PUBLIC || video.state !== VideoState.PUBLISHED || video.isBlacklisted()) return 37 if (video.privacy !== VideoPrivacy.PUBLIC || video.state !== VideoState.PUBLISHED || video.isBlacklisted()) return
28 38
@@ -30,7 +40,7 @@ class Notifier {
30 .catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err })) 40 .catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err }))
31 } 41 }
32 42
33 notifyOnVideoPublishedAfterTranscoding (video: VideoModel): void { 43 notifyOnVideoPublishedAfterTranscoding (video: MVideoFullLight): void {
34 // don't notify if didn't wait for transcoding or video is still blacklisted/waiting for scheduled update 44 // don't notify if didn't wait for transcoding or video is still blacklisted/waiting for scheduled update
35 if (!video.waitTranscoding || video.VideoBlacklist || video.ScheduleVideoUpdate) return 45 if (!video.waitTranscoding || video.VideoBlacklist || video.ScheduleVideoUpdate) return
36 46
@@ -38,7 +48,7 @@ class Notifier {
38 .catch(err => logger.error('Cannot notify owner that its video %s has been published after transcoding.', video.url, { err })) 48 .catch(err => logger.error('Cannot notify owner that its video %s has been published after transcoding.', video.url, { err }))
39 } 49 }
40 50
41 notifyOnVideoPublishedAfterScheduledUpdate (video: VideoModel): void { 51 notifyOnVideoPublishedAfterScheduledUpdate (video: MVideoFullLight): void {
42 // don't notify if video is still blacklisted or waiting for transcoding 52 // don't notify if video is still blacklisted or waiting for transcoding
43 if (video.VideoBlacklist || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return 53 if (video.VideoBlacklist || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return
44 54
@@ -46,7 +56,7 @@ class Notifier {
46 .catch(err => logger.error('Cannot notify owner that its video %s has been published after scheduled update.', video.url, { err })) 56 .catch(err => logger.error('Cannot notify owner that its video %s has been published after scheduled update.', video.url, { err }))
47 } 57 }
48 58
49 notifyOnVideoPublishedAfterRemovedFromAutoBlacklist (video: VideoModel): void { 59 notifyOnVideoPublishedAfterRemovedFromAutoBlacklist (video: MVideoFullLight): void {
50 // don't notify if video is still waiting for transcoding or scheduled update 60 // don't notify if video is still waiting for transcoding or scheduled update
51 if (video.ScheduleVideoUpdate || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return 61 if (video.ScheduleVideoUpdate || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return
52 62
@@ -54,7 +64,7 @@ class Notifier {
54 .catch(err => logger.error('Cannot notify owner that its video %s has been published after removed from auto-blacklist.', video.url, { err })) // tslint:disable-line:max-line-length 64 .catch(err => logger.error('Cannot notify owner that its video %s has been published after removed from auto-blacklist.', video.url, { err })) // tslint:disable-line:max-line-length
55 } 65 }
56 66
57 notifyOnNewComment (comment: VideoCommentModel): void { 67 notifyOnNewComment (comment: MCommentOwnerVideo): void {
58 this.notifyVideoOwnerOfNewComment(comment) 68 this.notifyVideoOwnerOfNewComment(comment)
59 .catch(err => logger.error('Cannot notify video owner of new comment %s.', comment.url, { err })) 69 .catch(err => logger.error('Cannot notify video owner of new comment %s.', comment.url, { err }))
60 70
@@ -62,37 +72,37 @@ class Notifier {
62 .catch(err => logger.error('Cannot notify mentions of comment %s.', comment.url, { err })) 72 .catch(err => logger.error('Cannot notify mentions of comment %s.', comment.url, { err }))
63 } 73 }
64 74
65 notifyOnNewVideoAbuse (videoAbuse: VideoAbuseModel): void { 75 notifyOnNewVideoAbuse (videoAbuse: MVideoAbuseVideo): void {
66 this.notifyModeratorsOfNewVideoAbuse(videoAbuse) 76 this.notifyModeratorsOfNewVideoAbuse(videoAbuse)
67 .catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err })) 77 .catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err }))
68 } 78 }
69 79
70 notifyOnVideoAutoBlacklist (video: VideoModel): void { 80 notifyOnVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo): void {
71 this.notifyModeratorsOfVideoAutoBlacklist(video) 81 this.notifyModeratorsOfVideoAutoBlacklist(videoBlacklist)
72 .catch(err => logger.error('Cannot notify of auto-blacklist of video %s.', video.url, { err })) 82 .catch(err => logger.error('Cannot notify of auto-blacklist of video %s.', videoBlacklist.Video.url, { err }))
73 } 83 }
74 84
75 notifyOnVideoBlacklist (videoBlacklist: VideoBlacklistModel): void { 85 notifyOnVideoBlacklist (videoBlacklist: MVideoBlacklistVideo): void {
76 this.notifyVideoOwnerOfBlacklist(videoBlacklist) 86 this.notifyVideoOwnerOfBlacklist(videoBlacklist)
77 .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err })) 87 .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err }))
78 } 88 }
79 89
80 notifyOnVideoUnblacklist (video: VideoModel): void { 90 notifyOnVideoUnblacklist (video: MVideoFullLight): void {
81 this.notifyVideoOwnerOfUnblacklist(video) 91 this.notifyVideoOwnerOfUnblacklist(video)
82 .catch(err => logger.error('Cannot notify video owner of unblacklist of %s.', video.url, { err })) 92 .catch(err => logger.error('Cannot notify video owner of unblacklist of %s.', video.url, { err }))
83 } 93 }
84 94
85 notifyOnFinishedVideoImport (videoImport: VideoImportModel, success: boolean): void { 95 notifyOnFinishedVideoImport (videoImport: MVideoImportVideo, success: boolean): void {
86 this.notifyOwnerVideoImportIsFinished(videoImport, success) 96 this.notifyOwnerVideoImportIsFinished(videoImport, success)
87 .catch(err => logger.error('Cannot notify owner that its video import %s is finished.', videoImport.getTargetIdentifier(), { err })) 97 .catch(err => logger.error('Cannot notify owner that its video import %s is finished.', videoImport.getTargetIdentifier(), { err }))
88 } 98 }
89 99
90 notifyOnNewUserRegistration (user: UserModel): void { 100 notifyOnNewUserRegistration (user: MUserDefault): void {
91 this.notifyModeratorsOfNewUserRegistration(user) 101 this.notifyModeratorsOfNewUserRegistration(user)
92 .catch(err => logger.error('Cannot notify moderators of new user registration (%s).', user.username, { err })) 102 .catch(err => logger.error('Cannot notify moderators of new user registration (%s).', user.username, { err }))
93 } 103 }
94 104
95 notifyOfNewUserFollow (actorFollow: ActorFollowModel): void { 105 notifyOfNewUserFollow (actorFollow: MActorFollowFull): void {
96 this.notifyUserOfNewActorFollow(actorFollow) 106 this.notifyUserOfNewActorFollow(actorFollow)
97 .catch(err => { 107 .catch(err => {
98 logger.error( 108 logger.error(
@@ -104,25 +114,32 @@ class Notifier {
104 }) 114 })
105 } 115 }
106 116
107 notifyOfNewInstanceFollow (actorFollow: ActorFollowModel): void { 117 notifyOfNewInstanceFollow (actorFollow: MActorFollowFull): void {
108 this.notifyAdminsOfNewInstanceFollow(actorFollow) 118 this.notifyAdminsOfNewInstanceFollow(actorFollow)
109 .catch(err => { 119 .catch(err => {
110 logger.error('Cannot notify administrators of new follower %s.', actorFollow.ActorFollower.url, { err }) 120 logger.error('Cannot notify administrators of new follower %s.', actorFollow.ActorFollower.url, { err })
111 }) 121 })
112 } 122 }
113 123
114 private async notifySubscribersOfNewVideo (video: VideoModel) { 124 notifyOfAutoInstanceFollowing (actorFollow: MActorFollowFull): void {
125 this.notifyAdminsOfAutoInstanceFollowing(actorFollow)
126 .catch(err => {
127 logger.error('Cannot notify administrators of auto instance following %s.', actorFollow.ActorFollowing.url, { err })
128 })
129 }
130
131 private async notifySubscribersOfNewVideo (video: MVideoAccountLight) {
115 // List all followers that are users 132 // List all followers that are users
116 const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId) 133 const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId)
117 134
118 logger.info('Notifying %d users of new video %s.', users.length, video.url) 135 logger.info('Notifying %d users of new video %s.', users.length, video.url)
119 136
120 function settingGetter (user: UserModel) { 137 function settingGetter (user: MUserWithNotificationSetting) {
121 return user.NotificationSetting.newVideoFromSubscription 138 return user.NotificationSetting.newVideoFromSubscription
122 } 139 }
123 140
124 async function notificationCreator (user: UserModel) { 141 async function notificationCreator (user: MUserWithNotificationSetting) {
125 const notification = await UserNotificationModel.create({ 142 const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
126 type: UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION, 143 type: UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION,
127 userId: user.id, 144 userId: user.id,
128 videoId: video.id 145 videoId: video.id
@@ -139,7 +156,7 @@ class Notifier {
139 return this.notify({ users, settingGetter, notificationCreator, emailSender }) 156 return this.notify({ users, settingGetter, notificationCreator, emailSender })
140 } 157 }
141 158
142 private async notifyVideoOwnerOfNewComment (comment: VideoCommentModel) { 159 private async notifyVideoOwnerOfNewComment (comment: MCommentOwnerVideo) {
143 if (comment.Video.isOwned() === false) return 160 if (comment.Video.isOwned() === false) return
144 161
145 const user = await UserModel.loadByVideoId(comment.videoId) 162 const user = await UserModel.loadByVideoId(comment.videoId)
@@ -152,12 +169,12 @@ class Notifier {
152 169
153 logger.info('Notifying user %s of new comment %s.', user.username, comment.url) 170 logger.info('Notifying user %s of new comment %s.', user.username, comment.url)
154 171
155 function settingGetter (user: UserModel) { 172 function settingGetter (user: MUserWithNotificationSetting) {
156 return user.NotificationSetting.newCommentOnMyVideo 173 return user.NotificationSetting.newCommentOnMyVideo
157 } 174 }
158 175
159 async function notificationCreator (user: UserModel) { 176 async function notificationCreator (user: MUserWithNotificationSetting) {
160 const notification = await UserNotificationModel.create({ 177 const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
161 type: UserNotificationType.NEW_COMMENT_ON_MY_VIDEO, 178 type: UserNotificationType.NEW_COMMENT_ON_MY_VIDEO,
162 userId: user.id, 179 userId: user.id,
163 commentId: comment.id 180 commentId: comment.id
@@ -174,7 +191,7 @@ class Notifier {
174 return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) 191 return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
175 } 192 }
176 193
177 private async notifyOfCommentMention (comment: VideoCommentModel) { 194 private async notifyOfCommentMention (comment: MCommentOwnerVideo) {
178 const extractedUsernames = comment.extractMentions() 195 const extractedUsernames = comment.extractMentions()
179 logger.debug( 196 logger.debug(
180 'Extracted %d username from comment %s.', extractedUsernames.length, comment.url, 197 'Extracted %d username from comment %s.', extractedUsernames.length, comment.url,
@@ -197,14 +214,14 @@ class Notifier {
197 214
198 logger.info('Notifying %d users of new comment %s.', users.length, comment.url) 215 logger.info('Notifying %d users of new comment %s.', users.length, comment.url)
199 216
200 function settingGetter (user: UserModel) { 217 function settingGetter (user: MUserNotifSettingAccount) {
201 if (accountMutedHash[user.Account.id] === true) return UserNotificationSettingValue.NONE 218 if (accountMutedHash[user.Account.id] === true) return UserNotificationSettingValue.NONE
202 219
203 return user.NotificationSetting.commentMention 220 return user.NotificationSetting.commentMention
204 } 221 }
205 222
206 async function notificationCreator (user: UserModel) { 223 async function notificationCreator (user: MUserNotifSettingAccount) {
207 const notification = await UserNotificationModel.create({ 224 const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
208 type: UserNotificationType.COMMENT_MENTION, 225 type: UserNotificationType.COMMENT_MENTION,
209 userId: user.id, 226 userId: user.id,
210 commentId: comment.id 227 commentId: comment.id
@@ -221,7 +238,7 @@ class Notifier {
221 return this.notify({ users, settingGetter, notificationCreator, emailSender }) 238 return this.notify({ users, settingGetter, notificationCreator, emailSender })
222 } 239 }
223 240
224 private async notifyUserOfNewActorFollow (actorFollow: ActorFollowModel) { 241 private async notifyUserOfNewActorFollow (actorFollow: MActorFollowFull) {
225 if (actorFollow.ActorFollowing.isOwned() === false) return 242 if (actorFollow.ActorFollowing.isOwned() === false) return
226 243
227 // Account follows one of our account? 244 // Account follows one of our account?
@@ -236,9 +253,6 @@ class Notifier {
236 253
237 if (!user) return 254 if (!user) return
238 255
239 if (!actorFollow.ActorFollower.Account || !actorFollow.ActorFollower.Account.name) {
240 actorFollow.ActorFollower.Account = await actorFollow.ActorFollower.$get('Account') as AccountModel
241 }
242 const followerAccount = actorFollow.ActorFollower.Account 256 const followerAccount = actorFollow.ActorFollower.Account
243 257
244 const accountMuted = await AccountBlocklistModel.isAccountMutedBy(user.Account.id, followerAccount.id) 258 const accountMuted = await AccountBlocklistModel.isAccountMutedBy(user.Account.id, followerAccount.id)
@@ -246,12 +260,12 @@ class Notifier {
246 260
247 logger.info('Notifying user %s of new follower: %s.', user.username, followerAccount.getDisplayName()) 261 logger.info('Notifying user %s of new follower: %s.', user.username, followerAccount.getDisplayName())
248 262
249 function settingGetter (user: UserModel) { 263 function settingGetter (user: MUserWithNotificationSetting) {
250 return user.NotificationSetting.newFollow 264 return user.NotificationSetting.newFollow
251 } 265 }
252 266
253 async function notificationCreator (user: UserModel) { 267 async function notificationCreator (user: MUserWithNotificationSetting) {
254 const notification = await UserNotificationModel.create({ 268 const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
255 type: UserNotificationType.NEW_FOLLOW, 269 type: UserNotificationType.NEW_FOLLOW,
256 userId: user.id, 270 userId: user.id,
257 actorFollowId: actorFollow.id 271 actorFollowId: actorFollow.id
@@ -268,17 +282,17 @@ class Notifier {
268 return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) 282 return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
269 } 283 }
270 284
271 private async notifyAdminsOfNewInstanceFollow (actorFollow: ActorFollowModel) { 285 private async notifyAdminsOfNewInstanceFollow (actorFollow: MActorFollowFull) {
272 const admins = await UserModel.listWithRight(UserRight.MANAGE_SERVER_FOLLOW) 286 const admins = await UserModel.listWithRight(UserRight.MANAGE_SERVER_FOLLOW)
273 287
274 logger.info('Notifying %d administrators of new instance follower: %s.', admins.length, actorFollow.ActorFollower.url) 288 logger.info('Notifying %d administrators of new instance follower: %s.', admins.length, actorFollow.ActorFollower.url)
275 289
276 function settingGetter (user: UserModel) { 290 function settingGetter (user: MUserWithNotificationSetting) {
277 return user.NotificationSetting.newInstanceFollower 291 return user.NotificationSetting.newInstanceFollower
278 } 292 }
279 293
280 async function notificationCreator (user: UserModel) { 294 async function notificationCreator (user: MUserWithNotificationSetting) {
281 const notification = await UserNotificationModel.create({ 295 const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
282 type: UserNotificationType.NEW_INSTANCE_FOLLOWER, 296 type: UserNotificationType.NEW_INSTANCE_FOLLOWER,
283 userId: user.id, 297 userId: user.id,
284 actorFollowId: actorFollow.id 298 actorFollowId: actorFollow.id
@@ -295,18 +309,45 @@ class Notifier {
295 return this.notify({ users: admins, settingGetter, notificationCreator, emailSender }) 309 return this.notify({ users: admins, settingGetter, notificationCreator, emailSender })
296 } 310 }
297 311
298 private async notifyModeratorsOfNewVideoAbuse (videoAbuse: VideoAbuseModel) { 312 private async notifyAdminsOfAutoInstanceFollowing (actorFollow: MActorFollowFull) {
313 const admins = await UserModel.listWithRight(UserRight.MANAGE_SERVER_FOLLOW)
314
315 logger.info('Notifying %d administrators of auto instance following: %s.', admins.length, actorFollow.ActorFollowing.url)
316
317 function settingGetter (user: MUserWithNotificationSetting) {
318 return user.NotificationSetting.autoInstanceFollowing
319 }
320
321 async function notificationCreator (user: MUserWithNotificationSetting) {
322 const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
323 type: UserNotificationType.AUTO_INSTANCE_FOLLOWING,
324 userId: user.id,
325 actorFollowId: actorFollow.id
326 })
327 notification.ActorFollow = actorFollow
328
329 return notification
330 }
331
332 function emailSender (emails: string[]) {
333 return Emailer.Instance.addAutoInstanceFollowingNotification(emails, actorFollow)
334 }
335
336 return this.notify({ users: admins, settingGetter, notificationCreator, emailSender })
337 }
338
339 private async notifyModeratorsOfNewVideoAbuse (videoAbuse: MVideoAbuseVideo) {
299 const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_ABUSES) 340 const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_ABUSES)
300 if (moderators.length === 0) return 341 if (moderators.length === 0) return
301 342
302 logger.info('Notifying %s user/moderators of new video abuse %s.', moderators.length, videoAbuse.Video.url) 343 logger.info('Notifying %s user/moderators of new video abuse %s.', moderators.length, videoAbuse.Video.url)
303 344
304 function settingGetter (user: UserModel) { 345 function settingGetter (user: MUserWithNotificationSetting) {
305 return user.NotificationSetting.videoAbuseAsModerator 346 return user.NotificationSetting.videoAbuseAsModerator
306 } 347 }
307 348
308 async function notificationCreator (user: UserModel) { 349 async function notificationCreator (user: MUserWithNotificationSetting) {
309 const notification = await UserNotificationModel.create({ 350 const notification: UserNotificationModelForApi = await UserNotificationModel.create<UserNotificationModelForApi>({
310 type: UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS, 351 type: UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS,
311 userId: user.id, 352 userId: user.id,
312 videoAbuseId: videoAbuse.id 353 videoAbuseId: videoAbuse.id
@@ -323,46 +364,46 @@ class Notifier {
323 return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) 364 return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
324 } 365 }
325 366
326 private async notifyModeratorsOfVideoAutoBlacklist (video: VideoModel) { 367 private async notifyModeratorsOfVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo) {
327 const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_BLACKLIST) 368 const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_BLACKLIST)
328 if (moderators.length === 0) return 369 if (moderators.length === 0) return
329 370
330 logger.info('Notifying %s moderators of video auto-blacklist %s.', moderators.length, video.url) 371 logger.info('Notifying %s moderators of video auto-blacklist %s.', moderators.length, videoBlacklist.Video.url)
331 372
332 function settingGetter (user: UserModel) { 373 function settingGetter (user: MUserWithNotificationSetting) {
333 return user.NotificationSetting.videoAutoBlacklistAsModerator 374 return user.NotificationSetting.videoAutoBlacklistAsModerator
334 } 375 }
335 async function notificationCreator (user: UserModel) {
336 376
337 const notification = await UserNotificationModel.create({ 377 async function notificationCreator (user: MUserWithNotificationSetting) {
378 const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
338 type: UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS, 379 type: UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS,
339 userId: user.id, 380 userId: user.id,
340 videoId: video.id 381 videoBlacklistId: videoBlacklist.id
341 }) 382 })
342 notification.Video = video 383 notification.VideoBlacklist = videoBlacklist
343 384
344 return notification 385 return notification
345 } 386 }
346 387
347 function emailSender (emails: string[]) { 388 function emailSender (emails: string[]) {
348 return Emailer.Instance.addVideoAutoBlacklistModeratorsNotification(emails, video) 389 return Emailer.Instance.addVideoAutoBlacklistModeratorsNotification(emails, videoBlacklist)
349 } 390 }
350 391
351 return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) 392 return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
352 } 393 }
353 394
354 private async notifyVideoOwnerOfBlacklist (videoBlacklist: VideoBlacklistModel) { 395 private async notifyVideoOwnerOfBlacklist (videoBlacklist: MVideoBlacklistVideo) {
355 const user = await UserModel.loadByVideoId(videoBlacklist.videoId) 396 const user = await UserModel.loadByVideoId(videoBlacklist.videoId)
356 if (!user) return 397 if (!user) return
357 398
358 logger.info('Notifying user %s that its video %s has been blacklisted.', user.username, videoBlacklist.Video.url) 399 logger.info('Notifying user %s that its video %s has been blacklisted.', user.username, videoBlacklist.Video.url)
359 400
360 function settingGetter (user: UserModel) { 401 function settingGetter (user: MUserWithNotificationSetting) {
361 return user.NotificationSetting.blacklistOnMyVideo 402 return user.NotificationSetting.blacklistOnMyVideo
362 } 403 }
363 404
364 async function notificationCreator (user: UserModel) { 405 async function notificationCreator (user: MUserWithNotificationSetting) {
365 const notification = await UserNotificationModel.create({ 406 const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
366 type: UserNotificationType.BLACKLIST_ON_MY_VIDEO, 407 type: UserNotificationType.BLACKLIST_ON_MY_VIDEO,
367 userId: user.id, 408 userId: user.id,
368 videoBlacklistId: videoBlacklist.id 409 videoBlacklistId: videoBlacklist.id
@@ -379,18 +420,18 @@ class Notifier {
379 return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) 420 return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
380 } 421 }
381 422
382 private async notifyVideoOwnerOfUnblacklist (video: VideoModel) { 423 private async notifyVideoOwnerOfUnblacklist (video: MVideoFullLight) {
383 const user = await UserModel.loadByVideoId(video.id) 424 const user = await UserModel.loadByVideoId(video.id)
384 if (!user) return 425 if (!user) return
385 426
386 logger.info('Notifying user %s that its video %s has been unblacklisted.', user.username, video.url) 427 logger.info('Notifying user %s that its video %s has been unblacklisted.', user.username, video.url)
387 428
388 function settingGetter (user: UserModel) { 429 function settingGetter (user: MUserWithNotificationSetting) {
389 return user.NotificationSetting.blacklistOnMyVideo 430 return user.NotificationSetting.blacklistOnMyVideo
390 } 431 }
391 432
392 async function notificationCreator (user: UserModel) { 433 async function notificationCreator (user: MUserWithNotificationSetting) {
393 const notification = await UserNotificationModel.create({ 434 const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
394 type: UserNotificationType.UNBLACKLIST_ON_MY_VIDEO, 435 type: UserNotificationType.UNBLACKLIST_ON_MY_VIDEO,
395 userId: user.id, 436 userId: user.id,
396 videoId: video.id 437 videoId: video.id
@@ -407,18 +448,18 @@ class Notifier {
407 return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) 448 return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
408 } 449 }
409 450
410 private async notifyOwnedVideoHasBeenPublished (video: VideoModel) { 451 private async notifyOwnedVideoHasBeenPublished (video: MVideoFullLight) {
411 const user = await UserModel.loadByVideoId(video.id) 452 const user = await UserModel.loadByVideoId(video.id)
412 if (!user) return 453 if (!user) return
413 454
414 logger.info('Notifying user %s of the publication of its video %s.', user.username, video.url) 455 logger.info('Notifying user %s of the publication of its video %s.', user.username, video.url)
415 456
416 function settingGetter (user: UserModel) { 457 function settingGetter (user: MUserWithNotificationSetting) {
417 return user.NotificationSetting.myVideoPublished 458 return user.NotificationSetting.myVideoPublished
418 } 459 }
419 460
420 async function notificationCreator (user: UserModel) { 461 async function notificationCreator (user: MUserWithNotificationSetting) {
421 const notification = await UserNotificationModel.create({ 462 const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
422 type: UserNotificationType.MY_VIDEO_PUBLISHED, 463 type: UserNotificationType.MY_VIDEO_PUBLISHED,
423 userId: user.id, 464 userId: user.id,
424 videoId: video.id 465 videoId: video.id
@@ -435,18 +476,18 @@ class Notifier {
435 return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) 476 return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
436 } 477 }
437 478
438 private async notifyOwnerVideoImportIsFinished (videoImport: VideoImportModel, success: boolean) { 479 private async notifyOwnerVideoImportIsFinished (videoImport: MVideoImportVideo, success: boolean) {
439 const user = await UserModel.loadByVideoImportId(videoImport.id) 480 const user = await UserModel.loadByVideoImportId(videoImport.id)
440 if (!user) return 481 if (!user) return
441 482
442 logger.info('Notifying user %s its video import %s is finished.', user.username, videoImport.getTargetIdentifier()) 483 logger.info('Notifying user %s its video import %s is finished.', user.username, videoImport.getTargetIdentifier())
443 484
444 function settingGetter (user: UserModel) { 485 function settingGetter (user: MUserWithNotificationSetting) {
445 return user.NotificationSetting.myVideoImportFinished 486 return user.NotificationSetting.myVideoImportFinished
446 } 487 }
447 488
448 async function notificationCreator (user: UserModel) { 489 async function notificationCreator (user: MUserWithNotificationSetting) {
449 const notification = await UserNotificationModel.create({ 490 const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
450 type: success ? UserNotificationType.MY_VIDEO_IMPORT_SUCCESS : UserNotificationType.MY_VIDEO_IMPORT_ERROR, 491 type: success ? UserNotificationType.MY_VIDEO_IMPORT_SUCCESS : UserNotificationType.MY_VIDEO_IMPORT_ERROR,
451 userId: user.id, 492 userId: user.id,
452 videoImportId: videoImport.id 493 videoImportId: videoImport.id
@@ -465,21 +506,21 @@ class Notifier {
465 return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) 506 return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
466 } 507 }
467 508
468 private async notifyModeratorsOfNewUserRegistration (registeredUser: UserModel) { 509 private async notifyModeratorsOfNewUserRegistration (registeredUser: MUserDefault) {
469 const moderators = await UserModel.listWithRight(UserRight.MANAGE_USERS) 510 const moderators = await UserModel.listWithRight(UserRight.MANAGE_USERS)
470 if (moderators.length === 0) return 511 if (moderators.length === 0) return
471 512
472 logger.info( 513 logger.info(
473 'Notifying %s moderators of new user registration of %s.', 514 'Notifying %s moderators of new user registration of %s.',
474 moderators.length, registeredUser.Account.Actor.preferredUsername 515 moderators.length, registeredUser.username
475 ) 516 )
476 517
477 function settingGetter (user: UserModel) { 518 function settingGetter (user: MUserWithNotificationSetting) {
478 return user.NotificationSetting.newUserRegistration 519 return user.NotificationSetting.newUserRegistration
479 } 520 }
480 521
481 async function notificationCreator (user: UserModel) { 522 async function notificationCreator (user: MUserWithNotificationSetting) {
482 const notification = await UserNotificationModel.create({ 523 const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
483 type: UserNotificationType.NEW_USER_REGISTRATION, 524 type: UserNotificationType.NEW_USER_REGISTRATION,
484 userId: user.id, 525 userId: user.id,
485 accountId: registeredUser.Account.id 526 accountId: registeredUser.Account.id
@@ -496,11 +537,11 @@ class Notifier {
496 return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) 537 return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
497 } 538 }
498 539
499 private async notify (options: { 540 private async notify <T extends MUserWithNotificationSetting> (options: {
500 users: UserModel[], 541 users: T[],
501 notificationCreator: (user: UserModel) => Promise<UserNotificationModel>, 542 notificationCreator: (user: T) => Promise<UserNotificationModelForApi>,
502 emailSender: (emails: string[]) => Promise<any> | Bluebird<any>, 543 emailSender: (emails: string[]) => Promise<any> | Bluebird<any>,
503 settingGetter: (user: UserModel) => UserNotificationSettingValue 544 settingGetter: (user: T) => UserNotificationSettingValue
504 }) { 545 }) {
505 const emails: string[] = [] 546 const emails: string[] = []
506 547
@@ -521,7 +562,7 @@ class Notifier {
521 } 562 }
522 } 563 }
523 564
524 private isEmailEnabled (user: UserModel, value: UserNotificationSettingValue) { 565 private isEmailEnabled (user: MUser, value: UserNotificationSettingValue) {
525 if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION === true && user.emailVerified === false) return false 566 if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION === true && user.emailVerified === false) return false
526 567
527 return value & UserNotificationSettingValue.EMAIL 568 return value & UserNotificationSettingValue.EMAIL
diff --git a/server/lib/oauth-model.ts b/server/lib/oauth-model.ts
index a1153e88a..086856f41 100644
--- a/server/lib/oauth-model.ts
+++ b/server/lib/oauth-model.ts
@@ -8,10 +8,11 @@ import { LRU_CACHE } from '../initializers/constants'
8import { Transaction } from 'sequelize' 8import { Transaction } from 'sequelize'
9import { CONFIG } from '../initializers/config' 9import { CONFIG } from '../initializers/config'
10import * as LRUCache from 'lru-cache' 10import * as LRUCache from 'lru-cache'
11import { MOAuthTokenUser } from '@server/typings/models/oauth/oauth-token'
11 12
12type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date } 13type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date }
13 14
14const accessTokenCache = new LRUCache<string, OAuthTokenModel>({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE }) 15const accessTokenCache = new LRUCache<string, MOAuthTokenUser>({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE })
15const userHavingToken = new LRUCache<number, string>({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE }) 16const userHavingToken = new LRUCache<number, string>({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE })
16 17
17// --------------------------------------------------------------------------- 18// ---------------------------------------------------------------------------
diff --git a/server/lib/peertube-socket.ts b/server/lib/peertube-socket.ts
index 17748fd18..26ced351f 100644
--- a/server/lib/peertube-socket.ts
+++ b/server/lib/peertube-socket.ts
@@ -1,8 +1,8 @@
1import * as SocketIO from 'socket.io' 1import * as SocketIO from 'socket.io'
2import { authenticateSocket } from '../middlewares' 2import { authenticateSocket } from '../middlewares'
3import { UserNotificationModel } from '../models/account/user-notification'
4import { logger } from '../helpers/logger' 3import { logger } from '../helpers/logger'
5import { Server } from 'http' 4import { Server } from 'http'
5import { UserNotificationModelForApi } from '@server/typings/models/user'
6 6
7class PeerTubeSocket { 7class PeerTubeSocket {
8 8
@@ -34,13 +34,14 @@ class PeerTubeSocket {
34 }) 34 })
35 } 35 }
36 36
37 sendNotification (userId: number, notification: UserNotificationModel) { 37 sendNotification (userId: number, notification: UserNotificationModelForApi) {
38 const sockets = this.userNotificationSockets[userId] 38 const sockets = this.userNotificationSockets[userId]
39 39
40 if (!sockets) return 40 if (!sockets) return
41 41
42 const notificationMessage = notification.toFormattedJSON()
42 for (const socket of sockets) { 43 for (const socket of sockets) {
43 socket.emit('new-notification', notification.toFormattedJSON()) 44 socket.emit('new-notification', notificationMessage)
44 } 45 }
45 } 46 }
46 47
diff --git a/server/lib/redundancy.ts b/server/lib/redundancy.ts
index 04d3ded8f..1b4ecd7c0 100644
--- a/server/lib/redundancy.ts
+++ b/server/lib/redundancy.ts
@@ -2,8 +2,9 @@ import { VideoRedundancyModel } from '../models/redundancy/video-redundancy'
2import { sendUndoCacheFile } from './activitypub/send' 2import { sendUndoCacheFile } from './activitypub/send'
3import { Transaction } from 'sequelize' 3import { Transaction } from 'sequelize'
4import { getServerActor } from '../helpers/utils' 4import { getServerActor } from '../helpers/utils'
5import { MVideoRedundancyVideo } from '@server/typings/models'
5 6
6async function removeVideoRedundancy (videoRedundancy: VideoRedundancyModel, t?: Transaction) { 7async function removeVideoRedundancy (videoRedundancy: MVideoRedundancyVideo, t?: Transaction) {
7 const serverActor = await getServerActor() 8 const serverActor = await getServerActor()
8 9
9 // Local cache, send undo to remote instances 10 // Local cache, send undo to remote instances
diff --git a/server/lib/schedulers/auto-follow-index-instances.ts b/server/lib/schedulers/auto-follow-index-instances.ts
new file mode 100644
index 000000000..ef11fc87f
--- /dev/null
+++ b/server/lib/schedulers/auto-follow-index-instances.ts
@@ -0,0 +1,72 @@
1import { logger } from '../../helpers/logger'
2import { AbstractScheduler } from './abstract-scheduler'
3import { INSTANCES_INDEX, SCHEDULER_INTERVALS_MS, SERVER_ACTOR_NAME } from '../../initializers/constants'
4import { CONFIG } from '../../initializers/config'
5import { chunk } from 'lodash'
6import { doRequest } from '@server/helpers/requests'
7import { ActorFollowModel } from '@server/models/activitypub/actor-follow'
8import { JobQueue } from '@server/lib/job-queue'
9import { getServerActor } from '@server/helpers/utils'
10
11export class AutoFollowIndexInstances extends AbstractScheduler {
12
13 private static instance: AbstractScheduler
14
15 protected schedulerIntervalMs = SCHEDULER_INTERVALS_MS.autoFollowIndexInstances
16
17 private lastCheck: Date
18
19 private constructor () {
20 super()
21 }
22
23 protected async internalExecute () {
24 return this.autoFollow()
25 }
26
27 private async autoFollow () {
28 if (CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.ENABLED === false) return
29
30 const indexUrl = CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.INDEX_URL
31
32 logger.info('Auto follow instances of index %s.', indexUrl)
33
34 try {
35 const serverActor = await getServerActor()
36
37 const uri = indexUrl + INSTANCES_INDEX.HOSTS_PATH
38
39 const qs = this.lastCheck ? { since: this.lastCheck.toISOString() } : {}
40 this.lastCheck = new Date()
41
42 const { body } = await doRequest({ uri, qs, json: true })
43
44 const hosts: string[] = body.data.map(o => o.host)
45 const chunks = chunk(hosts, 20)
46
47 for (const chunk of chunks) {
48 const unfollowedHosts = await ActorFollowModel.keepUnfollowedInstance(chunk)
49
50 for (const unfollowedHost of unfollowedHosts) {
51 const payload = {
52 host: unfollowedHost,
53 name: SERVER_ACTOR_NAME,
54 followerActorId: serverActor.id,
55 isAutoFollow: true
56 }
57
58 await JobQueue.Instance.createJob({ type: 'activitypub-follow', payload })
59 .catch(err => logger.error('Cannot create follow job for %s.', unfollowedHost, err))
60 }
61 }
62
63 } catch (err) {
64 logger.error('Cannot auto follow hosts of index %s.', indexUrl, { err })
65 }
66
67 }
68
69 static get Instance () {
70 return this.instance || (this.instance = new this())
71 }
72}
diff --git a/server/lib/schedulers/videos-redundancy-scheduler.ts b/server/lib/schedulers/videos-redundancy-scheduler.ts
index 5f4aad66e..1e30f6ebc 100644
--- a/server/lib/schedulers/videos-redundancy-scheduler.ts
+++ b/server/lib/schedulers/videos-redundancy-scheduler.ts
@@ -3,7 +3,6 @@ import { HLS_REDUNDANCY_DIRECTORY, REDUNDANCY, VIDEO_IMPORT_TIMEOUT, WEBSERVER }
3import { logger } from '../../helpers/logger' 3import { logger } from '../../helpers/logger'
4import { VideosRedundancy } from '../../../shared/models/redundancy' 4import { VideosRedundancy } from '../../../shared/models/redundancy'
5import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' 5import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy'
6import { VideoFileModel } from '../../models/video/video-file'
7import { downloadWebTorrentVideo } from '../../helpers/webtorrent' 6import { downloadWebTorrentVideo } from '../../helpers/webtorrent'
8import { join } from 'path' 7import { join } from 'path'
9import { move } from 'fs-extra' 8import { move } from 'fs-extra'
@@ -12,16 +11,31 @@ import { sendCreateCacheFile, sendUpdateCacheFile } from '../activitypub/send'
12import { getVideoCacheFileActivityPubUrl, getVideoCacheStreamingPlaylistActivityPubUrl } from '../activitypub/url' 11import { getVideoCacheFileActivityPubUrl, getVideoCacheStreamingPlaylistActivityPubUrl } from '../activitypub/url'
13import { removeVideoRedundancy } from '../redundancy' 12import { removeVideoRedundancy } from '../redundancy'
14import { getOrCreateVideoAndAccountAndChannel } from '../activitypub' 13import { getOrCreateVideoAndAccountAndChannel } from '../activitypub'
15import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist'
16import { VideoModel } from '../../models/video/video'
17import { downloadPlaylistSegments } from '../hls' 14import { downloadPlaylistSegments } from '../hls'
18import { CONFIG } from '../../initializers/config' 15import { CONFIG } from '../../initializers/config'
16import {
17 MStreamingPlaylist,
18 MStreamingPlaylistVideo,
19 MVideoAccountLight,
20 MVideoFile,
21 MVideoFileVideo,
22 MVideoRedundancyFileVideo,
23 MVideoRedundancyStreamingPlaylistVideo,
24 MVideoRedundancyVideo,
25 MVideoWithAllFiles
26} from '@server/typings/models'
19 27
20type CandidateToDuplicate = { 28type CandidateToDuplicate = {
21 redundancy: VideosRedundancy, 29 redundancy: VideosRedundancy,
22 video: VideoModel, 30 video: MVideoWithAllFiles,
23 files: VideoFileModel[], 31 files: MVideoFile[],
24 streamingPlaylists: VideoStreamingPlaylistModel[] 32 streamingPlaylists: MStreamingPlaylist[]
33}
34
35function isMVideoRedundancyFileVideo (
36 o: MVideoRedundancyFileVideo | MVideoRedundancyStreamingPlaylistVideo
37): o is MVideoRedundancyFileVideo {
38 return !!(o as MVideoRedundancyFileVideo).VideoFile
25} 39}
26 40
27export class VideosRedundancyScheduler extends AbstractScheduler { 41export class VideosRedundancyScheduler extends AbstractScheduler {
@@ -102,7 +116,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
102 } 116 }
103 } 117 }
104 118
105 private async extendsRedundancy (redundancyModel: VideoRedundancyModel) { 119 private async extendsRedundancy (redundancyModel: MVideoRedundancyVideo) {
106 const redundancy = CONFIG.REDUNDANCY.VIDEOS.STRATEGIES.find(s => s.strategy === redundancyModel.strategy) 120 const redundancy = CONFIG.REDUNDANCY.VIDEOS.STRATEGIES.find(s => s.strategy === redundancyModel.strategy)
107 // Redundancy strategy disabled, remove our redundancy instead of extending expiration 121 // Redundancy strategy disabled, remove our redundancy instead of extending expiration
108 if (!redundancy) { 122 if (!redundancy) {
@@ -172,7 +186,8 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
172 } 186 }
173 } 187 }
174 188
175 private async createVideoFileRedundancy (redundancy: VideosRedundancy, video: VideoModel, file: VideoFileModel) { 189 private async createVideoFileRedundancy (redundancy: VideosRedundancy, video: MVideoAccountLight, fileArg: MVideoFile) {
190 const file = fileArg as MVideoFileVideo
176 file.Video = video 191 file.Video = video
177 192
178 const serverActor = await getServerActor() 193 const serverActor = await getServerActor()
@@ -187,7 +202,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
187 const destPath = join(CONFIG.STORAGE.REDUNDANCY_DIR, video.getVideoFilename(file)) 202 const destPath = join(CONFIG.STORAGE.REDUNDANCY_DIR, video.getVideoFilename(file))
188 await move(tmpPath, destPath, { overwrite: true }) 203 await move(tmpPath, destPath, { overwrite: true })
189 204
190 const createdModel = await VideoRedundancyModel.create({ 205 const createdModel: MVideoRedundancyFileVideo = await VideoRedundancyModel.create({
191 expiresOn: this.buildNewExpiration(redundancy.minLifetime), 206 expiresOn: this.buildNewExpiration(redundancy.minLifetime),
192 url: getVideoCacheFileActivityPubUrl(file), 207 url: getVideoCacheFileActivityPubUrl(file),
193 fileUrl: video.getVideoRedundancyUrl(file, WEBSERVER.URL), 208 fileUrl: video.getVideoRedundancyUrl(file, WEBSERVER.URL),
@@ -203,7 +218,12 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
203 logger.info('Duplicated %s - %d -> %s.', video.url, file.resolution, createdModel.url) 218 logger.info('Duplicated %s - %d -> %s.', video.url, file.resolution, createdModel.url)
204 } 219 }
205 220
206 private async createStreamingPlaylistRedundancy (redundancy: VideosRedundancy, video: VideoModel, playlist: VideoStreamingPlaylistModel) { 221 private async createStreamingPlaylistRedundancy (
222 redundancy: VideosRedundancy,
223 video: MVideoAccountLight,
224 playlistArg: MStreamingPlaylist
225 ) {
226 const playlist = playlistArg as MStreamingPlaylistVideo
207 playlist.Video = video 227 playlist.Video = video
208 228
209 const serverActor = await getServerActor() 229 const serverActor = await getServerActor()
@@ -213,7 +233,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
213 const destDirectory = join(HLS_REDUNDANCY_DIRECTORY, video.uuid) 233 const destDirectory = join(HLS_REDUNDANCY_DIRECTORY, video.uuid)
214 await downloadPlaylistSegments(playlist.playlistUrl, destDirectory, VIDEO_IMPORT_TIMEOUT) 234 await downloadPlaylistSegments(playlist.playlistUrl, destDirectory, VIDEO_IMPORT_TIMEOUT)
215 235
216 const createdModel = await VideoRedundancyModel.create({ 236 const createdModel: MVideoRedundancyStreamingPlaylistVideo = await VideoRedundancyModel.create({
217 expiresOn: this.buildNewExpiration(redundancy.minLifetime), 237 expiresOn: this.buildNewExpiration(redundancy.minLifetime),
218 url: getVideoCacheStreamingPlaylistActivityPubUrl(video, playlist), 238 url: getVideoCacheStreamingPlaylistActivityPubUrl(video, playlist),
219 fileUrl: playlist.getVideoRedundancyUrl(WEBSERVER.URL), 239 fileUrl: playlist.getVideoRedundancyUrl(WEBSERVER.URL),
@@ -229,7 +249,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
229 logger.info('Duplicated playlist %s -> %s.', playlist.playlistUrl, createdModel.url) 249 logger.info('Duplicated playlist %s -> %s.', playlist.playlistUrl, createdModel.url)
230 } 250 }
231 251
232 private async extendsExpirationOf (redundancy: VideoRedundancyModel, expiresAfterMs: number) { 252 private async extendsExpirationOf (redundancy: MVideoRedundancyVideo, expiresAfterMs: number) {
233 logger.info('Extending expiration of %s.', redundancy.url) 253 logger.info('Extending expiration of %s.', redundancy.url)
234 254
235 const serverActor = await getServerActor() 255 const serverActor = await getServerActor()
@@ -243,7 +263,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
243 private async purgeCacheIfNeeded (candidateToDuplicate: CandidateToDuplicate) { 263 private async purgeCacheIfNeeded (candidateToDuplicate: CandidateToDuplicate) {
244 while (await this.isTooHeavy(candidateToDuplicate)) { 264 while (await this.isTooHeavy(candidateToDuplicate)) {
245 const redundancy = candidateToDuplicate.redundancy 265 const redundancy = candidateToDuplicate.redundancy
246 const toDelete = await VideoRedundancyModel.loadOldestLocalThatAlreadyExpired(redundancy.strategy, redundancy.minLifetime) 266 const toDelete = await VideoRedundancyModel.loadOldestLocalExpired(redundancy.strategy, redundancy.minLifetime)
247 if (!toDelete) return 267 if (!toDelete) return
248 268
249 await removeVideoRedundancy(toDelete) 269 await removeVideoRedundancy(toDelete)
@@ -263,19 +283,18 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
263 return new Date(Date.now() + expiresAfterMs) 283 return new Date(Date.now() + expiresAfterMs)
264 } 284 }
265 285
266 private buildEntryLogId (object: VideoRedundancyModel) { 286 private buildEntryLogId (object: MVideoRedundancyFileVideo | MVideoRedundancyStreamingPlaylistVideo) {
267 if (object.VideoFile) return `${object.VideoFile.Video.url}-${object.VideoFile.resolution}` 287 if (isMVideoRedundancyFileVideo(object)) return `${object.VideoFile.Video.url}-${object.VideoFile.resolution}`
268 288
269 return `${object.VideoStreamingPlaylist.playlistUrl}` 289 return `${object.VideoStreamingPlaylist.playlistUrl}`
270 } 290 }
271 291
272 private getTotalFileSizes (files: VideoFileModel[], playlists: VideoStreamingPlaylistModel[]) { 292 private getTotalFileSizes (files: MVideoFile[], playlists: MStreamingPlaylist[]) {
273 const fileReducer = (previous: number, current: VideoFileModel) => previous + current.size 293 const fileReducer = (previous: number, current: MVideoFile) => previous + current.size
274 294
275 const totalSize = files.reduce(fileReducer, 0) 295 const totalSize = files.reduce(fileReducer, 0)
276 if (playlists.length === 0) return totalSize
277 296
278 return totalSize * playlists.length 297 return totalSize + (totalSize * playlists.length)
279 } 298 }
280 299
281 private async loadAndRefreshVideo (videoUrl: string) { 300 private async loadAndRefreshVideo (videoUrl: string) {
diff --git a/server/lib/thumbnail.ts b/server/lib/thumbnail.ts
index a59773f5a..84791955e 100644
--- a/server/lib/thumbnail.ts
+++ b/server/lib/thumbnail.ts
@@ -1,20 +1,20 @@
1import { VideoFileModel } from '../models/video/video-file'
2import { generateImageFromVideoFile } from '../helpers/ffmpeg-utils' 1import { generateImageFromVideoFile } from '../helpers/ffmpeg-utils'
3import { CONFIG } from '../initializers/config' 2import { CONFIG } from '../initializers/config'
4import { PREVIEWS_SIZE, THUMBNAILS_SIZE, ASSETS_PATH } from '../initializers/constants' 3import { ASSETS_PATH, PREVIEWS_SIZE, THUMBNAILS_SIZE } from '../initializers/constants'
5import { VideoModel } from '../models/video/video'
6import { ThumbnailModel } from '../models/video/thumbnail' 4import { ThumbnailModel } from '../models/video/thumbnail'
7import { ThumbnailType } from '../../shared/models/videos/thumbnail.type' 5import { ThumbnailType } from '../../shared/models/videos/thumbnail.type'
8import { processImage } from '../helpers/image-utils' 6import { processImage } from '../helpers/image-utils'
9import { join } from 'path' 7import { join } from 'path'
10import { downloadImage } from '../helpers/requests' 8import { downloadImage } from '../helpers/requests'
11import { VideoPlaylistModel } from '../models/video/video-playlist' 9import { MVideoPlaylistThumbnail } from '../typings/models/video/video-playlist'
10import { MVideoFile, MVideoThumbnail } from '../typings/models'
11import { MThumbnail } from '../typings/models/video/thumbnail'
12 12
13type ImageSize = { height: number, width: number } 13type ImageSize = { height: number, width: number }
14 14
15function createPlaylistMiniatureFromExisting ( 15function createPlaylistMiniatureFromExisting (
16 inputPath: string, 16 inputPath: string,
17 playlist: VideoPlaylistModel, 17 playlist: MVideoPlaylistThumbnail,
18 automaticallyGenerated: boolean, 18 automaticallyGenerated: boolean,
19 keepOriginal = false, 19 keepOriginal = false,
20 size?: ImageSize 20 size?: ImageSize
@@ -26,7 +26,7 @@ function createPlaylistMiniatureFromExisting (
26 return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated, existingThumbnail }) 26 return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated, existingThumbnail })
27} 27}
28 28
29function createPlaylistMiniatureFromUrl (fileUrl: string, playlist: VideoPlaylistModel, size?: ImageSize) { 29function createPlaylistMiniatureFromUrl (fileUrl: string, playlist: MVideoPlaylistThumbnail, size?: ImageSize) {
30 const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size) 30 const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size)
31 const type = ThumbnailType.MINIATURE 31 const type = ThumbnailType.MINIATURE
32 32
@@ -34,7 +34,7 @@ function createPlaylistMiniatureFromUrl (fileUrl: string, playlist: VideoPlaylis
34 return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl }) 34 return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl })
35} 35}
36 36
37function createVideoMiniatureFromUrl (fileUrl: string, video: VideoModel, type: ThumbnailType, size?: ImageSize) { 37function createVideoMiniatureFromUrl (fileUrl: string, video: MVideoThumbnail, type: ThumbnailType, size?: ImageSize) {
38 const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) 38 const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size)
39 const thumbnailCreator = () => downloadImage(fileUrl, basePath, filename, { width, height }) 39 const thumbnailCreator = () => downloadImage(fileUrl, basePath, filename, { width, height })
40 40
@@ -43,7 +43,7 @@ function createVideoMiniatureFromUrl (fileUrl: string, video: VideoModel, type:
43 43
44function createVideoMiniatureFromExisting ( 44function createVideoMiniatureFromExisting (
45 inputPath: string, 45 inputPath: string,
46 video: VideoModel, 46 video: MVideoThumbnail,
47 type: ThumbnailType, 47 type: ThumbnailType,
48 automaticallyGenerated: boolean, 48 automaticallyGenerated: boolean,
49 size?: ImageSize 49 size?: ImageSize
@@ -54,7 +54,7 @@ function createVideoMiniatureFromExisting (
54 return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated, existingThumbnail }) 54 return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated, existingThumbnail })
55} 55}
56 56
57function generateVideoMiniature (video: VideoModel, videoFile: VideoFileModel, type: ThumbnailType) { 57function generateVideoMiniature (video: MVideoThumbnail, videoFile: MVideoFile, type: ThumbnailType) {
58 const input = video.getVideoFilePath(videoFile) 58 const input = video.getVideoFilePath(videoFile)
59 59
60 const { filename, basePath, height, width, existingThumbnail, outputPath } = buildMetadataFromVideo(video, type) 60 const { filename, basePath, height, width, existingThumbnail, outputPath } = buildMetadataFromVideo(video, type)
@@ -65,7 +65,7 @@ function generateVideoMiniature (video: VideoModel, videoFile: VideoFileModel, t
65 return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated: true, existingThumbnail }) 65 return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated: true, existingThumbnail })
66} 66}
67 67
68function createPlaceholderThumbnail (fileUrl: string, video: VideoModel, type: ThumbnailType, size: ImageSize) { 68function createPlaceholderThumbnail (fileUrl: string, video: MVideoThumbnail, type: ThumbnailType, size: ImageSize) {
69 const { filename, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) 69 const { filename, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size)
70 70
71 const thumbnail = existingThumbnail ? existingThumbnail : new ThumbnailModel() 71 const thumbnail = existingThumbnail ? existingThumbnail : new ThumbnailModel()
@@ -90,7 +90,7 @@ export {
90 createPlaylistMiniatureFromExisting 90 createPlaylistMiniatureFromExisting
91} 91}
92 92
93function buildMetadataFromPlaylist (playlist: VideoPlaylistModel, size: ImageSize) { 93function buildMetadataFromPlaylist (playlist: MVideoPlaylistThumbnail, size: ImageSize) {
94 const filename = playlist.generateThumbnailName() 94 const filename = playlist.generateThumbnailName()
95 const basePath = CONFIG.STORAGE.THUMBNAILS_DIR 95 const basePath = CONFIG.STORAGE.THUMBNAILS_DIR
96 96
@@ -104,7 +104,7 @@ function buildMetadataFromPlaylist (playlist: VideoPlaylistModel, size: ImageSiz
104 } 104 }
105} 105}
106 106
107function buildMetadataFromVideo (video: VideoModel, type: ThumbnailType, size?: ImageSize) { 107function buildMetadataFromVideo (video: MVideoThumbnail, type: ThumbnailType, size?: ImageSize) {
108 const existingThumbnail = Array.isArray(video.Thumbnails) 108 const existingThumbnail = Array.isArray(video.Thumbnails)
109 ? video.Thumbnails.find(t => t.type === type) 109 ? video.Thumbnails.find(t => t.type === type)
110 : undefined 110 : undefined
@@ -148,7 +148,7 @@ async function createThumbnailFromFunction (parameters: {
148 type: ThumbnailType, 148 type: ThumbnailType,
149 automaticallyGenerated?: boolean, 149 automaticallyGenerated?: boolean,
150 fileUrl?: string, 150 fileUrl?: string,
151 existingThumbnail?: ThumbnailModel 151 existingThumbnail?: MThumbnail
152}) { 152}) {
153 const { thumbnailCreator, filename, width, height, type, existingThumbnail, automaticallyGenerated = null, fileUrl = null } = parameters 153 const { thumbnailCreator, filename, width, height, type, existingThumbnail, automaticallyGenerated = null, fileUrl = null } = parameters
154 154
diff --git a/server/lib/user.ts b/server/lib/user.ts
index 0e4007770..c45438d95 100644
--- a/server/lib/user.ts
+++ b/server/lib/user.ts
@@ -2,10 +2,8 @@ import * as uuidv4 from 'uuid/v4'
2import { ActivityPubActorType } from '../../shared/models/activitypub' 2import { ActivityPubActorType } from '../../shared/models/activitypub'
3import { SERVER_ACTOR_NAME, WEBSERVER } from '../initializers/constants' 3import { SERVER_ACTOR_NAME, WEBSERVER } from '../initializers/constants'
4import { AccountModel } from '../models/account/account' 4import { AccountModel } from '../models/account/account'
5import { UserModel } from '../models/account/user'
6import { buildActorInstance, getAccountActivityPubUrl, setAsyncActorKeys } from './activitypub' 5import { buildActorInstance, getAccountActivityPubUrl, setAsyncActorKeys } from './activitypub'
7import { createVideoChannel } from './video-channel' 6import { createLocalVideoChannel } from './video-channel'
8import { VideoChannelModel } from '../models/video/video-channel'
9import { ActorModel } from '../models/activitypub/actor' 7import { ActorModel } from '../models/activitypub/actor'
10import { UserNotificationSettingModel } from '../models/account/user-notification-setting' 8import { UserNotificationSettingModel } from '../models/account/user-notification-setting'
11import { UserNotificationSetting, UserNotificationSettingValue } from '../../shared/models/users' 9import { UserNotificationSetting, UserNotificationSettingValue } from '../../shared/models/users'
@@ -14,14 +12,17 @@ import { sequelizeTypescript } from '../initializers/database'
14import { Transaction } from 'sequelize/types' 12import { Transaction } from 'sequelize/types'
15import { Redis } from './redis' 13import { Redis } from './redis'
16import { Emailer } from './emailer' 14import { Emailer } from './emailer'
15import { MAccountDefault, MActorDefault, MChannelActor } from '../typings/models'
16import { MUser, MUserDefault, MUserId } from '../typings/models/user'
17 17
18type ChannelNames = { name: string, displayName: string } 18type ChannelNames = { name: string, displayName: string }
19
19async function createUserAccountAndChannelAndPlaylist (parameters: { 20async function createUserAccountAndChannelAndPlaylist (parameters: {
20 userToCreate: UserModel, 21 userToCreate: MUser,
21 userDisplayName?: string, 22 userDisplayName?: string,
22 channelNames?: ChannelNames, 23 channelNames?: ChannelNames,
23 validateUser?: boolean 24 validateUser?: boolean
24}) { 25}): Promise<{ user: MUserDefault, account: MAccountDefault, videoChannel: MChannelActor }> {
25 const { userToCreate, userDisplayName, channelNames, validateUser = true } = parameters 26 const { userToCreate, userDisplayName, channelNames, validateUser = true } = parameters
26 27
27 const { user, account, videoChannel } = await sequelizeTypescript.transaction(async t => { 28 const { user, account, videoChannel } = await sequelizeTypescript.transaction(async t => {
@@ -30,7 +31,7 @@ async function createUserAccountAndChannelAndPlaylist (parameters: {
30 validate: validateUser 31 validate: validateUser
31 } 32 }
32 33
33 const userCreated = await userToCreate.save(userOptions) 34 const userCreated: MUserDefault = await userToCreate.save(userOptions)
34 userCreated.NotificationSetting = await createDefaultUserNotificationSettings(userCreated, t) 35 userCreated.NotificationSetting = await createDefaultUserNotificationSettings(userCreated, t)
35 36
36 const accountCreated = await createLocalAccountWithoutKeys({ 37 const accountCreated = await createLocalAccountWithoutKeys({
@@ -43,22 +44,22 @@ async function createUserAccountAndChannelAndPlaylist (parameters: {
43 userCreated.Account = accountCreated 44 userCreated.Account = accountCreated
44 45
45 const channelAttributes = await buildChannelAttributes(userCreated, channelNames) 46 const channelAttributes = await buildChannelAttributes(userCreated, channelNames)
46 const videoChannel = await createVideoChannel(channelAttributes, accountCreated, t) 47 const videoChannel = await createLocalVideoChannel(channelAttributes, accountCreated, t)
47 48
48 const videoPlaylist = await createWatchLaterPlaylist(accountCreated, t) 49 const videoPlaylist = await createWatchLaterPlaylist(accountCreated, t)
49 50
50 return { user: userCreated, account: accountCreated, videoChannel, videoPlaylist } 51 return { user: userCreated, account: accountCreated, videoChannel, videoPlaylist }
51 }) 52 })
52 53
53 const [ accountKeys, channelKeys ] = await Promise.all([ 54 const [ accountActorWithKeys, channelActorWithKeys ] = await Promise.all([
54 setAsyncActorKeys(account.Actor), 55 setAsyncActorKeys(account.Actor),
55 setAsyncActorKeys(videoChannel.Actor) 56 setAsyncActorKeys(videoChannel.Actor)
56 ]) 57 ])
57 58
58 account.Actor = accountKeys 59 account.Actor = accountActorWithKeys
59 videoChannel.Actor = channelKeys 60 videoChannel.Actor = channelActorWithKeys
60 61
61 return { user, account, videoChannel } as { user: UserModel, account: AccountModel, videoChannel: VideoChannelModel } 62 return { user, account, videoChannel }
62} 63}
63 64
64async function createLocalAccountWithoutKeys (parameters: { 65async function createLocalAccountWithoutKeys (parameters: {
@@ -73,7 +74,7 @@ async function createLocalAccountWithoutKeys (parameters: {
73 const url = getAccountActivityPubUrl(name) 74 const url = getAccountActivityPubUrl(name)
74 75
75 const actorInstance = buildActorInstance(type, url, name) 76 const actorInstance = buildActorInstance(type, url, name)
76 const actorInstanceCreated = await actorInstance.save({ transaction: t }) 77 const actorInstanceCreated: MActorDefault = await actorInstance.save({ transaction: t })
77 78
78 const accountInstance = new AccountModel({ 79 const accountInstance = new AccountModel({
79 name: displayName || name, 80 name: displayName || name,
@@ -82,7 +83,7 @@ async function createLocalAccountWithoutKeys (parameters: {
82 actorId: actorInstanceCreated.id 83 actorId: actorInstanceCreated.id
83 }) 84 })
84 85
85 const accountInstanceCreated = await accountInstance.save({ transaction: t }) 86 const accountInstanceCreated: MAccountDefault = await accountInstance.save({ transaction: t })
86 accountInstanceCreated.Actor = actorInstanceCreated 87 accountInstanceCreated.Actor = actorInstanceCreated
87 88
88 return accountInstanceCreated 89 return accountInstanceCreated
@@ -102,7 +103,7 @@ async function createApplicationActor (applicationId: number) {
102 return accountCreated 103 return accountCreated
103} 104}
104 105
105async function sendVerifyUserEmail (user: UserModel, isPendingEmail = false) { 106async function sendVerifyUserEmail (user: MUser, isPendingEmail = false) {
106 const verificationString = await Redis.Instance.setVerifyEmailVerificationString(user.id) 107 const verificationString = await Redis.Instance.setVerifyEmailVerificationString(user.id)
107 let url = WEBSERVER.URL + '/verify-account/email?userId=' + user.id + '&verificationString=' + verificationString 108 let url = WEBSERVER.URL + '/verify-account/email?userId=' + user.id + '&verificationString=' + verificationString
108 109
@@ -124,7 +125,7 @@ export {
124 125
125// --------------------------------------------------------------------------- 126// ---------------------------------------------------------------------------
126 127
127function createDefaultUserNotificationSettings (user: UserModel, t: Transaction | undefined) { 128function createDefaultUserNotificationSettings (user: MUserId, t: Transaction | undefined) {
128 const values: UserNotificationSetting & { userId: number } = { 129 const values: UserNotificationSetting & { userId: number } = {
129 userId: user.id, 130 userId: user.id,
130 newVideoFromSubscription: UserNotificationSettingValue.WEB, 131 newVideoFromSubscription: UserNotificationSettingValue.WEB,
@@ -137,13 +138,14 @@ function createDefaultUserNotificationSettings (user: UserModel, t: Transaction
137 newUserRegistration: UserNotificationSettingValue.WEB, 138 newUserRegistration: UserNotificationSettingValue.WEB,
138 commentMention: UserNotificationSettingValue.WEB, 139 commentMention: UserNotificationSettingValue.WEB,
139 newFollow: UserNotificationSettingValue.WEB, 140 newFollow: UserNotificationSettingValue.WEB,
140 newInstanceFollower: UserNotificationSettingValue.WEB 141 newInstanceFollower: UserNotificationSettingValue.WEB,
142 autoInstanceFollowing: UserNotificationSettingValue.WEB
141 } 143 }
142 144
143 return UserNotificationSettingModel.create(values, { transaction: t }) 145 return UserNotificationSettingModel.create(values, { transaction: t })
144} 146}
145 147
146async function buildChannelAttributes (user: UserModel, channelNames?: ChannelNames) { 148async function buildChannelAttributes (user: MUser, channelNames?: ChannelNames) {
147 if (channelNames) return channelNames 149 if (channelNames) return channelNames
148 150
149 let channelName = user.username + '_channel' 151 let channelName = user.username + '_channel'
diff --git a/server/lib/video-blacklist.ts b/server/lib/video-blacklist.ts
index bdaecd8e2..1dd45b76d 100644
--- a/server/lib/video-blacklist.ts
+++ b/server/lib/video-blacklist.ts
@@ -2,16 +2,15 @@ import { Transaction } from 'sequelize'
2import { CONFIG } from '../initializers/config' 2import { CONFIG } from '../initializers/config'
3import { UserRight, VideoBlacklistType } from '../../shared/models' 3import { UserRight, VideoBlacklistType } from '../../shared/models'
4import { VideoBlacklistModel } from '../models/video/video-blacklist' 4import { VideoBlacklistModel } from '../models/video/video-blacklist'
5import { UserModel } from '../models/account/user'
6import { VideoModel } from '../models/video/video'
7import { logger } from '../helpers/logger' 5import { logger } from '../helpers/logger'
8import { UserAdminFlag } from '../../shared/models/users/user-flag.model' 6import { UserAdminFlag } from '../../shared/models/users/user-flag.model'
9import { Hooks } from './plugins/hooks' 7import { Hooks } from './plugins/hooks'
10import { Notifier } from './notifier' 8import { Notifier } from './notifier'
9import { MUser, MVideoBlacklistVideo, MVideoWithBlacklistLight } from '@server/typings/models'
11 10
12async function autoBlacklistVideoIfNeeded (parameters: { 11async function autoBlacklistVideoIfNeeded (parameters: {
13 video: VideoModel, 12 video: MVideoWithBlacklistLight,
14 user?: UserModel, 13 user?: MUser,
15 isRemote: boolean, 14 isRemote: boolean,
16 isNew: boolean, 15 isNew: boolean,
17 notify?: boolean, 16 notify?: boolean,
@@ -32,7 +31,7 @@ async function autoBlacklistVideoIfNeeded (parameters: {
32 reason: 'Auto-blacklisted. Moderator review required.', 31 reason: 'Auto-blacklisted. Moderator review required.',
33 type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED 32 type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED
34 } 33 }
35 const [ videoBlacklist ] = await VideoBlacklistModel.findOrCreate({ 34 const [ videoBlacklist ] = await VideoBlacklistModel.findOrCreate<MVideoBlacklistVideo>({
36 where: { 35 where: {
37 videoId: video.id 36 videoId: video.id
38 }, 37 },
@@ -41,7 +40,9 @@ async function autoBlacklistVideoIfNeeded (parameters: {
41 }) 40 })
42 video.VideoBlacklist = videoBlacklist 41 video.VideoBlacklist = videoBlacklist
43 42
44 if (notify) Notifier.Instance.notifyOnVideoAutoBlacklist(video) 43 videoBlacklist.Video = video
44
45 if (notify) Notifier.Instance.notifyOnVideoAutoBlacklist(videoBlacklist)
45 46
46 logger.info('Video %s auto-blacklisted.', video.uuid) 47 logger.info('Video %s auto-blacklisted.', video.uuid)
47 48
@@ -49,10 +50,10 @@ async function autoBlacklistVideoIfNeeded (parameters: {
49} 50}
50 51
51async function autoBlacklistNeeded (parameters: { 52async function autoBlacklistNeeded (parameters: {
52 video: VideoModel, 53 video: MVideoWithBlacklistLight,
53 isRemote: boolean, 54 isRemote: boolean,
54 isNew: boolean, 55 isNew: boolean,
55 user?: UserModel 56 user?: MUser
56}) { 57}) {
57 const { user, video, isRemote, isNew } = parameters 58 const { user, video, isRemote, isNew } = parameters
58 59
diff --git a/server/lib/video-channel.ts b/server/lib/video-channel.ts
index ee0482c36..41eab456b 100644
--- a/server/lib/video-channel.ts
+++ b/server/lib/video-channel.ts
@@ -1,12 +1,19 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import * as uuidv4 from 'uuid/v4' 2import * as uuidv4 from 'uuid/v4'
3import { VideoChannelCreate } from '../../shared/models' 3import { VideoChannelCreate } from '../../shared/models'
4import { AccountModel } from '../models/account/account'
5import { VideoChannelModel } from '../models/video/video-channel' 4import { VideoChannelModel } from '../models/video/video-channel'
6import { buildActorInstance, federateVideoIfNeeded, getVideoChannelActivityPubUrl } from './activitypub' 5import { buildActorInstance, federateVideoIfNeeded, getVideoChannelActivityPubUrl } from './activitypub'
7import { VideoModel } from '../models/video/video' 6import { VideoModel } from '../models/video/video'
7import { MAccountId, MChannelDefault, MChannelId } from '../typings/models'
8 8
9async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account: AccountModel, t: Sequelize.Transaction) { 9type CustomVideoChannelModelAccount <T extends MAccountId> = MChannelDefault &
10 { Account?: T }
11
12async function createLocalVideoChannel <T extends MAccountId> (
13 videoChannelInfo: VideoChannelCreate,
14 account: T,
15 t: Sequelize.Transaction
16): Promise<CustomVideoChannelModelAccount<T>> {
10 const uuid = uuidv4() 17 const uuid = uuidv4()
11 const url = getVideoChannelActivityPubUrl(videoChannelInfo.name) 18 const url = getVideoChannelActivityPubUrl(videoChannelInfo.name)
12 const actorInstance = buildActorInstance('Group', url, videoChannelInfo.name, uuid) 19 const actorInstance = buildActorInstance('Group', url, videoChannelInfo.name, uuid)
@@ -21,10 +28,10 @@ async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account
21 actorId: actorInstanceCreated.id 28 actorId: actorInstanceCreated.id
22 } 29 }
23 30
24 const videoChannel = VideoChannelModel.build(videoChannelData) 31 const videoChannel = new VideoChannelModel(videoChannelData)
25 32
26 const options = { transaction: t } 33 const options = { transaction: t }
27 const videoChannelCreated = await videoChannel.save(options) 34 const videoChannelCreated: CustomVideoChannelModelAccount<T> = await videoChannel.save(options) as MChannelDefault
28 35
29 // Do not forget to add Account/Actor information to the created video channel 36 // Do not forget to add Account/Actor information to the created video channel
30 videoChannelCreated.Account = account 37 videoChannelCreated.Account = account
@@ -34,7 +41,7 @@ async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account
34 return videoChannelCreated 41 return videoChannelCreated
35} 42}
36 43
37async function federateAllVideosOfChannel (videoChannel: VideoChannelModel) { 44async function federateAllVideosOfChannel (videoChannel: MChannelId) {
38 const videoIds = await VideoModel.getAllIdsFromChannel(videoChannel) 45 const videoIds = await VideoModel.getAllIdsFromChannel(videoChannel)
39 46
40 for (const videoId of videoIds) { 47 for (const videoId of videoIds) {
@@ -47,6 +54,6 @@ async function federateAllVideosOfChannel (videoChannel: VideoChannelModel) {
47// --------------------------------------------------------------------------- 54// ---------------------------------------------------------------------------
48 55
49export { 56export {
50 createVideoChannel, 57 createLocalVideoChannel,
51 federateAllVideosOfChannel 58 federateAllVideosOfChannel
52} 59}
diff --git a/server/lib/video-comment.ts b/server/lib/video-comment.ts
index 449aa74cb..bb811bd2c 100644
--- a/server/lib/video-comment.ts
+++ b/server/lib/video-comment.ts
@@ -1,17 +1,16 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { ResultList } from '../../shared/models' 2import { ResultList } from '../../shared/models'
3import { VideoCommentThreadTree } from '../../shared/models/videos/video-comment.model' 3import { VideoCommentThreadTree } from '../../shared/models/videos/video-comment.model'
4import { AccountModel } from '../models/account/account'
5import { VideoModel } from '../models/video/video'
6import { VideoCommentModel } from '../models/video/video-comment' 4import { VideoCommentModel } from '../models/video/video-comment'
7import { getVideoCommentActivityPubUrl } from './activitypub' 5import { getVideoCommentActivityPubUrl } from './activitypub'
8import { sendCreateVideoComment } from './activitypub/send' 6import { sendCreateVideoComment } from './activitypub/send'
7import { MAccountDefault, MComment, MCommentOwnerVideoReply, MVideoFullLight } from '../typings/models'
9 8
10async function createVideoComment (obj: { 9async function createVideoComment (obj: {
11 text: string, 10 text: string,
12 inReplyToComment: VideoCommentModel | null, 11 inReplyToComment: MComment | null,
13 video: VideoModel 12 video: MVideoFullLight,
14 account: AccountModel 13 account: MAccountDefault
15}, t: Sequelize.Transaction) { 14}, t: Sequelize.Transaction) {
16 let originCommentId: number | null = null 15 let originCommentId: number | null = null
17 let inReplyToCommentId: number | null = null 16 let inReplyToCommentId: number | null = null
@@ -32,7 +31,7 @@ async function createVideoComment (obj: {
32 31
33 comment.url = getVideoCommentActivityPubUrl(obj.video, comment) 32 comment.url = getVideoCommentActivityPubUrl(obj.video, comment)
34 33
35 const savedComment = await comment.save({ transaction: t }) 34 const savedComment: MCommentOwnerVideoReply = await comment.save({ transaction: t })
36 savedComment.InReplyToVideoComment = obj.inReplyToComment 35 savedComment.InReplyToVideoComment = obj.inReplyToComment
37 savedComment.Video = obj.video 36 savedComment.Video = obj.video
38 savedComment.Account = obj.account 37 savedComment.Account = obj.account
diff --git a/server/lib/video-playlist.ts b/server/lib/video-playlist.ts
index 6e214e60f..29b70cfda 100644
--- a/server/lib/video-playlist.ts
+++ b/server/lib/video-playlist.ts
@@ -1,12 +1,13 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { AccountModel } from '../models/account/account'
3import { VideoPlaylistModel } from '../models/video/video-playlist' 2import { VideoPlaylistModel } from '../models/video/video-playlist'
4import { VideoPlaylistPrivacy } from '../../shared/models/videos/playlist/video-playlist-privacy.model' 3import { VideoPlaylistPrivacy } from '../../shared/models/videos/playlist/video-playlist-privacy.model'
5import { getVideoPlaylistActivityPubUrl } from './activitypub' 4import { getVideoPlaylistActivityPubUrl } from './activitypub'
6import { VideoPlaylistType } from '../../shared/models/videos/playlist/video-playlist-type.model' 5import { VideoPlaylistType } from '../../shared/models/videos/playlist/video-playlist-type.model'
6import { MAccount } from '../typings/models'
7import { MVideoPlaylistOwner } from '../typings/models/video/video-playlist'
7 8
8async function createWatchLaterPlaylist (account: AccountModel, t: Sequelize.Transaction) { 9async function createWatchLaterPlaylist (account: MAccount, t: Sequelize.Transaction) {
9 const videoPlaylist = new VideoPlaylistModel({ 10 const videoPlaylist: MVideoPlaylistOwner = new VideoPlaylistModel({
10 name: 'Watch later', 11 name: 'Watch later',
11 privacy: VideoPlaylistPrivacy.PRIVATE, 12 privacy: VideoPlaylistPrivacy.PRIVATE,
12 type: VideoPlaylistType.WATCH_LATER, 13 type: VideoPlaylistType.WATCH_LATER,
diff --git a/server/lib/video-transcoding.ts b/server/lib/video-transcoding.ts
index ba6b29163..a204c0c63 100644
--- a/server/lib/video-transcoding.ts
+++ b/server/lib/video-transcoding.ts
@@ -5,16 +5,16 @@ import { ensureDir, move, remove, stat } from 'fs-extra'
5import { logger } from '../helpers/logger' 5import { logger } from '../helpers/logger'
6import { VideoResolution } from '../../shared/models/videos' 6import { VideoResolution } from '../../shared/models/videos'
7import { VideoFileModel } from '../models/video/video-file' 7import { VideoFileModel } from '../models/video/video-file'
8import { VideoModel } from '../models/video/video'
9import { updateMasterHLSPlaylist, updateSha256Segments } from './hls' 8import { updateMasterHLSPlaylist, updateSha256Segments } from './hls'
10import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' 9import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist'
11import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type' 10import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type'
12import { CONFIG } from '../initializers/config' 11import { CONFIG } from '../initializers/config'
12import { MVideoFile, MVideoWithFile, MVideoWithFileThumbnail } from '@server/typings/models'
13 13
14/** 14/**
15 * Optimize the original video file and replace it. The resolution is not changed. 15 * Optimize the original video file and replace it. The resolution is not changed.
16 */ 16 */
17async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFileModel) { 17async function optimizeVideofile (video: MVideoWithFile, inputVideoFileArg?: MVideoFile) {
18 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR 18 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
19 const transcodeDirectory = CONFIG.STORAGE.TMP_DIR 19 const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
20 const newExtname = '.mp4' 20 const newExtname = '.mp4'
@@ -57,7 +57,7 @@ async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFi
57/** 57/**
58 * Transcode the original video file to a lower resolution. 58 * Transcode the original video file to a lower resolution.
59 */ 59 */
60async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoResolution, isPortrait: boolean) { 60async function transcodeOriginalVideofile (video: MVideoWithFile, resolution: VideoResolution, isPortrait: boolean) {
61 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR 61 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
62 const transcodeDirectory = CONFIG.STORAGE.TMP_DIR 62 const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
63 const extname = '.mp4' 63 const extname = '.mp4'
@@ -87,7 +87,7 @@ async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoR
87 return onVideoFileTranscoding(video, newVideoFile, videoTranscodedPath, videoOutputPath) 87 return onVideoFileTranscoding(video, newVideoFile, videoTranscodedPath, videoOutputPath)
88} 88}
89 89
90async function mergeAudioVideofile (video: VideoModel, resolution: VideoResolution) { 90async function mergeAudioVideofile (video: MVideoWithFileThumbnail, resolution: VideoResolution) {
91 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR 91 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
92 const transcodeDirectory = CONFIG.STORAGE.TMP_DIR 92 const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
93 const newExtname = '.mp4' 93 const newExtname = '.mp4'
@@ -117,7 +117,7 @@ async function mergeAudioVideofile (video: VideoModel, resolution: VideoResoluti
117 return onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath) 117 return onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath)
118} 118}
119 119
120async function generateHlsPlaylist (video: VideoModel, resolution: VideoResolution, isPortraitMode: boolean) { 120async function generateHlsPlaylist (video: MVideoWithFile, resolution: VideoResolution, isPortraitMode: boolean) {
121 const baseHlsDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) 121 const baseHlsDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)
122 await ensureDir(join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)) 122 await ensureDir(join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid))
123 123
@@ -165,14 +165,14 @@ export {
165 165
166// --------------------------------------------------------------------------- 166// ---------------------------------------------------------------------------
167 167
168async function onVideoFileTranscoding (video: VideoModel, videoFile: VideoFileModel, transcodingPath: string, outputPath: string) { 168async function onVideoFileTranscoding (video: MVideoWithFile, videoFile: MVideoFile, transcodingPath: string, outputPath: string) {
169 const stats = await stat(transcodingPath) 169 const stats = await stat(transcodingPath)
170 const fps = await getVideoFileFPS(transcodingPath) 170 const fps = await getVideoFileFPS(transcodingPath)
171 171
172 await move(transcodingPath, outputPath) 172 await move(transcodingPath, outputPath)
173 173
174 videoFile.set('size', stats.size) 174 videoFile.size = stats.size
175 videoFile.set('fps', fps) 175 videoFile.fps = fps
176 176
177 await video.createTorrentAndSetInfoHash(videoFile) 177 await video.createTorrentAndSetInfoHash(videoFile)
178 178