Federate likes/dislikes
This commit is contained in:
parent
d52eb8f656
commit
0032ebe94a
|
@ -3,6 +3,7 @@ import { UserVideoRateUpdate } from '../../../../shared'
|
||||||
import { logger, retryTransactionWrapper } from '../../../helpers'
|
import { logger, retryTransactionWrapper } from '../../../helpers'
|
||||||
import { VIDEO_RATE_TYPES } from '../../../initializers'
|
import { VIDEO_RATE_TYPES } from '../../../initializers'
|
||||||
import { database as db } from '../../../initializers/database'
|
import { database as db } from '../../../initializers/database'
|
||||||
|
import { sendVideoRateChangeToFollowers, sendVideoRateChangeToOrigin } from '../../../lib/activitypub/videos'
|
||||||
import { asyncMiddleware, authenticate, videoRateValidator } from '../../../middlewares'
|
import { asyncMiddleware, authenticate, videoRateValidator } from '../../../middlewares'
|
||||||
import { AccountInstance } from '../../../models/account/account-interface'
|
import { AccountInstance } from '../../../models/account/account-interface'
|
||||||
import { VideoInstance } from '../../../models/video/video-interface'
|
import { VideoInstance } from '../../../models/video/video-interface'
|
||||||
|
@ -82,10 +83,10 @@ async function rateVideo (req: express.Request, res: express.Response) {
|
||||||
// It is useful for the user to have a feedback
|
// It is useful for the user to have a feedback
|
||||||
await videoInstance.increment(incrementQuery, sequelizeOptions)
|
await videoInstance.increment(incrementQuery, sequelizeOptions)
|
||||||
|
|
||||||
if (videoInstance.isOwned() === false) {
|
if (videoInstance.isOwned()) {
|
||||||
// TODO: Send a event to original server
|
await sendVideoRateChangeToFollowers(accountInstance, videoInstance, likesToIncrement, dislikesToIncrement, t)
|
||||||
} else {
|
} else {
|
||||||
// TODO: Send update to followers
|
await sendVideoRateChangeToOrigin(accountInstance, videoInstance, likesToIncrement, dislikesToIncrement, t)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import * as validator from 'validator'
|
import * as validator from 'validator'
|
||||||
import { Activity, ActivityType } from '../../../../shared/models/activitypub/activity'
|
import { Activity, ActivityType } from '../../../../shared/models/activitypub/activity'
|
||||||
import { isAccountAcceptActivityValid, isAccountDeleteActivityValid, isAccountFollowActivityValid } from './account'
|
import { isAccountAcceptActivityValid, isAccountDeleteActivityValid, isAccountFollowActivityValid } from './account'
|
||||||
import { isAnnounceValid } from './announce'
|
import { isAnnounceActivityValid } from './announce'
|
||||||
import { isActivityPubUrlValid } from './misc'
|
import { isActivityPubUrlValid } from './misc'
|
||||||
import { isUndoValid } from './undo'
|
import { isUndoActivityValid } from './undo'
|
||||||
import { isVideoChannelCreateActivityValid, isVideoChannelDeleteActivityValid, isVideoChannelUpdateActivityValid } from './video-channels'
|
import { isVideoChannelCreateActivityValid, isVideoChannelDeleteActivityValid, isVideoChannelUpdateActivityValid } from './video-channels'
|
||||||
import {
|
import {
|
||||||
isVideoFlagValid,
|
isVideoFlagValid,
|
||||||
|
@ -12,6 +12,7 @@ import {
|
||||||
isVideoTorrentUpdateActivityValid
|
isVideoTorrentUpdateActivityValid
|
||||||
} from './videos'
|
} from './videos'
|
||||||
import { isViewActivityValid } from './view'
|
import { isViewActivityValid } from './view'
|
||||||
|
import { isDislikeActivityValid, isLikeActivityValid } from './rate'
|
||||||
|
|
||||||
function isRootActivityValid (activity: any) {
|
function isRootActivityValid (activity: any) {
|
||||||
return Array.isArray(activity['@context']) &&
|
return Array.isArray(activity['@context']) &&
|
||||||
|
@ -34,7 +35,8 @@ const activityCheckers: { [ P in ActivityType ]: (activity: Activity) => boolean
|
||||||
Follow: checkFollowActivity,
|
Follow: checkFollowActivity,
|
||||||
Accept: checkAcceptActivity,
|
Accept: checkAcceptActivity,
|
||||||
Announce: checkAnnounceActivity,
|
Announce: checkAnnounceActivity,
|
||||||
Undo: checkUndoActivity
|
Undo: checkUndoActivity,
|
||||||
|
Like: checkLikeActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
function isActivityValid (activity: any) {
|
function isActivityValid (activity: any) {
|
||||||
|
@ -55,9 +57,10 @@ export {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function checkCreateActivity (activity: any) {
|
function checkCreateActivity (activity: any) {
|
||||||
return isVideoChannelCreateActivityValid(activity) ||
|
return isViewActivityValid(activity) ||
|
||||||
isVideoFlagValid(activity) ||
|
isDislikeActivityValid(activity) ||
|
||||||
isViewActivityValid(activity)
|
isVideoChannelCreateActivityValid(activity) ||
|
||||||
|
isVideoFlagValid(activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkAddActivity (activity: any) {
|
function checkAddActivity (activity: any) {
|
||||||
|
@ -84,9 +87,13 @@ function checkAcceptActivity (activity: any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkAnnounceActivity (activity: any) {
|
function checkAnnounceActivity (activity: any) {
|
||||||
return isAnnounceValid(activity)
|
return isAnnounceActivityValid(activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkUndoActivity (activity: any) {
|
function checkUndoActivity (activity: any) {
|
||||||
return isUndoValid(activity)
|
return isUndoActivityValid(activity)
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkLikeActivity (activity: any) {
|
||||||
|
return isLikeActivityValid(activity)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { isBaseActivityValid } from './misc'
|
||||||
import { isVideoTorrentAddActivityValid } from './videos'
|
import { isVideoTorrentAddActivityValid } from './videos'
|
||||||
import { isVideoChannelCreateActivityValid } from './video-channels'
|
import { isVideoChannelCreateActivityValid } from './video-channels'
|
||||||
|
|
||||||
function isAnnounceValid (activity: any) {
|
function isAnnounceActivityValid (activity: any) {
|
||||||
return isBaseActivityValid(activity, 'Announce') &&
|
return isBaseActivityValid(activity, 'Announce') &&
|
||||||
(
|
(
|
||||||
isVideoChannelCreateActivityValid(activity.object) ||
|
isVideoChannelCreateActivityValid(activity.object) ||
|
||||||
|
@ -11,5 +11,5 @@ function isAnnounceValid (activity: any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
isAnnounceValid
|
isAnnounceActivityValid
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { isActivityPubUrlValid, isBaseActivityValid } from './misc'
|
||||||
|
|
||||||
|
function isLikeActivityValid (activity: any) {
|
||||||
|
return isBaseActivityValid(activity, 'Like') &&
|
||||||
|
isActivityPubUrlValid(activity.object)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDislikeActivityValid (activity: any) {
|
||||||
|
return isBaseActivityValid(activity, 'Create') &&
|
||||||
|
activity.object.type === 'Dislike' &&
|
||||||
|
isActivityPubUrlValid(activity.object.actor) &&
|
||||||
|
isActivityPubUrlValid(activity.object.object)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export {
|
||||||
|
isLikeActivityValid,
|
||||||
|
isDislikeActivityValid
|
||||||
|
}
|
|
@ -1,13 +1,16 @@
|
||||||
import { isAccountFollowActivityValid } from './account'
|
import { isAccountFollowActivityValid } from './account'
|
||||||
import { isBaseActivityValid } from './misc'
|
import { isBaseActivityValid } from './misc'
|
||||||
|
import { isDislikeActivityValid, isLikeActivityValid } from './rate'
|
||||||
|
|
||||||
function isUndoValid (activity: any) {
|
function isUndoActivityValid (activity: any) {
|
||||||
return isBaseActivityValid(activity, 'Undo') &&
|
return isBaseActivityValid(activity, 'Undo') &&
|
||||||
(
|
(
|
||||||
isAccountFollowActivityValid(activity.object)
|
isAccountFollowActivityValid(activity.object) ||
|
||||||
|
isLikeActivityValid(activity.object) ||
|
||||||
|
isDislikeActivityValid(activity.object)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
isUndoValid
|
isUndoActivityValid
|
||||||
}
|
}
|
||||||
|
|
|
@ -229,6 +229,7 @@ const ACTIVITY_PUB = {
|
||||||
PUBLIC: 'https://www.w3.org/ns/activitystreams#Public',
|
PUBLIC: 'https://www.w3.org/ns/activitystreams#Public',
|
||||||
COLLECTION_ITEMS_PER_PAGE: 10,
|
COLLECTION_ITEMS_PER_PAGE: 10,
|
||||||
FETCH_PAGE_LIMIT: 100,
|
FETCH_PAGE_LIMIT: 100,
|
||||||
|
MAX_HTTP_ATTEMPT: 5,
|
||||||
URL_MIME_TYPES: {
|
URL_MIME_TYPES: {
|
||||||
VIDEO: [ 'video/mp4', 'video/webm', 'video/ogg' ], // TODO: Merge with VIDEO_MIMETYPE_EXT
|
VIDEO: [ 'video/mp4', 'video/webm', 'video/ogg' ], // TODO: Merge with VIDEO_MIMETYPE_EXT
|
||||||
TORRENT: [ 'application/x-bittorrent' ],
|
TORRENT: [ 'application/x-bittorrent' ],
|
||||||
|
|
|
@ -5,5 +5,6 @@ export * from './process-announce'
|
||||||
export * from './process-create'
|
export * from './process-create'
|
||||||
export * from './process-delete'
|
export * from './process-delete'
|
||||||
export * from './process-follow'
|
export * from './process-follow'
|
||||||
|
export * from './process-like'
|
||||||
export * from './process-undo'
|
export * from './process-undo'
|
||||||
export * from './process-update'
|
export * from './process-update'
|
||||||
|
|
|
@ -5,9 +5,10 @@ import { logger, retryTransactionWrapper } from '../../../helpers'
|
||||||
import { database as db } from '../../../initializers'
|
import { database as db } from '../../../initializers'
|
||||||
import { AccountInstance } from '../../../models/account/account-interface'
|
import { AccountInstance } from '../../../models/account/account-interface'
|
||||||
import { getOrCreateAccountAndServer } from '../account'
|
import { getOrCreateAccountAndServer } from '../account'
|
||||||
import { sendCreateViewToVideoFollowers } from '../send/send-create'
|
import { sendCreateDislikeToVideoFollowers, sendCreateViewToVideoFollowers } from '../send/send-create'
|
||||||
import { getVideoChannelActivityPubUrl } from '../url'
|
import { getVideoChannelActivityPubUrl } from '../url'
|
||||||
import { videoChannelActivityObjectToDBAttributes } from './misc'
|
import { videoChannelActivityObjectToDBAttributes } from './misc'
|
||||||
|
import { DislikeObject } from '../../../../shared/models/activitypub/objects/dislike-object'
|
||||||
|
|
||||||
async function processCreateActivity (activity: ActivityCreate) {
|
async function processCreateActivity (activity: ActivityCreate) {
|
||||||
const activityObject = activity.object
|
const activityObject = activity.object
|
||||||
|
@ -16,6 +17,8 @@ async function processCreateActivity (activity: ActivityCreate) {
|
||||||
|
|
||||||
if (activityType === 'View') {
|
if (activityType === 'View') {
|
||||||
return processCreateView(activityObject as ViewObject)
|
return processCreateView(activityObject as ViewObject)
|
||||||
|
} else if (activityType === 'Dislike') {
|
||||||
|
return processCreateDislike(account, activityObject as DislikeObject)
|
||||||
} else if (activityType === 'VideoChannel') {
|
} else if (activityType === 'VideoChannel') {
|
||||||
return processCreateVideoChannel(account, activityObject as VideoChannelObject)
|
return processCreateVideoChannel(account, activityObject as VideoChannelObject)
|
||||||
} else if (activityType === 'Flag') {
|
} else if (activityType === 'Flag') {
|
||||||
|
@ -34,6 +37,36 @@ export {
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
async function processCreateDislike (byAccount: AccountInstance, dislike: DislikeObject) {
|
||||||
|
const options = {
|
||||||
|
arguments: [ byAccount, dislike ],
|
||||||
|
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)
|
||||||
|
|
||||||
|
if (!video) throw new Error('Unknown video ' + dislike.object)
|
||||||
|
|
||||||
|
const rate = {
|
||||||
|
type: 'dislike' as 'dislike',
|
||||||
|
videoId: video.id,
|
||||||
|
accountId: byAccount.id
|
||||||
|
}
|
||||||
|
const [ , created ] = await db.AccountVideoRate.findOrCreate({
|
||||||
|
where: rate,
|
||||||
|
defaults: rate
|
||||||
|
})
|
||||||
|
await video.increment('dislikes')
|
||||||
|
|
||||||
|
if (video.isOwned() && created === true) await sendCreateDislikeToVideoFollowers(byAccount, video, undefined)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async function processCreateView (view: ViewObject) {
|
async function processCreateView (view: ViewObject) {
|
||||||
const video = await db.Video.loadByUrlAndPopulateAccount(view.object)
|
const video = await db.Video.loadByUrlAndPopulateAccount(view.object)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { ActivityLike } from '../../../../shared/models/activitypub/activity'
|
||||||
|
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'
|
||||||
|
|
||||||
|
async function processLikeActivity (activity: ActivityLike) {
|
||||||
|
const account = await getOrCreateAccountAndServer(activity.actor)
|
||||||
|
|
||||||
|
return processLikeVideo(account, activity.object)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export {
|
||||||
|
processLikeActivity
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
async function processLikeVideo (byAccount: AccountInstance, videoUrl: string) {
|
||||||
|
const options = {
|
||||||
|
arguments: [ byAccount, videoUrl ],
|
||||||
|
errorMessage: 'Cannot like the video with many retries.'
|
||||||
|
}
|
||||||
|
|
||||||
|
return retryTransactionWrapper(createVideoLike, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
function createVideoLike (byAccount: AccountInstance, videoUrl: string) {
|
||||||
|
return db.sequelize.transaction(async t => {
|
||||||
|
const video = await db.Video.loadByUrlAndPopulateAccount(videoUrl)
|
||||||
|
|
||||||
|
if (!video) throw new Error('Unknown video ' + videoUrl)
|
||||||
|
|
||||||
|
const rate = {
|
||||||
|
type: 'like' as 'like',
|
||||||
|
videoId: video.id,
|
||||||
|
accountId: byAccount.id
|
||||||
|
}
|
||||||
|
const [ , created ] = await db.AccountVideoRate.findOrCreate({
|
||||||
|
where: rate,
|
||||||
|
defaults: rate
|
||||||
|
})
|
||||||
|
await video.increment('likes')
|
||||||
|
|
||||||
|
if (video.isOwned() && created === true) await sendLikeToVideoFollowers(byAccount, video, undefined)
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,20 +1,20 @@
|
||||||
import { ActivityUndo } from '../../../../shared/models/activitypub/activity'
|
import { ActivityCreate, ActivityFollow, ActivityLike, ActivityUndo } from '../../../../shared/models/activitypub/activity'
|
||||||
import { logger } from '../../../helpers/logger'
|
import { logger } from '../../../helpers/logger'
|
||||||
import { database as db } from '../../../initializers'
|
import { database as db } from '../../../initializers'
|
||||||
|
import { retryTransactionWrapper } from '../../../helpers/database-utils'
|
||||||
|
import { DislikeObject } from '../../../../shared/models/activitypub/objects/dislike-object'
|
||||||
|
import { sendUndoLikeToVideoFollowers } from '../send/send-undo'
|
||||||
|
import { sendUndoDislikeToVideoFollowers } from '../index'
|
||||||
|
|
||||||
async function processUndoActivity (activity: ActivityUndo) {
|
async function processUndoActivity (activity: ActivityUndo) {
|
||||||
const activityToUndo = activity.object
|
const activityToUndo = activity.object
|
||||||
|
|
||||||
if (activityToUndo.type === 'Follow') {
|
if (activityToUndo.type === 'Like') {
|
||||||
const follower = await db.Account.loadByUrl(activity.actor)
|
return processUndoLike(activity.actor, activityToUndo)
|
||||||
const following = await db.Account.loadByUrl(activityToUndo.object)
|
} else if (activityToUndo.type === 'Create' && activityToUndo.object.type === 'Dislike') {
|
||||||
const accountFollow = await db.AccountFollow.loadByAccountAndTarget(follower.id, following.id)
|
return processUndoDislike(activity.actor, activityToUndo.object)
|
||||||
|
} else if (activityToUndo.type === 'Follow') {
|
||||||
if (!accountFollow) throw new Error(`'Unknown account follow ${follower.id} -> ${following.id}.`)
|
return processUndoFollow(activity.actor, activityToUndo)
|
||||||
|
|
||||||
await accountFollow.destroy()
|
|
||||||
|
|
||||||
return undefined
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.warn('Unknown activity object type %s -> %s when undo activity.', activityToUndo.type, { activity: activity.id })
|
logger.warn('Unknown activity object type %s -> %s when undo activity.', activityToUndo.type, { activity: activity.id })
|
||||||
|
@ -29,3 +29,80 @@ export {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function processUndoLike (actor: string, likeActivity: ActivityLike) {
|
||||||
|
const options = {
|
||||||
|
arguments: [ actor, likeActivity ],
|
||||||
|
errorMessage: 'Cannot undo like with many retries.'
|
||||||
|
}
|
||||||
|
|
||||||
|
return retryTransactionWrapper(undoLike, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
function undoLike (actor: string, likeActivity: 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)
|
||||||
|
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')
|
||||||
|
|
||||||
|
if (video.isOwned()) await sendUndoLikeToVideoFollowers(byAccount, video, t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function processUndoDislike (actor: string, dislikeCreateActivity: DislikeObject) {
|
||||||
|
const options = {
|
||||||
|
arguments: [ actor, dislikeCreateActivity ],
|
||||||
|
errorMessage: 'Cannot undo dislike with many retries.'
|
||||||
|
}
|
||||||
|
|
||||||
|
return retryTransactionWrapper(undoDislike, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
function undoDislike (actor: string, dislike: 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)
|
||||||
|
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')
|
||||||
|
|
||||||
|
if (video.isOwned()) await sendUndoDislikeToVideoFollowers(byAccount, video, t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function processUndoFollow (actor: string, followActivity: ActivityFollow) {
|
||||||
|
const options = {
|
||||||
|
arguments: [ actor, followActivity ],
|
||||||
|
errorMessage: 'Cannot undo follow with many retries.'
|
||||||
|
}
|
||||||
|
|
||||||
|
return retryTransactionWrapper(undoFollow, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
function undoFollow (actor: string, followActivity: ActivityFollow) {
|
||||||
|
return db.sequelize.transaction(async t => {
|
||||||
|
const follower = await db.Account.loadByUrl(actor, t)
|
||||||
|
const following = await db.Account.loadByUrl(followActivity.object, t)
|
||||||
|
const accountFollow = await db.AccountFollow.loadByAccountAndTarget(follower.id, following.id, t)
|
||||||
|
|
||||||
|
if (!accountFollow) throw new Error(`'Unknown account follow ${follower.id} -> ${following.id}.`)
|
||||||
|
|
||||||
|
await accountFollow.destroy({ transaction: t })
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Activity, ActivityType } from '../../../../shared/models/activitypub/activity'
|
import { Activity, ActivityType } from '../../../../shared/models/activitypub/activity'
|
||||||
|
import { logger } from '../../../helpers/logger'
|
||||||
import { AccountInstance } from '../../../models/account/account-interface'
|
import { AccountInstance } from '../../../models/account/account-interface'
|
||||||
import { processAcceptActivity } from './process-accept'
|
import { processAcceptActivity } from './process-accept'
|
||||||
import { processAddActivity } from './process-add'
|
import { processAddActivity } from './process-add'
|
||||||
|
@ -6,9 +7,9 @@ import { processAnnounceActivity } from './process-announce'
|
||||||
import { processCreateActivity } from './process-create'
|
import { processCreateActivity } from './process-create'
|
||||||
import { processDeleteActivity } from './process-delete'
|
import { processDeleteActivity } from './process-delete'
|
||||||
import { processFollowActivity } from './process-follow'
|
import { processFollowActivity } from './process-follow'
|
||||||
|
import { processLikeActivity } from './process-like'
|
||||||
import { processUndoActivity } from './process-undo'
|
import { processUndoActivity } from './process-undo'
|
||||||
import { processUpdateActivity } from './process-update'
|
import { processUpdateActivity } from './process-update'
|
||||||
import { logger } from '../../../helpers/logger'
|
|
||||||
|
|
||||||
const processActivity: { [ P in ActivityType ]: (activity: Activity, inboxAccount?: AccountInstance) => Promise<any> } = {
|
const processActivity: { [ P in ActivityType ]: (activity: Activity, inboxAccount?: AccountInstance) => Promise<any> } = {
|
||||||
Create: processCreateActivity,
|
Create: processCreateActivity,
|
||||||
|
@ -18,7 +19,8 @@ const processActivity: { [ P in ActivityType ]: (activity: Activity, inboxAccoun
|
||||||
Follow: processFollowActivity,
|
Follow: processFollowActivity,
|
||||||
Accept: processAcceptActivity,
|
Accept: processAcceptActivity,
|
||||||
Announce: processAnnounceActivity,
|
Announce: processAnnounceActivity,
|
||||||
Undo: processUndoActivity
|
Undo: processUndoActivity,
|
||||||
|
Like: processLikeActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processActivities (activities: Activity[], inboxAccount?: AccountInstance) {
|
async function processActivities (activities: Activity[], inboxAccount?: AccountInstance) {
|
||||||
|
|
|
@ -4,4 +4,6 @@ export * from './send-announce'
|
||||||
export * from './send-create'
|
export * from './send-create'
|
||||||
export * from './send-delete'
|
export * from './send-delete'
|
||||||
export * from './send-follow'
|
export * from './send-follow'
|
||||||
|
export * from './send-like'
|
||||||
|
export * from './send-undo'
|
||||||
export * from './send-update'
|
export * from './send-update'
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { logger } from '../../../helpers/logger'
|
||||||
import { ACTIVITY_PUB, database as db } from '../../../initializers'
|
import { ACTIVITY_PUB, database as db } from '../../../initializers'
|
||||||
import { AccountInstance } from '../../../models/account/account-interface'
|
import { AccountInstance } from '../../../models/account/account-interface'
|
||||||
import { activitypubHttpJobScheduler } from '../../jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler'
|
import { activitypubHttpJobScheduler } from '../../jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler'
|
||||||
|
import { VideoInstance } from '../../../models/video/video-interface'
|
||||||
|
|
||||||
async function broadcastToFollowers (
|
async function broadcastToFollowers (
|
||||||
data: any,
|
data: any,
|
||||||
|
@ -41,6 +42,27 @@ async function unicastTo (data: any, byAccount: AccountInstance, toAccountUrl: s
|
||||||
return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpUnicastHandler', jobPayload)
|
return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpUnicastHandler', jobPayload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getOriginVideoAudience (video: VideoInstance) {
|
||||||
|
return {
|
||||||
|
to: [ video.VideoChannel.Account.url ],
|
||||||
|
cc: [ video.VideoChannel.Account.url + '/followers' ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVideoFollowersAudience (video: VideoInstance) {
|
||||||
|
return {
|
||||||
|
to: [ video.VideoChannel.Account.url + '/followers' ],
|
||||||
|
cc: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAccountsToForwardVideoAction (byAccount: AccountInstance, video: VideoInstance) {
|
||||||
|
const accountsToForwardView = await db.VideoShare.loadAccountsByShare(video.id)
|
||||||
|
accountsToForwardView.push(video.VideoChannel.Account)
|
||||||
|
|
||||||
|
return accountsToForwardView
|
||||||
|
}
|
||||||
|
|
||||||
async function getAudience (accountSender: AccountInstance, isPublic = true) {
|
async function getAudience (accountSender: AccountInstance, isPublic = true) {
|
||||||
const followerInboxUrls = await accountSender.getFollowerSharedInboxUrls()
|
const followerInboxUrls = await accountSender.getFollowerSharedInboxUrls()
|
||||||
|
|
||||||
|
@ -64,5 +86,8 @@ async function getAudience (accountSender: AccountInstance, isPublic = true) {
|
||||||
export {
|
export {
|
||||||
broadcastToFollowers,
|
broadcastToFollowers,
|
||||||
unicastTo,
|
unicastTo,
|
||||||
getAudience
|
getAudience,
|
||||||
|
getOriginVideoAudience,
|
||||||
|
getAccountsToForwardVideoAction,
|
||||||
|
getVideoFollowersAudience
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
import { Transaction } from 'sequelize'
|
import { Transaction } from 'sequelize'
|
||||||
import { ActivityCreate } from '../../../../shared/models/activitypub/activity'
|
import { ActivityCreate } from '../../../../shared/models/activitypub/activity'
|
||||||
|
import { getServerAccount } from '../../../helpers/utils'
|
||||||
import { AccountInstance, VideoChannelInstance, VideoInstance } from '../../../models'
|
import { AccountInstance, VideoChannelInstance, VideoInstance } from '../../../models'
|
||||||
import { VideoAbuseInstance } from '../../../models/video/video-abuse-interface'
|
import { VideoAbuseInstance } from '../../../models/video/video-abuse-interface'
|
||||||
import { broadcastToFollowers, getAudience, unicastTo } from './misc'
|
import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url'
|
||||||
import { getVideoAbuseActivityPubUrl, getVideoViewActivityPubUrl } from '../url'
|
import {
|
||||||
import { getServerAccount } from '../../../helpers/utils'
|
broadcastToFollowers,
|
||||||
import { database as db } from '../../../initializers'
|
getAccountsToForwardVideoAction,
|
||||||
|
getAudience,
|
||||||
|
getOriginVideoAudience,
|
||||||
|
getVideoFollowersAudience,
|
||||||
|
unicastTo
|
||||||
|
} from './misc'
|
||||||
|
|
||||||
async function sendCreateVideoChannel (videoChannel: VideoChannelInstance, t: Transaction) {
|
async function sendCreateVideoChannel (videoChannel: VideoChannelInstance, t: Transaction) {
|
||||||
const byAccount = videoChannel.Account
|
const byAccount = videoChannel.Account
|
||||||
|
@ -29,7 +35,7 @@ async function sendCreateViewToOrigin (byAccount: AccountInstance, video: VideoI
|
||||||
const url = getVideoViewActivityPubUrl(byAccount, video)
|
const url = getVideoViewActivityPubUrl(byAccount, video)
|
||||||
const viewActivity = createViewActivityData(byAccount, video)
|
const viewActivity = createViewActivityData(byAccount, video)
|
||||||
|
|
||||||
const audience = { to: [ video.VideoChannel.Account.url ], cc: [ video.VideoChannel.Account.url + '/followers' ] }
|
const audience = getOriginVideoAudience(video)
|
||||||
const data = await createActivityData(url, byAccount, viewActivity, audience)
|
const data = await createActivityData(url, byAccount, viewActivity, audience)
|
||||||
|
|
||||||
return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t)
|
return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t)
|
||||||
|
@ -39,16 +45,35 @@ async function sendCreateViewToVideoFollowers (byAccount: AccountInstance, video
|
||||||
const url = getVideoViewActivityPubUrl(byAccount, video)
|
const url = getVideoViewActivityPubUrl(byAccount, video)
|
||||||
const viewActivity = createViewActivityData(byAccount, video)
|
const viewActivity = createViewActivityData(byAccount, video)
|
||||||
|
|
||||||
const audience = { to: [ video.VideoChannel.Account.url + '/followers' ], cc: [] }
|
const audience = getVideoFollowersAudience(video)
|
||||||
const data = await createActivityData(url, byAccount, viewActivity, audience)
|
const data = await createActivityData(url, byAccount, viewActivity, audience)
|
||||||
|
|
||||||
const serverAccount = await getServerAccount()
|
const serverAccount = await getServerAccount()
|
||||||
const accountsToForwardView = await db.VideoShare.loadAccountsByShare(video.id)
|
const accountsToForwardView = await getAccountsToForwardVideoAction(byAccount, video)
|
||||||
accountsToForwardView.push(video.VideoChannel.Account)
|
|
||||||
|
|
||||||
// Don't forward view to server that sent it to us
|
const followersException = [ byAccount ]
|
||||||
const index = accountsToForwardView.findIndex(a => a.id === byAccount.id)
|
return broadcastToFollowers(data, serverAccount, accountsToForwardView, t, followersException)
|
||||||
if (index) accountsToForwardView.splice(index, 1)
|
}
|
||||||
|
|
||||||
|
async function sendCreateDislikeToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) {
|
||||||
|
const url = getVideoDislikeActivityPubUrl(byAccount, video)
|
||||||
|
const dislikeActivity = createDislikeActivityData(byAccount, video)
|
||||||
|
|
||||||
|
const audience = getOriginVideoAudience(video)
|
||||||
|
const data = await createActivityData(url, byAccount, dislikeActivity, audience)
|
||||||
|
|
||||||
|
return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendCreateDislikeToVideoFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) {
|
||||||
|
const url = getVideoDislikeActivityPubUrl(byAccount, video)
|
||||||
|
const dislikeActivity = createDislikeActivityData(byAccount, video)
|
||||||
|
|
||||||
|
const audience = getVideoFollowersAudience(video)
|
||||||
|
const data = await createActivityData(url, byAccount, dislikeActivity, audience)
|
||||||
|
|
||||||
|
const accountsToForwardView = await getAccountsToForwardVideoAction(byAccount, video)
|
||||||
|
const serverAccount = await getServerAccount()
|
||||||
|
|
||||||
const followersException = [ byAccount ]
|
const followersException = [ byAccount ]
|
||||||
return broadcastToFollowers(data, serverAccount, accountsToForwardView, t, followersException)
|
return broadcastToFollowers(data, serverAccount, accountsToForwardView, t, followersException)
|
||||||
|
@ -71,6 +96,16 @@ async function createActivityData (url: string, byAccount: AccountInstance, obje
|
||||||
return activity
|
return activity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createDislikeActivityData (byAccount: AccountInstance, video: VideoInstance) {
|
||||||
|
const obj = {
|
||||||
|
type: 'Dislike',
|
||||||
|
actor: byAccount.url,
|
||||||
|
object: video.url
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
@ -78,7 +113,10 @@ export {
|
||||||
sendVideoAbuse,
|
sendVideoAbuse,
|
||||||
createActivityData,
|
createActivityData,
|
||||||
sendCreateViewToOrigin,
|
sendCreateViewToOrigin,
|
||||||
sendCreateViewToVideoFollowers
|
sendCreateViewToVideoFollowers,
|
||||||
|
sendCreateDislikeToOrigin,
|
||||||
|
sendCreateDislikeToVideoFollowers,
|
||||||
|
createDislikeActivityData
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
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,
|
||||||
|
getAudience,
|
||||||
|
getOriginVideoAudience,
|
||||||
|
getVideoFollowersAudience,
|
||||||
|
unicastTo
|
||||||
|
} from './misc'
|
||||||
|
|
||||||
|
async function sendLikeToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) {
|
||||||
|
const url = getVideoLikeActivityPubUrl(byAccount, video)
|
||||||
|
|
||||||
|
const audience = getOriginVideoAudience(video)
|
||||||
|
const data = await likeActivityData(url, byAccount, video, audience)
|
||||||
|
|
||||||
|
return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendLikeToVideoFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) {
|
||||||
|
const url = getVideoLikeActivityPubUrl(byAccount, video)
|
||||||
|
|
||||||
|
const audience = getVideoFollowersAudience(video)
|
||||||
|
const data = await likeActivityData(url, byAccount, video, audience)
|
||||||
|
|
||||||
|
const accountsToForwardView = await getAccountsToForwardVideoAction(byAccount, video)
|
||||||
|
const serverAccount = await getServerAccount()
|
||||||
|
|
||||||
|
const followersException = [ byAccount ]
|
||||||
|
return broadcastToFollowers(data, serverAccount, accountsToForwardView, t, followersException)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function likeActivityData (url: string, byAccount: AccountInstance, video: VideoInstance, audience?: { to: string[], cc: string[] }) {
|
||||||
|
if (!audience) {
|
||||||
|
audience = await getAudience(byAccount)
|
||||||
|
}
|
||||||
|
|
||||||
|
const activity: ActivityLike = {
|
||||||
|
type: 'Like',
|
||||||
|
id: url,
|
||||||
|
actor: byAccount.url,
|
||||||
|
to: audience.to,
|
||||||
|
cc: audience.cc,
|
||||||
|
object: video.url
|
||||||
|
}
|
||||||
|
|
||||||
|
return activity
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export {
|
||||||
|
sendLikeToOrigin,
|
||||||
|
sendLikeToVideoFollowers,
|
||||||
|
likeActivityData
|
||||||
|
}
|
|
@ -1,10 +1,14 @@
|
||||||
import { Transaction } from 'sequelize'
|
import { Transaction } from 'sequelize'
|
||||||
import { ActivityFollow, ActivityUndo } from '../../../../shared/models/activitypub/activity'
|
import { ActivityCreate, ActivityFollow, ActivityLike, ActivityUndo } from '../../../../shared/models/activitypub/activity'
|
||||||
import { AccountInstance } from '../../../models'
|
import { AccountInstance } from '../../../models'
|
||||||
import { AccountFollowInstance } from '../../../models/account/account-follow-interface'
|
import { AccountFollowInstance } from '../../../models/account/account-follow-interface'
|
||||||
import { unicastTo } from './misc'
|
import { broadcastToFollowers, getAccountsToForwardVideoAction, unicastTo } from './misc'
|
||||||
import { followActivityData } from './send-follow'
|
import { followActivityData } from './send-follow'
|
||||||
import { getAccountFollowActivityPubUrl, getUndoActivityPubUrl } from '../url'
|
import { getAccountFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url'
|
||||||
|
import { VideoInstance } from '../../../models/video/video-interface'
|
||||||
|
import { likeActivityData } from './send-like'
|
||||||
|
import { createActivityData, createDislikeActivityData } from './send-create'
|
||||||
|
import { getServerAccount } from '../../../helpers/utils'
|
||||||
|
|
||||||
async function sendUndoFollow (accountFollow: AccountFollowInstance, t: Transaction) {
|
async function sendUndoFollow (accountFollow: AccountFollowInstance, t: Transaction) {
|
||||||
const me = accountFollow.AccountFollower
|
const me = accountFollow.AccountFollower
|
||||||
|
@ -19,15 +23,72 @@ async function sendUndoFollow (accountFollow: AccountFollowInstance, t: Transact
|
||||||
return unicastTo(data, me, following.inboxUrl, t)
|
return unicastTo(data, me, following.inboxUrl, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function sendUndoLikeToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) {
|
||||||
|
const likeUrl = getVideoLikeActivityPubUrl(byAccount, video)
|
||||||
|
const undoUrl = getUndoActivityPubUrl(likeUrl)
|
||||||
|
|
||||||
|
const object = await likeActivityData(likeUrl, byAccount, video)
|
||||||
|
const data = await undoActivityData(undoUrl, byAccount, object)
|
||||||
|
|
||||||
|
return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendUndoLikeToVideoFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) {
|
||||||
|
const likeUrl = getVideoLikeActivityPubUrl(byAccount, video)
|
||||||
|
const undoUrl = getUndoActivityPubUrl(likeUrl)
|
||||||
|
|
||||||
|
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 followersException = [ byAccount ]
|
||||||
|
return broadcastToFollowers(data, serverAccount, accountsToForwardView, t, followersException)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendUndoDislikeToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) {
|
||||||
|
const dislikeUrl = getVideoDislikeActivityPubUrl(byAccount, video)
|
||||||
|
const undoUrl = getUndoActivityPubUrl(dislikeUrl)
|
||||||
|
|
||||||
|
const dislikeActivity = createDislikeActivityData(byAccount, video)
|
||||||
|
const object = await createActivityData(undoUrl, byAccount, dislikeActivity)
|
||||||
|
|
||||||
|
const data = await undoActivityData(undoUrl, byAccount, object)
|
||||||
|
|
||||||
|
return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendUndoDislikeToVideoFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) {
|
||||||
|
const dislikeUrl = getVideoDislikeActivityPubUrl(byAccount, video)
|
||||||
|
const undoUrl = getUndoActivityPubUrl(dislikeUrl)
|
||||||
|
|
||||||
|
const dislikeActivity = createDislikeActivityData(byAccount, video)
|
||||||
|
const object = await createActivityData(undoUrl, byAccount, dislikeActivity)
|
||||||
|
|
||||||
|
const data = await undoActivityData(undoUrl, byAccount, object)
|
||||||
|
|
||||||
|
const accountsToForwardView = await getAccountsToForwardVideoAction(byAccount, video)
|
||||||
|
const serverAccount = await getServerAccount()
|
||||||
|
|
||||||
|
const followersException = [ byAccount ]
|
||||||
|
return broadcastToFollowers(data, serverAccount, accountsToForwardView, t, followersException)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export {
|
export {
|
||||||
sendUndoFollow
|
sendUndoFollow,
|
||||||
|
sendUndoLikeToOrigin,
|
||||||
|
sendUndoLikeToVideoFollowers,
|
||||||
|
sendUndoDislikeToOrigin,
|
||||||
|
sendUndoDislikeToVideoFollowers
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
async function undoActivityData (url: string, byAccount: AccountInstance, object: ActivityFollow) {
|
async function undoActivityData (url: string, byAccount: AccountInstance, object: ActivityFollow | ActivityLike | ActivityCreate) {
|
||||||
const activity: ActivityUndo = {
|
const activity: ActivityUndo = {
|
||||||
type: 'Undo',
|
type: 'Undo',
|
||||||
id: url,
|
id: url,
|
||||||
|
|
|
@ -25,6 +25,14 @@ function getVideoViewActivityPubUrl (byAccount: AccountInstance, video: VideoIns
|
||||||
return video.url + '#views/' + byAccount.uuid + '/' + new Date().toISOString()
|
return video.url + '#views/' + byAccount.uuid + '/' + new Date().toISOString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getVideoLikeActivityPubUrl (byAccount: AccountInstance, video: VideoInstance) {
|
||||||
|
return byAccount.url + '#likes/' + video.id
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVideoDislikeActivityPubUrl (byAccount: AccountInstance, video: VideoInstance) {
|
||||||
|
return byAccount.url + '#dislikes/' + video.id
|
||||||
|
}
|
||||||
|
|
||||||
function getAccountFollowActivityPubUrl (accountFollow: AccountFollowInstance) {
|
function getAccountFollowActivityPubUrl (accountFollow: AccountFollowInstance) {
|
||||||
const me = accountFollow.AccountFollower
|
const me = accountFollow.AccountFollower
|
||||||
const following = accountFollow.AccountFollowing
|
const following = accountFollow.AccountFollowing
|
||||||
|
@ -61,5 +69,7 @@ export {
|
||||||
getAnnounceActivityPubUrl,
|
getAnnounceActivityPubUrl,
|
||||||
getUpdateActivityPubUrl,
|
getUpdateActivityPubUrl,
|
||||||
getUndoActivityPubUrl,
|
getUndoActivityPubUrl,
|
||||||
getVideoViewActivityPubUrl
|
getVideoViewActivityPubUrl,
|
||||||
|
getVideoLikeActivityPubUrl,
|
||||||
|
getVideoDislikeActivityPubUrl
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,20 @@
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import * as request from 'request'
|
import * as request from 'request'
|
||||||
|
import { Transaction } from 'sequelize'
|
||||||
import { ActivityIconObject } from '../../../shared/index'
|
import { ActivityIconObject } from '../../../shared/index'
|
||||||
import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests'
|
import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests'
|
||||||
import { CONFIG, REMOTE_SCHEME, STATIC_PATHS } from '../../initializers/constants'
|
import { CONFIG, REMOTE_SCHEME, STATIC_PATHS } from '../../initializers/constants'
|
||||||
|
import { AccountInstance } from '../../models/account/account-interface'
|
||||||
import { VideoInstance } from '../../models/video/video-interface'
|
import { VideoInstance } from '../../models/video/video-interface'
|
||||||
|
import { sendLikeToOrigin } from './index'
|
||||||
|
import { sendCreateDislikeToOrigin, sendCreateDislikeToVideoFollowers } from './send/send-create'
|
||||||
|
import { sendLikeToVideoFollowers } from './send/send-like'
|
||||||
|
import {
|
||||||
|
sendUndoDislikeToOrigin,
|
||||||
|
sendUndoDislikeToVideoFollowers,
|
||||||
|
sendUndoLikeToOrigin,
|
||||||
|
sendUndoLikeToVideoFollowers
|
||||||
|
} from './send/send-undo'
|
||||||
|
|
||||||
function fetchRemoteVideoPreview (video: VideoInstance) {
|
function fetchRemoteVideoPreview (video: VideoInstance) {
|
||||||
// FIXME: use url
|
// FIXME: use url
|
||||||
|
@ -37,8 +48,42 @@ function generateThumbnailFromUrl (video: VideoInstance, icon: ActivityIconObjec
|
||||||
return doRequestAndSaveToFile(options, thumbnailPath)
|
return doRequestAndSaveToFile(options, thumbnailPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sendVideoRateChangeToFollowers (account: AccountInstance, video: VideoInstance, likes: number, dislikes: number, t: Transaction) {
|
||||||
|
const tasks: Promise<any>[] = []
|
||||||
|
|
||||||
|
// Undo Like
|
||||||
|
if (likes < 0) tasks.push(sendUndoLikeToVideoFollowers(account, video, t))
|
||||||
|
// Like
|
||||||
|
if (likes > 0) tasks.push(sendLikeToVideoFollowers(account, video, t))
|
||||||
|
|
||||||
|
// Undo Dislike
|
||||||
|
if (dislikes < 0) tasks.push(sendUndoDislikeToVideoFollowers(account, video, t))
|
||||||
|
// Dislike
|
||||||
|
if (dislikes > 0) tasks.push(sendCreateDislikeToVideoFollowers(account, video, t))
|
||||||
|
|
||||||
|
return Promise.all(tasks)
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendVideoRateChangeToOrigin (account: AccountInstance, video: VideoInstance, likes: number, dislikes: number, t: Transaction) {
|
||||||
|
const tasks: Promise<any>[] = []
|
||||||
|
|
||||||
|
// Undo Like
|
||||||
|
if (likes < 0) tasks.push(sendUndoLikeToOrigin(account, video, t))
|
||||||
|
// Like
|
||||||
|
if (likes > 0) tasks.push(sendLikeToOrigin(account, video, t))
|
||||||
|
|
||||||
|
// Undo Dislike
|
||||||
|
if (dislikes < 0) tasks.push(sendUndoDislikeToOrigin(account, video, t))
|
||||||
|
// Dislike
|
||||||
|
if (dislikes > 0) tasks.push(sendCreateDislikeToOrigin(account, video, t))
|
||||||
|
|
||||||
|
return Promise.all(tasks)
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
fetchRemoteVideoPreview,
|
fetchRemoteVideoPreview,
|
||||||
fetchRemoteVideoDescription,
|
fetchRemoteVideoDescription,
|
||||||
generateThumbnailFromUrl
|
generateThumbnailFromUrl,
|
||||||
|
sendVideoRateChangeToFollowers,
|
||||||
|
sendVideoRateChangeToOrigin
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { logger } from '../../../helpers'
|
||||||
import { buildSignedActivity } from '../../../helpers/activitypub'
|
import { buildSignedActivity } from '../../../helpers/activitypub'
|
||||||
import { doRequest } from '../../../helpers/requests'
|
import { doRequest } from '../../../helpers/requests'
|
||||||
import { database as db } from '../../../initializers'
|
import { database as db } from '../../../initializers'
|
||||||
import { ActivityPubHttpPayload } from './activitypub-http-job-scheduler'
|
import { ActivityPubHttpPayload, maybeRetryRequestLater } from './activitypub-http-job-scheduler'
|
||||||
|
|
||||||
async function process (payload: ActivityPubHttpPayload, jobId: number) {
|
async function process (payload: ActivityPubHttpPayload, jobId: number) {
|
||||||
logger.info('Processing ActivityPub broadcast in job %d.', jobId)
|
logger.info('Processing ActivityPub broadcast in job %d.', jobId)
|
||||||
|
@ -20,7 +20,12 @@ async function process (payload: ActivityPubHttpPayload, jobId: number) {
|
||||||
|
|
||||||
for (const uri of payload.uris) {
|
for (const uri of payload.uris) {
|
||||||
options.uri = uri
|
options.uri = uri
|
||||||
await doRequest(options)
|
|
||||||
|
try {
|
||||||
|
await doRequest(options)
|
||||||
|
} catch (err) {
|
||||||
|
await maybeRetryRequestLater(err, payload, uri)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,16 @@ import * as activitypubHttpBroadcastHandler from './activitypub-http-broadcast-h
|
||||||
import * as activitypubHttpUnicastHandler from './activitypub-http-unicast-handler'
|
import * as activitypubHttpUnicastHandler from './activitypub-http-unicast-handler'
|
||||||
import * as activitypubHttpFetcherHandler from './activitypub-http-fetcher-handler'
|
import * as activitypubHttpFetcherHandler from './activitypub-http-fetcher-handler'
|
||||||
import { JobCategory } from '../../../../shared'
|
import { JobCategory } from '../../../../shared'
|
||||||
|
import { ACTIVITY_PUB } from '../../../initializers/constants'
|
||||||
|
import { logger } from '../../../helpers/logger'
|
||||||
|
|
||||||
type ActivityPubHttpPayload = {
|
type ActivityPubHttpPayload = {
|
||||||
uris: string[]
|
uris: string[]
|
||||||
signatureAccountId?: number
|
signatureAccountId?: number
|
||||||
body?: any
|
body?: any
|
||||||
|
attemptNumber?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const jobHandlers: { [ handlerName: string ]: JobHandler<ActivityPubHttpPayload, void> } = {
|
const jobHandlers: { [ handlerName: string ]: JobHandler<ActivityPubHttpPayload, void> } = {
|
||||||
activitypubHttpBroadcastHandler,
|
activitypubHttpBroadcastHandler,
|
||||||
activitypubHttpUnicastHandler,
|
activitypubHttpUnicastHandler,
|
||||||
|
@ -19,7 +23,25 @@ const jobCategory: JobCategory = 'activitypub-http'
|
||||||
|
|
||||||
const activitypubHttpJobScheduler = new JobScheduler(jobCategory, jobHandlers)
|
const activitypubHttpJobScheduler = new JobScheduler(jobCategory, jobHandlers)
|
||||||
|
|
||||||
|
function maybeRetryRequestLater (err: Error, payload: ActivityPubHttpPayload, uri: string) {
|
||||||
|
logger.warn('Cannot make request to %s.', uri, err)
|
||||||
|
|
||||||
|
let attemptNumber = payload.attemptNumber || 1
|
||||||
|
attemptNumber += 1
|
||||||
|
|
||||||
|
if (attemptNumber < ACTIVITY_PUB.MAX_HTTP_ATTEMPT) {
|
||||||
|
logger.debug('Retrying request to %s (attempt %d/%d).', uri, attemptNumber, ACTIVITY_PUB.MAX_HTTP_ATTEMPT, err)
|
||||||
|
|
||||||
|
const newPayload = Object.assign(payload, {
|
||||||
|
uris: [ uri ],
|
||||||
|
attemptNumber
|
||||||
|
})
|
||||||
|
return activitypubHttpJobScheduler.createJob(undefined, 'activitypubHttpUnicastHandler', newPayload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
ActivityPubHttpPayload,
|
ActivityPubHttpPayload,
|
||||||
activitypubHttpJobScheduler
|
activitypubHttpJobScheduler,
|
||||||
|
maybeRetryRequestLater
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { logger } from '../../../helpers'
|
import { logger } from '../../../helpers'
|
||||||
import { doRequest } from '../../../helpers/requests'
|
import { doRequest } from '../../../helpers/requests'
|
||||||
import { ActivityPubHttpPayload } from './activitypub-http-job-scheduler'
|
import { ActivityPubHttpPayload, maybeRetryRequestLater } from './activitypub-http-job-scheduler'
|
||||||
import { database as db } from '../../../initializers/database'
|
import { database as db } from '../../../initializers/database'
|
||||||
import { buildSignedActivity } from '../../../helpers/activitypub'
|
import { buildSignedActivity } from '../../../helpers/activitypub'
|
||||||
|
|
||||||
|
@ -18,7 +18,12 @@ async function process (payload: ActivityPubHttpPayload, jobId: number) {
|
||||||
json: signedBody
|
json: signedBody
|
||||||
}
|
}
|
||||||
|
|
||||||
await doRequest(options)
|
try {
|
||||||
|
await doRequest(options)
|
||||||
|
} catch (err) {
|
||||||
|
await maybeRetryRequestLater(err, payload, uri)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onError (err: Error, jobId: number) {
|
function onError (err: Error, jobId: number) {
|
||||||
|
|
|
@ -5,7 +5,11 @@ import { ResultList } from '../../../shared/models/result-list.model'
|
||||||
import { AccountInstance } from './account-interface'
|
import { AccountInstance } from './account-interface'
|
||||||
|
|
||||||
export namespace AccountFollowMethods {
|
export namespace AccountFollowMethods {
|
||||||
export type LoadByAccountAndTarget = (accountId: number, targetAccountId: number) => Bluebird<AccountFollowInstance>
|
export type LoadByAccountAndTarget = (
|
||||||
|
accountId: number,
|
||||||
|
targetAccountId: number,
|
||||||
|
t?: Sequelize.Transaction
|
||||||
|
) => Bluebird<AccountFollowInstance>
|
||||||
|
|
||||||
export type ListFollowingForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList<AccountFollowInstance>>
|
export type ListFollowingForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList<AccountFollowInstance>>
|
||||||
export type ListFollowersForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList<AccountFollowInstance>>
|
export type ListFollowersForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList<AccountFollowInstance>>
|
||||||
|
|
|
@ -93,7 +93,7 @@ toFormattedJSON = function (this: AccountFollowInstance) {
|
||||||
return json
|
return json
|
||||||
}
|
}
|
||||||
|
|
||||||
loadByAccountAndTarget = function (accountId: number, targetAccountId: number) {
|
loadByAccountAndTarget = function (accountId: number, targetAccountId: number, t?: Sequelize.Transaction) {
|
||||||
const query = {
|
const query = {
|
||||||
where: {
|
where: {
|
||||||
accountId,
|
accountId,
|
||||||
|
@ -110,7 +110,8 @@ loadByAccountAndTarget = function (accountId: number, targetAccountId: number) {
|
||||||
required: true,
|
required: true,
|
||||||
as: 'AccountFollowing'
|
as: 'AccountFollowing'
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
transaction: t
|
||||||
}
|
}
|
||||||
|
|
||||||
return AccountFollow.findOne(query)
|
return AccountFollow.findOne(query)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Order of the tests we want to execute
|
// Order of the tests we want to execute
|
||||||
// import './multiple-servers'
|
// import './multiple-servers'
|
||||||
import './video-transcoder'
|
import './video-transcoder'
|
||||||
|
import './multiple-servers'
|
||||||
import './follows'
|
import './follows'
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import { ActivityPubSignature } from './activitypub-signature'
|
import { ActivityPubSignature } from './activitypub-signature'
|
||||||
import { VideoChannelObject, VideoTorrentObject } from './objects'
|
import { VideoChannelObject, VideoTorrentObject } from './objects'
|
||||||
|
import { DislikeObject } from './objects/dislike-object'
|
||||||
import { VideoAbuseObject } from './objects/video-abuse-object'
|
import { VideoAbuseObject } from './objects/video-abuse-object'
|
||||||
import { ViewObject } from './objects/view-object'
|
import { ViewObject } from './objects/view-object'
|
||||||
|
|
||||||
export type Activity = ActivityCreate | ActivityAdd | ActivityUpdate |
|
export type Activity = ActivityCreate | ActivityAdd | ActivityUpdate |
|
||||||
ActivityDelete | ActivityFollow | ActivityAccept | ActivityAnnounce |
|
ActivityDelete | ActivityFollow | ActivityAccept | ActivityAnnounce |
|
||||||
ActivityUndo
|
ActivityUndo | ActivityLike
|
||||||
|
|
||||||
export type ActivityType = 'Create' | 'Add' | 'Update' | 'Delete' | 'Follow' | 'Accept' | 'Announce' | 'Undo'
|
export type ActivityType = 'Create' | 'Add' | 'Update' | 'Delete' | 'Follow' | 'Accept' | 'Announce' | 'Undo' | 'Like'
|
||||||
|
|
||||||
export interface BaseActivity {
|
export interface BaseActivity {
|
||||||
'@context'?: any[]
|
'@context'?: any[]
|
||||||
|
@ -21,7 +22,7 @@ export interface BaseActivity {
|
||||||
|
|
||||||
export interface ActivityCreate extends BaseActivity {
|
export interface ActivityCreate extends BaseActivity {
|
||||||
type: 'Create'
|
type: 'Create'
|
||||||
object: VideoChannelObject | VideoAbuseObject | ViewObject
|
object: VideoChannelObject | VideoAbuseObject | ViewObject | DislikeObject
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ActivityAdd extends BaseActivity {
|
export interface ActivityAdd extends BaseActivity {
|
||||||
|
@ -55,5 +56,10 @@ export interface ActivityAnnounce extends BaseActivity {
|
||||||
|
|
||||||
export interface ActivityUndo extends BaseActivity {
|
export interface ActivityUndo extends BaseActivity {
|
||||||
type: 'Undo',
|
type: 'Undo',
|
||||||
object: ActivityFollow
|
object: ActivityFollow | ActivityLike | ActivityCreate
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ActivityLike extends BaseActivity {
|
||||||
|
type: 'Like',
|
||||||
|
object: string
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
export interface DislikeObject {
|
||||||
|
type: 'Dislike',
|
||||||
|
actor: string
|
||||||
|
object: string
|
||||||
|
}
|
|
@ -3,3 +3,4 @@ export * from './video-abuse-object'
|
||||||
export * from './video-channel-object'
|
export * from './video-channel-object'
|
||||||
export * from './video-torrent-object'
|
export * from './video-torrent-object'
|
||||||
export * from './view-object'
|
export * from './view-object'
|
||||||
|
export * from './dislike-object'
|
||||||
|
|
Loading…
Reference in New Issue