Correctly forward like/dislikes and undo

pull/128/head
Chocobozzz 2017-11-24 13:41:10 +01:00
parent d4f1e94c89
commit 63c93323ec
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
15 changed files with 246 additions and 134 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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)
}
})
}

View File

@ -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)
}
})
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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[] }) {

View File

@ -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
}

View File

@ -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) {

View File

@ -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
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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)
}

View File

@ -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
]

View File

@ -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