mirror of
https://github.com/Chocobozzz/PeerTube.git
synced 2024-11-25 18:20:31 -06:00
Server shares user videos
This commit is contained in:
parent
efc32059d9
commit
20494f1221
@ -4,10 +4,13 @@ import * as express from 'express'
|
||||
import { database as db } from '../../initializers'
|
||||
import { executeIfActivityPub, localAccountValidator } from '../../middlewares'
|
||||
import { pageToStartAndCount } from '../../helpers'
|
||||
import { AccountInstance } from '../../models'
|
||||
import { AccountInstance, VideoChannelInstance } from '../../models'
|
||||
import { activityPubCollectionPagination } from '../../helpers/activitypub'
|
||||
import { ACTIVITY_PUB } from '../../initializers/constants'
|
||||
import { asyncMiddleware } from '../../middlewares/async'
|
||||
import { videosGetValidator } from '../../middlewares/validators/videos'
|
||||
import { VideoInstance } from '../../models/video/video-interface'
|
||||
import { videoChannelsGetValidator } from '../../middlewares/validators/video-channels'
|
||||
|
||||
const activityPubClientRouter = express.Router()
|
||||
|
||||
@ -26,6 +29,16 @@ activityPubClientRouter.get('/account/:name/following',
|
||||
executeIfActivityPub(asyncMiddleware(accountFollowingController))
|
||||
)
|
||||
|
||||
activityPubClientRouter.get('/videos/watch/:id',
|
||||
executeIfActivityPub(videosGetValidator),
|
||||
executeIfActivityPub(asyncMiddleware(videoController))
|
||||
)
|
||||
|
||||
activityPubClientRouter.get('/video-channels/:id',
|
||||
executeIfActivityPub(videoChannelsGetValidator),
|
||||
executeIfActivityPub(asyncMiddleware(videoChannelController))
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
@ -63,3 +76,15 @@ async function accountFollowingController (req: express.Request, res: express.Re
|
||||
|
||||
return res.json(activityPubResult)
|
||||
}
|
||||
|
||||
async function videoController (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const video: VideoInstance = res.locals.video
|
||||
|
||||
return res.json(video.toActivityPubObject())
|
||||
}
|
||||
|
||||
async function videoChannelController (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const videoChannel: VideoChannelInstance = res.locals.videoChannel
|
||||
|
||||
return res.json(videoChannel.toActivityPubObject())
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
paginationValidator,
|
||||
setPagination,
|
||||
setVideoChannelsSort,
|
||||
videoChannelGetValidator,
|
||||
videoChannelsGetValidator,
|
||||
videoChannelsAddValidator,
|
||||
videoChannelsRemoveValidator,
|
||||
videoChannelsSortValidator,
|
||||
@ -53,7 +53,7 @@ videoChannelRouter.delete('/channels/:id',
|
||||
)
|
||||
|
||||
videoChannelRouter.get('/channels/:id',
|
||||
videoChannelGetValidator,
|
||||
videoChannelsGetValidator,
|
||||
asyncMiddleware(getVideoChannel)
|
||||
)
|
||||
|
||||
|
@ -4,13 +4,18 @@ import * as Sequelize from 'sequelize'
|
||||
import * as url from 'url'
|
||||
import { ActivityIconObject } from '../../shared/index'
|
||||
import { ActivityPubActor } from '../../shared/models/activitypub/activitypub-actor'
|
||||
import { VideoChannelObject } from '../../shared/models/activitypub/objects/video-channel-object'
|
||||
import { ResultList } from '../../shared/models/result-list.model'
|
||||
import { database as db, REMOTE_SCHEME } from '../initializers'
|
||||
import { ACTIVITY_PUB_ACCEPT_HEADER, CONFIG, STATIC_PATHS } from '../initializers/constants'
|
||||
import { sendAnnounce } from '../lib/activitypub/send-request'
|
||||
import { videoChannelActivityObjectToDBAttributes } from '../lib/activitypub/misc'
|
||||
import { sendVideoAnnounce } from '../lib/activitypub/send-request'
|
||||
import { sendVideoChannelAnnounce } from '../lib/index'
|
||||
import { AccountInstance } from '../models/account/account-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 { doRequest, doRequestAndSaveToFile } from './requests'
|
||||
import { getServerAccount } from './utils'
|
||||
@ -34,7 +39,7 @@ async function shareVideoChannelByServer (videoChannel: VideoChannelInstance, t:
|
||||
videoChannelId: videoChannel.id
|
||||
}, { transaction: t })
|
||||
|
||||
return sendAnnounce(serverAccount, videoChannel, t)
|
||||
return sendVideoChannelAnnounce(serverAccount, videoChannel, t)
|
||||
}
|
||||
|
||||
async function shareVideoByServer (video: VideoInstance, t: Sequelize.Transaction) {
|
||||
@ -45,7 +50,7 @@ async function shareVideoByServer (video: VideoInstance, t: Sequelize.Transactio
|
||||
videoId: video.id
|
||||
}, { transaction: t })
|
||||
|
||||
return sendAnnounce(serverAccount, video, t)
|
||||
return sendVideoAnnounce(serverAccount, video, t)
|
||||
}
|
||||
|
||||
function getActivityPubUrl (type: 'video' | 'videoChannel' | 'account' | 'videoAbuse', id: string) {
|
||||
@ -66,13 +71,27 @@ async function getOrCreateAccount (accountUrl: string) {
|
||||
if (res === undefined) throw new Error('Cannot fetch remote account.')
|
||||
|
||||
// Save our new account in database
|
||||
const account = res.account
|
||||
await account.save()
|
||||
account = await res.account.save()
|
||||
}
|
||||
|
||||
return account
|
||||
}
|
||||
|
||||
async function getOrCreateVideoChannel (ownerAccount: AccountInstance, videoChannelUrl: string) {
|
||||
let videoChannel = await db.VideoChannel.loadByUrl(videoChannelUrl)
|
||||
|
||||
// We don't have this account in our database, fetch it on remote
|
||||
if (!videoChannel) {
|
||||
videoChannel = await fetchRemoteVideoChannel(ownerAccount, videoChannelUrl)
|
||||
if (videoChannel === undefined) throw new Error('Cannot fetch remote video channel.')
|
||||
|
||||
// Save our new video channel in database
|
||||
await videoChannel.save()
|
||||
}
|
||||
|
||||
return videoChannel
|
||||
}
|
||||
|
||||
async function fetchRemoteAccountAndCreateServer (accountUrl: string) {
|
||||
const options = {
|
||||
uri: accountUrl,
|
||||
@ -131,6 +150,38 @@ async function fetchRemoteAccountAndCreateServer (accountUrl: string) {
|
||||
return { account, server }
|
||||
}
|
||||
|
||||
async function fetchRemoteVideoChannel (ownerAccount: AccountInstance, videoChannelUrl: string) {
|
||||
const options = {
|
||||
uri: videoChannelUrl,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': ACTIVITY_PUB_ACCEPT_HEADER
|
||||
}
|
||||
}
|
||||
|
||||
logger.info('Fetching remote video channel %s.', videoChannelUrl)
|
||||
|
||||
let requestResult
|
||||
try {
|
||||
requestResult = await doRequest(options)
|
||||
} catch (err) {
|
||||
logger.warn('Cannot fetch remote video channel %s.', videoChannelUrl, err)
|
||||
return undefined
|
||||
}
|
||||
|
||||
const videoChannelJSON: VideoChannelObject = JSON.parse(requestResult.body)
|
||||
if (isVideoChannelObjectValid(videoChannelJSON) === false) {
|
||||
logger.debug('Remote video channel JSON is not valid.', { videoChannelJSON })
|
||||
return undefined
|
||||
}
|
||||
|
||||
const videoChannelAttributes = videoChannelActivityObjectToDBAttributes(videoChannelJSON, ownerAccount)
|
||||
const videoChannel = db.VideoChannel.build(videoChannelAttributes)
|
||||
videoChannel.Account = ownerAccount
|
||||
|
||||
return videoChannel
|
||||
}
|
||||
|
||||
function fetchRemoteVideoPreview (video: VideoInstance) {
|
||||
// FIXME: use url
|
||||
const host = video.VideoChannel.Account.Server.host
|
||||
@ -200,7 +251,8 @@ export {
|
||||
fetchRemoteVideoPreview,
|
||||
fetchRemoteVideoDescription,
|
||||
shareVideoChannelByServer,
|
||||
shareVideoByServer
|
||||
shareVideoByServer,
|
||||
getOrCreateVideoChannel
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
@ -2,8 +2,7 @@ import * as validator from 'validator'
|
||||
import { isAccountAcceptActivityValid, isAccountDeleteActivityValid, isAccountFollowActivityValid } from './account'
|
||||
import { isActivityPubUrlValid } from './misc'
|
||||
import {
|
||||
isVideoAnnounceValid,
|
||||
isVideoChannelAnnounceValid,
|
||||
isAnnounceValid,
|
||||
isVideoChannelCreateActivityValid,
|
||||
isVideoChannelDeleteActivityValid,
|
||||
isVideoChannelUpdateActivityValid,
|
||||
@ -37,8 +36,7 @@ function isActivityValid (activity: any) {
|
||||
isAccountFollowActivityValid(activity) ||
|
||||
isAccountAcceptActivityValid(activity) ||
|
||||
isVideoFlagValid(activity) ||
|
||||
isVideoAnnounceValid(activity) ||
|
||||
isVideoChannelAnnounceValid(activity)
|
||||
isAnnounceValid(activity)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
@ -21,7 +21,7 @@ function isActivityPubUrlValid (url: string) {
|
||||
}
|
||||
|
||||
function isBaseActivityValid (activity: any, type: string) {
|
||||
return Array.isArray(activity['@context']) &&
|
||||
return (activity['@context'] === undefined || Array.isArray(activity['@context'])) &&
|
||||
activity.type === type &&
|
||||
isActivityPubUrlValid(activity.id) &&
|
||||
isActivityPubUrlValid(activity.actor) &&
|
||||
|
@ -39,6 +39,7 @@ function isActivityPubVideoDurationValid (value: string) {
|
||||
|
||||
function isVideoTorrentObjectValid (video: any) {
|
||||
return video.type === 'Video' &&
|
||||
isActivityPubUrlValid(video.id) &&
|
||||
isVideoNameValid(video.name) &&
|
||||
isActivityPubVideoDurationValid(video.duration) &&
|
||||
isUUIDValid(video.uuid) &&
|
||||
@ -62,14 +63,12 @@ function isVideoFlagValid (activity: any) {
|
||||
isActivityPubUrlValid(activity.object)
|
||||
}
|
||||
|
||||
function isVideoAnnounceValid (activity: any) {
|
||||
function isAnnounceValid (activity: any) {
|
||||
return isBaseActivityValid(activity, 'Announce') &&
|
||||
isVideoTorrentObjectValid(activity.object)
|
||||
}
|
||||
|
||||
function isVideoChannelAnnounceValid (activity: any) {
|
||||
return isBaseActivityValid(activity, 'Announce') &&
|
||||
isVideoChannelObjectValid(activity.object)
|
||||
(
|
||||
isVideoChannelCreateActivityValid(activity.object) ||
|
||||
isVideoTorrentAddActivityValid(activity.object)
|
||||
)
|
||||
}
|
||||
|
||||
function isVideoChannelCreateActivityValid (activity: any) {
|
||||
@ -88,8 +87,11 @@ function isVideoChannelDeleteActivityValid (activity: any) {
|
||||
|
||||
function isVideoChannelObjectValid (videoChannel: any) {
|
||||
return videoChannel.type === 'VideoChannel' &&
|
||||
isActivityPubUrlValid(videoChannel.id) &&
|
||||
isVideoChannelNameValid(videoChannel.name) &&
|
||||
isVideoChannelDescriptionValid(videoChannel.description) &&
|
||||
isVideoChannelDescriptionValid(videoChannel.content) &&
|
||||
isDateValid(videoChannel.published) &&
|
||||
isDateValid(videoChannel.updated) &&
|
||||
isUUIDValid(videoChannel.uuid)
|
||||
}
|
||||
|
||||
@ -103,8 +105,8 @@ export {
|
||||
isVideoChannelDeleteActivityValid,
|
||||
isVideoTorrentDeleteActivityValid,
|
||||
isVideoFlagValid,
|
||||
isVideoAnnounceValid,
|
||||
isVideoChannelAnnounceValid
|
||||
isAnnounceValid,
|
||||
isVideoChannelObjectValid
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -148,8 +150,20 @@ function setValidRemoteVideoUrls (video: any) {
|
||||
|
||||
function isRemoteVideoUrlValid (url: any) {
|
||||
return url.type === 'Link' &&
|
||||
ACTIVITY_PUB.VIDEO_URL_MIME_TYPES.indexOf(url.mimeType) !== -1 &&
|
||||
isVideoUrlValid(url.url) &&
|
||||
validator.isInt(url.width + '', { min: 0 }) &&
|
||||
validator.isInt(url.size + '', { min: 0 })
|
||||
(
|
||||
ACTIVITY_PUB.URL_MIME_TYPES.VIDEO.indexOf(url.mimeType) !== -1 &&
|
||||
isVideoUrlValid(url.url) &&
|
||||
validator.isInt(url.width + '', { min: 0 }) &&
|
||||
validator.isInt(url.size + '', { min: 0 })
|
||||
) ||
|
||||
(
|
||||
ACTIVITY_PUB.URL_MIME_TYPES.TORRENT.indexOf(url.mimeType) !== -1 &&
|
||||
isVideoUrlValid(url.url) &&
|
||||
validator.isInt(url.width + '', { min: 0 })
|
||||
) ||
|
||||
(
|
||||
ACTIVITY_PUB.URL_MIME_TYPES.MAGNET.indexOf(url.mimeType) !== -1 &&
|
||||
validator.isLength(url.url, { min: 5 }) &&
|
||||
validator.isInt(url.width + '', { min: 0 })
|
||||
)
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
// TODO: import from ES6 when retry typing file will include errorFilter function
|
||||
import * as retry from 'async/retry'
|
||||
|
||||
import * as Bluebird from 'bluebird'
|
||||
import { logger } from './logger'
|
||||
|
||||
type RetryTransactionWrapperOptions = { errorMessage: string, arguments?: any[] }
|
||||
function retryTransactionWrapper (functionToRetry: (...args) => Promise<any>, options: RetryTransactionWrapperOptions) {
|
||||
function retryTransactionWrapper (functionToRetry: (...args) => Promise<any> | Bluebird<any>, options: RetryTransactionWrapperOptions) {
|
||||
const args = options.arguments ? options.arguments : []
|
||||
|
||||
return transactionRetryer(callback => {
|
||||
@ -13,8 +13,8 @@ function retryTransactionWrapper (functionToRetry: (...args) => Promise<any>, op
|
||||
.catch(err => callback(err))
|
||||
})
|
||||
.catch(err => {
|
||||
// Do not throw the error, continue the process
|
||||
logger.error(options.errorMessage, err)
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
@ -28,7 +28,7 @@ function transactionRetryer (func: Function) {
|
||||
logger.debug('Maybe retrying the transaction function.', { willRetry })
|
||||
return willRetry
|
||||
}
|
||||
}, func, err => err ? rej(err) : res())
|
||||
}, func, (err, data) => err ? rej(err) : res(data))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -227,13 +227,11 @@ const ACTIVITY_PUB_ACCEPT_HEADER = 'application/ld+json; profile="https://www.w3
|
||||
|
||||
const ACTIVITY_PUB = {
|
||||
COLLECTION_ITEMS_PER_PAGE: 10,
|
||||
VIDEO_URL_MIME_TYPES: [
|
||||
'video/mp4',
|
||||
'video/webm',
|
||||
'video/ogg',
|
||||
'application/x-bittorrent',
|
||||
'application/x-bittorrent;x-scheme-handler/magnet'
|
||||
]
|
||||
URL_MIME_TYPES: {
|
||||
VIDEO: [ 'video/mp4', 'video/webm', 'video/ogg' ], // TODO: Merge with VIDEO_MIMETYPE_EXT
|
||||
TORRENT: [ 'application/x-bittorrent' ],
|
||||
MAGNET: [ 'application/x-bittorrent;x-scheme-handler/magnet' ]
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
@ -7,6 +7,21 @@ import { VIDEO_MIMETYPE_EXT } from '../../initializers/constants'
|
||||
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 { VideoChannelObject } from '../../../shared/models/activitypub/objects/video-channel-object'
|
||||
import { AccountInstance } from '../../models/account/account-interface'
|
||||
|
||||
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,
|
||||
@ -45,26 +60,32 @@ async function videoActivityObjectToDBAttributes (
|
||||
}
|
||||
|
||||
function videoFileActivityUrlToDBAttributes (videoCreated: VideoInstance, videoObject: VideoTorrentObject) {
|
||||
const fileUrls = videoObject.url
|
||||
.filter(u => Object.keys(VIDEO_MIMETYPE_EXT).indexOf(u.mimeType) !== -1 && u.url.startsWith('video/'))
|
||||
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 url of fileUrls) {
|
||||
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 === url.width
|
||||
})
|
||||
if (!magnet) throw new Error('Cannot find associated magnet uri for file ' + url.url)
|
||||
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[url.mimeType],
|
||||
extname: VIDEO_MIMETYPE_EXT[fileUrl.mimeType],
|
||||
infoHash: parsed.infoHash,
|
||||
resolution: url.width,
|
||||
size: url.size,
|
||||
resolution: fileUrl.width,
|
||||
size: fileUrl.size,
|
||||
videoId: videoCreated.id
|
||||
}
|
||||
attributes.push(attribute)
|
||||
@ -77,5 +98,6 @@ function videoFileActivityUrlToDBAttributes (videoCreated: VideoInstance, videoO
|
||||
|
||||
export {
|
||||
videoFileActivityUrlToDBAttributes,
|
||||
videoActivityObjectToDBAttributes
|
||||
videoActivityObjectToDBAttributes,
|
||||
videoChannelActivityObjectToDBAttributes
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ import { database as db } from '../../initializers'
|
||||
import { AccountInstance } from '../../models/account/account-interface'
|
||||
import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
|
||||
import Bluebird = require('bluebird')
|
||||
import { getOrCreateVideoChannel } from '../../helpers/activitypub'
|
||||
import { VideoChannelInstance } from '../../models/video/video-channel-interface'
|
||||
|
||||
async function processAddActivity (activity: ActivityAdd) {
|
||||
const activityObject = activity.object
|
||||
@ -12,7 +14,10 @@ async function processAddActivity (activity: ActivityAdd) {
|
||||
const account = await getOrCreateAccount(activity.actor)
|
||||
|
||||
if (activityType === 'Video') {
|
||||
return processAddVideo(account, activity.id, activityObject as VideoTorrentObject)
|
||||
const videoChannelUrl = activity.target
|
||||
const videoChannel = await getOrCreateVideoChannel(account, videoChannelUrl)
|
||||
|
||||
return processAddVideo(account, videoChannel, activityObject as VideoTorrentObject)
|
||||
}
|
||||
|
||||
logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id })
|
||||
@ -27,16 +32,16 @@ export {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function processAddVideo (account: AccountInstance, videoChannelUrl: string, video: VideoTorrentObject) {
|
||||
function processAddVideo (account: AccountInstance, videoChannel: VideoChannelInstance, video: VideoTorrentObject) {
|
||||
const options = {
|
||||
arguments: [ account, videoChannelUrl, video ],
|
||||
arguments: [ account, videoChannel, video ],
|
||||
errorMessage: 'Cannot insert the remote video with many retries.'
|
||||
}
|
||||
|
||||
return retryTransactionWrapper(addRemoteVideo, options)
|
||||
}
|
||||
|
||||
async function addRemoteVideo (account: AccountInstance, videoChannelUrl: string, videoToCreateData: VideoTorrentObject) {
|
||||
function addRemoteVideo (account: AccountInstance, videoChannel: VideoChannelInstance, videoToCreateData: VideoTorrentObject) {
|
||||
logger.debug('Adding remote video %s.', videoToCreateData.url)
|
||||
|
||||
return db.sequelize.transaction(async t => {
|
||||
@ -44,9 +49,6 @@ async function addRemoteVideo (account: AccountInstance, videoChannelUrl: string
|
||||
transaction: t
|
||||
}
|
||||
|
||||
const videoChannel = await db.VideoChannel.loadByUrl(videoChannelUrl, t)
|
||||
if (!videoChannel) throw new Error('Video channel not found.')
|
||||
|
||||
if (videoChannel.Account.id !== account.id) throw new Error('Video channel is not owned by this account.')
|
||||
|
||||
const videoData = await videoActivityObjectToDBAttributes(videoChannel, videoToCreateData, t)
|
||||
@ -59,8 +61,11 @@ async function addRemoteVideo (account: AccountInstance, videoChannelUrl: string
|
||||
const videoCreated = await video.save(sequelizeOptions)
|
||||
|
||||
const videoFileAttributes = await 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))
|
||||
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)
|
||||
@ -71,5 +76,4 @@ async function addRemoteVideo (account: AccountInstance, videoChannelUrl: string
|
||||
|
||||
return videoCreated
|
||||
})
|
||||
|
||||
}
|
||||
|
@ -10,38 +10,33 @@ import { VideoChannelInstance } from '../../models/video/video-channel-interface
|
||||
import { VideoInstance } from '../../models/index'
|
||||
|
||||
async function processAnnounceActivity (activity: ActivityAnnounce) {
|
||||
const activityType = activity.object.type
|
||||
const announcedActivity = activity.object
|
||||
const accountAnnouncer = await getOrCreateAccount(activity.actor)
|
||||
|
||||
if (activityType === 'VideoChannel') {
|
||||
const activityCreate = Object.assign(activity, {
|
||||
type: 'Create' as 'Create',
|
||||
actor: activity.object.actor,
|
||||
object: activity.object as VideoChannelObject
|
||||
})
|
||||
|
||||
if (announcedActivity.type === 'Create' && announcedActivity.object.type === 'VideoChannel') {
|
||||
// Add share entry
|
||||
const videoChannel: VideoChannelInstance = await processCreateActivity(activityCreate)
|
||||
const videoChannel: VideoChannelInstance = await processCreateActivity(announcedActivity)
|
||||
await db.VideoChannelShare.create({
|
||||
accountId: accountAnnouncer.id,
|
||||
videoChannelId: videoChannel.id
|
||||
})
|
||||
} else if (activityType === 'Video') {
|
||||
const activityAdd = Object.assign(activity, {
|
||||
type: 'Add' as 'Add',
|
||||
actor: activity.object.actor,
|
||||
object: activity.object as VideoTorrentObject
|
||||
})
|
||||
|
||||
return undefined
|
||||
} else if (announcedActivity.type === 'Add' && announcedActivity.object.type === 'Video') {
|
||||
// Add share entry
|
||||
const video: VideoInstance = await processAddActivity(activityAdd)
|
||||
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 when announcing activity.', activityType, { activity: activity.id })
|
||||
logger.warn(
|
||||
'Unknown activity object type %s -> %s when announcing activity.', announcedActivity.type, announcedActivity.object.type,
|
||||
{ activity: activity.id }
|
||||
)
|
||||
return Promise.resolve(undefined)
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ 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
|
||||
@ -37,23 +38,14 @@ function processCreateVideoChannel (account: AccountInstance, videoChannelToCrea
|
||||
return retryTransactionWrapper(addRemoteVideoChannel, options)
|
||||
}
|
||||
|
||||
async function addRemoteVideoChannel (account: AccountInstance, videoChannelToCreateData: VideoChannelObject) {
|
||||
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 = {
|
||||
name: videoChannelToCreateData.name,
|
||||
description: videoChannelToCreateData.content,
|
||||
uuid: videoChannelToCreateData.uuid,
|
||||
createdAt: new Date(videoChannelToCreateData.published),
|
||||
updatedAt: new Date(videoChannelToCreateData.updated),
|
||||
remote: true,
|
||||
accountId: account.id
|
||||
}
|
||||
|
||||
const videoChannelData = videoChannelActivityObjectToDBAttributes(videoChannelToCreateData, account)
|
||||
videoChannel = db.VideoChannel.build(videoChannelData)
|
||||
videoChannel.url = getActivityPubUrl('videoChannel', videoChannel.uuid)
|
||||
|
||||
@ -73,7 +65,7 @@ function processCreateVideoAbuse (account: AccountInstance, videoAbuseToCreateDa
|
||||
return retryTransactionWrapper(addRemoteVideoAbuse, options)
|
||||
}
|
||||
|
||||
async function addRemoteVideoAbuse (account: AccountInstance, videoAbuseToCreateData: VideoAbuseObject) {
|
||||
function addRemoteVideoAbuse (account: AccountInstance, videoAbuseToCreateData: VideoAbuseObject) {
|
||||
logger.debug('Reporting remote abuse for video %s.', videoAbuseToCreateData.object)
|
||||
|
||||
return db.sequelize.transaction(async t => {
|
||||
|
@ -59,24 +59,21 @@ async function sendDeleteAccount (account: AccountInstance, t: Sequelize.Transac
|
||||
return broadcastToFollowers(data, [ account ], t)
|
||||
}
|
||||
|
||||
async function sendAnnounce (byAccount: AccountInstance, instance: VideoInstance | VideoChannelInstance, t: Sequelize.Transaction) {
|
||||
const object = instance.toActivityPubObject()
|
||||
async function sendVideoChannelAnnounce (byAccount: AccountInstance, videoChannel: VideoChannelInstance, t: Sequelize.Transaction) {
|
||||
const url = getActivityPubUrl('videoChannel', videoChannel.uuid) + '#announce'
|
||||
const announcedActivity = await createActivityData(url, videoChannel.Account, videoChannel.toActivityPubObject(), true)
|
||||
|
||||
let url = ''
|
||||
let objectActorUrl: string
|
||||
if ((instance as any).VideoChannel !== undefined) {
|
||||
objectActorUrl = (instance as VideoInstance).VideoChannel.Account.url
|
||||
url = getActivityPubUrl('video', instance.uuid) + '#announce'
|
||||
} else {
|
||||
objectActorUrl = (instance as VideoChannelInstance).Account.url
|
||||
url = getActivityPubUrl('videoChannel', instance.uuid) + '#announce'
|
||||
}
|
||||
const data = await announceActivityData(url, byAccount, announcedActivity)
|
||||
return broadcastToFollowers(data, [ byAccount ], t)
|
||||
}
|
||||
|
||||
const objectWithActor = Object.assign(object, {
|
||||
actor: objectActorUrl
|
||||
})
|
||||
async function sendVideoAnnounce (byAccount: AccountInstance, video: VideoInstance, t: Sequelize.Transaction) {
|
||||
const url = getActivityPubUrl('video', video.uuid) + '#announce'
|
||||
|
||||
const data = await announceActivityData(url, byAccount, objectWithActor)
|
||||
const videoChannel = video.VideoChannel
|
||||
const announcedActivity = await addActivityData(url, videoChannel.Account, videoChannel.url, video.toActivityPubObject(), true)
|
||||
|
||||
const data = await announceActivityData(url, byAccount, announcedActivity)
|
||||
return broadcastToFollowers(data, [ byAccount ], t)
|
||||
}
|
||||
|
||||
@ -117,7 +114,8 @@ export {
|
||||
sendAccept,
|
||||
sendFollow,
|
||||
sendVideoAbuse,
|
||||
sendAnnounce
|
||||
sendVideoChannelAnnounce,
|
||||
sendVideoAnnounce
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -159,7 +157,7 @@ async function getPublicActivityTo (account: AccountInstance) {
|
||||
return inboxUrls.concat('https://www.w3.org/ns/activitystreams#Public')
|
||||
}
|
||||
|
||||
async function createActivityData (url: string, byAccount: AccountInstance, object: any) {
|
||||
async function createActivityData (url: string, byAccount: AccountInstance, object: any, raw = false) {
|
||||
const to = await getPublicActivityTo(byAccount)
|
||||
const base = {
|
||||
type: 'Create',
|
||||
@ -169,6 +167,8 @@ async function createActivityData (url: string, byAccount: AccountInstance, obje
|
||||
object
|
||||
}
|
||||
|
||||
if (raw === true) return base
|
||||
|
||||
return buildSignedActivity(byAccount, base)
|
||||
}
|
||||
|
||||
@ -195,7 +195,7 @@ async function deleteActivityData (url: string, byAccount: AccountInstance) {
|
||||
return buildSignedActivity(byAccount, base)
|
||||
}
|
||||
|
||||
async function addActivityData (url: string, byAccount: AccountInstance, target: string, object: any) {
|
||||
async function addActivityData (url: string, byAccount: AccountInstance, target: string, object: any, raw = false) {
|
||||
const to = await getPublicActivityTo(byAccount)
|
||||
const base = {
|
||||
type: 'Add',
|
||||
@ -206,6 +206,8 @@ async function addActivityData (url: string, byAccount: AccountInstance, target:
|
||||
target
|
||||
}
|
||||
|
||||
if (raw === true) return base
|
||||
|
||||
return buildSignedActivity(byAccount, base)
|
||||
}
|
||||
|
||||
|
@ -83,7 +83,7 @@ const videoChannelsRemoveValidator = [
|
||||
}
|
||||
]
|
||||
|
||||
const videoChannelGetValidator = [
|
||||
const videoChannelsGetValidator = [
|
||||
param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
|
||||
|
||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
@ -102,7 +102,7 @@ export {
|
||||
videoChannelsAddValidator,
|
||||
videoChannelsUpdateValidator,
|
||||
videoChannelsRemoveValidator,
|
||||
videoChannelGetValidator
|
||||
videoChannelsGetValidator
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
@ -264,7 +264,8 @@ loadByUrl = function (url: string, t?: Sequelize.Transaction) {
|
||||
const query: Sequelize.FindOptions<VideoChannelAttributes> = {
|
||||
where: {
|
||||
url
|
||||
}
|
||||
},
|
||||
include: [ VideoChannel['sequelize'].models.Account ]
|
||||
}
|
||||
|
||||
if (t !== undefined) query.transaction = t
|
||||
|
@ -24,6 +24,7 @@ export interface ActivityCreate extends BaseActivity {
|
||||
|
||||
export interface ActivityAdd extends BaseActivity {
|
||||
type: 'Add'
|
||||
target: string
|
||||
object: VideoTorrentObject
|
||||
}
|
||||
|
||||
@ -52,5 +53,5 @@ export interface ActivityAccept extends BaseActivity {
|
||||
|
||||
export interface ActivityAnnounce extends BaseActivity {
|
||||
type: 'Announce'
|
||||
object: VideoChannelObject | VideoTorrentObject
|
||||
object: ActivityCreate | ActivityAdd
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user