From d846501818c2d29e66e6fd141789cb04fd55a437 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 15 Nov 2017 17:56:21 +0100 Subject: [PATCH] Handle announces in inbox --- server/controllers/activitypub/inbox.ts | 4 +- .../custom-validators/activitypub/activity.ts | 8 ++- .../custom-validators/activitypub/videos.ts | 24 ++++++++- server/initializers/database.ts | 4 ++ server/lib/activitypub/index.ts | 5 ++ server/lib/activitypub/process-add.ts | 7 ++- server/lib/activitypub/process-announce.ts | 52 +++++++++++++++++++ server/lib/activitypub/process-create.ts | 9 ++-- server/models/video/index.ts | 2 + .../video/video-channel-share-interface.ts | 27 ++++++++++ server/models/video/video-channel-share.ts | 49 +++++++++++++++++ server/models/video/video-share-interface.ts | 25 +++++++++ server/models/video/video-share.ts | 49 +++++++++++++++++ server/models/video/video.ts | 11 ---- shared/models/activitypub/activity.ts | 9 +++- .../objects/video-channel-object.ts | 1 + .../objects/video-torrent-object.ts | 1 + 17 files changed, 264 insertions(+), 23 deletions(-) create mode 100644 server/lib/activitypub/process-announce.ts create mode 100644 server/models/video/video-channel-share-interface.ts create mode 100644 server/models/video/video-channel-share.ts create mode 100644 server/models/video/video-share-interface.ts create mode 100644 server/models/video/video-share.ts diff --git a/server/controllers/activitypub/inbox.ts b/server/controllers/activitypub/inbox.ts index e62125d85..5f47648df 100644 --- a/server/controllers/activitypub/inbox.ts +++ b/server/controllers/activitypub/inbox.ts @@ -5,6 +5,7 @@ import { isActivityValid } from '../../helpers/custom-validators/activitypub/act import { processCreateActivity, processFlagActivity, 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 { asyncMiddleware, checkSignature, localAccountValidator, signatureValidator } from '../../middlewares' @@ -18,7 +19,8 @@ const processActivity: { [ P in ActivityType ]: (activity: Activity, inboxAccoun Flag: processFlagActivity, Delete: processDeleteActivity, Follow: processFollowActivity, - Accept: processAcceptActivity + Accept: processAcceptActivity, + Announce: processAnnounceActivity } const inboxRouter = express.Router() diff --git a/server/helpers/custom-validators/activitypub/activity.ts b/server/helpers/custom-validators/activitypub/activity.ts index b5ba0f7af..08e5ae0aa 100644 --- a/server/helpers/custom-validators/activitypub/activity.ts +++ b/server/helpers/custom-validators/activitypub/activity.ts @@ -2,9 +2,12 @@ import * as validator from 'validator' import { isAccountAcceptActivityValid, isAccountDeleteActivityValid, isAccountFollowActivityValid } from './account' import { isActivityPubUrlValid } from './misc' import { + isVideoAnnounceValid, + isVideoChannelAnnounceValid, isVideoChannelCreateActivityValid, isVideoChannelDeleteActivityValid, isVideoChannelUpdateActivityValid, + isVideoFlagValid, isVideoTorrentAddActivityValid, isVideoTorrentDeleteActivityValid, isVideoTorrentUpdateActivityValid @@ -32,7 +35,10 @@ function isActivityValid (activity: any) { isVideoChannelDeleteActivityValid(activity) || isAccountDeleteActivityValid(activity) || isAccountFollowActivityValid(activity) || - isAccountAcceptActivityValid(activity) + isAccountAcceptActivityValid(activity) || + isVideoFlagValid(activity) || + isVideoAnnounceValid(activity) || + isVideoChannelAnnounceValid(activity) } // --------------------------------------------------------------------------- diff --git a/server/helpers/custom-validators/activitypub/videos.ts b/server/helpers/custom-validators/activitypub/videos.ts index a46757397..9ddacd601 100644 --- a/server/helpers/custom-validators/activitypub/videos.ts +++ b/server/helpers/custom-validators/activitypub/videos.ts @@ -3,6 +3,7 @@ import { ACTIVITY_PUB } from '../../../initializers' import { exists, isDateValid, isUUIDValid } from '../misc' import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../video-channels' import { + isVideoAbuseReasonValid, isVideoDurationValid, isVideoNameValid, isVideoNSFWValid, @@ -11,7 +12,7 @@ import { isVideoUrlValid, isVideoViewsValid } from '../videos' -import { isBaseActivityValid } from './misc' +import { isActivityPubUrlValid, isBaseActivityValid } from './misc' function isVideoTorrentAddActivityValid (activity: any) { return isBaseActivityValid(activity, 'Add') && @@ -54,6 +55,22 @@ function isVideoTorrentObjectValid (video: any) { setValidRemoteVideoUrls(video.url) } +function isVideoFlagValid (activity: any) { + return isBaseActivityValid(activity, 'Flag') && + isVideoAbuseReasonValid(activity.content) && + isActivityPubUrlValid(activity.object) +} + +function isVideoAnnounceValid (activity: any) { + return isBaseActivityValid(activity, 'Announce') && + isVideoTorrentObjectValid(activity.object) +} + +function isVideoChannelAnnounceValid (activity: any) { + return isBaseActivityValid(activity, 'Announce') && + isVideoChannelObjectValid(activity.object) +} + function isVideoChannelCreateActivityValid (activity: any) { return isBaseActivityValid(activity, 'Create') && isVideoChannelObjectValid(activity.object) @@ -83,7 +100,10 @@ export { isVideoTorrentUpdateActivityValid, isVideoChannelUpdateActivityValid, isVideoChannelDeleteActivityValid, - isVideoTorrentDeleteActivityValid + isVideoTorrentDeleteActivityValid, + isVideoFlagValid, + isVideoAnnounceValid, + isVideoChannelAnnounceValid } // --------------------------------------------------------------------------- diff --git a/server/initializers/database.ts b/server/initializers/database.ts index 0a716e4fb..5c757694e 100644 --- a/server/initializers/database.ts +++ b/server/initializers/database.ts @@ -24,6 +24,8 @@ import { OAuthClientModel } from './../models/oauth/oauth-client-interface' import { JobModel } from './../models/job/job-interface' import { AccountModel } from './../models/account/account-interface' import { ApplicationModel } from './../models/application/application-interface' +import { VideoChannelShareModel } from '../models/video/video-channel-share-interface' +import { VideoShareModel } from '../models/video/video-share-interface' const dbname = CONFIG.DATABASE.DBNAME const username = CONFIG.DATABASE.USERNAME @@ -45,6 +47,8 @@ const database: { User?: UserModel, VideoAbuse?: VideoAbuseModel, VideoChannel?: VideoChannelModel, + VideoChannelShare?: VideoChannelShareModel, + VideoShare?: VideoShareModel, VideoFile?: VideoFileModel, BlacklistedVideo?: BlacklistedVideoModel, VideoTag?: VideoTagModel, diff --git a/server/lib/activitypub/index.ts b/server/lib/activitypub/index.ts index f8d56528a..dca446fd4 100644 --- a/server/lib/activitypub/index.ts +++ b/server/lib/activitypub/index.ts @@ -1,4 +1,9 @@ +export * from './process-accept' +export * from './process-add' +export * from './process-announce' export * from './process-create' +export * from './process-delete' export * from './process-flag' +export * from './process-follow' export * from './process-update' export * from './send-request' diff --git a/server/lib/activitypub/process-add.ts b/server/lib/activitypub/process-add.ts index 06d23a2ea..98e414dbb 100644 --- a/server/lib/activitypub/process-add.ts +++ b/server/lib/activitypub/process-add.ts @@ -39,7 +39,7 @@ function processAddVideo (account: AccountInstance, videoChannelUrl: string, vid async function addRemoteVideo (account: AccountInstance, videoChannelUrl: string, videoToCreateData: VideoTorrentObject) { logger.debug('Adding remote video %s.', videoToCreateData.url) - await db.sequelize.transaction(async t => { + return db.sequelize.transaction(async t => { const sequelizeOptions = { transaction: t } @@ -66,7 +66,10 @@ async function addRemoteVideo (account: AccountInstance, videoChannelUrl: string 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 }) - logger.info('Remote video with uuid %s inserted.', videoToCreateData.uuid) } diff --git a/server/lib/activitypub/process-announce.ts b/server/lib/activitypub/process-announce.ts new file mode 100644 index 000000000..d67958aec --- /dev/null +++ b/server/lib/activitypub/process-announce.ts @@ -0,0 +1,52 @@ +import { ActivityAnnounce } from '../../../shared/models/activitypub/activity' +import { VideoChannelObject } from '../../../shared/models/activitypub/objects/video-channel-object' +import { VideoTorrentObject } from '../../../shared/models/activitypub/objects/video-torrent-object' +import { logger } from '../../helpers/logger' +import { processAddActivity } from './process-add' +import { processCreateActivity } from './process-create' +import { database as db } from '../../initializers/index' +import { getOrCreateAccount } from '../../helpers/activitypub' +import { VideoChannelInstance } from '../../models/video/video-channel-interface' +import { VideoInstance } from '../../models/index' + +async function processAnnounceActivity (activity: ActivityAnnounce) { + const activityType = activity.object.type + 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 + }) + + // Add share entry + const videoChannel: VideoChannelInstance = await processCreateActivity(activityCreate) + 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 + }) + + // Add share entry + const video: VideoInstance = await processAddActivity(activityAdd) + await db.VideoShare.create({ + accountId: accountAnnouncer.id, + videoId: video.id + }) + } + + logger.warn('Unknown activity object type %s when announcing activity.', activityType, { activity: activity.id }) + return Promise.resolve(undefined) +} + +// --------------------------------------------------------------------------- + +export { + processAnnounceActivity +} diff --git a/server/lib/activitypub/process-create.ts b/server/lib/activitypub/process-create.ts index 8d842b822..1b825ebbc 100644 --- a/server/lib/activitypub/process-create.ts +++ b/server/lib/activitypub/process-create.ts @@ -40,7 +40,7 @@ function processCreateVideoChannel (account: AccountInstance, videoChannelToCrea async function addRemoteVideoChannel (account: AccountInstance, videoChannelToCreateData: VideoChannelObject) { logger.debug('Adding remote video channel "%s".', videoChannelToCreateData.uuid) - await db.sequelize.transaction(async t => { + 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.') @@ -57,10 +57,11 @@ async function addRemoteVideoChannel (account: AccountInstance, videoChannelToCr videoChannel = db.VideoChannel.build(videoChannelData) videoChannel.url = getActivityPubUrl('videoChannel', videoChannel.uuid) - await videoChannel.save({ transaction: t }) - }) + videoChannel = await videoChannel.save({ transaction: t }) + logger.info('Remote video channel with uuid %s inserted.', videoChannelToCreateData.uuid) - logger.info('Remote video channel with uuid %s inserted.', videoChannelToCreateData.uuid) + return videoChannel + }) } function processCreateVideoAbuse (account: AccountInstance, videoAbuseToCreateData: VideoAbuseObject) { diff --git a/server/models/video/index.ts b/server/models/video/index.ts index 20d97931f..e17bbfab4 100644 --- a/server/models/video/index.ts +++ b/server/models/video/index.ts @@ -5,3 +5,5 @@ export * from './video-channel-interface' export * from './video-tag-interface' export * from './video-file-interface' export * from './video-interface' +export * from './video-share-interface' +export * from './video-channel-share-interface' diff --git a/server/models/video/video-channel-share-interface.ts b/server/models/video/video-channel-share-interface.ts new file mode 100644 index 000000000..9ac6d7b23 --- /dev/null +++ b/server/models/video/video-channel-share-interface.ts @@ -0,0 +1,27 @@ +import * as Sequelize from 'sequelize' +import { AccountInstance } from '../account/account-interface' +import { VideoChannelInstance } from './video-channel-interface' + +export namespace VideoChannelShareMethods { +} + +export interface VideoChannelShareClass { +} + +export interface VideoChannelShareAttributes { + accountId: number + videoChannelId: number +} + +export interface VideoChannelShareInstance + extends VideoChannelShareClass, VideoChannelShareAttributes, Sequelize.Instance { + id: number + createdAt: Date + updatedAt: Date + + Account?: AccountInstance + VideoChannel?: VideoChannelInstance +} + +export interface VideoChannelShareModel + extends VideoChannelShareClass, Sequelize.Model {} diff --git a/server/models/video/video-channel-share.ts b/server/models/video/video-channel-share.ts new file mode 100644 index 000000000..b6199279f --- /dev/null +++ b/server/models/video/video-channel-share.ts @@ -0,0 +1,49 @@ +import * as Sequelize from 'sequelize' + +import { addMethodsToModel } from '../utils' +import { VideoChannelShareAttributes, VideoChannelShareInstance } from './video-channel-share-interface' + +let VideoChannelShare: Sequelize.Model + +export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { + VideoChannelShare = sequelize.define('VideoChannelShare', + { }, + { + indexes: [ + { + fields: [ 'accountId' ] + }, + { + fields: [ 'videoChannelId' ] + } + ] + } + ) + + const classMethods = [ + associate + ] + addMethodsToModel(VideoChannelShare, classMethods) + + return VideoChannelShare +} + +// ------------------------------ METHODS ------------------------------ + +function associate (models) { + VideoChannelShare.belongsTo(models.Account, { + foreignKey: { + name: 'accountId', + allowNull: false + }, + onDelete: 'cascade' + }) + + VideoChannelShare.belongsTo(models.VideoChannel, { + foreignKey: { + name: 'videoChannelId', + allowNull: true + }, + onDelete: 'cascade' + }) +} diff --git a/server/models/video/video-share-interface.ts b/server/models/video/video-share-interface.ts new file mode 100644 index 000000000..7928b9a9c --- /dev/null +++ b/server/models/video/video-share-interface.ts @@ -0,0 +1,25 @@ +import * as Sequelize from 'sequelize' +import { AccountInstance } from '../account/account-interface' +import { VideoInstance } from './video-interface' + +export namespace VideoShareMethods { +} + +export interface VideoShareClass { +} + +export interface VideoShareAttributes { + accountId: number + videoId: number +} + +export interface VideoShareInstance extends VideoShareClass, VideoShareAttributes, Sequelize.Instance { + id: number + createdAt: Date + updatedAt: Date + + Account?: AccountInstance + Video?: VideoInstance +} + +export interface VideoShareModel extends VideoShareClass, Sequelize.Model {} diff --git a/server/models/video/video-share.ts b/server/models/video/video-share.ts new file mode 100644 index 000000000..358491fd2 --- /dev/null +++ b/server/models/video/video-share.ts @@ -0,0 +1,49 @@ +import * as Sequelize from 'sequelize' + +import { addMethodsToModel } from '../utils' +import { VideoShareAttributes, VideoShareInstance } from './video-share-interface' + +let VideoShare: Sequelize.Model + +export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { + VideoShare = sequelize.define('VideoShare', + { }, + { + indexes: [ + { + fields: [ 'accountId' ] + }, + { + fields: [ 'videoId' ] + } + ] + } + ) + + const classMethods = [ + associate + ] + addMethodsToModel(VideoShare, classMethods) + + return VideoShare +} + +// ------------------------------ METHODS ------------------------------ + +function associate (models) { + VideoShare.belongsTo(models.Account, { + foreignKey: { + name: 'accountId', + allowNull: false + }, + onDelete: 'cascade' + }) + + VideoShare.belongsTo(models.Video, { + foreignKey: { + name: 'videoId', + allowNull: true + }, + onDelete: 'cascade' + }) +} diff --git a/server/models/video/video.ts b/server/models/video/video.ts index b00081f25..480e54276 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -253,9 +253,6 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da }, { fields: [ 'channelId' ] - }, - { - fields: [ 'parentId' ] } ], hooks: { @@ -329,14 +326,6 @@ function associate (models) { onDelete: 'cascade' }) - Video.belongsTo(models.Video, { - foreignKey: { - name: 'parentId', - allowNull: true - }, - onDelete: 'cascade' - }) - Video.belongsToMany(models.Tag, { foreignKey: 'videoId', through: models.VideoTag, diff --git a/shared/models/activitypub/activity.ts b/shared/models/activitypub/activity.ts index 506e64eff..b858bf759 100644 --- a/shared/models/activitypub/activity.ts +++ b/shared/models/activitypub/activity.ts @@ -3,10 +3,10 @@ import { ActivityPubSignature } from './activitypub-signature' import { VideoAbuseObject } from './objects/video-abuse-object' export type Activity = ActivityCreate | ActivityAdd | ActivityUpdate | ActivityFlag | - ActivityDelete | ActivityFollow | ActivityAccept + ActivityDelete | ActivityFollow | ActivityAccept | ActivityAnnounce // Flag -> report abuse -export type ActivityType = 'Create' | 'Add' | 'Update' | 'Flag' | 'Delete' | 'Follow' | 'Accept' +export type ActivityType = 'Create' | 'Add' | 'Update' | 'Flag' | 'Delete' | 'Follow' | 'Accept' | 'Announce' export interface BaseActivity { '@context'?: any[] @@ -49,3 +49,8 @@ export interface ActivityFollow extends BaseActivity { export interface ActivityAccept extends BaseActivity { type: 'Accept' } + +export interface ActivityAnnounce extends BaseActivity { + type: 'Announce' + object: VideoChannelObject | VideoTorrentObject +} diff --git a/shared/models/activitypub/objects/video-channel-object.ts b/shared/models/activitypub/objects/video-channel-object.ts index de504d84c..468e1535e 100644 --- a/shared/models/activitypub/objects/video-channel-object.ts +++ b/shared/models/activitypub/objects/video-channel-object.ts @@ -6,4 +6,5 @@ export interface VideoChannelObject { uuid: string published: Date updated: Date + actor?: string } diff --git a/shared/models/activitypub/objects/video-torrent-object.ts b/shared/models/activitypub/objects/video-torrent-object.ts index 5685a43e0..99e7157b8 100644 --- a/shared/models/activitypub/objects/video-torrent-object.ts +++ b/shared/models/activitypub/objects/video-torrent-object.ts @@ -23,4 +23,5 @@ export interface VideoTorrentObject { content: string icon: ActivityIconObject url: ActivityUrlObject[] + actor?: string } -- 2.41.0