diff options
Diffstat (limited to 'server/lib')
49 files changed, 547 insertions, 406 deletions
diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts index 0b21de0ca..9eabef4b0 100644 --- a/server/lib/activitypub/actor.ts +++ b/server/lib/activitypub/actor.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import { Transaction } from 'sequelize' | 2 | import { Transaction } from 'sequelize' |
3 | import * as url from 'url' | 3 | import { URL } from 'url' |
4 | import * as uuidv4 from 'uuid/v4' | 4 | import * as uuidv4 from 'uuid/v4' |
5 | import { ActivityPubActor, ActivityPubActorType } from '../../../shared/models/activitypub' | 5 | import { ActivityPubActor, ActivityPubActorType } from '../../../shared/models/activitypub' |
6 | import { ActivityPubAttributedTo } from '../../../shared/models/activitypub/objects' | 6 | import { ActivityPubAttributedTo } from '../../../shared/models/activitypub/objects' |
@@ -33,9 +33,9 @@ import { | |||
33 | MActorFull, | 33 | MActorFull, |
34 | MActorFullActor, | 34 | MActorFullActor, |
35 | MActorId, | 35 | MActorId, |
36 | MChannel, | 36 | MChannel |
37 | MChannelAccountDefault | ||
38 | } from '../../typings/models' | 37 | } from '../../typings/models' |
38 | import { extname } from 'path' | ||
39 | 39 | ||
40 | // 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 |
41 | function setAsyncActorKeys <T extends MActor> (actor: T) { | 41 | function setAsyncActorKeys <T extends MActor> (actor: T) { |
@@ -121,13 +121,13 @@ async function getOrCreateActorAndServerAndModel ( | |||
121 | 121 | ||
122 | if ((created === true || refreshed === true) && updateCollections === true) { | 122 | if ((created === true || refreshed === true) && updateCollections === true) { |
123 | const payload = { uri: actor.outboxUrl, type: 'activity' as 'activity' } | 123 | const payload = { uri: actor.outboxUrl, type: 'activity' as 'activity' } |
124 | await JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload }) | 124 | await JobQueue.Instance.createJobWithPromise({ type: 'activitypub-http-fetcher', payload }) |
125 | } | 125 | } |
126 | 126 | ||
127 | // We created a new account: fetch the playlists | 127 | // We created a new account: fetch the playlists |
128 | if (created === true && actor.Account && accountPlaylistsUrl) { | 128 | if (created === true && actor.Account && accountPlaylistsUrl) { |
129 | const payload = { uri: accountPlaylistsUrl, accountId: actor.Account.id, type: 'account-playlists' as 'account-playlists' } | 129 | const payload = { uri: accountPlaylistsUrl, accountId: actor.Account.id, type: 'account-playlists' as 'account-playlists' } |
130 | await JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload }) | 130 | await JobQueue.Instance.createJobWithPromise({ type: 'activitypub-http-fetcher', payload }) |
131 | } | 131 | } |
132 | 132 | ||
133 | return actorRefreshed | 133 | return actorRefreshed |
@@ -215,20 +215,28 @@ async function fetchActorTotalItems (url: string) { | |||
215 | } | 215 | } |
216 | } | 216 | } |
217 | 217 | ||
218 | async function getAvatarInfoIfExists (actorJSON: ActivityPubActor) { | 218 | function getAvatarInfoIfExists (actorJSON: ActivityPubActor) { |
219 | if ( | 219 | const mimetypes = MIMETYPES.IMAGE |
220 | actorJSON.icon && actorJSON.icon.type === 'Image' && MIMETYPES.IMAGE.MIMETYPE_EXT[actorJSON.icon.mediaType] !== undefined && | 220 | const icon = actorJSON.icon |
221 | isActivityPubUrlValid(actorJSON.icon.url) | ||
222 | ) { | ||
223 | const extension = MIMETYPES.IMAGE.MIMETYPE_EXT[actorJSON.icon.mediaType] | ||
224 | 221 | ||
225 | return { | 222 | if (!icon || icon.type !== 'Image' || !isActivityPubUrlValid(icon.url)) return undefined |
226 | name: uuidv4() + extension, | 223 | |
227 | fileUrl: actorJSON.icon.url | 224 | let extension: string |
228 | } | 225 | |
226 | if (icon.mediaType) { | ||
227 | extension = mimetypes.MIMETYPE_EXT[icon.mediaType] | ||
228 | } else { | ||
229 | const tmp = extname(icon.url) | ||
230 | |||
231 | if (mimetypes.EXT_MIMETYPE[tmp] !== undefined) extension = tmp | ||
229 | } | 232 | } |
230 | 233 | ||
231 | return undefined | 234 | if (!extension) return undefined |
235 | |||
236 | return { | ||
237 | name: uuidv4() + extension, | ||
238 | fileUrl: icon.url | ||
239 | } | ||
232 | } | 240 | } |
233 | 241 | ||
234 | async function addFetchOutboxJob (actor: Pick<ActorModel, 'id' | 'outboxUrl'>) { | 242 | async function addFetchOutboxJob (actor: Pick<ActorModel, 'id' | 'outboxUrl'>) { |
@@ -271,7 +279,10 @@ async function refreshActorIfNeeded <T extends MActorFull | MActorAccountChannel | |||
271 | 279 | ||
272 | if (statusCode === 404) { | 280 | if (statusCode === 404) { |
273 | logger.info('Deleting actor %s because there is a 404 in refresh actor.', actor.url) | 281 | logger.info('Deleting actor %s because there is a 404 in refresh actor.', actor.url) |
274 | actor.Account ? actor.Account.destroy() : actor.VideoChannel.destroy() | 282 | actor.Account |
283 | ? await actor.Account.destroy() | ||
284 | : await actor.VideoChannel.destroy() | ||
285 | |||
275 | return { actor: undefined, refreshed: false } | 286 | return { actor: undefined, refreshed: false } |
276 | } | 287 | } |
277 | 288 | ||
@@ -337,14 +348,14 @@ function saveActorAndServerAndModelIfNotExist ( | |||
337 | ownerActor?: MActorFullActor, | 348 | ownerActor?: MActorFullActor, |
338 | t?: Transaction | 349 | t?: Transaction |
339 | ): Bluebird<MActorFullActor> | Promise<MActorFullActor> { | 350 | ): Bluebird<MActorFullActor> | Promise<MActorFullActor> { |
340 | let actor = result.actor | 351 | const actor = result.actor |
341 | 352 | ||
342 | if (t !== undefined) return save(t) | 353 | if (t !== undefined) return save(t) |
343 | 354 | ||
344 | return sequelizeTypescript.transaction(t => save(t)) | 355 | return sequelizeTypescript.transaction(t => save(t)) |
345 | 356 | ||
346 | async function save (t: Transaction) { | 357 | async function save (t: Transaction) { |
347 | const actorHost = url.parse(actor.url).host | 358 | const actorHost = new URL(actor.url).host |
348 | 359 | ||
349 | const serverOptions = { | 360 | const serverOptions = { |
350 | where: { | 361 | where: { |
@@ -402,7 +413,7 @@ type FetchRemoteActorResult = { | |||
402 | support?: string | 413 | support?: string |
403 | playlists?: string | 414 | playlists?: string |
404 | avatar?: { | 415 | avatar?: { |
405 | name: string, | 416 | name: string |
406 | fileUrl: string | 417 | fileUrl: string |
407 | } | 418 | } |
408 | attributedTo: ActivityPubAttributedTo[] | 419 | attributedTo: ActivityPubAttributedTo[] |
diff --git a/server/lib/activitypub/audience.ts b/server/lib/activitypub/audience.ts index f2ab54cf7..9933ae2b5 100644 --- a/server/lib/activitypub/audience.ts +++ b/server/lib/activitypub/audience.ts | |||
@@ -4,11 +4,11 @@ import { ACTIVITY_PUB } from '../../initializers/constants' | |||
4 | import { ActorModel } from '../../models/activitypub/actor' | 4 | import { ActorModel } from '../../models/activitypub/actor' |
5 | import { VideoModel } from '../../models/video/video' | 5 | import { VideoModel } from '../../models/video/video' |
6 | import { VideoShareModel } from '../../models/video/video-share' | 6 | import { VideoShareModel } from '../../models/video/video-share' |
7 | import { MActorFollowersUrl, MActorLight, MCommentOwner, MCommentOwnerVideo, MVideo, MVideoAccountLight } from '../../typings/models' | 7 | import { MActorFollowersUrl, MActorLight, MActorUrl, MCommentOwner, MCommentOwnerVideo, MVideoId } from '../../typings/models' |
8 | 8 | ||
9 | function getRemoteVideoAudience (video: MVideoAccountLight, actorsInvolvedInVideo: MActorFollowersUrl[]): ActivityAudience { | 9 | function getRemoteVideoAudience (accountActor: MActorUrl, actorsInvolvedInVideo: MActorFollowersUrl[]): ActivityAudience { |
10 | return { | 10 | return { |
11 | to: [ video.VideoChannel.Account.Actor.url ], | 11 | to: [ accountActor.url ], |
12 | cc: actorsInvolvedInVideo.map(a => a.followersUrl) | 12 | cc: actorsInvolvedInVideo.map(a => a.followersUrl) |
13 | } | 13 | } |
14 | } | 14 | } |
@@ -48,7 +48,7 @@ function getAudienceFromFollowersOf (actorsInvolvedInObject: MActorFollowersUrl[ | |||
48 | } | 48 | } |
49 | } | 49 | } |
50 | 50 | ||
51 | async function getActorsInvolvedInVideo (video: MVideo, t: Transaction) { | 51 | async function getActorsInvolvedInVideo (video: MVideoId, t: Transaction) { |
52 | const actors: MActorLight[] = await VideoShareModel.loadActorsByShare(video.id, t) | 52 | const actors: MActorLight[] = await VideoShareModel.loadActorsByShare(video.id, t) |
53 | 53 | ||
54 | const videoAll = video as VideoModel | 54 | const videoAll = video as VideoModel |
diff --git a/server/lib/activitypub/cache-file.ts b/server/lib/activitypub/cache-file.ts index 65b2dcb49..8252e95e9 100644 --- a/server/lib/activitypub/cache-file.ts +++ b/server/lib/activitypub/cache-file.ts | |||
@@ -13,7 +13,7 @@ function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject | |||
13 | if (!playlist) throw new Error('Cannot find HLS playlist of video ' + video.url) | 13 | if (!playlist) throw new Error('Cannot find HLS playlist of video ' + video.url) |
14 | 14 | ||
15 | return { | 15 | return { |
16 | expiresOn: new Date(cacheFileObject.expires), | 16 | expiresOn: cacheFileObject.expires ? new Date(cacheFileObject.expires) : null, |
17 | url: cacheFileObject.id, | 17 | url: cacheFileObject.id, |
18 | fileUrl: url.href, | 18 | fileUrl: url.href, |
19 | strategy: null, | 19 | strategy: null, |
@@ -30,7 +30,7 @@ function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject | |||
30 | if (!videoFile) throw new Error(`Cannot find video file ${url.height} ${url.fps} of video ${video.url}`) | 30 | if (!videoFile) throw new Error(`Cannot find video file ${url.height} ${url.fps} of video ${video.url}`) |
31 | 31 | ||
32 | return { | 32 | return { |
33 | expiresOn: new Date(cacheFileObject.expires), | 33 | expiresOn: cacheFileObject.expires ? new Date(cacheFileObject.expires) : null, |
34 | url: cacheFileObject.id, | 34 | url: cacheFileObject.id, |
35 | fileUrl: url.href, | 35 | fileUrl: url.href, |
36 | strategy: null, | 36 | strategy: null, |
diff --git a/server/lib/activitypub/crawl.ts b/server/lib/activitypub/crawl.ts index 9e469e3e6..eeafdf4ba 100644 --- a/server/lib/activitypub/crawl.ts +++ b/server/lib/activitypub/crawl.ts | |||
@@ -3,7 +3,7 @@ import { doRequest } from '../../helpers/requests' | |||
3 | import { logger } from '../../helpers/logger' | 3 | import { logger } from '../../helpers/logger' |
4 | import * as Bluebird from 'bluebird' | 4 | import * as Bluebird from 'bluebird' |
5 | import { ActivityPubOrderedCollection } from '../../../shared/models/activitypub' | 5 | import { ActivityPubOrderedCollection } from '../../../shared/models/activitypub' |
6 | import { parse } from 'url' | 6 | import { URL } from 'url' |
7 | 7 | ||
8 | type HandlerFunction<T> = (items: T[]) => (Promise<any> | Bluebird<any>) | 8 | type HandlerFunction<T> = (items: T[]) => (Promise<any> | Bluebird<any>) |
9 | type CleanerFunction = (startedDate: Date) => (Promise<any> | Bluebird<any>) | 9 | type CleanerFunction = (startedDate: Date) => (Promise<any> | Bluebird<any>) |
@@ -24,7 +24,7 @@ async function crawlCollectionPage <T> (uri: string, handler: HandlerFunction<T> | |||
24 | const response = await doRequest<ActivityPubOrderedCollection<T>>(options) | 24 | const response = await doRequest<ActivityPubOrderedCollection<T>>(options) |
25 | const firstBody = response.body | 25 | const firstBody = response.body |
26 | 26 | ||
27 | let limit = ACTIVITY_PUB.FETCH_PAGE_LIMIT | 27 | const limit = ACTIVITY_PUB.FETCH_PAGE_LIMIT |
28 | let i = 0 | 28 | let i = 0 |
29 | let nextLink = firstBody.first | 29 | let nextLink = firstBody.first |
30 | while (nextLink && i < limit) { | 30 | while (nextLink && i < limit) { |
@@ -32,7 +32,7 @@ async function crawlCollectionPage <T> (uri: string, handler: HandlerFunction<T> | |||
32 | 32 | ||
33 | if (typeof nextLink === 'string') { | 33 | if (typeof nextLink === 'string') { |
34 | // Don't crawl ourselves | 34 | // Don't crawl ourselves |
35 | const remoteHost = parse(nextLink).host | 35 | const remoteHost = new URL(nextLink).host |
36 | if (remoteHost === WEBSERVER.HOST) continue | 36 | if (remoteHost === WEBSERVER.HOST) continue |
37 | 37 | ||
38 | options.uri = nextLink | 38 | options.uri = nextLink |
diff --git a/server/lib/activitypub/follow.ts b/server/lib/activitypub/follow.ts index 1abf43cd4..a1c95504e 100644 --- a/server/lib/activitypub/follow.ts +++ b/server/lib/activitypub/follow.ts | |||
@@ -27,7 +27,6 @@ async function autoFollowBackIfNeeded (actorFollow: MActorFollowActors) { | |||
27 | } | 27 | } |
28 | 28 | ||
29 | JobQueue.Instance.createJob({ type: 'activitypub-follow', payload }) | 29 | JobQueue.Instance.createJob({ type: 'activitypub-follow', payload }) |
30 | .catch(err => logger.error('Cannot create auto follow back job for %s.', host, err)) | ||
31 | } | 30 | } |
32 | } | 31 | } |
33 | 32 | ||
diff --git a/server/lib/activitypub/process/process-view.ts b/server/lib/activitypub/process/process-view.ts index df29ee968..b3b6c933d 100644 --- a/server/lib/activitypub/process/process-view.ts +++ b/server/lib/activitypub/process/process-view.ts | |||
@@ -23,7 +23,8 @@ async function processCreateView (activity: ActivityView | ActivityCreate, byAct | |||
23 | 23 | ||
24 | const options = { | 24 | const options = { |
25 | videoObject, | 25 | videoObject, |
26 | fetchType: 'only-video' as 'only-video' | 26 | fetchType: 'only-immutable-attributes' as 'only-immutable-attributes', |
27 | allowRefresh: false as false | ||
27 | } | 28 | } |
28 | const { video } = await getOrCreateVideoAndAccountAndChannel(options) | 29 | const { video } = await getOrCreateVideoAndAccountAndChannel(options) |
29 | 30 | ||
diff --git a/server/lib/activitypub/send/send-accept.ts b/server/lib/activitypub/send/send-accept.ts index 9f0225b64..c4c6b849b 100644 --- a/server/lib/activitypub/send/send-accept.ts +++ b/server/lib/activitypub/send/send-accept.ts | |||
@@ -5,7 +5,7 @@ import { buildFollowActivity } from './send-follow' | |||
5 | import { logger } from '../../../helpers/logger' | 5 | import { logger } from '../../../helpers/logger' |
6 | import { MActor, MActorFollowActors } from '../../../typings/models' | 6 | import { MActor, MActorFollowActors } from '../../../typings/models' |
7 | 7 | ||
8 | async function sendAccept (actorFollow: MActorFollowActors) { | 8 | function sendAccept (actorFollow: MActorFollowActors) { |
9 | const follower = actorFollow.ActorFollower | 9 | const follower = actorFollow.ActorFollower |
10 | const me = actorFollow.ActorFollowing | 10 | const me = actorFollow.ActorFollowing |
11 | 11 | ||
diff --git a/server/lib/activitypub/send/send-announce.ts b/server/lib/activitypub/send/send-announce.ts index a0f33852c..d03b358f1 100644 --- a/server/lib/activitypub/send/send-announce.ts +++ b/server/lib/activitypub/send/send-announce.ts | |||
@@ -28,7 +28,7 @@ async function sendVideoAnnounce (byActor: MActorLight, videoShare: MVideoShare, | |||
28 | logger.info('Creating job to send announce %s.', videoShare.url) | 28 | logger.info('Creating job to send announce %s.', videoShare.url) |
29 | 29 | ||
30 | const followersException = [ byActor ] | 30 | const followersException = [ byActor ] |
31 | return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, t, followersException) | 31 | return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, t, followersException, 'Announce') |
32 | } | 32 | } |
33 | 33 | ||
34 | function buildAnnounceActivity (url: string, byActor: MActorLight, object: string, audience?: ActivityAudience): ActivityAnnounce { | 34 | function buildAnnounceActivity (url: string, byActor: MActorLight, object: string, audience?: ActivityAudience): ActivityAnnounce { |
diff --git a/server/lib/activitypub/send/send-create.ts b/server/lib/activitypub/send/send-create.ts index 1709d8348..8bdcf6417 100644 --- a/server/lib/activitypub/send/send-create.ts +++ b/server/lib/activitypub/send/send-create.ts | |||
@@ -16,6 +16,7 @@ import { | |||
16 | MVideoRedundancyFileVideo, | 16 | MVideoRedundancyFileVideo, |
17 | MVideoRedundancyStreamingPlaylistVideo | 17 | MVideoRedundancyStreamingPlaylistVideo |
18 | } from '../../../typings/models' | 18 | } from '../../../typings/models' |
19 | import { ContextType } from '@server/helpers/activitypub' | ||
19 | 20 | ||
20 | async function sendCreateVideo (video: MVideoAP, t: Transaction) { | 21 | async function sendCreateVideo (video: MVideoAP, t: Transaction) { |
21 | if (!video.hasPrivacyForFederation()) return undefined | 22 | if (!video.hasPrivacyForFederation()) return undefined |
@@ -42,7 +43,8 @@ async function sendCreateCacheFile ( | |||
42 | byActor, | 43 | byActor, |
43 | video, | 44 | video, |
44 | url: fileRedundancy.url, | 45 | url: fileRedundancy.url, |
45 | object: fileRedundancy.toActivityPubObject() | 46 | object: fileRedundancy.toActivityPubObject(), |
47 | contextType: 'CacheFile' | ||
46 | }) | 48 | }) |
47 | } | 49 | } |
48 | 50 | ||
@@ -130,11 +132,12 @@ export { | |||
130 | // --------------------------------------------------------------------------- | 132 | // --------------------------------------------------------------------------- |
131 | 133 | ||
132 | async function sendVideoRelatedCreateActivity (options: { | 134 | async function sendVideoRelatedCreateActivity (options: { |
133 | byActor: MActorLight, | 135 | byActor: MActorLight |
134 | video: MVideoAccountLight, | 136 | video: MVideoAccountLight |
135 | url: string, | 137 | url: string |
136 | object: any, | 138 | object: any |
137 | transaction?: Transaction | 139 | transaction?: Transaction |
140 | contextType?: ContextType | ||
138 | }) { | 141 | }) { |
139 | const activityBuilder = (audience: ActivityAudience) => { | 142 | const activityBuilder = (audience: ActivityAudience) => { |
140 | return buildCreateActivity(options.url, options.byActor, options.object, audience) | 143 | return buildCreateActivity(options.url, options.byActor, options.object, audience) |
diff --git a/server/lib/activitypub/send/send-dislike.ts b/server/lib/activitypub/send/send-dislike.ts index 6e41f241f..600469c71 100644 --- a/server/lib/activitypub/send/send-dislike.ts +++ b/server/lib/activitypub/send/send-dislike.ts | |||
@@ -6,7 +6,7 @@ import { sendVideoRelatedActivity } from './utils' | |||
6 | import { audiencify, getAudience } from '../audience' | 6 | import { audiencify, getAudience } from '../audience' |
7 | import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../typings/models' | 7 | import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../typings/models' |
8 | 8 | ||
9 | async function sendDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { | 9 | function sendDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { |
10 | logger.info('Creating job to dislike %s.', video.url) | 10 | logger.info('Creating job to dislike %s.', video.url) |
11 | 11 | ||
12 | const activityBuilder = (audience: ActivityAudience) => { | 12 | const activityBuilder = (audience: ActivityAudience) => { |
diff --git a/server/lib/activitypub/send/send-flag.ts b/server/lib/activitypub/send/send-flag.ts index da7638a7b..e4e523631 100644 --- a/server/lib/activitypub/send/send-flag.ts +++ b/server/lib/activitypub/send/send-flag.ts | |||
@@ -7,7 +7,7 @@ import { Transaction } from 'sequelize' | |||
7 | import { MActor, MVideoFullLight } from '../../../typings/models' | 7 | import { MActor, MVideoFullLight } from '../../../typings/models' |
8 | import { MVideoAbuseVideo } from '../../../typings/models/video' | 8 | import { MVideoAbuseVideo } from '../../../typings/models/video' |
9 | 9 | ||
10 | async function sendVideoAbuse (byActor: MActor, videoAbuse: MVideoAbuseVideo, video: MVideoFullLight, t: Transaction) { | 10 | function sendVideoAbuse (byActor: MActor, videoAbuse: MVideoAbuseVideo, video: MVideoFullLight, t: Transaction) { |
11 | if (!video.VideoChannel.Account.Actor.serverId) return // Local user | 11 | if (!video.VideoChannel.Account.Actor.serverId) return // Local user |
12 | 12 | ||
13 | const url = getVideoAbuseActivityPubUrl(videoAbuse) | 13 | const url = getVideoAbuseActivityPubUrl(videoAbuse) |
diff --git a/server/lib/activitypub/send/send-like.ts b/server/lib/activitypub/send/send-like.ts index e84a6f98b..5db252325 100644 --- a/server/lib/activitypub/send/send-like.ts +++ b/server/lib/activitypub/send/send-like.ts | |||
@@ -6,7 +6,7 @@ import { audiencify, getAudience } from '../audience' | |||
6 | import { logger } from '../../../helpers/logger' | 6 | import { logger } from '../../../helpers/logger' |
7 | import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../typings/models' | 7 | import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../typings/models' |
8 | 8 | ||
9 | async function sendLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { | 9 | function sendLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { |
10 | logger.info('Creating job to like %s.', video.url) | 10 | logger.info('Creating job to like %s.', video.url) |
11 | 11 | ||
12 | const activityBuilder = (audience: ActivityAudience) => { | 12 | const activityBuilder = (audience: ActivityAudience) => { |
diff --git a/server/lib/activitypub/send/send-reject.ts b/server/lib/activitypub/send/send-reject.ts index 4258a3c36..643c468a9 100644 --- a/server/lib/activitypub/send/send-reject.ts +++ b/server/lib/activitypub/send/send-reject.ts | |||
@@ -5,7 +5,7 @@ import { buildFollowActivity } from './send-follow' | |||
5 | import { logger } from '../../../helpers/logger' | 5 | import { logger } from '../../../helpers/logger' |
6 | import { MActor } from '../../../typings/models' | 6 | import { MActor } from '../../../typings/models' |
7 | 7 | ||
8 | async function sendReject (follower: MActor, following: MActor) { | 8 | function sendReject (follower: MActor, following: MActor) { |
9 | if (!follower.serverId) { // This should never happen | 9 | if (!follower.serverId) { // This should never happen |
10 | logger.warn('Do not sending reject to local follower.') | 10 | logger.warn('Do not sending reject to local follower.') |
11 | return | 11 | return |
diff --git a/server/lib/activitypub/send/send-undo.ts b/server/lib/activitypub/send/send-undo.ts index e9ab5b3c5..33f1d4921 100644 --- a/server/lib/activitypub/send/send-undo.ts +++ b/server/lib/activitypub/send/send-undo.ts | |||
@@ -28,7 +28,7 @@ import { | |||
28 | MVideoShare | 28 | MVideoShare |
29 | } from '../../../typings/models' | 29 | } from '../../../typings/models' |
30 | 30 | ||
31 | async function sendUndoFollow (actorFollow: MActorFollowActors, t: Transaction) { | 31 | function sendUndoFollow (actorFollow: MActorFollowActors, t: Transaction) { |
32 | const me = actorFollow.ActorFollower | 32 | const me = actorFollow.ActorFollower |
33 | const following = actorFollow.ActorFollowing | 33 | const following = actorFollow.ActorFollowing |
34 | 34 | ||
@@ -118,10 +118,10 @@ function undoActivityData ( | |||
118 | } | 118 | } |
119 | 119 | ||
120 | async function sendUndoVideoRelatedActivity (options: { | 120 | async function sendUndoVideoRelatedActivity (options: { |
121 | byActor: MActor, | 121 | byActor: MActor |
122 | video: MVideoAccountLight, | 122 | video: MVideoAccountLight |
123 | url: string, | 123 | url: string |
124 | activity: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce, | 124 | activity: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce |
125 | transaction: Transaction | 125 | transaction: Transaction |
126 | }) { | 126 | }) { |
127 | const activityBuilder = (audience: ActivityAudience) => { | 127 | const activityBuilder = (audience: ActivityAudience) => { |
diff --git a/server/lib/activitypub/send/send-update.ts b/server/lib/activitypub/send/send-update.ts index 9c76671b5..2b01ca5e7 100644 --- a/server/lib/activitypub/send/send-update.ts +++ b/server/lib/activitypub/send/send-update.ts | |||
@@ -8,7 +8,6 @@ import { getUpdateActivityPubUrl } from '../url' | |||
8 | import { broadcastToFollowers, sendVideoRelatedActivity } from './utils' | 8 | import { broadcastToFollowers, sendVideoRelatedActivity } from './utils' |
9 | import { audiencify, getActorsInvolvedInVideo, getAudience } from '../audience' | 9 | import { audiencify, getActorsInvolvedInVideo, getAudience } from '../audience' |
10 | import { logger } from '../../../helpers/logger' | 10 | import { logger } from '../../../helpers/logger' |
11 | import { VideoCaptionModel } from '../../../models/video/video-caption' | ||
12 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' | 11 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' |
13 | import { getServerActor } from '../../../helpers/utils' | 12 | import { getServerActor } from '../../../helpers/utils' |
14 | import { | 13 | import { |
@@ -29,7 +28,7 @@ async function sendUpdateVideo (videoArg: MVideoAPWithoutCaption, t: Transaction | |||
29 | 28 | ||
30 | logger.info('Creating job to update video %s.', video.url) | 29 | logger.info('Creating job to update video %s.', video.url) |
31 | 30 | ||
32 | const byActor = overrodeByActor ? overrodeByActor : video.VideoChannel.Account.Actor | 31 | const byActor = overrodeByActor || video.VideoChannel.Account.Actor |
33 | 32 | ||
34 | const url = getUpdateActivityPubUrl(video.url, video.updatedAt.toISOString()) | 33 | const url = getUpdateActivityPubUrl(video.url, video.updatedAt.toISOString()) |
35 | 34 | ||
@@ -85,7 +84,7 @@ async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVide | |||
85 | return buildUpdateActivity(url, byActor, redundancyObject, audience) | 84 | return buildUpdateActivity(url, byActor, redundancyObject, audience) |
86 | } | 85 | } |
87 | 86 | ||
88 | return sendVideoRelatedActivity(activityBuilder, { byActor, video }) | 87 | return sendVideoRelatedActivity(activityBuilder, { byActor, video, contextType: 'CacheFile' }) |
89 | } | 88 | } |
90 | 89 | ||
91 | async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, t: Transaction) { | 90 | async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, t: Transaction) { |
diff --git a/server/lib/activitypub/send/send-view.ts b/server/lib/activitypub/send/send-view.ts index 8809417f9..1f864ea52 100644 --- a/server/lib/activitypub/send/send-view.ts +++ b/server/lib/activitypub/send/send-view.ts | |||
@@ -5,9 +5,9 @@ import { getVideoLikeActivityPubUrl } from '../url' | |||
5 | import { sendVideoRelatedActivity } from './utils' | 5 | import { sendVideoRelatedActivity } from './utils' |
6 | import { audiencify, getAudience } from '../audience' | 6 | import { audiencify, getAudience } from '../audience' |
7 | import { logger } from '../../../helpers/logger' | 7 | import { logger } from '../../../helpers/logger' |
8 | import { MActorAudience, MVideoAccountLight, MVideoUrl } from '@server/typings/models' | 8 | import { MActorAudience, MVideoImmutable, MVideoUrl } from '@server/typings/models' |
9 | 9 | ||
10 | async function sendView (byActor: ActorModel, video: MVideoAccountLight, t: Transaction) { | 10 | async function sendView (byActor: ActorModel, video: MVideoImmutable, 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) => { |
@@ -16,7 +16,7 @@ async function sendView (byActor: ActorModel, video: MVideoAccountLight, t: Tran | |||
16 | return buildViewActivity(url, byActor, video, audience) | 16 | return buildViewActivity(url, byActor, video, audience) |
17 | } | 17 | } |
18 | 18 | ||
19 | return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t }) | 19 | return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t, contextType: 'View' }) |
20 | } | 20 | } |
21 | 21 | ||
22 | function buildViewActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityView { | 22 | function buildViewActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityView { |
diff --git a/server/lib/activitypub/send/utils.ts b/server/lib/activitypub/send/utils.ts index 77b723479..b57bae8fd 100644 --- a/server/lib/activitypub/send/utils.ts +++ b/server/lib/activitypub/send/utils.ts | |||
@@ -7,24 +7,28 @@ import { JobQueue } from '../../job-queue' | |||
7 | import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getRemoteVideoAudience } from '../audience' | 7 | import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getRemoteVideoAudience } from '../audience' |
8 | import { getServerActor } from '../../../helpers/utils' | 8 | import { getServerActor } from '../../../helpers/utils' |
9 | import { afterCommitIfTransaction } from '../../../helpers/database-utils' | 9 | import { afterCommitIfTransaction } from '../../../helpers/database-utils' |
10 | import { MActorWithInboxes, MActor, MActorId, MActorLight, MVideo, MVideoAccountLight } from '../../../typings/models' | 10 | import { MActor, MActorId, MActorLight, MActorWithInboxes, MVideoAccountLight, MVideoId, MVideoImmutable } from '../../../typings/models' |
11 | import { ContextType } from '@server/helpers/activitypub' | ||
11 | 12 | ||
12 | async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: { | 13 | async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: { |
13 | byActor: MActorLight, | 14 | byActor: MActorLight |
14 | video: MVideoAccountLight, | 15 | video: MVideoImmutable | MVideoAccountLight |
15 | transaction?: Transaction | 16 | transaction?: Transaction |
17 | contextType?: ContextType | ||
16 | }) { | 18 | }) { |
17 | const { byActor, video, transaction } = options | 19 | const { byActor, video, transaction, contextType } = options |
18 | 20 | ||
19 | const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, transaction) | 21 | const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, transaction) |
20 | 22 | ||
21 | // Send to origin | 23 | // Send to origin |
22 | if (video.isOwned() === false) { | 24 | if (video.isOwned() === false) { |
23 | const audience = getRemoteVideoAudience(video, actorsInvolvedInVideo) | 25 | const accountActor = (video as MVideoAccountLight).VideoChannel?.Account?.Actor || await ActorModel.loadAccountActorByVideoId(video.id) |
26 | |||
27 | const audience = getRemoteVideoAudience(accountActor, actorsInvolvedInVideo) | ||
24 | const activity = activityBuilder(audience) | 28 | const activity = activityBuilder(audience) |
25 | 29 | ||
26 | return afterCommitIfTransaction(transaction, () => { | 30 | return afterCommitIfTransaction(transaction, () => { |
27 | return unicastTo(activity, byActor, video.VideoChannel.Account.Actor.getSharedInbox()) | 31 | return unicastTo(activity, byActor, accountActor.getSharedInbox(), contextType) |
28 | }) | 32 | }) |
29 | } | 33 | } |
30 | 34 | ||
@@ -34,14 +38,14 @@ async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAud | |||
34 | 38 | ||
35 | const actorsException = [ byActor ] | 39 | const actorsException = [ byActor ] |
36 | 40 | ||
37 | return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, transaction, actorsException) | 41 | return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, transaction, actorsException, contextType) |
38 | } | 42 | } |
39 | 43 | ||
40 | async function forwardVideoRelatedActivity ( | 44 | async function forwardVideoRelatedActivity ( |
41 | activity: Activity, | 45 | activity: Activity, |
42 | t: Transaction, | 46 | t: Transaction, |
43 | followersException: MActorWithInboxes[] = [], | 47 | followersException: MActorWithInboxes[] = [], |
44 | video: MVideo | 48 | video: MVideoId |
45 | ) { | 49 | ) { |
46 | // Mastodon does not add our announces in audience, so we forward to them manually | 50 | // Mastodon does not add our announces in audience, so we forward to them manually |
47 | const additionalActors = await getActorsInvolvedInVideo(video, t) | 51 | const additionalActors = await getActorsInvolvedInVideo(video, t) |
@@ -90,11 +94,12 @@ async function broadcastToFollowers ( | |||
90 | byActor: MActorId, | 94 | byActor: MActorId, |
91 | toFollowersOf: MActorId[], | 95 | toFollowersOf: MActorId[], |
92 | t: Transaction, | 96 | t: Transaction, |
93 | actorsException: MActorWithInboxes[] = [] | 97 | actorsException: MActorWithInboxes[] = [], |
98 | contextType?: ContextType | ||
94 | ) { | 99 | ) { |
95 | const uris = await computeFollowerUris(toFollowersOf, actorsException, t) | 100 | const uris = await computeFollowerUris(toFollowersOf, actorsException, t) |
96 | 101 | ||
97 | return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor)) | 102 | return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor, contextType)) |
98 | } | 103 | } |
99 | 104 | ||
100 | async function broadcastToActors ( | 105 | async function broadcastToActors ( |
@@ -102,13 +107,14 @@ async function broadcastToActors ( | |||
102 | byActor: MActorId, | 107 | byActor: MActorId, |
103 | toActors: MActor[], | 108 | toActors: MActor[], |
104 | t?: Transaction, | 109 | t?: Transaction, |
105 | actorsException: MActorWithInboxes[] = [] | 110 | actorsException: MActorWithInboxes[] = [], |
111 | contextType?: ContextType | ||
106 | ) { | 112 | ) { |
107 | const uris = await computeUris(toActors, actorsException) | 113 | const uris = await computeUris(toActors, actorsException) |
108 | return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor)) | 114 | return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor, contextType)) |
109 | } | 115 | } |
110 | 116 | ||
111 | function broadcastTo (uris: string[], data: any, byActor: MActorId) { | 117 | function broadcastTo (uris: string[], data: any, byActor: MActorId, contextType?: ContextType) { |
112 | if (uris.length === 0) return undefined | 118 | if (uris.length === 0) return undefined |
113 | 119 | ||
114 | logger.debug('Creating broadcast job.', { uris }) | 120 | logger.debug('Creating broadcast job.', { uris }) |
@@ -116,19 +122,21 @@ function broadcastTo (uris: string[], data: any, byActor: MActorId) { | |||
116 | const payload = { | 122 | const payload = { |
117 | uris, | 123 | uris, |
118 | signatureActorId: byActor.id, | 124 | signatureActorId: byActor.id, |
119 | body: data | 125 | body: data, |
126 | contextType | ||
120 | } | 127 | } |
121 | 128 | ||
122 | return JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload }) | 129 | return JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload }) |
123 | } | 130 | } |
124 | 131 | ||
125 | function unicastTo (data: any, byActor: MActorId, toActorUrl: string) { | 132 | function unicastTo (data: any, byActor: MActorId, toActorUrl: string, contextType?: ContextType) { |
126 | logger.debug('Creating unicast job.', { uri: toActorUrl }) | 133 | logger.debug('Creating unicast job.', { uri: toActorUrl }) |
127 | 134 | ||
128 | const payload = { | 135 | const payload = { |
129 | uri: toActorUrl, | 136 | uri: toActorUrl, |
130 | signatureActorId: byActor.id, | 137 | signatureActorId: byActor.id, |
131 | body: data | 138 | body: data, |
139 | contextType | ||
132 | } | 140 | } |
133 | 141 | ||
134 | JobQueue.Instance.createJob({ type: 'activitypub-http-unicast', payload }) | 142 | JobQueue.Instance.createJob({ type: 'activitypub-http-unicast', payload }) |
diff --git a/server/lib/activitypub/video-comments.ts b/server/lib/activitypub/video-comments.ts index d5c078a29..8642d2432 100644 --- a/server/lib/activitypub/video-comments.ts +++ b/server/lib/activitypub/video-comments.ts | |||
@@ -10,9 +10,9 @@ import { checkUrlsSameHost } from '../../helpers/activitypub' | |||
10 | import { MCommentOwner, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../typings/models/video' | 10 | import { MCommentOwner, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../typings/models/video' |
11 | 11 | ||
12 | type ResolveThreadParams = { | 12 | type ResolveThreadParams = { |
13 | url: string, | 13 | url: string |
14 | comments?: MCommentOwner[], | 14 | comments?: MCommentOwner[] |
15 | isVideo?: boolean, | 15 | isVideo?: boolean |
16 | commentCreated?: boolean | 16 | commentCreated?: boolean |
17 | } | 17 | } |
18 | type ResolveThreadResult = Promise<{ video: MVideoAccountLightBlacklistAllFiles, comment: MCommentOwnerVideo, commentCreated: boolean }> | 18 | type ResolveThreadResult = Promise<{ video: MVideoAccountLightBlacklistAllFiles, comment: MCommentOwnerVideo, commentCreated: boolean }> |
@@ -28,7 +28,7 @@ async function resolveThread (params: ResolveThreadParams): ResolveThreadResult | |||
28 | if (params.commentCreated === undefined) params.commentCreated = false | 28 | if (params.commentCreated === undefined) params.commentCreated = false |
29 | if (params.comments === undefined) params.comments = [] | 29 | if (params.comments === undefined) params.comments = [] |
30 | 30 | ||
31 | // Already have this comment? | 31 | // Already have this comment? |
32 | if (isVideo !== true) { | 32 | if (isVideo !== true) { |
33 | const result = await resolveCommentFromDB(params) | 33 | const result = await resolveCommentFromDB(params) |
34 | if (result) return result | 34 | if (result) return result |
@@ -87,7 +87,7 @@ async function tryResolveThreadFromVideo (params: ResolveThreadParams) { | |||
87 | 87 | ||
88 | let resultComment: MCommentOwnerVideo | 88 | let resultComment: MCommentOwnerVideo |
89 | if (comments.length !== 0) { | 89 | if (comments.length !== 0) { |
90 | const firstReply = comments[ comments.length - 1 ] as MCommentOwnerVideo | 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,9 +97,9 @@ 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 ] as MCommentOwnerVideo | 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 |
104 | comment.changed('updatedAt', true) | 104 | comment.changed('updatedAt', true) |
105 | comment.Video = video | 105 | comment.Video = video |
diff --git a/server/lib/activitypub/video-rates.ts b/server/lib/activitypub/video-rates.ts index 6bd46bb58..79ccfbc7e 100644 --- a/server/lib/activitypub/video-rates.ts +++ b/server/lib/activitypub/video-rates.ts | |||
@@ -58,8 +58,6 @@ async function createRates (ratesUrl: string[], video: MVideo, rate: VideoRateTy | |||
58 | const field = rate === 'like' ? 'likes' : 'dislikes' | 58 | const field = rate === 'like' ? 'likes' : 'dislikes' |
59 | await video.increment(field, { by: rateCounts }) | 59 | await video.increment(field, { by: rateCounts }) |
60 | } | 60 | } |
61 | |||
62 | return | ||
63 | } | 61 | } |
64 | 62 | ||
65 | async function sendVideoRateChange ( | 63 | async function sendVideoRateChange ( |
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts index ade93150f..d182ca5a2 100644 --- a/server/lib/activitypub/videos.ts +++ b/server/lib/activitypub/videos.ts | |||
@@ -6,7 +6,8 @@ import { | |||
6 | ActivityHashTagObject, | 6 | ActivityHashTagObject, |
7 | ActivityMagnetUrlObject, | 7 | ActivityMagnetUrlObject, |
8 | ActivityPlaylistSegmentHashesObject, | 8 | ActivityPlaylistSegmentHashesObject, |
9 | ActivityPlaylistUrlObject, ActivityTagObject, | 9 | ActivityPlaylistUrlObject, |
10 | ActivityTagObject, | ||
10 | ActivityUrlObject, | 11 | ActivityUrlObject, |
11 | ActivityVideoUrlObject, | 12 | ActivityVideoUrlObject, |
12 | VideoState | 13 | VideoState |
@@ -17,14 +18,14 @@ import { sanitizeAndCheckVideoTorrentObject } from '../../helpers/custom-validat | |||
17 | import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos' | 18 | import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos' |
18 | import { deleteNonExistingModels, resetSequelizeInstance, retryTransactionWrapper } from '../../helpers/database-utils' | 19 | import { deleteNonExistingModels, resetSequelizeInstance, retryTransactionWrapper } from '../../helpers/database-utils' |
19 | import { logger } from '../../helpers/logger' | 20 | import { logger } from '../../helpers/logger' |
20 | import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests' | 21 | import { doRequest } from '../../helpers/requests' |
21 | import { | 22 | import { |
22 | ACTIVITY_PUB, | 23 | ACTIVITY_PUB, |
23 | MIMETYPES, | 24 | MIMETYPES, |
24 | P2P_MEDIA_LOADER_PEER_VERSION, | 25 | P2P_MEDIA_LOADER_PEER_VERSION, |
25 | PREVIEWS_SIZE, | 26 | PREVIEWS_SIZE, |
26 | REMOTE_SCHEME, | 27 | REMOTE_SCHEME, |
27 | STATIC_PATHS | 28 | STATIC_PATHS, THUMBNAILS_SIZE |
28 | } from '../../initializers/constants' | 29 | } from '../../initializers/constants' |
29 | import { TagModel } from '../../models/video/tag' | 30 | import { TagModel } from '../../models/video/tag' |
30 | import { VideoModel } from '../../models/video/video' | 31 | import { VideoModel } from '../../models/video/video' |
@@ -40,7 +41,7 @@ import { ActivitypubHttpFetcherPayload } from '../job-queue/handlers/activitypub | |||
40 | import { createRates } from './video-rates' | 41 | import { createRates } from './video-rates' |
41 | import { addVideoShares, shareVideoByServerAndChannel } from './share' | 42 | import { addVideoShares, shareVideoByServerAndChannel } from './share' |
42 | import { fetchVideoByUrl, VideoFetchByUrlType } from '../../helpers/video' | 43 | import { fetchVideoByUrl, VideoFetchByUrlType } from '../../helpers/video' |
43 | import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' | 44 | import { buildRemoteVideoBaseUrl, checkUrlsSameHost, getAPId } from '../../helpers/activitypub' |
44 | import { Notifier } from '../notifier' | 45 | import { Notifier } from '../notifier' |
45 | import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist' | 46 | import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist' |
46 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' | 47 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' |
@@ -67,10 +68,11 @@ import { | |||
67 | MVideoAPWithoutCaption, | 68 | MVideoAPWithoutCaption, |
68 | MVideoFile, | 69 | MVideoFile, |
69 | MVideoFullLight, | 70 | MVideoFullLight, |
70 | MVideoId, | 71 | MVideoId, MVideoImmutable, |
71 | MVideoThumbnail | 72 | MVideoThumbnail |
72 | } from '../../typings/models' | 73 | } from '../../typings/models' |
73 | import { MThumbnail } from '../../typings/models/video/thumbnail' | 74 | import { MThumbnail } from '../../typings/models/video/thumbnail' |
75 | import { maxBy, minBy } from 'lodash' | ||
74 | 76 | ||
75 | async function federateVideoIfNeeded (videoArg: MVideoAPWithoutCaption, isNewVideo: boolean, transaction?: sequelize.Transaction) { | 77 | async function federateVideoIfNeeded (videoArg: MVideoAPWithoutCaption, isNewVideo: boolean, transaction?: sequelize.Transaction) { |
76 | const video = videoArg as MVideoAP | 78 | const video = videoArg as MVideoAP |
@@ -131,19 +133,6 @@ async function fetchRemoteVideoDescription (video: MVideoAccountLight) { | |||
131 | return body.description ? body.description : '' | 133 | return body.description ? body.description : '' |
132 | } | 134 | } |
133 | 135 | ||
134 | function fetchRemoteVideoStaticFile (video: MVideoAccountLight, path: string, destPath: string) { | ||
135 | const url = buildRemoteBaseUrl(video, path) | ||
136 | |||
137 | // We need to provide a callback, if no we could have an uncaught exception | ||
138 | return doRequestAndSaveToFile({ uri: url }, destPath) | ||
139 | } | ||
140 | |||
141 | function buildRemoteBaseUrl (video: MVideoAccountLight, path: string) { | ||
142 | const host = video.VideoChannel.Account.Actor.Server.host | ||
143 | |||
144 | return REMOTE_SCHEME.HTTP + '://' + host + path | ||
145 | } | ||
146 | |||
147 | function getOrCreateVideoChannelFromVideoObject (videoObject: VideoTorrentObject) { | 136 | function getOrCreateVideoChannelFromVideoObject (videoObject: VideoTorrentObject) { |
148 | const channel = videoObject.attributedTo.find(a => a.type === 'Group') | 137 | const channel = videoObject.attributedTo.find(a => a.type === 'Group') |
149 | if (!channel) throw new Error('Cannot find associated video channel to video ' + videoObject.url) | 138 | if (!channel) throw new Error('Cannot find associated video channel to video ' + videoObject.url) |
@@ -173,7 +162,7 @@ async function syncVideoExternalAttributes (video: MVideo, fetchedVideo: VideoTo | |||
173 | const cleaner = crawlStartDate => AccountVideoRateModel.cleanOldRatesOf(video.id, 'like' as 'like', crawlStartDate) | 162 | const cleaner = crawlStartDate => AccountVideoRateModel.cleanOldRatesOf(video.id, 'like' as 'like', crawlStartDate) |
174 | 163 | ||
175 | await crawlCollectionPage<string>(fetchedVideo.likes, handler, cleaner) | 164 | await crawlCollectionPage<string>(fetchedVideo.likes, handler, cleaner) |
176 | .catch(err => logger.error('Cannot add likes of video %s.', video.uuid, { err })) | 165 | .catch(err => logger.error('Cannot add likes of video %s.', video.uuid, { err, rootUrl: fetchedVideo.likes })) |
177 | } else { | 166 | } else { |
178 | jobPayloads.push({ uri: fetchedVideo.likes, videoId: video.id, type: 'video-likes' as 'video-likes' }) | 167 | jobPayloads.push({ uri: fetchedVideo.likes, videoId: video.id, type: 'video-likes' as 'video-likes' }) |
179 | } | 168 | } |
@@ -183,7 +172,7 @@ async function syncVideoExternalAttributes (video: MVideo, fetchedVideo: VideoTo | |||
183 | const cleaner = crawlStartDate => AccountVideoRateModel.cleanOldRatesOf(video.id, 'dislike' as 'dislike', crawlStartDate) | 172 | const cleaner = crawlStartDate => AccountVideoRateModel.cleanOldRatesOf(video.id, 'dislike' as 'dislike', crawlStartDate) |
184 | 173 | ||
185 | await crawlCollectionPage<string>(fetchedVideo.dislikes, handler, cleaner) | 174 | await crawlCollectionPage<string>(fetchedVideo.dislikes, handler, cleaner) |
186 | .catch(err => logger.error('Cannot add dislikes of video %s.', video.uuid, { err })) | 175 | .catch(err => logger.error('Cannot add dislikes of video %s.', video.uuid, { err, rootUrl: fetchedVideo.dislikes })) |
187 | } else { | 176 | } else { |
188 | jobPayloads.push({ uri: fetchedVideo.dislikes, videoId: video.id, type: 'video-dislikes' as 'video-dislikes' }) | 177 | jobPayloads.push({ uri: fetchedVideo.dislikes, videoId: video.id, type: 'video-dislikes' as 'video-dislikes' }) |
189 | } | 178 | } |
@@ -193,7 +182,7 @@ async function syncVideoExternalAttributes (video: MVideo, fetchedVideo: VideoTo | |||
193 | const cleaner = crawlStartDate => VideoShareModel.cleanOldSharesOf(video.id, crawlStartDate) | 182 | const cleaner = crawlStartDate => VideoShareModel.cleanOldSharesOf(video.id, crawlStartDate) |
194 | 183 | ||
195 | await crawlCollectionPage<string>(fetchedVideo.shares, handler, cleaner) | 184 | await crawlCollectionPage<string>(fetchedVideo.shares, handler, cleaner) |
196 | .catch(err => logger.error('Cannot add shares of video %s.', video.uuid, { err })) | 185 | .catch(err => logger.error('Cannot add shares of video %s.', video.uuid, { err, rootUrl: fetchedVideo.shares })) |
197 | } else { | 186 | } else { |
198 | jobPayloads.push({ uri: fetchedVideo.shares, videoId: video.id, type: 'video-shares' as 'video-shares' }) | 187 | jobPayloads.push({ uri: fetchedVideo.shares, videoId: video.id, type: 'video-shares' as 'video-shares' }) |
199 | } | 188 | } |
@@ -203,32 +192,49 @@ async function syncVideoExternalAttributes (video: MVideo, fetchedVideo: VideoTo | |||
203 | const cleaner = crawlStartDate => VideoCommentModel.cleanOldCommentsOf(video.id, crawlStartDate) | 192 | const cleaner = crawlStartDate => VideoCommentModel.cleanOldCommentsOf(video.id, crawlStartDate) |
204 | 193 | ||
205 | await crawlCollectionPage<string>(fetchedVideo.comments, handler, cleaner) | 194 | await crawlCollectionPage<string>(fetchedVideo.comments, handler, cleaner) |
206 | .catch(err => logger.error('Cannot add comments of video %s.', video.uuid, { err })) | 195 | .catch(err => logger.error('Cannot add comments of video %s.', video.uuid, { err, rootUrl: fetchedVideo.comments })) |
207 | } else { | 196 | } else { |
208 | jobPayloads.push({ uri: fetchedVideo.comments, videoId: video.id, type: 'video-comments' as 'video-comments' }) | 197 | jobPayloads.push({ uri: fetchedVideo.comments, videoId: video.id, type: 'video-comments' as 'video-comments' }) |
209 | } | 198 | } |
210 | 199 | ||
211 | await Bluebird.map(jobPayloads, payload => JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload })) | 200 | await Bluebird.map(jobPayloads, payload => JobQueue.Instance.createJobWithPromise({ type: 'activitypub-http-fetcher', payload })) |
212 | } | 201 | } |
213 | 202 | ||
214 | function getOrCreateVideoAndAccountAndChannel (options: { | 203 | type GetVideoResult <T> = Promise<{ |
215 | videoObject: { id: string } | string, | 204 | video: T |
216 | syncParam?: SyncParam, | 205 | created: boolean |
217 | fetchType?: 'all', | 206 | autoBlacklisted?: boolean |
207 | }> | ||
208 | |||
209 | type GetVideoParamAll = { | ||
210 | videoObject: { id: string } | string | ||
211 | syncParam?: SyncParam | ||
212 | fetchType?: 'all' | ||
218 | allowRefresh?: boolean | 213 | allowRefresh?: boolean |
219 | }): Promise<{ video: MVideoAccountLightBlacklistAllFiles, created: boolean, autoBlacklisted?: boolean }> | 214 | } |
220 | function getOrCreateVideoAndAccountAndChannel (options: { | 215 | |
221 | videoObject: { id: string } | string, | 216 | type GetVideoParamImmutable = { |
222 | syncParam?: SyncParam, | 217 | videoObject: { id: string } | string |
223 | fetchType?: VideoFetchByUrlType, | 218 | syncParam?: SyncParam |
219 | fetchType: 'only-immutable-attributes' | ||
220 | allowRefresh: false | ||
221 | } | ||
222 | |||
223 | type GetVideoParamOther = { | ||
224 | videoObject: { id: string } | string | ||
225 | syncParam?: SyncParam | ||
226 | fetchType?: 'all' | 'only-video' | ||
224 | allowRefresh?: boolean | 227 | allowRefresh?: boolean |
225 | }): Promise<{ video: MVideoAccountLightBlacklistAllFiles | MVideoThumbnail, created: boolean, autoBlacklisted?: boolean }> | 228 | } |
226 | async function getOrCreateVideoAndAccountAndChannel (options: { | 229 | |
227 | videoObject: { id: string } | string, | 230 | function getOrCreateVideoAndAccountAndChannel (options: GetVideoParamAll): GetVideoResult<MVideoAccountLightBlacklistAllFiles> |
228 | syncParam?: SyncParam, | 231 | function getOrCreateVideoAndAccountAndChannel (options: GetVideoParamImmutable): GetVideoResult<MVideoImmutable> |
229 | fetchType?: VideoFetchByUrlType, | 232 | function getOrCreateVideoAndAccountAndChannel ( |
230 | allowRefresh?: boolean // true by default | 233 | options: GetVideoParamOther |
231 | }): Promise<{ video: MVideoAccountLightBlacklistAllFiles | MVideoThumbnail, created: boolean, autoBlacklisted?: boolean }> { | 234 | ): GetVideoResult<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail> |
235 | async function getOrCreateVideoAndAccountAndChannel ( | ||
236 | options: GetVideoParamAll | GetVideoParamImmutable | GetVideoParamOther | ||
237 | ): GetVideoResult<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail | MVideoImmutable> { | ||
232 | // Default params | 238 | // Default params |
233 | const syncParam = options.syncParam || { likes: true, dislikes: true, shares: true, comments: true, thumbnail: true, refreshVideo: false } | 239 | const syncParam = options.syncParam || { likes: true, dislikes: true, shares: true, comments: true, thumbnail: true, refreshVideo: false } |
234 | const fetchType = options.fetchType || 'all' | 240 | const fetchType = options.fetchType || 'all' |
@@ -236,18 +242,25 @@ async function getOrCreateVideoAndAccountAndChannel (options: { | |||
236 | 242 | ||
237 | // Get video url | 243 | // Get video url |
238 | const videoUrl = getAPId(options.videoObject) | 244 | const videoUrl = getAPId(options.videoObject) |
239 | |||
240 | let videoFromDatabase = await fetchVideoByUrl(videoUrl, fetchType) | 245 | let videoFromDatabase = await fetchVideoByUrl(videoUrl, fetchType) |
246 | |||
241 | if (videoFromDatabase) { | 247 | if (videoFromDatabase) { |
242 | if (videoFromDatabase.isOutdated() && allowRefresh === true) { | 248 | // If allowRefresh is true, we could not call this function using 'only-immutable-attributes' fetch type |
249 | if (allowRefresh === true && (videoFromDatabase as MVideoThumbnail).isOutdated()) { | ||
243 | const refreshOptions = { | 250 | const refreshOptions = { |
244 | video: videoFromDatabase, | 251 | video: videoFromDatabase as MVideoThumbnail, |
245 | fetchedType: fetchType, | 252 | fetchedType: fetchType, |
246 | syncParam | 253 | syncParam |
247 | } | 254 | } |
248 | 255 | ||
249 | if (syncParam.refreshVideo === true) videoFromDatabase = await refreshVideoIfNeeded(refreshOptions) | 256 | if (syncParam.refreshVideo === true) { |
250 | else await JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video', url: videoFromDatabase.url } }) | 257 | videoFromDatabase = await refreshVideoIfNeeded(refreshOptions) |
258 | } else { | ||
259 | await JobQueue.Instance.createJobWithPromise({ | ||
260 | type: 'activitypub-refresher', | ||
261 | payload: { type: 'video', url: videoFromDatabase.url } | ||
262 | }) | ||
263 | } | ||
251 | } | 264 | } |
252 | 265 | ||
253 | return { video: videoFromDatabase, created: false } | 266 | return { video: videoFromDatabase, created: false } |
@@ -266,10 +279,10 @@ async function getOrCreateVideoAndAccountAndChannel (options: { | |||
266 | } | 279 | } |
267 | 280 | ||
268 | async function updateVideoFromAP (options: { | 281 | async function updateVideoFromAP (options: { |
269 | video: MVideoAccountLightBlacklistAllFiles, | 282 | video: MVideoAccountLightBlacklistAllFiles |
270 | videoObject: VideoTorrentObject, | 283 | videoObject: VideoTorrentObject |
271 | account: MAccountIdActor, | 284 | account: MAccountIdActor |
272 | channel: MChannelDefault, | 285 | channel: MChannelDefault |
273 | overrideTo?: string[] | 286 | overrideTo?: string[] |
274 | }) { | 287 | }) { |
275 | const { video, videoObject, account, channel, overrideTo } = options | 288 | const { video, videoObject, account, channel, overrideTo } = options |
@@ -284,7 +297,7 @@ async function updateVideoFromAP (options: { | |||
284 | let thumbnailModel: MThumbnail | 297 | let thumbnailModel: MThumbnail |
285 | 298 | ||
286 | try { | 299 | try { |
287 | thumbnailModel = await createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE) | 300 | thumbnailModel = await createVideoMiniatureFromUrl(getThumbnailFromIcons(videoObject).url, video, ThumbnailType.MINIATURE) |
288 | } catch (err) { | 301 | } catch (err) { |
289 | logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err }) | 302 | logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err }) |
290 | } | 303 | } |
@@ -300,7 +313,7 @@ async function updateVideoFromAP (options: { | |||
300 | throw new Error('Account ' + account.Actor.url + ' does not own video channel ' + videoChannel.Actor.url) | 313 | throw new Error('Account ' + account.Actor.url + ' does not own video channel ' + videoChannel.Actor.url) |
301 | } | 314 | } |
302 | 315 | ||
303 | const to = overrideTo ? overrideTo : videoObject.to | 316 | const to = overrideTo || videoObject.to |
304 | const videoData = await videoActivityObjectToDBAttributes(channel, videoObject, to) | 317 | const videoData = await videoActivityObjectToDBAttributes(channel, videoObject, to) |
305 | video.name = videoData.name | 318 | video.name = videoData.name |
306 | video.uuid = videoData.uuid | 319 | video.uuid = videoData.uuid |
@@ -327,10 +340,11 @@ async function updateVideoFromAP (options: { | |||
327 | 340 | ||
328 | if (thumbnailModel) await videoUpdated.addAndSaveThumbnail(thumbnailModel, t) | 341 | if (thumbnailModel) await videoUpdated.addAndSaveThumbnail(thumbnailModel, t) |
329 | 342 | ||
330 | // FIXME: use icon URL instead | 343 | if (videoUpdated.getPreview()) { |
331 | const previewUrl = buildRemoteBaseUrl(videoUpdated, join(STATIC_PATHS.PREVIEWS, videoUpdated.getPreview().filename)) | 344 | const previewUrl = videoUpdated.getPreview().getFileUrl(videoUpdated) |
332 | const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE) | 345 | const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE) |
333 | await videoUpdated.addAndSaveThumbnail(previewModel, t) | 346 | await videoUpdated.addAndSaveThumbnail(previewModel, t) |
347 | } | ||
334 | 348 | ||
335 | { | 349 | { |
336 | const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoUpdated, videoObject.url) | 350 | const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoUpdated, videoObject.url) |
@@ -391,7 +405,7 @@ async function updateVideoFromAP (options: { | |||
391 | await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(videoUpdated.id, t) | 405 | await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(videoUpdated.id, t) |
392 | 406 | ||
393 | const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { | 407 | const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { |
394 | return VideoCaptionModel.insertOrReplaceLanguage(videoUpdated.id, c.identifier, t) | 408 | return VideoCaptionModel.insertOrReplaceLanguage(videoUpdated.id, c.identifier, c.url, t) |
395 | }) | 409 | }) |
396 | await Promise.all(videoCaptionsPromises) | 410 | await Promise.all(videoCaptionsPromises) |
397 | } | 411 | } |
@@ -424,8 +438,8 @@ async function updateVideoFromAP (options: { | |||
424 | } | 438 | } |
425 | 439 | ||
426 | async function refreshVideoIfNeeded (options: { | 440 | async function refreshVideoIfNeeded (options: { |
427 | video: MVideoThumbnail, | 441 | video: MVideoThumbnail |
428 | fetchedType: VideoFetchByUrlType, | 442 | fetchedType: VideoFetchByUrlType |
429 | syncParam: SyncParam | 443 | syncParam: SyncParam |
430 | }): Promise<MVideoThumbnail> { | 444 | }): Promise<MVideoThumbnail> { |
431 | if (!options.video.isOutdated()) return options.video | 445 | if (!options.video.isOutdated()) return options.video |
@@ -483,7 +497,6 @@ export { | |||
483 | federateVideoIfNeeded, | 497 | federateVideoIfNeeded, |
484 | fetchRemoteVideo, | 498 | fetchRemoteVideo, |
485 | getOrCreateVideoAndAccountAndChannel, | 499 | getOrCreateVideoAndAccountAndChannel, |
486 | fetchRemoteVideoStaticFile, | ||
487 | fetchRemoteVideoDescription, | 500 | fetchRemoteVideoDescription, |
488 | getOrCreateVideoChannelFromVideoObject | 501 | getOrCreateVideoChannelFromVideoObject |
489 | } | 502 | } |
@@ -519,7 +532,11 @@ async function createVideo (videoObject: VideoTorrentObject, channel: MChannelAc | |||
519 | const videoData = await videoActivityObjectToDBAttributes(channel, videoObject, videoObject.to) | 532 | const videoData = await videoActivityObjectToDBAttributes(channel, videoObject, videoObject.to) |
520 | const video = VideoModel.build(videoData) as MVideoThumbnail | 533 | const video = VideoModel.build(videoData) as MVideoThumbnail |
521 | 534 | ||
522 | const promiseThumbnail = createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE) | 535 | const promiseThumbnail = createVideoMiniatureFromUrl(getThumbnailFromIcons(videoObject).url, video, ThumbnailType.MINIATURE) |
536 | .catch(err => { | ||
537 | logger.error('Cannot create miniature from url.', { err }) | ||
538 | return undefined | ||
539 | }) | ||
523 | 540 | ||
524 | let thumbnailModel: MThumbnail | 541 | let thumbnailModel: MThumbnail |
525 | if (waitThumbnail === true) { | 542 | if (waitThumbnail === true) { |
@@ -534,9 +551,12 @@ async function createVideo (videoObject: VideoTorrentObject, channel: MChannelAc | |||
534 | 551 | ||
535 | if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t) | 552 | if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t) |
536 | 553 | ||
537 | // FIXME: use icon URL instead | 554 | const previewIcon = getPreviewFromIcons(videoObject) |
538 | const previewUrl = buildRemoteBaseUrl(videoCreated, join(STATIC_PATHS.PREVIEWS, video.generatePreviewName())) | 555 | const previewUrl = previewIcon |
539 | const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE) | 556 | ? previewIcon.url |
557 | : buildRemoteVideoBaseUrl(videoCreated, join(STATIC_PATHS.PREVIEWS, video.generatePreviewName())) | ||
558 | const previewModel = createPlaceholderThumbnail(previewUrl, videoCreated, ThumbnailType.PREVIEW, PREVIEWS_SIZE) | ||
559 | |||
540 | if (thumbnailModel) await videoCreated.addAndSaveThumbnail(previewModel, t) | 560 | if (thumbnailModel) await videoCreated.addAndSaveThumbnail(previewModel, t) |
541 | 561 | ||
542 | // Process files | 562 | // Process files |
@@ -567,7 +587,7 @@ async function createVideo (videoObject: VideoTorrentObject, channel: MChannelAc | |||
567 | 587 | ||
568 | // Process captions | 588 | // Process captions |
569 | const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { | 589 | const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { |
570 | return VideoCaptionModel.insertOrReplaceLanguage(videoCreated.id, c.identifier, t) | 590 | return VideoCaptionModel.insertOrReplaceLanguage(videoCreated.id, c.identifier, c.url, t) |
571 | }) | 591 | }) |
572 | await Promise.all(videoCaptionsPromises) | 592 | await Promise.all(videoCaptionsPromises) |
573 | 593 | ||
@@ -588,7 +608,11 @@ async function createVideo (videoObject: VideoTorrentObject, channel: MChannelAc | |||
588 | }) | 608 | }) |
589 | 609 | ||
590 | if (waitThumbnail === false) { | 610 | if (waitThumbnail === false) { |
611 | // Error is already caught above | ||
612 | // eslint-disable-next-line @typescript-eslint/no-floating-promises | ||
591 | promiseThumbnail.then(thumbnailModel => { | 613 | promiseThumbnail.then(thumbnailModel => { |
614 | if (!thumbnailModel) return | ||
615 | |||
592 | thumbnailModel = videoCreated.id | 616 | thumbnailModel = videoCreated.id |
593 | 617 | ||
594 | return thumbnailModel.save() | 618 | return thumbnailModel.save() |
@@ -598,24 +622,19 @@ async function createVideo (videoObject: VideoTorrentObject, channel: MChannelAc | |||
598 | return { autoBlacklisted, videoCreated } | 622 | return { autoBlacklisted, videoCreated } |
599 | } | 623 | } |
600 | 624 | ||
601 | async function videoActivityObjectToDBAttributes (videoChannel: MChannelId, videoObject: VideoTorrentObject, to: string[] = []) { | 625 | function videoActivityObjectToDBAttributes (videoChannel: MChannelId, videoObject: VideoTorrentObject, to: string[] = []) { |
602 | const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPrivacy.PUBLIC : VideoPrivacy.UNLISTED | 626 | const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPrivacy.PUBLIC : VideoPrivacy.UNLISTED |
603 | const duration = videoObject.duration.replace(/[^\d]+/, '') | 627 | const duration = videoObject.duration.replace(/[^\d]+/, '') |
604 | 628 | ||
605 | let language: string | undefined | 629 | const language = videoObject.language?.identifier |
606 | if (videoObject.language) { | ||
607 | language = videoObject.language.identifier | ||
608 | } | ||
609 | 630 | ||
610 | let category: number | undefined | 631 | const category = videoObject.category |
611 | if (videoObject.category) { | 632 | ? parseInt(videoObject.category.identifier, 10) |
612 | category = parseInt(videoObject.category.identifier, 10) | 633 | : undefined |
613 | } | ||
614 | 634 | ||
615 | let licence: number | undefined | 635 | const licence = videoObject.licence |
616 | if (videoObject.licence) { | 636 | ? parseInt(videoObject.licence.identifier, 10) |
617 | licence = parseInt(videoObject.licence.identifier, 10) | 637 | : undefined |
618 | } | ||
619 | 638 | ||
620 | const description = videoObject.content || null | 639 | const description = videoObject.content || null |
621 | const support = videoObject.support || null | 640 | const support = videoObject.support || null |
@@ -638,8 +657,11 @@ async function videoActivityObjectToDBAttributes (videoChannel: MChannelId, vide | |||
638 | duration: parseInt(duration, 10), | 657 | duration: parseInt(duration, 10), |
639 | createdAt: new Date(videoObject.published), | 658 | createdAt: new Date(videoObject.published), |
640 | publishedAt: new Date(videoObject.published), | 659 | publishedAt: new Date(videoObject.published), |
641 | originallyPublishedAt: videoObject.originallyPublishedAt ? new Date(videoObject.originallyPublishedAt) : null, | 660 | |
642 | // FIXME: updatedAt does not seems to be considered by Sequelize | 661 | originallyPublishedAt: videoObject.originallyPublishedAt |
662 | ? new Date(videoObject.originallyPublishedAt) | ||
663 | : null, | ||
664 | |||
643 | updatedAt: new Date(videoObject.updated), | 665 | updatedAt: new Date(videoObject.updated), |
644 | views: videoObject.views, | 666 | views: videoObject.views, |
645 | likes: 0, | 667 | likes: 0, |
@@ -672,7 +694,7 @@ function videoFileActivityUrlToDBAttributes ( | |||
672 | 694 | ||
673 | const mediaType = fileUrl.mediaType | 695 | const mediaType = fileUrl.mediaType |
674 | const attribute = { | 696 | const attribute = { |
675 | extname: MIMETYPES.VIDEO.MIMETYPE_EXT[ mediaType ], | 697 | extname: MIMETYPES.VIDEO.MIMETYPE_EXT[mediaType], |
676 | infoHash: parsed.infoHash, | 698 | infoHash: parsed.infoHash, |
677 | resolution: fileUrl.height, | 699 | resolution: fileUrl.height, |
678 | size: fileUrl.size, | 700 | size: fileUrl.size, |
@@ -722,3 +744,19 @@ function streamingPlaylistActivityUrlToDBAttributes (video: MVideoId, videoObjec | |||
722 | 744 | ||
723 | return attributes | 745 | return attributes |
724 | } | 746 | } |
747 | |||
748 | function getThumbnailFromIcons (videoObject: VideoTorrentObject) { | ||
749 | let validIcons = videoObject.icon.filter(i => i.width > THUMBNAILS_SIZE.minWidth) | ||
750 | // Fallback if there are not valid icons | ||
751 | if (validIcons.length === 0) validIcons = videoObject.icon | ||
752 | |||
753 | return minBy(validIcons, 'width') | ||
754 | } | ||
755 | |||
756 | function getPreviewFromIcons (videoObject: VideoTorrentObject) { | ||
757 | const validIcons = videoObject.icon.filter(i => i.width > PREVIEWS_SIZE.minWidth) | ||
758 | |||
759 | // FIXME: don't put a fallback here for compatibility with PeerTube <2.2 | ||
760 | |||
761 | return maxBy(validIcons, 'width') | ||
762 | } | ||
diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts index 1d8a08ed0..572bd03bd 100644 --- a/server/lib/client-html.ts +++ b/server/lib/client-html.ts | |||
@@ -17,7 +17,7 @@ import { MAccountActor, MChannelActor, MVideo } from '../typings/models' | |||
17 | 17 | ||
18 | export class ClientHtml { | 18 | export class ClientHtml { |
19 | 19 | ||
20 | private static htmlCache: { [ path: string ]: string } = {} | 20 | private static htmlCache: { [path: string]: string } = {} |
21 | 21 | ||
22 | static invalidCache () { | 22 | static invalidCache () { |
23 | logger.info('Cleaning HTML cache.') | 23 | logger.info('Cleaning HTML cache.') |
@@ -94,7 +94,7 @@ export class ClientHtml { | |||
94 | 94 | ||
95 | private static async getIndexHTML (req: express.Request, res: express.Response, paramLang?: string) { | 95 | private static async getIndexHTML (req: express.Request, res: express.Response, paramLang?: string) { |
96 | const path = ClientHtml.getIndexPath(req, res, paramLang) | 96 | const path = ClientHtml.getIndexPath(req, res, paramLang) |
97 | if (ClientHtml.htmlCache[ path ]) return ClientHtml.htmlCache[ path ] | 97 | if (ClientHtml.htmlCache[path]) return ClientHtml.htmlCache[path] |
98 | 98 | ||
99 | const buffer = await readFile(path) | 99 | const buffer = await readFile(path) |
100 | 100 | ||
@@ -104,7 +104,7 @@ export class ClientHtml { | |||
104 | html = ClientHtml.addCustomCSS(html) | 104 | html = ClientHtml.addCustomCSS(html) |
105 | html = await ClientHtml.addAsyncPluginCSS(html) | 105 | html = await ClientHtml.addAsyncPluginCSS(html) |
106 | 106 | ||
107 | ClientHtml.htmlCache[ path ] = html | 107 | ClientHtml.htmlCache[path] = html |
108 | 108 | ||
109 | return html | 109 | return html |
110 | } | 110 | } |
@@ -214,21 +214,21 @@ export class ClientHtml { | |||
214 | const schemaTags = { | 214 | const schemaTags = { |
215 | '@context': 'http://schema.org', | 215 | '@context': 'http://schema.org', |
216 | '@type': 'VideoObject', | 216 | '@type': 'VideoObject', |
217 | name: videoNameEscaped, | 217 | 'name': videoNameEscaped, |
218 | description: videoDescriptionEscaped, | 218 | 'description': videoDescriptionEscaped, |
219 | thumbnailUrl: previewUrl, | 219 | 'thumbnailUrl': previewUrl, |
220 | uploadDate: video.createdAt.toISOString(), | 220 | 'uploadDate': video.createdAt.toISOString(), |
221 | duration: getActivityStreamDuration(video.duration), | 221 | 'duration': getActivityStreamDuration(video.duration), |
222 | contentUrl: videoUrl, | 222 | 'contentUrl': videoUrl, |
223 | embedUrl: embedUrl, | 223 | 'embedUrl': embedUrl, |
224 | interactionCount: video.views | 224 | 'interactionCount': video.views |
225 | } | 225 | } |
226 | 226 | ||
227 | let tagsString = '' | 227 | let tagsString = '' |
228 | 228 | ||
229 | // Opengraph | 229 | // Opengraph |
230 | Object.keys(openGraphMetaTags).forEach(tagName => { | 230 | Object.keys(openGraphMetaTags).forEach(tagName => { |
231 | const tagValue = openGraphMetaTags[ tagName ] | 231 | const tagValue = openGraphMetaTags[tagName] |
232 | 232 | ||
233 | tagsString += `<meta property="${tagName}" content="${tagValue}" />` | 233 | tagsString += `<meta property="${tagName}" content="${tagValue}" />` |
234 | }) | 234 | }) |
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts index 7484524a4..d0874ab20 100644 --- a/server/lib/emailer.ts +++ b/server/lib/emailer.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { createTransport, Transporter } from 'nodemailer' | 1 | import { createTransport, Transporter } from 'nodemailer' |
2 | import { isTestInstance } from '../helpers/core-utils' | 2 | import { isTestInstance } from '../helpers/core-utils' |
3 | import { bunyanLogger, logger } from '../helpers/logger' | 3 | import { bunyanLogger, logger } from '../helpers/logger' |
4 | import { CONFIG } from '../initializers/config' | 4 | import { CONFIG, isEmailEnabled } from '../initializers/config' |
5 | import { JobQueue } from './job-queue' | 5 | import { JobQueue } from './job-queue' |
6 | import { EmailPayload } from './job-queue/handlers/email' | 6 | import { EmailPayload } from './job-queue/handlers/email' |
7 | import { readFileSync } from 'fs-extra' | 7 | import { readFileSync } from 'fs-extra' |
@@ -32,14 +32,15 @@ class Emailer { | |||
32 | private initialized = false | 32 | private initialized = false |
33 | private transporter: Transporter | 33 | private transporter: Transporter |
34 | 34 | ||
35 | private constructor () {} | 35 | private constructor () { |
36 | } | ||
36 | 37 | ||
37 | init () { | 38 | init () { |
38 | // Already initialized | 39 | // Already initialized |
39 | if (this.initialized === true) return | 40 | if (this.initialized === true) return |
40 | this.initialized = true | 41 | this.initialized = true |
41 | 42 | ||
42 | if (Emailer.isEnabled()) { | 43 | if (isEmailEnabled) { |
43 | logger.info('Using %s:%s as SMTP server.', CONFIG.SMTP.HOSTNAME, CONFIG.SMTP.PORT) | 44 | logger.info('Using %s:%s as SMTP server.', CONFIG.SMTP.HOSTNAME, CONFIG.SMTP.PORT) |
44 | 45 | ||
45 | let tls | 46 | let tls |
@@ -97,12 +98,12 @@ class Emailer { | |||
97 | const channelName = video.VideoChannel.getDisplayName() | 98 | const channelName = video.VideoChannel.getDisplayName() |
98 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() | 99 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() |
99 | 100 | ||
100 | const text = `Hi dear user,\n\n` + | 101 | const text = 'Hi dear user,\n\n' + |
101 | `Your subscription ${channelName} just published a new video: ${video.name}` + | 102 | `Your subscription ${channelName} just published a new video: ${video.name}` + |
102 | `\n\n` + | 103 | '\n\n' + |
103 | `You can view it on ${videoUrl} ` + | 104 | `You can view it on ${videoUrl} ` + |
104 | `\n\n` + | 105 | '\n\n' + |
105 | `Cheers,\n` + | 106 | 'Cheers,\n' + |
106 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | 107 | `${CONFIG.EMAIL.BODY.SIGNATURE}` |
107 | 108 | ||
108 | const emailPayload: EmailPayload = { | 109 | const emailPayload: EmailPayload = { |
@@ -118,10 +119,10 @@ class Emailer { | |||
118 | const followerName = actorFollow.ActorFollower.Account.getDisplayName() | 119 | const followerName = actorFollow.ActorFollower.Account.getDisplayName() |
119 | const followingName = (actorFollow.ActorFollowing.VideoChannel || actorFollow.ActorFollowing.Account).getDisplayName() | 120 | const followingName = (actorFollow.ActorFollowing.VideoChannel || actorFollow.ActorFollowing.Account).getDisplayName() |
120 | 121 | ||
121 | const text = `Hi dear user,\n\n` + | 122 | const text = 'Hi dear user,\n\n' + |
122 | `Your ${followType} ${followingName} has a new subscriber: ${followerName}` + | 123 | `Your ${followType} ${followingName} has a new subscriber: ${followerName}` + |
123 | `\n\n` + | 124 | '\n\n' + |
124 | `Cheers,\n` + | 125 | 'Cheers,\n' + |
125 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | 126 | `${CONFIG.EMAIL.BODY.SIGNATURE}` |
126 | 127 | ||
127 | const emailPayload: EmailPayload = { | 128 | const emailPayload: EmailPayload = { |
@@ -136,10 +137,10 @@ class Emailer { | |||
136 | addNewInstanceFollowerNotification (to: string[], actorFollow: MActorFollowActors) { | 137 | addNewInstanceFollowerNotification (to: string[], actorFollow: MActorFollowActors) { |
137 | const awaitingApproval = actorFollow.state === 'pending' ? ' awaiting manual approval.' : '' | 138 | const awaitingApproval = actorFollow.state === 'pending' ? ' awaiting manual approval.' : '' |
138 | 139 | ||
139 | const text = `Hi dear admin,\n\n` + | 140 | const text = 'Hi dear admin,\n\n' + |
140 | `Your instance has a new follower: ${actorFollow.ActorFollower.url}${awaitingApproval}` + | 141 | `Your instance has a new follower: ${actorFollow.ActorFollower.url}${awaitingApproval}` + |
141 | `\n\n` + | 142 | '\n\n' + |
142 | `Cheers,\n` + | 143 | 'Cheers,\n' + |
143 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | 144 | `${CONFIG.EMAIL.BODY.SIGNATURE}` |
144 | 145 | ||
145 | const emailPayload: EmailPayload = { | 146 | const emailPayload: EmailPayload = { |
@@ -152,10 +153,10 @@ class Emailer { | |||
152 | } | 153 | } |
153 | 154 | ||
154 | addAutoInstanceFollowingNotification (to: string[], actorFollow: MActorFollowActors) { | 155 | addAutoInstanceFollowingNotification (to: string[], actorFollow: MActorFollowActors) { |
155 | const text = `Hi dear admin,\n\n` + | 156 | const text = 'Hi dear admin,\n\n' + |
156 | `Your instance automatically followed a new instance: ${actorFollow.ActorFollowing.url}` + | 157 | `Your instance automatically followed a new instance: ${actorFollow.ActorFollowing.url}` + |
157 | `\n\n` + | 158 | '\n\n' + |
158 | `Cheers,\n` + | 159 | 'Cheers,\n' + |
159 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | 160 | `${CONFIG.EMAIL.BODY.SIGNATURE}` |
160 | 161 | ||
161 | const emailPayload: EmailPayload = { | 162 | const emailPayload: EmailPayload = { |
@@ -170,12 +171,12 @@ class Emailer { | |||
170 | myVideoPublishedNotification (to: string[], video: MVideo) { | 171 | myVideoPublishedNotification (to: string[], video: MVideo) { |
171 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() | 172 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() |
172 | 173 | ||
173 | const text = `Hi dear user,\n\n` + | 174 | const text = 'Hi dear user,\n\n' + |
174 | `Your video ${video.name} has been published.` + | 175 | `Your video ${video.name} has been published.` + |
175 | `\n\n` + | 176 | '\n\n' + |
176 | `You can view it on ${videoUrl} ` + | 177 | `You can view it on ${videoUrl} ` + |
177 | `\n\n` + | 178 | '\n\n' + |
178 | `Cheers,\n` + | 179 | 'Cheers,\n' + |
179 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | 180 | `${CONFIG.EMAIL.BODY.SIGNATURE}` |
180 | 181 | ||
181 | const emailPayload: EmailPayload = { | 182 | const emailPayload: EmailPayload = { |
@@ -190,12 +191,12 @@ class Emailer { | |||
190 | myVideoImportSuccessNotification (to: string[], videoImport: MVideoImportVideo) { | 191 | myVideoImportSuccessNotification (to: string[], videoImport: MVideoImportVideo) { |
191 | const videoUrl = WEBSERVER.URL + videoImport.Video.getWatchStaticPath() | 192 | const videoUrl = WEBSERVER.URL + videoImport.Video.getWatchStaticPath() |
192 | 193 | ||
193 | const text = `Hi dear user,\n\n` + | 194 | const text = 'Hi dear user,\n\n' + |
194 | `Your video import ${videoImport.getTargetIdentifier()} is finished.` + | 195 | `Your video import ${videoImport.getTargetIdentifier()} is finished.` + |
195 | `\n\n` + | 196 | '\n\n' + |
196 | `You can view the imported video on ${videoUrl} ` + | 197 | `You can view the imported video on ${videoUrl} ` + |
197 | `\n\n` + | 198 | '\n\n' + |
198 | `Cheers,\n` + | 199 | 'Cheers,\n' + |
199 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | 200 | `${CONFIG.EMAIL.BODY.SIGNATURE}` |
200 | 201 | ||
201 | const emailPayload: EmailPayload = { | 202 | const emailPayload: EmailPayload = { |
@@ -210,12 +211,12 @@ class Emailer { | |||
210 | myVideoImportErrorNotification (to: string[], videoImport: MVideoImport) { | 211 | myVideoImportErrorNotification (to: string[], videoImport: MVideoImport) { |
211 | const importUrl = WEBSERVER.URL + '/my-account/video-imports' | 212 | const importUrl = WEBSERVER.URL + '/my-account/video-imports' |
212 | 213 | ||
213 | const text = `Hi dear user,\n\n` + | 214 | const text = 'Hi dear user,\n\n' + |
214 | `Your video import ${videoImport.getTargetIdentifier()} encountered an error.` + | 215 | `Your video import ${videoImport.getTargetIdentifier()} encountered an error.` + |
215 | `\n\n` + | 216 | '\n\n' + |
216 | `See your videos import dashboard for more information: ${importUrl}` + | 217 | `See your videos import dashboard for more information: ${importUrl}` + |
217 | `\n\n` + | 218 | '\n\n' + |
218 | `Cheers,\n` + | 219 | 'Cheers,\n' + |
219 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | 220 | `${CONFIG.EMAIL.BODY.SIGNATURE}` |
220 | 221 | ||
221 | const emailPayload: EmailPayload = { | 222 | const emailPayload: EmailPayload = { |
@@ -232,12 +233,12 @@ class Emailer { | |||
232 | const video = comment.Video | 233 | const video = comment.Video |
233 | const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() | 234 | const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() |
234 | 235 | ||
235 | const text = `Hi dear user,\n\n` + | 236 | const text = 'Hi dear user,\n\n' + |
236 | `A new comment has been posted by ${accountName} on your video ${video.name}` + | 237 | `A new comment has been posted by ${accountName} on your video ${video.name}` + |
237 | `\n\n` + | 238 | '\n\n' + |
238 | `You can view it on ${commentUrl} ` + | 239 | `You can view it on ${commentUrl} ` + |
239 | `\n\n` + | 240 | '\n\n' + |
240 | `Cheers,\n` + | 241 | 'Cheers,\n' + |
241 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | 242 | `${CONFIG.EMAIL.BODY.SIGNATURE}` |
242 | 243 | ||
243 | const emailPayload: EmailPayload = { | 244 | const emailPayload: EmailPayload = { |
@@ -254,12 +255,12 @@ class Emailer { | |||
254 | const video = comment.Video | 255 | const video = comment.Video |
255 | const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() | 256 | const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() |
256 | 257 | ||
257 | const text = `Hi dear user,\n\n` + | 258 | const text = 'Hi dear user,\n\n' + |
258 | `${accountName} mentioned you on video ${video.name}` + | 259 | `${accountName} mentioned you on video ${video.name}` + |
259 | `\n\n` + | 260 | '\n\n' + |
260 | `You can view the comment on ${commentUrl} ` + | 261 | `You can view the comment on ${commentUrl} ` + |
261 | `\n\n` + | 262 | '\n\n' + |
262 | `Cheers,\n` + | 263 | 'Cheers,\n' + |
263 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | 264 | `${CONFIG.EMAIL.BODY.SIGNATURE}` |
264 | 265 | ||
265 | const emailPayload: EmailPayload = { | 266 | const emailPayload: EmailPayload = { |
@@ -274,9 +275,9 @@ class Emailer { | |||
274 | addVideoAbuseModeratorsNotification (to: string[], videoAbuse: MVideoAbuseVideo) { | 275 | addVideoAbuseModeratorsNotification (to: string[], videoAbuse: MVideoAbuseVideo) { |
275 | const videoUrl = WEBSERVER.URL + videoAbuse.Video.getWatchStaticPath() | 276 | const videoUrl = WEBSERVER.URL + videoAbuse.Video.getWatchStaticPath() |
276 | 277 | ||
277 | const text = `Hi,\n\n` + | 278 | const text = 'Hi,\n\n' + |
278 | `${WEBSERVER.HOST} received an abuse for the following video ${videoUrl}\n\n` + | 279 | `${WEBSERVER.HOST} received an abuse for the following video ${videoUrl}\n\n` + |
279 | `Cheers,\n` + | 280 | 'Cheers,\n' + |
280 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | 281 | `${CONFIG.EMAIL.BODY.SIGNATURE}` |
281 | 282 | ||
282 | const emailPayload: EmailPayload = { | 283 | const emailPayload: EmailPayload = { |
@@ -292,14 +293,14 @@ class Emailer { | |||
292 | const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list' | 293 | const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list' |
293 | const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath() | 294 | const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath() |
294 | 295 | ||
295 | const text = `Hi,\n\n` + | 296 | const text = 'Hi,\n\n' + |
296 | `A recently added video was auto-blacklisted and requires moderator review before publishing.` + | 297 | 'A recently added video was auto-blacklisted and requires moderator review before publishing.' + |
297 | `\n\n` + | 298 | '\n\n' + |
298 | `You can view it and take appropriate action on ${videoUrl}` + | 299 | `You can view it and take appropriate action on ${videoUrl}` + |
299 | `\n\n` + | 300 | '\n\n' + |
300 | `A full list of auto-blacklisted videos can be reviewed here: ${VIDEO_AUTO_BLACKLIST_URL}` + | 301 | `A full list of auto-blacklisted videos can be reviewed here: ${VIDEO_AUTO_BLACKLIST_URL}` + |
301 | `\n\n` + | 302 | '\n\n' + |
302 | `Cheers,\n` + | 303 | 'Cheers,\n' + |
303 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | 304 | `${CONFIG.EMAIL.BODY.SIGNATURE}` |
304 | 305 | ||
305 | const emailPayload: EmailPayload = { | 306 | const emailPayload: EmailPayload = { |
@@ -312,9 +313,9 @@ class Emailer { | |||
312 | } | 313 | } |
313 | 314 | ||
314 | addNewUserRegistrationNotification (to: string[], user: MUser) { | 315 | addNewUserRegistrationNotification (to: string[], user: MUser) { |
315 | const text = `Hi,\n\n` + | 316 | const text = 'Hi,\n\n' + |
316 | `User ${user.username} just registered on ${WEBSERVER.HOST} PeerTube instance.\n\n` + | 317 | `User ${user.username} just registered on ${WEBSERVER.HOST} PeerTube instance.\n\n` + |
317 | `Cheers,\n` + | 318 | 'Cheers,\n' + |
318 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | 319 | `${CONFIG.EMAIL.BODY.SIGNATURE}` |
319 | 320 | ||
320 | const emailPayload: EmailPayload = { | 321 | const emailPayload: EmailPayload = { |
@@ -367,11 +368,11 @@ class Emailer { | |||
367 | } | 368 | } |
368 | 369 | ||
369 | addPasswordResetEmailJob (to: string, resetPasswordUrl: string) { | 370 | addPasswordResetEmailJob (to: string, resetPasswordUrl: string) { |
370 | const text = `Hi dear user,\n\n` + | 371 | const text = 'Hi dear user,\n\n' + |
371 | `A reset password procedure for your account ${to} has been requested on ${WEBSERVER.HOST} ` + | 372 | `A reset password procedure for your account ${to} has been requested on ${WEBSERVER.HOST} ` + |
372 | `Please follow this link to reset it: ${resetPasswordUrl} (the link will expire within 1 hour)\n\n` + | 373 | `Please follow this link to reset it: ${resetPasswordUrl} (the link will expire within 1 hour)\n\n` + |
373 | `If you are not the person who initiated this request, please ignore this email.\n\n` + | 374 | 'If you are not the person who initiated this request, please ignore this email.\n\n' + |
374 | `Cheers,\n` + | 375 | 'Cheers,\n' + |
375 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | 376 | `${CONFIG.EMAIL.BODY.SIGNATURE}` |
376 | 377 | ||
377 | const emailPayload: EmailPayload = { | 378 | const emailPayload: EmailPayload = { |
@@ -383,12 +384,28 @@ class Emailer { | |||
383 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 384 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
384 | } | 385 | } |
385 | 386 | ||
387 | addPasswordCreateEmailJob (username: string, to: string, resetPasswordUrl: string) { | ||
388 | const text = 'Hi,\n\n' + | ||
389 | `Welcome to your ${WEBSERVER.HOST} PeerTube instance. Your username is: ${username}.\n\n` + | ||
390 | `Please set your password by following this link: ${resetPasswordUrl} (this link will expire within seven days).\n\n` + | ||
391 | 'Cheers,\n' + | ||
392 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
393 | |||
394 | const emailPayload: EmailPayload = { | ||
395 | to: [ to ], | ||
396 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New PeerTube account password', | ||
397 | text | ||
398 | } | ||
399 | |||
400 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | ||
401 | } | ||
402 | |||
386 | addVerifyEmailJob (to: string, verifyEmailUrl: string) { | 403 | addVerifyEmailJob (to: string, verifyEmailUrl: string) { |
387 | const text = `Welcome to PeerTube,\n\n` + | 404 | const text = 'Welcome to PeerTube,\n\n' + |
388 | `To start using PeerTube on ${WEBSERVER.HOST} you must verify your email! ` + | 405 | `To start using PeerTube on ${WEBSERVER.HOST} you must verify your email! ` + |
389 | `Please follow this link to verify this email belongs to you: ${verifyEmailUrl}\n\n` + | 406 | `Please follow this link to verify this email belongs to you: ${verifyEmailUrl}\n\n` + |
390 | `If you are not the person who initiated this request, please ignore this email.\n\n` + | 407 | 'If you are not the person who initiated this request, please ignore this email.\n\n' + |
391 | `Cheers,\n` + | 408 | 'Cheers,\n' + |
392 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | 409 | `${CONFIG.EMAIL.BODY.SIGNATURE}` |
393 | 410 | ||
394 | const emailPayload: EmailPayload = { | 411 | const emailPayload: EmailPayload = { |
@@ -442,7 +459,7 @@ class Emailer { | |||
442 | } | 459 | } |
443 | 460 | ||
444 | async sendMail (options: EmailPayload) { | 461 | async sendMail (options: EmailPayload) { |
445 | if (!Emailer.isEnabled()) { | 462 | if (!isEmailEnabled()) { |
446 | throw new Error('Cannot send mail because SMTP is not configured.') | 463 | throw new Error('Cannot send mail because SMTP is not configured.') |
447 | } | 464 | } |
448 | 465 | ||
diff --git a/server/lib/files-cache/videos-caption-cache.ts b/server/lib/files-cache/videos-caption-cache.ts index 440c3fde8..26ab3bd0d 100644 --- a/server/lib/files-cache/videos-caption-cache.ts +++ b/server/lib/files-cache/videos-caption-cache.ts | |||
@@ -5,7 +5,7 @@ import { VideoCaptionModel } from '../../models/video/video-caption' | |||
5 | import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' | 5 | import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' |
6 | import { CONFIG } from '../../initializers/config' | 6 | import { CONFIG } from '../../initializers/config' |
7 | import { logger } from '../../helpers/logger' | 7 | import { logger } from '../../helpers/logger' |
8 | import { fetchRemoteVideoStaticFile } from '../activitypub' | 8 | import { doRequestAndSaveToFile } from '@server/helpers/requests' |
9 | 9 | ||
10 | type GetPathParam = { videoId: string, language: string } | 10 | type GetPathParam = { videoId: string, language: string } |
11 | 11 | ||
@@ -46,11 +46,10 @@ class VideosCaptionCache extends AbstractVideoStaticFileCache <GetPathParam> { | |||
46 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId) | 46 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId) |
47 | if (!video) return undefined | 47 | if (!video) return undefined |
48 | 48 | ||
49 | // FIXME: use URL | 49 | const remoteUrl = videoCaption.getFileUrl(video) |
50 | const remoteStaticPath = videoCaption.getCaptionStaticPath() | ||
51 | const destPath = join(FILES_CACHE.VIDEO_CAPTIONS.DIRECTORY, videoCaption.getCaptionName()) | 50 | const destPath = join(FILES_CACHE.VIDEO_CAPTIONS.DIRECTORY, videoCaption.getCaptionName()) |
52 | 51 | ||
53 | await fetchRemoteVideoStaticFile(video, remoteStaticPath, destPath) | 52 | await doRequestAndSaveToFile({ uri: remoteUrl }, destPath) |
54 | 53 | ||
55 | return { isOwned: false, path: destPath } | 54 | return { isOwned: false, path: destPath } |
56 | } | 55 | } |
diff --git a/server/lib/files-cache/videos-preview-cache.ts b/server/lib/files-cache/videos-preview-cache.ts index 3da6bb138..d0d4fc5b5 100644 --- a/server/lib/files-cache/videos-preview-cache.ts +++ b/server/lib/files-cache/videos-preview-cache.ts | |||
@@ -1,9 +1,8 @@ | |||
1 | import { join } from 'path' | 1 | import { join } from 'path' |
2 | import { FILES_CACHE, STATIC_PATHS } from '../../initializers/constants' | 2 | import { FILES_CACHE } from '../../initializers/constants' |
3 | import { VideoModel } from '../../models/video/video' | 3 | import { VideoModel } from '../../models/video/video' |
4 | import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' | 4 | import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' |
5 | import { CONFIG } from '../../initializers/config' | 5 | import { doRequestAndSaveToFile } from '@server/helpers/requests' |
6 | import { fetchRemoteVideoStaticFile } from '../activitypub' | ||
7 | 6 | ||
8 | class VideosPreviewCache extends AbstractVideoStaticFileCache <string> { | 7 | class VideosPreviewCache extends AbstractVideoStaticFileCache <string> { |
9 | 8 | ||
@@ -32,11 +31,11 @@ class VideosPreviewCache extends AbstractVideoStaticFileCache <string> { | |||
32 | 31 | ||
33 | if (video.isOwned()) throw new Error('Cannot load remote preview of owned video.') | 32 | if (video.isOwned()) throw new Error('Cannot load remote preview of owned video.') |
34 | 33 | ||
35 | // FIXME: use URL | 34 | const preview = video.getPreview() |
36 | const remoteStaticPath = join(STATIC_PATHS.PREVIEWS, video.getPreview().filename) | 35 | const destPath = join(FILES_CACHE.PREVIEWS.DIRECTORY, preview.filename) |
37 | const destPath = join(FILES_CACHE.PREVIEWS.DIRECTORY, video.getPreview().filename) | ||
38 | 36 | ||
39 | await fetchRemoteVideoStaticFile(video, remoteStaticPath, destPath) | 37 | const remoteUrl = preview.getFileUrl(video) |
38 | await doRequestAndSaveToFile({ uri: remoteUrl }, destPath) | ||
40 | 39 | ||
41 | return { isOwned: false, path: destPath } | 40 | return { isOwned: false, path: destPath } |
42 | } | 41 | } |
diff --git a/server/lib/job-queue/handlers/activitypub-http-broadcast.ts b/server/lib/job-queue/handlers/activitypub-http-broadcast.ts index 0ff7b44a0..7d9dd61e9 100644 --- a/server/lib/job-queue/handlers/activitypub-http-broadcast.ts +++ b/server/lib/job-queue/handlers/activitypub-http-broadcast.ts | |||
@@ -5,11 +5,13 @@ import { doRequest } from '../../../helpers/requests' | |||
5 | import { buildGlobalHeaders, buildSignedRequestOptions, computeBody } from './utils/activitypub-http-utils' | 5 | import { buildGlobalHeaders, buildSignedRequestOptions, computeBody } from './utils/activitypub-http-utils' |
6 | import { BROADCAST_CONCURRENCY, JOB_REQUEST_TIMEOUT } from '../../../initializers/constants' | 6 | import { BROADCAST_CONCURRENCY, JOB_REQUEST_TIMEOUT } from '../../../initializers/constants' |
7 | import { ActorFollowScoreCache } from '../../files-cache' | 7 | import { ActorFollowScoreCache } from '../../files-cache' |
8 | import { ContextType } from '@server/helpers/activitypub' | ||
8 | 9 | ||
9 | export type ActivitypubHttpBroadcastPayload = { | 10 | export type ActivitypubHttpBroadcastPayload = { |
10 | uris: string[] | 11 | uris: string[] |
11 | signatureActorId?: number | 12 | signatureActorId?: number |
12 | body: any | 13 | body: any |
14 | contextType?: ContextType | ||
13 | } | 15 | } |
14 | 16 | ||
15 | async function processActivityPubHttpBroadcast (job: Bull.Job) { | 17 | async function processActivityPubHttpBroadcast (job: Bull.Job) { |
diff --git a/server/lib/job-queue/handlers/activitypub-http-unicast.ts b/server/lib/job-queue/handlers/activitypub-http-unicast.ts index c70ce3be9..6b71e2891 100644 --- a/server/lib/job-queue/handlers/activitypub-http-unicast.ts +++ b/server/lib/job-queue/handlers/activitypub-http-unicast.ts | |||
@@ -4,11 +4,13 @@ import { doRequest } from '../../../helpers/requests' | |||
4 | import { buildGlobalHeaders, buildSignedRequestOptions, computeBody } from './utils/activitypub-http-utils' | 4 | import { buildGlobalHeaders, buildSignedRequestOptions, computeBody } from './utils/activitypub-http-utils' |
5 | import { JOB_REQUEST_TIMEOUT } from '../../../initializers/constants' | 5 | import { JOB_REQUEST_TIMEOUT } from '../../../initializers/constants' |
6 | import { ActorFollowScoreCache } from '../../files-cache' | 6 | import { ActorFollowScoreCache } from '../../files-cache' |
7 | import { ContextType } from '@server/helpers/activitypub' | ||
7 | 8 | ||
8 | export type ActivitypubHttpUnicastPayload = { | 9 | export type ActivitypubHttpUnicastPayload = { |
9 | uri: string | 10 | uri: string |
10 | signatureActorId?: number | 11 | signatureActorId?: number |
11 | body: any | 12 | body: any |
13 | contextType?: ContextType | ||
12 | } | 14 | } |
13 | 15 | ||
14 | async function processActivityPubHttpUnicast (job: Bull.Job) { | 16 | async function processActivityPubHttpUnicast (job: Bull.Job) { |
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 d3bde6e6a..54b35840d 100644 --- a/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts +++ b/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts | |||
@@ -1,11 +1,11 @@ | |||
1 | import { buildSignedActivity } from '../../../../helpers/activitypub' | 1 | import { buildSignedActivity, ContextType } from '../../../../helpers/activitypub' |
2 | import { getServerActor } from '../../../../helpers/utils' | 2 | import { getServerActor } from '../../../../helpers/utils' |
3 | import { ActorModel } from '../../../../models/activitypub/actor' | 3 | import { ActorModel } from '../../../../models/activitypub/actor' |
4 | import { sha256 } from '../../../../helpers/core-utils' | 4 | import { sha256 } from '../../../../helpers/core-utils' |
5 | import { HTTP_SIGNATURE } from '../../../../initializers/constants' | 5 | import { HTTP_SIGNATURE } from '../../../../initializers/constants' |
6 | import { MActor } from '../../../../typings/models' | 6 | import { MActor } from '../../../../typings/models' |
7 | 7 | ||
8 | type Payload = { body: any, signatureActorId?: number } | 8 | type Payload = { body: any, contextType?: ContextType, signatureActorId?: number } |
9 | 9 | ||
10 | async function computeBody (payload: Payload) { | 10 | async function computeBody (payload: Payload) { |
11 | let body = payload.body | 11 | let body = payload.body |
@@ -13,7 +13,7 @@ async function computeBody (payload: Payload) { | |||
13 | if (payload.signatureActorId) { | 13 | if (payload.signatureActorId) { |
14 | const actorSignature = await ActorModel.load(payload.signatureActorId) | 14 | const actorSignature = await ActorModel.load(payload.signatureActorId) |
15 | if (!actorSignature) throw new Error('Unknown signature actor id.') | 15 | if (!actorSignature) throw new Error('Unknown signature actor id.') |
16 | body = await buildSignedActivity(actorSignature, payload.body) | 16 | body = await buildSignedActivity(actorSignature, payload.body, payload.contextType) |
17 | } | 17 | } |
18 | 18 | ||
19 | return body | 19 | return body |
@@ -42,7 +42,7 @@ async function buildSignedRequestOptions (payload: Payload) { | |||
42 | 42 | ||
43 | function buildGlobalHeaders (body: any) { | 43 | function buildGlobalHeaders (body: any) { |
44 | return { | 44 | return { |
45 | 'Digest': buildDigest(body) | 45 | Digest: buildDigest(body) |
46 | } | 46 | } |
47 | } | 47 | } |
48 | 48 | ||
diff --git a/server/lib/job-queue/handlers/video-file-import.ts b/server/lib/job-queue/handlers/video-file-import.ts index 99c991e72..be9e7d181 100644 --- a/server/lib/job-queue/handlers/video-file-import.ts +++ b/server/lib/job-queue/handlers/video-file-import.ts | |||
@@ -11,7 +11,7 @@ import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' | |||
11 | import { getVideoFilePath } from '@server/lib/video-paths' | 11 | import { getVideoFilePath } from '@server/lib/video-paths' |
12 | 12 | ||
13 | export type VideoFileImportPayload = { | 13 | export type VideoFileImportPayload = { |
14 | videoUUID: string, | 14 | videoUUID: string |
15 | filePath: string | 15 | filePath: string |
16 | } | 16 | } |
17 | 17 | ||
diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts index 1fca17584..09f225cec 100644 --- a/server/lib/job-queue/handlers/video-import.ts +++ b/server/lib/job-queue/handlers/video-import.ts | |||
@@ -221,7 +221,7 @@ async function processFile (downloader: () => Promise<string>, videoImport: MVid | |||
221 | isNewVideo: true | 221 | isNewVideo: true |
222 | } | 222 | } |
223 | 223 | ||
224 | await JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput }) | 224 | await JobQueue.Instance.createJobWithPromise({ type: 'video-transcoding', payload: dataInput }) |
225 | } | 225 | } |
226 | 226 | ||
227 | } catch (err) { | 227 | } catch (err) { |
diff --git a/server/lib/job-queue/handlers/video-redundancy.ts b/server/lib/job-queue/handlers/video-redundancy.ts new file mode 100644 index 000000000..319d7090e --- /dev/null +++ b/server/lib/job-queue/handlers/video-redundancy.ts | |||
@@ -0,0 +1,20 @@ | |||
1 | import * as Bull from 'bull' | ||
2 | import { logger } from '../../../helpers/logger' | ||
3 | import { VideosRedundancyScheduler } from '@server/lib/schedulers/videos-redundancy-scheduler' | ||
4 | |||
5 | export type VideoRedundancyPayload = { | ||
6 | videoId: number | ||
7 | } | ||
8 | |||
9 | async function processVideoRedundancy (job: Bull.Job) { | ||
10 | const payload = job.data as VideoRedundancyPayload | ||
11 | logger.info('Processing video redundancy in job %d.', job.id) | ||
12 | |||
13 | return VideosRedundancyScheduler.Instance.createManualRedundancy(payload.videoId) | ||
14 | } | ||
15 | |||
16 | // --------------------------------------------------------------------------- | ||
17 | |||
18 | export { | ||
19 | processVideoRedundancy | ||
20 | } | ||
diff --git a/server/lib/job-queue/handlers/video-transcoding.ts b/server/lib/job-queue/handlers/video-transcoding.ts index 39b9fac98..c020057c9 100644 --- a/server/lib/job-queue/handlers/video-transcoding.ts +++ b/server/lib/job-queue/handlers/video-transcoding.ts | |||
@@ -6,7 +6,6 @@ import { JobQueue } from '../job-queue' | |||
6 | import { federateVideoIfNeeded } from '../../activitypub' | 6 | import { federateVideoIfNeeded } from '../../activitypub' |
7 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 7 | import { retryTransactionWrapper } from '../../../helpers/database-utils' |
8 | import { sequelizeTypescript } from '../../../initializers' | 8 | import { sequelizeTypescript } from '../../../initializers' |
9 | import * as Bluebird from 'bluebird' | ||
10 | import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils' | 9 | import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils' |
11 | import { generateHlsPlaylist, mergeAudioVideofile, optimizeOriginalVideofile, transcodeNewResolution } from '../../video-transcoding' | 10 | import { generateHlsPlaylist, mergeAudioVideofile, optimizeOriginalVideofile, transcodeNewResolution } from '../../video-transcoding' |
12 | import { Notifier } from '../../notifier' | 11 | import { Notifier } from '../../notifier' |
@@ -40,8 +39,11 @@ interface OptimizeTranscodingPayload extends BaseTranscodingPayload { | |||
40 | type: 'optimize' | 39 | type: 'optimize' |
41 | } | 40 | } |
42 | 41 | ||
43 | export type VideoTranscodingPayload = HLSTranscodingPayload | NewResolutionTranscodingPayload | 42 | export type VideoTranscodingPayload = |
44 | | OptimizeTranscodingPayload | MergeAudioTranscodingPayload | 43 | HLSTranscodingPayload |
44 | | NewResolutionTranscodingPayload | ||
45 | | OptimizeTranscodingPayload | ||
46 | | MergeAudioTranscodingPayload | ||
45 | 47 | ||
46 | async function processVideoTranscoding (job: Bull.Job) { | 48 | async function processVideoTranscoding (job: Bull.Job) { |
47 | const payload = job.data as VideoTranscodingPayload | 49 | const payload = job.data as VideoTranscodingPayload |
@@ -105,7 +107,7 @@ async function onVideoFileOptimizerSuccess (videoArg: MVideoWithFile, payload: O | |||
105 | 107 | ||
106 | const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => { | 108 | const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => { |
107 | // Maybe the video changed in database, refresh it | 109 | // Maybe the video changed in database, refresh it |
108 | let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoArg.uuid, t) | 110 | const videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoArg.uuid, t) |
109 | // Video does not exist anymore | 111 | // Video does not exist anymore |
110 | if (!videoDatabase) return undefined | 112 | if (!videoDatabase) return undefined |
111 | 113 | ||
@@ -122,8 +124,6 @@ async function onVideoFileOptimizerSuccess (videoArg: MVideoWithFile, payload: O | |||
122 | await createHlsJobIfEnabled(hlsPayload) | 124 | await createHlsJobIfEnabled(hlsPayload) |
123 | 125 | ||
124 | if (resolutionsEnabled.length !== 0) { | 126 | if (resolutionsEnabled.length !== 0) { |
125 | const tasks: (Bluebird<Bull.Job<any>> | Promise<Bull.Job<any>>)[] = [] | ||
126 | |||
127 | for (const resolution of resolutionsEnabled) { | 127 | for (const resolution of resolutionsEnabled) { |
128 | let dataInput: VideoTranscodingPayload | 128 | let dataInput: VideoTranscodingPayload |
129 | 129 | ||
@@ -143,12 +143,9 @@ async function onVideoFileOptimizerSuccess (videoArg: MVideoWithFile, payload: O | |||
143 | } | 143 | } |
144 | } | 144 | } |
145 | 145 | ||
146 | const p = JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput }) | 146 | JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput }) |
147 | tasks.push(p) | ||
148 | } | 147 | } |
149 | 148 | ||
150 | await Promise.all(tasks) | ||
151 | |||
152 | logger.info('Transcoding jobs created for uuid %s.', videoDatabase.uuid, { resolutionsEnabled }) | 149 | logger.info('Transcoding jobs created for uuid %s.', videoDatabase.uuid, { resolutionsEnabled }) |
153 | } else { | 150 | } else { |
154 | // No transcoding to do, it's now published | 151 | // No transcoding to do, it's now published |
diff --git a/server/lib/job-queue/handlers/video-views.ts b/server/lib/job-queue/handlers/video-views.ts index 73fa5ed04..2258cd029 100644 --- a/server/lib/job-queue/handlers/video-views.ts +++ b/server/lib/job-queue/handlers/video-views.ts | |||
@@ -23,6 +23,8 @@ async function processVideosViews () { | |||
23 | for (const videoId of videoIds) { | 23 | for (const videoId of videoIds) { |
24 | try { | 24 | try { |
25 | const views = await Redis.Instance.getVideoViews(videoId, hour) | 25 | const views = await Redis.Instance.getVideoViews(videoId, hour) |
26 | await Redis.Instance.deleteVideoViews(videoId, hour) | ||
27 | |||
26 | if (views) { | 28 | if (views) { |
27 | logger.debug('Adding %d views to video %d in hour %d.', views, videoId, hour) | 29 | logger.debug('Adding %d views to video %d in hour %d.', views, videoId, hour) |
28 | 30 | ||
@@ -52,8 +54,6 @@ async function processVideosViews () { | |||
52 | logger.error('Cannot create video views for video %d in hour %d.', videoId, hour, { err }) | 54 | logger.error('Cannot create video views for video %d in hour %d.', videoId, hour, { err }) |
53 | } | 55 | } |
54 | } | 56 | } |
55 | |||
56 | await Redis.Instance.deleteVideoViews(videoId, hour) | ||
57 | } catch (err) { | 57 | } catch (err) { |
58 | logger.error('Cannot update video views of video %d in hour %d.', videoId, hour, { err }) | 58 | logger.error('Cannot update video views of video %d in hour %d.', videoId, hour, { err }) |
59 | } | 59 | } |
diff --git a/server/lib/job-queue/job-queue.ts b/server/lib/job-queue/job-queue.ts index ec601e9ea..14acace7d 100644 --- a/server/lib/job-queue/job-queue.ts +++ b/server/lib/job-queue/job-queue.ts | |||
@@ -13,6 +13,7 @@ import { processVideoImport, VideoImportPayload } from './handlers/video-import' | |||
13 | import { processVideosViews } from './handlers/video-views' | 13 | import { processVideosViews } from './handlers/video-views' |
14 | import { refreshAPObject, RefreshPayload } from './handlers/activitypub-refresher' | 14 | import { refreshAPObject, RefreshPayload } from './handlers/activitypub-refresher' |
15 | import { processVideoFileImport, VideoFileImportPayload } from './handlers/video-file-import' | 15 | import { processVideoFileImport, VideoFileImportPayload } from './handlers/video-file-import' |
16 | import { processVideoRedundancy, VideoRedundancyPayload } from '@server/lib/job-queue/handlers/video-redundancy' | ||
16 | 17 | ||
17 | type CreateJobArgument = | 18 | type CreateJobArgument = |
18 | { type: 'activitypub-http-broadcast', payload: ActivitypubHttpBroadcastPayload } | | 19 | { type: 'activitypub-http-broadcast', payload: ActivitypubHttpBroadcastPayload } | |
@@ -24,20 +25,21 @@ type CreateJobArgument = | |||
24 | { type: 'email', payload: EmailPayload } | | 25 | { type: 'email', payload: EmailPayload } | |
25 | { type: 'video-import', payload: VideoImportPayload } | | 26 | { type: 'video-import', payload: VideoImportPayload } | |
26 | { type: 'activitypub-refresher', payload: RefreshPayload } | | 27 | { type: 'activitypub-refresher', payload: RefreshPayload } | |
27 | { type: 'videos-views', payload: {} } | 28 | { type: 'videos-views', payload: {} } | |
29 | { type: 'video-redundancy', payload: VideoRedundancyPayload } | ||
28 | 30 | ||
29 | const handlers: { [ id in (JobType | 'video-file') ]: (job: Bull.Job) => Promise<any>} = { | 31 | const handlers: { [id in JobType]: (job: Bull.Job) => Promise<any> } = { |
30 | 'activitypub-http-broadcast': processActivityPubHttpBroadcast, | 32 | 'activitypub-http-broadcast': processActivityPubHttpBroadcast, |
31 | 'activitypub-http-unicast': processActivityPubHttpUnicast, | 33 | 'activitypub-http-unicast': processActivityPubHttpUnicast, |
32 | 'activitypub-http-fetcher': processActivityPubHttpFetcher, | 34 | 'activitypub-http-fetcher': processActivityPubHttpFetcher, |
33 | 'activitypub-follow': processActivityPubFollow, | 35 | 'activitypub-follow': processActivityPubFollow, |
34 | 'video-file-import': processVideoFileImport, | 36 | 'video-file-import': processVideoFileImport, |
35 | 'video-transcoding': processVideoTranscoding, | 37 | 'video-transcoding': processVideoTranscoding, |
36 | 'video-file': processVideoTranscoding, // TODO: remove it (changed in 1.3) | ||
37 | 'email': processEmail, | 38 | 'email': processEmail, |
38 | 'video-import': processVideoImport, | 39 | 'video-import': processVideoImport, |
39 | 'videos-views': processVideosViews, | 40 | 'videos-views': processVideosViews, |
40 | 'activitypub-refresher': refreshAPObject | 41 | 'activitypub-refresher': refreshAPObject, |
42 | 'video-redundancy': processVideoRedundancy | ||
41 | } | 43 | } |
42 | 44 | ||
43 | const jobTypes: JobType[] = [ | 45 | const jobTypes: JobType[] = [ |
@@ -50,20 +52,22 @@ const jobTypes: JobType[] = [ | |||
50 | 'video-file-import', | 52 | 'video-file-import', |
51 | 'video-import', | 53 | 'video-import', |
52 | 'videos-views', | 54 | 'videos-views', |
53 | 'activitypub-refresher' | 55 | 'activitypub-refresher', |
56 | 'video-redundancy' | ||
54 | ] | 57 | ] |
55 | 58 | ||
56 | class JobQueue { | 59 | class JobQueue { |
57 | 60 | ||
58 | private static instance: JobQueue | 61 | private static instance: JobQueue |
59 | 62 | ||
60 | private queues: { [ id in JobType ]?: Bull.Queue } = {} | 63 | private queues: { [id in JobType]?: Bull.Queue } = {} |
61 | private initialized = false | 64 | private initialized = false |
62 | private jobRedisPrefix: string | 65 | private jobRedisPrefix: string |
63 | 66 | ||
64 | private constructor () {} | 67 | private constructor () { |
68 | } | ||
65 | 69 | ||
66 | async init () { | 70 | init () { |
67 | // Already initialized | 71 | // Already initialized |
68 | if (this.initialized === true) return | 72 | if (this.initialized === true) return |
69 | this.initialized = true | 73 | this.initialized = true |
@@ -105,11 +109,16 @@ class JobQueue { | |||
105 | } | 109 | } |
106 | } | 110 | } |
107 | 111 | ||
108 | createJob (obj: CreateJobArgument) { | 112 | createJob (obj: CreateJobArgument): void { |
113 | this.createJobWithPromise(obj) | ||
114 | .catch(err => logger.error('Cannot create job.', { err, obj })) | ||
115 | } | ||
116 | |||
117 | createJobWithPromise (obj: CreateJobArgument) { | ||
109 | const queue = this.queues[obj.type] | 118 | const queue = this.queues[obj.type] |
110 | if (queue === undefined) { | 119 | if (queue === undefined) { |
111 | logger.error('Unknown queue %s: cannot create job.', obj.type) | 120 | logger.error('Unknown queue %s: cannot create job.', obj.type) |
112 | throw Error('Unknown queue, cannot create job') | 121 | return |
113 | } | 122 | } |
114 | 123 | ||
115 | const jobArgs: Bull.JobOptions = { | 124 | const jobArgs: Bull.JobOptions = { |
@@ -122,10 +131,10 @@ class JobQueue { | |||
122 | } | 131 | } |
123 | 132 | ||
124 | async listForApi (options: { | 133 | async listForApi (options: { |
125 | state: JobState, | 134 | state: JobState |
126 | start: number, | 135 | start: number |
127 | count: number, | 136 | count: number |
128 | asc?: boolean, | 137 | asc?: boolean |
129 | jobType: JobType | 138 | jobType: JobType |
130 | }): Promise<Bull.Job[]> { | 139 | }): Promise<Bull.Job[]> { |
131 | const { state, start, count, asc, jobType } = options | 140 | const { state, start, count, asc, jobType } = options |
@@ -133,16 +142,14 @@ class JobQueue { | |||
133 | 142 | ||
134 | const filteredJobTypes = this.filterJobTypes(jobType) | 143 | const filteredJobTypes = this.filterJobTypes(jobType) |
135 | 144 | ||
136 | // TODO: optimize | ||
137 | for (const jobType of filteredJobTypes) { | 145 | for (const jobType of filteredJobTypes) { |
138 | const queue = this.queues[ jobType ] | 146 | const queue = this.queues[jobType] |
139 | if (queue === undefined) { | 147 | if (queue === undefined) { |
140 | logger.error('Unknown queue %s to list jobs.', jobType) | 148 | logger.error('Unknown queue %s to list jobs.', jobType) |
141 | continue | 149 | continue |
142 | } | 150 | } |
143 | 151 | ||
144 | // FIXME: Bull queue typings does not have getJobs method | 152 | const jobs = await queue.getJobs([ state ], 0, start + count, asc) |
145 | const jobs = await (queue as any).getJobs(state, 0, start + count, asc) | ||
146 | results = results.concat(jobs) | 153 | results = results.concat(jobs) |
147 | } | 154 | } |
148 | 155 | ||
@@ -164,7 +171,7 @@ class JobQueue { | |||
164 | const filteredJobTypes = this.filterJobTypes(jobType) | 171 | const filteredJobTypes = this.filterJobTypes(jobType) |
165 | 172 | ||
166 | for (const type of filteredJobTypes) { | 173 | for (const type of filteredJobTypes) { |
167 | const queue = this.queues[ type ] | 174 | const queue = this.queues[type] |
168 | if (queue === undefined) { | 175 | if (queue === undefined) { |
169 | logger.error('Unknown queue %s to count jobs.', type) | 176 | logger.error('Unknown queue %s to count jobs.', type) |
170 | continue | 177 | continue |
@@ -172,7 +179,7 @@ class JobQueue { | |||
172 | 179 | ||
173 | const counts = await queue.getJobCounts() | 180 | const counts = await queue.getJobCounts() |
174 | 181 | ||
175 | total += counts[ state ] | 182 | total += counts[state] |
176 | } | 183 | } |
177 | 184 | ||
178 | return total | 185 | return total |
@@ -188,7 +195,7 @@ class JobQueue { | |||
188 | private addRepeatableJobs () { | 195 | private addRepeatableJobs () { |
189 | this.queues['videos-views'].add({}, { | 196 | this.queues['videos-views'].add({}, { |
190 | repeat: REPEAT_JOBS['videos-views'] | 197 | repeat: REPEAT_JOBS['videos-views'] |
191 | }) | 198 | }).catch(err => logger.error('Cannot add repeatable job.', { err })) |
192 | } | 199 | } |
193 | 200 | ||
194 | private filterJobTypes (jobType?: JobType) { | 201 | private filterJobTypes (jobType?: JobType) { |
diff --git a/server/lib/moderation.ts b/server/lib/moderation.ts index b609f4585..55f7a985d 100644 --- a/server/lib/moderation.ts +++ b/server/lib/moderation.ts | |||
@@ -15,41 +15,41 @@ export type AcceptResult = { | |||
15 | 15 | ||
16 | // Can be filtered by plugins | 16 | // Can be filtered by plugins |
17 | function isLocalVideoAccepted (object: { | 17 | function isLocalVideoAccepted (object: { |
18 | videoBody: VideoCreate, | 18 | videoBody: VideoCreate |
19 | videoFile: Express.Multer.File & { duration?: number }, | 19 | videoFile: Express.Multer.File & { duration?: number } |
20 | user: UserModel | 20 | user: UserModel |
21 | }): AcceptResult { | 21 | }): AcceptResult { |
22 | return { accepted: true } | 22 | return { accepted: true } |
23 | } | 23 | } |
24 | 24 | ||
25 | function isLocalVideoThreadAccepted (_object: { | 25 | function isLocalVideoThreadAccepted (_object: { |
26 | commentBody: VideoCommentCreate, | 26 | commentBody: VideoCommentCreate |
27 | video: VideoModel, | 27 | video: VideoModel |
28 | user: UserModel | 28 | user: UserModel |
29 | }): AcceptResult { | 29 | }): AcceptResult { |
30 | return { accepted: true } | 30 | return { accepted: true } |
31 | } | 31 | } |
32 | 32 | ||
33 | function isLocalVideoCommentReplyAccepted (_object: { | 33 | function isLocalVideoCommentReplyAccepted (_object: { |
34 | commentBody: VideoCommentCreate, | 34 | commentBody: VideoCommentCreate |
35 | parentComment: VideoCommentModel, | 35 | parentComment: VideoCommentModel |
36 | video: VideoModel, | 36 | video: VideoModel |
37 | user: UserModel | 37 | user: UserModel |
38 | }): AcceptResult { | 38 | }): AcceptResult { |
39 | return { accepted: true } | 39 | return { accepted: true } |
40 | } | 40 | } |
41 | 41 | ||
42 | function isRemoteVideoAccepted (_object: { | 42 | function isRemoteVideoAccepted (_object: { |
43 | activity: ActivityCreate, | 43 | activity: ActivityCreate |
44 | videoAP: VideoTorrentObject, | 44 | videoAP: VideoTorrentObject |
45 | byActor: ActorModel | 45 | byActor: ActorModel |
46 | }): AcceptResult { | 46 | }): AcceptResult { |
47 | return { accepted: true } | 47 | return { accepted: true } |
48 | } | 48 | } |
49 | 49 | ||
50 | function isRemoteVideoCommentAccepted (_object: { | 50 | function isRemoteVideoCommentAccepted (_object: { |
51 | activity: ActivityCreate, | 51 | activity: ActivityCreate |
52 | commentAP: VideoCommentObject, | 52 | commentAP: VideoCommentObject |
53 | byActor: ActorModel | 53 | byActor: ActorModel |
54 | }): AcceptResult { | 54 | }): AcceptResult { |
55 | return { accepted: true } | 55 | return { accepted: true } |
diff --git a/server/lib/notifier.ts b/server/lib/notifier.ts index 679b9bcf6..63197eee1 100644 --- a/server/lib/notifier.ts +++ b/server/lib/notifier.ts | |||
@@ -6,7 +6,6 @@ import { UserModel } from '../models/account/user' | |||
6 | import { PeerTubeSocket } from './peertube-socket' | 6 | import { PeerTubeSocket } from './peertube-socket' |
7 | import { CONFIG } from '../initializers/config' | 7 | import { CONFIG } from '../initializers/config' |
8 | import { VideoPrivacy, VideoState } from '../../shared/models/videos' | 8 | import { VideoPrivacy, VideoState } from '../../shared/models/videos' |
9 | import * as Bluebird from 'bluebird' | ||
10 | import { AccountBlocklistModel } from '../models/account/account-blocklist' | 9 | import { AccountBlocklistModel } from '../models/account/account-blocklist' |
11 | import { | 10 | import { |
12 | MCommentOwnerVideo, | 11 | MCommentOwnerVideo, |
@@ -17,7 +16,8 @@ import { | |||
17 | MVideoFullLight | 16 | MVideoFullLight |
18 | } from '../typings/models/video' | 17 | } from '../typings/models/video' |
19 | import { | 18 | import { |
20 | MUser, MUserAccount, | 19 | MUser, |
20 | MUserAccount, | ||
21 | MUserDefault, | 21 | MUserDefault, |
22 | MUserNotifSettingAccount, | 22 | MUserNotifSettingAccount, |
23 | MUserWithNotificationSetting, | 23 | MUserWithNotificationSetting, |
@@ -32,14 +32,15 @@ class Notifier { | |||
32 | 32 | ||
33 | private static instance: Notifier | 33 | private static instance: Notifier |
34 | 34 | ||
35 | private constructor () {} | 35 | private constructor () { |
36 | } | ||
36 | 37 | ||
37 | notifyOnNewVideoIfNeeded (video: MVideoAccountLight): void { | 38 | notifyOnNewVideoIfNeeded (video: MVideoAccountLight): void { |
38 | // Only notify on public and published videos which are not blacklisted | 39 | // Only notify on public and published videos which are not blacklisted |
39 | if (video.privacy !== VideoPrivacy.PUBLIC || video.state !== VideoState.PUBLISHED || video.isBlacklisted()) return | 40 | if (video.privacy !== VideoPrivacy.PUBLIC || video.state !== VideoState.PUBLISHED || video.isBlacklisted()) return |
40 | 41 | ||
41 | this.notifySubscribersOfNewVideo(video) | 42 | this.notifySubscribersOfNewVideo(video) |
42 | .catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err })) | 43 | .catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err })) |
43 | } | 44 | } |
44 | 45 | ||
45 | notifyOnVideoPublishedAfterTranscoding (video: MVideoFullLight): void { | 46 | notifyOnVideoPublishedAfterTranscoding (video: MVideoFullLight): void { |
@@ -63,7 +64,9 @@ class Notifier { | |||
63 | if (video.ScheduleVideoUpdate || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return | 64 | if (video.ScheduleVideoUpdate || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return |
64 | 65 | ||
65 | this.notifyOwnedVideoHasBeenPublished(video) | 66 | this.notifyOwnedVideoHasBeenPublished(video) |
66 | .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 | 67 | .catch(err => { |
68 | logger.error('Cannot notify owner that its video %s has been published after removed from auto-blacklist.', video.url, { err }) | ||
69 | }) | ||
67 | } | 70 | } |
68 | 71 | ||
69 | notifyOnNewComment (comment: MCommentOwnerVideo): void { | 72 | notifyOnNewComment (comment: MCommentOwnerVideo): void { |
@@ -76,17 +79,17 @@ class Notifier { | |||
76 | 79 | ||
77 | notifyOnNewVideoAbuse (videoAbuse: MVideoAbuseVideo): void { | 80 | notifyOnNewVideoAbuse (videoAbuse: MVideoAbuseVideo): void { |
78 | this.notifyModeratorsOfNewVideoAbuse(videoAbuse) | 81 | this.notifyModeratorsOfNewVideoAbuse(videoAbuse) |
79 | .catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err })) | 82 | .catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err })) |
80 | } | 83 | } |
81 | 84 | ||
82 | notifyOnVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo): void { | 85 | notifyOnVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo): void { |
83 | this.notifyModeratorsOfVideoAutoBlacklist(videoBlacklist) | 86 | this.notifyModeratorsOfVideoAutoBlacklist(videoBlacklist) |
84 | .catch(err => logger.error('Cannot notify of auto-blacklist of video %s.', videoBlacklist.Video.url, { err })) | 87 | .catch(err => logger.error('Cannot notify of auto-blacklist of video %s.', videoBlacklist.Video.url, { err })) |
85 | } | 88 | } |
86 | 89 | ||
87 | notifyOnVideoBlacklist (videoBlacklist: MVideoBlacklistVideo): void { | 90 | notifyOnVideoBlacklist (videoBlacklist: MVideoBlacklistVideo): void { |
88 | this.notifyVideoOwnerOfBlacklist(videoBlacklist) | 91 | this.notifyVideoOwnerOfBlacklist(videoBlacklist) |
89 | .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err })) | 92 | .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err })) |
90 | } | 93 | } |
91 | 94 | ||
92 | notifyOnVideoUnblacklist (video: MVideoFullLight): void { | 95 | notifyOnVideoUnblacklist (video: MVideoFullLight): void { |
@@ -96,7 +99,7 @@ class Notifier { | |||
96 | 99 | ||
97 | notifyOnFinishedVideoImport (videoImport: MVideoImportVideo, success: boolean): void { | 100 | notifyOnFinishedVideoImport (videoImport: MVideoImportVideo, success: boolean): void { |
98 | this.notifyOwnerVideoImportIsFinished(videoImport, success) | 101 | this.notifyOwnerVideoImportIsFinished(videoImport, success) |
99 | .catch(err => logger.error('Cannot notify owner that its video import %s is finished.', videoImport.getTargetIdentifier(), { err })) | 102 | .catch(err => logger.error('Cannot notify owner that its video import %s is finished.', videoImport.getTargetIdentifier(), { err })) |
100 | } | 103 | } |
101 | 104 | ||
102 | notifyOnNewUserRegistration (user: MUserDefault): void { | 105 | notifyOnNewUserRegistration (user: MUserDefault): void { |
@@ -106,14 +109,14 @@ class Notifier { | |||
106 | 109 | ||
107 | notifyOfNewUserFollow (actorFollow: MActorFollowFull): void { | 110 | notifyOfNewUserFollow (actorFollow: MActorFollowFull): void { |
108 | this.notifyUserOfNewActorFollow(actorFollow) | 111 | this.notifyUserOfNewActorFollow(actorFollow) |
109 | .catch(err => { | 112 | .catch(err => { |
110 | logger.error( | 113 | logger.error( |
111 | 'Cannot notify owner of channel %s of a new follow by %s.', | 114 | 'Cannot notify owner of channel %s of a new follow by %s.', |
112 | actorFollow.ActorFollowing.VideoChannel.getDisplayName(), | 115 | actorFollow.ActorFollowing.VideoChannel.getDisplayName(), |
113 | actorFollow.ActorFollower.Account.getDisplayName(), | 116 | actorFollow.ActorFollower.Account.getDisplayName(), |
114 | { err } | 117 | { err } |
115 | ) | 118 | ) |
116 | }) | 119 | }) |
117 | } | 120 | } |
118 | 121 | ||
119 | notifyOfNewInstanceFollow (actorFollow: MActorFollowFull): void { | 122 | notifyOfNewInstanceFollow (actorFollow: MActorFollowFull): void { |
@@ -548,10 +551,10 @@ class Notifier { | |||
548 | return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) | 551 | return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) |
549 | } | 552 | } |
550 | 553 | ||
551 | private async notify <T extends MUserWithNotificationSetting> (options: { | 554 | private async notify<T extends MUserWithNotificationSetting> (options: { |
552 | users: T[], | 555 | users: T[] |
553 | notificationCreator: (user: T) => Promise<UserNotificationModelForApi>, | 556 | notificationCreator: (user: T) => Promise<UserNotificationModelForApi> |
554 | emailSender: (emails: string[]) => Promise<any> | Bluebird<any>, | 557 | emailSender: (emails: string[]) => void |
555 | settingGetter: (user: T) => UserNotificationSettingValue | 558 | settingGetter: (user: T) => UserNotificationSettingValue |
556 | }) { | 559 | }) { |
557 | const emails: string[] = [] | 560 | const emails: string[] = [] |
@@ -569,7 +572,7 @@ class Notifier { | |||
569 | } | 572 | } |
570 | 573 | ||
571 | if (emails.length !== 0) { | 574 | if (emails.length !== 0) { |
572 | await options.emailSender(emails) | 575 | options.emailSender(emails) |
573 | } | 576 | } |
574 | } | 577 | } |
575 | 578 | ||
diff --git a/server/lib/plugins/plugin-index.ts b/server/lib/plugins/plugin-index.ts index 25b4f3c61..dcdfba28c 100644 --- a/server/lib/plugins/plugin-index.ts +++ b/server/lib/plugins/plugin-index.ts | |||
@@ -31,7 +31,7 @@ async function listAvailablePluginsFromIndex (options: PeertubePluginIndexList) | |||
31 | 31 | ||
32 | logger.debug('Got result from PeerTube index.', { body }) | 32 | logger.debug('Got result from PeerTube index.', { body }) |
33 | 33 | ||
34 | await addInstanceInformation(body) | 34 | addInstanceInformation(body) |
35 | 35 | ||
36 | return body as ResultList<PeerTubePluginIndex> | 36 | return body as ResultList<PeerTubePluginIndex> |
37 | } catch (err) { | 37 | } catch (err) { |
@@ -40,7 +40,7 @@ async function listAvailablePluginsFromIndex (options: PeertubePluginIndexList) | |||
40 | } | 40 | } |
41 | } | 41 | } |
42 | 42 | ||
43 | async function addInstanceInformation (result: ResultList<PeerTubePluginIndex>) { | 43 | function addInstanceInformation (result: ResultList<PeerTubePluginIndex>) { |
44 | for (const d of result.data) { | 44 | for (const d of result.data) { |
45 | d.installed = PluginManager.Instance.isRegistered(d.npmName) | 45 | d.installed = PluginManager.Instance.isRegistered(d.npmName) |
46 | d.name = PluginModel.normalizePluginName(d.npmName) | 46 | d.name = PluginModel.normalizePluginName(d.npmName) |
diff --git a/server/lib/plugins/plugin-manager.ts b/server/lib/plugins/plugin-manager.ts index 7ebdabd34..73f7a71ce 100644 --- a/server/lib/plugins/plugin-manager.ts +++ b/server/lib/plugins/plugin-manager.ts | |||
@@ -55,30 +55,30 @@ export interface HookInformationValue { | |||
55 | } | 55 | } |
56 | 56 | ||
57 | type AlterableVideoConstant = 'language' | 'licence' | 'category' | 57 | type AlterableVideoConstant = 'language' | 'licence' | 'category' |
58 | type VideoConstant = { [ key in number | string ]: string } | 58 | type VideoConstant = { [key in number | string]: string } |
59 | type UpdatedVideoConstant = { | 59 | type UpdatedVideoConstant = { |
60 | [ name in AlterableVideoConstant ]: { | 60 | [name in AlterableVideoConstant]: { |
61 | [ npmName: string ]: { | 61 | [npmName: string]: { |
62 | added: { key: number | string, label: string }[], | 62 | added: { key: number | string, label: string }[] |
63 | deleted: { key: number | string, label: string }[] | 63 | deleted: { key: number | string, label: string }[] |
64 | } | 64 | } |
65 | } | 65 | } |
66 | } | 66 | } |
67 | 67 | ||
68 | type PluginLocalesTranslations = { | 68 | type PluginLocalesTranslations = { |
69 | [ locale: string ]: PluginTranslation | 69 | [locale: string]: PluginTranslation |
70 | } | 70 | } |
71 | 71 | ||
72 | export class PluginManager implements ServerHook { | 72 | export class PluginManager implements ServerHook { |
73 | 73 | ||
74 | private static instance: PluginManager | 74 | private static instance: PluginManager |
75 | 75 | ||
76 | private registeredPlugins: { [ name: string ]: RegisteredPlugin } = {} | 76 | private registeredPlugins: { [name: string]: RegisteredPlugin } = {} |
77 | private settings: { [ name: string ]: RegisterServerSettingOptions[] } = {} | 77 | private settings: { [name: string]: RegisterServerSettingOptions[] } = {} |
78 | private hooks: { [ name: string ]: HookInformationValue[] } = {} | 78 | private hooks: { [name: string]: HookInformationValue[] } = {} |
79 | private translations: PluginLocalesTranslations = {} | 79 | private translations: PluginLocalesTranslations = {} |
80 | 80 | ||
81 | private updatedVideoConstants: UpdatedVideoConstant = { | 81 | private readonly updatedVideoConstants: UpdatedVideoConstant = { |
82 | language: {}, | 82 | language: {}, |
83 | licence: {}, | 83 | licence: {}, |
84 | category: {} | 84 | category: {} |
@@ -133,7 +133,7 @@ export class PluginManager implements ServerHook { | |||
133 | 133 | ||
134 | // ###################### Hooks ###################### | 134 | // ###################### Hooks ###################### |
135 | 135 | ||
136 | async runHook <T> (hookName: ServerHookName, result?: T, params?: any): Promise<T> { | 136 | async runHook<T> (hookName: ServerHookName, result?: T, params?: any): Promise<T> { |
137 | if (!this.hooks[hookName]) return Promise.resolve(result) | 137 | if (!this.hooks[hookName]) return Promise.resolve(result) |
138 | 138 | ||
139 | const hookType = getHookType(hookName) | 139 | const hookType = getHookType(hookName) |
@@ -312,7 +312,7 @@ export class PluginManager implements ServerHook { | |||
312 | clientScripts[c.script] = c | 312 | clientScripts[c.script] = c |
313 | } | 313 | } |
314 | 314 | ||
315 | this.registeredPlugins[ npmName ] = { | 315 | this.registeredPlugins[npmName] = { |
316 | npmName, | 316 | npmName, |
317 | name: plugin.name, | 317 | name: plugin.name, |
318 | type: plugin.type, | 318 | type: plugin.type, |
@@ -438,7 +438,7 @@ export class PluginManager implements ServerHook { | |||
438 | const plugins: RegisteredPlugin[] = [] | 438 | const plugins: RegisteredPlugin[] = [] |
439 | 439 | ||
440 | for (const npmName of Object.keys(this.registeredPlugins)) { | 440 | for (const npmName of Object.keys(this.registeredPlugins)) { |
441 | const plugin = this.registeredPlugins[ npmName ] | 441 | const plugin = this.registeredPlugins[npmName] |
442 | if (plugin.type !== type) continue | 442 | if (plugin.type !== type) continue |
443 | 443 | ||
444 | plugins.push(plugin) | 444 | plugins.push(plugin) |
@@ -518,11 +518,11 @@ export class PluginManager implements ServerHook { | |||
518 | } | 518 | } |
519 | } | 519 | } |
520 | 520 | ||
521 | private addConstant <T extends string | number> (parameters: { | 521 | private addConstant<T extends string | number> (parameters: { |
522 | npmName: string, | 522 | npmName: string |
523 | type: AlterableVideoConstant, | 523 | type: AlterableVideoConstant |
524 | obj: VideoConstant, | 524 | obj: VideoConstant |
525 | key: T, | 525 | key: T |
526 | label: string | 526 | label: string |
527 | }) { | 527 | }) { |
528 | const { npmName, type, obj, key, label } = parameters | 528 | const { npmName, type, obj, key, label } = parameters |
@@ -545,10 +545,10 @@ export class PluginManager implements ServerHook { | |||
545 | return true | 545 | return true |
546 | } | 546 | } |
547 | 547 | ||
548 | private deleteConstant <T extends string | number> (parameters: { | 548 | private deleteConstant<T extends string | number> (parameters: { |
549 | npmName: string, | 549 | npmName: string |
550 | type: AlterableVideoConstant, | 550 | type: AlterableVideoConstant |
551 | obj: VideoConstant, | 551 | obj: VideoConstant |
552 | key: T | 552 | key: T |
553 | }) { | 553 | }) { |
554 | const { npmName, type, obj, key } = parameters | 554 | const { npmName, type, obj, key } = parameters |
@@ -604,7 +604,7 @@ export class PluginManager implements ServerHook { | |||
604 | const { result: packageJSONValid, badFields } = isPackageJSONValid(packageJSON, pluginType) | 604 | const { result: packageJSONValid, badFields } = isPackageJSONValid(packageJSON, pluginType) |
605 | if (!packageJSONValid) { | 605 | if (!packageJSONValid) { |
606 | const formattedFields = badFields.map(f => `"${f}"`) | 606 | const formattedFields = badFields.map(f => `"${f}"`) |
607 | .join(', ') | 607 | .join(', ') |
608 | 608 | ||
609 | throw new Error(`PackageJSON is invalid (invalid fields: ${formattedFields}).`) | 609 | throw new Error(`PackageJSON is invalid (invalid fields: ${formattedFields}).`) |
610 | } | 610 | } |
diff --git a/server/lib/redis.ts b/server/lib/redis.ts index f77d0b62c..b4cd6f8e7 100644 --- a/server/lib/redis.ts +++ b/server/lib/redis.ts | |||
@@ -6,13 +6,14 @@ import { | |||
6 | CONTACT_FORM_LIFETIME, | 6 | CONTACT_FORM_LIFETIME, |
7 | USER_EMAIL_VERIFY_LIFETIME, | 7 | USER_EMAIL_VERIFY_LIFETIME, |
8 | USER_PASSWORD_RESET_LIFETIME, | 8 | USER_PASSWORD_RESET_LIFETIME, |
9 | USER_PASSWORD_CREATE_LIFETIME, | ||
9 | VIDEO_VIEW_LIFETIME, | 10 | VIDEO_VIEW_LIFETIME, |
10 | WEBSERVER | 11 | WEBSERVER |
11 | } from '../initializers/constants' | 12 | } from '../initializers/constants' |
12 | import { CONFIG } from '../initializers/config' | 13 | import { CONFIG } from '../initializers/config' |
13 | 14 | ||
14 | type CachedRoute = { | 15 | type CachedRoute = { |
15 | body: string, | 16 | body: string |
16 | contentType?: string | 17 | contentType?: string |
17 | statusCode?: string | 18 | statusCode?: string |
18 | } | 19 | } |
@@ -24,7 +25,8 @@ class Redis { | |||
24 | private client: RedisClient | 25 | private client: RedisClient |
25 | private prefix: string | 26 | private prefix: string |
26 | 27 | ||
27 | private constructor () {} | 28 | private constructor () { |
29 | } | ||
28 | 30 | ||
29 | init () { | 31 | init () { |
30 | // Already initialized | 32 | // Already initialized |
@@ -49,9 +51,9 @@ class Redis { | |||
49 | return Object.assign({}, | 51 | return Object.assign({}, |
50 | (CONFIG.REDIS.AUTH && CONFIG.REDIS.AUTH != null) ? { password: CONFIG.REDIS.AUTH } : {}, | 52 | (CONFIG.REDIS.AUTH && CONFIG.REDIS.AUTH != null) ? { password: CONFIG.REDIS.AUTH } : {}, |
51 | (CONFIG.REDIS.DB) ? { db: CONFIG.REDIS.DB } : {}, | 53 | (CONFIG.REDIS.DB) ? { db: CONFIG.REDIS.DB } : {}, |
52 | (CONFIG.REDIS.HOSTNAME && CONFIG.REDIS.PORT) ? | 54 | (CONFIG.REDIS.HOSTNAME && CONFIG.REDIS.PORT) |
53 | { host: CONFIG.REDIS.HOSTNAME, port: CONFIG.REDIS.PORT } : | 55 | ? { host: CONFIG.REDIS.HOSTNAME, port: CONFIG.REDIS.PORT } |
54 | { path: CONFIG.REDIS.SOCKET } | 56 | : { path: CONFIG.REDIS.SOCKET } |
55 | ) | 57 | ) |
56 | } | 58 | } |
57 | 59 | ||
@@ -63,7 +65,7 @@ class Redis { | |||
63 | return this.prefix | 65 | return this.prefix |
64 | } | 66 | } |
65 | 67 | ||
66 | /************* Forgot password *************/ | 68 | /* ************ Forgot password ************ */ |
67 | 69 | ||
68 | async setResetPasswordVerificationString (userId: number) { | 70 | async setResetPasswordVerificationString (userId: number) { |
69 | const generatedString = await generateRandomString(32) | 71 | const generatedString = await generateRandomString(32) |
@@ -73,11 +75,19 @@ class Redis { | |||
73 | return generatedString | 75 | return generatedString |
74 | } | 76 | } |
75 | 77 | ||
78 | async setCreatePasswordVerificationString (userId: number) { | ||
79 | const generatedString = await generateRandomString(32) | ||
80 | |||
81 | await this.setValue(this.generateResetPasswordKey(userId), generatedString, USER_PASSWORD_CREATE_LIFETIME) | ||
82 | |||
83 | return generatedString | ||
84 | } | ||
85 | |||
76 | async getResetPasswordLink (userId: number) { | 86 | async getResetPasswordLink (userId: number) { |
77 | return this.getValue(this.generateResetPasswordKey(userId)) | 87 | return this.getValue(this.generateResetPasswordKey(userId)) |
78 | } | 88 | } |
79 | 89 | ||
80 | /************* Email verification *************/ | 90 | /* ************ Email verification ************ */ |
81 | 91 | ||
82 | async setVerifyEmailVerificationString (userId: number) { | 92 | async setVerifyEmailVerificationString (userId: number) { |
83 | const generatedString = await generateRandomString(32) | 93 | const generatedString = await generateRandomString(32) |
@@ -91,7 +101,7 @@ class Redis { | |||
91 | return this.getValue(this.generateVerifyEmailKey(userId)) | 101 | return this.getValue(this.generateVerifyEmailKey(userId)) |
92 | } | 102 | } |
93 | 103 | ||
94 | /************* Contact form per IP *************/ | 104 | /* ************ Contact form per IP ************ */ |
95 | 105 | ||
96 | async setContactFormIp (ip: string) { | 106 | async setContactFormIp (ip: string) { |
97 | return this.setValue(this.generateContactFormKey(ip), '1', CONTACT_FORM_LIFETIME) | 107 | return this.setValue(this.generateContactFormKey(ip), '1', CONTACT_FORM_LIFETIME) |
@@ -101,7 +111,7 @@ class Redis { | |||
101 | return this.exists(this.generateContactFormKey(ip)) | 111 | return this.exists(this.generateContactFormKey(ip)) |
102 | } | 112 | } |
103 | 113 | ||
104 | /************* Views per IP *************/ | 114 | /* ************ Views per IP ************ */ |
105 | 115 | ||
106 | setIPVideoView (ip: string, videoUUID: string) { | 116 | setIPVideoView (ip: string, videoUUID: string) { |
107 | return this.setValue(this.generateViewKey(ip, videoUUID), '1', VIDEO_VIEW_LIFETIME) | 117 | return this.setValue(this.generateViewKey(ip, videoUUID), '1', VIDEO_VIEW_LIFETIME) |
@@ -111,7 +121,7 @@ class Redis { | |||
111 | return this.exists(this.generateViewKey(ip, videoUUID)) | 121 | return this.exists(this.generateViewKey(ip, videoUUID)) |
112 | } | 122 | } |
113 | 123 | ||
114 | /************* API cache *************/ | 124 | /* ************ API cache ************ */ |
115 | 125 | ||
116 | async getCachedRoute (req: express.Request) { | 126 | async getCachedRoute (req: express.Request) { |
117 | const cached = await this.getObject(this.generateCachedRouteKey(req)) | 127 | const cached = await this.getObject(this.generateCachedRouteKey(req)) |
@@ -120,17 +130,17 @@ class Redis { | |||
120 | } | 130 | } |
121 | 131 | ||
122 | setCachedRoute (req: express.Request, body: any, lifetime: number, contentType?: string, statusCode?: number) { | 132 | setCachedRoute (req: express.Request, body: any, lifetime: number, contentType?: string, statusCode?: number) { |
123 | const cached: CachedRoute = Object.assign({}, { | 133 | const cached: CachedRoute = Object.assign( |
124 | body: body.toString() | 134 | {}, |
125 | }, | 135 | { body: body.toString() }, |
126 | (contentType) ? { contentType } : null, | 136 | (contentType) ? { contentType } : null, |
127 | (statusCode) ? { statusCode: statusCode.toString() } : null | 137 | (statusCode) ? { statusCode: statusCode.toString() } : null |
128 | ) | 138 | ) |
129 | 139 | ||
130 | return this.setObject(this.generateCachedRouteKey(req), cached, lifetime) | 140 | return this.setObject(this.generateCachedRouteKey(req), cached, lifetime) |
131 | } | 141 | } |
132 | 142 | ||
133 | /************* Video views *************/ | 143 | /* ************ Video views ************ */ |
134 | 144 | ||
135 | addVideoView (videoId: number) { | 145 | addVideoView (videoId: number) { |
136 | const keyIncr = this.generateVideoViewKey(videoId) | 146 | const keyIncr = this.generateVideoViewKey(videoId) |
@@ -173,7 +183,7 @@ class Redis { | |||
173 | ]) | 183 | ]) |
174 | } | 184 | } |
175 | 185 | ||
176 | /************* Keys generation *************/ | 186 | /* ************ Keys generation ************ */ |
177 | 187 | ||
178 | generateCachedRouteKey (req: express.Request) { | 188 | generateCachedRouteKey (req: express.Request) { |
179 | return req.method + '-' + req.originalUrl | 189 | return req.method + '-' + req.originalUrl |
@@ -207,7 +217,7 @@ class Redis { | |||
207 | return 'contact-form-' + ip | 217 | return 'contact-form-' + ip |
208 | } | 218 | } |
209 | 219 | ||
210 | /************* Redis helpers *************/ | 220 | /* ************ Redis helpers ************ */ |
211 | 221 | ||
212 | private getValue (key: string) { | 222 | private getValue (key: string) { |
213 | return new Promise<string>((res, rej) => { | 223 | return new Promise<string>((res, rej) => { |
@@ -265,7 +275,7 @@ class Redis { | |||
265 | }) | 275 | }) |
266 | } | 276 | } |
267 | 277 | ||
268 | private setObject (key: string, obj: { [ id: string ]: string }, expirationMilliseconds: number) { | 278 | private setObject (key: string, obj: { [id: string]: string }, expirationMilliseconds: number) { |
269 | return new Promise<void>((res, rej) => { | 279 | return new Promise<void>((res, rej) => { |
270 | this.client.hmset(this.prefix + key, obj, (err, ok) => { | 280 | this.client.hmset(this.prefix + key, obj, (err, ok) => { |
271 | if (err) return rej(err) | 281 | if (err) return rej(err) |
@@ -282,7 +292,7 @@ class Redis { | |||
282 | } | 292 | } |
283 | 293 | ||
284 | private getObject (key: string) { | 294 | private getObject (key: string) { |
285 | return new Promise<{ [ id: string ]: string }>((res, rej) => { | 295 | return new Promise<{ [id: string]: string }>((res, rej) => { |
286 | this.client.hgetall(this.prefix + key, (err, value) => { | 296 | this.client.hgetall(this.prefix + key, (err, value) => { |
287 | if (err) return rej(err) | 297 | if (err) return rej(err) |
288 | 298 | ||
diff --git a/server/lib/redundancy.ts b/server/lib/redundancy.ts index 1b4ecd7c0..78d84e02e 100644 --- a/server/lib/redundancy.ts +++ b/server/lib/redundancy.ts | |||
@@ -13,10 +13,10 @@ async function removeVideoRedundancy (videoRedundancy: MVideoRedundancyVideo, t? | |||
13 | await videoRedundancy.destroy({ transaction: t }) | 13 | await videoRedundancy.destroy({ transaction: t }) |
14 | } | 14 | } |
15 | 15 | ||
16 | async function removeRedundancyOf (serverId: number) { | 16 | async function removeRedundanciesOfServer (serverId: number) { |
17 | const videosRedundancy = await VideoRedundancyModel.listLocalOfServer(serverId) | 17 | const redundancies = await VideoRedundancyModel.listLocalOfServer(serverId) |
18 | 18 | ||
19 | for (const redundancy of videosRedundancy) { | 19 | for (const redundancy of redundancies) { |
20 | await removeVideoRedundancy(redundancy) | 20 | await removeVideoRedundancy(redundancy) |
21 | } | 21 | } |
22 | } | 22 | } |
@@ -24,6 +24,6 @@ async function removeRedundancyOf (serverId: number) { | |||
24 | // --------------------------------------------------------------------------- | 24 | // --------------------------------------------------------------------------- |
25 | 25 | ||
26 | export { | 26 | export { |
27 | removeRedundancyOf, | 27 | removeRedundanciesOfServer, |
28 | removeVideoRedundancy | 28 | removeVideoRedundancy |
29 | } | 29 | } |
diff --git a/server/lib/schedulers/auto-follow-index-instances.ts b/server/lib/schedulers/auto-follow-index-instances.ts index dd326bc1e..d700a99f0 100644 --- a/server/lib/schedulers/auto-follow-index-instances.ts +++ b/server/lib/schedulers/auto-follow-index-instances.ts | |||
@@ -57,8 +57,7 @@ export class AutoFollowIndexInstances extends AbstractScheduler { | |||
57 | isAutoFollow: true | 57 | isAutoFollow: true |
58 | } | 58 | } |
59 | 59 | ||
60 | await JobQueue.Instance.createJob({ type: 'activitypub-follow', payload }) | 60 | JobQueue.Instance.createJob({ type: 'activitypub-follow', payload }) |
61 | .catch(err => logger.error('Cannot create follow job for %s.', unfollowedHost, err)) | ||
62 | } | 61 | } |
63 | } | 62 | } |
64 | 63 | ||
diff --git a/server/lib/schedulers/plugins-check-scheduler.ts b/server/lib/schedulers/plugins-check-scheduler.ts index 7ff41e639..014993e94 100644 --- a/server/lib/schedulers/plugins-check-scheduler.ts +++ b/server/lib/schedulers/plugins-check-scheduler.ts | |||
@@ -43,7 +43,7 @@ export class PluginsCheckScheduler extends AbstractScheduler { | |||
43 | const results = await getLatestPluginsVersion(npmNames) | 43 | const results = await getLatestPluginsVersion(npmNames) |
44 | 44 | ||
45 | for (const result of results) { | 45 | for (const result of results) { |
46 | const plugin = pluginIndex[ result.npmName ] | 46 | const plugin = pluginIndex[result.npmName] |
47 | if (!result.latestVersion) continue | 47 | if (!result.latestVersion) continue |
48 | 48 | ||
49 | if ( | 49 | if ( |
diff --git a/server/lib/schedulers/remove-old-views-scheduler.ts b/server/lib/schedulers/remove-old-views-scheduler.ts index 39fbb9163..5ae87fe50 100644 --- a/server/lib/schedulers/remove-old-views-scheduler.ts +++ b/server/lib/schedulers/remove-old-views-scheduler.ts | |||
@@ -1,9 +1,7 @@ | |||
1 | import { logger } from '../../helpers/logger' | 1 | import { logger } from '../../helpers/logger' |
2 | import { AbstractScheduler } from './abstract-scheduler' | 2 | import { AbstractScheduler } from './abstract-scheduler' |
3 | import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants' | 3 | import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants' |
4 | import { UserVideoHistoryModel } from '../../models/account/user-video-history' | ||
5 | import { CONFIG } from '../../initializers/config' | 4 | import { CONFIG } from '../../initializers/config' |
6 | import { isTestInstance } from '../../helpers/core-utils' | ||
7 | import { VideoViewModel } from '../../models/video/video-views' | 5 | import { VideoViewModel } from '../../models/video/video-views' |
8 | 6 | ||
9 | export class RemoveOldViewsScheduler extends AbstractScheduler { | 7 | export class RemoveOldViewsScheduler extends AbstractScheduler { |
diff --git a/server/lib/schedulers/update-videos-scheduler.ts b/server/lib/schedulers/update-videos-scheduler.ts index 350a335d3..956780a77 100644 --- a/server/lib/schedulers/update-videos-scheduler.ts +++ b/server/lib/schedulers/update-videos-scheduler.ts | |||
@@ -4,7 +4,6 @@ import { ScheduleVideoUpdateModel } from '../../models/video/schedule-video-upda | |||
4 | import { retryTransactionWrapper } from '../../helpers/database-utils' | 4 | import { retryTransactionWrapper } from '../../helpers/database-utils' |
5 | import { federateVideoIfNeeded } from '../activitypub' | 5 | import { federateVideoIfNeeded } from '../activitypub' |
6 | import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants' | 6 | import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants' |
7 | import { VideoPrivacy } from '../../../shared/models/videos' | ||
8 | import { Notifier } from '../notifier' | 7 | import { Notifier } from '../notifier' |
9 | import { sequelizeTypescript } from '../../initializers/database' | 8 | import { sequelizeTypescript } from '../../initializers/database' |
10 | import { MVideoFullLight } from '@server/typings/models' | 9 | import { MVideoFullLight } from '@server/typings/models' |
diff --git a/server/lib/schedulers/videos-redundancy-scheduler.ts b/server/lib/schedulers/videos-redundancy-scheduler.ts index c1c91b656..e33a4133a 100644 --- a/server/lib/schedulers/videos-redundancy-scheduler.ts +++ b/server/lib/schedulers/videos-redundancy-scheduler.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { AbstractScheduler } from './abstract-scheduler' | 1 | import { AbstractScheduler } from './abstract-scheduler' |
2 | import { HLS_REDUNDANCY_DIRECTORY, REDUNDANCY, VIDEO_IMPORT_TIMEOUT, WEBSERVER } from '../../initializers/constants' | 2 | import { HLS_REDUNDANCY_DIRECTORY, REDUNDANCY, VIDEO_IMPORT_TIMEOUT, WEBSERVER } from '../../initializers/constants' |
3 | import { logger } from '../../helpers/logger' | 3 | import { logger } from '../../helpers/logger' |
4 | import { VideosRedundancy } from '../../../shared/models/redundancy' | 4 | import { VideosRedundancyStrategy } from '../../../shared/models/redundancy' |
5 | import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' | 5 | import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' |
6 | import { downloadWebTorrentVideo, generateMagnetUri } from '../../helpers/webtorrent' | 6 | import { downloadWebTorrentVideo, generateMagnetUri } from '../../helpers/webtorrent' |
7 | import { join } from 'path' | 7 | import { join } from 'path' |
@@ -25,11 +25,12 @@ import { | |||
25 | MVideoWithAllFiles | 25 | MVideoWithAllFiles |
26 | } from '@server/typings/models' | 26 | } from '@server/typings/models' |
27 | import { getVideoFilename } from '../video-paths' | 27 | import { getVideoFilename } from '../video-paths' |
28 | import { VideoModel } from '@server/models/video/video' | ||
28 | 29 | ||
29 | type CandidateToDuplicate = { | 30 | type CandidateToDuplicate = { |
30 | redundancy: VideosRedundancy, | 31 | redundancy: VideosRedundancyStrategy |
31 | video: MVideoWithAllFiles, | 32 | video: MVideoWithAllFiles |
32 | files: MVideoFile[], | 33 | files: MVideoFile[] |
33 | streamingPlaylists: MStreamingPlaylistFiles[] | 34 | streamingPlaylists: MStreamingPlaylistFiles[] |
34 | } | 35 | } |
35 | 36 | ||
@@ -41,7 +42,7 @@ function isMVideoRedundancyFileVideo ( | |||
41 | 42 | ||
42 | export class VideosRedundancyScheduler extends AbstractScheduler { | 43 | export class VideosRedundancyScheduler extends AbstractScheduler { |
43 | 44 | ||
44 | private static instance: AbstractScheduler | 45 | private static instance: VideosRedundancyScheduler |
45 | 46 | ||
46 | protected schedulerIntervalMs = CONFIG.REDUNDANCY.VIDEOS.CHECK_INTERVAL | 47 | protected schedulerIntervalMs = CONFIG.REDUNDANCY.VIDEOS.CHECK_INTERVAL |
47 | 48 | ||
@@ -49,6 +50,22 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
49 | super() | 50 | super() |
50 | } | 51 | } |
51 | 52 | ||
53 | async createManualRedundancy (videoId: number) { | ||
54 | const videoToDuplicate = await VideoModel.loadWithFiles(videoId) | ||
55 | |||
56 | if (!videoToDuplicate) { | ||
57 | logger.warn('Video to manually duplicate %d does not exist anymore.', videoId) | ||
58 | return | ||
59 | } | ||
60 | |||
61 | return this.createVideoRedundancies({ | ||
62 | video: videoToDuplicate, | ||
63 | redundancy: null, | ||
64 | files: videoToDuplicate.VideoFiles, | ||
65 | streamingPlaylists: videoToDuplicate.VideoStreamingPlaylists | ||
66 | }) | ||
67 | } | ||
68 | |||
52 | protected async internalExecute () { | 69 | protected async internalExecute () { |
53 | for (const redundancyConfig of CONFIG.REDUNDANCY.VIDEOS.STRATEGIES) { | 70 | for (const redundancyConfig of CONFIG.REDUNDANCY.VIDEOS.STRATEGIES) { |
54 | logger.info('Running redundancy scheduler for strategy %s.', redundancyConfig.strategy) | 71 | logger.info('Running redundancy scheduler for strategy %s.', redundancyConfig.strategy) |
@@ -94,7 +111,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
94 | for (const redundancyModel of expired) { | 111 | for (const redundancyModel of expired) { |
95 | try { | 112 | try { |
96 | const redundancyConfig = CONFIG.REDUNDANCY.VIDEOS.STRATEGIES.find(s => s.strategy === redundancyModel.strategy) | 113 | const redundancyConfig = CONFIG.REDUNDANCY.VIDEOS.STRATEGIES.find(s => s.strategy === redundancyModel.strategy) |
97 | const candidate = { | 114 | const candidate: CandidateToDuplicate = { |
98 | redundancy: redundancyConfig, | 115 | redundancy: redundancyConfig, |
99 | video: null, | 116 | video: null, |
100 | files: [], | 117 | files: [], |
@@ -140,7 +157,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
140 | } | 157 | } |
141 | } | 158 | } |
142 | 159 | ||
143 | private findVideoToDuplicate (cache: VideosRedundancy) { | 160 | private findVideoToDuplicate (cache: VideosRedundancyStrategy) { |
144 | if (cache.strategy === 'most-views') { | 161 | if (cache.strategy === 'most-views') { |
145 | return VideoRedundancyModel.findMostViewToDuplicate(REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR) | 162 | return VideoRedundancyModel.findMostViewToDuplicate(REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR) |
146 | } | 163 | } |
@@ -187,13 +204,21 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
187 | } | 204 | } |
188 | } | 205 | } |
189 | 206 | ||
190 | private async createVideoFileRedundancy (redundancy: VideosRedundancy, video: MVideoAccountLight, fileArg: MVideoFile) { | 207 | private async createVideoFileRedundancy (redundancy: VideosRedundancyStrategy | null, video: MVideoAccountLight, fileArg: MVideoFile) { |
208 | let strategy = 'manual' | ||
209 | let expiresOn: Date = null | ||
210 | |||
211 | if (redundancy) { | ||
212 | strategy = redundancy.strategy | ||
213 | expiresOn = this.buildNewExpiration(redundancy.minLifetime) | ||
214 | } | ||
215 | |||
191 | const file = fileArg as MVideoFileVideo | 216 | const file = fileArg as MVideoFileVideo |
192 | file.Video = video | 217 | file.Video = video |
193 | 218 | ||
194 | const serverActor = await getServerActor() | 219 | const serverActor = await getServerActor() |
195 | 220 | ||
196 | logger.info('Duplicating %s - %d in videos redundancy with "%s" strategy.', video.url, file.resolution, redundancy.strategy) | 221 | logger.info('Duplicating %s - %d in videos redundancy with "%s" strategy.', video.url, file.resolution, strategy) |
197 | 222 | ||
198 | const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() | 223 | const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() |
199 | const magnetUri = generateMagnetUri(video, file, baseUrlHttp, baseUrlWs) | 224 | const magnetUri = generateMagnetUri(video, file, baseUrlHttp, baseUrlWs) |
@@ -204,10 +229,10 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
204 | await move(tmpPath, destPath, { overwrite: true }) | 229 | await move(tmpPath, destPath, { overwrite: true }) |
205 | 230 | ||
206 | const createdModel: MVideoRedundancyFileVideo = await VideoRedundancyModel.create({ | 231 | const createdModel: MVideoRedundancyFileVideo = await VideoRedundancyModel.create({ |
207 | expiresOn: this.buildNewExpiration(redundancy.minLifetime), | 232 | expiresOn, |
208 | url: getVideoCacheFileActivityPubUrl(file), | 233 | url: getVideoCacheFileActivityPubUrl(file), |
209 | fileUrl: video.getVideoRedundancyUrl(file, WEBSERVER.URL), | 234 | fileUrl: video.getVideoRedundancyUrl(file, WEBSERVER.URL), |
210 | strategy: redundancy.strategy, | 235 | strategy, |
211 | videoFileId: file.id, | 236 | videoFileId: file.id, |
212 | actorId: serverActor.id | 237 | actorId: serverActor.id |
213 | }) | 238 | }) |
@@ -220,25 +245,33 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
220 | } | 245 | } |
221 | 246 | ||
222 | private async createStreamingPlaylistRedundancy ( | 247 | private async createStreamingPlaylistRedundancy ( |
223 | redundancy: VideosRedundancy, | 248 | redundancy: VideosRedundancyStrategy, |
224 | video: MVideoAccountLight, | 249 | video: MVideoAccountLight, |
225 | playlistArg: MStreamingPlaylist | 250 | playlistArg: MStreamingPlaylist |
226 | ) { | 251 | ) { |
252 | let strategy = 'manual' | ||
253 | let expiresOn: Date = null | ||
254 | |||
255 | if (redundancy) { | ||
256 | strategy = redundancy.strategy | ||
257 | expiresOn = this.buildNewExpiration(redundancy.minLifetime) | ||
258 | } | ||
259 | |||
227 | const playlist = playlistArg as MStreamingPlaylistVideo | 260 | const playlist = playlistArg as MStreamingPlaylistVideo |
228 | playlist.Video = video | 261 | playlist.Video = video |
229 | 262 | ||
230 | const serverActor = await getServerActor() | 263 | const serverActor = await getServerActor() |
231 | 264 | ||
232 | logger.info('Duplicating %s streaming playlist in videos redundancy with "%s" strategy.', video.url, redundancy.strategy) | 265 | logger.info('Duplicating %s streaming playlist in videos redundancy with "%s" strategy.', video.url, strategy) |
233 | 266 | ||
234 | const destDirectory = join(HLS_REDUNDANCY_DIRECTORY, video.uuid) | 267 | const destDirectory = join(HLS_REDUNDANCY_DIRECTORY, video.uuid) |
235 | await downloadPlaylistSegments(playlist.playlistUrl, destDirectory, VIDEO_IMPORT_TIMEOUT) | 268 | await downloadPlaylistSegments(playlist.playlistUrl, destDirectory, VIDEO_IMPORT_TIMEOUT) |
236 | 269 | ||
237 | const createdModel: MVideoRedundancyStreamingPlaylistVideo = await VideoRedundancyModel.create({ | 270 | const createdModel: MVideoRedundancyStreamingPlaylistVideo = await VideoRedundancyModel.create({ |
238 | expiresOn: this.buildNewExpiration(redundancy.minLifetime), | 271 | expiresOn, |
239 | url: getVideoCacheStreamingPlaylistActivityPubUrl(video, playlist), | 272 | url: getVideoCacheStreamingPlaylistActivityPubUrl(video, playlist), |
240 | fileUrl: playlist.getVideoRedundancyUrl(WEBSERVER.URL), | 273 | fileUrl: playlist.getVideoRedundancyUrl(WEBSERVER.URL), |
241 | strategy: redundancy.strategy, | 274 | strategy, |
242 | videoStreamingPlaylistId: playlist.id, | 275 | videoStreamingPlaylistId: playlist.id, |
243 | actorId: serverActor.id | 276 | actorId: serverActor.id |
244 | }) | 277 | }) |
diff --git a/server/lib/thumbnail.ts b/server/lib/thumbnail.ts index a99f71629..8dbd41771 100644 --- a/server/lib/thumbnail.ts +++ b/server/lib/thumbnail.ts | |||
@@ -69,7 +69,7 @@ function generateVideoMiniature (video: MVideoThumbnail, videoFile: MVideoFile, | |||
69 | function createPlaceholderThumbnail (fileUrl: string, video: MVideoThumbnail, type: ThumbnailType, size: ImageSize) { | 69 | function createPlaceholderThumbnail (fileUrl: string, video: MVideoThumbnail, type: ThumbnailType, size: ImageSize) { |
70 | const { filename, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) | 70 | const { filename, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) |
71 | 71 | ||
72 | const thumbnail = existingThumbnail ? existingThumbnail : new ThumbnailModel() | 72 | const thumbnail = existingThumbnail || new ThumbnailModel() |
73 | 73 | ||
74 | thumbnail.filename = filename | 74 | thumbnail.filename = filename |
75 | thumbnail.height = height | 75 | thumbnail.height = height |
@@ -142,18 +142,18 @@ function buildMetadataFromVideo (video: MVideoThumbnail, type: ThumbnailType, si | |||
142 | } | 142 | } |
143 | 143 | ||
144 | async function createThumbnailFromFunction (parameters: { | 144 | async function createThumbnailFromFunction (parameters: { |
145 | thumbnailCreator: () => Promise<any>, | 145 | thumbnailCreator: () => Promise<any> |
146 | filename: string, | 146 | filename: string |
147 | height: number, | 147 | height: number |
148 | width: number, | 148 | width: number |
149 | type: ThumbnailType, | 149 | type: ThumbnailType |
150 | automaticallyGenerated?: boolean, | 150 | automaticallyGenerated?: boolean |
151 | fileUrl?: string, | 151 | fileUrl?: string |
152 | existingThumbnail?: MThumbnail | 152 | existingThumbnail?: MThumbnail |
153 | }) { | 153 | }) { |
154 | const { thumbnailCreator, filename, width, height, type, existingThumbnail, automaticallyGenerated = null, fileUrl = null } = parameters | 154 | const { thumbnailCreator, filename, width, height, type, existingThumbnail, automaticallyGenerated = null, fileUrl = null } = parameters |
155 | 155 | ||
156 | const thumbnail = existingThumbnail ? existingThumbnail : new ThumbnailModel() | 156 | const thumbnail = existingThumbnail || new ThumbnailModel() |
157 | 157 | ||
158 | thumbnail.filename = filename | 158 | thumbnail.filename = filename |
159 | thumbnail.height = height | 159 | thumbnail.height = height |
diff --git a/server/lib/user.ts b/server/lib/user.ts index c45438d95..88e60a7df 100644 --- a/server/lib/user.ts +++ b/server/lib/user.ts | |||
@@ -18,9 +18,9 @@ import { MUser, MUserDefault, MUserId } from '../typings/models/user' | |||
18 | type ChannelNames = { name: string, displayName: string } | 18 | type ChannelNames = { name: string, displayName: string } |
19 | 19 | ||
20 | async function createUserAccountAndChannelAndPlaylist (parameters: { | 20 | async function createUserAccountAndChannelAndPlaylist (parameters: { |
21 | userToCreate: MUser, | 21 | userToCreate: MUser |
22 | userDisplayName?: string, | 22 | userDisplayName?: string |
23 | channelNames?: ChannelNames, | 23 | channelNames?: ChannelNames |
24 | validateUser?: boolean | 24 | validateUser?: boolean |
25 | }): Promise<{ user: MUserDefault, account: MAccountDefault, videoChannel: MChannelActor }> { | 25 | }): Promise<{ user: MUserDefault, account: MAccountDefault, videoChannel: MChannelActor }> { |
26 | const { userToCreate, userDisplayName, channelNames, validateUser = true } = parameters | 26 | const { userToCreate, userDisplayName, channelNames, validateUser = true } = parameters |
@@ -63,11 +63,11 @@ async function createUserAccountAndChannelAndPlaylist (parameters: { | |||
63 | } | 63 | } |
64 | 64 | ||
65 | async function createLocalAccountWithoutKeys (parameters: { | 65 | async function createLocalAccountWithoutKeys (parameters: { |
66 | name: string, | 66 | name: string |
67 | displayName?: string, | 67 | displayName?: string |
68 | userId: number | null, | 68 | userId: number | null |
69 | applicationId: number | null, | 69 | applicationId: number | null |
70 | t: Transaction | undefined, | 70 | t: Transaction | undefined |
71 | type?: ActivityPubActorType | 71 | type?: ActivityPubActorType |
72 | }) { | 72 | }) { |
73 | const { name, displayName, userId, applicationId, t, type = 'Person' } = parameters | 73 | const { name, displayName, userId, applicationId, t, type = 'Person' } = parameters |
diff --git a/server/lib/video-blacklist.ts b/server/lib/video-blacklist.ts index 1dd45b76d..3b90b1b94 100644 --- a/server/lib/video-blacklist.ts +++ b/server/lib/video-blacklist.ts | |||
@@ -9,15 +9,15 @@ import { Notifier } from './notifier' | |||
9 | import { MUser, MVideoBlacklistVideo, MVideoWithBlacklistLight } from '@server/typings/models' | 9 | import { MUser, MVideoBlacklistVideo, MVideoWithBlacklistLight } from '@server/typings/models' |
10 | 10 | ||
11 | async function autoBlacklistVideoIfNeeded (parameters: { | 11 | async function autoBlacklistVideoIfNeeded (parameters: { |
12 | video: MVideoWithBlacklistLight, | 12 | video: MVideoWithBlacklistLight |
13 | user?: MUser, | 13 | user?: MUser |
14 | isRemote: boolean, | 14 | isRemote: boolean |
15 | isNew: boolean, | 15 | isNew: boolean |
16 | notify?: boolean, | 16 | notify?: boolean |
17 | transaction?: Transaction | 17 | transaction?: Transaction |
18 | }) { | 18 | }) { |
19 | const { video, user, isRemote, isNew, notify = true, transaction } = parameters | 19 | const { video, user, isRemote, isNew, notify = true, transaction } = parameters |
20 | const doAutoBlacklist = await Hooks.wrapPromiseFun( | 20 | const doAutoBlacklist = await Hooks.wrapFun( |
21 | autoBlacklistNeeded, | 21 | autoBlacklistNeeded, |
22 | { video, user, isRemote, isNew }, | 22 | { video, user, isRemote, isNew }, |
23 | 'filter:video.auto-blacklist.result' | 23 | 'filter:video.auto-blacklist.result' |
@@ -49,10 +49,10 @@ async function autoBlacklistVideoIfNeeded (parameters: { | |||
49 | return true | 49 | return true |
50 | } | 50 | } |
51 | 51 | ||
52 | async function autoBlacklistNeeded (parameters: { | 52 | function autoBlacklistNeeded (parameters: { |
53 | video: MVideoWithBlacklistLight, | 53 | video: MVideoWithBlacklistLight |
54 | isRemote: boolean, | 54 | isRemote: boolean |
55 | isNew: boolean, | 55 | isNew: boolean |
56 | user?: MUser | 56 | user?: MUser |
57 | }) { | 57 | }) { |
58 | const { user, video, isRemote, isNew } = parameters | 58 | const { user, video, isRemote, isNew } = parameters |
diff --git a/server/lib/video-channel.ts b/server/lib/video-channel.ts index 41eab456b..14829c9d6 100644 --- a/server/lib/video-channel.ts +++ b/server/lib/video-channel.ts | |||
@@ -6,8 +6,7 @@ import { buildActorInstance, federateVideoIfNeeded, getVideoChannelActivityPubUr | |||
6 | import { VideoModel } from '../models/video/video' | 6 | import { VideoModel } from '../models/video/video' |
7 | import { MAccountId, MChannelDefault, MChannelId } from '../typings/models' | 7 | import { MAccountId, MChannelDefault, MChannelId } from '../typings/models' |
8 | 8 | ||
9 | type CustomVideoChannelModelAccount <T extends MAccountId> = MChannelDefault & | 9 | type CustomVideoChannelModelAccount <T extends MAccountId> = MChannelDefault & { Account?: T } |
10 | { Account?: T } | ||
11 | 10 | ||
12 | async function createLocalVideoChannel <T extends MAccountId> ( | 11 | async function createLocalVideoChannel <T extends MAccountId> ( |
13 | videoChannelInfo: VideoChannelCreate, | 12 | videoChannelInfo: VideoChannelCreate, |
diff --git a/server/lib/video-comment.ts b/server/lib/video-comment.ts index b8074e6d2..fe83d23e7 100644 --- a/server/lib/video-comment.ts +++ b/server/lib/video-comment.ts | |||
@@ -7,9 +7,9 @@ import { sendCreateVideoComment } from './activitypub/send' | |||
7 | import { MAccountDefault, MComment, MCommentOwnerVideoReply, MVideoFullLight } from '../typings/models' | 7 | import { MAccountDefault, MComment, MCommentOwnerVideoReply, MVideoFullLight } from '../typings/models' |
8 | 8 | ||
9 | async function createVideoComment (obj: { | 9 | async function createVideoComment (obj: { |
10 | text: string, | 10 | text: string |
11 | inReplyToComment: MComment | null, | 11 | inReplyToComment: MComment | null |
12 | video: MVideoFullLight, | 12 | video: MVideoFullLight |
13 | account: MAccountDefault | 13 | account: MAccountDefault |
14 | }, t: Sequelize.Transaction) { | 14 | }, t: Sequelize.Transaction) { |
15 | let originCommentId: number | null = null | 15 | let originCommentId: number | null = null |