aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
authorChocobozzz <florian.bigard@gmail.com>2017-11-22 16:25:03 +0100
committerChocobozzz <florian.bigard@gmail.com>2017-11-27 19:40:53 +0100
commit40ff57078e15d5b86ee6b71e198b95d3feb78eaf (patch)
tree88031d4eac6a26597e8a1f2fc63674664e3eae26 /server
parentc46edbc2f6ca310b2f0331f979ac6caf27f6eb92 (diff)
downloadPeerTube-40ff57078e15d5b86ee6b71e198b95d3feb78eaf.tar.gz
PeerTube-40ff57078e15d5b86ee6b71e198b95d3feb78eaf.tar.zst
PeerTube-40ff57078e15d5b86ee6b71e198b95d3feb78eaf.zip
Federate video views
Diffstat (limited to 'server')
-rw-r--r--server/controllers/activitypub/outbox.ts14
-rw-r--r--server/controllers/api/server/follows.ts9
-rw-r--r--server/controllers/api/videos/index.ts26
-rw-r--r--server/helpers/custom-validators/activitypub/activity.ts4
-rw-r--r--server/helpers/custom-validators/activitypub/index.ts1
-rw-r--r--server/helpers/custom-validators/activitypub/videos.ts2
-rw-r--r--server/helpers/custom-validators/activitypub/view.ts13
-rw-r--r--server/lib/activitypub/process/misc.ts7
-rw-r--r--server/lib/activitypub/process/process-create.ts19
-rw-r--r--server/lib/activitypub/process/process-follow.ts6
-rw-r--r--server/lib/activitypub/send/misc.ts14
-rw-r--r--server/lib/activitypub/send/send-create.ts64
-rw-r--r--server/lib/activitypub/url.ts7
-rw-r--r--server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-fetcher-handler.ts6
-rw-r--r--server/models/video/video-interface.ts2
-rw-r--r--server/models/video/video.ts27
16 files changed, 179 insertions, 42 deletions
diff --git a/server/controllers/activitypub/outbox.ts b/server/controllers/activitypub/outbox.ts
index 74d399763..8c63eeb2e 100644
--- a/server/controllers/activitypub/outbox.ts
+++ b/server/controllers/activitypub/outbox.ts
@@ -1,14 +1,14 @@
1import * as express from 'express' 1import * as express from 'express'
2import { Activity, ActivityAdd } from '../../../shared/models/activitypub/activity' 2import { Activity, ActivityAdd } from '../../../shared/models/activitypub/activity'
3import { activityPubCollectionPagination, activityPubContextify } from '../../helpers/activitypub' 3import { activityPubCollectionPagination } from '../../helpers/activitypub'
4import { pageToStartAndCount } from '../../helpers/core-utils'
4import { database as db } from '../../initializers' 5import { database as db } from '../../initializers'
6import { ACTIVITY_PUB } from '../../initializers/constants'
5import { addActivityData } from '../../lib/activitypub/send/send-add' 7import { addActivityData } from '../../lib/activitypub/send/send-add'
6import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url' 8import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url'
7import { announceActivityData } from '../../lib/index' 9import { announceActivityData } from '../../lib/index'
8import { asyncMiddleware, localAccountValidator } from '../../middlewares' 10import { asyncMiddleware, localAccountValidator } from '../../middlewares'
9import { AccountInstance } from '../../models/account/account-interface' 11import { AccountInstance } from '../../models/account/account-interface'
10import { pageToStartAndCount } from '../../helpers/core-utils'
11import { ACTIVITY_PUB } from '../../initializers/constants'
12 12
13const outboxRouter = express.Router() 13const outboxRouter = express.Router()
14 14
@@ -36,14 +36,18 @@ async function outboxController (req: express.Request, res: express.Response, ne
36 36
37 for (const video of data.data) { 37 for (const video of data.data) {
38 const videoObject = video.toActivityPubObject() 38 const videoObject = video.toActivityPubObject()
39 let addActivity: ActivityAdd = await addActivityData(video.url, account, video, video.VideoChannel.url, videoObject)
40 39
41 // This is a shared video 40 // This is a shared video
42 if (video.VideoShare !== undefined) { 41 if (video.VideoShares !== undefined && video.VideoShares.length !== 0) {
42 const addActivity = await addActivityData(video.url, video.VideoChannel.Account, video, video.VideoChannel.url, videoObject)
43
43 const url = getAnnounceActivityPubUrl(video.url, account) 44 const url = getAnnounceActivityPubUrl(video.url, account)
44 const announceActivity = await announceActivityData(url, account, addActivity) 45 const announceActivity = await announceActivityData(url, account, addActivity)
46
45 activities.push(announceActivity) 47 activities.push(announceActivity)
46 } else { 48 } else {
49 const addActivity = await addActivityData(video.url, account, video, video.VideoChannel.url, videoObject)
50
47 activities.push(addActivity) 51 activities.push(addActivity)
48 } 52 }
49 } 53 }
diff --git a/server/controllers/api/server/follows.ts b/server/controllers/api/server/follows.ts
index 391f8bdca..535d530f7 100644
--- a/server/controllers/api/server/follows.ts
+++ b/server/controllers/api/server/follows.ts
@@ -148,10 +148,17 @@ async function removeFollow (req: express.Request, res: express.Response, next:
148 const follow: AccountFollowInstance = res.locals.follow 148 const follow: AccountFollowInstance = res.locals.follow
149 149
150 await db.sequelize.transaction(async t => { 150 await db.sequelize.transaction(async t => {
151 await sendUndoFollow(follow, t) 151 if (follow.state === 'accepted') await sendUndoFollow(follow, t)
152
152 await follow.destroy({ transaction: t }) 153 await follow.destroy({ transaction: t })
153 }) 154 })
154 155
156 // Destroy the account that will destroy video channels, videos and video files too
157 // This could be long so don't wait this task
158 const following = follow.AccountFollowing
159 following.destroy()
160 .catch(err => logger.error('Cannot destroy account that we do not follow anymore %s.', following.url, err))
161
155 return res.status(204).end() 162 return res.status(204).end()
156} 163}
157 164
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index 0d114dcd2..2b5afd632 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -11,10 +11,15 @@ import {
11 resetSequelizeInstance, 11 resetSequelizeInstance,
12 retryTransactionWrapper 12 retryTransactionWrapper
13} from '../../../helpers' 13} from '../../../helpers'
14import { getServerAccount } from '../../../helpers/utils'
14import { CONFIG, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_MIMETYPE_EXT, VIDEO_PRIVACIES } from '../../../initializers' 15import { CONFIG, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_MIMETYPE_EXT, VIDEO_PRIVACIES } from '../../../initializers'
15import { database as db } from '../../../initializers/database' 16import { database as db } from '../../../initializers/database'
16import { sendAddVideo } from '../../../lib/activitypub/send/send-add' 17import { sendAddVideo } from '../../../lib/activitypub/send/send-add'
17import { sendUpdateVideo } from '../../../lib/activitypub/send/send-update' 18import { sendUpdateVideo } from '../../../lib/activitypub/send/send-update'
19import { shareVideoByServer } from '../../../lib/activitypub/share'
20import { getVideoActivityPubUrl } from '../../../lib/activitypub/url'
21import { fetchRemoteVideoDescription } from '../../../lib/activitypub/videos'
22import { sendCreateViewToVideoFollowers } from '../../../lib/index'
18import { transcodingJobScheduler } from '../../../lib/jobs/transcoding-job-scheduler/transcoding-job-scheduler' 23import { transcodingJobScheduler } from '../../../lib/jobs/transcoding-job-scheduler/transcoding-job-scheduler'
19import { 24import {
20 asyncMiddleware, 25 asyncMiddleware,
@@ -35,9 +40,7 @@ import { abuseVideoRouter } from './abuse'
35import { blacklistRouter } from './blacklist' 40import { blacklistRouter } from './blacklist'
36import { videoChannelRouter } from './channel' 41import { videoChannelRouter } from './channel'
37import { rateVideoRouter } from './rate' 42import { rateVideoRouter } from './rate'
38import { getVideoActivityPubUrl } from '../../../lib/activitypub/url' 43import { sendCreateViewToOrigin } from '../../../lib/activitypub/send/send-create'
39import { shareVideoByServer } from '../../../lib/activitypub/share'
40import { fetchRemoteVideoDescription } from '../../../lib/activitypub/videos'
41 44
42const videosRouter = express.Router() 45const videosRouter = express.Router()
43 46
@@ -311,17 +314,18 @@ async function updateVideo (req: express.Request, res: express.Response) {
311async function getVideo (req: express.Request, res: express.Response) { 314async function getVideo (req: express.Request, res: express.Response) {
312 const videoInstance = res.locals.video 315 const videoInstance = res.locals.video
313 316
317 const baseIncrementPromise = videoInstance.increment('views')
318 .then(() => getServerAccount())
319
314 if (videoInstance.isOwned()) { 320 if (videoInstance.isOwned()) {
315 // The increment is done directly in the database, not using the instance value 321 // The increment is done directly in the database, not using the instance value
316 // FIXME: make a real view system 322 baseIncrementPromise
317 // For example, only add a view when a user watch a video during 30s etc 323 .then(serverAccount => sendCreateViewToVideoFollowers(serverAccount, videoInstance, undefined))
318 videoInstance.increment('views') 324 .catch(err => logger.error('Cannot add view to video/send view to followers for %s.', videoInstance.uuid, err))
319 .then(() => {
320 // TODO: send to followers a notification
321 })
322 .catch(err => logger.error('Cannot add view to video %s.', videoInstance.uuid, err))
323 } else { 325 } else {
324 // TODO: send view event to followers 326 baseIncrementPromise
327 .then(serverAccount => sendCreateViewToOrigin(serverAccount, videoInstance, undefined))
328 .catch(err => logger.error('Cannot send view to origin server for %s.', videoInstance.uuid, err))
325 } 329 }
326 330
327 // Do not wait the view system 331 // Do not wait the view system
diff --git a/server/helpers/custom-validators/activitypub/activity.ts b/server/helpers/custom-validators/activitypub/activity.ts
index 9305e092c..66e557d39 100644
--- a/server/helpers/custom-validators/activitypub/activity.ts
+++ b/server/helpers/custom-validators/activitypub/activity.ts
@@ -11,6 +11,7 @@ import {
11 isVideoTorrentDeleteActivityValid, 11 isVideoTorrentDeleteActivityValid,
12 isVideoTorrentUpdateActivityValid 12 isVideoTorrentUpdateActivityValid
13} from './videos' 13} from './videos'
14import { isViewActivityValid } from './view'
14 15
15function isRootActivityValid (activity: any) { 16function isRootActivityValid (activity: any) {
16 return Array.isArray(activity['@context']) && 17 return Array.isArray(activity['@context']) &&
@@ -55,7 +56,8 @@ export {
55 56
56function checkCreateActivity (activity: any) { 57function checkCreateActivity (activity: any) {
57 return isVideoChannelCreateActivityValid(activity) || 58 return isVideoChannelCreateActivityValid(activity) ||
58 isVideoFlagValid(activity) 59 isVideoFlagValid(activity) ||
60 isViewActivityValid(activity)
59} 61}
60 62
61function checkAddActivity (activity: any) { 63function checkAddActivity (activity: any) {
diff --git a/server/helpers/custom-validators/activitypub/index.ts b/server/helpers/custom-validators/activitypub/index.ts
index 6685b269f..f8dfae4ff 100644
--- a/server/helpers/custom-validators/activitypub/index.ts
+++ b/server/helpers/custom-validators/activitypub/index.ts
@@ -5,3 +5,4 @@ export * from './signature'
5export * from './undo' 5export * from './undo'
6export * from './video-channels' 6export * from './video-channels'
7export * from './videos' 7export * from './videos'
8export * from './view'
diff --git a/server/helpers/custom-validators/activitypub/videos.ts b/server/helpers/custom-validators/activitypub/videos.ts
index faeedd3df..55e79c4e8 100644
--- a/server/helpers/custom-validators/activitypub/videos.ts
+++ b/server/helpers/custom-validators/activitypub/videos.ts
@@ -52,7 +52,7 @@ function isVideoTorrentObjectValid (video: any) {
52 setValidRemoteTags(video) && 52 setValidRemoteTags(video) &&
53 isRemoteIdentifierValid(video.category) && 53 isRemoteIdentifierValid(video.category) &&
54 isRemoteIdentifierValid(video.licence) && 54 isRemoteIdentifierValid(video.licence) &&
55 isRemoteIdentifierValid(video.language) && 55 (!video.language || isRemoteIdentifierValid(video.language)) &&
56 isVideoViewsValid(video.views) && 56 isVideoViewsValid(video.views) &&
57 isVideoNSFWValid(video.nsfw) && 57 isVideoNSFWValid(video.nsfw) &&
58 isDateValid(video.published) && 58 isDateValid(video.published) &&
diff --git a/server/helpers/custom-validators/activitypub/view.ts b/server/helpers/custom-validators/activitypub/view.ts
new file mode 100644
index 000000000..7a3aca6f5
--- /dev/null
+++ b/server/helpers/custom-validators/activitypub/view.ts
@@ -0,0 +1,13 @@
1import { isActivityPubUrlValid, isBaseActivityValid } from './misc'
2
3function isViewActivityValid (activity: any) {
4 return isBaseActivityValid(activity, 'Create') &&
5 activity.object.type === 'View' &&
6 isActivityPubUrlValid(activity.object.actor) &&
7 isActivityPubUrlValid(activity.object.object)
8}
9// ---------------------------------------------------------------------------
10
11export {
12 isViewActivityValid
13}
diff --git a/server/lib/activitypub/process/misc.ts b/server/lib/activitypub/process/misc.ts
index e90a793fc..eefbe2884 100644
--- a/server/lib/activitypub/process/misc.ts
+++ b/server/lib/activitypub/process/misc.ts
@@ -33,13 +33,18 @@ async function videoActivityObjectToDBAttributes (
33 else if (cc.indexOf(ACTIVITY_PUB.PUBLIC) !== -1) privacy = VideoPrivacy.UNLISTED 33 else if (cc.indexOf(ACTIVITY_PUB.PUBLIC) !== -1) privacy = VideoPrivacy.UNLISTED
34 34
35 const duration = videoObject.duration.replace(/[^\d]+/, '') 35 const duration = videoObject.duration.replace(/[^\d]+/, '')
36 let language = null
37 if (videoObject.language) {
38 language = parseInt(videoObject.language.identifier, 10)
39 }
40
36 const videoData: VideoAttributes = { 41 const videoData: VideoAttributes = {
37 name: videoObject.name, 42 name: videoObject.name,
38 uuid: videoObject.uuid, 43 uuid: videoObject.uuid,
39 url: videoObject.id, 44 url: videoObject.id,
40 category: parseInt(videoObject.category.identifier, 10), 45 category: parseInt(videoObject.category.identifier, 10),
41 licence: parseInt(videoObject.licence.identifier, 10), 46 licence: parseInt(videoObject.licence.identifier, 10),
42 language: parseInt(videoObject.language.identifier, 10), 47 language,
43 nsfw: videoObject.nsfw, 48 nsfw: videoObject.nsfw,
44 description: videoObject.content, 49 description: videoObject.content,
45 channelId: videoChannel.id, 50 channelId: videoChannel.id,
diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts
index ddf7c74f6..1777733a0 100644
--- a/server/lib/activitypub/process/process-create.ts
+++ b/server/lib/activitypub/process/process-create.ts
@@ -1,9 +1,11 @@
1import { ActivityCreate, VideoChannelObject } from '../../../../shared' 1import { ActivityCreate, VideoChannelObject } from '../../../../shared'
2import { VideoAbuseObject } from '../../../../shared/models/activitypub/objects/video-abuse-object' 2import { VideoAbuseObject } from '../../../../shared/models/activitypub/objects/video-abuse-object'
3import { ViewObject } from '../../../../shared/models/activitypub/objects/view-object'
3import { logger, retryTransactionWrapper } from '../../../helpers' 4import { logger, retryTransactionWrapper } from '../../../helpers'
4import { database as db } from '../../../initializers' 5import { database as db } from '../../../initializers'
5import { AccountInstance } from '../../../models/account/account-interface' 6import { AccountInstance } from '../../../models/account/account-interface'
6import { getOrCreateAccountAndServer } from '../account' 7import { getOrCreateAccountAndServer } from '../account'
8import { sendCreateViewToVideoFollowers } from '../send/send-create'
7import { getVideoChannelActivityPubUrl } from '../url' 9import { getVideoChannelActivityPubUrl } from '../url'
8import { videoChannelActivityObjectToDBAttributes } from './misc' 10import { videoChannelActivityObjectToDBAttributes } from './misc'
9 11
@@ -12,7 +14,9 @@ async function processCreateActivity (activity: ActivityCreate) {
12 const activityType = activityObject.type 14 const activityType = activityObject.type
13 const account = await getOrCreateAccountAndServer(activity.actor) 15 const account = await getOrCreateAccountAndServer(activity.actor)
14 16
15 if (activityType === 'VideoChannel') { 17 if (activityType === 'View') {
18 return processCreateView(activityObject as ViewObject)
19 } else if (activityType === 'VideoChannel') {
16 return processCreateVideoChannel(account, activityObject as VideoChannelObject) 20 return processCreateVideoChannel(account, activityObject as VideoChannelObject)
17 } else if (activityType === 'Flag') { 21 } else if (activityType === 'Flag') {
18 return processCreateVideoAbuse(account, activityObject as VideoAbuseObject) 22 return processCreateVideoAbuse(account, activityObject as VideoAbuseObject)
@@ -30,6 +34,19 @@ export {
30 34
31// --------------------------------------------------------------------------- 35// ---------------------------------------------------------------------------
32 36
37async function processCreateView (view: ViewObject) {
38 const video = await db.Video.loadByUrlAndPopulateAccount(view.object)
39
40 if (!video) throw new Error('Unknown video ' + view.object)
41
42 const account = await db.Account.loadByUrl(view.actor)
43 if (!account) throw new Error('Unknown account ' + view.actor)
44
45 await video.increment('views')
46
47 if (video.isOwned()) await sendCreateViewToVideoFollowers(account, video, undefined)
48}
49
33function processCreateVideoChannel (account: AccountInstance, videoChannelToCreateData: VideoChannelObject) { 50function processCreateVideoChannel (account: AccountInstance, videoChannelToCreateData: VideoChannelObject) {
34 const options = { 51 const options = {
35 arguments: [ account, videoChannelToCreateData ], 52 arguments: [ account, videoChannelToCreateData ],
diff --git a/server/lib/activitypub/process/process-follow.ts b/server/lib/activitypub/process/process-follow.ts
index 248004226..320dc1138 100644
--- a/server/lib/activitypub/process/process-follow.ts
+++ b/server/lib/activitypub/process/process-follow.ts
@@ -49,6 +49,12 @@ async function follow (account: AccountInstance, targetAccountURL: string) {
49 }, 49 },
50 transaction: t 50 transaction: t
51 }) 51 })
52
53 if (accountFollow.state !== 'accepted') {
54 accountFollow.state = 'accepted'
55 await accountFollow.save({ transaction: t })
56 }
57
52 accountFollow.AccountFollower = account 58 accountFollow.AccountFollower = account
53 accountFollow.AccountFollowing = targetAccount 59 accountFollow.AccountFollowing = targetAccount
54 60
diff --git a/server/lib/activitypub/send/misc.ts b/server/lib/activitypub/send/misc.ts
index bea955b67..f3dc5c148 100644
--- a/server/lib/activitypub/send/misc.ts
+++ b/server/lib/activitypub/send/misc.ts
@@ -4,16 +4,26 @@ import { ACTIVITY_PUB, database as db } from '../../../initializers'
4import { AccountInstance } from '../../../models/account/account-interface' 4import { AccountInstance } from '../../../models/account/account-interface'
5import { activitypubHttpJobScheduler } from '../../jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler' 5import { activitypubHttpJobScheduler } from '../../jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler'
6 6
7async function broadcastToFollowers (data: any, byAccount: AccountInstance, toAccountFollowers: AccountInstance[], t: Transaction) { 7async function broadcastToFollowers (
8 data: any,
9 byAccount: AccountInstance,
10 toAccountFollowers: AccountInstance[],
11 t: Transaction,
12 followersException: AccountInstance[] = []
13) {
8 const toAccountFollowerIds = toAccountFollowers.map(a => a.id) 14 const toAccountFollowerIds = toAccountFollowers.map(a => a.id)
15
9 const result = await db.AccountFollow.listAcceptedFollowerSharedInboxUrls(toAccountFollowerIds) 16 const result = await db.AccountFollow.listAcceptedFollowerSharedInboxUrls(toAccountFollowerIds)
10 if (result.data.length === 0) { 17 if (result.data.length === 0) {
11 logger.info('Not broadcast because of 0 followers for %s.', toAccountFollowerIds.join(', ')) 18 logger.info('Not broadcast because of 0 followers for %s.', toAccountFollowerIds.join(', '))
12 return undefined 19 return undefined
13 } 20 }
14 21
22 const followersSharedInboxException = followersException.map(f => f.sharedInboxUrl)
23 const uris = result.data.filter(sharedInbox => followersSharedInboxException.indexOf(sharedInbox) === -1)
24
15 const jobPayload = { 25 const jobPayload = {
16 uris: result.data, 26 uris,
17 signatureAccountId: byAccount.id, 27 signatureAccountId: byAccount.id,
18 body: data 28 body: data
19 } 29 }
diff --git a/server/lib/activitypub/send/send-create.ts b/server/lib/activitypub/send/send-create.ts
index df8e0a642..e5fb212b7 100644
--- a/server/lib/activitypub/send/send-create.ts
+++ b/server/lib/activitypub/send/send-create.ts
@@ -3,7 +3,9 @@ import { ActivityCreate } from '../../../../shared/models/activitypub/activity'
3import { AccountInstance, VideoChannelInstance, VideoInstance } from '../../../models' 3import { AccountInstance, VideoChannelInstance, VideoInstance } from '../../../models'
4import { VideoAbuseInstance } from '../../../models/video/video-abuse-interface' 4import { VideoAbuseInstance } from '../../../models/video/video-abuse-interface'
5import { broadcastToFollowers, getAudience, unicastTo } from './misc' 5import { broadcastToFollowers, getAudience, unicastTo } from './misc'
6import { getVideoAbuseActivityPubUrl } from '../url' 6import { getVideoAbuseActivityPubUrl, getVideoViewActivityPubUrl } from '../url'
7import { getServerAccount } from '../../../helpers/utils'
8import { database as db } from '../../../initializers'
7 9
8async function sendCreateVideoChannel (videoChannel: VideoChannelInstance, t: Transaction) { 10async function sendCreateVideoChannel (videoChannel: VideoChannelInstance, t: Transaction) {
9 const byAccount = videoChannel.Account 11 const byAccount = videoChannel.Account
@@ -16,21 +18,53 @@ async function sendCreateVideoChannel (videoChannel: VideoChannelInstance, t: Tr
16 18
17async function sendVideoAbuse (byAccount: AccountInstance, videoAbuse: VideoAbuseInstance, video: VideoInstance, t: Transaction) { 19async function sendVideoAbuse (byAccount: AccountInstance, videoAbuse: VideoAbuseInstance, video: VideoInstance, t: Transaction) {
18 const url = getVideoAbuseActivityPubUrl(videoAbuse) 20 const url = getVideoAbuseActivityPubUrl(videoAbuse)
19 const data = await createActivityData(url, byAccount, videoAbuse.toActivityPubObject()) 21
22 const audience = { to: [ video.VideoChannel.Account.url ], cc: [] }
23 const data = await createActivityData(url, byAccount, videoAbuse.toActivityPubObject(), audience)
24
25 return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t)
26}
27
28async function sendCreateViewToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) {
29 const url = getVideoViewActivityPubUrl(byAccount, video)
30 const viewActivity = createViewActivityData(byAccount, video)
31
32 const audience = { to: [ video.VideoChannel.Account.url ], cc: [ video.VideoChannel.Account.url + '/followers' ] }
33 const data = await createActivityData(url, byAccount, viewActivity, audience)
20 34
21 return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t) 35 return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t)
22} 36}
23 37
24// async function sendCreateView () 38async function sendCreateViewToVideoFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) {
39 const url = getVideoViewActivityPubUrl(byAccount, video)
40 const viewActivity = createViewActivityData(byAccount, video)
41
42 const audience = { to: [ video.VideoChannel.Account.url + '/followers' ], cc: [] }
43 const data = await createActivityData(url, byAccount, viewActivity, audience)
44
45 const serverAccount = await getServerAccount()
46 const accountsToForwardView = await db.VideoShare.loadAccountsByShare(video.id)
47 accountsToForwardView.push(video.VideoChannel.Account)
48
49 // Don't forward view to server that sent it to us
50 const index = accountsToForwardView.findIndex(a => a.id === byAccount.id)
51 if (index) accountsToForwardView.splice(index, 1)
52
53 const followersException = [ byAccount ]
54 return broadcastToFollowers(data, serverAccount, accountsToForwardView, t, followersException)
55}
56
57async function createActivityData (url: string, byAccount: AccountInstance, object: any, audience?: { to: string[], cc: string[] }) {
58 if (!audience) {
59 audience = await getAudience(byAccount)
60 }
25 61
26async function createActivityData (url: string, byAccount: AccountInstance, object: any) {
27 const { to, cc } = await getAudience(byAccount)
28 const activity: ActivityCreate = { 62 const activity: ActivityCreate = {
29 type: 'Create', 63 type: 'Create',
30 id: url, 64 id: url,
31 actor: byAccount.url, 65 actor: byAccount.url,
32 to, 66 to: audience.to,
33 cc, 67 cc: audience.cc,
34 object 68 object
35 } 69 }
36 70
@@ -42,5 +76,19 @@ async function createActivityData (url: string, byAccount: AccountInstance, obje
42export { 76export {
43 sendCreateVideoChannel, 77 sendCreateVideoChannel,
44 sendVideoAbuse, 78 sendVideoAbuse,
45 createActivityData 79 createActivityData,
80 sendCreateViewToOrigin,
81 sendCreateViewToVideoFollowers
82}
83
84// ---------------------------------------------------------------------------
85
86function createViewActivityData (byAccount: AccountInstance, video: VideoInstance) {
87 const obj = {
88 type: 'View',
89 actor: byAccount.url,
90 object: video.url
91 }
92
93 return obj
46} 94}
diff --git a/server/lib/activitypub/url.ts b/server/lib/activitypub/url.ts
index 41ac0f9a8..d98561e33 100644
--- a/server/lib/activitypub/url.ts
+++ b/server/lib/activitypub/url.ts
@@ -21,6 +21,10 @@ function getVideoAbuseActivityPubUrl (videoAbuse: VideoAbuseInstance) {
21 return CONFIG.WEBSERVER.URL + '/admin/video-abuses/' + videoAbuse.id 21 return CONFIG.WEBSERVER.URL + '/admin/video-abuses/' + videoAbuse.id
22} 22}
23 23
24function getVideoViewActivityPubUrl (byAccount: AccountInstance, video: VideoInstance) {
25 return video.url + '#views/' + byAccount.uuid + '/' + new Date().toISOString()
26}
27
24function getAccountFollowActivityPubUrl (accountFollow: AccountFollowInstance) { 28function getAccountFollowActivityPubUrl (accountFollow: AccountFollowInstance) {
25 const me = accountFollow.AccountFollower 29 const me = accountFollow.AccountFollower
26 const following = accountFollow.AccountFollowing 30 const following = accountFollow.AccountFollowing
@@ -56,5 +60,6 @@ export {
56 getAccountFollowAcceptActivityPubUrl, 60 getAccountFollowAcceptActivityPubUrl,
57 getAnnounceActivityPubUrl, 61 getAnnounceActivityPubUrl,
58 getUpdateActivityPubUrl, 62 getUpdateActivityPubUrl,
59 getUndoActivityPubUrl 63 getUndoActivityPubUrl,
64 getVideoViewActivityPubUrl
60} 65}
diff --git a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-fetcher-handler.ts b/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-fetcher-handler.ts
index 09efaa622..bda319592 100644
--- a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-fetcher-handler.ts
+++ b/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-fetcher-handler.ts
@@ -1,10 +1,8 @@
1import { logger } from '../../../helpers' 1import { logger } from '../../../helpers'
2import { buildSignedActivity } from '../../../helpers/activitypub'
3import { doRequest } from '../../../helpers/requests' 2import { doRequest } from '../../../helpers/requests'
4import { database as db } from '../../../initializers'
5import { ActivityPubHttpPayload } from './activitypub-http-job-scheduler'
6import { processActivities } from '../../activitypub/process/process'
7import { ACTIVITY_PUB } from '../../../initializers/constants' 3import { ACTIVITY_PUB } from '../../../initializers/constants'
4import { processActivities } from '../../activitypub/process/process'
5import { ActivityPubHttpPayload } from './activitypub-http-job-scheduler'
8 6
9async function process (payload: ActivityPubHttpPayload, jobId: number) { 7async function process (payload: ActivityPubHttpPayload, jobId: number) {
10 logger.info('Processing ActivityPub fetcher in job %d.', jobId) 8 logger.info('Processing ActivityPub fetcher in job %d.', jobId)
diff --git a/server/models/video/video-interface.ts b/server/models/video/video-interface.ts
index 391ecff43..b97f163ab 100644
--- a/server/models/video/video-interface.ts
+++ b/server/models/video/video-interface.ts
@@ -122,7 +122,7 @@ export interface VideoAttributes {
122 VideoChannel?: VideoChannelInstance 122 VideoChannel?: VideoChannelInstance
123 Tags?: TagInstance[] 123 Tags?: TagInstance[]
124 VideoFiles?: VideoFileInstance[] 124 VideoFiles?: VideoFileInstance[]
125 VideoShare?: VideoShareInstance 125 VideoShares?: VideoShareInstance[]
126} 126}
127 127
128export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.Instance<VideoAttributes> { 128export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.Instance<VideoAttributes> {
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 9b411a92e..052fc0ae8 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -567,6 +567,14 @@ toActivityPubObject = function (this: VideoInstance) {
567 name: t.name 567 name: t.name
568 })) 568 }))
569 569
570 let language
571 if (this.language) {
572 language = {
573 identifier: this.language + '',
574 name: this.getLanguageLabel()
575 }
576 }
577
570 const url = [] 578 const url = []
571 for (const file of this.VideoFiles) { 579 for (const file of this.VideoFiles) {
572 url.push({ 580 url.push({
@@ -608,10 +616,7 @@ toActivityPubObject = function (this: VideoInstance) {
608 identifier: this.licence + '', 616 identifier: this.licence + '',
609 name: this.getLicenceLabel() 617 name: this.getLicenceLabel()
610 }, 618 },
611 language: { 619 language,
612 identifier: this.language + '',
613 name: this.getLanguageLabel()
614 },
615 views: this.views, 620 views: this.views,
616 nsfw: this.nsfw, 621 nsfw: this.nsfw,
617 published: this.createdAt.toISOString(), 622 published: this.createdAt.toISOString(),
@@ -816,7 +821,19 @@ listAllAndSharedByAccountForOutbox = function (accountId: number, start: number,
816 include: [ 821 include: [
817 { 822 {
818 model: Video['sequelize'].models.VideoShare, 823 model: Video['sequelize'].models.VideoShare,
819 required: false 824 required: false,
825 where: {
826 [Sequelize.Op.and]: [
827 {
828 id: {
829 [Sequelize.Op.not]: null
830 }
831 },
832 {
833 accountId
834 }
835 ]
836 }
820 }, 837 },
821 { 838 {
822 model: Video['sequelize'].models.VideoChannel, 839 model: Video['sequelize'].models.VideoChannel,