From 265ba139ebf56bbdc1c65f6ea4f367774c691fc0 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 3 Jan 2018 16:38:50 +0100 Subject: Send account activitypub update events --- server/lib/activitypub/actor.ts | 86 ++++++++++++---------- server/lib/activitypub/process/process-update.ts | 94 +++++++++++++++++++++++- server/lib/activitypub/send/send-update.ts | 16 ++++ 3 files changed, 154 insertions(+), 42 deletions(-) (limited to 'server/lib') diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts index e557896e8..b6ba2cc22 100644 --- a/server/lib/activitypub/actor.ts +++ b/server/lib/activitypub/actor.ts @@ -5,13 +5,13 @@ import * as url from 'url' import * as uuidv4 from 'uuid/v4' import { ActivityPubActor, ActivityPubActorType } from '../../../shared/models/activitypub' import { ActivityPubAttributedTo } from '../../../shared/models/activitypub/objects' -import { isRemoteActorValid } from '../../helpers/custom-validators/activitypub/actor' +import { isActorObjectValid } from '../../helpers/custom-validators/activitypub/actor' import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' import { retryTransactionWrapper } from '../../helpers/database-utils' import { logger } from '../../helpers/logger' import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto' import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests' -import { CONFIG, sequelizeTypescript } from '../../initializers' +import { AVATAR_MIMETYPE_EXT, CONFIG, sequelizeTypescript } from '../../initializers' import { AccountModel } from '../../models/account/account' import { ActorModel } from '../../models/activitypub/actor' import { AvatarModel } from '../../models/avatar/avatar' @@ -84,10 +84,52 @@ function buildActorInstance (type: ActivityPubActorType, url: string, preferredU }) } +async function fetchActorTotalItems (url: string) { + const options = { + uri: url, + method: 'GET', + json: true, + activityPub: true + } + + let requestResult + try { + requestResult = await doRequest(options) + } catch (err) { + logger.warn('Cannot fetch remote actor count %s.', url, err) + return undefined + } + + return requestResult.totalItems ? requestResult.totalItems : 0 +} + +async function fetchAvatarIfExists (actorJSON: ActivityPubActor) { + if ( + actorJSON.icon && actorJSON.icon.type === 'Image' && AVATAR_MIMETYPE_EXT[actorJSON.icon.mediaType] !== undefined && + isActivityPubUrlValid(actorJSON.icon.url) + ) { + const extension = AVATAR_MIMETYPE_EXT[actorJSON.icon.mediaType] + + const avatarName = uuidv4() + extension + const destPath = join(CONFIG.STORAGE.AVATARS_DIR, avatarName) + + await doRequestAndSaveToFile({ + method: 'GET', + uri: actorJSON.icon.url + }, destPath) + + return avatarName + } + + return undefined +} + export { getOrCreateActorAndServerAndModel, buildActorInstance, - setAsyncActorKeys + setAsyncActorKeys, + fetchActorTotalItems, + fetchAvatarIfExists } // --------------------------------------------------------------------------- @@ -166,7 +208,7 @@ async function fetchRemoteActor (actorUrl: string): Promise { @@ -54,6 +60,8 @@ async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) { const videoInstance = await VideoModel.loadByUrlAndPopulateAccount(videoAttributesToUpdate.id, t) if (!videoInstance) throw new Error('Video ' + videoAttributesToUpdate.id + ' not found.') + videoFieldsSave = videoInstance.toJSON() + const videoChannel = videoInstance.VideoChannel if (videoChannel.Account.Actor.id !== actor.id) { throw new Error('Account ' + actor.url + ' does not own video channel ' + videoChannel.Actor.url) @@ -102,3 +110,83 @@ async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) { throw err } } + +function processUpdateAccount (actor: ActorModel, activity: ActivityUpdate) { + const options = { + arguments: [ actor, activity ], + errorMessage: 'Cannot update the remote account with many retries' + } + + return retryTransactionWrapper(updateRemoteAccount, options) +} + +async function updateRemoteAccount (actor: ActorModel, activity: ActivityUpdate) { + const accountAttributesToUpdate = activity.object as ActivityPubActor + + logger.debug('Updating remote account "%s".', accountAttributesToUpdate.uuid) + let actorInstance: ActorModel + let accountInstance: AccountModel + let actorFieldsSave: object + let accountFieldsSave: object + + // Fetch icon? + const avatarName = await fetchAvatarIfExists(accountAttributesToUpdate) + + try { + await sequelizeTypescript.transaction(async t => { + actorInstance = await ActorModel.loadByUrl(accountAttributesToUpdate.id, t) + if (!actorInstance) throw new Error('Actor ' + accountAttributesToUpdate.id + ' not found.') + + actorFieldsSave = actorInstance.toJSON() + accountInstance = actorInstance.Account + accountFieldsSave = actorInstance.Account.toJSON() + + const followersCount = await fetchActorTotalItems(accountAttributesToUpdate.followers) + const followingCount = await fetchActorTotalItems(accountAttributesToUpdate.following) + + actorInstance.set('type', accountAttributesToUpdate.type) + actorInstance.set('uuid', accountAttributesToUpdate.uuid) + actorInstance.set('preferredUsername', accountAttributesToUpdate.preferredUsername) + actorInstance.set('url', accountAttributesToUpdate.id) + actorInstance.set('publicKey', accountAttributesToUpdate.publicKey.publicKeyPem) + actorInstance.set('followersCount', followersCount) + actorInstance.set('followingCount', followingCount) + actorInstance.set('inboxUrl', accountAttributesToUpdate.inbox) + actorInstance.set('outboxUrl', accountAttributesToUpdate.outbox) + actorInstance.set('sharedInboxUrl', accountAttributesToUpdate.endpoints.sharedInbox) + actorInstance.set('followersUrl', accountAttributesToUpdate.followers) + actorInstance.set('followingUrl', accountAttributesToUpdate.following) + + if (avatarName !== undefined) { + if (actorInstance.avatarId) { + await actorInstance.Avatar.destroy({ transaction: t }) + } + + const avatar = await AvatarModel.create({ + filename: avatarName + }, { transaction: t }) + + actor.set('avatarId', avatar.id) + } + + await actor.save({ transaction: t }) + + actor.Account.set('name', accountAttributesToUpdate.name || accountAttributesToUpdate.preferredUsername) + await actor.Account.save({ transaction: t }) + }) + + logger.info('Remote account with uuid %s updated', accountAttributesToUpdate.uuid) + } catch (err) { + if (actorInstance !== undefined && actorFieldsSave !== undefined) { + resetSequelizeInstance(actorInstance, actorFieldsSave) + } + + if (accountInstance !== undefined && accountFieldsSave !== undefined) { + resetSequelizeInstance(accountInstance, accountFieldsSave) + } + + // This is just a debug because we will retry the insert + logger.debug('Cannot update the remote account.', err) + throw err + } +} diff --git a/server/lib/activitypub/send/send-update.ts b/server/lib/activitypub/send/send-update.ts index b623fec6c..e8f11edd0 100644 --- a/server/lib/activitypub/send/send-update.ts +++ b/server/lib/activitypub/send/send-update.ts @@ -1,6 +1,7 @@ import { Transaction } from 'sequelize' import { ActivityAudience, ActivityUpdate } from '../../../../shared/models/activitypub' import { VideoPrivacy } from '../../../../shared/models/videos' +import { UserModel } from '../../../models/account/user' import { ActorModel } from '../../../models/activitypub/actor' import { VideoModel } from '../../../models/video/video' import { VideoShareModel } from '../../../models/video/video-share' @@ -22,9 +23,24 @@ async function sendUpdateVideo (video: VideoModel, t: Transaction) { return broadcastToFollowers(data, byActor, actorsInvolved, t) } +async function sendUpdateUser (user: UserModel, t: Transaction) { + const byActor = user.Account.Actor + + const url = getUpdateActivityPubUrl(byActor.url, byActor.updatedAt.toISOString()) + const accountObject = user.Account.toActivityPubObject() + const audience = await getAudience(byActor, t) + const data = await updateActivityData(url, byActor, accountObject, t, audience) + + const actorsInvolved = await VideoShareModel.loadActorsByVideoOwner(byActor.id, t) + actorsInvolved.push(byActor) + + return broadcastToFollowers(data, byActor, actorsInvolved, t) +} + // --------------------------------------------------------------------------- export { + sendUpdateUser, sendUpdateVideo } -- cgit v1.2.3