aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
authorChocobozzz <florian.bigard@gmail.com>2017-11-16 11:08:25 +0100
committerChocobozzz <florian.bigard@gmail.com>2017-11-27 19:40:52 +0100
commitefc32059d980c51793e8e9ac0fb6a885a8026f94 (patch)
treec272e63fd57a9625b53dc26ceb1b46aee35d21d3 /server
parentd846501818c2d29e66e6fd141789cb04fd55a437 (diff)
downloadPeerTube-efc32059d980c51793e8e9ac0fb6a885a8026f94.tar.gz
PeerTube-efc32059d980c51793e8e9ac0fb6a885a8026f94.tar.zst
PeerTube-efc32059d980c51793e8e9ac0fb6a885a8026f94.zip
Send server announce when users upload a video
Diffstat (limited to 'server')
-rw-r--r--server/controllers/activitypub/client.ts4
-rw-r--r--server/controllers/api/server/follows.ts8
-rw-r--r--server/controllers/api/videos/index.ts11
-rw-r--r--server/helpers/activitypub.ts30
-rw-r--r--server/helpers/core-utils.ts9
-rw-r--r--server/helpers/custom-validators/activitypub/videos.ts15
-rw-r--r--server/helpers/peertube-crypto.ts10
-rw-r--r--server/helpers/utils.ts18
-rw-r--r--server/initializers/constants.ts6
-rw-r--r--server/initializers/installer.ts2
-rw-r--r--server/lib/activitypub/misc.ts6
-rw-r--r--server/lib/activitypub/process-create.ts4
-rw-r--r--server/lib/activitypub/send-request.ts56
-rw-r--r--server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts2
-rw-r--r--server/lib/video-channel.ts5
-rw-r--r--server/models/account/account-follow-interface.ts6
-rw-r--r--server/models/account/account-follow.ts30
-rw-r--r--server/models/video/video-channel.ts4
-rw-r--r--server/models/video/video.ts6
19 files changed, 156 insertions, 76 deletions
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts
index 49dd24e79..76049f496 100644
--- a/server/controllers/activitypub/client.ts
+++ b/server/controllers/activitypub/client.ts
@@ -46,7 +46,7 @@ async function accountFollowersController (req: express.Request, res: express.Re
46 const page = req.params.page || 1 46 const page = req.params.page || 1
47 const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE) 47 const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE)
48 48
49 const result = await db.AccountFollow.listAcceptedFollowerUrlsForApi(account.id, start, count) 49 const result = await db.AccountFollow.listAcceptedFollowerUrlsForApi([ account.id ], start, count)
50 const activityPubResult = activityPubCollectionPagination(req.url, page, result) 50 const activityPubResult = activityPubCollectionPagination(req.url, page, result)
51 51
52 return res.json(activityPubResult) 52 return res.json(activityPubResult)
@@ -58,7 +58,7 @@ async function accountFollowingController (req: express.Request, res: express.Re
58 const page = req.params.page || 1 58 const page = req.params.page || 1
59 const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE) 59 const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE)
60 60
61 const result = await db.AccountFollow.listAcceptedFollowingUrlsForApi(account.id, start, count) 61 const result = await db.AccountFollow.listAcceptedFollowingUrlsForApi([ account.id ], start, count)
62 const activityPubResult = activityPubCollectionPagination(req.url, page, result) 62 const activityPubResult = activityPubCollectionPagination(req.url, page, result)
63 63
64 return res.json(activityPubResult) 64 return res.json(activityPubResult)
diff --git a/server/controllers/api/server/follows.ts b/server/controllers/api/server/follows.ts
index ac8ea87f9..e00787f02 100644
--- a/server/controllers/api/server/follows.ts
+++ b/server/controllers/api/server/follows.ts
@@ -2,7 +2,7 @@ import * as express from 'express'
2import { UserRight } from '../../../../shared/models/users/user-right.enum' 2import { UserRight } from '../../../../shared/models/users/user-right.enum'
3import { getFormattedObjects } from '../../../helpers' 3import { getFormattedObjects } from '../../../helpers'
4import { logger } from '../../../helpers/logger' 4import { logger } from '../../../helpers/logger'
5import { getApplicationAccount } from '../../../helpers/utils' 5import { getServerAccount } from '../../../helpers/utils'
6import { getAccountFromWebfinger } from '../../../helpers/webfinger' 6import { getAccountFromWebfinger } from '../../../helpers/webfinger'
7import { SERVER_ACCOUNT_NAME } from '../../../initializers/constants' 7import { SERVER_ACCOUNT_NAME } from '../../../initializers/constants'
8import { database as db } from '../../../initializers/database' 8import { database as db } from '../../../initializers/database'
@@ -50,14 +50,14 @@ export {
50// --------------------------------------------------------------------------- 50// ---------------------------------------------------------------------------
51 51
52async function listFollowing (req: express.Request, res: express.Response, next: express.NextFunction) { 52async function listFollowing (req: express.Request, res: express.Response, next: express.NextFunction) {
53 const applicationAccount = await getApplicationAccount() 53 const applicationAccount = await getServerAccount()
54 const resultList = await db.AccountFollow.listFollowingForApi(applicationAccount.id, req.query.start, req.query.count, req.query.sort) 54 const resultList = await db.AccountFollow.listFollowingForApi(applicationAccount.id, req.query.start, req.query.count, req.query.sort)
55 55
56 return res.json(getFormattedObjects(resultList.data, resultList.total)) 56 return res.json(getFormattedObjects(resultList.data, resultList.total))
57} 57}
58 58
59async function listFollowers (req: express.Request, res: express.Response, next: express.NextFunction) { 59async function listFollowers (req: express.Request, res: express.Response, next: express.NextFunction) {
60 const applicationAccount = await getApplicationAccount() 60 const applicationAccount = await getServerAccount()
61 const resultList = await db.AccountFollow.listFollowersForApi(applicationAccount.id, req.query.start, req.query.count, req.query.sort) 61 const resultList = await db.AccountFollow.listFollowersForApi(applicationAccount.id, req.query.start, req.query.count, req.query.sort)
62 62
63 return res.json(getFormattedObjects(resultList.data, resultList.total)) 63 return res.json(getFormattedObjects(resultList.data, resultList.total))
@@ -65,7 +65,7 @@ async function listFollowers (req: express.Request, res: express.Response, next:
65 65
66async function follow (req: express.Request, res: express.Response, next: express.NextFunction) { 66async function follow (req: express.Request, res: express.Response, next: express.NextFunction) {
67 const hosts = req.body.hosts as string[] 67 const hosts = req.body.hosts as string[]
68 const fromAccount = await getApplicationAccount() 68 const fromAccount = await getServerAccount()
69 69
70 const tasks: Promise<any>[] = [] 70 const tasks: Promise<any>[] = []
71 const accountName = SERVER_ACCOUNT_NAME 71 const accountName = SERVER_ACCOUNT_NAME
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index ebc07e179..a5414cc50 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -12,10 +12,10 @@ import {
12 resetSequelizeInstance, 12 resetSequelizeInstance,
13 retryTransactionWrapper 13 retryTransactionWrapper
14} from '../../../helpers' 14} from '../../../helpers'
15import { getActivityPubUrl } from '../../../helpers/activitypub' 15import { getActivityPubUrl, shareVideoByServer } from '../../../helpers/activitypub'
16import { CONFIG, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_MIMETYPE_EXT, VIDEO_PRIVACIES } from '../../../initializers' 16import { CONFIG, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_MIMETYPE_EXT, VIDEO_PRIVACIES } from '../../../initializers'
17import { database as db } from '../../../initializers/database' 17import { database as db } from '../../../initializers/database'
18import { sendAddVideo, sendUpdateVideoChannel } from '../../../lib/activitypub/send-request' 18import { sendAddVideo, sendUpdateVideo } from '../../../lib/activitypub/send-request'
19import { transcodingJobScheduler } from '../../../lib/jobs/transcoding-job-scheduler/transcoding-job-scheduler' 19import { transcodingJobScheduler } from '../../../lib/jobs/transcoding-job-scheduler/transcoding-job-scheduler'
20import { 20import {
21 asyncMiddleware, 21 asyncMiddleware,
@@ -56,7 +56,7 @@ const storage = multer.diskStorage({
56 randomString = 'fake-random-string' 56 randomString = 'fake-random-string'
57 } 57 }
58 58
59 cb(null, randomString + '.' + extension) 59 cb(null, randomString + extension)
60 } 60 }
61}) 61})
62 62
@@ -237,6 +237,7 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi
237 if (video.privacy === VideoPrivacy.PRIVATE) return undefined 237 if (video.privacy === VideoPrivacy.PRIVATE) return undefined
238 238
239 await sendAddVideo(video, t) 239 await sendAddVideo(video, t)
240 await shareVideoByServer(video, t)
240 }) 241 })
241 242
242 logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoUUID) 243 logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoUUID)
@@ -254,7 +255,7 @@ async function updateVideoRetryWrapper (req: express.Request, res: express.Respo
254} 255}
255 256
256async function updateVideo (req: express.Request, res: express.Response) { 257async function updateVideo (req: express.Request, res: express.Response) {
257 const videoInstance = res.locals.video 258 const videoInstance: VideoInstance = res.locals.video
258 const videoFieldsSave = videoInstance.toJSON() 259 const videoFieldsSave = videoInstance.toJSON()
259 const videoInfoToUpdate: VideoUpdate = req.body 260 const videoInfoToUpdate: VideoUpdate = req.body
260 const wasPrivateVideo = videoInstance.privacy === VideoPrivacy.PRIVATE 261 const wasPrivateVideo = videoInstance.privacy === VideoPrivacy.PRIVATE
@@ -284,7 +285,7 @@ async function updateVideo (req: express.Request, res: express.Response) {
284 285
285 // Now we'll update the video's meta data to our friends 286 // Now we'll update the video's meta data to our friends
286 if (wasPrivateVideo === false) { 287 if (wasPrivateVideo === false) {
287 await sendUpdateVideoChannel(videoInstance, t) 288 await sendUpdateVideo(videoInstance, t)
288 } 289 }
289 290
290 // Video is not private anymore, send a create action to remote servers 291 // Video is not private anymore, send a create action to remote servers
diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts
index de20ba55d..b376b8ca2 100644
--- a/server/helpers/activitypub.ts
+++ b/server/helpers/activitypub.ts
@@ -1,15 +1,19 @@
1import { join } from 'path' 1import { join } from 'path'
2import * as request from 'request' 2import * as request from 'request'
3import * as Sequelize from 'sequelize'
3import * as url from 'url' 4import * as url from 'url'
4import { ActivityIconObject } from '../../shared/index' 5import { ActivityIconObject } from '../../shared/index'
5import { ActivityPubActor } from '../../shared/models/activitypub/activitypub-actor' 6import { ActivityPubActor } from '../../shared/models/activitypub/activitypub-actor'
6import { ResultList } from '../../shared/models/result-list.model' 7import { ResultList } from '../../shared/models/result-list.model'
7import { database as db, REMOTE_SCHEME } from '../initializers' 8import { database as db, REMOTE_SCHEME } from '../initializers'
8import { ACTIVITY_PUB_ACCEPT_HEADER, CONFIG, STATIC_PATHS } from '../initializers/constants' 9import { ACTIVITY_PUB_ACCEPT_HEADER, CONFIG, STATIC_PATHS } from '../initializers/constants'
10import { sendAnnounce } from '../lib/activitypub/send-request'
11import { VideoChannelInstance } from '../models/video/video-channel-interface'
9import { VideoInstance } from '../models/video/video-interface' 12import { VideoInstance } from '../models/video/video-interface'
10import { isRemoteAccountValid } from './custom-validators' 13import { isRemoteAccountValid } from './custom-validators'
11import { logger } from './logger' 14import { logger } from './logger'
12import { doRequest, doRequestAndSaveToFile } from './requests' 15import { doRequest, doRequestAndSaveToFile } from './requests'
16import { getServerAccount } from './utils'
13 17
14function generateThumbnailFromUrl (video: VideoInstance, icon: ActivityIconObject) { 18function generateThumbnailFromUrl (video: VideoInstance, icon: ActivityIconObject) {
15 const thumbnailName = video.getThumbnailName() 19 const thumbnailName = video.getThumbnailName()
@@ -22,6 +26,28 @@ function generateThumbnailFromUrl (video: VideoInstance, icon: ActivityIconObjec
22 return doRequestAndSaveToFile(options, thumbnailPath) 26 return doRequestAndSaveToFile(options, thumbnailPath)
23} 27}
24 28
29async function shareVideoChannelByServer (videoChannel: VideoChannelInstance, t: Sequelize.Transaction) {
30 const serverAccount = await getServerAccount()
31
32 await db.VideoChannelShare.create({
33 accountId: serverAccount.id,
34 videoChannelId: videoChannel.id
35 }, { transaction: t })
36
37 return sendAnnounce(serverAccount, videoChannel, t)
38}
39
40async function shareVideoByServer (video: VideoInstance, t: Sequelize.Transaction) {
41 const serverAccount = await getServerAccount()
42
43 await db.VideoShare.create({
44 accountId: serverAccount.id,
45 videoId: video.id
46 }, { transaction: t })
47
48 return sendAnnounce(serverAccount, video, t)
49}
50
25function getActivityPubUrl (type: 'video' | 'videoChannel' | 'account' | 'videoAbuse', id: string) { 51function getActivityPubUrl (type: 'video' | 'videoChannel' | 'account' | 'videoAbuse', id: string) {
26 if (type === 'video') return CONFIG.WEBSERVER.URL + '/videos/watch/' + id 52 if (type === 'video') return CONFIG.WEBSERVER.URL + '/videos/watch/' + id
27 else if (type === 'videoChannel') return CONFIG.WEBSERVER.URL + '/video-channels/' + id 53 else if (type === 'videoChannel') return CONFIG.WEBSERVER.URL + '/video-channels/' + id
@@ -172,7 +198,9 @@ export {
172 generateThumbnailFromUrl, 198 generateThumbnailFromUrl,
173 getOrCreateAccount, 199 getOrCreateAccount,
174 fetchRemoteVideoPreview, 200 fetchRemoteVideoPreview,
175 fetchRemoteVideoDescription 201 fetchRemoteVideoDescription,
202 shareVideoChannelByServer,
203 shareVideoByServer
176} 204}
177 205
178// --------------------------------------------------------------------------- 206// ---------------------------------------------------------------------------
diff --git a/server/helpers/core-utils.ts b/server/helpers/core-utils.ts
index d8748e1d7..4ff07848c 100644
--- a/server/helpers/core-utils.ts
+++ b/server/helpers/core-utils.ts
@@ -20,9 +20,6 @@ import * as bcrypt from 'bcrypt'
20import * as createTorrent from 'create-torrent' 20import * as createTorrent from 'create-torrent'
21import * as rimraf from 'rimraf' 21import * as rimraf from 'rimraf'
22import * as pem from 'pem' 22import * as pem from 'pem'
23import * as jsonld from 'jsonld'
24import * as jsig from 'jsonld-signatures'
25jsig.use('jsonld', jsonld)
26 23
27function isTestInstance () { 24function isTestInstance () {
28 return process.env.NODE_ENV === 'test' 25 return process.env.NODE_ENV === 'test'
@@ -120,8 +117,6 @@ const bcryptHashPromise = promisify2<any, string | number, string>(bcrypt.hash)
120const createTorrentPromise = promisify2<string, any, any>(createTorrent) 117const createTorrentPromise = promisify2<string, any, any>(createTorrent)
121const rimrafPromise = promisify1WithVoid<string>(rimraf) 118const rimrafPromise = promisify1WithVoid<string>(rimraf)
122const statPromise = promisify1<string, Stats>(stat) 119const statPromise = promisify1<string, Stats>(stat)
123const jsonldSignPromise = promisify2<object, { privateKeyPem: string, creator: string }, object>(jsig.sign)
124const jsonldVerifyPromise = promisify2<object, object, object>(jsig.verify)
125 120
126// --------------------------------------------------------------------------- 121// ---------------------------------------------------------------------------
127 122
@@ -150,7 +145,5 @@ export {
150 bcryptHashPromise, 145 bcryptHashPromise,
151 createTorrentPromise, 146 createTorrentPromise,
152 rimrafPromise, 147 rimrafPromise,
153 statPromise, 148 statPromise
154 jsonldSignPromise,
155 jsonldVerifyPromise
156} 149}
diff --git a/server/helpers/custom-validators/activitypub/videos.ts b/server/helpers/custom-validators/activitypub/videos.ts
index 9ddacd601..89c49b0df 100644
--- a/server/helpers/custom-validators/activitypub/videos.ts
+++ b/server/helpers/custom-validators/activitypub/videos.ts
@@ -34,7 +34,7 @@ function isActivityPubVideoDurationValid (value: string) {
34 typeof value === 'string' && 34 typeof value === 'string' &&
35 value.startsWith('PT') && 35 value.startsWith('PT') &&
36 value.endsWith('S') && 36 value.endsWith('S') &&
37 isVideoDurationValid(value.replace(/[^0-9]+/, '')) 37 isVideoDurationValid(value.replace(/[^0-9]+/g, ''))
38} 38}
39 39
40function isVideoTorrentObjectValid (video: any) { 40function isVideoTorrentObjectValid (video: any) {
@@ -46,13 +46,14 @@ function isVideoTorrentObjectValid (video: any) {
46 isRemoteIdentifierValid(video.category) && 46 isRemoteIdentifierValid(video.category) &&
47 isRemoteIdentifierValid(video.licence) && 47 isRemoteIdentifierValid(video.licence) &&
48 isRemoteIdentifierValid(video.language) && 48 isRemoteIdentifierValid(video.language) &&
49 isVideoViewsValid(video.video) && 49 isVideoViewsValid(video.views) &&
50 isVideoNSFWValid(video.nsfw) && 50 isVideoNSFWValid(video.nsfw) &&
51 isDateValid(video.published) && 51 isDateValid(video.published) &&
52 isDateValid(video.updated) && 52 isDateValid(video.updated) &&
53 isRemoteVideoContentValid(video.mediaType, video.content) && 53 isRemoteVideoContentValid(video.mediaType, video.content) &&
54 isRemoteVideoIconValid(video.icon) && 54 isRemoteVideoIconValid(video.icon) &&
55 setValidRemoteVideoUrls(video.url) 55 setValidRemoteVideoUrls(video) &&
56 video.url.length !== 0
56} 57}
57 58
58function isVideoFlagValid (activity: any) { 59function isVideoFlagValid (activity: any) {
@@ -132,8 +133,8 @@ function isRemoteVideoIconValid (icon: any) {
132 return icon.type === 'Image' && 133 return icon.type === 'Image' &&
133 isVideoUrlValid(icon.url) && 134 isVideoUrlValid(icon.url) &&
134 icon.mediaType === 'image/jpeg' && 135 icon.mediaType === 'image/jpeg' &&
135 validator.isInt(icon.width, { min: 0 }) && 136 validator.isInt(icon.width + '', { min: 0 }) &&
136 validator.isInt(icon.height, { min: 0 }) 137 validator.isInt(icon.height + '', { min: 0 })
137} 138}
138 139
139function setValidRemoteVideoUrls (video: any) { 140function setValidRemoteVideoUrls (video: any) {
@@ -149,6 +150,6 @@ function isRemoteVideoUrlValid (url: any) {
149 return url.type === 'Link' && 150 return url.type === 'Link' &&
150 ACTIVITY_PUB.VIDEO_URL_MIME_TYPES.indexOf(url.mimeType) !== -1 && 151 ACTIVITY_PUB.VIDEO_URL_MIME_TYPES.indexOf(url.mimeType) !== -1 &&
151 isVideoUrlValid(url.url) && 152 isVideoUrlValid(url.url) &&
152 validator.isInt(url.width, { min: 0 }) && 153 validator.isInt(url.width + '', { min: 0 }) &&
153 validator.isInt(url.size, { min: 0 }) 154 validator.isInt(url.size + '', { min: 0 })
154} 155}
diff --git a/server/helpers/peertube-crypto.ts b/server/helpers/peertube-crypto.ts
index 6d50e446f..04a8d5681 100644
--- a/server/helpers/peertube-crypto.ts
+++ b/server/helpers/peertube-crypto.ts
@@ -1,4 +1,6 @@
1import * as jsonld from 'jsonld'
1import * as jsig from 'jsonld-signatures' 2import * as jsig from 'jsonld-signatures'
3jsig.use('jsonld', jsonld)
2 4
3import { 5import {
4 PRIVATE_RSA_KEY_SIZE, 6 PRIVATE_RSA_KEY_SIZE,
@@ -9,9 +11,7 @@ import {
9 bcryptGenSaltPromise, 11 bcryptGenSaltPromise,
10 bcryptHashPromise, 12 bcryptHashPromise,
11 createPrivateKey, 13 createPrivateKey,
12 getPublicKey, 14 getPublicKey
13 jsonldSignPromise,
14 jsonldVerifyPromise
15} from './core-utils' 15} from './core-utils'
16import { logger } from './logger' 16import { logger } from './logger'
17import { AccountInstance } from '../models/account/account-interface' 17import { AccountInstance } from '../models/account/account-interface'
@@ -45,7 +45,7 @@ function isSignatureVerified (fromAccount: AccountInstance, signedDocument: obje
45 publicKeyOwner: publicKeyOwnerObject 45 publicKeyOwner: publicKeyOwnerObject
46 } 46 }
47 47
48 return jsonldVerifyPromise(signedDocument, options) 48 return jsig.promises.verify(signedDocument, options)
49 .catch(err => { 49 .catch(err => {
50 logger.error('Cannot check signature.', err) 50 logger.error('Cannot check signature.', err)
51 return false 51 return false
@@ -58,7 +58,7 @@ function signObject (byAccount: AccountInstance, data: any) {
58 creator: byAccount.url 58 creator: byAccount.url
59 } 59 }
60 60
61 return jsonldSignPromise(data, options) 61 return jsig.promises.sign(data, options)
62} 62}
63 63
64function comparePassword (plainPassword: string, hashPassword: string) { 64function comparePassword (plainPassword: string, hashPassword: string) {
diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts
index 39957c90f..3af14a68a 100644
--- a/server/helpers/utils.ts
+++ b/server/helpers/utils.ts
@@ -6,6 +6,7 @@ import { CONFIG, database as db } from '../initializers'
6import { ResultList } from '../../shared' 6import { ResultList } from '../../shared'
7import { VideoResolution } from '../../shared/models/videos/video-resolution.enum' 7import { VideoResolution } from '../../shared/models/videos/video-resolution.enum'
8import { AccountInstance } from '../models/account/account-interface' 8import { AccountInstance } from '../models/account/account-interface'
9import { logger } from './logger'
9 10
10function badRequest (req: express.Request, res: express.Response, next: express.NextFunction) { 11function badRequest (req: express.Request, res: express.Response, next: express.NextFunction) {
11 return res.type('json').status(400).end() 12 return res.type('json').status(400).end()
@@ -79,13 +80,18 @@ function resetSequelizeInstance (instance: Sequelize.Instance<any>, savedFields:
79 }) 80 })
80} 81}
81 82
82let applicationAccount: AccountInstance 83let serverAccount: AccountInstance
83async function getApplicationAccount () { 84async function getServerAccount () {
84 if (applicationAccount === undefined) { 85 if (serverAccount === undefined) {
85 applicationAccount = await db.Account.loadApplication() 86 serverAccount = await db.Account.loadApplication()
86 } 87 }
87 88
88 return Promise.resolve(applicationAccount) 89 if (!serverAccount) {
90 logger.error('Cannot load server account.')
91 process.exit(0)
92 }
93
94 return Promise.resolve(serverAccount)
89} 95}
90 96
91type SortType = { sortModel: any, sortValue: string } 97type SortType = { sortModel: any, sortValue: string }
@@ -99,6 +105,6 @@ export {
99 isSignupAllowed, 105 isSignupAllowed,
100 computeResolutionsToTranscode, 106 computeResolutionsToTranscode,
101 resetSequelizeInstance, 107 resetSequelizeInstance,
102 getApplicationAccount, 108 getServerAccount,
103 SortType 109 SortType
104} 110}
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index 99da95ab9..dca223370 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -209,9 +209,9 @@ const VIDEO_PRIVACIES = {
209} 209}
210 210
211const VIDEO_MIMETYPE_EXT = { 211const VIDEO_MIMETYPE_EXT = {
212 'video/webm': 'webm', 212 'video/webm': '.webm',
213 'video/ogg': 'ogv', 213 'video/ogg': '.ogv',
214 'video/mp4': 'mp4' 214 'video/mp4': '.mp4'
215} 215}
216 216
217// --------------------------------------------------------------------------- 217// ---------------------------------------------------------------------------
diff --git a/server/initializers/installer.ts b/server/initializers/installer.ts
index c3521a9e4..3f4c4dfbb 100644
--- a/server/initializers/installer.ts
+++ b/server/initializers/installer.ts
@@ -13,9 +13,9 @@ async function installApplication () {
13 await db.sequelize.sync() 13 await db.sequelize.sync()
14 await removeCacheDirectories() 14 await removeCacheDirectories()
15 await createDirectoriesIfNotExist() 15 await createDirectoriesIfNotExist()
16 await createApplicationIfNotExist()
16 await createOAuthClientIfNotExist() 17 await createOAuthClientIfNotExist()
17 await createOAuthAdminIfNotExist() 18 await createOAuthAdminIfNotExist()
18 await createApplicationIfNotExist()
19 } catch (err) { 19 } catch (err) {
20 logger.error('Cannot install application.', err) 20 logger.error('Cannot install application.', err)
21 throw err 21 throw err
diff --git a/server/lib/activitypub/misc.ts b/server/lib/activitypub/misc.ts
index 2cf0c4fd1..43d26c328 100644
--- a/server/lib/activitypub/misc.ts
+++ b/server/lib/activitypub/misc.ts
@@ -28,9 +28,9 @@ async function videoActivityObjectToDBAttributes (
28 description: videoObject.content, 28 description: videoObject.content,
29 channelId: videoChannel.id, 29 channelId: videoChannel.id,
30 duration: parseInt(duration, 10), 30 duration: parseInt(duration, 10),
31 createdAt: videoObject.published, 31 createdAt: new Date(videoObject.published),
32 // FIXME: updatedAt does not seems to be considered by Sequelize 32 // FIXME: updatedAt does not seems to be considered by Sequelize
33 updatedAt: videoObject.updated, 33 updatedAt: new Date(videoObject.updated),
34 views: videoObject.views, 34 views: videoObject.views,
35 likes: 0, 35 likes: 0,
36 dislikes: 0, 36 dislikes: 0,
@@ -46,7 +46,7 @@ async function videoActivityObjectToDBAttributes (
46 46
47function videoFileActivityUrlToDBAttributes (videoCreated: VideoInstance, videoObject: VideoTorrentObject) { 47function videoFileActivityUrlToDBAttributes (videoCreated: VideoInstance, videoObject: VideoTorrentObject) {
48 const fileUrls = videoObject.url 48 const fileUrls = videoObject.url
49 .filter(u => Object.keys(VIDEO_MIMETYPE_EXT).indexOf(u.mimeType) !== -1) 49 .filter(u => Object.keys(VIDEO_MIMETYPE_EXT).indexOf(u.mimeType) !== -1 && u.url.startsWith('video/'))
50 50
51 const attributes: VideoFileAttributes[] = [] 51 const attributes: VideoFileAttributes[] = []
52 for (const url of fileUrls) { 52 for (const url of fileUrls) {
diff --git a/server/lib/activitypub/process-create.ts b/server/lib/activitypub/process-create.ts
index 1b825ebbc..4e4c9f703 100644
--- a/server/lib/activitypub/process-create.ts
+++ b/server/lib/activitypub/process-create.ts
@@ -48,8 +48,8 @@ async function addRemoteVideoChannel (account: AccountInstance, videoChannelToCr
48 name: videoChannelToCreateData.name, 48 name: videoChannelToCreateData.name,
49 description: videoChannelToCreateData.content, 49 description: videoChannelToCreateData.content,
50 uuid: videoChannelToCreateData.uuid, 50 uuid: videoChannelToCreateData.uuid,
51 createdAt: videoChannelToCreateData.published, 51 createdAt: new Date(videoChannelToCreateData.published),
52 updatedAt: videoChannelToCreateData.updated, 52 updatedAt: new Date(videoChannelToCreateData.updated),
53 remote: true, 53 remote: true,
54 accountId: account.id 54 accountId: account.id
55 } 55 }
diff --git a/server/lib/activitypub/send-request.ts b/server/lib/activitypub/send-request.ts
index 1dad51828..664b9d826 100644
--- a/server/lib/activitypub/send-request.ts
+++ b/server/lib/activitypub/send-request.ts
@@ -17,46 +17,67 @@ async function sendCreateVideoChannel (videoChannel: VideoChannelInstance, t: Se
17 const videoChannelObject = videoChannel.toActivityPubObject() 17 const videoChannelObject = videoChannel.toActivityPubObject()
18 const data = await createActivityData(videoChannel.url, videoChannel.Account, videoChannelObject) 18 const data = await createActivityData(videoChannel.url, videoChannel.Account, videoChannelObject)
19 19
20 return broadcastToFollowers(data, videoChannel.Account, t) 20 return broadcastToFollowers(data, [ videoChannel.Account ], t)
21} 21}
22 22
23async function sendUpdateVideoChannel (videoChannel: VideoChannelInstance, t: Sequelize.Transaction) { 23async function sendUpdateVideoChannel (videoChannel: VideoChannelInstance, t: Sequelize.Transaction) {
24 const videoChannelObject = videoChannel.toActivityPubObject() 24 const videoChannelObject = videoChannel.toActivityPubObject()
25 const data = await updateActivityData(videoChannel.url, videoChannel.Account, videoChannelObject) 25 const data = await updateActivityData(videoChannel.url, videoChannel.Account, videoChannelObject)
26 26
27 return broadcastToFollowers(data, videoChannel.Account, t) 27 return broadcastToFollowers(data, [ videoChannel.Account ], t)
28} 28}
29 29
30async function sendDeleteVideoChannel (videoChannel: VideoChannelInstance, t: Sequelize.Transaction) { 30async function sendDeleteVideoChannel (videoChannel: VideoChannelInstance, t: Sequelize.Transaction) {
31 const data = await deleteActivityData(videoChannel.url, videoChannel.Account) 31 const data = await deleteActivityData(videoChannel.url, videoChannel.Account)
32 32
33 return broadcastToFollowers(data, videoChannel.Account, t) 33 return broadcastToFollowers(data, [ videoChannel.Account ], t)
34} 34}
35 35
36async function sendAddVideo (video: VideoInstance, t: Sequelize.Transaction) { 36async function sendAddVideo (video: VideoInstance, t: Sequelize.Transaction) {
37 const videoObject = video.toActivityPubObject() 37 const videoObject = video.toActivityPubObject()
38 const data = await addActivityData(video.url, video.VideoChannel.Account, video.VideoChannel.url, videoObject) 38 const data = await addActivityData(video.url, video.VideoChannel.Account, video.VideoChannel.url, videoObject)
39 39
40 return broadcastToFollowers(data, video.VideoChannel.Account, t) 40 return broadcastToFollowers(data, [ video.VideoChannel.Account ], t)
41} 41}
42 42
43async function sendUpdateVideo (video: VideoInstance, t: Sequelize.Transaction) { 43async function sendUpdateVideo (video: VideoInstance, t: Sequelize.Transaction) {
44 const videoObject = video.toActivityPubObject() 44 const videoObject = video.toActivityPubObject()
45 const data = await updateActivityData(video.url, video.VideoChannel.Account, videoObject) 45 const data = await updateActivityData(video.url, video.VideoChannel.Account, videoObject)
46 46
47 return broadcastToFollowers(data, video.VideoChannel.Account, t) 47 return broadcastToFollowers(data, [ video.VideoChannel.Account ], t)
48} 48}
49 49
50async function sendDeleteVideo (video: VideoInstance, t: Sequelize.Transaction) { 50async function sendDeleteVideo (video: VideoInstance, t: Sequelize.Transaction) {
51 const data = await deleteActivityData(video.url, video.VideoChannel.Account) 51 const data = await deleteActivityData(video.url, video.VideoChannel.Account)
52 52
53 return broadcastToFollowers(data, video.VideoChannel.Account, t) 53 return broadcastToFollowers(data, [ video.VideoChannel.Account ], t)
54} 54}
55 55
56async function sendDeleteAccount (account: AccountInstance, t: Sequelize.Transaction) { 56async function sendDeleteAccount (account: AccountInstance, t: Sequelize.Transaction) {
57 const data = await deleteActivityData(account.url, account) 57 const data = await deleteActivityData(account.url, account)
58 58
59 return broadcastToFollowers(data, account, t) 59 return broadcastToFollowers(data, [ account ], t)
60}
61
62async function sendAnnounce (byAccount: AccountInstance, instance: VideoInstance | VideoChannelInstance, t: Sequelize.Transaction) {
63 const object = instance.toActivityPubObject()
64
65 let url = ''
66 let objectActorUrl: string
67 if ((instance as any).VideoChannel !== undefined) {
68 objectActorUrl = (instance as VideoInstance).VideoChannel.Account.url
69 url = getActivityPubUrl('video', instance.uuid) + '#announce'
70 } else {
71 objectActorUrl = (instance as VideoChannelInstance).Account.url
72 url = getActivityPubUrl('videoChannel', instance.uuid) + '#announce'
73 }
74
75 const objectWithActor = Object.assign(object, {
76 actor: objectActorUrl
77 })
78
79 const data = await announceActivityData(url, byAccount, objectWithActor)
80 return broadcastToFollowers(data, [ byAccount ], t)
60} 81}
61 82
62async function sendVideoAbuse ( 83async function sendVideoAbuse (
@@ -95,15 +116,17 @@ export {
95 sendDeleteAccount, 116 sendDeleteAccount,
96 sendAccept, 117 sendAccept,
97 sendFollow, 118 sendFollow,
98 sendVideoAbuse 119 sendVideoAbuse,
120 sendAnnounce
99} 121}
100 122
101// --------------------------------------------------------------------------- 123// ---------------------------------------------------------------------------
102 124
103async function broadcastToFollowers (data: any, fromAccount: AccountInstance, t: Sequelize.Transaction) { 125async function broadcastToFollowers (data: any, toAccountFollowers: AccountInstance[], t: Sequelize.Transaction) {
104 const result = await db.AccountFollow.listAcceptedFollowerUrlsForApi(fromAccount.id) 126 const toAccountFollowerIds = toAccountFollowers.map(a => a.id)
127 const result = await db.AccountFollow.listAcceptedFollowerSharedInboxUrls(toAccountFollowerIds)
105 if (result.data.length === 0) { 128 if (result.data.length === 0) {
106 logger.info('Not broadcast because of 0 followers.') 129 logger.info('Not broadcast because of 0 followers for %s.', toAccountFollowerIds.join(', '))
107 return 130 return
108 } 131 }
109 132
@@ -186,6 +209,17 @@ async function addActivityData (url: string, byAccount: AccountInstance, target:
186 return buildSignedActivity(byAccount, base) 209 return buildSignedActivity(byAccount, base)
187} 210}
188 211
212async function announceActivityData (url: string, byAccount: AccountInstance, object: any) {
213 const base = {
214 type: 'Announce',
215 id: url,
216 actor: byAccount.url,
217 object
218 }
219
220 return buildSignedActivity(byAccount, base)
221}
222
189async function followActivityData (url: string, byAccount: AccountInstance) { 223async function followActivityData (url: string, byAccount: AccountInstance) {
190 const base = { 224 const base = {
191 type: 'Follow', 225 type: 'Follow',
diff --git a/server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts b/server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts
index f6d9627a5..6443899d3 100644
--- a/server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts
+++ b/server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts
@@ -6,6 +6,7 @@ import { VideoInstance } from '../../../models'
6import { sendAddVideo } from '../../activitypub/send-request' 6import { sendAddVideo } from '../../activitypub/send-request'
7import { JobScheduler } from '../job-scheduler' 7import { JobScheduler } from '../job-scheduler'
8import { TranscodingJobPayload } from './transcoding-job-scheduler' 8import { TranscodingJobPayload } from './transcoding-job-scheduler'
9import { shareVideoByServer } from '../../../helpers/activitypub'
9 10
10async function process (data: TranscodingJobPayload, jobId: number) { 11async function process (data: TranscodingJobPayload, jobId: number) {
11 const video = await db.Video.loadByUUIDAndPopulateAccountAndServerAndTags(data.videoUUID) 12 const video = await db.Video.loadByUUIDAndPopulateAccountAndServerAndTags(data.videoUUID)
@@ -37,6 +38,7 @@ async function onSuccess (jobId: number, video: VideoInstance, jobScheduler: Job
37 38
38 // Now we'll add the video's meta data to our followers 39 // Now we'll add the video's meta data to our followers
39 await sendAddVideo(video, undefined) 40 await sendAddVideo(video, undefined)
41 await shareVideoByServer(video, undefined)
40 42
41 const originalFileHeight = await videoDatabase.getOriginalFileHeight() 43 const originalFileHeight = await videoDatabase.getOriginalFileHeight()
42 44
diff --git a/server/lib/video-channel.ts b/server/lib/video-channel.ts
index 80303fb83..e69ec062f 100644
--- a/server/lib/video-channel.ts
+++ b/server/lib/video-channel.ts
@@ -5,7 +5,7 @@ import { logger } from '../helpers'
5import { AccountInstance } from '../models' 5import { AccountInstance } from '../models'
6import { VideoChannelCreate } from '../../shared/models' 6import { VideoChannelCreate } from '../../shared/models'
7import { sendCreateVideoChannel } from './activitypub/send-request' 7import { sendCreateVideoChannel } from './activitypub/send-request'
8import { getActivityPubUrl } from '../helpers/activitypub' 8import { getActivityPubUrl, shareVideoChannelByServer } from '../helpers/activitypub'
9 9
10async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account: AccountInstance, t: Sequelize.Transaction) { 10async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account: AccountInstance, t: Sequelize.Transaction) {
11 const videoChannelData = { 11 const videoChannelData = {
@@ -25,7 +25,8 @@ async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account
25 // Do not forget to add Account information to the created video channel 25 // Do not forget to add Account information to the created video channel
26 videoChannelCreated.Account = account 26 videoChannelCreated.Account = account
27 27
28 sendCreateVideoChannel(videoChannelCreated, t) 28 await sendCreateVideoChannel(videoChannelCreated, t)
29 await shareVideoChannelByServer(videoChannelCreated, t)
29 30
30 return videoChannelCreated 31 return videoChannelCreated
31} 32}
diff --git a/server/models/account/account-follow-interface.ts b/server/models/account/account-follow-interface.ts
index 54baf45ed..21fda98ce 100644
--- a/server/models/account/account-follow-interface.ts
+++ b/server/models/account/account-follow-interface.ts
@@ -10,8 +10,9 @@ export namespace AccountFollowMethods {
10 export type ListFollowingForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList<AccountInstance> > 10 export type ListFollowingForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList<AccountInstance> >
11 export type ListFollowersForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList<AccountInstance> > 11 export type ListFollowersForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList<AccountInstance> >
12 12
13 export type ListAcceptedFollowerUrlsForApi = (id: number, start?: number, count?: number) => Promise< ResultList<string> > 13 export type ListAcceptedFollowerUrlsForApi = (accountId: number[], start?: number, count?: number) => Promise< ResultList<string> >
14 export type ListAcceptedFollowingUrlsForApi = (id: number, start?: number, count?: number) => Promise< ResultList<string> > 14 export type ListAcceptedFollowingUrlsForApi = (accountId: number[], start?: number, count?: number) => Promise< ResultList<string> >
15 export type ListAcceptedFollowerSharedInboxUrls = (accountId: number[]) => Promise< ResultList<string> >
15} 16}
16 17
17export interface AccountFollowClass { 18export interface AccountFollowClass {
@@ -21,6 +22,7 @@ export interface AccountFollowClass {
21 22
22 listAcceptedFollowerUrlsForApi: AccountFollowMethods.ListAcceptedFollowerUrlsForApi 23 listAcceptedFollowerUrlsForApi: AccountFollowMethods.ListAcceptedFollowerUrlsForApi
23 listAcceptedFollowingUrlsForApi: AccountFollowMethods.ListAcceptedFollowingUrlsForApi 24 listAcceptedFollowingUrlsForApi: AccountFollowMethods.ListAcceptedFollowingUrlsForApi
25 listAcceptedFollowerSharedInboxUrls: AccountFollowMethods.ListAcceptedFollowerSharedInboxUrls
24} 26}
25 27
26export interface AccountFollowAttributes { 28export interface AccountFollowAttributes {
diff --git a/server/models/account/account-follow.ts b/server/models/account/account-follow.ts
index f457e43e9..8a7474c9d 100644
--- a/server/models/account/account-follow.ts
+++ b/server/models/account/account-follow.ts
@@ -11,6 +11,7 @@ let listFollowingForApi: AccountFollowMethods.ListFollowingForApi
11let listFollowersForApi: AccountFollowMethods.ListFollowersForApi 11let listFollowersForApi: AccountFollowMethods.ListFollowersForApi
12let listAcceptedFollowerUrlsForApi: AccountFollowMethods.ListAcceptedFollowerUrlsForApi 12let listAcceptedFollowerUrlsForApi: AccountFollowMethods.ListAcceptedFollowerUrlsForApi
13let listAcceptedFollowingUrlsForApi: AccountFollowMethods.ListAcceptedFollowingUrlsForApi 13let listAcceptedFollowingUrlsForApi: AccountFollowMethods.ListAcceptedFollowingUrlsForApi
14let listAcceptedFollowerSharedInboxUrls: AccountFollowMethods.ListAcceptedFollowerSharedInboxUrls
14 15
15export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { 16export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
16 AccountFollow = sequelize.define<AccountFollowInstance, AccountFollowAttributes>('AccountFollow', 17 AccountFollow = sequelize.define<AccountFollowInstance, AccountFollowAttributes>('AccountFollow',
@@ -42,7 +43,8 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
42 listFollowingForApi, 43 listFollowingForApi,
43 listFollowersForApi, 44 listFollowersForApi,
44 listAcceptedFollowerUrlsForApi, 45 listAcceptedFollowerUrlsForApi,
45 listAcceptedFollowingUrlsForApi 46 listAcceptedFollowingUrlsForApi,
47 listAcceptedFollowerSharedInboxUrls
46 ] 48 ]
47 addMethodsToModel(AccountFollow, classMethods) 49 addMethodsToModel(AccountFollow, classMethods)
48 50
@@ -146,17 +148,27 @@ listFollowersForApi = function (id: number, start: number, count: number, sort:
146 }) 148 })
147} 149}
148 150
149listAcceptedFollowerUrlsForApi = function (accountId: number, start?: number, count?: number) { 151listAcceptedFollowerUrlsForApi = function (accountIds: number[], start?: number, count?: number) {
150 return createListAcceptedFollowForApiQuery('followers', accountId, start, count) 152 return createListAcceptedFollowForApiQuery('followers', accountIds, start, count)
151} 153}
152 154
153listAcceptedFollowingUrlsForApi = function (accountId: number, start?: number, count?: number) { 155listAcceptedFollowerSharedInboxUrls = function (accountIds: number[]) {
154 return createListAcceptedFollowForApiQuery('following', accountId, start, count) 156 return createListAcceptedFollowForApiQuery('followers', accountIds, undefined, undefined, 'sharedInboxUrl')
157}
158
159listAcceptedFollowingUrlsForApi = function (accountIds: number[], start?: number, count?: number) {
160 return createListAcceptedFollowForApiQuery('following', accountIds, start, count)
155} 161}
156 162
157// ------------------------------ UTILS ------------------------------ 163// ------------------------------ UTILS ------------------------------
158 164
159async function createListAcceptedFollowForApiQuery (type: 'followers' | 'following', accountId: number, start?: number, count?: number) { 165async function createListAcceptedFollowForApiQuery (
166 type: 'followers' | 'following',
167 accountIds: number[],
168 start?: number,
169 count?: number,
170 columnUrl = 'url'
171) {
160 let firstJoin: string 172 let firstJoin: string
161 let secondJoin: string 173 let secondJoin: string
162 174
@@ -168,20 +180,20 @@ async function createListAcceptedFollowForApiQuery (type: 'followers' | 'followi
168 secondJoin = 'targetAccountId' 180 secondJoin = 'targetAccountId'
169 } 181 }
170 182
171 const selections = [ '"Follows"."url" AS "url"', 'COUNT(*) AS "total"' ] 183 const selections = [ '"Follows"."' + columnUrl + '" AS "url"', 'COUNT(*) AS "total"' ]
172 const tasks: Promise<any>[] = [] 184 const tasks: Promise<any>[] = []
173 185
174 for (const selection of selections) { 186 for (const selection of selections) {
175 let query = 'SELECT ' + selection + ' FROM "Accounts" ' + 187 let query = 'SELECT ' + selection + ' FROM "Accounts" ' +
176 'INNER JOIN "AccountFollows" ON "AccountFollows"."' + firstJoin + '" = "Accounts"."id" ' + 188 'INNER JOIN "AccountFollows" ON "AccountFollows"."' + firstJoin + '" = "Accounts"."id" ' +
177 'INNER JOIN "Accounts" AS "Follows" ON "AccountFollows"."' + secondJoin + '" = "Follows"."id" ' + 189 'INNER JOIN "Accounts" AS "Follows" ON "AccountFollows"."' + secondJoin + '" = "Follows"."id" ' +
178 'WHERE "Accounts"."id" = $accountId AND "AccountFollows"."state" = \'accepted\' ' 190 'WHERE "Accounts"."id" IN ($accountIds) AND "AccountFollows"."state" = \'accepted\' '
179 191
180 if (start !== undefined) query += 'LIMIT ' + start 192 if (start !== undefined) query += 'LIMIT ' + start
181 if (count !== undefined) query += ', ' + count 193 if (count !== undefined) query += ', ' + count
182 194
183 const options = { 195 const options = {
184 bind: { accountId }, 196 bind: { accountIds: accountIds.join(',') },
185 type: Sequelize.QueryTypes.SELECT 197 type: Sequelize.QueryTypes.SELECT
186 } 198 }
187 tasks.push(AccountFollow['sequelize'].query(query, options)) 199 tasks.push(AccountFollow['sequelize'].query(query, options))
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts
index 3cb4a33b9..1f4604f1d 100644
--- a/server/models/video/video-channel.ts
+++ b/server/models/video/video-channel.ts
@@ -153,8 +153,8 @@ toActivityPubObject = function (this: VideoChannelInstance) {
153 uuid: this.uuid, 153 uuid: this.uuid,
154 content: this.description, 154 content: this.description,
155 name: this.name, 155 name: this.name,
156 published: this.createdAt, 156 published: this.createdAt.toISOString(),
157 updated: this.updatedAt 157 updated: this.updatedAt.toISOString()
158 } 158 }
159 159
160 return json 160 return json
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 480e54276..64ee7ae34 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -558,7 +558,7 @@ toActivityPubObject = function (this: VideoInstance) {
558 for (const file of this.VideoFiles) { 558 for (const file of this.VideoFiles) {
559 url.push({ 559 url.push({
560 type: 'Link', 560 type: 'Link',
561 mimeType: 'video/' + file.extname, 561 mimeType: 'video/' + file.extname.replace('.', ''),
562 url: getVideoFileUrl(this, file, baseUrlHttp), 562 url: getVideoFileUrl(this, file, baseUrlHttp),
563 width: file.resolution, 563 width: file.resolution,
564 size: file.size 564 size: file.size
@@ -601,8 +601,8 @@ toActivityPubObject = function (this: VideoInstance) {
601 }, 601 },
602 views: this.views, 602 views: this.views,
603 nsfw: this.nsfw, 603 nsfw: this.nsfw,
604 published: this.createdAt, 604 published: this.createdAt.toISOString(),
605 updated: this.updatedAt, 605 updated: this.updatedAt.toISOString(),
606 mediaType: 'text/markdown', 606 mediaType: 'text/markdown',
607 content: this.getTruncatedDescription(), 607 content: this.getTruncatedDescription(),
608 icon: { 608 icon: {