import { pageToStartAndCount } from '../../helpers/core-utils'
import { ACTIVITY_PUB, CONFIG, ROUTE_CACHE_LIFETIME } from '../../initializers'
import { buildVideoAnnounce } from '../../lib/activitypub/send'
-import { audiencify, getAudience } from '../../lib/activitypub/send/misc'
+import { audiencify, getAudience } from '../../lib/activitypub/audience'
import { createActivityData } from '../../lib/activitypub/send/send-create'
import { asyncMiddleware, executeIfActivityPub, localAccountValidator } from '../../middlewares'
import { videoChannelsGetValidator, videosGetValidator, videosShareValidator } from '../../middlewares/validators'
import { logger } from '../../helpers/logger'
import { ACTIVITY_PUB } from '../../initializers/constants'
import { announceActivityData, createActivityData } from '../../lib/activitypub/send'
-import { buildAudience } from '../../lib/activitypub/send/misc'
+import { buildAudience } from '../../lib/activitypub/audience'
import { asyncMiddleware, localAccountValidator } from '../../middlewares'
import { AccountModel } from '../../models/account/account'
import { ActorModel } from '../../models/activitypub/actor'
--- /dev/null
+import { Transaction } from 'sequelize'
+import { ActivityAudience } from '../../../shared/models/activitypub'
+import { ACTIVITY_PUB } from '../../initializers'
+import { ActorModel } from '../../models/activitypub/actor'
+import { VideoModel } from '../../models/video/video'
+import { VideoCommentModel } from '../../models/video/video-comment'
+import { VideoShareModel } from '../../models/video/video-share'
+
+function getVideoAudience (video: VideoModel, actorsInvolvedInVideo: ActorModel[]) {
+ return {
+ to: [ video.VideoChannel.Account.Actor.url ],
+ cc: actorsInvolvedInVideo.map(a => a.followersUrl)
+ }
+}
+
+function getVideoCommentAudience (
+ videoComment: VideoCommentModel,
+ threadParentComments: VideoCommentModel[],
+ actorsInvolvedInVideo: ActorModel[],
+ isOrigin = false
+) {
+ const to = [ ACTIVITY_PUB.PUBLIC ]
+ const cc = [ ]
+
+ // Owner of the video we comment
+ if (isOrigin === false) {
+ cc.push(videoComment.Video.VideoChannel.Account.Actor.url)
+ }
+
+ // Followers of the poster
+ cc.push(videoComment.Account.Actor.followersUrl)
+
+ // Send to actors we reply to
+ for (const parentComment of threadParentComments) {
+ cc.push(parentComment.Account.Actor.url)
+ }
+
+ return {
+ to,
+ cc: cc.concat(actorsInvolvedInVideo.map(a => a.followersUrl))
+ }
+}
+
+function getObjectFollowersAudience (actorsInvolvedInObject: ActorModel[]) {
+ return {
+ to: [ ACTIVITY_PUB.PUBLIC ].concat(actorsInvolvedInObject.map(a => a.followersUrl)),
+ cc: []
+ }
+}
+
+async function getActorsInvolvedInVideo (video: VideoModel, t: Transaction) {
+ const actors = await VideoShareModel.loadActorsByShare(video.id, t)
+ actors.push(video.VideoChannel.Account.Actor)
+
+ return actors
+}
+
+async function getAudience (actorSender: ActorModel, t: Transaction, isPublic = true) {
+ return buildAudience([ actorSender.followersUrl ], isPublic)
+}
+
+function buildAudience (followerInboxUrls: string[], isPublic = true) {
+ // Thanks Mastodon: https://github.com/tootsuite/mastodon/blob/master/app/lib/activitypub/tag_manager.rb#L47
+ let to = []
+ let cc = []
+
+ if (isPublic) {
+ to = [ ACTIVITY_PUB.PUBLIC ]
+ cc = followerInboxUrls
+ } else { // Unlisted
+ to = [ ]
+ cc = [ ]
+ }
+
+ return { to, cc }
+}
+
+function audiencify <T> (object: T, audience: ActivityAudience) {
+ return Object.assign(object, audience)
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ buildAudience,
+ getAudience,
+ getVideoAudience,
+ getActorsInvolvedInVideo,
+ getObjectFollowersAudience,
+ audiencify,
+ getVideoCommentAudience
+}
export * from './process'
export * from './send'
export * from './actor'
-export * from './fetch'
export * from './share'
export * from './videos'
export * from './video-comments'
import { VideoModel } from '../../../models/video/video'
import { VideoShareModel } from '../../../models/video/video-share'
import { getOrCreateActorAndServerAndModel } from '../actor'
-import { forwardActivity } from '../send/misc'
+import { forwardActivity } from '../send/utils'
import { getOrCreateAccountAndVideoAndChannel } from '../videos'
async function processAnnounceActivity (activity: ActivityAnnounce) {
import { VideoAbuseModel } from '../../../models/video/video-abuse'
import { VideoCommentModel } from '../../../models/video/video-comment'
import { getOrCreateActorAndServerAndModel } from '../actor'
-import { forwardActivity, getActorsInvolvedInVideo } from '../send/misc'
+import { getActorsInvolvedInVideo } from '../audience'
import { resolveThread } from '../video-comments'
import { getOrCreateAccountAndVideoAndChannel } from '../videos'
+import { forwardActivity } from '../send/utils'
async function processCreateActivity (activity: ActivityCreate) {
const activityObject = activity.object
import { VideoChannelModel } from '../../../models/video/video-channel'
import { VideoCommentModel } from '../../../models/video/video-comment'
import { getOrCreateActorAndServerAndModel } from '../actor'
-import { forwardActivity } from '../send/misc'
+import { forwardActivity } from '../send/utils'
async function processDeleteActivity (activity: ActivityDelete) {
const objectUrl = typeof activity.object === 'string' ? activity.object : activity.object.id
import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
import { ActorModel } from '../../../models/activitypub/actor'
import { getOrCreateActorAndServerAndModel } from '../actor'
-import { forwardActivity } from '../send/misc'
+import { forwardActivity } from '../send/utils'
import { getOrCreateAccountAndVideoAndChannel } from '../videos'
async function processLikeActivity (activity: ActivityLike) {
import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
import { ActorModel } from '../../../models/activitypub/actor'
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
-import { forwardActivity } from '../send/misc'
+import { forwardActivity } from '../send/utils'
import { getOrCreateAccountAndVideoAndChannel } from '../videos'
import { VideoShareModel } from '../../../models/video/video-share'
import { fetchAvatarIfExists, getOrCreateActorAndServerAndModel, updateActorAvatarInstance, updateActorInstance } from '../actor'
import {
generateThumbnailFromUrl,
- getOrCreateAccountAndVideoAndChannel, getOrCreateVideoChannel,
+ getOrCreateAccountAndVideoAndChannel,
+ getOrCreateVideoChannel,
videoActivityObjectToDBAttributes,
videoFileActivityUrlToDBAttributes
} from '../videos'
+++ /dev/null
-import { Transaction } from 'sequelize'
-import { Activity, ActivityAudience } from '../../../../shared/models/activitypub'
-import { logger } from '../../../helpers/logger'
-import { ACTIVITY_PUB } from '../../../initializers'
-import { ActorModel } from '../../../models/activitypub/actor'
-import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
-import { VideoModel } from '../../../models/video/video'
-import { VideoCommentModel } from '../../../models/video/video-comment'
-import { VideoShareModel } from '../../../models/video/video-share'
-import { JobQueue } from '../../job-queue'
-
-async function forwardActivity (
- activity: Activity,
- t: Transaction,
- followersException: ActorModel[] = [],
- additionalFollowerUrls: string[] = []
-) {
- const to = activity.to || []
- const cc = activity.cc || []
-
- const followersUrls = additionalFollowerUrls
- for (const dest of to.concat(cc)) {
- if (dest.endsWith('/followers')) {
- followersUrls.push(dest)
- }
- }
-
- const toActorFollowers = await ActorModel.listByFollowersUrls(followersUrls, t)
- const uris = await computeFollowerUris(toActorFollowers, followersException, t)
-
- if (uris.length === 0) {
- logger.info('0 followers for %s, no forwarding.', toActorFollowers.map(a => a.id).join(', '))
- return undefined
- }
-
- logger.debug('Creating forwarding job.', { uris })
-
- const payload = {
- uris,
- body: activity
- }
- return JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload })
-}
-
-async function broadcastToFollowers (
- data: any,
- byActor: ActorModel,
- toActorFollowers: ActorModel[],
- t: Transaction,
- actorsException: ActorModel[] = []
-) {
- const uris = await computeFollowerUris(toActorFollowers, actorsException, t)
- return broadcastTo(uris, data, byActor)
-}
-
-async function broadcastToActors (
- data: any,
- byActor: ActorModel,
- toActors: ActorModel[],
- actorsException: ActorModel[] = []
-) {
- const uris = await computeUris(toActors, actorsException)
- return broadcastTo(uris, data, byActor)
-}
-
-async function broadcastTo (uris: string[], data: any, byActor: ActorModel) {
- if (uris.length === 0) return undefined
-
- logger.debug('Creating broadcast job.', { uris })
-
- const payload = {
- uris,
- signatureActorId: byActor.id,
- body: data
- }
-
- return JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload })
-}
-
-async function unicastTo (data: any, byActor: ActorModel, toActorUrl: string) {
- logger.debug('Creating unicast job.', { uri: toActorUrl })
-
- const payload = {
- uri: toActorUrl,
- signatureActorId: byActor.id,
- body: data
- }
-
- return JobQueue.Instance.createJob({ type: 'activitypub-http-unicast', payload })
-}
-
-function getOriginVideoAudience (video: VideoModel, actorsInvolvedInVideo: ActorModel[]) {
- return {
- to: [ video.VideoChannel.Account.Actor.url ],
- cc: actorsInvolvedInVideo.map(a => a.followersUrl)
- }
-}
-
-function getVideoCommentAudience (
- videoComment: VideoCommentModel,
- threadParentComments: VideoCommentModel[],
- actorsInvolvedInVideo: ActorModel[],
- isOrigin = false
-) {
- const to = [ ACTIVITY_PUB.PUBLIC ]
- const cc = [ ]
-
- // Owner of the video we comment
- if (isOrigin === false) {
- cc.push(videoComment.Video.VideoChannel.Account.Actor.url)
- }
-
- // Followers of the poster
- cc.push(videoComment.Account.Actor.followersUrl)
-
- // Send to actors we reply to
- for (const parentComment of threadParentComments) {
- cc.push(parentComment.Account.Actor.url)
- }
-
- return {
- to,
- cc: cc.concat(actorsInvolvedInVideo.map(a => a.followersUrl))
- }
-}
-
-function getObjectFollowersAudience (actorsInvolvedInObject: ActorModel[]) {
- return {
- to: [ ACTIVITY_PUB.PUBLIC ].concat(actorsInvolvedInObject.map(a => a.followersUrl)),
- cc: []
- }
-}
-
-async function getActorsInvolvedInVideo (video: VideoModel, t: Transaction) {
- const actors = await VideoShareModel.loadActorsByShare(video.id, t)
- actors.push(video.VideoChannel.Account.Actor)
-
- return actors
-}
-
-async function getAudience (actorSender: ActorModel, t: Transaction, isPublic = true) {
- return buildAudience([ actorSender.followersUrl ], isPublic)
-}
-
-function buildAudience (followerInboxUrls: string[], isPublic = true) {
- // Thanks Mastodon: https://github.com/tootsuite/mastodon/blob/master/app/lib/activitypub/tag_manager.rb#L47
- let to = []
- let cc = []
-
- if (isPublic) {
- to = [ ACTIVITY_PUB.PUBLIC ]
- cc = followerInboxUrls
- } else { // Unlisted
- to = [ ]
- cc = [ ]
- }
-
- return { to, cc }
-}
-
-function audiencify <T> (object: T, audience: ActivityAudience) {
- return Object.assign(object, audience)
-}
-
-async function computeFollowerUris (toActorFollower: ActorModel[], actorsException: ActorModel[], t: Transaction) {
- const toActorFollowerIds = toActorFollower.map(a => a.id)
-
- const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t)
- const sharedInboxesException = actorsException.map(f => f.sharedInboxUrl || f.inboxUrl)
- return result.data.filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1)
-}
-
-async function computeUris (toActors: ActorModel[], actorsException: ActorModel[] = []) {
- const toActorSharedInboxesSet = new Set(toActors.map(a => a.sharedInboxUrl || a.inboxUrl))
-
- const sharedInboxesException = actorsException.map(f => f.sharedInboxUrl || f.inboxUrl)
- return Array.from(toActorSharedInboxesSet)
- .filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1)
-}
-
-// ---------------------------------------------------------------------------
-
-export {
- broadcastToFollowers,
- unicastTo,
- buildAudience,
- getAudience,
- getOriginVideoAudience,
- getActorsInvolvedInVideo,
- getObjectFollowersAudience,
- forwardActivity,
- audiencify,
- getVideoCommentAudience,
- computeUris,
- broadcastToActors
-}
import { ActorModel } from '../../../models/activitypub/actor'
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
import { getActorFollowAcceptActivityPubUrl, getActorFollowActivityPubUrl } from '../url'
-import { unicastTo } from './misc'
+import { unicastTo } from './utils'
import { followActivityData } from './send-follow'
async function sendAccept (actorFollow: ActorFollowModel) {
import { ActorModel } from '../../../models/activitypub/actor'
import { VideoModel } from '../../../models/video/video'
import { VideoShareModel } from '../../../models/video/video-share'
-import { broadcastToFollowers, getActorsInvolvedInVideo, getAudience, getObjectFollowersAudience } from './misc'
+import { broadcastToFollowers } from './utils'
+import { getActorsInvolvedInVideo, getAudience, getObjectFollowersAudience } from '../audience'
async function buildVideoAnnounce (byActor: ActorModel, videoShare: VideoShareModel, video: VideoModel, t: Transaction) {
const announcedObject = video.url
import { VideoAbuseModel } from '../../../models/video/video-abuse'
import { VideoCommentModel } from '../../../models/video/video-comment'
import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url'
+import { broadcastToActors, broadcastToFollowers, unicastTo } from './utils'
import {
audiencify,
- broadcastToActors,
- broadcastToFollowers,
getActorsInvolvedInVideo,
getAudience,
getObjectFollowersAudience,
- getOriginVideoAudience,
- getVideoCommentAudience,
- unicastTo
-} from './misc'
+ getVideoAudience,
+ getVideoCommentAudience
+} from '../audience'
async function sendCreateVideo (video: VideoModel, t: Transaction) {
if (video.privacy === VideoPrivacy.PRIVATE) return undefined
// Send to origin
if (video.isOwned() === false) {
- const audience = getOriginVideoAudience(video, actorsInvolvedInVideo)
+ const audience = getVideoAudience(video, actorsInvolvedInVideo)
const data = await createActivityData(url, byActor, viewActivityData, t, audience)
return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
// Send to origin
if (video.isOwned() === false) {
- const audience = getOriginVideoAudience(video, actorsInvolvedInVideo)
+ const audience = getVideoAudience(video, actorsInvolvedInVideo)
const data = await createActivityData(url, byActor, dislikeActivityData, t, audience)
return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
import { VideoCommentModel } from '../../../models/video/video-comment'
import { VideoShareModel } from '../../../models/video/video-share'
import { getDeleteActivityPubUrl } from '../url'
-import { audiencify, broadcastToActors, broadcastToFollowers, getActorsInvolvedInVideo, getVideoCommentAudience, unicastTo } from './misc'
+import { broadcastToActors, broadcastToFollowers, unicastTo } from './utils'
+import { audiencify, getActorsInvolvedInVideo, getVideoCommentAudience } from '../audience'
async function sendDeleteVideo (video: VideoModel, t: Transaction) {
const url = getDeleteActivityPubUrl(video.url)
import { ActorModel } from '../../../models/activitypub/actor'
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
import { getActorFollowActivityPubUrl } from '../url'
-import { unicastTo } from './misc'
+import { unicastTo } from './utils'
function sendFollow (actorFollow: ActorFollowModel) {
const me = actorFollow.ActorFollower
import { ActorModel } from '../../../models/activitypub/actor'
import { VideoModel } from '../../../models/video/video'
import { getVideoLikeActivityPubUrl } from '../url'
-import {
- audiencify,
- broadcastToFollowers,
- getActorsInvolvedInVideo,
- getAudience,
- getObjectFollowersAudience,
- getOriginVideoAudience,
- unicastTo
-} from './misc'
+import { broadcastToFollowers, unicastTo } from './utils'
+import { audiencify, getActorsInvolvedInVideo, getAudience, getObjectFollowersAudience, getVideoAudience } from '../audience'
async function sendLike (byActor: ActorModel, video: VideoModel, t: Transaction) {
const url = getVideoLikeActivityPubUrl(byActor, video)
// Send to origin
if (video.isOwned() === false) {
- const audience = getOriginVideoAudience(video, accountsInvolvedInVideo)
+ const audience = getVideoAudience(video, accountsInvolvedInVideo)
const data = await likeActivityData(url, byActor, video, t, audience)
return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
import { VideoModel } from '../../../models/video/video'
import { getActorFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url'
-import {
- audiencify,
- broadcastToFollowers,
- getActorsInvolvedInVideo,
- getAudience,
- getObjectFollowersAudience,
- getOriginVideoAudience,
- unicastTo
-} from './misc'
+import { broadcastToFollowers, unicastTo } from './utils'
+import { audiencify, getActorsInvolvedInVideo, getAudience, getObjectFollowersAudience, getVideoAudience } from '../audience'
import { createActivityData, createDislikeActivityData } from './send-create'
import { followActivityData } from './send-follow'
import { likeActivityData } from './send-like'
// Send to origin
if (video.isOwned() === false) {
- const audience = getOriginVideoAudience(video, actorsInvolvedInVideo)
+ const audience = getVideoAudience(video, actorsInvolvedInVideo)
const data = await undoActivityData(undoUrl, byActor, object, t, audience)
return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
const object = await createActivityData(dislikeUrl, byActor, dislikeActivity, t)
if (video.isOwned() === false) {
- const audience = getOriginVideoAudience(video, actorsInvolvedInVideo)
+ const audience = getVideoAudience(video, actorsInvolvedInVideo)
const data = await undoActivityData(undoUrl, byActor, object, t, audience)
return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
import { VideoChannelModel } from '../../../models/video/video-channel'
import { VideoShareModel } from '../../../models/video/video-share'
import { getUpdateActivityPubUrl } from '../url'
-import { audiencify, broadcastToFollowers, getAudience } from './misc'
+import { broadcastToFollowers } from './utils'
+import { audiencify, getAudience } from '../audience'
async function sendUpdateVideo (video: VideoModel, t: Transaction) {
const byActor = video.VideoChannel.Account.Actor
--- /dev/null
+import { Transaction } from 'sequelize'
+import { Activity } from '../../../../shared/models/activitypub'
+import { logger } from '../../../helpers/logger'
+import { ActorModel } from '../../../models/activitypub/actor'
+import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
+import { JobQueue } from '../../job-queue'
+
+async function forwardActivity (
+ activity: Activity,
+ t: Transaction,
+ followersException: ActorModel[] = [],
+ additionalFollowerUrls: string[] = []
+) {
+ const to = activity.to || []
+ const cc = activity.cc || []
+
+ const followersUrls = additionalFollowerUrls
+ for (const dest of to.concat(cc)) {
+ if (dest.endsWith('/followers')) {
+ followersUrls.push(dest)
+ }
+ }
+
+ const toActorFollowers = await ActorModel.listByFollowersUrls(followersUrls, t)
+ const uris = await computeFollowerUris(toActorFollowers, followersException, t)
+
+ if (uris.length === 0) {
+ logger.info('0 followers for %s, no forwarding.', toActorFollowers.map(a => a.id).join(', '))
+ return undefined
+ }
+
+ logger.debug('Creating forwarding job.', { uris })
+
+ const payload = {
+ uris,
+ body: activity
+ }
+ return JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload })
+}
+
+async function broadcastToFollowers (
+ data: any,
+ byActor: ActorModel,
+ toActorFollowers: ActorModel[],
+ t: Transaction,
+ actorsException: ActorModel[] = []
+) {
+ const uris = await computeFollowerUris(toActorFollowers, actorsException, t)
+ return broadcastTo(uris, data, byActor)
+}
+
+async function broadcastToActors (
+ data: any,
+ byActor: ActorModel,
+ toActors: ActorModel[],
+ actorsException: ActorModel[] = []
+) {
+ const uris = await computeUris(toActors, actorsException)
+ return broadcastTo(uris, data, byActor)
+}
+
+async function broadcastTo (uris: string[], data: any, byActor: ActorModel) {
+ if (uris.length === 0) return undefined
+
+ logger.debug('Creating broadcast job.', { uris })
+
+ const payload = {
+ uris,
+ signatureActorId: byActor.id,
+ body: data
+ }
+
+ return JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload })
+}
+
+async function unicastTo (data: any, byActor: ActorModel, toActorUrl: string) {
+ logger.debug('Creating unicast job.', { uri: toActorUrl })
+
+ const payload = {
+ uri: toActorUrl,
+ signatureActorId: byActor.id,
+ body: data
+ }
+
+ return JobQueue.Instance.createJob({ type: 'activitypub-http-unicast', payload })
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ broadcastToFollowers,
+ unicastTo,
+ forwardActivity,
+ broadcastToActors
+}
+
+// ---------------------------------------------------------------------------
+
+async function computeFollowerUris (toActorFollower: ActorModel[], actorsException: ActorModel[], t: Transaction) {
+ const toActorFollowerIds = toActorFollower.map(a => a.id)
+
+ const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t)
+ const sharedInboxesException = actorsException.map(f => f.sharedInboxUrl || f.inboxUrl)
+ return result.data.filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1)
+}
+
+async function computeUris (toActors: ActorModel[], actorsException: ActorModel[] = []) {
+ const toActorSharedInboxesSet = new Set(toActors.map(a => a.sharedInboxUrl || a.inboxUrl))
+
+ const sharedInboxesException = actorsException.map(f => f.sharedInboxUrl || f.inboxUrl)
+ return Array.from(toActorSharedInboxesSet)
+ .filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1)
+}