import { Activity, ActivityPubCollection, ActivityPubOrderedCollection, ActivityType, RootActivity } from '../../../shared'
import { logger } from '../../helpers'
import { isActivityValid } from '../../helpers/custom-validators/activitypub/activity'
-import { processCreateActivity, processUpdateActivity } from '../../lib'
-import { processAcceptActivity } from '../../lib/activitypub/process-accept'
-import { processAddActivity } from '../../lib/activitypub/process-add'
-import { processAnnounceActivity } from '../../lib/activitypub/process-announce'
-import { processDeleteActivity } from '../../lib/activitypub/process-delete'
-import { processFollowActivity } from '../../lib/activitypub/process-follow'
+import { processCreateActivity, processUpdateActivity, processUndoActivity } from '../../lib'
+import { processAcceptActivity } from '../../lib/activitypub/process/process-accept'
+import { processAddActivity } from '../../lib/activitypub/process/process-add'
+import { processAnnounceActivity } from '../../lib/activitypub/process/process-announce'
+import { processDeleteActivity } from '../../lib/activitypub/process/process-delete'
+import { processFollowActivity } from '../../lib/activitypub/process/process-follow'
import { asyncMiddleware, checkSignature, localAccountValidator, signatureValidator } from '../../middlewares'
import { activityPubValidator } from '../../middlewares/validators/activitypub/activity'
import { AccountInstance } from '../../models/account/account-interface'
Delete: processDeleteActivity,
Follow: processFollowActivity,
Accept: processAcceptActivity,
- Announce: processAnnounceActivity
+ Announce: processAnnounceActivity,
+ Undo: processUndoActivity
}
const inboxRouter = express.Router()
import { getAccountFromWebfinger } from '../../../helpers/webfinger'
import { SERVER_ACCOUNT_NAME } from '../../../initializers/constants'
import { database as db } from '../../../initializers/database'
-import { sendFollow } from '../../../lib/activitypub/send-request'
-import { asyncMiddleware, paginationValidator, setFollowersSort, setPagination } from '../../../middlewares'
+import { asyncMiddleware, paginationValidator, removeFollowingValidator, setFollowersSort, setPagination } from '../../../middlewares'
import { authenticate } from '../../../middlewares/oauth'
import { setBodyHostsPort } from '../../../middlewares/servers'
import { setFollowingSort } from '../../../middlewares/sort'
import { ensureUserHasRight } from '../../../middlewares/user-right'
-import { followValidator } from '../../../middlewares/validators/servers'
+import { followValidator } from '../../../middlewares/validators/follows'
import { followersSortValidator, followingSortValidator } from '../../../middlewares/validators/sort'
+import { AccountFollowInstance } from '../../../models/index'
+import { sendFollow } from '../../../lib/index'
+import { sendUndoFollow } from '../../../lib/activitypub/send/send-undo'
const serverFollowsRouter = express.Router()
asyncMiddleware(follow)
)
+serverFollowsRouter.delete('/following/:accountId',
+ authenticate,
+ ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW),
+ removeFollowingValidator,
+ asyncMiddleware(removeFollow)
+)
+
serverFollowsRouter.get('/followers',
paginationValidator,
followersSortValidator,
},
transaction: t
})
+ accountFollow.AccountFollowing = targetAccount
+ accountFollow.AccountFollower = fromAccount
// Send a notification to remote server
if (accountFollow.state === 'pending') {
- await sendFollow(fromAccount, targetAccount, t)
+ await sendFollow(accountFollow, t)
}
})
})
return res.status(204).end()
}
+async function removeFollow (req: express.Request, res: express.Response, next: express.NextFunction) {
+ const following: AccountFollowInstance = res.locals.following
+
+ await db.sequelize.transaction(async t => {
+ await sendUndoFollow(following, t)
+ await following.destroy({ transaction: t })
+ })
+
+ return res.status(204).end()
+}
+
async function loadLocalOrGetAccountFromWebfinger (name: string, host: string) {
let loadedFromDB = true
let account = await db.Account.loadByNameAndHost(name, host)
videoChannelsUpdateValidator
} from '../../../middlewares'
import { AccountInstance, VideoChannelInstance } from '../../../models'
-import { sendUpdateVideoChannel } from '../../../lib/activitypub/send-request'
+import { sendUpdateVideoChannel } from '../../../lib/activitypub/send/send-update'
const videoChannelRouter = express.Router()
if (videoChannelInfoToUpdate.name !== undefined) videoChannelInstance.set('name', videoChannelInfoToUpdate.name)
if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.set('description', videoChannelInfoToUpdate.description)
- await videoChannelInstance.save(sequelizeOptions)
+ const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions)
- await sendUpdateVideoChannel(videoChannelInstance, t)
+ await sendUpdateVideoChannel(videoChannelInstanceUpdated, t)
})
logger.info('Video channel with name %s and uuid %s updated.', videoChannelInstance.name, videoChannelInstance.uuid)
resetSequelizeInstance,
retryTransactionWrapper
} from '../../../helpers'
-import { getActivityPubUrl, shareVideoByServer } from '../../../helpers/activitypub'
+import { getVideoActivityPubUrl, shareVideoByServer } from '../../../helpers/activitypub'
import { CONFIG, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_MIMETYPE_EXT, VIDEO_PRIVACIES } from '../../../initializers'
import { database as db } from '../../../initializers/database'
-import { sendAddVideo, sendUpdateVideo } from '../../../lib/activitypub/send-request'
+import { sendAddVideo } from '../../../lib/activitypub/send/send-add'
+import { sendUpdateVideo } from '../../../lib/activitypub/send/send-update'
import { transcodingJobScheduler } from '../../../lib/jobs/transcoding-job-scheduler/transcoding-job-scheduler'
import {
asyncMiddleware,
channelId: res.locals.videoChannel.id
}
const video = db.Video.build(videoData)
- video.url = getActivityPubUrl('video', video.uuid)
+ video.url = getVideoActivityPubUrl(video)
const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename)
const videoFileHeight = await getVideoFileHeight(videoFilePath)
if (videoInfoToUpdate.privacy !== undefined) videoInstance.set('privacy', videoInfoToUpdate.privacy)
if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description)
- await videoInstance.save(sequelizeOptions)
+ const videoInstanceUpdated = await videoInstance.save(sequelizeOptions)
if (videoInfoToUpdate.tags) {
const tagInstances = await db.Tag.findOrCreateTags(videoInfoToUpdate.tags, t)
// Now we'll update the video's meta data to our friends
if (wasPrivateVideo === false) {
- await sendUpdateVideo(videoInstance, t)
+ await sendUpdateVideo(videoInstanceUpdated, t)
}
// Video is not private anymore, send a create action to remote servers
import { ResultList } from '../../shared/models/result-list.model'
import { database as db, REMOTE_SCHEME } from '../initializers'
import { ACTIVITY_PUB, CONFIG, STATIC_PATHS } from '../initializers/constants'
-import { videoChannelActivityObjectToDBAttributes } from '../lib/activitypub/misc'
-import { sendVideoAnnounce } from '../lib/activitypub/send-request'
+import { videoChannelActivityObjectToDBAttributes } from '../lib/activitypub/process/misc'
+import { sendVideoAnnounce } from '../lib/activitypub/send/send-announce'
import { sendVideoChannelAnnounce } from '../lib/index'
+import { AccountFollowInstance } from '../models/account/account-follow-interface'
import { AccountInstance } from '../models/account/account-interface'
+import { VideoAbuseInstance } from '../models/video/video-abuse-interface'
import { VideoChannelInstance } from '../models/video/video-channel-interface'
import { VideoInstance } from '../models/video/video-interface'
import { isRemoteAccountValid } from './custom-validators'
-import { isVideoChannelObjectValid } from './custom-validators/activitypub/videos'
import { logger } from './logger'
import { signObject } from './peertube-crypto'
import { doRequest, doRequestAndSaveToFile } from './requests'
import { getServerAccount } from './utils'
+import { isVideoChannelObjectValid } from './custom-validators/activitypub/video-channels'
function generateThumbnailFromUrl (video: VideoInstance, icon: ActivityIconObject) {
const thumbnailName = video.getThumbnailName()
return sendVideoAnnounce(serverAccount, video, t)
}
-function getActivityPubUrl (type: 'video' | 'videoChannel' | 'account' | 'videoAbuse', id: string) {
- if (type === 'video') return CONFIG.WEBSERVER.URL + '/videos/watch/' + id
- else if (type === 'videoChannel') return CONFIG.WEBSERVER.URL + '/video-channels/' + id
- else if (type === 'account') return CONFIG.WEBSERVER.URL + '/account/' + id
- else if (type === 'videoAbuse') return CONFIG.WEBSERVER.URL + '/admin/video-abuses/' + id
+function getVideoActivityPubUrl (video: VideoInstance) {
+ return CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid
+}
+
+function getVideoChannelActivityPubUrl (videoChannel: VideoChannelInstance) {
+ return CONFIG.WEBSERVER.URL + '/video-channels/' + videoChannel.uuid
+}
+
+function getAccountActivityPubUrl (accountName: string) {
+ return CONFIG.WEBSERVER.URL + '/account/' + accountName
+}
+
+function getVideoAbuseActivityPubUrl (videoAbuse: VideoAbuseInstance) {
+ return CONFIG.WEBSERVER.URL + '/admin/video-abuses/' + videoAbuse.id
+}
+
+function getAccountFollowActivityPubUrl (accountFollow: AccountFollowInstance) {
+ const me = accountFollow.AccountFollower
+ const following = accountFollow.AccountFollowing
+
+ return me.url + '#follows/' + following.id
+}
+
+function getAccountFollowAcceptActivityPubUrl (accountFollow: AccountFollowInstance) {
+ const follower = accountFollow.AccountFollower
+ const me = accountFollow.AccountFollowing
+
+ return follower.url + '#accepts/follows/' + me.id
+}
+
+function getAnnounceActivityPubUrl (originalUrl: string, byAccount: AccountInstance) {
+ return originalUrl + '#announces/' + byAccount.id
+}
+
+function getUpdateActivityPubUrl (originalUrl: string, updatedAt: string) {
+ return originalUrl + '#updates/' + updatedAt
+}
- return ''
+function getUndoActivityPubUrl (originalUrl: string) {
+ return originalUrl + '/undo'
}
async function getOrCreateAccount (accountUrl: string) {
fetchRemoteAccountAndCreateServer,
activityPubContextify,
activityPubCollectionPagination,
- getActivityPubUrl,
generateThumbnailFromUrl,
getOrCreateAccount,
fetchRemoteVideoPreview,
shareVideoChannelByServer,
shareVideoByServer,
getOrCreateVideoChannel,
- buildSignedActivity
+ buildSignedActivity,
+ getVideoActivityPubUrl,
+ getVideoChannelActivityPubUrl,
+ getAccountActivityPubUrl,
+ getVideoAbuseActivityPubUrl,
+ getAccountFollowActivityPubUrl,
+ getAccountFollowAcceptActivityPubUrl,
+ getAnnounceActivityPubUrl,
+ getUpdateActivityPubUrl,
+ getUndoActivityPubUrl
}
// ---------------------------------------------------------------------------
import * as validator from 'validator'
+import { Activity, ActivityType } from '../../../../shared/models/activitypub/activity'
import { isAccountAcceptActivityValid, isAccountDeleteActivityValid, isAccountFollowActivityValid } from './account'
+import { isAnnounceValid } from './announce'
import { isActivityPubUrlValid } from './misc'
+import { isUndoValid } from './undo'
+import { isVideoChannelCreateActivityValid, isVideoChannelDeleteActivityValid, isVideoChannelUpdateActivityValid } from './video-channels'
import {
- isAnnounceValid,
- isVideoChannelCreateActivityValid,
- isVideoChannelDeleteActivityValid,
- isVideoChannelUpdateActivityValid,
isVideoFlagValid,
isVideoTorrentAddActivityValid,
isVideoTorrentDeleteActivityValid,
)
}
+const activityCheckers: { [ P in ActivityType ]: (activity: Activity) => boolean } = {
+ Create: checkCreateActivity,
+ Add: checkAddActivity,
+ Update: checkUpdateActivity,
+ Delete: checkDeleteActivity,
+ Follow: checkFollowActivity,
+ Accept: checkAcceptActivity,
+ Announce: checkAnnounceActivity,
+ Undo: checkUndoActivity
+}
+
function isActivityValid (activity: any) {
- return isVideoTorrentAddActivityValid(activity) ||
- isVideoChannelCreateActivityValid(activity) ||
- isVideoTorrentUpdateActivityValid(activity) ||
- isVideoChannelUpdateActivityValid(activity) ||
- isVideoTorrentDeleteActivityValid(activity) ||
- isVideoChannelDeleteActivityValid(activity) ||
- isAccountDeleteActivityValid(activity) ||
- isAccountFollowActivityValid(activity) ||
- isAccountAcceptActivityValid(activity) ||
- isVideoFlagValid(activity) ||
- isAnnounceValid(activity)
+ const checker = activityCheckers[activity.type]
+ // Unknown activity type
+ if (!checker) return false
+
+ return checker(activity)
}
// ---------------------------------------------------------------------------
isRootActivityValid,
isActivityValid
}
+
+// ---------------------------------------------------------------------------
+
+function checkCreateActivity (activity: any) {
+ return isVideoChannelCreateActivityValid(activity) ||
+ isVideoFlagValid(activity)
+}
+
+function checkAddActivity (activity: any) {
+ return isVideoTorrentAddActivityValid(activity)
+}
+
+function checkUpdateActivity (activity: any) {
+ return isVideoTorrentUpdateActivityValid(activity) ||
+ isVideoChannelUpdateActivityValid(activity)
+}
+
+function checkDeleteActivity (activity: any) {
+ return isVideoTorrentDeleteActivityValid(activity) ||
+ isVideoChannelDeleteActivityValid(activity) ||
+ isAccountDeleteActivityValid(activity)
+}
+
+function checkFollowActivity (activity: any) {
+ return isAccountFollowActivityValid(activity)
+}
+
+function checkAcceptActivity (activity: any) {
+ return isAccountAcceptActivityValid(activity)
+}
+
+function checkAnnounceActivity (activity: any) {
+ return isAnnounceValid(activity)
+}
+
+function checkUndoActivity (activity: any) {
+ return isUndoValid(activity)
+}
--- /dev/null
+import { isBaseActivityValid } from './misc'
+import { isVideoTorrentAddActivityValid } from './videos'
+import { isVideoChannelCreateActivityValid } from './video-channels'
+
+function isAnnounceValid (activity: any) {
+ return isBaseActivityValid(activity, 'Announce') &&
+ (
+ isVideoChannelCreateActivityValid(activity.object) ||
+ isVideoTorrentAddActivityValid(activity.object)
+ )
+}
+
+export {
+ isAnnounceValid
+}
export * from './account'
export * from './activity'
-export * from './signature'
export * from './misc'
+export * from './signature'
+export * from './undo'
+export * from './video-channels'
export * from './videos'
--- /dev/null
+import { isAccountFollowActivityValid } from './account'
+import { isBaseActivityValid } from './misc'
+
+function isUndoValid (activity: any) {
+ return isBaseActivityValid(activity, 'Undo') &&
+ (
+ isAccountFollowActivityValid(activity.object)
+ )
+}
+
+export {
+ isUndoValid
+}
--- /dev/null
+import { isDateValid, isUUIDValid } from '../misc'
+import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../video-channels'
+import { isActivityPubUrlValid, isBaseActivityValid } from './misc'
+
+function isVideoChannelCreateActivityValid (activity: any) {
+ return isBaseActivityValid(activity, 'Create') &&
+ isVideoChannelObjectValid(activity.object)
+}
+
+function isVideoChannelUpdateActivityValid (activity: any) {
+ return isBaseActivityValid(activity, 'Update') &&
+ isVideoChannelObjectValid(activity.object)
+}
+
+function isVideoChannelDeleteActivityValid (activity: any) {
+ return isBaseActivityValid(activity, 'Delete')
+}
+
+function isVideoChannelObjectValid (videoChannel: any) {
+ return videoChannel.type === 'VideoChannel' &&
+ isActivityPubUrlValid(videoChannel.id) &&
+ isVideoChannelNameValid(videoChannel.name) &&
+ isVideoChannelDescriptionValid(videoChannel.content) &&
+ isDateValid(videoChannel.published) &&
+ isDateValid(videoChannel.updated) &&
+ isUUIDValid(videoChannel.uuid)
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ isVideoChannelCreateActivityValid,
+ isVideoChannelUpdateActivityValid,
+ isVideoChannelDeleteActivityValid,
+ isVideoChannelObjectValid
+}
import * as validator from 'validator'
import { ACTIVITY_PUB } from '../../../initializers'
import { exists, isDateValid, isUUIDValid } from '../misc'
-import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../video-channels'
import {
isVideoAbuseReasonValid,
isVideoDurationValid,
return isBaseActivityValid(activity, 'Delete')
}
+function isVideoFlagValid (activity: any) {
+ return isBaseActivityValid(activity, 'Create') &&
+ activity.object.type === 'Flag' &&
+ isVideoAbuseReasonValid(activity.object.content) &&
+ isActivityPubUrlValid(activity.object.object)
+}
+
function isActivityPubVideoDurationValid (value: string) {
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration
return exists(value) &&
video.url.length !== 0
}
-function isVideoFlagValid (activity: any) {
- return isBaseActivityValid(activity, 'Create') &&
- activity.object.type === 'Flag' &&
- isVideoAbuseReasonValid(activity.object.content) &&
- isActivityPubUrlValid(activity.object.object)
-}
-
-function isAnnounceValid (activity: any) {
- return isBaseActivityValid(activity, 'Announce') &&
- (
- isVideoChannelCreateActivityValid(activity.object) ||
- isVideoTorrentAddActivityValid(activity.object)
- )
-}
-
-function isVideoChannelCreateActivityValid (activity: any) {
- return isBaseActivityValid(activity, 'Create') &&
- isVideoChannelObjectValid(activity.object)
-}
-
-function isVideoChannelUpdateActivityValid (activity: any) {
- return isBaseActivityValid(activity, 'Update') &&
- isVideoChannelObjectValid(activity.object)
-}
-
-function isVideoChannelDeleteActivityValid (activity: any) {
- return isBaseActivityValid(activity, 'Delete')
-}
-
-function isVideoChannelObjectValid (videoChannel: any) {
- return videoChannel.type === 'VideoChannel' &&
- isActivityPubUrlValid(videoChannel.id) &&
- isVideoChannelNameValid(videoChannel.name) &&
- isVideoChannelDescriptionValid(videoChannel.content) &&
- isDateValid(videoChannel.published) &&
- isDateValid(videoChannel.updated) &&
- isUUIDValid(videoChannel.uuid)
-}
-
// ---------------------------------------------------------------------------
export {
isVideoTorrentAddActivityValid,
- isVideoChannelCreateActivityValid,
isVideoTorrentUpdateActivityValid,
- isVideoChannelUpdateActivityValid,
- isVideoChannelDeleteActivityValid,
isVideoTorrentDeleteActivityValid,
- isVideoFlagValid,
- isAnnounceValid,
- isVideoChannelObjectValid
+ isVideoFlagValid
}
// ---------------------------------------------------------------------------
-export * from './process-accept'
-export * from './process-add'
-export * from './process-announce'
-export * from './process-create'
-export * from './process-delete'
-export * from './process-follow'
-export * from './process-update'
-export * from './send-request'
+export * from './process'
+export * from './send'
+++ /dev/null
-import * as magnetUtil from 'magnet-uri'
-import { VideoTorrentObject } from '../../../shared'
-import { VideoChannelObject } from '../../../shared/models/activitypub/objects/video-channel-object'
-import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos'
-import { ACTIVITY_PUB, VIDEO_MIMETYPE_EXT } from '../../initializers/constants'
-import { AccountInstance } from '../../models/account/account-interface'
-import { VideoChannelInstance } from '../../models/video/video-channel-interface'
-import { VideoFileAttributes } from '../../models/video/video-file-interface'
-import { VideoAttributes, VideoInstance } from '../../models/video/video-interface'
-import { VideoPrivacy } from '../../../shared/models/videos/video-privacy.enum'
-
-function videoChannelActivityObjectToDBAttributes (videoChannelObject: VideoChannelObject, account: AccountInstance) {
- return {
- name: videoChannelObject.name,
- description: videoChannelObject.content,
- uuid: videoChannelObject.uuid,
- url: videoChannelObject.id,
- createdAt: new Date(videoChannelObject.published),
- updatedAt: new Date(videoChannelObject.updated),
- remote: true,
- accountId: account.id
- }
-}
-
-async function videoActivityObjectToDBAttributes (
- videoChannel: VideoChannelInstance,
- videoObject: VideoTorrentObject,
- to: string[] = [],
- cc: string[] = []
-) {
- let privacy = VideoPrivacy.PRIVATE
- if (to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1) privacy = VideoPrivacy.PUBLIC
- else if (cc.indexOf(ACTIVITY_PUB.PUBLIC) !== -1) privacy = VideoPrivacy.UNLISTED
-
- const duration = videoObject.duration.replace(/[^\d]+/, '')
- const videoData: VideoAttributes = {
- name: videoObject.name,
- uuid: videoObject.uuid,
- url: videoObject.id,
- category: parseInt(videoObject.category.identifier, 10),
- licence: parseInt(videoObject.licence.identifier, 10),
- language: parseInt(videoObject.language.identifier, 10),
- nsfw: videoObject.nsfw,
- description: videoObject.content,
- channelId: videoChannel.id,
- duration: parseInt(duration, 10),
- createdAt: new Date(videoObject.published),
- // FIXME: updatedAt does not seems to be considered by Sequelize
- updatedAt: new Date(videoObject.updated),
- views: videoObject.views,
- likes: 0,
- dislikes: 0,
- remote: true,
- privacy
- }
-
- return videoData
-}
-
-function videoFileActivityUrlToDBAttributes (videoCreated: VideoInstance, videoObject: VideoTorrentObject) {
- const mimeTypes = Object.keys(VIDEO_MIMETYPE_EXT)
- const fileUrls = videoObject.url.filter(u => {
- return mimeTypes.indexOf(u.mimeType) !== -1 && u.mimeType.startsWith('video/')
- })
-
- if (fileUrls.length === 0) {
- throw new Error('Cannot find video files for ' + videoCreated.url)
- }
-
- const attributes: VideoFileAttributes[] = []
- for (const fileUrl of fileUrls) {
- // Fetch associated magnet uri
- const magnet = videoObject.url.find(u => {
- return u.mimeType === 'application/x-bittorrent;x-scheme-handler/magnet' && u.width === fileUrl.width
- })
-
- if (!magnet) throw new Error('Cannot find associated magnet uri for file ' + fileUrl.url)
-
- const parsed = magnetUtil.decode(magnet.url)
- if (!parsed || isVideoFileInfoHashValid(parsed.infoHash) === false) throw new Error('Cannot parse magnet URI ' + magnet.url)
-
- const attribute = {
- extname: VIDEO_MIMETYPE_EXT[fileUrl.mimeType],
- infoHash: parsed.infoHash,
- resolution: fileUrl.width,
- size: fileUrl.size,
- videoId: videoCreated.id
- }
- attributes.push(attribute)
- }
-
- return attributes
-}
-
-// ---------------------------------------------------------------------------
-
-export {
- videoFileActivityUrlToDBAttributes,
- videoActivityObjectToDBAttributes,
- videoChannelActivityObjectToDBAttributes
-}
+++ /dev/null
-import { ActivityAccept } from '../../../shared/models/activitypub/activity'
-import { database as db } from '../../initializers'
-import { AccountInstance } from '../../models/account/account-interface'
-
-async function processAcceptActivity (activity: ActivityAccept, inboxAccount?: AccountInstance) {
- if (inboxAccount === undefined) throw new Error('Need to accept on explicit inbox.')
-
- const targetAccount = await db.Account.loadByUrl(activity.actor)
-
- return processAccept(inboxAccount, targetAccount)
-}
-
-// ---------------------------------------------------------------------------
-
-export {
- processAcceptActivity
-}
-
-// ---------------------------------------------------------------------------
-
-async function processAccept (account: AccountInstance, targetAccount: AccountInstance) {
- const follow = await db.AccountFollow.loadByAccountAndTarget(account.id, targetAccount.id)
- if (!follow) throw new Error('Cannot find associated follow.')
-
- follow.set('state', 'accepted')
- await follow.save()
-}
+++ /dev/null
-import * as Bluebird from 'bluebird'
-import { VideoTorrentObject } from '../../../shared'
-import { ActivityAdd } from '../../../shared/models/activitypub/activity'
-import { generateThumbnailFromUrl, getOrCreateAccount, logger, retryTransactionWrapper } from '../../helpers'
-import { getOrCreateVideoChannel } from '../../helpers/activitypub'
-import { database as db } from '../../initializers'
-import { AccountInstance } from '../../models/account/account-interface'
-import { VideoChannelInstance } from '../../models/video/video-channel-interface'
-import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
-
-async function processAddActivity (activity: ActivityAdd) {
- const activityObject = activity.object
- const activityType = activityObject.type
- const account = await getOrCreateAccount(activity.actor)
-
- if (activityType === 'Video') {
- const videoChannelUrl = activity.target
- const videoChannel = await getOrCreateVideoChannel(account, videoChannelUrl)
-
- return processAddVideo(account, activity, videoChannel, activityObject)
- }
-
- logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id })
- return Promise.resolve(undefined)
-}
-
-// ---------------------------------------------------------------------------
-
-export {
- processAddActivity
-}
-
-// ---------------------------------------------------------------------------
-
-function processAddVideo (account: AccountInstance, activity: ActivityAdd, videoChannel: VideoChannelInstance, video: VideoTorrentObject) {
- const options = {
- arguments: [ account, activity, videoChannel, video ],
- errorMessage: 'Cannot insert the remote video with many retries.'
- }
-
- return retryTransactionWrapper(addRemoteVideo, options)
-}
-
-function addRemoteVideo (
- account: AccountInstance,
- activity: ActivityAdd,
- videoChannel: VideoChannelInstance,
- videoToCreateData: VideoTorrentObject
-) {
- logger.debug('Adding remote video %s.', videoToCreateData.url)
-
- return db.sequelize.transaction(async t => {
- const sequelizeOptions = {
- transaction: t
- }
-
- if (videoChannel.Account.id !== account.id) throw new Error('Video channel is not owned by this account.')
-
- const videoFromDatabase = await db.Video.loadByUUIDOrURL(videoToCreateData.uuid, videoToCreateData.id, t)
- if (videoFromDatabase) throw new Error('Video with this UUID/Url already exists.')
-
- const videoData = await videoActivityObjectToDBAttributes(videoChannel, videoToCreateData, activity.to, activity.cc)
- const video = db.Video.build(videoData)
-
- // Don't block on request
- generateThumbnailFromUrl(video, videoToCreateData.icon)
- .catch(err => logger.warn('Cannot generate thumbnail of %s.', videoToCreateData.id, err))
-
- const videoCreated = await video.save(sequelizeOptions)
-
- const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoCreated, videoToCreateData)
- if (videoFileAttributes.length === 0) {
- throw new Error('Cannot find valid files for video %s ' + videoToCreateData.url)
- }
-
- const tasks: Bluebird<any>[] = videoFileAttributes.map(f => db.VideoFile.create(f, { transaction: t }))
- await Promise.all(tasks)
-
- const tags = videoToCreateData.tag.map(t => t.name)
- const tagInstances = await db.Tag.findOrCreateTags(tags, t)
- await videoCreated.setTags(tagInstances, sequelizeOptions)
-
- logger.info('Remote video with uuid %s inserted.', videoToCreateData.uuid)
-
- return videoCreated
- })
-}
+++ /dev/null
-import { ActivityAnnounce } from '../../../shared/models/activitypub/activity'
-import { getOrCreateAccount } from '../../helpers/activitypub'
-import { logger } from '../../helpers/logger'
-import { database as db } from '../../initializers/index'
-import { VideoInstance } from '../../models/index'
-import { VideoChannelInstance } from '../../models/video/video-channel-interface'
-import { processAddActivity } from './process-add'
-import { processCreateActivity } from './process-create'
-
-async function processAnnounceActivity (activity: ActivityAnnounce) {
- const announcedActivity = activity.object
- const accountAnnouncer = await getOrCreateAccount(activity.actor)
-
- if (announcedActivity.type === 'Create' && announcedActivity.object.type === 'VideoChannel') {
- // Add share entry
- const videoChannel: VideoChannelInstance = await processCreateActivity(announcedActivity)
- await db.VideoChannelShare.create({
- accountId: accountAnnouncer.id,
- videoChannelId: videoChannel.id
- })
-
- return undefined
- } else if (announcedActivity.type === 'Add' && announcedActivity.object.type === 'Video') {
- // Add share entry
- const video: VideoInstance = await processAddActivity(announcedActivity)
- await db.VideoShare.create({
- accountId: accountAnnouncer.id,
- videoId: video.id
- })
-
- return undefined
- }
-
- logger.warn(
- 'Unknown activity object type %s -> %s when announcing activity.', announcedActivity.type, announcedActivity.object.type,
- { activity: activity.id }
- )
- return Promise.resolve(undefined)
-}
-
-// ---------------------------------------------------------------------------
-
-export {
- processAnnounceActivity
-}
+++ /dev/null
-import { ActivityCreate, VideoChannelObject } from '../../../shared'
-import { VideoAbuseObject } from '../../../shared/models/activitypub/objects/video-abuse-object'
-import { logger, retryTransactionWrapper } from '../../helpers'
-import { getActivityPubUrl, getOrCreateAccount } from '../../helpers/activitypub'
-import { database as db } from '../../initializers'
-import { AccountInstance } from '../../models/account/account-interface'
-import { videoChannelActivityObjectToDBAttributes } from './misc'
-
-async function processCreateActivity (activity: ActivityCreate) {
- const activityObject = activity.object
- const activityType = activityObject.type
- const account = await getOrCreateAccount(activity.actor)
-
- if (activityType === 'VideoChannel') {
- return processCreateVideoChannel(account, activityObject as VideoChannelObject)
- } else if (activityType === 'Flag') {
- return processCreateVideoAbuse(account, activityObject as VideoAbuseObject)
- }
-
- logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id })
- return Promise.resolve(undefined)
-}
-
-// ---------------------------------------------------------------------------
-
-export {
- processCreateActivity
-}
-
-// ---------------------------------------------------------------------------
-
-function processCreateVideoChannel (account: AccountInstance, videoChannelToCreateData: VideoChannelObject) {
- const options = {
- arguments: [ account, videoChannelToCreateData ],
- errorMessage: 'Cannot insert the remote video channel with many retries.'
- }
-
- return retryTransactionWrapper(addRemoteVideoChannel, options)
-}
-
-function addRemoteVideoChannel (account: AccountInstance, videoChannelToCreateData: VideoChannelObject) {
- logger.debug('Adding remote video channel "%s".', videoChannelToCreateData.uuid)
-
- return db.sequelize.transaction(async t => {
- let videoChannel = await db.VideoChannel.loadByUUIDOrUrl(videoChannelToCreateData.uuid, videoChannelToCreateData.id, t)
- if (videoChannel) throw new Error('Video channel with this URL/UUID already exists.')
-
- const videoChannelData = videoChannelActivityObjectToDBAttributes(videoChannelToCreateData, account)
- videoChannel = db.VideoChannel.build(videoChannelData)
- videoChannel.url = getActivityPubUrl('videoChannel', videoChannel.uuid)
-
- videoChannel = await videoChannel.save({ transaction: t })
- logger.info('Remote video channel with uuid %s inserted.', videoChannelToCreateData.uuid)
-
- return videoChannel
- })
-}
-
-function processCreateVideoAbuse (account: AccountInstance, videoAbuseToCreateData: VideoAbuseObject) {
- const options = {
- arguments: [ account, videoAbuseToCreateData ],
- errorMessage: 'Cannot insert the remote video abuse with many retries.'
- }
-
- return retryTransactionWrapper(addRemoteVideoAbuse, options)
-}
-
-function addRemoteVideoAbuse (account: AccountInstance, videoAbuseToCreateData: VideoAbuseObject) {
- logger.debug('Reporting remote abuse for video %s.', videoAbuseToCreateData.object)
-
- return db.sequelize.transaction(async t => {
- const video = await db.Video.loadByUrlAndPopulateAccount(videoAbuseToCreateData.object, t)
- if (!video) {
- logger.warn('Unknown video %s for remote video abuse.', videoAbuseToCreateData.object)
- return undefined
- }
-
- const videoAbuseData = {
- reporterAccountId: account.id,
- reason: videoAbuseToCreateData.content,
- videoId: video.id
- }
-
- await db.VideoAbuse.create(videoAbuseData)
-
- logger.info('Remote abuse for video uuid %s created', videoAbuseToCreateData.object)
- })
-}
+++ /dev/null
-import { ActivityDelete } from '../../../shared/models/activitypub/activity'
-import { getOrCreateAccount } from '../../helpers/activitypub'
-import { retryTransactionWrapper } from '../../helpers/database-utils'
-import { logger } from '../../helpers/logger'
-import { database as db } from '../../initializers'
-import { AccountInstance } from '../../models/account/account-interface'
-import { VideoChannelInstance } from '../../models/video/video-channel-interface'
-import { VideoInstance } from '../../models/video/video-interface'
-
-async function processDeleteActivity (activity: ActivityDelete) {
- const account = await getOrCreateAccount(activity.actor)
-
- if (account.url === activity.id) {
- return processDeleteAccount(account)
- }
-
- {
- let videoObject = await db.Video.loadByUrlAndPopulateAccount(activity.id)
- if (videoObject !== undefined) {
- return processDeleteVideo(account, videoObject)
- }
- }
-
- {
- let videoChannelObject = await db.VideoChannel.loadByUrl(activity.id)
- if (videoChannelObject !== undefined) {
- return processDeleteVideoChannel(account, videoChannelObject)
- }
- }
-
- return
-}
-
-// ---------------------------------------------------------------------------
-
-export {
- processDeleteActivity
-}
-
-// ---------------------------------------------------------------------------
-
-async function processDeleteVideo (account: AccountInstance, videoToDelete: VideoInstance) {
- const options = {
- arguments: [ account, videoToDelete ],
- errorMessage: 'Cannot remove the remote video with many retries.'
- }
-
- await retryTransactionWrapper(deleteRemoteVideo, options)
-}
-
-async function deleteRemoteVideo (account: AccountInstance, videoToDelete: VideoInstance) {
- logger.debug('Removing remote video "%s".', videoToDelete.uuid)
-
- await db.sequelize.transaction(async t => {
- if (videoToDelete.VideoChannel.Account.id !== account.id) {
- throw new Error('Account ' + account.url + ' does not own video channel ' + videoToDelete.VideoChannel.url)
- }
-
- await videoToDelete.destroy({ transaction: t })
- })
-
- logger.info('Remote video with uuid %s removed.', videoToDelete.uuid)
-}
-
-async function processDeleteVideoChannel (account: AccountInstance, videoChannelToRemove: VideoChannelInstance) {
- const options = {
- arguments: [ account, videoChannelToRemove ],
- errorMessage: 'Cannot remove the remote video channel with many retries.'
- }
-
- await retryTransactionWrapper(deleteRemoteVideoChannel, options)
-}
-
-async function deleteRemoteVideoChannel (account: AccountInstance, videoChannelToRemove: VideoChannelInstance) {
- logger.debug('Removing remote video channel "%s".', videoChannelToRemove.uuid)
-
- await db.sequelize.transaction(async t => {
- if (videoChannelToRemove.Account.id !== account.id) {
- throw new Error('Account ' + account.url + ' does not own video channel ' + videoChannelToRemove.url)
- }
-
- await videoChannelToRemove.destroy({ transaction: t })
- })
-
- logger.info('Remote video channel with uuid %s removed.', videoChannelToRemove.uuid)
-}
-
-async function processDeleteAccount (accountToRemove: AccountInstance) {
- const options = {
- arguments: [ accountToRemove ],
- errorMessage: 'Cannot remove the remote account with many retries.'
- }
-
- await retryTransactionWrapper(deleteRemoteAccount, options)
-}
-
-async function deleteRemoteAccount (accountToRemove: AccountInstance) {
- logger.debug('Removing remote account "%s".', accountToRemove.uuid)
-
- await db.sequelize.transaction(async t => {
- await accountToRemove.destroy({ transaction: t })
- })
-
- logger.info('Remote account with uuid %s removed.', accountToRemove.uuid)
-}
+++ /dev/null
-import { ActivityFollow } from '../../../shared/models/activitypub/activity'
-import { getOrCreateAccount, retryTransactionWrapper } from '../../helpers'
-import { database as db } from '../../initializers'
-import { AccountInstance } from '../../models/account/account-interface'
-import { sendAccept } from './send-request'
-import { logger } from '../../helpers/logger'
-
-async function processFollowActivity (activity: ActivityFollow) {
- const activityObject = activity.object
- const account = await getOrCreateAccount(activity.actor)
-
- return processFollow(account, activityObject)
-}
-
-// ---------------------------------------------------------------------------
-
-export {
- processFollowActivity
-}
-
-// ---------------------------------------------------------------------------
-
-function processFollow (account: AccountInstance, targetAccountURL: string) {
- const options = {
- arguments: [ account, targetAccountURL ],
- errorMessage: 'Cannot follow with many retries.'
- }
-
- return retryTransactionWrapper(follow, options)
-}
-
-async function follow (account: AccountInstance, targetAccountURL: string) {
- await db.sequelize.transaction(async t => {
- const targetAccount = await db.Account.loadByUrl(targetAccountURL, t)
-
- if (targetAccount === undefined) throw new Error('Unknown account')
- if (targetAccount.isOwned() === false) throw new Error('This is not a local account.')
-
- await db.AccountFollow.findOrCreate({
- where: {
- accountId: account.id,
- targetAccountId: targetAccount.id
- },
- defaults: {
- accountId: account.id,
- targetAccountId: targetAccount.id,
- state: 'accepted'
- },
- transaction: t
- })
-
- // Target sends to account he accepted the follow request
- return sendAccept(targetAccount, account, t)
- })
-
- logger.info('Account uuid %s is followed by account %s.', account.url, targetAccountURL)
-}
+++ /dev/null
-import { VideoChannelObject, VideoTorrentObject } from '../../../shared'
-import { ActivityUpdate } from '../../../shared/models/activitypub/activity'
-import { getOrCreateAccount } from '../../helpers/activitypub'
-import { retryTransactionWrapper } from '../../helpers/database-utils'
-import { logger } from '../../helpers/logger'
-import { resetSequelizeInstance } from '../../helpers/utils'
-import { database as db } from '../../initializers'
-import { AccountInstance } from '../../models/account/account-interface'
-import { VideoInstance } from '../../models/video/video-interface'
-import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
-import Bluebird = require('bluebird')
-
-async function processUpdateActivity (activity: ActivityUpdate) {
- const account = await getOrCreateAccount(activity.actor)
-
- if (activity.object.type === 'Video') {
- return processUpdateVideo(account, activity.object)
- } else if (activity.object.type === 'VideoChannel') {
- return processUpdateVideoChannel(account, activity.object)
- }
-
- return undefined
-}
-
-// ---------------------------------------------------------------------------
-
-export {
- processUpdateActivity
-}
-
-// ---------------------------------------------------------------------------
-
-function processUpdateVideo (account: AccountInstance, video: VideoTorrentObject) {
- const options = {
- arguments: [ account, video ],
- errorMessage: 'Cannot update the remote video with many retries'
- }
-
- return retryTransactionWrapper(updateRemoteVideo, options)
-}
-
-async function updateRemoteVideo (account: AccountInstance, videoAttributesToUpdate: VideoTorrentObject) {
- logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid)
- let videoInstance: VideoInstance
- let videoFieldsSave: object
-
- try {
- await db.sequelize.transaction(async t => {
- const sequelizeOptions = {
- transaction: t
- }
-
- const videoInstance = await db.Video.loadByUrlAndPopulateAccount(videoAttributesToUpdate.id, t)
- if (!videoInstance) throw new Error('Video ' + videoAttributesToUpdate.id + ' not found.')
-
- if (videoInstance.VideoChannel.Account.id !== account.id) {
- throw new Error('Account ' + account.url + ' does not own video channel ' + videoInstance.VideoChannel.url)
- }
-
- const videoData = await videoActivityObjectToDBAttributes(videoInstance.VideoChannel, videoAttributesToUpdate)
- videoInstance.set('name', videoData.name)
- videoInstance.set('category', videoData.category)
- videoInstance.set('licence', videoData.licence)
- videoInstance.set('language', videoData.language)
- videoInstance.set('nsfw', videoData.nsfw)
- videoInstance.set('description', videoData.description)
- videoInstance.set('duration', videoData.duration)
- videoInstance.set('createdAt', videoData.createdAt)
- videoInstance.set('updatedAt', videoData.updatedAt)
- videoInstance.set('views', videoData.views)
- // videoInstance.set('likes', videoData.likes)
- // videoInstance.set('dislikes', videoData.dislikes)
-
- await videoInstance.save(sequelizeOptions)
-
- // Remove old video files
- const videoFileDestroyTasks: Bluebird<void>[] = []
- for (const videoFile of videoInstance.VideoFiles) {
- videoFileDestroyTasks.push(videoFile.destroy(sequelizeOptions))
- }
- await Promise.all(videoFileDestroyTasks)
-
- const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoInstance, videoAttributesToUpdate)
- const tasks: Bluebird<any>[] = videoFileAttributes.map(f => db.VideoFile.create(f))
- await Promise.all(tasks)
-
- const tags = videoAttributesToUpdate.tag.map(t => t.name)
- const tagInstances = await db.Tag.findOrCreateTags(tags, t)
- await videoInstance.setTags(tagInstances, sequelizeOptions)
- })
-
- logger.info('Remote video with uuid %s updated', videoAttributesToUpdate.uuid)
- } catch (err) {
- if (videoInstance !== undefined && videoFieldsSave !== undefined) {
- resetSequelizeInstance(videoInstance, videoFieldsSave)
- }
-
- // This is just a debug because we will retry the insert
- logger.debug('Cannot update the remote video.', err)
- throw err
- }
-}
-
-async function processUpdateVideoChannel (account: AccountInstance, videoChannel: VideoChannelObject) {
- const options = {
- arguments: [ account, videoChannel ],
- errorMessage: 'Cannot update the remote video channel with many retries.'
- }
-
- await retryTransactionWrapper(updateRemoteVideoChannel, options)
-}
-
-async function updateRemoteVideoChannel (account: AccountInstance, videoChannel: VideoChannelObject) {
- logger.debug('Updating remote video channel "%s".', videoChannel.uuid)
-
- await db.sequelize.transaction(async t => {
- const sequelizeOptions = { transaction: t }
-
- const videoChannelInstance = await db.VideoChannel.loadByUrl(videoChannel.id)
- if (!videoChannelInstance) throw new Error('Video ' + videoChannel.id + ' not found.')
-
- if (videoChannelInstance.Account.id !== account.id) {
- throw new Error('Account ' + account.id + ' does not own video channel ' + videoChannelInstance.url)
- }
-
- videoChannelInstance.set('name', videoChannel.name)
- videoChannelInstance.set('description', videoChannel.content)
- videoChannelInstance.set('createdAt', videoChannel.published)
- videoChannelInstance.set('updatedAt', videoChannel.updated)
-
- await videoChannelInstance.save(sequelizeOptions)
- })
-
- logger.info('Remote video channel with uuid %s updated', videoChannel.uuid)
-}
--- /dev/null
+export * from './process-accept'
+export * from './process-add'
+export * from './process-announce'
+export * from './process-create'
+export * from './process-delete'
+export * from './process-follow'
+export * from './process-undo'
+export * from './process-update'
--- /dev/null
+import * as magnetUtil from 'magnet-uri'
+import { VideoTorrentObject } from '../../../../shared'
+import { VideoChannelObject } from '../../../../shared/models/activitypub/objects/video-channel-object'
+import { isVideoFileInfoHashValid } from '../../../helpers/custom-validators/videos'
+import { ACTIVITY_PUB, VIDEO_MIMETYPE_EXT } from '../../../initializers/constants'
+import { AccountInstance } from '../../../models/account/account-interface'
+import { VideoChannelInstance } from '../../../models/video/video-channel-interface'
+import { VideoFileAttributes } from '../../../models/video/video-file-interface'
+import { VideoAttributes, VideoInstance } from '../../../models/video/video-interface'
+import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum'
+
+function videoChannelActivityObjectToDBAttributes (videoChannelObject: VideoChannelObject, account: AccountInstance) {
+ return {
+ name: videoChannelObject.name,
+ description: videoChannelObject.content,
+ uuid: videoChannelObject.uuid,
+ url: videoChannelObject.id,
+ createdAt: new Date(videoChannelObject.published),
+ updatedAt: new Date(videoChannelObject.updated),
+ remote: true,
+ accountId: account.id
+ }
+}
+
+async function videoActivityObjectToDBAttributes (
+ videoChannel: VideoChannelInstance,
+ videoObject: VideoTorrentObject,
+ to: string[] = [],
+ cc: string[] = []
+) {
+ let privacy = VideoPrivacy.PRIVATE
+ if (to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1) privacy = VideoPrivacy.PUBLIC
+ else if (cc.indexOf(ACTIVITY_PUB.PUBLIC) !== -1) privacy = VideoPrivacy.UNLISTED
+
+ const duration = videoObject.duration.replace(/[^\d]+/, '')
+ const videoData: VideoAttributes = {
+ name: videoObject.name,
+ uuid: videoObject.uuid,
+ url: videoObject.id,
+ category: parseInt(videoObject.category.identifier, 10),
+ licence: parseInt(videoObject.licence.identifier, 10),
+ language: parseInt(videoObject.language.identifier, 10),
+ nsfw: videoObject.nsfw,
+ description: videoObject.content,
+ channelId: videoChannel.id,
+ duration: parseInt(duration, 10),
+ createdAt: new Date(videoObject.published),
+ // FIXME: updatedAt does not seems to be considered by Sequelize
+ updatedAt: new Date(videoObject.updated),
+ views: videoObject.views,
+ likes: 0,
+ dislikes: 0,
+ remote: true,
+ privacy
+ }
+
+ return videoData
+}
+
+function videoFileActivityUrlToDBAttributes (videoCreated: VideoInstance, videoObject: VideoTorrentObject) {
+ const mimeTypes = Object.keys(VIDEO_MIMETYPE_EXT)
+ const fileUrls = videoObject.url.filter(u => {
+ return mimeTypes.indexOf(u.mimeType) !== -1 && u.mimeType.startsWith('video/')
+ })
+
+ if (fileUrls.length === 0) {
+ throw new Error('Cannot find video files for ' + videoCreated.url)
+ }
+
+ const attributes: VideoFileAttributes[] = []
+ for (const fileUrl of fileUrls) {
+ // Fetch associated magnet uri
+ const magnet = videoObject.url.find(u => {
+ return u.mimeType === 'application/x-bittorrent;x-scheme-handler/magnet' && u.width === fileUrl.width
+ })
+
+ if (!magnet) throw new Error('Cannot find associated magnet uri for file ' + fileUrl.url)
+
+ const parsed = magnetUtil.decode(magnet.url)
+ if (!parsed || isVideoFileInfoHashValid(parsed.infoHash) === false) throw new Error('Cannot parse magnet URI ' + magnet.url)
+
+ const attribute = {
+ extname: VIDEO_MIMETYPE_EXT[fileUrl.mimeType],
+ infoHash: parsed.infoHash,
+ resolution: fileUrl.width,
+ size: fileUrl.size,
+ videoId: videoCreated.id
+ }
+ attributes.push(attribute)
+ }
+
+ return attributes
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ videoFileActivityUrlToDBAttributes,
+ videoActivityObjectToDBAttributes,
+ videoChannelActivityObjectToDBAttributes
+}
--- /dev/null
+import { ActivityAccept } from '../../../../shared/models/activitypub/activity'
+import { database as db } from '../../../initializers'
+import { AccountInstance } from '../../../models/account/account-interface'
+
+async function processAcceptActivity (activity: ActivityAccept, inboxAccount?: AccountInstance) {
+ if (inboxAccount === undefined) throw new Error('Need to accept on explicit inbox.')
+
+ const targetAccount = await db.Account.loadByUrl(activity.actor)
+
+ return processAccept(inboxAccount, targetAccount)
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ processAcceptActivity
+}
+
+// ---------------------------------------------------------------------------
+
+async function processAccept (account: AccountInstance, targetAccount: AccountInstance) {
+ const follow = await db.AccountFollow.loadByAccountAndTarget(account.id, targetAccount.id)
+ if (!follow) throw new Error('Cannot find associated follow.')
+
+ follow.set('state', 'accepted')
+ await follow.save()
+}
--- /dev/null
+import * as Bluebird from 'bluebird'
+import { VideoTorrentObject } from '../../../../shared'
+import { ActivityAdd } from '../../../../shared/models/activitypub/activity'
+import { generateThumbnailFromUrl, getOrCreateAccount, logger, retryTransactionWrapper } from '../../../helpers'
+import { getOrCreateVideoChannel } from '../../../helpers/activitypub'
+import { database as db } from '../../../initializers'
+import { AccountInstance } from '../../../models/account/account-interface'
+import { VideoChannelInstance } from '../../../models/video/video-channel-interface'
+import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
+
+async function processAddActivity (activity: ActivityAdd) {
+ const activityObject = activity.object
+ const activityType = activityObject.type
+ const account = await getOrCreateAccount(activity.actor)
+
+ if (activityType === 'Video') {
+ const videoChannelUrl = activity.target
+ const videoChannel = await getOrCreateVideoChannel(account, videoChannelUrl)
+
+ return processAddVideo(account, activity, videoChannel, activityObject)
+ }
+
+ logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id })
+ return Promise.resolve(undefined)
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ processAddActivity
+}
+
+// ---------------------------------------------------------------------------
+
+function processAddVideo (account: AccountInstance, activity: ActivityAdd, videoChannel: VideoChannelInstance, video: VideoTorrentObject) {
+ const options = {
+ arguments: [ account, activity, videoChannel, video ],
+ errorMessage: 'Cannot insert the remote video with many retries.'
+ }
+
+ return retryTransactionWrapper(addRemoteVideo, options)
+}
+
+function addRemoteVideo (
+ account: AccountInstance,
+ activity: ActivityAdd,
+ videoChannel: VideoChannelInstance,
+ videoToCreateData: VideoTorrentObject
+) {
+ logger.debug('Adding remote video %s.', videoToCreateData.url)
+
+ return db.sequelize.transaction(async t => {
+ const sequelizeOptions = {
+ transaction: t
+ }
+
+ if (videoChannel.Account.id !== account.id) throw new Error('Video channel is not owned by this account.')
+
+ const videoFromDatabase = await db.Video.loadByUUIDOrURL(videoToCreateData.uuid, videoToCreateData.id, t)
+ if (videoFromDatabase) throw new Error('Video with this UUID/Url already exists.')
+
+ const videoData = await videoActivityObjectToDBAttributes(videoChannel, videoToCreateData, activity.to, activity.cc)
+ const video = db.Video.build(videoData)
+
+ // Don't block on request
+ generateThumbnailFromUrl(video, videoToCreateData.icon)
+ .catch(err => logger.warn('Cannot generate thumbnail of %s.', videoToCreateData.id, err))
+
+ const videoCreated = await video.save(sequelizeOptions)
+
+ const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoCreated, videoToCreateData)
+ if (videoFileAttributes.length === 0) {
+ throw new Error('Cannot find valid files for video %s ' + videoToCreateData.url)
+ }
+
+ const tasks: Bluebird<any>[] = videoFileAttributes.map(f => db.VideoFile.create(f, { transaction: t }))
+ await Promise.all(tasks)
+
+ const tags = videoToCreateData.tag.map(t => t.name)
+ const tagInstances = await db.Tag.findOrCreateTags(tags, t)
+ await videoCreated.setTags(tagInstances, sequelizeOptions)
+
+ logger.info('Remote video with uuid %s inserted.', videoToCreateData.uuid)
+
+ return videoCreated
+ })
+}
--- /dev/null
+import { ActivityAnnounce } from '../../../../shared/models/activitypub/activity'
+import { getOrCreateAccount } from '../../../helpers/activitypub'
+import { logger } from '../../../helpers/logger'
+import { database as db } from '../../../initializers/index'
+import { VideoInstance } from '../../../models/index'
+import { VideoChannelInstance } from '../../../models/video/video-channel-interface'
+import { processAddActivity } from './process-add'
+import { processCreateActivity } from './process-create'
+
+async function processAnnounceActivity (activity: ActivityAnnounce) {
+ const announcedActivity = activity.object
+ const accountAnnouncer = await getOrCreateAccount(activity.actor)
+
+ if (announcedActivity.type === 'Create' && announcedActivity.object.type === 'VideoChannel') {
+ // Add share entry
+ const videoChannel: VideoChannelInstance = await processCreateActivity(announcedActivity)
+ await db.VideoChannelShare.create({
+ accountId: accountAnnouncer.id,
+ videoChannelId: videoChannel.id
+ })
+
+ return undefined
+ } else if (announcedActivity.type === 'Add' && announcedActivity.object.type === 'Video') {
+ // Add share entry
+ const video: VideoInstance = await processAddActivity(announcedActivity)
+ await db.VideoShare.create({
+ accountId: accountAnnouncer.id,
+ videoId: video.id
+ })
+
+ return undefined
+ }
+
+ logger.warn(
+ 'Unknown activity object type %s -> %s when announcing activity.', announcedActivity.type, announcedActivity.object.type,
+ { activity: activity.id }
+ )
+
+ return undefined
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ processAnnounceActivity
+}
--- /dev/null
+import { ActivityCreate, VideoChannelObject } from '../../../../shared'
+import { VideoAbuseObject } from '../../../../shared/models/activitypub/objects/video-abuse-object'
+import { logger, retryTransactionWrapper } from '../../../helpers'
+import { getOrCreateAccount, getVideoChannelActivityPubUrl } from '../../../helpers/activitypub'
+import { database as db } from '../../../initializers'
+import { AccountInstance } from '../../../models/account/account-interface'
+import { videoChannelActivityObjectToDBAttributes } from './misc'
+
+async function processCreateActivity (activity: ActivityCreate) {
+ const activityObject = activity.object
+ const activityType = activityObject.type
+ const account = await getOrCreateAccount(activity.actor)
+
+ if (activityType === 'VideoChannel') {
+ return processCreateVideoChannel(account, activityObject as VideoChannelObject)
+ } else if (activityType === 'Flag') {
+ return processCreateVideoAbuse(account, activityObject as VideoAbuseObject)
+ }
+
+ logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id })
+ return Promise.resolve(undefined)
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ processCreateActivity
+}
+
+// ---------------------------------------------------------------------------
+
+function processCreateVideoChannel (account: AccountInstance, videoChannelToCreateData: VideoChannelObject) {
+ const options = {
+ arguments: [ account, videoChannelToCreateData ],
+ errorMessage: 'Cannot insert the remote video channel with many retries.'
+ }
+
+ return retryTransactionWrapper(addRemoteVideoChannel, options)
+}
+
+function addRemoteVideoChannel (account: AccountInstance, videoChannelToCreateData: VideoChannelObject) {
+ logger.debug('Adding remote video channel "%s".', videoChannelToCreateData.uuid)
+
+ return db.sequelize.transaction(async t => {
+ let videoChannel = await db.VideoChannel.loadByUUIDOrUrl(videoChannelToCreateData.uuid, videoChannelToCreateData.id, t)
+ if (videoChannel) throw new Error('Video channel with this URL/UUID already exists.')
+
+ const videoChannelData = videoChannelActivityObjectToDBAttributes(videoChannelToCreateData, account)
+ videoChannel = db.VideoChannel.build(videoChannelData)
+ videoChannel.url = getVideoChannelActivityPubUrl(videoChannel)
+
+ videoChannel = await videoChannel.save({ transaction: t })
+ logger.info('Remote video channel with uuid %s inserted.', videoChannelToCreateData.uuid)
+
+ return videoChannel
+ })
+}
+
+function processCreateVideoAbuse (account: AccountInstance, videoAbuseToCreateData: VideoAbuseObject) {
+ const options = {
+ arguments: [ account, videoAbuseToCreateData ],
+ errorMessage: 'Cannot insert the remote video abuse with many retries.'
+ }
+
+ return retryTransactionWrapper(addRemoteVideoAbuse, options)
+}
+
+function addRemoteVideoAbuse (account: AccountInstance, videoAbuseToCreateData: VideoAbuseObject) {
+ logger.debug('Reporting remote abuse for video %s.', videoAbuseToCreateData.object)
+
+ return db.sequelize.transaction(async t => {
+ const video = await db.Video.loadByUrlAndPopulateAccount(videoAbuseToCreateData.object, t)
+ if (!video) {
+ logger.warn('Unknown video %s for remote video abuse.', videoAbuseToCreateData.object)
+ return undefined
+ }
+
+ const videoAbuseData = {
+ reporterAccountId: account.id,
+ reason: videoAbuseToCreateData.content,
+ videoId: video.id
+ }
+
+ await db.VideoAbuse.create(videoAbuseData)
+
+ logger.info('Remote abuse for video uuid %s created', videoAbuseToCreateData.object)
+ })
+}
--- /dev/null
+import { ActivityDelete } from '../../../../shared/models/activitypub/activity'
+import { getOrCreateAccount } from '../../../helpers/activitypub'
+import { retryTransactionWrapper } from '../../../helpers/database-utils'
+import { logger } from '../../../helpers/logger'
+import { database as db } from '../../../initializers'
+import { AccountInstance } from '../../../models/account/account-interface'
+import { VideoChannelInstance } from '../../../models/video/video-channel-interface'
+import { VideoInstance } from '../../../models/video/video-interface'
+
+async function processDeleteActivity (activity: ActivityDelete) {
+ const account = await getOrCreateAccount(activity.actor)
+
+ if (account.url === activity.id) {
+ return processDeleteAccount(account)
+ }
+
+ {
+ let videoObject = await db.Video.loadByUrlAndPopulateAccount(activity.id)
+ if (videoObject !== undefined) {
+ return processDeleteVideo(account, videoObject)
+ }
+ }
+
+ {
+ let videoChannelObject = await db.VideoChannel.loadByUrl(activity.id)
+ if (videoChannelObject !== undefined) {
+ return processDeleteVideoChannel(account, videoChannelObject)
+ }
+ }
+
+ return
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ processDeleteActivity
+}
+
+// ---------------------------------------------------------------------------
+
+async function processDeleteVideo (account: AccountInstance, videoToDelete: VideoInstance) {
+ const options = {
+ arguments: [ account, videoToDelete ],
+ errorMessage: 'Cannot remove the remote video with many retries.'
+ }
+
+ await retryTransactionWrapper(deleteRemoteVideo, options)
+}
+
+async function deleteRemoteVideo (account: AccountInstance, videoToDelete: VideoInstance) {
+ logger.debug('Removing remote video "%s".', videoToDelete.uuid)
+
+ await db.sequelize.transaction(async t => {
+ if (videoToDelete.VideoChannel.Account.id !== account.id) {
+ throw new Error('Account ' + account.url + ' does not own video channel ' + videoToDelete.VideoChannel.url)
+ }
+
+ await videoToDelete.destroy({ transaction: t })
+ })
+
+ logger.info('Remote video with uuid %s removed.', videoToDelete.uuid)
+}
+
+async function processDeleteVideoChannel (account: AccountInstance, videoChannelToRemove: VideoChannelInstance) {
+ const options = {
+ arguments: [ account, videoChannelToRemove ],
+ errorMessage: 'Cannot remove the remote video channel with many retries.'
+ }
+
+ await retryTransactionWrapper(deleteRemoteVideoChannel, options)
+}
+
+async function deleteRemoteVideoChannel (account: AccountInstance, videoChannelToRemove: VideoChannelInstance) {
+ logger.debug('Removing remote video channel "%s".', videoChannelToRemove.uuid)
+
+ await db.sequelize.transaction(async t => {
+ if (videoChannelToRemove.Account.id !== account.id) {
+ throw new Error('Account ' + account.url + ' does not own video channel ' + videoChannelToRemove.url)
+ }
+
+ await videoChannelToRemove.destroy({ transaction: t })
+ })
+
+ logger.info('Remote video channel with uuid %s removed.', videoChannelToRemove.uuid)
+}
+
+async function processDeleteAccount (accountToRemove: AccountInstance) {
+ const options = {
+ arguments: [ accountToRemove ],
+ errorMessage: 'Cannot remove the remote account with many retries.'
+ }
+
+ await retryTransactionWrapper(deleteRemoteAccount, options)
+}
+
+async function deleteRemoteAccount (accountToRemove: AccountInstance) {
+ logger.debug('Removing remote account "%s".', accountToRemove.uuid)
+
+ await db.sequelize.transaction(async t => {
+ await accountToRemove.destroy({ transaction: t })
+ })
+
+ logger.info('Remote account with uuid %s removed.', accountToRemove.uuid)
+}
--- /dev/null
+import { ActivityFollow } from '../../../../shared/models/activitypub/activity'
+import { getOrCreateAccount, retryTransactionWrapper } from '../../../helpers'
+import { database as db } from '../../../initializers'
+import { AccountInstance } from '../../../models/account/account-interface'
+import { logger } from '../../../helpers/logger'
+import { sendAccept } from '../send/send-accept'
+
+async function processFollowActivity (activity: ActivityFollow) {
+ const activityObject = activity.object
+ const account = await getOrCreateAccount(activity.actor)
+
+ return processFollow(account, activityObject)
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ processFollowActivity
+}
+
+// ---------------------------------------------------------------------------
+
+function processFollow (account: AccountInstance, targetAccountURL: string) {
+ const options = {
+ arguments: [ account, targetAccountURL ],
+ errorMessage: 'Cannot follow with many retries.'
+ }
+
+ return retryTransactionWrapper(follow, options)
+}
+
+async function follow (account: AccountInstance, targetAccountURL: string) {
+ await db.sequelize.transaction(async t => {
+ const targetAccount = await db.Account.loadByUrl(targetAccountURL, t)
+
+ if (!targetAccount) throw new Error('Unknown account')
+ if (targetAccount.isOwned() === false) throw new Error('This is not a local account.')
+
+ const [ accountFollow ] = await db.AccountFollow.findOrCreate({
+ where: {
+ accountId: account.id,
+ targetAccountId: targetAccount.id
+ },
+ defaults: {
+ accountId: account.id,
+ targetAccountId: targetAccount.id,
+ state: 'accepted'
+ },
+ transaction: t
+ })
+ accountFollow.AccountFollower = account
+ accountFollow.AccountFollowing = targetAccount
+
+ // Target sends to account he accepted the follow request
+ return sendAccept(accountFollow, t)
+ })
+
+ logger.info('Account uuid %s is followed by account %s.', account.url, targetAccountURL)
+}
--- /dev/null
+import { ActivityUndo } from '../../../../shared/models/activitypub/activity'
+import { logger } from '../../../helpers/logger'
+import { database as db } from '../../../initializers'
+
+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
+ }
+
+ logger.warn('Unknown activity object type %s -> %s when undo activity.', activityToUndo.type, { activity: activity.id })
+
+ return undefined
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ processUndoActivity
+}
+
+// ---------------------------------------------------------------------------
--- /dev/null
+import { VideoChannelObject, VideoTorrentObject } from '../../../../shared'
+import { ActivityUpdate } from '../../../../shared/models/activitypub/activity'
+import { getOrCreateAccount } from '../../../helpers/activitypub'
+import { retryTransactionWrapper } from '../../../helpers/database-utils'
+import { logger } from '../../../helpers/logger'
+import { resetSequelizeInstance } from '../../../helpers/utils'
+import { database as db } from '../../../initializers'
+import { AccountInstance } from '../../../models/account/account-interface'
+import { VideoInstance } from '../../../models/video/video-interface'
+import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
+import Bluebird = require('bluebird')
+
+async function processUpdateActivity (activity: ActivityUpdate) {
+ const account = await getOrCreateAccount(activity.actor)
+
+ if (activity.object.type === 'Video') {
+ return processUpdateVideo(account, activity.object)
+ } else if (activity.object.type === 'VideoChannel') {
+ return processUpdateVideoChannel(account, activity.object)
+ }
+
+ return undefined
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ processUpdateActivity
+}
+
+// ---------------------------------------------------------------------------
+
+function processUpdateVideo (account: AccountInstance, video: VideoTorrentObject) {
+ const options = {
+ arguments: [ account, video ],
+ errorMessage: 'Cannot update the remote video with many retries'
+ }
+
+ return retryTransactionWrapper(updateRemoteVideo, options)
+}
+
+async function updateRemoteVideo (account: AccountInstance, videoAttributesToUpdate: VideoTorrentObject) {
+ logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid)
+ let videoInstance: VideoInstance
+ let videoFieldsSave: object
+
+ try {
+ await db.sequelize.transaction(async t => {
+ const sequelizeOptions = {
+ transaction: t
+ }
+
+ const videoInstance = await db.Video.loadByUrlAndPopulateAccount(videoAttributesToUpdate.id, t)
+ if (!videoInstance) throw new Error('Video ' + videoAttributesToUpdate.id + ' not found.')
+
+ if (videoInstance.VideoChannel.Account.id !== account.id) {
+ throw new Error('Account ' + account.url + ' does not own video channel ' + videoInstance.VideoChannel.url)
+ }
+
+ const videoData = await videoActivityObjectToDBAttributes(videoInstance.VideoChannel, videoAttributesToUpdate)
+ videoInstance.set('name', videoData.name)
+ videoInstance.set('category', videoData.category)
+ videoInstance.set('licence', videoData.licence)
+ videoInstance.set('language', videoData.language)
+ videoInstance.set('nsfw', videoData.nsfw)
+ videoInstance.set('description', videoData.description)
+ videoInstance.set('duration', videoData.duration)
+ videoInstance.set('createdAt', videoData.createdAt)
+ videoInstance.set('updatedAt', videoData.updatedAt)
+ videoInstance.set('views', videoData.views)
+ // videoInstance.set('likes', videoData.likes)
+ // videoInstance.set('dislikes', videoData.dislikes)
+
+ await videoInstance.save(sequelizeOptions)
+
+ // Remove old video files
+ const videoFileDestroyTasks: Bluebird<void>[] = []
+ for (const videoFile of videoInstance.VideoFiles) {
+ videoFileDestroyTasks.push(videoFile.destroy(sequelizeOptions))
+ }
+ await Promise.all(videoFileDestroyTasks)
+
+ const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoInstance, videoAttributesToUpdate)
+ const tasks: Bluebird<any>[] = videoFileAttributes.map(f => db.VideoFile.create(f))
+ await Promise.all(tasks)
+
+ const tags = videoAttributesToUpdate.tag.map(t => t.name)
+ const tagInstances = await db.Tag.findOrCreateTags(tags, t)
+ await videoInstance.setTags(tagInstances, sequelizeOptions)
+ })
+
+ logger.info('Remote video with uuid %s updated', videoAttributesToUpdate.uuid)
+ } catch (err) {
+ if (videoInstance !== undefined && videoFieldsSave !== undefined) {
+ resetSequelizeInstance(videoInstance, videoFieldsSave)
+ }
+
+ // This is just a debug because we will retry the insert
+ logger.debug('Cannot update the remote video.', err)
+ throw err
+ }
+}
+
+async function processUpdateVideoChannel (account: AccountInstance, videoChannel: VideoChannelObject) {
+ const options = {
+ arguments: [ account, videoChannel ],
+ errorMessage: 'Cannot update the remote video channel with many retries.'
+ }
+
+ await retryTransactionWrapper(updateRemoteVideoChannel, options)
+}
+
+async function updateRemoteVideoChannel (account: AccountInstance, videoChannel: VideoChannelObject) {
+ logger.debug('Updating remote video channel "%s".', videoChannel.uuid)
+
+ await db.sequelize.transaction(async t => {
+ const sequelizeOptions = { transaction: t }
+
+ const videoChannelInstance = await db.VideoChannel.loadByUrl(videoChannel.id)
+ if (!videoChannelInstance) throw new Error('Video ' + videoChannel.id + ' not found.')
+
+ if (videoChannelInstance.Account.id !== account.id) {
+ throw new Error('Account ' + account.id + ' does not own video channel ' + videoChannelInstance.url)
+ }
+
+ videoChannelInstance.set('name', videoChannel.name)
+ videoChannelInstance.set('description', videoChannel.content)
+ videoChannelInstance.set('createdAt', videoChannel.published)
+ videoChannelInstance.set('updatedAt', videoChannel.updated)
+
+ await videoChannelInstance.save(sequelizeOptions)
+ })
+
+ logger.info('Remote video channel with uuid %s updated', videoChannel.uuid)
+}
+++ /dev/null
-import { Transaction } from 'sequelize'
-import {
- ActivityAccept,
- ActivityAdd,
- ActivityCreate,
- ActivityDelete,
- ActivityFollow,
- ActivityUpdate
-} from '../../../shared/models/activitypub/activity'
-import { getActivityPubUrl } from '../../helpers/activitypub'
-import { logger } from '../../helpers/logger'
-import { database as db } from '../../initializers'
-import { AccountInstance, VideoChannelInstance, VideoInstance } from '../../models'
-import { VideoAbuseInstance } from '../../models/video/video-abuse-interface'
-import { activitypubHttpJobScheduler } from '../jobs'
-import { ACTIVITY_PUB } from '../../initializers/constants'
-import { VideoPrivacy } from '../../../shared/models/videos/video-privacy.enum'
-
-async function sendCreateVideoChannel (videoChannel: VideoChannelInstance, t: Transaction) {
- const byAccount = videoChannel.Account
-
- const videoChannelObject = videoChannel.toActivityPubObject()
- const data = await createActivityData(videoChannel.url, byAccount, videoChannelObject)
-
- return broadcastToFollowers(data, byAccount, [ byAccount ], t)
-}
-
-async function sendUpdateVideoChannel (videoChannel: VideoChannelInstance, t: Transaction) {
- const byAccount = videoChannel.Account
-
- const videoChannelObject = videoChannel.toActivityPubObject()
- const data = await updateActivityData(videoChannel.url, byAccount, videoChannelObject)
-
- const accountsInvolved = await db.VideoChannelShare.loadAccountsByShare(videoChannel.id)
- accountsInvolved.push(byAccount)
-
- return broadcastToFollowers(data, byAccount, accountsInvolved, t)
-}
-
-async function sendDeleteVideoChannel (videoChannel: VideoChannelInstance, t: Transaction) {
- const byAccount = videoChannel.Account
-
- const data = await deleteActivityData(videoChannel.url, byAccount)
-
- const accountsInvolved = await db.VideoChannelShare.loadAccountsByShare(videoChannel.id)
- accountsInvolved.push(byAccount)
-
- return broadcastToFollowers(data, byAccount, accountsInvolved, t)
-}
-
-async function sendAddVideo (video: VideoInstance, t: Transaction) {
- const byAccount = video.VideoChannel.Account
-
- const videoObject = video.toActivityPubObject()
- const data = await addActivityData(video.url, byAccount, video, video.VideoChannel.url, videoObject)
-
- return broadcastToFollowers(data, byAccount, [ byAccount ], t)
-}
-
-async function sendUpdateVideo (video: VideoInstance, t: Transaction) {
- const byAccount = video.VideoChannel.Account
-
- const videoObject = video.toActivityPubObject()
- const data = await updateActivityData(video.url, byAccount, videoObject)
-
- const accountsInvolved = await db.VideoShare.loadAccountsByShare(video.id)
- accountsInvolved.push(byAccount)
-
- return broadcastToFollowers(data, byAccount, accountsInvolved, t)
-}
-
-async function sendDeleteVideo (video: VideoInstance, t: Transaction) {
- const byAccount = video.VideoChannel.Account
-
- const data = await deleteActivityData(video.url, byAccount)
-
- const accountsInvolved = await db.VideoShare.loadAccountsByShare(video.id)
- accountsInvolved.push(byAccount)
-
- return broadcastToFollowers(data, byAccount, accountsInvolved, t)
-}
-
-async function sendDeleteAccount (account: AccountInstance, t: Transaction) {
- const data = await deleteActivityData(account.url, account)
-
- return broadcastToFollowers(data, account, [ account ], t)
-}
-
-async function sendVideoChannelAnnounce (byAccount: AccountInstance, videoChannel: VideoChannelInstance, t: Transaction) {
- const url = getActivityPubUrl('videoChannel', videoChannel.uuid) + '#announce'
- const announcedActivity = await createActivityData(url, videoChannel.Account, videoChannel.toActivityPubObject())
-
- const data = await announceActivityData(url, byAccount, announcedActivity)
- return broadcastToFollowers(data, byAccount, [ byAccount ], t)
-}
-
-async function sendVideoAnnounce (byAccount: AccountInstance, video: VideoInstance, t: Transaction) {
- const url = getActivityPubUrl('video', video.uuid) + '#announce'
-
- const videoChannel = video.VideoChannel
- const announcedActivity = await addActivityData(url, videoChannel.Account, video, videoChannel.url, video.toActivityPubObject())
-
- const data = await announceActivityData(url, byAccount, announcedActivity)
- return broadcastToFollowers(data, byAccount, [ byAccount ], t)
-}
-
-async function sendVideoAbuse (byAccount: AccountInstance, videoAbuse: VideoAbuseInstance, video: VideoInstance, t: Transaction) {
- const url = getActivityPubUrl('videoAbuse', videoAbuse.id.toString())
- const data = await createActivityData(url, byAccount, videoAbuse.toActivityPubObject())
-
- return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t)
-}
-
-async function sendAccept (byAccount: AccountInstance, toAccount: AccountInstance, t: Transaction) {
- const data = await acceptActivityData(byAccount)
-
- return unicastTo(data, byAccount, toAccount.inboxUrl, t)
-}
-
-async function sendFollow (byAccount: AccountInstance, toAccount: AccountInstance, t: Transaction) {
- const data = await followActivityData(toAccount.url, byAccount)
-
- return unicastTo(data, byAccount, toAccount.inboxUrl, t)
-}
-
-// ---------------------------------------------------------------------------
-
-export {
- sendCreateVideoChannel,
- sendUpdateVideoChannel,
- sendDeleteVideoChannel,
- sendAddVideo,
- sendUpdateVideo,
- sendDeleteVideo,
- sendDeleteAccount,
- sendAccept,
- sendFollow,
- sendVideoAbuse,
- sendVideoChannelAnnounce,
- sendVideoAnnounce
-}
-
-// ---------------------------------------------------------------------------
-
-async function broadcastToFollowers (data: any, byAccount: AccountInstance, toAccountFollowers: AccountInstance[], t: Transaction) {
- const toAccountFollowerIds = toAccountFollowers.map(a => a.id)
- const result = await db.AccountFollow.listAcceptedFollowerSharedInboxUrls(toAccountFollowerIds)
- if (result.data.length === 0) {
- logger.info('Not broadcast because of 0 followers for %s.', toAccountFollowerIds.join(', '))
- return undefined
- }
-
- const jobPayload = {
- uris: result.data,
- signatureAccountId: byAccount.id,
- body: data
- }
-
- return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpBroadcastHandler', jobPayload)
-}
-
-async function unicastTo (data: any, byAccount: AccountInstance, toAccountUrl: string, t: Transaction) {
- const jobPayload = {
- uris: [ toAccountUrl ],
- signatureAccountId: byAccount.id,
- body: data
- }
-
- return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpUnicastHandler', jobPayload)
-}
-
-async function getAudience (accountSender: AccountInstance, isPublic = true) {
- const followerInboxUrls = await accountSender.getFollowerSharedInboxUrls()
-
- // Thanks Mastodon: https://github.com/tootsuite/mastodon/blob/master/app/lib/activitypub/tag_manager.rb#L47
- let to = []
- let cc = []
-
- if (isPublic) {
- to = [ ACTIVITY_PUB.PUBLIC ]
- cc = followerInboxUrls
- } else { // Unlisted
- to = followerInboxUrls
- cc = [ ACTIVITY_PUB.PUBLIC ]
- }
-
- return { to, cc }
-}
-
-async function createActivityData (url: string, byAccount: AccountInstance, object: any) {
- const { to, cc } = await getAudience(byAccount)
- const activity: ActivityCreate = {
- type: 'Create',
- id: url,
- actor: byAccount.url,
- to,
- cc,
- object
- }
-
- return activity
-}
-
-async function updateActivityData (url: string, byAccount: AccountInstance, object: any) {
- const { to, cc } = await getAudience(byAccount)
- const activity: ActivityUpdate = {
- type: 'Update',
- id: url,
- actor: byAccount.url,
- to,
- cc,
- object
- }
-
- return activity
-}
-
-async function deleteActivityData (url: string, byAccount: AccountInstance) {
- const activity: ActivityDelete = {
- type: 'Delete',
- id: url,
- actor: byAccount.url
- }
-
- return activity
-}
-
-async function addActivityData (url: string, byAccount: AccountInstance, video: VideoInstance, target: string, object: any) {
- const videoPublic = video.privacy === VideoPrivacy.PUBLIC
-
- const { to, cc } = await getAudience(byAccount, videoPublic)
- const activity: ActivityAdd = {
- type: 'Add',
- id: url,
- actor: byAccount.url,
- to,
- cc,
- object,
- target
- }
-
- return activity
-}
-
-async function announceActivityData (url: string, byAccount: AccountInstance, object: any) {
- const activity = {
- type: 'Announce',
- id: url,
- actor: byAccount.url,
- object
- }
-
- return activity
-}
-
-async function followActivityData (url: string, byAccount: AccountInstance) {
- const activity: ActivityFollow = {
- type: 'Follow',
- id: byAccount.url,
- actor: byAccount.url,
- object: url
- }
-
- return activity
-}
-
-async function acceptActivityData (byAccount: AccountInstance) {
- const activity: ActivityAccept = {
- type: 'Accept',
- id: byAccount.url,
- actor: byAccount.url
- }
-
- return activity
-}
--- /dev/null
+export * from './send-accept'
+export * from './send-add'
+export * from './send-announce'
+export * from './send-create'
+export * from './send-delete'
+export * from './send-follow'
+export * from './send-update'
--- /dev/null
+import { Transaction } from 'sequelize'
+import { logger } from '../../../helpers/logger'
+import { ACTIVITY_PUB, database as db } from '../../../initializers'
+import { AccountInstance } from '../../../models/account/account-interface'
+import { activitypubHttpJobScheduler } from '../../jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler'
+
+async function broadcastToFollowers (data: any, byAccount: AccountInstance, toAccountFollowers: AccountInstance[], t: Transaction) {
+ const toAccountFollowerIds = toAccountFollowers.map(a => a.id)
+ const result = await db.AccountFollow.listAcceptedFollowerSharedInboxUrls(toAccountFollowerIds)
+ if (result.data.length === 0) {
+ logger.info('Not broadcast because of 0 followers for %s.', toAccountFollowerIds.join(', '))
+ return undefined
+ }
+
+ const jobPayload = {
+ uris: result.data,
+ signatureAccountId: byAccount.id,
+ body: data
+ }
+
+ return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpBroadcastHandler', jobPayload)
+}
+
+async function unicastTo (data: any, byAccount: AccountInstance, toAccountUrl: string, t: Transaction) {
+ const jobPayload = {
+ uris: [ toAccountUrl ],
+ signatureAccountId: byAccount.id,
+ body: data
+ }
+
+ return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpUnicastHandler', jobPayload)
+}
+
+async function getAudience (accountSender: AccountInstance, isPublic = true) {
+ const followerInboxUrls = await accountSender.getFollowerSharedInboxUrls()
+
+ // Thanks Mastodon: https://github.com/tootsuite/mastodon/blob/master/app/lib/activitypub/tag_manager.rb#L47
+ let to = []
+ let cc = []
+
+ if (isPublic) {
+ to = [ ACTIVITY_PUB.PUBLIC ]
+ cc = followerInboxUrls
+ } else { // Unlisted
+ to = followerInboxUrls
+ cc = [ ACTIVITY_PUB.PUBLIC ]
+ }
+
+ return { to, cc }
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ broadcastToFollowers,
+ unicastTo,
+ getAudience
+}
--- /dev/null
+import { Transaction } from 'sequelize'
+import { ActivityAccept } from '../../../../shared/models/activitypub/activity'
+import { AccountInstance } from '../../../models'
+import { AccountFollowInstance } from '../../../models/account/account-follow-interface'
+import { unicastTo } from './misc'
+import { getAccountFollowAcceptActivityPubUrl } from '../../../helpers/activitypub'
+
+async function sendAccept (accountFollow: AccountFollowInstance, t: Transaction) {
+ const follower = accountFollow.AccountFollower
+ const me = accountFollow.AccountFollowing
+
+ const url = getAccountFollowAcceptActivityPubUrl(accountFollow)
+ const data = await acceptActivityData(url, me)
+
+ return unicastTo(data, me, follower.inboxUrl, t)
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ sendAccept
+}
+
+// ---------------------------------------------------------------------------
+
+async function acceptActivityData (url: string, byAccount: AccountInstance) {
+ const activity: ActivityAccept = {
+ type: 'Accept',
+ id: url,
+ actor: byAccount.url
+ }
+
+ return activity
+}
--- /dev/null
+import { Transaction } from 'sequelize'
+import { ActivityAdd } from '../../../../shared/models/activitypub/activity'
+import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum'
+import { AccountInstance, VideoInstance } from '../../../models'
+import { broadcastToFollowers, getAudience } from './misc'
+
+async function sendAddVideo (video: VideoInstance, t: Transaction) {
+ const byAccount = video.VideoChannel.Account
+
+ const videoObject = video.toActivityPubObject()
+ const data = await addActivityData(video.url, byAccount, video, video.VideoChannel.url, videoObject)
+
+ return broadcastToFollowers(data, byAccount, [ byAccount ], t)
+}
+
+async function addActivityData (url: string, byAccount: AccountInstance, video: VideoInstance, target: string, object: any) {
+ const videoPublic = video.privacy === VideoPrivacy.PUBLIC
+
+ const { to, cc } = await getAudience(byAccount, videoPublic)
+ const activity: ActivityAdd = {
+ type: 'Add',
+ id: url,
+ actor: byAccount.url,
+ to,
+ cc,
+ object,
+ target
+ }
+
+ return activity
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ addActivityData,
+ sendAddVideo
+}
--- /dev/null
+import { Transaction } from 'sequelize'
+import { AccountInstance, VideoInstance } from '../../../models'
+import { VideoChannelInstance } from '../../../models/video/video-channel-interface'
+import { broadcastToFollowers } from './misc'
+import { addActivityData } from './send-add'
+import { createActivityData } from './send-create'
+import { getAnnounceActivityPubUrl } from '../../../helpers/activitypub'
+
+async function sendVideoAnnounce (byAccount: AccountInstance, video: VideoInstance, t: Transaction) {
+ const url = getAnnounceActivityPubUrl(video.url, byAccount)
+
+ const videoChannel = video.VideoChannel
+ const announcedActivity = await addActivityData(url, videoChannel.Account, video, videoChannel.url, video.toActivityPubObject())
+
+ const data = await announceActivityData(url, byAccount, announcedActivity)
+ return broadcastToFollowers(data, byAccount, [ byAccount ], t)
+}
+
+async function sendVideoChannelAnnounce (byAccount: AccountInstance, videoChannel: VideoChannelInstance, t: Transaction) {
+ const url = getAnnounceActivityPubUrl(videoChannel.url, byAccount)
+ const announcedActivity = await createActivityData(url, videoChannel.Account, videoChannel.toActivityPubObject())
+
+ const data = await announceActivityData(url, byAccount, announcedActivity)
+ return broadcastToFollowers(data, byAccount, [ byAccount ], t)
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ sendVideoAnnounce,
+ sendVideoChannelAnnounce
+}
+
+// ---------------------------------------------------------------------------
+
+async function announceActivityData (url: string, byAccount: AccountInstance, object: any) {
+ const activity = {
+ type: 'Announce',
+ id: url,
+ actor: byAccount.url,
+ object
+ }
+
+ return activity
+}
--- /dev/null
+import { Transaction } from 'sequelize'
+import { ActivityCreate } from '../../../../shared/models/activitypub/activity'
+import { AccountInstance, VideoChannelInstance, VideoInstance } from '../../../models'
+import { VideoAbuseInstance } from '../../../models/video/video-abuse-interface'
+import { broadcastToFollowers, getAudience, unicastTo } from './misc'
+import { getVideoAbuseActivityPubUrl } from '../../../helpers/activitypub'
+
+async function sendCreateVideoChannel (videoChannel: VideoChannelInstance, t: Transaction) {
+ const byAccount = videoChannel.Account
+
+ const videoChannelObject = videoChannel.toActivityPubObject()
+ const data = await createActivityData(videoChannel.url, byAccount, videoChannelObject)
+
+ return broadcastToFollowers(data, byAccount, [ byAccount ], t)
+}
+
+async function sendVideoAbuse (byAccount: AccountInstance, videoAbuse: VideoAbuseInstance, video: VideoInstance, t: Transaction) {
+ const url = getVideoAbuseActivityPubUrl(videoAbuse)
+ const data = await createActivityData(url, byAccount, videoAbuse.toActivityPubObject())
+
+ return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t)
+}
+
+async function createActivityData (url: string, byAccount: AccountInstance, object: any) {
+ const { to, cc } = await getAudience(byAccount)
+ const activity: ActivityCreate = {
+ type: 'Create',
+ id: url,
+ actor: byAccount.url,
+ to,
+ cc,
+ object
+ }
+
+ return activity
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ sendCreateVideoChannel,
+ sendVideoAbuse,
+ createActivityData
+}
--- /dev/null
+import { Transaction } from 'sequelize'
+import { ActivityDelete } from '../../../../shared/models/activitypub/activity'
+import { database as db } from '../../../initializers'
+import { AccountInstance, VideoChannelInstance, VideoInstance } from '../../../models'
+import { broadcastToFollowers } from './misc'
+
+async function sendDeleteVideoChannel (videoChannel: VideoChannelInstance, t: Transaction) {
+ const byAccount = videoChannel.Account
+
+ const data = await deleteActivityData(videoChannel.url, byAccount)
+
+ const accountsInvolved = await db.VideoChannelShare.loadAccountsByShare(videoChannel.id)
+ accountsInvolved.push(byAccount)
+
+ return broadcastToFollowers(data, byAccount, accountsInvolved, t)
+}
+
+async function sendDeleteVideo (video: VideoInstance, t: Transaction) {
+ const byAccount = video.VideoChannel.Account
+
+ const data = await deleteActivityData(video.url, byAccount)
+
+ const accountsInvolved = await db.VideoShare.loadAccountsByShare(video.id)
+ accountsInvolved.push(byAccount)
+
+ return broadcastToFollowers(data, byAccount, accountsInvolved, t)
+}
+
+async function sendDeleteAccount (account: AccountInstance, t: Transaction) {
+ const data = await deleteActivityData(account.url, account)
+
+ return broadcastToFollowers(data, account, [ account ], t)
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ sendDeleteVideoChannel,
+ sendDeleteVideo,
+ sendDeleteAccount
+}
+
+// ---------------------------------------------------------------------------
+
+async function deleteActivityData (url: string, byAccount: AccountInstance) {
+ const activity: ActivityDelete = {
+ type: 'Delete',
+ id: url,
+ actor: byAccount.url
+ }
+
+ return activity
+}
--- /dev/null
+import { Transaction } from 'sequelize'
+import { ActivityFollow } from '../../../../shared/models/activitypub/activity'
+import { AccountInstance } from '../../../models'
+import { AccountFollowInstance } from '../../../models/account/account-follow-interface'
+import { unicastTo } from './misc'
+import { getAccountFollowActivityPubUrl } from '../../../helpers/activitypub'
+
+async function sendFollow (accountFollow: AccountFollowInstance, t: Transaction) {
+ const me = accountFollow.AccountFollower
+ const following = accountFollow.AccountFollowing
+
+ const url = getAccountFollowActivityPubUrl(accountFollow)
+ const data = await followActivityData(url, me, following)
+
+ return unicastTo(data, me, following.inboxUrl, t)
+}
+
+async function followActivityData (url: string, byAccount: AccountInstance, targetAccount: AccountInstance) {
+ const activity: ActivityFollow = {
+ type: 'Follow',
+ id: url,
+ actor: byAccount.url,
+ object: targetAccount.url
+ }
+
+ return activity
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ sendFollow,
+ followActivityData
+}
--- /dev/null
+import { Transaction } from 'sequelize'
+import { ActivityFollow, ActivityUndo } from '../../../../shared/models/activitypub/activity'
+import { AccountInstance } from '../../../models'
+import { AccountFollowInstance } from '../../../models/account/account-follow-interface'
+import { unicastTo } from './misc'
+import { getAccountFollowActivityPubUrl, getUndoActivityPubUrl } from '../../../helpers/activitypub'
+import { followActivityData } from './send-follow'
+
+async function sendUndoFollow (accountFollow: AccountFollowInstance, t: Transaction) {
+ const me = accountFollow.AccountFollower
+ const following = accountFollow.AccountFollowing
+
+ const followUrl = getAccountFollowActivityPubUrl(accountFollow)
+ const undoUrl = getUndoActivityPubUrl(followUrl)
+
+ const object = await followActivityData(followUrl, me, following)
+ const data = await undoActivityData(undoUrl, me, object)
+
+ return unicastTo(data, me, following.inboxUrl, t)
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ sendUndoFollow
+}
+
+// ---------------------------------------------------------------------------
+
+async function undoActivityData (url: string, byAccount: AccountInstance, object: ActivityFollow) {
+ const activity: ActivityUndo = {
+ type: 'Undo',
+ id: url,
+ actor: byAccount.url,
+ object
+ }
+
+ return activity
+}
--- /dev/null
+import { Transaction } from 'sequelize'
+import { ActivityUpdate } from '../../../../shared/models/activitypub/activity'
+import { getUpdateActivityPubUrl } from '../../../helpers/activitypub'
+import { database as db } from '../../../initializers'
+import { AccountInstance, VideoChannelInstance, VideoInstance } from '../../../models'
+import { broadcastToFollowers, getAudience } from './misc'
+
+async function sendUpdateVideoChannel (videoChannel: VideoChannelInstance, t: Transaction) {
+ const byAccount = videoChannel.Account
+
+ const url = getUpdateActivityPubUrl(videoChannel.url, videoChannel.updatedAt.toISOString())
+ const videoChannelObject = videoChannel.toActivityPubObject()
+ const data = await updateActivityData(url, byAccount, videoChannelObject)
+
+ const accountsInvolved = await db.VideoChannelShare.loadAccountsByShare(videoChannel.id)
+ accountsInvolved.push(byAccount)
+
+ return broadcastToFollowers(data, byAccount, accountsInvolved, t)
+}
+
+async function sendUpdateVideo (video: VideoInstance, t: Transaction) {
+ const byAccount = video.VideoChannel.Account
+
+ const url = getUpdateActivityPubUrl(video.url, video.updatedAt.toISOString())
+ const videoObject = video.toActivityPubObject()
+ const data = await updateActivityData(url, byAccount, videoObject)
+
+ const accountsInvolved = await db.VideoShare.loadAccountsByShare(video.id)
+ accountsInvolved.push(byAccount)
+
+ return broadcastToFollowers(data, byAccount, accountsInvolved, t)
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ sendUpdateVideoChannel,
+ sendUpdateVideo
+}
+
+// ---------------------------------------------------------------------------
+
+async function updateActivityData (url: string, byAccount: AccountInstance, object: any) {
+ const { to, cc } = await getAudience(byAccount)
+ const activity: ActivityUpdate = {
+ type: 'Update',
+ id: url,
+ actor: byAccount.url,
+ to,
+ cc,
+ object
+ }
+
+ return activity
+}
import { database as db } from '../../../initializers/database'
import { VideoInstance } from '../../../models'
-import { sendAddVideo } from '../../activitypub/send-request'
+
import { JobScheduler } from '../job-scheduler'
import { TranscodingJobPayload } from './transcoding-job-scheduler'
import { shareVideoByServer } from '../../../helpers/activitypub'
+import { sendAddVideo } from '../../activitypub/send/send-add'
async function process (data: TranscodingJobPayload, jobId: number) {
const video = await db.Video.loadByUUIDAndPopulateAccountAndServerAndTags(data.videoUUID)
import { logger } from '../../../helpers'
import { database as db } from '../../../initializers/database'
import { VideoInstance } from '../../../models'
-import { sendUpdateVideo } from '../../activitypub/send-request'
+import { sendUpdateVideo } from '../../activitypub/send/send-update'
async function process (data: { videoUUID: string, resolution: VideoResolution }, jobId: number) {
const video = await db.Video.loadByUUIDAndPopulateAccountAndServerAndTags(data.videoUUID)
import * as Sequelize from 'sequelize'
-import { getActivityPubUrl } from '../helpers/activitypub'
import { createPrivateAndPublicKeys } from '../helpers/peertube-crypto'
import { database as db } from '../initializers'
import { CONFIG } from '../initializers/constants'
import { UserInstance } from '../models'
import { createVideoChannel } from './video-channel'
import { logger } from '../helpers/logger'
+import { getAccountActivityPubUrl } from '../helpers/activitypub'
async function createUserAccountAndChannel (user: UserInstance, validateUser = true) {
const { account, videoChannel } = await db.sequelize.transaction(async t => {
}
async function createLocalAccountWithoutKeys (name: string, userId: number, applicationId: number, t: Sequelize.Transaction) {
- const url = getActivityPubUrl('account', name)
+ const url = getAccountActivityPubUrl(name)
const accountInstance = db.Account.build({
name,
import * as Sequelize from 'sequelize'
import { VideoChannelCreate } from '../../shared/models'
import { logger } from '../helpers'
-import { getActivityPubUrl } from '../helpers/activitypub'
import { database as db } from '../initializers'
import { AccountInstance } from '../models'
+import { getVideoChannelActivityPubUrl } from '../helpers/activitypub'
async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account: AccountInstance, t: Sequelize.Transaction) {
const videoChannelData = {
}
const videoChannel = db.VideoChannel.build(videoChannelData)
- videoChannel.set('url', getActivityPubUrl('videoChannel', videoChannel.uuid))
+ videoChannel.set('url', getVideoChannelActivityPubUrl(videoChannel))
const options = { transaction: t }
--- /dev/null
+import * as express from 'express'
+import { body } from 'express-validator/check'
+import { isTestInstance } from '../../helpers/core-utils'
+import { isAccountIdValid } from '../../helpers/custom-validators/activitypub/account'
+import { isEachUniqueHostValid } from '../../helpers/custom-validators/servers'
+import { logger } from '../../helpers/logger'
+import { CONFIG, database as db } from '../../initializers'
+import { checkErrors } from './utils'
+import { getServerAccount } from '../../helpers/utils'
+
+const followValidator = [
+ body('hosts').custom(isEachUniqueHostValid).withMessage('Should have an array of unique hosts'),
+
+ (req: express.Request, res: express.Response, next: express.NextFunction) => {
+ // Force https if the administrator wants to make friends
+ if (isTestInstance() === false && CONFIG.WEBSERVER.SCHEME === 'http') {
+ return res.status(400)
+ .json({
+ error: 'Cannot follow non HTTPS web server.'
+ })
+ .end()
+ }
+
+ logger.debug('Checking follow parameters', { parameters: req.body })
+
+ checkErrors(req, res, next)
+ }
+]
+
+const removeFollowingValidator = [
+ body('accountId').custom(isAccountIdValid).withMessage('Should have a valid account id'),
+
+ (req: express.Request, res: express.Response, next: express.NextFunction) => {
+ logger.debug('Checking follow parameters', { parameters: req.body })
+
+ checkErrors(req, res, async () => {
+ try {
+ const serverAccount = await getServerAccount()
+ const following = await db.AccountFollow.loadByAccountAndTarget(serverAccount.id, req.params.accountId)
+
+ if (!following) {
+ return res.status(404)
+ .end()
+ }
+
+ res.locals.following = following
+
+ return next()
+ } catch (err) {
+ logger.error('Error in remove following validator.', err)
+ return res.sendStatus(500)
+ }
+ })
+ }
+]
+
+// ---------------------------------------------------------------------------
+
+export {
+ followValidator,
+ removeFollowingValidator
+}
export * from './oembed'
export * from './activitypub'
export * from './pagination'
-export * from './servers'
+export * from './follows'
export * from './sort'
export * from './users'
export * from './videos'
+++ /dev/null
-import * as express from 'express'
-import { body } from 'express-validator/check'
-import { isEachUniqueHostValid } from '../../helpers/custom-validators/servers'
-import { isTestInstance } from '../../helpers/core-utils'
-import { CONFIG } from '../../initializers/constants'
-import { logger } from '../../helpers/logger'
-import { checkErrors } from './utils'
-
-const followValidator = [
- body('hosts').custom(isEachUniqueHostValid).withMessage('Should have an array of unique hosts'),
-
- (req: express.Request, res: express.Response, next: express.NextFunction) => {
- // Force https if the administrator wants to make friends
- if (isTestInstance() === false && CONFIG.WEBSERVER.SCHEME === 'http') {
- return res.status(400)
- .json({
- error: 'Cannot follow non HTTPS web server.'
- })
- .end()
- }
-
- logger.debug('Checking follow parameters', { parameters: req.body })
-
- checkErrors(req, res, next)
- }
-]
-
-// ---------------------------------------------------------------------------
-
-export {
- followValidator
-}
where: {
accountId,
targetAccountId
- }
+ },
+ include: [
+ {
+ model: AccountFollow[ 'sequelize' ].models.Account,
+ required: true,
+ as: 'AccountFollower'
+ },
+ {
+ model: AccountFollow['sequelize'].models.Account,
+ required: true,
+ as: 'AccountFollowing'
+ }
+ ]
}
return AccountFollow.findOne(query)
export interface AccountAttributes {
name: string
- url: string
+ url?: string
publicKey: string
privateKey: string
followersCount: number
import * as Sequelize from 'sequelize'
-
import {
activityPubContextify,
isAccountFollowersCountValid,
isUserUsernameValid
} from '../../helpers'
import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers/constants'
-import { sendDeleteAccount } from '../../lib/activitypub/send-request'
+import { sendDeleteAccount } from '../../lib/activitypub/send/send-delete'
import { addMethodsToModel } from '../utils'
import { AccountAttributes, AccountInstance, AccountMethods } from './account-interface'
import * as Sequelize from 'sequelize'
-
-import { isVideoChannelNameValid, isVideoChannelDescriptionValid } from '../../helpers'
-
-import { addMethodsToModel, getSort } from '../utils'
-import {
- VideoChannelInstance,
- VideoChannelAttributes,
-
- VideoChannelMethods
-} from './video-channel-interface'
-import { sendDeleteVideoChannel } from '../../lib/activitypub/send-request'
+import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../../helpers'
import { isVideoChannelUrlValid } from '../../helpers/custom-validators/video-channels'
import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
+import { sendDeleteVideoChannel } from '../../lib/activitypub/send/send-delete'
+
+import { addMethodsToModel, getSort } from '../utils'
+import { VideoChannelAttributes, VideoChannelInstance, VideoChannelMethods } from './video-channel-interface'
let VideoChannel: Sequelize.Model<VideoChannelInstance, VideoChannelAttributes>
let toFormattedJSON: VideoChannelMethods.ToFormattedJSON
import {
createTorrentPromise,
generateImageFromVideoFile,
- getActivityPubUrl,
getVideoFileHeight,
isVideoCategoryValid,
isVideoDescriptionValid,
VIDEO_LICENCES,
VIDEO_PRIVACIES
} from '../../initializers'
-import { sendDeleteVideo } from '../../lib/activitypub/send-request'
import { addMethodsToModel, getSort } from '../utils'
import { TagInstance } from './tag-interface'
import { VideoFileInstance, VideoFileModel } from './video-file-interface'
import { VideoAttributes, VideoInstance, VideoMethods } from './video-interface'
+import { sendDeleteVideo } from '../../lib/index'
const Buffer = safeBuffer.Buffer
const videoObject: VideoTorrentObject = {
type: 'Video' as 'Video',
- id: getActivityPubUrl('video', this.uuid),
+ id: this.url,
name: this.name,
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration
duration: 'PT' + this.duration + 'S',
width: THUMBNAILS_SIZE.width,
height: THUMBNAILS_SIZE.height
},
- url
+ url // FIXME: needed?
}
return videoObject
expect(video1.serverHost).to.equal('localhost:9003')
expect(video1.duration).to.equal(5)
expect(video1.tags).to.deep.equal([ 'tag1p3' ])
- expect(video1.author).to.equal('root')
+ expect(video1.account).to.equal('root')
expect(dateIsValid(video1.createdAt)).to.be.true
expect(dateIsValid(video1.updatedAt)).to.be.true
expect(video2.serverHost).to.equal('localhost:9003')
expect(video2.duration).to.equal(5)
expect(video2.tags).to.deep.equal([ 'tag2p3', 'tag3p3', 'tag4p3' ])
- expect(video2.author).to.equal('root')
+ expect(video2.account).to.equal('root')
expect(dateIsValid(video2.createdAt)).to.be.true
expect(dateIsValid(video2.updatedAt)).to.be.true
expect(baseVideo.licence).to.equal(video.licence)
expect(baseVideo.category).to.equal(video.category)
expect(baseVideo.nsfw).to.equal(video.nsfw)
- expect(baseVideo.author).to.equal(video.account)
+ expect(baseVideo.account).to.equal(video.account)
expect(baseVideo.tags).to.deep.equal(video.tags)
}
})
import { VideoAbuseObject } from './objects/video-abuse-object'
export type Activity = ActivityCreate | ActivityAdd | ActivityUpdate |
- ActivityDelete | ActivityFollow | ActivityAccept | ActivityAnnounce
+ ActivityDelete | ActivityFollow | ActivityAccept | ActivityAnnounce |
+ ActivityUndo
-// Flag -> report abuse
-export type ActivityType = 'Create' | 'Add' | 'Update' | 'Delete' | 'Follow' | 'Accept' | 'Announce'
+export type ActivityType = 'Create' | 'Add' | 'Update' | 'Delete' | 'Follow' | 'Accept' | 'Announce' | 'Undo'
export interface BaseActivity {
'@context'?: any[]
type: 'Announce'
object: ActivityCreate | ActivityAdd
}
+
+export interface ActivityUndo extends BaseActivity {
+ type: 'Undo',
+ object: ActivityFollow
+}