]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Server shares user videos
authorChocobozzz <florian.bigard@gmail.com>
Thu, 16 Nov 2017 14:22:39 +0000 (15:22 +0100)
committerChocobozzz <florian.bigard@gmail.com>
Mon, 27 Nov 2017 18:40:52 +0000 (19:40 +0100)
16 files changed:
server/controllers/activitypub/client.ts
server/controllers/api/videos/channel.ts
server/helpers/activitypub.ts
server/helpers/custom-validators/activitypub/activity.ts
server/helpers/custom-validators/activitypub/misc.ts
server/helpers/custom-validators/activitypub/videos.ts
server/helpers/database-utils.ts
server/initializers/constants.ts
server/lib/activitypub/misc.ts
server/lib/activitypub/process-add.ts
server/lib/activitypub/process-announce.ts
server/lib/activitypub/process-create.ts
server/lib/activitypub/send-request.ts
server/middlewares/validators/video-channels.ts
server/models/video/video-channel.ts
shared/models/activitypub/activity.ts

index 76049f496055681630a08e5a5e8b645f41cee4b1..7b3921770dcb80a0e61bbb1a2b15a1dbe55e2083 100644 (file)
@@ -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())
+}
index 656bc31298ae1663b8722b82c69458ebc7f852c7..8f3df2550341bfdeb85fce870440cfa55e49af1c 100644 (file)
@@ -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)
 )
 
index b376b8ca20dada4c73d4b36c2293391350a3c084..c710117cd771da930f3a622da89bf4a01f0088b1 100644 (file)
@@ -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
 }
 
 // ---------------------------------------------------------------------------
index 08e5ae0aad6b8e0052187aeb4fabe25f95be3433..8084cf7b0a8fdff6b95ac564951d7339a83d1db2 100644 (file)
@@ -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)
 }
 
 // ---------------------------------------------------------------------------
index 665a63a73f35cadf49fbc63e6f8f54be0f7d092a..f09a764b6701c4e6f6bf57929d987f00886fda2b 100644 (file)
@@ -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) &&
index 89c49b0dfd36d222839e84ebe4483829520c82bd..8486297adfcfd2bd41a388c67720fc2b3ea3ef98 100644 (file)
@@ -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 })
+    )
 }
index d62462d353db26fdcbfc8ab3d3d4d85db6eb7860..169b80065693cfcbf44f7ab874bb775681fbdca5 100644 (file)
@@ -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))
   })
 }
 
index dca223370a57f06049f93011064819d09c26e188..eeda8347dd28b99e466f616c8e94c5297083b890 100644 (file)
@@ -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' ]
+  }
 }
 
 // ---------------------------------------------------------------------------
index 43d26c3282be6c996ce5b97f4683c3a2d59bd11c..13838fc4c53d1b4b82ab902563021ccf365503ea 100644 (file)
@@ -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
 }
index 98e414dbb9372bfa0710900c1c8a3d1a7160c933..df7139d466c8a80a82c4e8d8c6d0929ec87be9eb 100644 (file)
@@ -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
   })
-
 }
index d67958aec15907d7e585b5e8d487a4b58687717a..f9674e09508b444a2d679dee8387ccc1f721f77f 100644 (file)
@@ -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)
 }
 
index 4e4c9f7032925b951452bf508187f17cee2bc450..c4706a66b78ee4af872a339df0f45b763d22af19 100644 (file)
@@ -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 => {
index 664b9d8268db36c16f52d94c68e637cd8d8eaabb..f9b72f2a8d2dccc14ae12f8375a44a4c6f232f3f 100644 (file)
@@ -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()
-
-  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'
-  }
+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)
+
+  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)
 }
 
index f0ead24e3d5395676c21e0637c5861d45729b6b8..0326e05b916209a9c2b240247523762bac58938a 100644 (file)
@@ -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
 }
 
 // ---------------------------------------------------------------------------
index 1f4604f1dc5b038177a0d19a8f6c6a9a81c52663..f8414d4a870758cfd20fdef109aff6a8af942dc0 100644 (file)
@@ -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
index b858bf7594071d5503c822427baff741a7ec4c20..f8e982fbb79b0622d7e73cf8b3f9e7e8f0149dda 100644 (file)
@@ -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
 }