From efc32059d980c51793e8e9ac0fb6a885a8026f94 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 16 Nov 2017 11:08:25 +0100 Subject: [PATCH] Send server announce when users upload a video --- client/src/app/core/auth/auth.service.ts | 1 + server/controllers/activitypub/client.ts | 4 +- server/controllers/api/server/follows.ts | 8 +-- server/controllers/api/videos/index.ts | 11 ++-- server/helpers/activitypub.ts | 30 +++++++++- server/helpers/core-utils.ts | 9 +-- .../custom-validators/activitypub/videos.ts | 15 ++--- server/helpers/peertube-crypto.ts | 10 ++-- server/helpers/utils.ts | 18 ++++-- server/initializers/constants.ts | 6 +- server/initializers/installer.ts | 2 +- server/lib/activitypub/misc.ts | 6 +- server/lib/activitypub/process-create.ts | 4 +- server/lib/activitypub/send-request.ts | 56 +++++++++++++++---- .../video-file-optimizer-handler.ts | 2 + server/lib/video-channel.ts | 5 +- .../account/account-follow-interface.ts | 6 +- server/models/account/account-follow.ts | 30 +++++++--- server/models/video/video-channel.ts | 4 +- server/models/video/video.ts | 6 +- .../objects/video-channel-object.ts | 4 +- .../objects/video-torrent-object.ts | 4 +- 22 files changed, 161 insertions(+), 80 deletions(-) diff --git a/client/src/app/core/auth/auth.service.ts b/client/src/app/core/auth/auth.service.ts index 0aa276c69..9e6c6b888 100644 --- a/client/src/app/core/auth/auth.service.ts +++ b/client/src/app/core/auth/auth.service.ts @@ -257,6 +257,7 @@ export class AuthService { this.user.save() this.setStatus(AuthStatus.LoggedIn) + this.userInformationLoaded.next(true) } private handleRefreshToken (obj: UserRefreshToken) { diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts index 49dd24e79..76049f496 100644 --- a/server/controllers/activitypub/client.ts +++ b/server/controllers/activitypub/client.ts @@ -46,7 +46,7 @@ async function accountFollowersController (req: express.Request, res: express.Re const page = req.params.page || 1 const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE) - const result = await db.AccountFollow.listAcceptedFollowerUrlsForApi(account.id, start, count) + const result = await db.AccountFollow.listAcceptedFollowerUrlsForApi([ account.id ], start, count) const activityPubResult = activityPubCollectionPagination(req.url, page, result) return res.json(activityPubResult) @@ -58,7 +58,7 @@ async function accountFollowingController (req: express.Request, res: express.Re const page = req.params.page || 1 const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE) - const result = await db.AccountFollow.listAcceptedFollowingUrlsForApi(account.id, start, count) + const result = await db.AccountFollow.listAcceptedFollowingUrlsForApi([ account.id ], start, count) const activityPubResult = activityPubCollectionPagination(req.url, page, result) return res.json(activityPubResult) diff --git a/server/controllers/api/server/follows.ts b/server/controllers/api/server/follows.ts index ac8ea87f9..e00787f02 100644 --- a/server/controllers/api/server/follows.ts +++ b/server/controllers/api/server/follows.ts @@ -2,7 +2,7 @@ import * as express from 'express' import { UserRight } from '../../../../shared/models/users/user-right.enum' import { getFormattedObjects } from '../../../helpers' import { logger } from '../../../helpers/logger' -import { getApplicationAccount } from '../../../helpers/utils' +import { getServerAccount } from '../../../helpers/utils' import { getAccountFromWebfinger } from '../../../helpers/webfinger' import { SERVER_ACCOUNT_NAME } from '../../../initializers/constants' import { database as db } from '../../../initializers/database' @@ -50,14 +50,14 @@ export { // --------------------------------------------------------------------------- async function listFollowing (req: express.Request, res: express.Response, next: express.NextFunction) { - const applicationAccount = await getApplicationAccount() + const applicationAccount = await getServerAccount() const resultList = await db.AccountFollow.listFollowingForApi(applicationAccount.id, req.query.start, req.query.count, req.query.sort) return res.json(getFormattedObjects(resultList.data, resultList.total)) } async function listFollowers (req: express.Request, res: express.Response, next: express.NextFunction) { - const applicationAccount = await getApplicationAccount() + const applicationAccount = await getServerAccount() const resultList = await db.AccountFollow.listFollowersForApi(applicationAccount.id, req.query.start, req.query.count, req.query.sort) return res.json(getFormattedObjects(resultList.data, resultList.total)) @@ -65,7 +65,7 @@ async function listFollowers (req: express.Request, res: express.Response, next: async function follow (req: express.Request, res: express.Response, next: express.NextFunction) { const hosts = req.body.hosts as string[] - const fromAccount = await getApplicationAccount() + const fromAccount = await getServerAccount() const tasks: Promise[] = [] const accountName = SERVER_ACCOUNT_NAME diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index ebc07e179..a5414cc50 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts @@ -12,10 +12,10 @@ import { resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers' -import { getActivityPubUrl } from '../../../helpers/activitypub' +import { getActivityPubUrl, shareVideoByServer } from '../../../helpers/activitypub' import { CONFIG, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_MIMETYPE_EXT, VIDEO_PRIVACIES } from '../../../initializers' import { database as db } from '../../../initializers/database' -import { sendAddVideo, sendUpdateVideoChannel } from '../../../lib/activitypub/send-request' +import { sendAddVideo, sendUpdateVideo } from '../../../lib/activitypub/send-request' import { transcodingJobScheduler } from '../../../lib/jobs/transcoding-job-scheduler/transcoding-job-scheduler' import { asyncMiddleware, @@ -56,7 +56,7 @@ const storage = multer.diskStorage({ randomString = 'fake-random-string' } - cb(null, randomString + '.' + extension) + cb(null, randomString + extension) } }) @@ -237,6 +237,7 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi if (video.privacy === VideoPrivacy.PRIVATE) return undefined await sendAddVideo(video, t) + await shareVideoByServer(video, t) }) logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoUUID) @@ -254,7 +255,7 @@ async function updateVideoRetryWrapper (req: express.Request, res: express.Respo } async function updateVideo (req: express.Request, res: express.Response) { - const videoInstance = res.locals.video + const videoInstance: VideoInstance = res.locals.video const videoFieldsSave = videoInstance.toJSON() const videoInfoToUpdate: VideoUpdate = req.body const wasPrivateVideo = videoInstance.privacy === VideoPrivacy.PRIVATE @@ -284,7 +285,7 @@ async function updateVideo (req: express.Request, res: express.Response) { // Now we'll update the video's meta data to our friends if (wasPrivateVideo === false) { - await sendUpdateVideoChannel(videoInstance, t) + await sendUpdateVideo(videoInstance, t) } // Video is not private anymore, send a create action to remote servers diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts index de20ba55d..b376b8ca2 100644 --- a/server/helpers/activitypub.ts +++ b/server/helpers/activitypub.ts @@ -1,15 +1,19 @@ import { join } from 'path' import * as request from 'request' +import * as Sequelize from 'sequelize' import * as url from 'url' import { ActivityIconObject } from '../../shared/index' import { ActivityPubActor } from '../../shared/models/activitypub/activitypub-actor' 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 { VideoChannelInstance } from '../models/video/video-channel-interface' import { VideoInstance } from '../models/video/video-interface' import { isRemoteAccountValid } from './custom-validators' import { logger } from './logger' import { doRequest, doRequestAndSaveToFile } from './requests' +import { getServerAccount } from './utils' function generateThumbnailFromUrl (video: VideoInstance, icon: ActivityIconObject) { const thumbnailName = video.getThumbnailName() @@ -22,6 +26,28 @@ function generateThumbnailFromUrl (video: VideoInstance, icon: ActivityIconObjec return doRequestAndSaveToFile(options, thumbnailPath) } +async function shareVideoChannelByServer (videoChannel: VideoChannelInstance, t: Sequelize.Transaction) { + const serverAccount = await getServerAccount() + + await db.VideoChannelShare.create({ + accountId: serverAccount.id, + videoChannelId: videoChannel.id + }, { transaction: t }) + + return sendAnnounce(serverAccount, videoChannel, t) +} + +async function shareVideoByServer (video: VideoInstance, t: Sequelize.Transaction) { + const serverAccount = await getServerAccount() + + await db.VideoShare.create({ + accountId: serverAccount.id, + videoId: video.id + }, { transaction: t }) + + return sendAnnounce(serverAccount, video, t) +} + function getActivityPubUrl (type: 'video' | 'videoChannel' | 'account' | 'videoAbuse', id: string) { if (type === 'video') return CONFIG.WEBSERVER.URL + '/videos/watch/' + id else if (type === 'videoChannel') return CONFIG.WEBSERVER.URL + '/video-channels/' + id @@ -172,7 +198,9 @@ export { generateThumbnailFromUrl, getOrCreateAccount, fetchRemoteVideoPreview, - fetchRemoteVideoDescription + fetchRemoteVideoDescription, + shareVideoChannelByServer, + shareVideoByServer } // --------------------------------------------------------------------------- diff --git a/server/helpers/core-utils.ts b/server/helpers/core-utils.ts index d8748e1d7..4ff07848c 100644 --- a/server/helpers/core-utils.ts +++ b/server/helpers/core-utils.ts @@ -20,9 +20,6 @@ import * as bcrypt from 'bcrypt' import * as createTorrent from 'create-torrent' import * as rimraf from 'rimraf' import * as pem from 'pem' -import * as jsonld from 'jsonld' -import * as jsig from 'jsonld-signatures' -jsig.use('jsonld', jsonld) function isTestInstance () { return process.env.NODE_ENV === 'test' @@ -120,8 +117,6 @@ const bcryptHashPromise = promisify2(bcrypt.hash) const createTorrentPromise = promisify2(createTorrent) const rimrafPromise = promisify1WithVoid(rimraf) const statPromise = promisify1(stat) -const jsonldSignPromise = promisify2(jsig.sign) -const jsonldVerifyPromise = promisify2(jsig.verify) // --------------------------------------------------------------------------- @@ -150,7 +145,5 @@ export { bcryptHashPromise, createTorrentPromise, rimrafPromise, - statPromise, - jsonldSignPromise, - jsonldVerifyPromise + statPromise } diff --git a/server/helpers/custom-validators/activitypub/videos.ts b/server/helpers/custom-validators/activitypub/videos.ts index 9ddacd601..89c49b0df 100644 --- a/server/helpers/custom-validators/activitypub/videos.ts +++ b/server/helpers/custom-validators/activitypub/videos.ts @@ -34,7 +34,7 @@ function isActivityPubVideoDurationValid (value: string) { typeof value === 'string' && value.startsWith('PT') && value.endsWith('S') && - isVideoDurationValid(value.replace(/[^0-9]+/, '')) + isVideoDurationValid(value.replace(/[^0-9]+/g, '')) } function isVideoTorrentObjectValid (video: any) { @@ -46,13 +46,14 @@ function isVideoTorrentObjectValid (video: any) { isRemoteIdentifierValid(video.category) && isRemoteIdentifierValid(video.licence) && isRemoteIdentifierValid(video.language) && - isVideoViewsValid(video.video) && + isVideoViewsValid(video.views) && isVideoNSFWValid(video.nsfw) && isDateValid(video.published) && isDateValid(video.updated) && isRemoteVideoContentValid(video.mediaType, video.content) && isRemoteVideoIconValid(video.icon) && - setValidRemoteVideoUrls(video.url) + setValidRemoteVideoUrls(video) && + video.url.length !== 0 } function isVideoFlagValid (activity: any) { @@ -132,8 +133,8 @@ function isRemoteVideoIconValid (icon: any) { return icon.type === 'Image' && isVideoUrlValid(icon.url) && icon.mediaType === 'image/jpeg' && - validator.isInt(icon.width, { min: 0 }) && - validator.isInt(icon.height, { min: 0 }) + validator.isInt(icon.width + '', { min: 0 }) && + validator.isInt(icon.height + '', { min: 0 }) } function setValidRemoteVideoUrls (video: any) { @@ -149,6 +150,6 @@ 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 }) + validator.isInt(url.width + '', { min: 0 }) && + validator.isInt(url.size + '', { min: 0 }) } diff --git a/server/helpers/peertube-crypto.ts b/server/helpers/peertube-crypto.ts index 6d50e446f..04a8d5681 100644 --- a/server/helpers/peertube-crypto.ts +++ b/server/helpers/peertube-crypto.ts @@ -1,4 +1,6 @@ +import * as jsonld from 'jsonld' import * as jsig from 'jsonld-signatures' +jsig.use('jsonld', jsonld) import { PRIVATE_RSA_KEY_SIZE, @@ -9,9 +11,7 @@ import { bcryptGenSaltPromise, bcryptHashPromise, createPrivateKey, - getPublicKey, - jsonldSignPromise, - jsonldVerifyPromise + getPublicKey } from './core-utils' import { logger } from './logger' import { AccountInstance } from '../models/account/account-interface' @@ -45,7 +45,7 @@ function isSignatureVerified (fromAccount: AccountInstance, signedDocument: obje publicKeyOwner: publicKeyOwnerObject } - return jsonldVerifyPromise(signedDocument, options) + return jsig.promises.verify(signedDocument, options) .catch(err => { logger.error('Cannot check signature.', err) return false @@ -58,7 +58,7 @@ function signObject (byAccount: AccountInstance, data: any) { creator: byAccount.url } - return jsonldSignPromise(data, options) + return jsig.promises.sign(data, options) } function comparePassword (plainPassword: string, hashPassword: string) { diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts index 39957c90f..3af14a68a 100644 --- a/server/helpers/utils.ts +++ b/server/helpers/utils.ts @@ -6,6 +6,7 @@ import { CONFIG, database as db } from '../initializers' import { ResultList } from '../../shared' import { VideoResolution } from '../../shared/models/videos/video-resolution.enum' import { AccountInstance } from '../models/account/account-interface' +import { logger } from './logger' function badRequest (req: express.Request, res: express.Response, next: express.NextFunction) { return res.type('json').status(400).end() @@ -79,13 +80,18 @@ function resetSequelizeInstance (instance: Sequelize.Instance, savedFields: }) } -let applicationAccount: AccountInstance -async function getApplicationAccount () { - if (applicationAccount === undefined) { - applicationAccount = await db.Account.loadApplication() +let serverAccount: AccountInstance +async function getServerAccount () { + if (serverAccount === undefined) { + serverAccount = await db.Account.loadApplication() } - return Promise.resolve(applicationAccount) + if (!serverAccount) { + logger.error('Cannot load server account.') + process.exit(0) + } + + return Promise.resolve(serverAccount) } type SortType = { sortModel: any, sortValue: string } @@ -99,6 +105,6 @@ export { isSignupAllowed, computeResolutionsToTranscode, resetSequelizeInstance, - getApplicationAccount, + getServerAccount, SortType } diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 99da95ab9..dca223370 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts @@ -209,9 +209,9 @@ const VIDEO_PRIVACIES = { } const VIDEO_MIMETYPE_EXT = { - 'video/webm': 'webm', - 'video/ogg': 'ogv', - 'video/mp4': 'mp4' + 'video/webm': '.webm', + 'video/ogg': '.ogv', + 'video/mp4': '.mp4' } // --------------------------------------------------------------------------- diff --git a/server/initializers/installer.ts b/server/initializers/installer.ts index c3521a9e4..3f4c4dfbb 100644 --- a/server/initializers/installer.ts +++ b/server/initializers/installer.ts @@ -13,9 +13,9 @@ async function installApplication () { await db.sequelize.sync() await removeCacheDirectories() await createDirectoriesIfNotExist() + await createApplicationIfNotExist() await createOAuthClientIfNotExist() await createOAuthAdminIfNotExist() - await createApplicationIfNotExist() } catch (err) { logger.error('Cannot install application.', err) throw err diff --git a/server/lib/activitypub/misc.ts b/server/lib/activitypub/misc.ts index 2cf0c4fd1..43d26c328 100644 --- a/server/lib/activitypub/misc.ts +++ b/server/lib/activitypub/misc.ts @@ -28,9 +28,9 @@ async function videoActivityObjectToDBAttributes ( description: videoObject.content, channelId: videoChannel.id, duration: parseInt(duration, 10), - createdAt: videoObject.published, + createdAt: new Date(videoObject.published), // FIXME: updatedAt does not seems to be considered by Sequelize - updatedAt: videoObject.updated, + updatedAt: new Date(videoObject.updated), views: videoObject.views, likes: 0, dislikes: 0, @@ -46,7 +46,7 @@ async function videoActivityObjectToDBAttributes ( function videoFileActivityUrlToDBAttributes (videoCreated: VideoInstance, videoObject: VideoTorrentObject) { const fileUrls = videoObject.url - .filter(u => Object.keys(VIDEO_MIMETYPE_EXT).indexOf(u.mimeType) !== -1) + .filter(u => Object.keys(VIDEO_MIMETYPE_EXT).indexOf(u.mimeType) !== -1 && u.url.startsWith('video/')) const attributes: VideoFileAttributes[] = [] for (const url of fileUrls) { diff --git a/server/lib/activitypub/process-create.ts b/server/lib/activitypub/process-create.ts index 1b825ebbc..4e4c9f703 100644 --- a/server/lib/activitypub/process-create.ts +++ b/server/lib/activitypub/process-create.ts @@ -48,8 +48,8 @@ async function addRemoteVideoChannel (account: AccountInstance, videoChannelToCr name: videoChannelToCreateData.name, description: videoChannelToCreateData.content, uuid: videoChannelToCreateData.uuid, - createdAt: videoChannelToCreateData.published, - updatedAt: videoChannelToCreateData.updated, + createdAt: new Date(videoChannelToCreateData.published), + updatedAt: new Date(videoChannelToCreateData.updated), remote: true, accountId: account.id } diff --git a/server/lib/activitypub/send-request.ts b/server/lib/activitypub/send-request.ts index 1dad51828..664b9d826 100644 --- a/server/lib/activitypub/send-request.ts +++ b/server/lib/activitypub/send-request.ts @@ -17,46 +17,67 @@ async function sendCreateVideoChannel (videoChannel: VideoChannelInstance, t: Se const videoChannelObject = videoChannel.toActivityPubObject() const data = await createActivityData(videoChannel.url, videoChannel.Account, videoChannelObject) - return broadcastToFollowers(data, videoChannel.Account, t) + return broadcastToFollowers(data, [ videoChannel.Account ], t) } async function sendUpdateVideoChannel (videoChannel: VideoChannelInstance, t: Sequelize.Transaction) { const videoChannelObject = videoChannel.toActivityPubObject() const data = await updateActivityData(videoChannel.url, videoChannel.Account, videoChannelObject) - return broadcastToFollowers(data, videoChannel.Account, t) + return broadcastToFollowers(data, [ videoChannel.Account ], t) } async function sendDeleteVideoChannel (videoChannel: VideoChannelInstance, t: Sequelize.Transaction) { const data = await deleteActivityData(videoChannel.url, videoChannel.Account) - return broadcastToFollowers(data, videoChannel.Account, t) + return broadcastToFollowers(data, [ videoChannel.Account ], t) } async function sendAddVideo (video: VideoInstance, t: Sequelize.Transaction) { const videoObject = video.toActivityPubObject() const data = await addActivityData(video.url, video.VideoChannel.Account, video.VideoChannel.url, videoObject) - return broadcastToFollowers(data, video.VideoChannel.Account, t) + return broadcastToFollowers(data, [ video.VideoChannel.Account ], t) } async function sendUpdateVideo (video: VideoInstance, t: Sequelize.Transaction) { const videoObject = video.toActivityPubObject() const data = await updateActivityData(video.url, video.VideoChannel.Account, videoObject) - return broadcastToFollowers(data, video.VideoChannel.Account, t) + return broadcastToFollowers(data, [ video.VideoChannel.Account ], t) } async function sendDeleteVideo (video: VideoInstance, t: Sequelize.Transaction) { const data = await deleteActivityData(video.url, video.VideoChannel.Account) - return broadcastToFollowers(data, video.VideoChannel.Account, t) + return broadcastToFollowers(data, [ video.VideoChannel.Account ], t) } async function sendDeleteAccount (account: AccountInstance, t: Sequelize.Transaction) { const data = await deleteActivityData(account.url, account) - return broadcastToFollowers(data, account, t) + 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' + } + + const objectWithActor = Object.assign(object, { + actor: objectActorUrl + }) + + const data = await announceActivityData(url, byAccount, objectWithActor) + return broadcastToFollowers(data, [ byAccount ], t) } async function sendVideoAbuse ( @@ -95,15 +116,17 @@ export { sendDeleteAccount, sendAccept, sendFollow, - sendVideoAbuse + sendVideoAbuse, + sendAnnounce } // --------------------------------------------------------------------------- -async function broadcastToFollowers (data: any, fromAccount: AccountInstance, t: Sequelize.Transaction) { - const result = await db.AccountFollow.listAcceptedFollowerUrlsForApi(fromAccount.id) +async function broadcastToFollowers (data: any, toAccountFollowers: AccountInstance[], t: Sequelize.Transaction) { + const toAccountFollowerIds = toAccountFollowers.map(a => a.id) + const result = await db.AccountFollow.listAcceptedFollowerSharedInboxUrls(toAccountFollowerIds) if (result.data.length === 0) { - logger.info('Not broadcast because of 0 followers.') + logger.info('Not broadcast because of 0 followers for %s.', toAccountFollowerIds.join(', ')) return } @@ -186,6 +209,17 @@ async function addActivityData (url: string, byAccount: AccountInstance, target: return buildSignedActivity(byAccount, base) } +async function announceActivityData (url: string, byAccount: AccountInstance, object: any) { + const base = { + type: 'Announce', + id: url, + actor: byAccount.url, + object + } + + return buildSignedActivity(byAccount, base) +} + async function followActivityData (url: string, byAccount: AccountInstance) { const base = { type: 'Follow', diff --git a/server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts b/server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts index f6d9627a5..6443899d3 100644 --- a/server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts +++ b/server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts @@ -6,6 +6,7 @@ import { VideoInstance } from '../../../models' import { sendAddVideo } from '../../activitypub/send-request' import { JobScheduler } from '../job-scheduler' import { TranscodingJobPayload } from './transcoding-job-scheduler' +import { shareVideoByServer } from '../../../helpers/activitypub' async function process (data: TranscodingJobPayload, jobId: number) { const video = await db.Video.loadByUUIDAndPopulateAccountAndServerAndTags(data.videoUUID) @@ -37,6 +38,7 @@ async function onSuccess (jobId: number, video: VideoInstance, jobScheduler: Job // Now we'll add the video's meta data to our followers await sendAddVideo(video, undefined) + await shareVideoByServer(video, undefined) const originalFileHeight = await videoDatabase.getOriginalFileHeight() diff --git a/server/lib/video-channel.ts b/server/lib/video-channel.ts index 80303fb83..e69ec062f 100644 --- a/server/lib/video-channel.ts +++ b/server/lib/video-channel.ts @@ -5,7 +5,7 @@ import { logger } from '../helpers' import { AccountInstance } from '../models' import { VideoChannelCreate } from '../../shared/models' import { sendCreateVideoChannel } from './activitypub/send-request' -import { getActivityPubUrl } from '../helpers/activitypub' +import { getActivityPubUrl, shareVideoChannelByServer } from '../helpers/activitypub' async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account: AccountInstance, t: Sequelize.Transaction) { const videoChannelData = { @@ -25,7 +25,8 @@ async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account // Do not forget to add Account information to the created video channel videoChannelCreated.Account = account - sendCreateVideoChannel(videoChannelCreated, t) + await sendCreateVideoChannel(videoChannelCreated, t) + await shareVideoChannelByServer(videoChannelCreated, t) return videoChannelCreated } diff --git a/server/models/account/account-follow-interface.ts b/server/models/account/account-follow-interface.ts index 54baf45ed..21fda98ce 100644 --- a/server/models/account/account-follow-interface.ts +++ b/server/models/account/account-follow-interface.ts @@ -10,8 +10,9 @@ export namespace AccountFollowMethods { export type ListFollowingForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList > export type ListFollowersForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList > - export type ListAcceptedFollowerUrlsForApi = (id: number, start?: number, count?: number) => Promise< ResultList > - export type ListAcceptedFollowingUrlsForApi = (id: number, start?: number, count?: number) => Promise< ResultList > + export type ListAcceptedFollowerUrlsForApi = (accountId: number[], start?: number, count?: number) => Promise< ResultList > + export type ListAcceptedFollowingUrlsForApi = (accountId: number[], start?: number, count?: number) => Promise< ResultList > + export type ListAcceptedFollowerSharedInboxUrls = (accountId: number[]) => Promise< ResultList > } export interface AccountFollowClass { @@ -21,6 +22,7 @@ export interface AccountFollowClass { listAcceptedFollowerUrlsForApi: AccountFollowMethods.ListAcceptedFollowerUrlsForApi listAcceptedFollowingUrlsForApi: AccountFollowMethods.ListAcceptedFollowingUrlsForApi + listAcceptedFollowerSharedInboxUrls: AccountFollowMethods.ListAcceptedFollowerSharedInboxUrls } export interface AccountFollowAttributes { diff --git a/server/models/account/account-follow.ts b/server/models/account/account-follow.ts index f457e43e9..8a7474c9d 100644 --- a/server/models/account/account-follow.ts +++ b/server/models/account/account-follow.ts @@ -11,6 +11,7 @@ let listFollowingForApi: AccountFollowMethods.ListFollowingForApi let listFollowersForApi: AccountFollowMethods.ListFollowersForApi let listAcceptedFollowerUrlsForApi: AccountFollowMethods.ListAcceptedFollowerUrlsForApi let listAcceptedFollowingUrlsForApi: AccountFollowMethods.ListAcceptedFollowingUrlsForApi +let listAcceptedFollowerSharedInboxUrls: AccountFollowMethods.ListAcceptedFollowerSharedInboxUrls export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { AccountFollow = sequelize.define('AccountFollow', @@ -42,7 +43,8 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da listFollowingForApi, listFollowersForApi, listAcceptedFollowerUrlsForApi, - listAcceptedFollowingUrlsForApi + listAcceptedFollowingUrlsForApi, + listAcceptedFollowerSharedInboxUrls ] addMethodsToModel(AccountFollow, classMethods) @@ -146,17 +148,27 @@ listFollowersForApi = function (id: number, start: number, count: number, sort: }) } -listAcceptedFollowerUrlsForApi = function (accountId: number, start?: number, count?: number) { - return createListAcceptedFollowForApiQuery('followers', accountId, start, count) +listAcceptedFollowerUrlsForApi = function (accountIds: number[], start?: number, count?: number) { + return createListAcceptedFollowForApiQuery('followers', accountIds, start, count) } -listAcceptedFollowingUrlsForApi = function (accountId: number, start?: number, count?: number) { - return createListAcceptedFollowForApiQuery('following', accountId, start, count) +listAcceptedFollowerSharedInboxUrls = function (accountIds: number[]) { + return createListAcceptedFollowForApiQuery('followers', accountIds, undefined, undefined, 'sharedInboxUrl') +} + +listAcceptedFollowingUrlsForApi = function (accountIds: number[], start?: number, count?: number) { + return createListAcceptedFollowForApiQuery('following', accountIds, start, count) } // ------------------------------ UTILS ------------------------------ -async function createListAcceptedFollowForApiQuery (type: 'followers' | 'following', accountId: number, start?: number, count?: number) { +async function createListAcceptedFollowForApiQuery ( + type: 'followers' | 'following', + accountIds: number[], + start?: number, + count?: number, + columnUrl = 'url' +) { let firstJoin: string let secondJoin: string @@ -168,20 +180,20 @@ async function createListAcceptedFollowForApiQuery (type: 'followers' | 'followi secondJoin = 'targetAccountId' } - const selections = [ '"Follows"."url" AS "url"', 'COUNT(*) AS "total"' ] + const selections = [ '"Follows"."' + columnUrl + '" AS "url"', 'COUNT(*) AS "total"' ] const tasks: Promise[] = [] for (const selection of selections) { let query = 'SELECT ' + selection + ' FROM "Accounts" ' + 'INNER JOIN "AccountFollows" ON "AccountFollows"."' + firstJoin + '" = "Accounts"."id" ' + 'INNER JOIN "Accounts" AS "Follows" ON "AccountFollows"."' + secondJoin + '" = "Follows"."id" ' + - 'WHERE "Accounts"."id" = $accountId AND "AccountFollows"."state" = \'accepted\' ' + 'WHERE "Accounts"."id" IN ($accountIds) AND "AccountFollows"."state" = \'accepted\' ' if (start !== undefined) query += 'LIMIT ' + start if (count !== undefined) query += ', ' + count const options = { - bind: { accountId }, + bind: { accountIds: accountIds.join(',') }, type: Sequelize.QueryTypes.SELECT } tasks.push(AccountFollow['sequelize'].query(query, options)) diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index 3cb4a33b9..1f4604f1d 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts @@ -153,8 +153,8 @@ toActivityPubObject = function (this: VideoChannelInstance) { uuid: this.uuid, content: this.description, name: this.name, - published: this.createdAt, - updated: this.updatedAt + published: this.createdAt.toISOString(), + updated: this.updatedAt.toISOString() } return json diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 480e54276..64ee7ae34 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -558,7 +558,7 @@ toActivityPubObject = function (this: VideoInstance) { for (const file of this.VideoFiles) { url.push({ type: 'Link', - mimeType: 'video/' + file.extname, + mimeType: 'video/' + file.extname.replace('.', ''), url: getVideoFileUrl(this, file, baseUrlHttp), width: file.resolution, size: file.size @@ -601,8 +601,8 @@ toActivityPubObject = function (this: VideoInstance) { }, views: this.views, nsfw: this.nsfw, - published: this.createdAt, - updated: this.updatedAt, + published: this.createdAt.toISOString(), + updated: this.updatedAt.toISOString(), mediaType: 'text/markdown', content: this.getTruncatedDescription(), icon: { diff --git a/shared/models/activitypub/objects/video-channel-object.ts b/shared/models/activitypub/objects/video-channel-object.ts index 468e1535e..c9325b5df 100644 --- a/shared/models/activitypub/objects/video-channel-object.ts +++ b/shared/models/activitypub/objects/video-channel-object.ts @@ -4,7 +4,7 @@ export interface VideoChannelObject { name: string content: string uuid: string - published: Date - updated: Date + published: string + updated: string actor?: string } diff --git a/shared/models/activitypub/objects/video-torrent-object.ts b/shared/models/activitypub/objects/video-torrent-object.ts index 99e7157b8..ae8f807c8 100644 --- a/shared/models/activitypub/objects/video-torrent-object.ts +++ b/shared/models/activitypub/objects/video-torrent-object.ts @@ -17,8 +17,8 @@ export interface VideoTorrentObject { language: ActivityIdentifierObject views: number nsfw: boolean - published: Date - updated: Date + published: string + updated: string mediaType: 'text/markdown' content: string icon: ActivityIconObject -- 2.41.0