diff options
author | Chocobozzz <florian.bigard@gmail.com> | 2017-11-22 16:25:03 +0100 |
---|---|---|
committer | Chocobozzz <florian.bigard@gmail.com> | 2017-11-27 19:40:53 +0100 |
commit | 40ff57078e15d5b86ee6b71e198b95d3feb78eaf (patch) | |
tree | 88031d4eac6a26597e8a1f2fc63674664e3eae26 | |
parent | c46edbc2f6ca310b2f0331f979ac6caf27f6eb92 (diff) | |
download | PeerTube-40ff57078e15d5b86ee6b71e198b95d3feb78eaf.tar.gz PeerTube-40ff57078e15d5b86ee6b71e198b95d3feb78eaf.tar.zst PeerTube-40ff57078e15d5b86ee6b71e198b95d3feb78eaf.zip |
Federate video views
19 files changed, 188 insertions, 44 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { Activity, ActivityAdd } from '../../../shared/models/activitypub/activity' | 2 | import { Activity, ActivityAdd } from '../../../shared/models/activitypub/activity' |
3 | import { activityPubCollectionPagination, activityPubContextify } from '../../helpers/activitypub' | 3 | import { activityPubCollectionPagination } from '../../helpers/activitypub' |
4 | import { pageToStartAndCount } from '../../helpers/core-utils' | ||
4 | import { database as db } from '../../initializers' | 5 | import { database as db } from '../../initializers' |
6 | import { ACTIVITY_PUB } from '../../initializers/constants' | ||
5 | import { addActivityData } from '../../lib/activitypub/send/send-add' | 7 | import { addActivityData } from '../../lib/activitypub/send/send-add' |
6 | import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url' | 8 | import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url' |
7 | import { announceActivityData } from '../../lib/index' | 9 | import { announceActivityData } from '../../lib/index' |
8 | import { asyncMiddleware, localAccountValidator } from '../../middlewares' | 10 | import { asyncMiddleware, localAccountValidator } from '../../middlewares' |
9 | import { AccountInstance } from '../../models/account/account-interface' | 11 | import { AccountInstance } from '../../models/account/account-interface' |
10 | import { pageToStartAndCount } from '../../helpers/core-utils' | ||
11 | import { ACTIVITY_PUB } from '../../initializers/constants' | ||
12 | 12 | ||
13 | const outboxRouter = express.Router() | 13 | const 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' |
14 | import { getServerAccount } from '../../../helpers/utils' | ||
14 | import { CONFIG, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_MIMETYPE_EXT, VIDEO_PRIVACIES } from '../../../initializers' | 15 | import { CONFIG, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_MIMETYPE_EXT, VIDEO_PRIVACIES } from '../../../initializers' |
15 | import { database as db } from '../../../initializers/database' | 16 | import { database as db } from '../../../initializers/database' |
16 | import { sendAddVideo } from '../../../lib/activitypub/send/send-add' | 17 | import { sendAddVideo } from '../../../lib/activitypub/send/send-add' |
17 | import { sendUpdateVideo } from '../../../lib/activitypub/send/send-update' | 18 | import { sendUpdateVideo } from '../../../lib/activitypub/send/send-update' |
19 | import { shareVideoByServer } from '../../../lib/activitypub/share' | ||
20 | import { getVideoActivityPubUrl } from '../../../lib/activitypub/url' | ||
21 | import { fetchRemoteVideoDescription } from '../../../lib/activitypub/videos' | ||
22 | import { sendCreateViewToVideoFollowers } from '../../../lib/index' | ||
18 | import { transcodingJobScheduler } from '../../../lib/jobs/transcoding-job-scheduler/transcoding-job-scheduler' | 23 | import { transcodingJobScheduler } from '../../../lib/jobs/transcoding-job-scheduler/transcoding-job-scheduler' |
19 | import { | 24 | import { |
20 | asyncMiddleware, | 25 | asyncMiddleware, |
@@ -35,9 +40,7 @@ import { abuseVideoRouter } from './abuse' | |||
35 | import { blacklistRouter } from './blacklist' | 40 | import { blacklistRouter } from './blacklist' |
36 | import { videoChannelRouter } from './channel' | 41 | import { videoChannelRouter } from './channel' |
37 | import { rateVideoRouter } from './rate' | 42 | import { rateVideoRouter } from './rate' |
38 | import { getVideoActivityPubUrl } from '../../../lib/activitypub/url' | 43 | import { sendCreateViewToOrigin } from '../../../lib/activitypub/send/send-create' |
39 | import { shareVideoByServer } from '../../../lib/activitypub/share' | ||
40 | import { fetchRemoteVideoDescription } from '../../../lib/activitypub/videos' | ||
41 | 44 | ||
42 | const videosRouter = express.Router() | 45 | const videosRouter = express.Router() |
43 | 46 | ||
@@ -311,17 +314,18 @@ async function updateVideo (req: express.Request, res: express.Response) { | |||
311 | async function getVideo (req: express.Request, res: express.Response) { | 314 | async 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' |
14 | import { isViewActivityValid } from './view' | ||
14 | 15 | ||
15 | function isRootActivityValid (activity: any) { | 16 | function isRootActivityValid (activity: any) { |
16 | return Array.isArray(activity['@context']) && | 17 | return Array.isArray(activity['@context']) && |
@@ -55,7 +56,8 @@ export { | |||
55 | 56 | ||
56 | function checkCreateActivity (activity: any) { | 57 | function checkCreateActivity (activity: any) { |
57 | return isVideoChannelCreateActivityValid(activity) || | 58 | return isVideoChannelCreateActivityValid(activity) || |
58 | isVideoFlagValid(activity) | 59 | isVideoFlagValid(activity) || |
60 | isViewActivityValid(activity) | ||
59 | } | 61 | } |
60 | 62 | ||
61 | function checkAddActivity (activity: any) { | 63 | function 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' | |||
5 | export * from './undo' | 5 | export * from './undo' |
6 | export * from './video-channels' | 6 | export * from './video-channels' |
7 | export * from './videos' | 7 | export * from './videos' |
8 | export * 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 @@ | |||
1 | import { isActivityPubUrlValid, isBaseActivityValid } from './misc' | ||
2 | |||
3 | function 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 | |||
11 | export { | ||
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 @@ | |||
1 | import { ActivityCreate, VideoChannelObject } from '../../../../shared' | 1 | import { ActivityCreate, VideoChannelObject } from '../../../../shared' |
2 | import { VideoAbuseObject } from '../../../../shared/models/activitypub/objects/video-abuse-object' | 2 | import { VideoAbuseObject } from '../../../../shared/models/activitypub/objects/video-abuse-object' |
3 | import { ViewObject } from '../../../../shared/models/activitypub/objects/view-object' | ||
3 | import { logger, retryTransactionWrapper } from '../../../helpers' | 4 | import { logger, retryTransactionWrapper } from '../../../helpers' |
4 | import { database as db } from '../../../initializers' | 5 | import { database as db } from '../../../initializers' |
5 | import { AccountInstance } from '../../../models/account/account-interface' | 6 | import { AccountInstance } from '../../../models/account/account-interface' |
6 | import { getOrCreateAccountAndServer } from '../account' | 7 | import { getOrCreateAccountAndServer } from '../account' |
8 | import { sendCreateViewToVideoFollowers } from '../send/send-create' | ||
7 | import { getVideoChannelActivityPubUrl } from '../url' | 9 | import { getVideoChannelActivityPubUrl } from '../url' |
8 | import { videoChannelActivityObjectToDBAttributes } from './misc' | 10 | import { 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 | ||
37 | async 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 | |||
33 | function processCreateVideoChannel (account: AccountInstance, videoChannelToCreateData: VideoChannelObject) { | 50 | function 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' | |||
4 | import { AccountInstance } from '../../../models/account/account-interface' | 4 | import { AccountInstance } from '../../../models/account/account-interface' |
5 | import { activitypubHttpJobScheduler } from '../../jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler' | 5 | import { activitypubHttpJobScheduler } from '../../jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler' |
6 | 6 | ||
7 | async function broadcastToFollowers (data: any, byAccount: AccountInstance, toAccountFollowers: AccountInstance[], t: Transaction) { | 7 | async 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' | |||
3 | import { AccountInstance, VideoChannelInstance, VideoInstance } from '../../../models' | 3 | import { AccountInstance, VideoChannelInstance, VideoInstance } from '../../../models' |
4 | import { VideoAbuseInstance } from '../../../models/video/video-abuse-interface' | 4 | import { VideoAbuseInstance } from '../../../models/video/video-abuse-interface' |
5 | import { broadcastToFollowers, getAudience, unicastTo } from './misc' | 5 | import { broadcastToFollowers, getAudience, unicastTo } from './misc' |
6 | import { getVideoAbuseActivityPubUrl } from '../url' | 6 | import { getVideoAbuseActivityPubUrl, getVideoViewActivityPubUrl } from '../url' |
7 | import { getServerAccount } from '../../../helpers/utils' | ||
8 | import { database as db } from '../../../initializers' | ||
7 | 9 | ||
8 | async function sendCreateVideoChannel (videoChannel: VideoChannelInstance, t: Transaction) { | 10 | async 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 | ||
17 | async function sendVideoAbuse (byAccount: AccountInstance, videoAbuse: VideoAbuseInstance, video: VideoInstance, t: Transaction) { | 19 | async 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 | |||
28 | async 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 () | 38 | async 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 | |||
57 | async function createActivityData (url: string, byAccount: AccountInstance, object: any, audience?: { to: string[], cc: string[] }) { | ||
58 | if (!audience) { | ||
59 | audience = await getAudience(byAccount) | ||
60 | } | ||
25 | 61 | ||
26 | async 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 | |||
42 | export { | 76 | export { |
43 | sendCreateVideoChannel, | 77 | sendCreateVideoChannel, |
44 | sendVideoAbuse, | 78 | sendVideoAbuse, |
45 | createActivityData | 79 | createActivityData, |
80 | sendCreateViewToOrigin, | ||
81 | sendCreateViewToVideoFollowers | ||
82 | } | ||
83 | |||
84 | // --------------------------------------------------------------------------- | ||
85 | |||
86 | function 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 | ||
24 | function getVideoViewActivityPubUrl (byAccount: AccountInstance, video: VideoInstance) { | ||
25 | return video.url + '#views/' + byAccount.uuid + '/' + new Date().toISOString() | ||
26 | } | ||
27 | |||
24 | function getAccountFollowActivityPubUrl (accountFollow: AccountFollowInstance) { | 28 | function 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 @@ | |||
1 | import { logger } from '../../../helpers' | 1 | import { logger } from '../../../helpers' |
2 | import { buildSignedActivity } from '../../../helpers/activitypub' | ||
3 | import { doRequest } from '../../../helpers/requests' | 2 | import { doRequest } from '../../../helpers/requests' |
4 | import { database as db } from '../../../initializers' | ||
5 | import { ActivityPubHttpPayload } from './activitypub-http-job-scheduler' | ||
6 | import { processActivities } from '../../activitypub/process/process' | ||
7 | import { ACTIVITY_PUB } from '../../../initializers/constants' | 3 | import { ACTIVITY_PUB } from '../../../initializers/constants' |
4 | import { processActivities } from '../../activitypub/process/process' | ||
5 | import { ActivityPubHttpPayload } from './activitypub-http-job-scheduler' | ||
8 | 6 | ||
9 | async function process (payload: ActivityPubHttpPayload, jobId: number) { | 7 | async 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 | ||
128 | export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.Instance<VideoAttributes> { | 128 | export 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, |
diff --git a/shared/models/activitypub/activity.ts b/shared/models/activitypub/activity.ts index 3d035d7d7..ce150bc12 100644 --- a/shared/models/activitypub/activity.ts +++ b/shared/models/activitypub/activity.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import { VideoChannelObject, VideoTorrentObject } from './objects' | ||
2 | import { ActivityPubSignature } from './activitypub-signature' | 1 | import { ActivityPubSignature } from './activitypub-signature' |
2 | import { VideoChannelObject, VideoTorrentObject } from './objects' | ||
3 | import { VideoAbuseObject } from './objects/video-abuse-object' | 3 | import { VideoAbuseObject } from './objects/video-abuse-object' |
4 | import { ViewObject } from './objects/view-object' | ||
4 | 5 | ||
5 | export type Activity = ActivityCreate | ActivityAdd | ActivityUpdate | | 6 | export type Activity = ActivityCreate | ActivityAdd | ActivityUpdate | |
6 | ActivityDelete | ActivityFollow | ActivityAccept | ActivityAnnounce | | 7 | ActivityDelete | ActivityFollow | ActivityAccept | ActivityAnnounce | |
@@ -20,7 +21,7 @@ export interface BaseActivity { | |||
20 | 21 | ||
21 | export interface ActivityCreate extends BaseActivity { | 22 | export interface ActivityCreate extends BaseActivity { |
22 | type: 'Create' | 23 | type: 'Create' |
23 | object: VideoChannelObject | VideoAbuseObject | 24 | object: VideoChannelObject | VideoAbuseObject | ViewObject |
24 | } | 25 | } |
25 | 26 | ||
26 | export interface ActivityAdd extends BaseActivity { | 27 | export interface ActivityAdd extends BaseActivity { |
diff --git a/shared/models/activitypub/objects/index.ts b/shared/models/activitypub/objects/index.ts index cd772b28d..d92f772e2 100644 --- a/shared/models/activitypub/objects/index.ts +++ b/shared/models/activitypub/objects/index.ts | |||
@@ -2,3 +2,4 @@ export * from './common-objects' | |||
2 | export * from './video-abuse-object' | 2 | export * from './video-abuse-object' |
3 | export * from './video-channel-object' | 3 | export * from './video-channel-object' |
4 | export * from './video-torrent-object' | 4 | export * from './video-torrent-object' |
5 | export * from './view-object' | ||
diff --git a/shared/models/activitypub/objects/view-object.ts b/shared/models/activitypub/objects/view-object.ts new file mode 100644 index 000000000..00348116a --- /dev/null +++ b/shared/models/activitypub/objects/view-object.ts | |||
@@ -0,0 +1,5 @@ | |||
1 | export interface ViewObject { | ||
2 | type: 'View', | ||
3 | actor: string | ||
4 | object: string | ||
5 | } | ||