diff options
Diffstat (limited to 'server/helpers/activitypub.ts')
-rw-r--r-- | server/helpers/activitypub.ts | 275 |
1 files changed, 1 insertions, 274 deletions
diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts index 9622a1801..5c577bb61 100644 --- a/server/helpers/activitypub.ts +++ b/server/helpers/activitypub.ts | |||
@@ -1,244 +1,7 @@ | |||
1 | import { join } from 'path' | ||
2 | import * as request from 'request' | ||
3 | import * as Sequelize from 'sequelize' | ||
4 | import * as url from 'url' | ||
5 | import { ActivityIconObject } from '../../shared/index' | ||
6 | import { Activity } from '../../shared/models/activitypub/activity' | 1 | import { Activity } from '../../shared/models/activitypub/activity' |
7 | import { ActivityPubActor } from '../../shared/models/activitypub/activitypub-actor' | ||
8 | import { VideoChannelObject } from '../../shared/models/activitypub/objects/video-channel-object' | ||
9 | import { ResultList } from '../../shared/models/result-list.model' | 2 | import { ResultList } from '../../shared/models/result-list.model' |
10 | import { database as db, REMOTE_SCHEME } from '../initializers' | ||
11 | import { ACTIVITY_PUB, CONFIG, STATIC_PATHS } from '../initializers/constants' | ||
12 | import { videoChannelActivityObjectToDBAttributes } from '../lib/activitypub/process/misc' | ||
13 | import { sendVideoAnnounce } from '../lib/activitypub/send/send-announce' | ||
14 | import { sendVideoChannelAnnounce } from '../lib/index' | ||
15 | import { AccountFollowInstance } from '../models/account/account-follow-interface' | ||
16 | import { AccountInstance } from '../models/account/account-interface' | 3 | import { AccountInstance } from '../models/account/account-interface' |
17 | import { VideoAbuseInstance } from '../models/video/video-abuse-interface' | ||
18 | import { VideoChannelInstance } from '../models/video/video-channel-interface' | ||
19 | import { VideoInstance } from '../models/video/video-interface' | ||
20 | import { isRemoteAccountValid } from './custom-validators' | ||
21 | import { logger } from './logger' | ||
22 | import { signObject } from './peertube-crypto' | 4 | import { signObject } from './peertube-crypto' |
23 | import { doRequest, doRequestAndSaveToFile } from './requests' | ||
24 | import { getServerAccount } from './utils' | ||
25 | import { isVideoChannelObjectValid } from './custom-validators/activitypub/video-channels' | ||
26 | |||
27 | function generateThumbnailFromUrl (video: VideoInstance, icon: ActivityIconObject) { | ||
28 | const thumbnailName = video.getThumbnailName() | ||
29 | const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, thumbnailName) | ||
30 | |||
31 | const options = { | ||
32 | method: 'GET', | ||
33 | uri: icon.url | ||
34 | } | ||
35 | return doRequestAndSaveToFile(options, thumbnailPath) | ||
36 | } | ||
37 | |||
38 | async function shareVideoChannelByServer (videoChannel: VideoChannelInstance, t: Sequelize.Transaction) { | ||
39 | const serverAccount = await getServerAccount() | ||
40 | |||
41 | await db.VideoChannelShare.create({ | ||
42 | accountId: serverAccount.id, | ||
43 | videoChannelId: videoChannel.id | ||
44 | }, { transaction: t }) | ||
45 | |||
46 | return sendVideoChannelAnnounce(serverAccount, videoChannel, t) | ||
47 | } | ||
48 | |||
49 | async function shareVideoByServer (video: VideoInstance, t: Sequelize.Transaction) { | ||
50 | const serverAccount = await getServerAccount() | ||
51 | |||
52 | await db.VideoShare.create({ | ||
53 | accountId: serverAccount.id, | ||
54 | videoId: video.id | ||
55 | }, { transaction: t }) | ||
56 | |||
57 | return sendVideoAnnounce(serverAccount, video, t) | ||
58 | } | ||
59 | |||
60 | function getVideoActivityPubUrl (video: VideoInstance) { | ||
61 | return CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid | ||
62 | } | ||
63 | |||
64 | function getVideoChannelActivityPubUrl (videoChannel: VideoChannelInstance) { | ||
65 | return CONFIG.WEBSERVER.URL + '/video-channels/' + videoChannel.uuid | ||
66 | } | ||
67 | |||
68 | function getAccountActivityPubUrl (accountName: string) { | ||
69 | return CONFIG.WEBSERVER.URL + '/account/' + accountName | ||
70 | } | ||
71 | |||
72 | function getVideoAbuseActivityPubUrl (videoAbuse: VideoAbuseInstance) { | ||
73 | return CONFIG.WEBSERVER.URL + '/admin/video-abuses/' + videoAbuse.id | ||
74 | } | ||
75 | |||
76 | function getAccountFollowActivityPubUrl (accountFollow: AccountFollowInstance) { | ||
77 | const me = accountFollow.AccountFollower | ||
78 | const following = accountFollow.AccountFollowing | ||
79 | |||
80 | return me.url + '#follows/' + following.id | ||
81 | } | ||
82 | |||
83 | function getAccountFollowAcceptActivityPubUrl (accountFollow: AccountFollowInstance) { | ||
84 | const follower = accountFollow.AccountFollower | ||
85 | const me = accountFollow.AccountFollowing | ||
86 | |||
87 | return follower.url + '#accepts/follows/' + me.id | ||
88 | } | ||
89 | |||
90 | function getAnnounceActivityPubUrl (originalUrl: string, byAccount: AccountInstance) { | ||
91 | return originalUrl + '#announces/' + byAccount.id | ||
92 | } | ||
93 | |||
94 | function getUpdateActivityPubUrl (originalUrl: string, updatedAt: string) { | ||
95 | return originalUrl + '#updates/' + updatedAt | ||
96 | } | ||
97 | |||
98 | function getUndoActivityPubUrl (originalUrl: string) { | ||
99 | return originalUrl + '/undo' | ||
100 | } | ||
101 | |||
102 | async function getOrCreateAccount (accountUrl: string) { | ||
103 | let account = await db.Account.loadByUrl(accountUrl) | ||
104 | |||
105 | // We don't have this account in our database, fetch it on remote | ||
106 | if (!account) { | ||
107 | const res = await fetchRemoteAccountAndCreateServer(accountUrl) | ||
108 | if (res === undefined) throw new Error('Cannot fetch remote account.') | ||
109 | |||
110 | // Save our new account in database | ||
111 | account = await res.account.save() | ||
112 | } | ||
113 | |||
114 | return account | ||
115 | } | ||
116 | |||
117 | async function getOrCreateVideoChannel (ownerAccount: AccountInstance, videoChannelUrl: string) { | ||
118 | let videoChannel = await db.VideoChannel.loadByUrl(videoChannelUrl) | ||
119 | |||
120 | // We don't have this account in our database, fetch it on remote | ||
121 | if (!videoChannel) { | ||
122 | videoChannel = await fetchRemoteVideoChannel(ownerAccount, videoChannelUrl) | ||
123 | if (videoChannel === undefined) throw new Error('Cannot fetch remote video channel.') | ||
124 | |||
125 | // Save our new video channel in database | ||
126 | await videoChannel.save() | ||
127 | } | ||
128 | |||
129 | return videoChannel | ||
130 | } | ||
131 | |||
132 | async function fetchRemoteAccountAndCreateServer (accountUrl: string) { | ||
133 | const options = { | ||
134 | uri: accountUrl, | ||
135 | method: 'GET', | ||
136 | headers: { | ||
137 | 'Accept': ACTIVITY_PUB.ACCEPT_HEADER | ||
138 | } | ||
139 | } | ||
140 | |||
141 | logger.info('Fetching remote account %s.', accountUrl) | ||
142 | |||
143 | let requestResult | ||
144 | try { | ||
145 | requestResult = await doRequest(options) | ||
146 | } catch (err) { | ||
147 | logger.warn('Cannot fetch remote account %s.', accountUrl, err) | ||
148 | return undefined | ||
149 | } | ||
150 | |||
151 | const accountJSON: ActivityPubActor = JSON.parse(requestResult.body) | ||
152 | if (isRemoteAccountValid(accountJSON) === false) { | ||
153 | logger.debug('Remote account JSON is not valid.', { accountJSON }) | ||
154 | return undefined | ||
155 | } | ||
156 | |||
157 | const followersCount = await fetchAccountCount(accountJSON.followers) | ||
158 | const followingCount = await fetchAccountCount(accountJSON.following) | ||
159 | |||
160 | const account = db.Account.build({ | ||
161 | uuid: accountJSON.uuid, | ||
162 | name: accountJSON.preferredUsername, | ||
163 | url: accountJSON.url, | ||
164 | publicKey: accountJSON.publicKey.publicKeyPem, | ||
165 | privateKey: null, | ||
166 | followersCount: followersCount, | ||
167 | followingCount: followingCount, | ||
168 | inboxUrl: accountJSON.inbox, | ||
169 | outboxUrl: accountJSON.outbox, | ||
170 | sharedInboxUrl: accountJSON.endpoints.sharedInbox, | ||
171 | followersUrl: accountJSON.followers, | ||
172 | followingUrl: accountJSON.following | ||
173 | }) | ||
174 | |||
175 | const accountHost = url.parse(account.url).host | ||
176 | const serverOptions = { | ||
177 | where: { | ||
178 | host: accountHost | ||
179 | }, | ||
180 | defaults: { | ||
181 | host: accountHost | ||
182 | } | ||
183 | } | ||
184 | const [ server ] = await db.Server.findOrCreate(serverOptions) | ||
185 | account.set('serverId', server.id) | ||
186 | |||
187 | return { account, server } | ||
188 | } | ||
189 | |||
190 | async function fetchRemoteVideoChannel (ownerAccount: AccountInstance, videoChannelUrl: string) { | ||
191 | const options = { | ||
192 | uri: videoChannelUrl, | ||
193 | method: 'GET', | ||
194 | headers: { | ||
195 | 'Accept': ACTIVITY_PUB.ACCEPT_HEADER | ||
196 | } | ||
197 | } | ||
198 | |||
199 | logger.info('Fetching remote video channel %s.', videoChannelUrl) | ||
200 | |||
201 | let requestResult | ||
202 | try { | ||
203 | requestResult = await doRequest(options) | ||
204 | } catch (err) { | ||
205 | logger.warn('Cannot fetch remote video channel %s.', videoChannelUrl, err) | ||
206 | return undefined | ||
207 | } | ||
208 | |||
209 | const videoChannelJSON: VideoChannelObject = JSON.parse(requestResult.body) | ||
210 | if (isVideoChannelObjectValid(videoChannelJSON) === false) { | ||
211 | logger.debug('Remote video channel JSON is not valid.', { videoChannelJSON }) | ||
212 | return undefined | ||
213 | } | ||
214 | |||
215 | const videoChannelAttributes = videoChannelActivityObjectToDBAttributes(videoChannelJSON, ownerAccount) | ||
216 | const videoChannel = db.VideoChannel.build(videoChannelAttributes) | ||
217 | videoChannel.Account = ownerAccount | ||
218 | |||
219 | return videoChannel | ||
220 | } | ||
221 | |||
222 | function fetchRemoteVideoPreview (video: VideoInstance) { | ||
223 | // FIXME: use url | ||
224 | const host = video.VideoChannel.Account.Server.host | ||
225 | const path = join(STATIC_PATHS.PREVIEWS, video.getPreviewName()) | ||
226 | |||
227 | return request.get(REMOTE_SCHEME.HTTP + '://' + host + path) | ||
228 | } | ||
229 | |||
230 | async function fetchRemoteVideoDescription (video: VideoInstance) { | ||
231 | // FIXME: use url | ||
232 | const host = video.VideoChannel.Account.Server.host | ||
233 | const path = video.getDescriptionPath() | ||
234 | const options = { | ||
235 | uri: REMOTE_SCHEME.HTTP + '://' + host + path, | ||
236 | json: true | ||
237 | } | ||
238 | |||
239 | const { body } = await doRequest(options) | ||
240 | return body.description ? body.description : '' | ||
241 | } | ||
242 | 5 | ||
243 | function activityPubContextify <T> (data: T) { | 6 | function activityPubContextify <T> (data: T) { |
244 | return Object.assign(data,{ | 7 | return Object.assign(data,{ |
@@ -289,43 +52,7 @@ function buildSignedActivity (byAccount: AccountInstance, data: Object) { | |||
289 | // --------------------------------------------------------------------------- | 52 | // --------------------------------------------------------------------------- |
290 | 53 | ||
291 | export { | 54 | export { |
292 | fetchRemoteAccountAndCreateServer, | ||
293 | activityPubContextify, | 55 | activityPubContextify, |
294 | activityPubCollectionPagination, | 56 | activityPubCollectionPagination, |
295 | generateThumbnailFromUrl, | 57 | buildSignedActivity |
296 | getOrCreateAccount, | ||
297 | fetchRemoteVideoPreview, | ||
298 | fetchRemoteVideoDescription, | ||
299 | shareVideoChannelByServer, | ||
300 | shareVideoByServer, | ||
301 | getOrCreateVideoChannel, | ||
302 | buildSignedActivity, | ||
303 | getVideoActivityPubUrl, | ||
304 | getVideoChannelActivityPubUrl, | ||
305 | getAccountActivityPubUrl, | ||
306 | getVideoAbuseActivityPubUrl, | ||
307 | getAccountFollowActivityPubUrl, | ||
308 | getAccountFollowAcceptActivityPubUrl, | ||
309 | getAnnounceActivityPubUrl, | ||
310 | getUpdateActivityPubUrl, | ||
311 | getUndoActivityPubUrl | ||
312 | } | ||
313 | |||
314 | // --------------------------------------------------------------------------- | ||
315 | |||
316 | async function fetchAccountCount (url: string) { | ||
317 | const options = { | ||
318 | uri: url, | ||
319 | method: 'GET' | ||
320 | } | ||
321 | |||
322 | let requestResult | ||
323 | try { | ||
324 | requestResult = await doRequest(options) | ||
325 | } catch (err) { | ||
326 | logger.warn('Cannot fetch remote account count %s.', url, err) | ||
327 | return undefined | ||
328 | } | ||
329 | |||
330 | return requestResult.totalItems ? requestResult.totalItems : 0 | ||
331 | } | 58 | } |