2 import { checkUrlsSameHost, getAPId } from '@server/helpers/activitypub'
3 import { retryTransactionWrapper } from '@server/helpers/database-utils'
4 import { logger } from '@server/helpers/logger'
5 import { JobQueue } from '@server/lib/job-queue'
6 import { ActorFetchByUrlType, fetchActorByUrl } from '@server/lib/model-loaders'
7 import { MActor, MActorAccountChannelId, MActorAccountChannelIdActor, MActorAccountId, MActorFullActor } from '@server/types/models'
8 import { ActivityPubActor } from '@shared/models'
9 import { refreshActorIfNeeded } from './refresh'
10 import { APActorCreator, fetchRemoteActor } from './shared'
12 function getOrCreateAPActor (
13 activityActor: string | ActivityPubActor,
15 recurseIfNeeded?: boolean,
16 updateCollections?: boolean
17 ): Promise<MActorFullActor>
19 function getOrCreateAPActor (
20 activityActor: string | ActivityPubActor,
21 fetchType?: 'association-ids',
22 recurseIfNeeded?: boolean,
23 updateCollections?: boolean
24 ): Promise<MActorAccountChannelId>
26 async function getOrCreateAPActor (
27 activityActor: string | ActivityPubActor,
28 fetchType: ActorFetchByUrlType = 'association-ids',
29 recurseIfNeeded = true,
30 updateCollections = false
31 ): Promise<MActorFullActor | MActorAccountChannelId> {
32 const actorUrl = getAPId(activityActor)
33 let actor = await loadActorFromDB(actorUrl, fetchType)
36 let accountPlaylistsUrl: string
38 // We don't have this actor in our database, fetch it on remote
40 const { actorObject } = await fetchRemoteActor(actorUrl)
41 if (actorObject === undefined) throw new Error('Cannot fetch remote actor ' + actorUrl)
43 // Create the attributed to actor
44 // In PeerTube a video channel is owned by an account
45 let ownerActor: MActorFullActor
46 if (recurseIfNeeded === true && actorObject.type === 'Group') {
47 ownerActor = await getOrCreateAPOwner(actorObject, actorUrl)
50 const creator = new APActorCreator(actorObject, ownerActor)
51 actor = await retryTransactionWrapper(creator.create.bind(creator))
53 accountPlaylistsUrl = actorObject.playlists
56 if (actor.Account) (actor as MActorAccountChannelIdActor).Account.Actor = actor
57 if (actor.VideoChannel) (actor as MActorAccountChannelIdActor).VideoChannel.Actor = actor
59 const { actor: actorRefreshed, refreshed } = await retryTransactionWrapper(refreshActorIfNeeded, actor, fetchType)
60 if (!actorRefreshed) throw new Error('Actor ' + actor.url + ' does not exist anymore.')
62 await scheduleOutboxFetchIfNeeded(actor, created, refreshed, updateCollections)
63 await schedulePlaylistFetchIfNeeded(actor, created, accountPlaylistsUrl)
68 // ---------------------------------------------------------------------------
74 // ---------------------------------------------------------------------------
76 async function loadActorFromDB (actorUrl: string, fetchType: ActorFetchByUrlType) {
77 let actor = await fetchActorByUrl(actorUrl, fetchType)
79 // Orphan actor (not associated to an account of channel) so recreate it
80 if (actor && (!actor.Account && !actor.VideoChannel)) {
88 function getOrCreateAPOwner (actorObject: ActivityPubActor, actorUrl: string) {
89 const accountAttributedTo = actorObject.attributedTo.find(a => a.type === 'Person')
90 if (!accountAttributedTo) throw new Error('Cannot find account attributed to video channel ' + actorUrl)
92 if (checkUrlsSameHost(accountAttributedTo.id, actorUrl) !== true) {
93 throw new Error(`Account attributed to ${accountAttributedTo.id} does not have the same host than actor url ${actorUrl}`)
97 // Don't recurse another time
98 const recurseIfNeeded = false
99 return getOrCreateAPActor(accountAttributedTo.id, 'all', recurseIfNeeded)
101 logger.error('Cannot get or create account attributed to video channel ' + actorUrl)
106 async function scheduleOutboxFetchIfNeeded (actor: MActor, created: boolean, refreshed: boolean, updateCollections: boolean) {
107 if ((created === true || refreshed === true) && updateCollections === true) {
108 const payload = { uri: actor.outboxUrl, type: 'activity' as 'activity' }
109 await JobQueue.Instance.createJobWithPromise({ type: 'activitypub-http-fetcher', payload })
113 async function schedulePlaylistFetchIfNeeded (actor: MActorAccountId, created: boolean, accountPlaylistsUrl: string) {
114 // We created a new account: fetch the playlists
115 if (created === true && actor.Account && accountPlaylistsUrl) {
116 const payload = { uri: accountPlaylistsUrl, accountId: actor.Account.id, type: 'account-playlists' as 'account-playlists' }
117 await JobQueue.Instance.createJobWithPromise({ type: 'activitypub-http-fetcher', payload })