mirror of https://github.com/Chocobozzz/PeerTube
Correctly forward like/dislikes and undo
parent
d4f1e94c89
commit
63c93323ec
|
@ -76,7 +76,7 @@ function addRemoteVideo (account: AccountInstance,
|
|||
if (videoChannel.Account.id !== account.id) throw new Error('Video channel is not owned by this account.')
|
||||
|
||||
const videoFromDatabase = await db.Video.loadByUUIDOrURL(videoToCreateData.uuid, videoToCreateData.id, t)
|
||||
if (videoFromDatabase) throw new Error('Video with this UUID/Url already exists.')
|
||||
if (videoFromDatabase) return videoFromDatabase
|
||||
|
||||
const videoData = await videoActivityObjectToDBAttributes(videoChannel, videoToCreateData, activity.to, activity.cc)
|
||||
const video = db.Video.build(videoData)
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { ActivityCreate, VideoChannelObject } from '../../../../shared'
|
||||
import { DislikeObject } from '../../../../shared/models/activitypub/objects/dislike-object'
|
||||
import { VideoAbuseObject } from '../../../../shared/models/activitypub/objects/video-abuse-object'
|
||||
import { ViewObject } from '../../../../shared/models/activitypub/objects/view-object'
|
||||
import { logger, retryTransactionWrapper } from '../../../helpers'
|
||||
import { database as db } from '../../../initializers'
|
||||
import { AccountInstance } from '../../../models/account/account-interface'
|
||||
import { getOrCreateAccountAndServer } from '../account'
|
||||
import { sendCreateDislikeToVideoFollowers, sendCreateViewToVideoFollowers } from '../send/send-create'
|
||||
import { forwardActivity } from '../send/misc'
|
||||
import { getVideoChannelActivityPubUrl } from '../url'
|
||||
import { videoChannelActivityObjectToDBAttributes } from './misc'
|
||||
import { DislikeObject } from '../../../../shared/models/activitypub/objects/dislike-object'
|
||||
|
||||
async function processCreateActivity (activity: ActivityCreate) {
|
||||
const activityObject = activity.object
|
||||
|
@ -16,9 +16,9 @@ async function processCreateActivity (activity: ActivityCreate) {
|
|||
const account = await getOrCreateAccountAndServer(activity.actor)
|
||||
|
||||
if (activityType === 'View') {
|
||||
return processCreateView(activityObject as ViewObject)
|
||||
return processCreateView(account, activity)
|
||||
} else if (activityType === 'Dislike') {
|
||||
return processCreateDislike(account, activityObject as DislikeObject)
|
||||
return processCreateDislike(account, activity)
|
||||
} else if (activityType === 'VideoChannel') {
|
||||
return processCreateVideoChannel(account, activityObject as VideoChannelObject)
|
||||
} else if (activityType === 'Flag') {
|
||||
|
@ -37,19 +37,20 @@ export {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function processCreateDislike (byAccount: AccountInstance, dislike: DislikeObject) {
|
||||
async function processCreateDislike (byAccount: AccountInstance, activity: ActivityCreate) {
|
||||
const options = {
|
||||
arguments: [ byAccount, dislike ],
|
||||
arguments: [ byAccount, activity ],
|
||||
errorMessage: 'Cannot dislike the video with many retries.'
|
||||
}
|
||||
|
||||
return retryTransactionWrapper(createVideoDislike, options)
|
||||
}
|
||||
|
||||
function createVideoDislike (byAccount: AccountInstance, dislike: DislikeObject) {
|
||||
return db.sequelize.transaction(async t => {
|
||||
const video = await db.Video.loadByUrlAndPopulateAccount(dislike.object)
|
||||
function createVideoDislike (byAccount: AccountInstance, activity: ActivityCreate) {
|
||||
const dislike = activity.object as DislikeObject
|
||||
|
||||
return db.sequelize.transaction(async t => {
|
||||
const video = await db.Video.loadByUrlAndPopulateAccount(dislike.object, t)
|
||||
if (!video) throw new Error('Unknown video ' + dislike.object)
|
||||
|
||||
const rate = {
|
||||
|
@ -59,15 +60,22 @@ function createVideoDislike (byAccount: AccountInstance, dislike: DislikeObject)
|
|||
}
|
||||
const [ , created ] = await db.AccountVideoRate.findOrCreate({
|
||||
where: rate,
|
||||
defaults: rate
|
||||
defaults: rate,
|
||||
transaction: t
|
||||
})
|
||||
await video.increment('dislikes')
|
||||
await video.increment('dislikes', { transaction: t })
|
||||
|
||||
if (video.isOwned() && created === true) await sendCreateDislikeToVideoFollowers(byAccount, video, undefined)
|
||||
if (video.isOwned() && created === true) {
|
||||
// Don't resend the activity to the sender
|
||||
const exceptions = [ byAccount ]
|
||||
await forwardActivity(activity, t, exceptions)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function processCreateView (view: ViewObject) {
|
||||
async function processCreateView (byAccount: AccountInstance, activity: ActivityCreate) {
|
||||
const view = activity.object as ViewObject
|
||||
|
||||
const video = await db.Video.loadByUrlAndPopulateAccount(view.object)
|
||||
|
||||
if (!video) throw new Error('Unknown video ' + view.object)
|
||||
|
@ -77,7 +85,11 @@ async function processCreateView (view: ViewObject) {
|
|||
|
||||
await video.increment('views')
|
||||
|
||||
if (video.isOwned()) await sendCreateViewToVideoFollowers(account, video, undefined)
|
||||
if (video.isOwned()) {
|
||||
// Don't resend the activity to the sender
|
||||
const exceptions = [ byAccount ]
|
||||
await forwardActivity(activity, undefined, exceptions)
|
||||
}
|
||||
}
|
||||
|
||||
function processCreateVideoChannel (account: AccountInstance, videoChannelToCreateData: VideoChannelObject) {
|
||||
|
@ -94,7 +106,7 @@ function addRemoteVideoChannel (account: AccountInstance, videoChannelToCreateDa
|
|||
|
||||
return db.sequelize.transaction(async t => {
|
||||
let videoChannel = await db.VideoChannel.loadByUUIDOrUrl(videoChannelToCreateData.uuid, videoChannelToCreateData.id, t)
|
||||
if (videoChannel) throw new Error('Video channel with this URL/UUID already exists.')
|
||||
if (videoChannel) return videoChannel
|
||||
|
||||
const videoChannelData = videoChannelActivityObjectToDBAttributes(videoChannelToCreateData, account)
|
||||
videoChannel = db.VideoChannel.build(videoChannelData)
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { ActivityLike } from '../../../../shared/models/activitypub/activity'
|
||||
import { retryTransactionWrapper } from '../../../helpers/database-utils'
|
||||
import { database as db } from '../../../initializers'
|
||||
import { AccountInstance } from '../../../models/account/account-interface'
|
||||
import { getOrCreateAccountAndServer } from '../account'
|
||||
import { sendLikeToVideoFollowers } from '../send/send-like'
|
||||
import { retryTransactionWrapper } from '../../../helpers/database-utils'
|
||||
import { forwardActivity } from '../send/misc'
|
||||
|
||||
async function processLikeActivity (activity: ActivityLike) {
|
||||
const account = await getOrCreateAccountAndServer(activity.actor)
|
||||
|
||||
return processLikeVideo(account, activity.object)
|
||||
return processLikeVideo(account, activity)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
@ -19,16 +19,18 @@ export {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function processLikeVideo (byAccount: AccountInstance, videoUrl: string) {
|
||||
async function processLikeVideo (byAccount: AccountInstance, activity: ActivityLike) {
|
||||
const options = {
|
||||
arguments: [ byAccount, videoUrl ],
|
||||
arguments: [ byAccount, activity ],
|
||||
errorMessage: 'Cannot like the video with many retries.'
|
||||
}
|
||||
|
||||
return retryTransactionWrapper(createVideoLike, options)
|
||||
}
|
||||
|
||||
function createVideoLike (byAccount: AccountInstance, videoUrl: string) {
|
||||
function createVideoLike (byAccount: AccountInstance, activity: ActivityLike) {
|
||||
const videoUrl = activity.object
|
||||
|
||||
return db.sequelize.transaction(async t => {
|
||||
const video = await db.Video.loadByUrlAndPopulateAccount(videoUrl)
|
||||
|
||||
|
@ -41,10 +43,15 @@ function createVideoLike (byAccount: AccountInstance, videoUrl: string) {
|
|||
}
|
||||
const [ , created ] = await db.AccountVideoRate.findOrCreate({
|
||||
where: rate,
|
||||
defaults: rate
|
||||
defaults: rate,
|
||||
transaction: t
|
||||
})
|
||||
await video.increment('likes')
|
||||
await video.increment('likes', { transaction: t })
|
||||
|
||||
if (video.isOwned() && created === true) await sendLikeToVideoFollowers(byAccount, video, undefined)
|
||||
if (video.isOwned() && created === true) {
|
||||
// Don't resend the activity to the sender
|
||||
const exceptions = [ byAccount ]
|
||||
await forwardActivity(activity, t, exceptions)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -3,16 +3,15 @@ import { DislikeObject } from '../../../../shared/models/activitypub/objects/dis
|
|||
import { retryTransactionWrapper } from '../../../helpers/database-utils'
|
||||
import { logger } from '../../../helpers/logger'
|
||||
import { database as db } from '../../../initializers'
|
||||
import { sendUndoDislikeToVideoFollowers } from '../index'
|
||||
import { sendUndoLikeToVideoFollowers } from '../send/send-undo'
|
||||
import { forwardActivity } from '../send/misc'
|
||||
|
||||
async function processUndoActivity (activity: ActivityUndo) {
|
||||
const activityToUndo = activity.object
|
||||
|
||||
if (activityToUndo.type === 'Like') {
|
||||
return processUndoLike(activity.actor, activityToUndo)
|
||||
return processUndoLike(activity.actor, activity)
|
||||
} else if (activityToUndo.type === 'Create' && activityToUndo.object.type === 'Dislike') {
|
||||
return processUndoDislike(activity.actor, activityToUndo.object)
|
||||
return processUndoDislike(activity.actor, activity)
|
||||
} else if (activityToUndo.type === 'Follow') {
|
||||
return processUndoFollow(activity.actor, activityToUndo)
|
||||
}
|
||||
|
@ -30,57 +29,69 @@ export {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function processUndoLike (actor: string, likeActivity: ActivityLike) {
|
||||
function processUndoLike (actor: string, activity: ActivityUndo) {
|
||||
const options = {
|
||||
arguments: [ actor, likeActivity ],
|
||||
arguments: [ actor, activity ],
|
||||
errorMessage: 'Cannot undo like with many retries.'
|
||||
}
|
||||
|
||||
return retryTransactionWrapper(undoLike, options)
|
||||
}
|
||||
|
||||
function undoLike (actor: string, likeActivity: ActivityLike) {
|
||||
function undoLike (actor: string, activity: ActivityUndo) {
|
||||
const likeActivity = activity.object as ActivityLike
|
||||
|
||||
return db.sequelize.transaction(async t => {
|
||||
const byAccount = await db.Account.loadByUrl(actor, t)
|
||||
if (!byAccount) throw new Error('Unknown account ' + actor)
|
||||
|
||||
const video = await db.Video.loadByUrlAndPopulateAccount(likeActivity.object)
|
||||
const video = await db.Video.loadByUrlAndPopulateAccount(likeActivity.object, t)
|
||||
if (!video) throw new Error('Unknown video ' + likeActivity.actor)
|
||||
|
||||
const rate = await db.AccountVideoRate.load(byAccount.id, video.id, t)
|
||||
if (!rate) throw new Error(`Unknown rate by account ${byAccount.id} for video ${video.id}.`)
|
||||
|
||||
await rate.destroy({ transaction: t })
|
||||
await video.decrement('likes')
|
||||
await video.decrement('likes', { transaction: t })
|
||||
|
||||
if (video.isOwned()) await sendUndoLikeToVideoFollowers(byAccount, video, t)
|
||||
if (video.isOwned()) {
|
||||
// Don't resend the activity to the sender
|
||||
const exceptions = [ byAccount ]
|
||||
await forwardActivity(activity, t, exceptions)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function processUndoDislike (actor: string, dislikeCreateActivity: DislikeObject) {
|
||||
function processUndoDislike (actor: string, activity: ActivityUndo) {
|
||||
const options = {
|
||||
arguments: [ actor, dislikeCreateActivity ],
|
||||
arguments: [ actor, activity ],
|
||||
errorMessage: 'Cannot undo dislike with many retries.'
|
||||
}
|
||||
|
||||
return retryTransactionWrapper(undoDislike, options)
|
||||
}
|
||||
|
||||
function undoDislike (actor: string, dislike: DislikeObject) {
|
||||
function undoDislike (actor: string, activity: ActivityUndo) {
|
||||
const dislike = activity.object.object as DislikeObject
|
||||
|
||||
return db.sequelize.transaction(async t => {
|
||||
const byAccount = await db.Account.loadByUrl(actor, t)
|
||||
if (!byAccount) throw new Error('Unknown account ' + actor)
|
||||
|
||||
const video = await db.Video.loadByUrlAndPopulateAccount(dislike.object)
|
||||
const video = await db.Video.loadByUrlAndPopulateAccount(dislike.object, t)
|
||||
if (!video) throw new Error('Unknown video ' + dislike.actor)
|
||||
|
||||
const rate = await db.AccountVideoRate.load(byAccount.id, video.id, t)
|
||||
if (!rate) throw new Error(`Unknown rate by account ${byAccount.id} for video ${video.id}.`)
|
||||
|
||||
await rate.destroy({ transaction: t })
|
||||
await video.decrement('dislikes')
|
||||
await video.decrement('dislikes', { transaction: t })
|
||||
|
||||
if (video.isOwned()) await sendUndoDislikeToVideoFollowers(byAccount, video, t)
|
||||
if (video.isOwned()) {
|
||||
// Don't resend the activity to the sender
|
||||
const exceptions = [ byAccount ]
|
||||
await forwardActivity(activity, t, exceptions)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -2,8 +2,45 @@ import { Transaction } from 'sequelize'
|
|||
import { logger } from '../../../helpers/logger'
|
||||
import { ACTIVITY_PUB, database as db } from '../../../initializers'
|
||||
import { AccountInstance } from '../../../models/account/account-interface'
|
||||
import { activitypubHttpJobScheduler } from '../../jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler'
|
||||
import {
|
||||
activitypubHttpJobScheduler,
|
||||
ActivityPubHttpPayload
|
||||
} from '../../jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler'
|
||||
import { VideoInstance } from '../../../models/video/video-interface'
|
||||
import { Activity } from '../../../../shared/models/activitypub/activity'
|
||||
|
||||
async function forwardActivity (
|
||||
activity: Activity,
|
||||
t: Transaction,
|
||||
followersException: AccountInstance[] = []
|
||||
) {
|
||||
const to = activity.to || []
|
||||
const cc = activity.cc || []
|
||||
|
||||
const followersUrls: string[] = []
|
||||
for (const dest of to.concat(cc)) {
|
||||
if (dest.endsWith('/followers')) {
|
||||
followersUrls.push(dest)
|
||||
}
|
||||
}
|
||||
|
||||
const toAccountFollowers = await db.Account.listByFollowersUrls(followersUrls)
|
||||
const uris = await computeFollowerUris(toAccountFollowers, followersException)
|
||||
|
||||
if (uris.length === 0) {
|
||||
logger.info('0 followers for %s, no forwarding.', toAccountFollowers.map(a => a.id).join(', '))
|
||||
return
|
||||
}
|
||||
|
||||
logger.debug('Creating forwarding job.', { uris })
|
||||
|
||||
const jobPayload: ActivityPubHttpPayload = {
|
||||
uris,
|
||||
body: activity
|
||||
}
|
||||
|
||||
return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpBroadcastHandler', jobPayload)
|
||||
}
|
||||
|
||||
async function broadcastToFollowers (
|
||||
data: any,
|
||||
|
@ -12,18 +49,15 @@ async function broadcastToFollowers (
|
|||
t: Transaction,
|
||||
followersException: AccountInstance[] = []
|
||||
) {
|
||||
const toAccountFollowerIds = toAccountFollowers.map(a => a.id)
|
||||
|
||||
const result = await db.AccountFollow.listAcceptedFollowerSharedInboxUrls(toAccountFollowerIds)
|
||||
if (result.data.length === 0) {
|
||||
logger.info('Not broadcast because of 0 followers for %s.', toAccountFollowerIds.join(', '))
|
||||
return undefined
|
||||
const uris = await computeFollowerUris(toAccountFollowers, followersException)
|
||||
if (uris.length === 0) {
|
||||
logger.info('0 followers for %s, no broadcasting.', toAccountFollowers.map(a => a.id).join(', '))
|
||||
return
|
||||
}
|
||||
|
||||
const followersSharedInboxException = followersException.map(f => f.sharedInboxUrl)
|
||||
const uris = result.data.filter(sharedInbox => followersSharedInboxException.indexOf(sharedInbox) === -1)
|
||||
logger.debug('Creating broadcast job.', { uris })
|
||||
|
||||
const jobPayload = {
|
||||
const jobPayload: ActivityPubHttpPayload = {
|
||||
uris,
|
||||
signatureAccountId: byAccount.id,
|
||||
body: data
|
||||
|
@ -33,7 +67,9 @@ async function broadcastToFollowers (
|
|||
}
|
||||
|
||||
async function unicastTo (data: any, byAccount: AccountInstance, toAccountUrl: string, t: Transaction) {
|
||||
const jobPayload = {
|
||||
logger.debug('Creating unicast job.', { uri: toAccountUrl })
|
||||
|
||||
const jobPayload: ActivityPubHttpPayload = {
|
||||
uris: [ toAccountUrl ],
|
||||
signatureAccountId: byAccount.id,
|
||||
body: data
|
||||
|
@ -42,21 +78,21 @@ async function unicastTo (data: any, byAccount: AccountInstance, toAccountUrl: s
|
|||
return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpUnicastHandler', jobPayload)
|
||||
}
|
||||
|
||||
function getOriginVideoAudience (video: VideoInstance) {
|
||||
function getOriginVideoAudience (video: VideoInstance, accountsInvolvedInVideo: AccountInstance[]) {
|
||||
return {
|
||||
to: [ video.VideoChannel.Account.url ],
|
||||
cc: [ video.VideoChannel.Account.url + '/followers' ]
|
||||
cc: accountsInvolvedInVideo.map(a => a.followersUrl)
|
||||
}
|
||||
}
|
||||
|
||||
function getVideoFollowersAudience (video: VideoInstance) {
|
||||
function getVideoFollowersAudience (accountsInvolvedInVideo: AccountInstance[]) {
|
||||
return {
|
||||
to: [ video.VideoChannel.Account.url + '/followers' ],
|
||||
to: accountsInvolvedInVideo.map(a => a.followersUrl),
|
||||
cc: []
|
||||
}
|
||||
}
|
||||
|
||||
async function getAccountsToForwardVideoAction (byAccount: AccountInstance, video: VideoInstance) {
|
||||
async function getAccountsInvolvedInVideo (video: VideoInstance) {
|
||||
const accountsToForwardView = await db.VideoShare.loadAccountsByShare(video.id)
|
||||
accountsToForwardView.push(video.VideoChannel.Account)
|
||||
|
||||
|
@ -81,6 +117,16 @@ async function getAudience (accountSender: AccountInstance, isPublic = true) {
|
|||
return { to, cc }
|
||||
}
|
||||
|
||||
async function computeFollowerUris (toAccountFollower: AccountInstance[], followersException: AccountInstance[]) {
|
||||
const toAccountFollowerIds = toAccountFollower.map(a => a.id)
|
||||
|
||||
const result = await db.AccountFollow.listAcceptedFollowerSharedInboxUrls(toAccountFollowerIds)
|
||||
const followersSharedInboxException = followersException.map(f => f.sharedInboxUrl)
|
||||
const uris = result.data.filter(sharedInbox => followersSharedInboxException.indexOf(sharedInbox) === -1)
|
||||
|
||||
return uris
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
|
@ -88,6 +134,7 @@ export {
|
|||
unicastTo,
|
||||
getAudience,
|
||||
getOriginVideoAudience,
|
||||
getAccountsToForwardVideoAction,
|
||||
getVideoFollowersAudience
|
||||
getAccountsInvolvedInVideo,
|
||||
getVideoFollowersAudience,
|
||||
forwardActivity
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { Transaction } from 'sequelize'
|
||||
import { ActivityCreate } from '../../../../shared/models/activitypub/activity'
|
||||
import { ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub/activity'
|
||||
import { getServerAccount } from '../../../helpers/utils'
|
||||
import { AccountInstance, VideoChannelInstance, VideoInstance } from '../../../models'
|
||||
import { VideoAbuseInstance } from '../../../models/video/video-abuse-interface'
|
||||
import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url'
|
||||
import {
|
||||
broadcastToFollowers,
|
||||
getAccountsToForwardVideoAction,
|
||||
getAccountsInvolvedInVideo,
|
||||
getAudience,
|
||||
getOriginVideoAudience,
|
||||
getVideoFollowersAudience,
|
||||
|
@ -35,7 +35,8 @@ async function sendCreateViewToOrigin (byAccount: AccountInstance, video: VideoI
|
|||
const url = getVideoViewActivityPubUrl(byAccount, video)
|
||||
const viewActivity = createViewActivityData(byAccount, video)
|
||||
|
||||
const audience = getOriginVideoAudience(video)
|
||||
const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video)
|
||||
const audience = getOriginVideoAudience(video, accountsInvolvedInVideo)
|
||||
const data = await createActivityData(url, byAccount, viewActivity, audience)
|
||||
|
||||
return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t)
|
||||
|
@ -45,12 +46,12 @@ async function sendCreateViewToVideoFollowers (byAccount: AccountInstance, video
|
|||
const url = getVideoViewActivityPubUrl(byAccount, video)
|
||||
const viewActivity = createViewActivityData(byAccount, video)
|
||||
|
||||
const audience = getVideoFollowersAudience(video)
|
||||
const accountsToForwardView = await getAccountsInvolvedInVideo(video)
|
||||
const audience = getVideoFollowersAudience(accountsToForwardView)
|
||||
const data = await createActivityData(url, byAccount, viewActivity, audience)
|
||||
|
||||
// Use the server account to send the view, because it could be an unregistered account
|
||||
const serverAccount = await getServerAccount()
|
||||
const accountsToForwardView = await getAccountsToForwardVideoAction(byAccount, video)
|
||||
|
||||
const followersException = [ byAccount ]
|
||||
return broadcastToFollowers(data, serverAccount, accountsToForwardView, t, followersException)
|
||||
}
|
||||
|
@ -59,7 +60,8 @@ async function sendCreateDislikeToOrigin (byAccount: AccountInstance, video: Vid
|
|||
const url = getVideoDislikeActivityPubUrl(byAccount, video)
|
||||
const dislikeActivity = createDislikeActivityData(byAccount, video)
|
||||
|
||||
const audience = getOriginVideoAudience(video)
|
||||
const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video)
|
||||
const audience = getOriginVideoAudience(video, accountsInvolvedInVideo)
|
||||
const data = await createActivityData(url, byAccount, dislikeActivity, audience)
|
||||
|
||||
return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t)
|
||||
|
@ -69,17 +71,15 @@ async function sendCreateDislikeToVideoFollowers (byAccount: AccountInstance, vi
|
|||
const url = getVideoDislikeActivityPubUrl(byAccount, video)
|
||||
const dislikeActivity = createDislikeActivityData(byAccount, video)
|
||||
|
||||
const audience = getVideoFollowersAudience(video)
|
||||
const accountsToForwardView = await getAccountsInvolvedInVideo(video)
|
||||
const audience = getVideoFollowersAudience(accountsToForwardView)
|
||||
const data = await createActivityData(url, byAccount, dislikeActivity, audience)
|
||||
|
||||
const accountsToForwardView = await getAccountsToForwardVideoAction(byAccount, video)
|
||||
const serverAccount = await getServerAccount()
|
||||
|
||||
const followersException = [ byAccount ]
|
||||
return broadcastToFollowers(data, serverAccount, accountsToForwardView, t, followersException)
|
||||
return broadcastToFollowers(data, byAccount, accountsToForwardView, t, followersException)
|
||||
}
|
||||
|
||||
async function createActivityData (url: string, byAccount: AccountInstance, object: any, audience?: { to: string[], cc: string[] }) {
|
||||
async function createActivityData (url: string, byAccount: AccountInstance, object: any, audience?: ActivityAudience) {
|
||||
if (!audience) {
|
||||
audience = await getAudience(byAccount)
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { Transaction } from 'sequelize'
|
||||
import { ActivityLike } from '../../../../shared/models/activitypub/activity'
|
||||
import { getServerAccount } from '../../../helpers/utils'
|
||||
import { AccountInstance, VideoInstance } from '../../../models'
|
||||
import { getVideoLikeActivityPubUrl } from '../url'
|
||||
import {
|
||||
broadcastToFollowers,
|
||||
getAccountsToForwardVideoAction,
|
||||
getAccountsInvolvedInVideo,
|
||||
getAudience,
|
||||
getOriginVideoAudience,
|
||||
getVideoFollowersAudience,
|
||||
|
@ -15,7 +14,8 @@ import {
|
|||
async function sendLikeToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) {
|
||||
const url = getVideoLikeActivityPubUrl(byAccount, video)
|
||||
|
||||
const audience = getOriginVideoAudience(video)
|
||||
const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video)
|
||||
const audience = getOriginVideoAudience(video, accountsInvolvedInVideo)
|
||||
const data = await likeActivityData(url, byAccount, video, audience)
|
||||
|
||||
return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t)
|
||||
|
@ -24,14 +24,14 @@ async function sendLikeToOrigin (byAccount: AccountInstance, video: VideoInstanc
|
|||
async function sendLikeToVideoFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) {
|
||||
const url = getVideoLikeActivityPubUrl(byAccount, video)
|
||||
|
||||
const audience = getVideoFollowersAudience(video)
|
||||
const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video)
|
||||
const audience = getVideoFollowersAudience(accountsInvolvedInVideo)
|
||||
const data = await likeActivityData(url, byAccount, video, audience)
|
||||
|
||||
const accountsToForwardView = await getAccountsToForwardVideoAction(byAccount, video)
|
||||
const serverAccount = await getServerAccount()
|
||||
const toAccountsFollowers = await getAccountsInvolvedInVideo(video)
|
||||
|
||||
const followersException = [ byAccount ]
|
||||
return broadcastToFollowers(data, serverAccount, accountsToForwardView, t, followersException)
|
||||
return broadcastToFollowers(data, byAccount, toAccountsFollowers, t, followersException)
|
||||
}
|
||||
|
||||
async function likeActivityData (url: string, byAccount: AccountInstance, video: VideoInstance, audience?: { to: string[], cc: string[] }) {
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
import { Transaction } from 'sequelize'
|
||||
import { ActivityCreate, ActivityFollow, ActivityLike, ActivityUndo } from '../../../../shared/models/activitypub/activity'
|
||||
import { getServerAccount } from '../../../helpers/utils'
|
||||
import {
|
||||
ActivityAudience,
|
||||
ActivityCreate,
|
||||
ActivityFollow,
|
||||
ActivityLike,
|
||||
ActivityUndo
|
||||
} from '../../../../shared/models/activitypub/activity'
|
||||
import { AccountInstance } from '../../../models'
|
||||
import { AccountFollowInstance } from '../../../models/account/account-follow-interface'
|
||||
import { VideoInstance } from '../../../models/video/video-interface'
|
||||
import { getAccountFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url'
|
||||
import { broadcastToFollowers, getAccountsToForwardVideoAction, unicastTo } from './misc'
|
||||
import { broadcastToFollowers, getAccountsInvolvedInVideo, getAudience, getVideoFollowersAudience, unicastTo } from './misc'
|
||||
import { createActivityData, createDislikeActivityData } from './send-create'
|
||||
import { followActivityData } from './send-follow'
|
||||
import { likeActivityData } from './send-like'
|
||||
|
@ -37,14 +42,13 @@ async function sendUndoLikeToVideoFollowers (byAccount: AccountInstance, video:
|
|||
const likeUrl = getVideoLikeActivityPubUrl(byAccount, video)
|
||||
const undoUrl = getUndoActivityPubUrl(likeUrl)
|
||||
|
||||
const toAccountsFollowers = await getAccountsInvolvedInVideo(video)
|
||||
const audience = getVideoFollowersAudience(toAccountsFollowers)
|
||||
const object = await likeActivityData(likeUrl, byAccount, video)
|
||||
const data = await undoActivityData(undoUrl, byAccount, object)
|
||||
|
||||
const accountsToForwardView = await getAccountsToForwardVideoAction(byAccount, video)
|
||||
const serverAccount = await getServerAccount()
|
||||
const data = await undoActivityData(undoUrl, byAccount, object, audience)
|
||||
|
||||
const followersException = [ byAccount ]
|
||||
return broadcastToFollowers(data, serverAccount, accountsToForwardView, t, followersException)
|
||||
return broadcastToFollowers(data, byAccount, toAccountsFollowers, t, followersException)
|
||||
}
|
||||
|
||||
async function sendUndoDislikeToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) {
|
||||
|
@ -68,11 +72,10 @@ async function sendUndoDislikeToVideoFollowers (byAccount: AccountInstance, vide
|
|||
|
||||
const data = await undoActivityData(undoUrl, byAccount, object)
|
||||
|
||||
const accountsToForwardView = await getAccountsToForwardVideoAction(byAccount, video)
|
||||
const serverAccount = await getServerAccount()
|
||||
const toAccountsFollowers = await getAccountsInvolvedInVideo(video)
|
||||
|
||||
const followersException = [ byAccount ]
|
||||
return broadcastToFollowers(data, serverAccount, accountsToForwardView, t, followersException)
|
||||
return broadcastToFollowers(data, byAccount, toAccountsFollowers, t, followersException)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
@ -87,11 +90,22 @@ export {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function undoActivityData (url: string, byAccount: AccountInstance, object: ActivityFollow | ActivityLike | ActivityCreate) {
|
||||
async function undoActivityData (
|
||||
url: string,
|
||||
byAccount: AccountInstance,
|
||||
object: ActivityFollow | ActivityLike | ActivityCreate,
|
||||
audience?: ActivityAudience
|
||||
) {
|
||||
if (!audience) {
|
||||
audience = await getAudience(byAccount)
|
||||
}
|
||||
|
||||
const activity: ActivityUndo = {
|
||||
type: 'Undo',
|
||||
id: url,
|
||||
actor: byAccount.url,
|
||||
to: audience.to,
|
||||
cc: audience.cc,
|
||||
object
|
||||
}
|
||||
|
||||
|
|
|
@ -1,21 +1,16 @@
|
|||
import { logger } from '../../../helpers'
|
||||
import { buildSignedActivity } from '../../../helpers/activitypub'
|
||||
import { doRequest } from '../../../helpers/requests'
|
||||
import { database as db } from '../../../initializers'
|
||||
import { ActivityPubHttpPayload, maybeRetryRequestLater } from './activitypub-http-job-scheduler'
|
||||
import { ActivityPubHttpPayload, computeBody, maybeRetryRequestLater } from './activitypub-http-job-scheduler'
|
||||
|
||||
async function process (payload: ActivityPubHttpPayload, jobId: number) {
|
||||
logger.info('Processing ActivityPub broadcast in job %d.', jobId)
|
||||
|
||||
const accountSignature = await db.Account.load(payload.signatureAccountId)
|
||||
if (!accountSignature) throw new Error('Unknown signature account id.')
|
||||
|
||||
const signedBody = await buildSignedActivity(accountSignature, payload.body)
|
||||
const body = await computeBody(payload)
|
||||
|
||||
const options = {
|
||||
method: 'POST',
|
||||
uri: '',
|
||||
json: signedBody
|
||||
json: body
|
||||
}
|
||||
|
||||
for (const uri of payload.uris) {
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import { JobScheduler, JobHandler } from '../job-scheduler'
|
||||
import { JobCategory } from '../../../../shared'
|
||||
import { buildSignedActivity } from '../../../helpers/activitypub'
|
||||
import { logger } from '../../../helpers/logger'
|
||||
import { ACTIVITY_PUB } from '../../../initializers/constants'
|
||||
import { database as db } from '../../../initializers/database'
|
||||
import { JobHandler, JobScheduler } from '../job-scheduler'
|
||||
|
||||
import * as activitypubHttpBroadcastHandler from './activitypub-http-broadcast-handler'
|
||||
import * as activitypubHttpUnicastHandler from './activitypub-http-unicast-handler'
|
||||
import * as activitypubHttpFetcherHandler from './activitypub-http-fetcher-handler'
|
||||
import { JobCategory } from '../../../../shared'
|
||||
import { ACTIVITY_PUB } from '../../../initializers/constants'
|
||||
import { logger } from '../../../helpers/logger'
|
||||
import * as activitypubHttpUnicastHandler from './activitypub-http-unicast-handler'
|
||||
|
||||
type ActivityPubHttpPayload = {
|
||||
uris: string[]
|
||||
|
@ -40,8 +42,21 @@ function maybeRetryRequestLater (err: Error, payload: ActivityPubHttpPayload, ur
|
|||
}
|
||||
}
|
||||
|
||||
async function computeBody (payload: ActivityPubHttpPayload) {
|
||||
let body = payload.body
|
||||
|
||||
if (payload.signatureAccountId) {
|
||||
const accountSignature = await db.Account.load(payload.signatureAccountId)
|
||||
if (!accountSignature) throw new Error('Unknown signature account id.')
|
||||
body = await buildSignedActivity(accountSignature, payload.body)
|
||||
}
|
||||
|
||||
return body
|
||||
}
|
||||
|
||||
export {
|
||||
ActivityPubHttpPayload,
|
||||
activitypubHttpJobScheduler,
|
||||
maybeRetryRequestLater
|
||||
maybeRetryRequestLater,
|
||||
computeBody
|
||||
}
|
||||
|
|
|
@ -1,21 +1,17 @@
|
|||
import { logger } from '../../../helpers'
|
||||
import { doRequest } from '../../../helpers/requests'
|
||||
import { ActivityPubHttpPayload, maybeRetryRequestLater } from './activitypub-http-job-scheduler'
|
||||
import { database as db } from '../../../initializers/database'
|
||||
import { buildSignedActivity } from '../../../helpers/activitypub'
|
||||
import { ActivityPubHttpPayload, computeBody, maybeRetryRequestLater } from './activitypub-http-job-scheduler'
|
||||
|
||||
async function process (payload: ActivityPubHttpPayload, jobId: number) {
|
||||
logger.info('Processing ActivityPub unicast in job %d.', jobId)
|
||||
|
||||
const accountSignature = await db.Account.load(payload.signatureAccountId)
|
||||
if (!accountSignature) throw new Error('Unknown signature account id.')
|
||||
const body = await computeBody(payload)
|
||||
|
||||
const signedBody = await buildSignedActivity(accountSignature, payload.body)
|
||||
const uri = payload.uris[0]
|
||||
const options = {
|
||||
method: 'POST',
|
||||
uri,
|
||||
json: signedBody
|
||||
json: body
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
|
@ -12,6 +12,7 @@ export namespace AccountMethods {
|
|||
export type LoadByUrl = (url: string, transaction?: Sequelize.Transaction) => Bluebird<AccountInstance>
|
||||
export type LoadLocalByName = (name: string) => Bluebird<AccountInstance>
|
||||
export type LoadByNameAndHost = (name: string, host: string) => Bluebird<AccountInstance>
|
||||
export type ListByFollowersUrls = (followerUrls: string[], transaction?: Sequelize.Transaction) => Bluebird<AccountInstance[]>
|
||||
|
||||
export type ToActivityPubObject = (this: AccountInstance) => ActivityPubActor
|
||||
export type ToFormattedJSON = (this: AccountInstance) => FormattedAccount
|
||||
|
@ -29,6 +30,7 @@ export interface AccountClass {
|
|||
loadByUrl: AccountMethods.LoadByUrl
|
||||
loadLocalByName: AccountMethods.LoadLocalByName
|
||||
loadByNameAndHost: AccountMethods.LoadByNameAndHost
|
||||
listByFollowersUrls: AccountMethods.ListByFollowersUrls
|
||||
}
|
||||
|
||||
export interface AccountAttributes {
|
||||
|
|
|
@ -26,6 +26,7 @@ let loadByUUID: AccountMethods.LoadByUUID
|
|||
let loadByUrl: AccountMethods.LoadByUrl
|
||||
let loadLocalByName: AccountMethods.LoadLocalByName
|
||||
let loadByNameAndHost: AccountMethods.LoadByNameAndHost
|
||||
let listByFollowersUrls: AccountMethods.ListByFollowersUrls
|
||||
let isOwned: AccountMethods.IsOwned
|
||||
let toActivityPubObject: AccountMethods.ToActivityPubObject
|
||||
let toFormattedJSON: AccountMethods.ToFormattedJSON
|
||||
|
@ -188,7 +189,8 @@ export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes
|
|||
loadByUUID,
|
||||
loadByUrl,
|
||||
loadLocalByName,
|
||||
loadByNameAndHost
|
||||
loadByNameAndHost,
|
||||
listByFollowersUrls
|
||||
]
|
||||
const instanceMethods = [
|
||||
isOwned,
|
||||
|
@ -427,3 +429,16 @@ loadByUrl = function (url: string, transaction?: Sequelize.Transaction) {
|
|||
|
||||
return Account.findOne(query)
|
||||
}
|
||||
|
||||
listByFollowersUrls = function (followersUrls: string[], transaction?: Sequelize.Transaction) {
|
||||
const query: Sequelize.FindOptions<AccountAttributes> = {
|
||||
where: {
|
||||
followersUrl: {
|
||||
[Sequelize.Op.in]: followersUrls
|
||||
}
|
||||
},
|
||||
transaction
|
||||
}
|
||||
|
||||
return Account.findAll(query)
|
||||
}
|
||||
|
|
|
@ -6,27 +6,8 @@ import { join } from 'path'
|
|||
import * as Sequelize from 'sequelize'
|
||||
import { VideoPrivacy, VideoResolution } from '../../../shared'
|
||||
import { VideoTorrentObject } from '../../../shared/models/activitypub/objects/video-torrent-object'
|
||||
import {
|
||||
createTorrentPromise,
|
||||
generateImageFromVideoFile,
|
||||
getVideoFileHeight,
|
||||
isVideoCategoryValid,
|
||||
isVideoDescriptionValid,
|
||||
isVideoDurationValid,
|
||||
isVideoLanguageValid,
|
||||
isVideoLicenceValid,
|
||||
isVideoNameValid,
|
||||
isVideoNSFWValid,
|
||||
isVideoPrivacyValid,
|
||||
logger,
|
||||
renamePromise,
|
||||
statPromise,
|
||||
transcode,
|
||||
unlinkPromise,
|
||||
writeFilePromise
|
||||
} from '../../helpers'
|
||||
import { activityPubCollection } from '../../helpers/activitypub'
|
||||
import { isVideoUrlValid } from '../../helpers/custom-validators/videos'
|
||||
import { isVideoCategoryValid, isVideoLanguageValid, isVideoPrivacyValid, isVideoUrlValid } from '../../helpers/custom-validators/videos'
|
||||
import {
|
||||
API_VERSION,
|
||||
CONFIG,
|
||||
|
@ -39,7 +20,7 @@ import {
|
|||
VIDEO_LANGUAGES,
|
||||
VIDEO_LICENCES,
|
||||
VIDEO_PRIVACIES
|
||||
} from '../../initializers'
|
||||
} from '../../initializers/constants'
|
||||
import { sendDeleteVideo } from '../../lib/index'
|
||||
|
||||
import { addMethodsToModel, getSort } from '../utils'
|
||||
|
@ -47,6 +28,10 @@ import { addMethodsToModel, getSort } from '../utils'
|
|||
import { TagInstance } from './tag-interface'
|
||||
import { VideoFileInstance, VideoFileModel } from './video-file-interface'
|
||||
import { VideoAttributes, VideoInstance, VideoMethods } from './video-interface'
|
||||
import { isVideoNameValid, isVideoLicenceValid, isVideoNSFWValid, isVideoDescriptionValid, isVideoDurationValid } from '../../helpers/index'
|
||||
import { logger } from '../../helpers/logger'
|
||||
import { generateImageFromVideoFile, transcode, getVideoFileHeight } from '../../helpers/ffmpeg-utils'
|
||||
import { createTorrentPromise, writeFilePromise, unlinkPromise, renamePromise, statPromise } from '../../helpers/core-utils'
|
||||
|
||||
let Video: Sequelize.Model<VideoInstance, VideoAttributes>
|
||||
let getOriginalFile: VideoMethods.GetOriginalFile
|
||||
|
@ -1013,6 +998,10 @@ loadAndPopulateAccountAndServerAndTags = function (id: number) {
|
|||
model: Video['sequelize'].models.AccountVideoRate,
|
||||
include: [ Video['sequelize'].models.Account ]
|
||||
},
|
||||
{
|
||||
model: Video['sequelize'].models.VideoShare,
|
||||
include: [ Video['sequelize'].models.Account ]
|
||||
},
|
||||
Video['sequelize'].models.Tag,
|
||||
Video['sequelize'].models.VideoFile
|
||||
]
|
||||
|
@ -1040,6 +1029,10 @@ loadByUUIDAndPopulateAccountAndServerAndTags = function (uuid: string) {
|
|||
model: Video['sequelize'].models.AccountVideoRate,
|
||||
include: [ Video['sequelize'].models.Account ]
|
||||
},
|
||||
{
|
||||
model: Video['sequelize'].models.VideoShare,
|
||||
include: [ Video['sequelize'].models.Account ]
|
||||
},
|
||||
Video['sequelize'].models.Tag,
|
||||
Video['sequelize'].models.VideoFile
|
||||
]
|
||||
|
|
|
@ -10,6 +10,11 @@ export type Activity = ActivityCreate | ActivityAdd | ActivityUpdate |
|
|||
|
||||
export type ActivityType = 'Create' | 'Add' | 'Update' | 'Delete' | 'Follow' | 'Accept' | 'Announce' | 'Undo' | 'Like'
|
||||
|
||||
export interface ActivityAudience {
|
||||
to: string[]
|
||||
cc: string[]
|
||||
}
|
||||
|
||||
export interface BaseActivity {
|
||||
'@context'?: any[]
|
||||
id: string
|
||||
|
|
Loading…
Reference in New Issue