From 136d7efde798d3dc0ec0dd18aac674365f7d162e Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 3 Jun 2021 16:02:29 +0200 Subject: Refactor AP actors --- server/lib/activitypub/actors/get.ts | 119 +++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 server/lib/activitypub/actors/get.ts (limited to 'server/lib/activitypub/actors/get.ts') diff --git a/server/lib/activitypub/actors/get.ts b/server/lib/activitypub/actors/get.ts new file mode 100644 index 000000000..0d5bea789 --- /dev/null +++ b/server/lib/activitypub/actors/get.ts @@ -0,0 +1,119 @@ + +import { checkUrlsSameHost, getAPId } from '@server/helpers/activitypub' +import { ActorFetchByUrlType, fetchActorByUrl } from '@server/helpers/actor' +import { retryTransactionWrapper } from '@server/helpers/database-utils' +import { logger } from '@server/helpers/logger' +import { JobQueue } from '@server/lib/job-queue' +import { MActor, MActorAccountChannelId, MActorAccountChannelIdActor, MActorAccountId, MActorFullActor } from '@server/types/models' +import { ActivityPubActor } from '@shared/models' +import { refreshActorIfNeeded } from './refresh' +import { APActorCreator, fetchRemoteActor } from './shared' + +function getOrCreateAPActor ( + activityActor: string | ActivityPubActor, + fetchType: 'all', + recurseIfNeeded?: boolean, + updateCollections?: boolean +): Promise + +function getOrCreateAPActor ( + activityActor: string | ActivityPubActor, + fetchType?: 'association-ids', + recurseIfNeeded?: boolean, + updateCollections?: boolean +): Promise + +async function getOrCreateAPActor ( + activityActor: string | ActivityPubActor, + fetchType: ActorFetchByUrlType = 'association-ids', + recurseIfNeeded = true, + updateCollections = false +): Promise { + const actorUrl = getAPId(activityActor) + let actor = await loadActorFromDB(actorUrl, fetchType) + + let created = false + let accountPlaylistsUrl: string + + // We don't have this actor in our database, fetch it on remote + if (!actor) { + const { actorObject } = await fetchRemoteActor(actorUrl) + if (actorObject === undefined) throw new Error('Cannot fetch remote actor ' + actorUrl) + + // Create the attributed to actor + // In PeerTube a video channel is owned by an account + let ownerActor: MActorFullActor + if (recurseIfNeeded === true && actorObject.type === 'Group') { + ownerActor = await getOrCreateAPOwner(actorObject, actorUrl) + } + + const creator = new APActorCreator(actorObject, ownerActor) + actor = await retryTransactionWrapper(creator.create.bind(creator)) + created = true + accountPlaylistsUrl = actorObject.playlists + } + + if (actor.Account) (actor as MActorAccountChannelIdActor).Account.Actor = actor + if (actor.VideoChannel) (actor as MActorAccountChannelIdActor).VideoChannel.Actor = actor + + const { actor: actorRefreshed, refreshed } = await retryTransactionWrapper(refreshActorIfNeeded, actor, fetchType) + if (!actorRefreshed) throw new Error('Actor ' + actor.url + ' does not exist anymore.') + + await scheduleOutboxFetchIfNeeded(actor, created, refreshed, updateCollections) + await schedulePlaylistFetchIfNeeded(actor, created, accountPlaylistsUrl) + + return actorRefreshed +} + +// --------------------------------------------------------------------------- + +export { + getOrCreateAPActor +} + +// --------------------------------------------------------------------------- + +async function loadActorFromDB (actorUrl: string, fetchType: ActorFetchByUrlType) { + let actor = await fetchActorByUrl(actorUrl, fetchType) + + // Orphan actor (not associated to an account of channel) so recreate it + if (actor && (!actor.Account && !actor.VideoChannel)) { + await actor.destroy() + actor = null + } + + return actor +} + +function getOrCreateAPOwner (actorObject: ActivityPubActor, actorUrl: string) { + const accountAttributedTo = actorObject.attributedTo.find(a => a.type === 'Person') + if (!accountAttributedTo) throw new Error('Cannot find account attributed to video channel ' + actorUrl) + + if (checkUrlsSameHost(accountAttributedTo.id, actorUrl) !== true) { + throw new Error(`Account attributed to ${accountAttributedTo.id} does not have the same host than actor url ${actorUrl}`) + } + + try { + // Don't recurse another time + const recurseIfNeeded = false + return getOrCreateAPActor(accountAttributedTo.id, 'all', recurseIfNeeded) + } catch (err) { + logger.error('Cannot get or create account attributed to video channel ' + actorUrl) + throw new Error(err) + } +} + +async function scheduleOutboxFetchIfNeeded (actor: MActor, created: boolean, refreshed: boolean, updateCollections: boolean) { + if ((created === true || refreshed === true) && updateCollections === true) { + const payload = { uri: actor.outboxUrl, type: 'activity' as 'activity' } + await JobQueue.Instance.createJobWithPromise({ type: 'activitypub-http-fetcher', payload }) + } +} + +async function schedulePlaylistFetchIfNeeded (actor: MActorAccountId, created: boolean, accountPlaylistsUrl: string) { + // We created a new account: fetch the playlists + if (created === true && actor.Account && accountPlaylistsUrl) { + const payload = { uri: accountPlaylistsUrl, accountId: actor.Account.id, type: 'account-playlists' as 'account-playlists' } + await JobQueue.Instance.createJobWithPromise({ type: 'activitypub-http-fetcher', payload }) + } +} -- cgit v1.2.3