aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/helpers/activitypub.ts
diff options
context:
space:
mode:
Diffstat (limited to 'server/helpers/activitypub.ts')
-rw-r--r--server/helpers/activitypub.ts275
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 @@
1import { join } from 'path'
2import * as request from 'request'
3import * as Sequelize from 'sequelize'
4import * as url from 'url'
5import { ActivityIconObject } from '../../shared/index'
6import { Activity } from '../../shared/models/activitypub/activity' 1import { Activity } from '../../shared/models/activitypub/activity'
7import { ActivityPubActor } from '../../shared/models/activitypub/activitypub-actor'
8import { VideoChannelObject } from '../../shared/models/activitypub/objects/video-channel-object'
9import { ResultList } from '../../shared/models/result-list.model' 2import { ResultList } from '../../shared/models/result-list.model'
10import { database as db, REMOTE_SCHEME } from '../initializers'
11import { ACTIVITY_PUB, CONFIG, STATIC_PATHS } from '../initializers/constants'
12import { videoChannelActivityObjectToDBAttributes } from '../lib/activitypub/process/misc'
13import { sendVideoAnnounce } from '../lib/activitypub/send/send-announce'
14import { sendVideoChannelAnnounce } from '../lib/index'
15import { AccountFollowInstance } from '../models/account/account-follow-interface'
16import { AccountInstance } from '../models/account/account-interface' 3import { AccountInstance } from '../models/account/account-interface'
17import { VideoAbuseInstance } from '../models/video/video-abuse-interface'
18import { VideoChannelInstance } from '../models/video/video-channel-interface'
19import { VideoInstance } from '../models/video/video-interface'
20import { isRemoteAccountValid } from './custom-validators'
21import { logger } from './logger'
22import { signObject } from './peertube-crypto' 4import { signObject } from './peertube-crypto'
23import { doRequest, doRequestAndSaveToFile } from './requests'
24import { getServerAccount } from './utils'
25import { isVideoChannelObjectValid } from './custom-validators/activitypub/video-channels'
26
27function 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
38async 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
49async 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
60function getVideoActivityPubUrl (video: VideoInstance) {
61 return CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid
62}
63
64function getVideoChannelActivityPubUrl (videoChannel: VideoChannelInstance) {
65 return CONFIG.WEBSERVER.URL + '/video-channels/' + videoChannel.uuid
66}
67
68function getAccountActivityPubUrl (accountName: string) {
69 return CONFIG.WEBSERVER.URL + '/account/' + accountName
70}
71
72function getVideoAbuseActivityPubUrl (videoAbuse: VideoAbuseInstance) {
73 return CONFIG.WEBSERVER.URL + '/admin/video-abuses/' + videoAbuse.id
74}
75
76function getAccountFollowActivityPubUrl (accountFollow: AccountFollowInstance) {
77 const me = accountFollow.AccountFollower
78 const following = accountFollow.AccountFollowing
79
80 return me.url + '#follows/' + following.id
81}
82
83function getAccountFollowAcceptActivityPubUrl (accountFollow: AccountFollowInstance) {
84 const follower = accountFollow.AccountFollower
85 const me = accountFollow.AccountFollowing
86
87 return follower.url + '#accepts/follows/' + me.id
88}
89
90function getAnnounceActivityPubUrl (originalUrl: string, byAccount: AccountInstance) {
91 return originalUrl + '#announces/' + byAccount.id
92}
93
94function getUpdateActivityPubUrl (originalUrl: string, updatedAt: string) {
95 return originalUrl + '#updates/' + updatedAt
96}
97
98function getUndoActivityPubUrl (originalUrl: string) {
99 return originalUrl + '/undo'
100}
101
102async 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
117async 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
132async 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
190async 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
222function 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
230async 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
243function activityPubContextify <T> (data: T) { 6function 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
291export { 54export {
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
316async 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}