diff options
author | Chocobozzz <me@florianbigard.com> | 2021-06-03 16:02:29 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2021-06-03 16:40:32 +0200 |
commit | 136d7efde798d3dc0ec0dd18aac674365f7d162e (patch) | |
tree | 3a0e2a7a5d04dedf0d8ffda99c2787cecb838891 /server/lib/activitypub/actors/get.ts | |
parent | 49af5ac8c2653cb0ef23479c9d3256c5b724d49d (diff) | |
download | PeerTube-136d7efde798d3dc0ec0dd18aac674365f7d162e.tar.gz PeerTube-136d7efde798d3dc0ec0dd18aac674365f7d162e.tar.zst PeerTube-136d7efde798d3dc0ec0dd18aac674365f7d162e.zip |
Refactor AP actors
Diffstat (limited to 'server/lib/activitypub/actors/get.ts')
-rw-r--r-- | server/lib/activitypub/actors/get.ts | 119 |
1 files changed, 119 insertions, 0 deletions
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 @@ | |||
1 | |||
2 | import { checkUrlsSameHost, getAPId } from '@server/helpers/activitypub' | ||
3 | import { ActorFetchByUrlType, fetchActorByUrl } from '@server/helpers/actor' | ||
4 | import { retryTransactionWrapper } from '@server/helpers/database-utils' | ||
5 | import { logger } from '@server/helpers/logger' | ||
6 | import { JobQueue } from '@server/lib/job-queue' | ||
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' | ||
11 | |||
12 | function getOrCreateAPActor ( | ||
13 | activityActor: string | ActivityPubActor, | ||
14 | fetchType: 'all', | ||
15 | recurseIfNeeded?: boolean, | ||
16 | updateCollections?: boolean | ||
17 | ): Promise<MActorFullActor> | ||
18 | |||
19 | function getOrCreateAPActor ( | ||
20 | activityActor: string | ActivityPubActor, | ||
21 | fetchType?: 'association-ids', | ||
22 | recurseIfNeeded?: boolean, | ||
23 | updateCollections?: boolean | ||
24 | ): Promise<MActorAccountChannelId> | ||
25 | |||
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) | ||
34 | |||
35 | let created = false | ||
36 | let accountPlaylistsUrl: string | ||
37 | |||
38 | // We don't have this actor in our database, fetch it on remote | ||
39 | if (!actor) { | ||
40 | const { actorObject } = await fetchRemoteActor(actorUrl) | ||
41 | if (actorObject === undefined) throw new Error('Cannot fetch remote actor ' + actorUrl) | ||
42 | |||
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) | ||
48 | } | ||
49 | |||
50 | const creator = new APActorCreator(actorObject, ownerActor) | ||
51 | actor = await retryTransactionWrapper(creator.create.bind(creator)) | ||
52 | created = true | ||
53 | accountPlaylistsUrl = actorObject.playlists | ||
54 | } | ||
55 | |||
56 | if (actor.Account) (actor as MActorAccountChannelIdActor).Account.Actor = actor | ||
57 | if (actor.VideoChannel) (actor as MActorAccountChannelIdActor).VideoChannel.Actor = actor | ||
58 | |||
59 | const { actor: actorRefreshed, refreshed } = await retryTransactionWrapper(refreshActorIfNeeded, actor, fetchType) | ||
60 | if (!actorRefreshed) throw new Error('Actor ' + actor.url + ' does not exist anymore.') | ||
61 | |||
62 | await scheduleOutboxFetchIfNeeded(actor, created, refreshed, updateCollections) | ||
63 | await schedulePlaylistFetchIfNeeded(actor, created, accountPlaylistsUrl) | ||
64 | |||
65 | return actorRefreshed | ||
66 | } | ||
67 | |||
68 | // --------------------------------------------------------------------------- | ||
69 | |||
70 | export { | ||
71 | getOrCreateAPActor | ||
72 | } | ||
73 | |||
74 | // --------------------------------------------------------------------------- | ||
75 | |||
76 | async function loadActorFromDB (actorUrl: string, fetchType: ActorFetchByUrlType) { | ||
77 | let actor = await fetchActorByUrl(actorUrl, fetchType) | ||
78 | |||
79 | // Orphan actor (not associated to an account of channel) so recreate it | ||
80 | if (actor && (!actor.Account && !actor.VideoChannel)) { | ||
81 | await actor.destroy() | ||
82 | actor = null | ||
83 | } | ||
84 | |||
85 | return actor | ||
86 | } | ||
87 | |||
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) | ||
91 | |||
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}`) | ||
94 | } | ||
95 | |||
96 | try { | ||
97 | // Don't recurse another time | ||
98 | const recurseIfNeeded = false | ||
99 | return getOrCreateAPActor(accountAttributedTo.id, 'all', recurseIfNeeded) | ||
100 | } catch (err) { | ||
101 | logger.error('Cannot get or create account attributed to video channel ' + actorUrl) | ||
102 | throw new Error(err) | ||
103 | } | ||
104 | } | ||
105 | |||
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 }) | ||
110 | } | ||
111 | } | ||
112 | |||
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 }) | ||
118 | } | ||
119 | } | ||