mirror of https://github.com/Chocobozzz/PeerTube
Federate likes/dislikes
parent
d52eb8f656
commit
0032ebe94a
|
@ -3,6 +3,7 @@ import { UserVideoRateUpdate } from '../../../../shared'
|
|||
import { logger, retryTransactionWrapper } from '../../../helpers'
|
||||
import { VIDEO_RATE_TYPES } from '../../../initializers'
|
||||
import { database as db } from '../../../initializers/database'
|
||||
import { sendVideoRateChangeToFollowers, sendVideoRateChangeToOrigin } from '../../../lib/activitypub/videos'
|
||||
import { asyncMiddleware, authenticate, videoRateValidator } from '../../../middlewares'
|
||||
import { AccountInstance } from '../../../models/account/account-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
|
||||
await videoInstance.increment(incrementQuery, sequelizeOptions)
|
||||
|
||||
if (videoInstance.isOwned() === false) {
|
||||
// TODO: Send a event to original server
|
||||
if (videoInstance.isOwned()) {
|
||||
await sendVideoRateChangeToFollowers(accountInstance, videoInstance, likesToIncrement, dislikesToIncrement, t)
|
||||
} else {
|
||||
// TODO: Send update to followers
|
||||
await sendVideoRateChangeToOrigin(accountInstance, videoInstance, likesToIncrement, dislikesToIncrement, t)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import * as validator from 'validator'
|
||||
import { Activity, ActivityType } from '../../../../shared/models/activitypub/activity'
|
||||
import { isAccountAcceptActivityValid, isAccountDeleteActivityValid, isAccountFollowActivityValid } from './account'
|
||||
import { isAnnounceValid } from './announce'
|
||||
import { isAnnounceActivityValid } from './announce'
|
||||
import { isActivityPubUrlValid } from './misc'
|
||||
import { isUndoValid } from './undo'
|
||||
import { isUndoActivityValid } from './undo'
|
||||
import { isVideoChannelCreateActivityValid, isVideoChannelDeleteActivityValid, isVideoChannelUpdateActivityValid } from './video-channels'
|
||||
import {
|
||||
isVideoFlagValid,
|
||||
|
@ -12,6 +12,7 @@ import {
|
|||
isVideoTorrentUpdateActivityValid
|
||||
} from './videos'
|
||||
import { isViewActivityValid } from './view'
|
||||
import { isDislikeActivityValid, isLikeActivityValid } from './rate'
|
||||
|
||||
function isRootActivityValid (activity: any) {
|
||||
return Array.isArray(activity['@context']) &&
|
||||
|
@ -34,7 +35,8 @@ const activityCheckers: { [ P in ActivityType ]: (activity: Activity) => boolean
|
|||
Follow: checkFollowActivity,
|
||||
Accept: checkAcceptActivity,
|
||||
Announce: checkAnnounceActivity,
|
||||
Undo: checkUndoActivity
|
||||
Undo: checkUndoActivity,
|
||||
Like: checkLikeActivity
|
||||
}
|
||||
|
||||
function isActivityValid (activity: any) {
|
||||
|
@ -55,9 +57,10 @@ export {
|
|||
// ---------------------------------------------------------------------------
|
||||
|
||||
function checkCreateActivity (activity: any) {
|
||||
return isVideoChannelCreateActivityValid(activity) ||
|
||||
isVideoFlagValid(activity) ||
|
||||
isViewActivityValid(activity)
|
||||
return isViewActivityValid(activity) ||
|
||||
isDislikeActivityValid(activity) ||
|
||||
isVideoChannelCreateActivityValid(activity) ||
|
||||
isVideoFlagValid(activity)
|
||||
}
|
||||
|
||||
function checkAddActivity (activity: any) {
|
||||
|
@ -84,9 +87,13 @@ function checkAcceptActivity (activity: any) {
|
|||
}
|
||||
|
||||
function checkAnnounceActivity (activity: any) {
|
||||
return isAnnounceValid(activity)
|
||||
return isAnnounceActivityValid(activity)
|
||||
}
|
||||
|
||||
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 { isVideoChannelCreateActivityValid } from './video-channels'
|
||||
|
||||
function isAnnounceValid (activity: any) {
|
||||
function isAnnounceActivityValid (activity: any) {
|
||||
return isBaseActivityValid(activity, 'Announce') &&
|
||||
(
|
||||
isVideoChannelCreateActivityValid(activity.object) ||
|
||||
|
@ -11,5 +11,5 @@ function isAnnounceValid (activity: any) {
|
|||
}
|
||||
|
||||
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 { isBaseActivityValid } from './misc'
|
||||
import { isDislikeActivityValid, isLikeActivityValid } from './rate'
|
||||
|
||||
function isUndoValid (activity: any) {
|
||||
function isUndoActivityValid (activity: any) {
|
||||
return isBaseActivityValid(activity, 'Undo') &&
|
||||
(
|
||||
isAccountFollowActivityValid(activity.object)
|
||||
isAccountFollowActivityValid(activity.object) ||
|
||||
isLikeActivityValid(activity.object) ||
|
||||
isDislikeActivityValid(activity.object)
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
isUndoValid
|
||||
isUndoActivityValid
|
||||
}
|
||||
|
|
|
@ -229,6 +229,7 @@ const ACTIVITY_PUB = {
|
|||
PUBLIC: 'https://www.w3.org/ns/activitystreams#Public',
|
||||
COLLECTION_ITEMS_PER_PAGE: 10,
|
||||
FETCH_PAGE_LIMIT: 100,
|
||||
MAX_HTTP_ATTEMPT: 5,
|
||||
URL_MIME_TYPES: {
|
||||
VIDEO: [ 'video/mp4', 'video/webm', 'video/ogg' ], // TODO: Merge with VIDEO_MIMETYPE_EXT
|
||||
TORRENT: [ 'application/x-bittorrent' ],
|
||||
|
|
|
@ -5,5 +5,6 @@ export * from './process-announce'
|
|||
export * from './process-create'
|
||||
export * from './process-delete'
|
||||
export * from './process-follow'
|
||||
export * from './process-like'
|
||||
export * from './process-undo'
|
||||
export * from './process-update'
|
||||
|
|
|
@ -5,9 +5,10 @@ import { logger, retryTransactionWrapper } from '../../../helpers'
|
|||
import { database as db } from '../../../initializers'
|
||||
import { AccountInstance } from '../../../models/account/account-interface'
|
||||
import { getOrCreateAccountAndServer } from '../account'
|
||||
import { sendCreateViewToVideoFollowers } from '../send/send-create'
|
||||
import { sendCreateDislikeToVideoFollowers, sendCreateViewToVideoFollowers } from '../send/send-create'
|
||||
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,6 +17,8 @@ async function processCreateActivity (activity: ActivityCreate) {
|
|||
|
||||
if (activityType === 'View') {
|
||||
return processCreateView(activityObject as ViewObject)
|
||||
} else if (activityType === 'Dislike') {
|
||||
return processCreateDislike(account, activityObject as DislikeObject)
|
||||
} else if (activityType === 'VideoChannel') {
|
||||
return processCreateVideoChannel(account, activityObject as VideoChannelObject)
|
||||
} 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) {
|
||||
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 { 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) {
|
||||
const activityToUndo = activity.object
|
||||
|
||||
if (activityToUndo.type === 'Follow') {
|
||||
const follower = await db.Account.loadByUrl(activity.actor)
|
||||
const following = await db.Account.loadByUrl(activityToUndo.object)
|
||||
const accountFollow = await db.AccountFollow.loadByAccountAndTarget(follower.id, following.id)
|
||||
|
||||
if (!accountFollow) throw new Error(`'Unknown account follow ${follower.id} -> ${following.id}.`)
|
||||
|
||||
await accountFollow.destroy()
|
||||
|
||||
return undefined
|
||||
if (activityToUndo.type === 'Like') {
|
||||
return processUndoLike(activity.actor, activityToUndo)
|
||||
} else if (activityToUndo.type === 'Create' && activityToUndo.object.type === 'Dislike') {
|
||||
return processUndoDislike(activity.actor, activityToUndo.object)
|
||||
} else if (activityToUndo.type === 'Follow') {
|
||||
return processUndoFollow(activity.actor, activityToUndo)
|
||||
}
|
||||
|
||||
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 { logger } from '../../../helpers/logger'
|
||||
import { AccountInstance } from '../../../models/account/account-interface'
|
||||
import { processAcceptActivity } from './process-accept'
|
||||
import { processAddActivity } from './process-add'
|
||||
|
@ -6,9 +7,9 @@ import { processAnnounceActivity } from './process-announce'
|
|||
import { processCreateActivity } from './process-create'
|
||||
import { processDeleteActivity } from './process-delete'
|
||||
import { processFollowActivity } from './process-follow'
|
||||
import { processLikeActivity } from './process-like'
|
||||
import { processUndoActivity } from './process-undo'
|
||||
import { processUpdateActivity } from './process-update'
|
||||
import { logger } from '../../../helpers/logger'
|
||||
|
||||
const processActivity: { [ P in ActivityType ]: (activity: Activity, inboxAccount?: AccountInstance) => Promise<any> } = {
|
||||
Create: processCreateActivity,
|
||||
|
@ -18,7 +19,8 @@ const processActivity: { [ P in ActivityType ]: (activity: Activity, inboxAccoun
|
|||
Follow: processFollowActivity,
|
||||
Accept: processAcceptActivity,
|
||||
Announce: processAnnounceActivity,
|
||||
Undo: processUndoActivity
|
||||
Undo: processUndoActivity,
|
||||
Like: processLikeActivity
|
||||
}
|
||||
|
||||
async function processActivities (activities: Activity[], inboxAccount?: AccountInstance) {
|
||||
|
|
|
@ -4,4 +4,6 @@ export * from './send-announce'
|
|||
export * from './send-create'
|
||||
export * from './send-delete'
|
||||
export * from './send-follow'
|
||||
export * from './send-like'
|
||||
export * from './send-undo'
|
||||
export * from './send-update'
|
||||
|
|
|
@ -3,6 +3,7 @@ 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 { VideoInstance } from '../../../models/video/video-interface'
|
||||
|
||||
async function broadcastToFollowers (
|
||||
data: any,
|
||||
|
@ -41,6 +42,27 @@ async function unicastTo (data: any, byAccount: AccountInstance, toAccountUrl: s
|
|||
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) {
|
||||
const followerInboxUrls = await accountSender.getFollowerSharedInboxUrls()
|
||||
|
||||
|
@ -64,5 +86,8 @@ async function getAudience (accountSender: AccountInstance, isPublic = true) {
|
|||
export {
|
||||
broadcastToFollowers,
|
||||
unicastTo,
|
||||
getAudience
|
||||
getAudience,
|
||||
getOriginVideoAudience,
|
||||
getAccountsToForwardVideoAction,
|
||||
getVideoFollowersAudience
|
||||
}
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
import { Transaction } from 'sequelize'
|
||||
import { 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 { broadcastToFollowers, getAudience, unicastTo } from './misc'
|
||||
import { getVideoAbuseActivityPubUrl, getVideoViewActivityPubUrl } from '../url'
|
||||
import { getServerAccount } from '../../../helpers/utils'
|
||||
import { database as db } from '../../../initializers'
|
||||
import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url'
|
||||
import {
|
||||
broadcastToFollowers,
|
||||
getAccountsToForwardVideoAction,
|
||||
getAudience,
|
||||
getOriginVideoAudience,
|
||||
getVideoFollowersAudience,
|
||||
unicastTo
|
||||
} from './misc'
|
||||
|
||||
async function sendCreateVideoChannel (videoChannel: VideoChannelInstance, t: Transaction) {
|
||||
const byAccount = videoChannel.Account
|
||||
|
@ -29,7 +35,7 @@ async function sendCreateViewToOrigin (byAccount: AccountInstance, video: VideoI
|
|||
const url = getVideoViewActivityPubUrl(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)
|
||||
|
||||
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 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 serverAccount = await getServerAccount()
|
||||
const accountsToForwardView = await db.VideoShare.loadAccountsByShare(video.id)
|
||||
accountsToForwardView.push(video.VideoChannel.Account)
|
||||
const accountsToForwardView = await getAccountsToForwardVideoAction(byAccount, video)
|
||||
|
||||
// Don't forward view to server that sent it to us
|
||||
const index = accountsToForwardView.findIndex(a => a.id === byAccount.id)
|
||||
if (index) accountsToForwardView.splice(index, 1)
|
||||
const followersException = [ byAccount ]
|
||||
return broadcastToFollowers(data, serverAccount, accountsToForwardView, t, followersException)
|
||||
}
|
||||
|
||||
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 ]
|
||||
return broadcastToFollowers(data, serverAccount, accountsToForwardView, t, followersException)
|
||||
|
@ -71,6 +96,16 @@ async function createActivityData (url: string, byAccount: AccountInstance, obje
|
|||
return activity
|
||||
}
|
||||
|
||||
function createDislikeActivityData (byAccount: AccountInstance, video: VideoInstance) {
|
||||
const obj = {
|
||||
type: 'Dislike',
|
||||
actor: byAccount.url,
|
||||
object: video.url
|
||||
}
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
|
@ -78,7 +113,10 @@ export {
|
|||
sendVideoAbuse,
|
||||
createActivityData,
|
||||
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 { ActivityFollow, ActivityUndo } from '../../../../shared/models/activitypub/activity'
|
||||
import { ActivityCreate, ActivityFollow, ActivityLike, ActivityUndo } from '../../../../shared/models/activitypub/activity'
|
||||
import { AccountInstance } from '../../../models'
|
||||
import { AccountFollowInstance } from '../../../models/account/account-follow-interface'
|
||||
import { unicastTo } from './misc'
|
||||
import { broadcastToFollowers, getAccountsToForwardVideoAction, unicastTo } from './misc'
|
||||
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) {
|
||||
const me = accountFollow.AccountFollower
|
||||
|
@ -19,15 +23,72 @@ async function sendUndoFollow (accountFollow: AccountFollowInstance, t: Transact
|
|||
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 {
|
||||
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 = {
|
||||
type: 'Undo',
|
||||
id: url,
|
||||
|
|
|
@ -25,6 +25,14 @@ function getVideoViewActivityPubUrl (byAccount: AccountInstance, video: VideoIns
|
|||
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) {
|
||||
const me = accountFollow.AccountFollower
|
||||
const following = accountFollow.AccountFollowing
|
||||
|
@ -61,5 +69,7 @@ export {
|
|||
getAnnounceActivityPubUrl,
|
||||
getUpdateActivityPubUrl,
|
||||
getUndoActivityPubUrl,
|
||||
getVideoViewActivityPubUrl
|
||||
getVideoViewActivityPubUrl,
|
||||
getVideoLikeActivityPubUrl,
|
||||
getVideoDislikeActivityPubUrl
|
||||
}
|
||||
|
|
|
@ -1,9 +1,20 @@
|
|||
import { join } from 'path'
|
||||
import * as request from 'request'
|
||||
import { Transaction } from 'sequelize'
|
||||
import { ActivityIconObject } from '../../../shared/index'
|
||||
import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests'
|
||||
import { CONFIG, REMOTE_SCHEME, STATIC_PATHS } from '../../initializers/constants'
|
||||
import { AccountInstance } from '../../models/account/account-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) {
|
||||
// FIXME: use url
|
||||
|
@ -37,8 +48,42 @@ function generateThumbnailFromUrl (video: VideoInstance, icon: ActivityIconObjec
|
|||
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 {
|
||||
fetchRemoteVideoPreview,
|
||||
fetchRemoteVideoDescription,
|
||||
generateThumbnailFromUrl
|
||||
generateThumbnailFromUrl,
|
||||
sendVideoRateChangeToFollowers,
|
||||
sendVideoRateChangeToOrigin
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { logger } from '../../../helpers'
|
|||
import { buildSignedActivity } from '../../../helpers/activitypub'
|
||||
import { doRequest } from '../../../helpers/requests'
|
||||
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) {
|
||||
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) {
|
||||
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 activitypubHttpFetcherHandler from './activitypub-http-fetcher-handler'
|
||||
import { JobCategory } from '../../../../shared'
|
||||
import { ACTIVITY_PUB } from '../../../initializers/constants'
|
||||
import { logger } from '../../../helpers/logger'
|
||||
|
||||
type ActivityPubHttpPayload = {
|
||||
uris: string[]
|
||||
signatureAccountId?: number
|
||||
body?: any
|
||||
attemptNumber?: number
|
||||
}
|
||||
|
||||
const jobHandlers: { [ handlerName: string ]: JobHandler<ActivityPubHttpPayload, void> } = {
|
||||
activitypubHttpBroadcastHandler,
|
||||
activitypubHttpUnicastHandler,
|
||||
|
@ -19,7 +23,25 @@ const jobCategory: JobCategory = 'activitypub-http'
|
|||
|
||||
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 {
|
||||
ActivityPubHttpPayload,
|
||||
activitypubHttpJobScheduler
|
||||
activitypubHttpJobScheduler,
|
||||
maybeRetryRequestLater
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { logger } from '../../../helpers'
|
||||
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 { buildSignedActivity } from '../../../helpers/activitypub'
|
||||
|
||||
|
@ -18,7 +18,12 @@ async function process (payload: ActivityPubHttpPayload, jobId: number) {
|
|||
json: signedBody
|
||||
}
|
||||
|
||||
await doRequest(options)
|
||||
try {
|
||||
await doRequest(options)
|
||||
} catch (err) {
|
||||
await maybeRetryRequestLater(err, payload, uri)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
function onError (err: Error, jobId: number) {
|
||||
|
|
|
@ -5,7 +5,11 @@ import { ResultList } from '../../../shared/models/result-list.model'
|
|||
import { AccountInstance } from './account-interface'
|
||||
|
||||
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 ListFollowersForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList<AccountFollowInstance>>
|
||||
|
|
|
@ -93,7 +93,7 @@ toFormattedJSON = function (this: AccountFollowInstance) {
|
|||
return json
|
||||
}
|
||||
|
||||
loadByAccountAndTarget = function (accountId: number, targetAccountId: number) {
|
||||
loadByAccountAndTarget = function (accountId: number, targetAccountId: number, t?: Sequelize.Transaction) {
|
||||
const query = {
|
||||
where: {
|
||||
accountId,
|
||||
|
@ -110,7 +110,8 @@ loadByAccountAndTarget = function (accountId: number, targetAccountId: number) {
|
|||
required: true,
|
||||
as: 'AccountFollowing'
|
||||
}
|
||||
]
|
||||
],
|
||||
transaction: t
|
||||
}
|
||||
|
||||
return AccountFollow.findOne(query)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// Order of the tests we want to execute
|
||||
// import './multiple-servers'
|
||||
import './video-transcoder'
|
||||
import './multiple-servers'
|
||||
import './follows'
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import { ActivityPubSignature } from './activitypub-signature'
|
||||
import { VideoChannelObject, VideoTorrentObject } from './objects'
|
||||
import { DislikeObject } from './objects/dislike-object'
|
||||
import { VideoAbuseObject } from './objects/video-abuse-object'
|
||||
import { ViewObject } from './objects/view-object'
|
||||
|
||||
export type Activity = ActivityCreate | ActivityAdd | ActivityUpdate |
|
||||
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 {
|
||||
'@context'?: any[]
|
||||
|
@ -21,7 +22,7 @@ export interface BaseActivity {
|
|||
|
||||
export interface ActivityCreate extends BaseActivity {
|
||||
type: 'Create'
|
||||
object: VideoChannelObject | VideoAbuseObject | ViewObject
|
||||
object: VideoChannelObject | VideoAbuseObject | ViewObject | DislikeObject
|
||||
}
|
||||
|
||||
export interface ActivityAdd extends BaseActivity {
|
||||
|
@ -55,5 +56,10 @@ export interface ActivityAnnounce extends BaseActivity {
|
|||
|
||||
export interface ActivityUndo extends BaseActivity {
|
||||
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-torrent-object'
|
||||
export * from './view-object'
|
||||
export * from './dislike-object'
|
||||
|
|
Loading…
Reference in New Issue