From 20494f122186bb1bfd82f4c598c4744acea27b0c Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 16 Nov 2017 15:22:39 +0100 Subject: Server shares user videos --- server/lib/activitypub/misc.ts | 46 ++++++++++++++++++++++-------- server/lib/activitypub/process-add.ts | 22 ++++++++------ server/lib/activitypub/process-announce.ts | 29 ++++++++----------- server/lib/activitypub/process-create.ts | 16 +++-------- server/lib/activitypub/send-request.ts | 40 ++++++++++++++------------ 5 files changed, 84 insertions(+), 69 deletions(-) (limited to 'server/lib/activitypub') diff --git a/server/lib/activitypub/misc.ts b/server/lib/activitypub/misc.ts index 43d26c328..13838fc4c 100644 --- a/server/lib/activitypub/misc.ts +++ b/server/lib/activitypub/misc.ts @@ -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 } diff --git a/server/lib/activitypub/process-add.ts b/server/lib/activitypub/process-add.ts index 98e414dbb..df7139d46 100644 --- a/server/lib/activitypub/process-add.ts +++ b/server/lib/activitypub/process-add.ts @@ -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[] = videoFileAttributes.map(f => db.VideoFile.create(f)) + const tasks: Bluebird[] = 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 }) - } diff --git a/server/lib/activitypub/process-announce.ts b/server/lib/activitypub/process-announce.ts index d67958aec..f9674e095 100644 --- a/server/lib/activitypub/process-announce.ts +++ b/server/lib/activitypub/process-announce.ts @@ -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) } diff --git a/server/lib/activitypub/process-create.ts b/server/lib/activitypub/process-create.ts index 4e4c9f703..c4706a66b 100644 --- a/server/lib/activitypub/process-create.ts +++ b/server/lib/activitypub/process-create.ts @@ -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 => { diff --git a/server/lib/activitypub/send-request.ts b/server/lib/activitypub/send-request.ts index 664b9d826..f9b72f2a8 100644 --- a/server/lib/activitypub/send-request.ts +++ b/server/lib/activitypub/send-request.ts @@ -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) } -- cgit v1.2.3