]>
Commit | Line | Data |
---|---|---|
571389d4 C |
1 | import { join } from 'path' |
2 | import * as request from 'request' | |
e4f97bab | 3 | import * as url from 'url' |
571389d4 | 4 | import { ActivityIconObject } from '../../shared/index' |
e4f97bab C |
5 | import { ActivityPubActor } from '../../shared/models/activitypub/activitypub-actor' |
6 | import { ResultList } from '../../shared/models/result-list.model' | |
571389d4 | 7 | import { database as db, REMOTE_SCHEME } from '../initializers' |
350e31d6 | 8 | import { ACTIVITY_PUB_ACCEPT_HEADER, CONFIG, STATIC_PATHS } from '../initializers/constants' |
0d0e8dd0 | 9 | import { VideoInstance } from '../models/video/video-interface' |
571389d4 C |
10 | import { isRemoteAccountValid } from './custom-validators' |
11 | import { logger } from './logger' | |
12 | import { doRequest, doRequestAndSaveToFile } from './requests' | |
0d0e8dd0 C |
13 | |
14 | function generateThumbnailFromUrl (video: VideoInstance, icon: ActivityIconObject) { | |
15 | const thumbnailName = video.getThumbnailName() | |
16 | const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, thumbnailName) | |
17 | ||
18 | const options = { | |
19 | method: 'GET', | |
20 | uri: icon.url | |
21 | } | |
22 | return doRequestAndSaveToFile(options, thumbnailPath) | |
23 | } | |
24 | ||
571389d4 C |
25 | function getActivityPubUrl (type: 'video' | 'videoChannel' | 'account', id: string) { |
26 | if (type === 'video') return CONFIG.WEBSERVER.URL + '/videos/watch/' + id | |
27 | else if (type === 'videoChannel') return CONFIG.WEBSERVER.URL + '/video-channels/' + id | |
28 | else if (type === 'account') return CONFIG.WEBSERVER.URL + '/account/' + id | |
0d0e8dd0 C |
29 | |
30 | return '' | |
31 | } | |
32 | ||
33 | async function getOrCreateAccount (accountUrl: string) { | |
34 | let account = await db.Account.loadByUrl(accountUrl) | |
35 | ||
36 | // We don't have this account in our database, fetch it on remote | |
37 | if (!account) { | |
350e31d6 C |
38 | const res = await fetchRemoteAccountAndCreatePod(accountUrl) |
39 | if (res === undefined) throw new Error('Cannot fetch remote account.') | |
0d0e8dd0 C |
40 | |
41 | // Save our new account in database | |
350e31d6 | 42 | const account = res.account |
0d0e8dd0 C |
43 | await account.save() |
44 | } | |
45 | ||
46 | return account | |
47 | } | |
e4f97bab C |
48 | |
49 | async function fetchRemoteAccountAndCreatePod (accountUrl: string) { | |
50 | const options = { | |
51 | uri: accountUrl, | |
350e31d6 C |
52 | method: 'GET', |
53 | headers: { | |
54 | 'Accept': ACTIVITY_PUB_ACCEPT_HEADER | |
55 | } | |
e4f97bab C |
56 | } |
57 | ||
350e31d6 C |
58 | logger.info('Fetching remote account %s.', accountUrl) |
59 | ||
e4f97bab C |
60 | let requestResult |
61 | try { | |
62 | requestResult = await doRequest(options) | |
63 | } catch (err) { | |
350e31d6 | 64 | logger.warn('Cannot fetch remote account %s.', accountUrl, err) |
e4f97bab C |
65 | return undefined |
66 | } | |
67 | ||
350e31d6 C |
68 | const accountJSON: ActivityPubActor = JSON.parse(requestResult.body) |
69 | if (isRemoteAccountValid(accountJSON) === false) { | |
70 | logger.debug('Remote account JSON is not valid.', { accountJSON }) | |
71 | return undefined | |
72 | } | |
e4f97bab C |
73 | |
74 | const followersCount = await fetchAccountCount(accountJSON.followers) | |
75 | const followingCount = await fetchAccountCount(accountJSON.following) | |
76 | ||
77 | const account = db.Account.build({ | |
78 | uuid: accountJSON.uuid, | |
79 | name: accountJSON.preferredUsername, | |
80 | url: accountJSON.url, | |
81 | publicKey: accountJSON.publicKey.publicKeyPem, | |
82 | privateKey: null, | |
83 | followersCount: followersCount, | |
84 | followingCount: followingCount, | |
85 | inboxUrl: accountJSON.inbox, | |
86 | outboxUrl: accountJSON.outbox, | |
87 | sharedInboxUrl: accountJSON.endpoints.sharedInbox, | |
88 | followersUrl: accountJSON.followers, | |
89 | followingUrl: accountJSON.following | |
90 | }) | |
91 | ||
92 | const accountHost = url.parse(account.url).host | |
93 | const podOptions = { | |
94 | where: { | |
95 | host: accountHost | |
96 | }, | |
97 | defaults: { | |
98 | host: accountHost | |
99 | } | |
100 | } | |
350e31d6 C |
101 | const [ pod ] = await db.Pod.findOrCreate(podOptions) |
102 | account.set('podId', pod.id) | |
e4f97bab C |
103 | |
104 | return { account, pod } | |
105 | } | |
106 | ||
571389d4 C |
107 | function fetchRemoteVideoPreview (video: VideoInstance) { |
108 | // FIXME: use url | |
109 | const host = video.VideoChannel.Account.Pod.host | |
110 | const path = join(STATIC_PATHS.PREVIEWS, video.getPreviewName()) | |
111 | ||
112 | return request.get(REMOTE_SCHEME.HTTP + '://' + host + path) | |
113 | } | |
114 | ||
115 | async function fetchRemoteVideoDescription (video: VideoInstance) { | |
116 | const options = { | |
117 | uri: video.url | |
118 | } | |
119 | ||
120 | const { body } = await doRequest(options) | |
121 | return body.description ? body.description : '' | |
122 | } | |
123 | ||
124 | function activityPubContextify <T> (data: T) { | |
e4f97bab C |
125 | return Object.assign(data,{ |
126 | '@context': [ | |
127 | 'https://www.w3.org/ns/activitystreams', | |
128 | 'https://w3id.org/security/v1', | |
129 | { | |
130 | 'Hashtag': 'as:Hashtag', | |
131 | 'uuid': 'http://schema.org/identifier', | |
132 | 'category': 'http://schema.org/category', | |
133 | 'licence': 'http://schema.org/license', | |
134 | 'nsfw': 'as:sensitive', | |
135 | 'language': 'http://schema.org/inLanguage', | |
136 | 'views': 'http://schema.org/Number', | |
137 | 'size': 'http://schema.org/Number' | |
138 | } | |
139 | ] | |
140 | }) | |
141 | } | |
142 | ||
143 | function activityPubCollectionPagination (url: string, page: number, result: ResultList<any>) { | |
144 | const baseUrl = url.split('?').shift | |
145 | ||
146 | const obj = { | |
147 | id: baseUrl, | |
148 | type: 'Collection', | |
149 | totalItems: result.total, | |
150 | first: { | |
151 | id: baseUrl + '?page=' + page, | |
152 | type: 'CollectionPage', | |
153 | totalItems: result.total, | |
154 | next: baseUrl + '?page=' + (page + 1), | |
155 | partOf: baseUrl, | |
156 | items: result.data | |
157 | } | |
158 | } | |
159 | ||
160 | return activityPubContextify(obj) | |
161 | } | |
162 | ||
163 | // --------------------------------------------------------------------------- | |
164 | ||
165 | export { | |
166 | fetchRemoteAccountAndCreatePod, | |
167 | activityPubContextify, | |
0d0e8dd0 C |
168 | activityPubCollectionPagination, |
169 | getActivityPubUrl, | |
170 | generateThumbnailFromUrl, | |
571389d4 C |
171 | getOrCreateAccount, |
172 | fetchRemoteVideoPreview, | |
173 | fetchRemoteVideoDescription | |
e4f97bab C |
174 | } |
175 | ||
176 | // --------------------------------------------------------------------------- | |
177 | ||
178 | async function fetchAccountCount (url: string) { | |
179 | const options = { | |
180 | uri: url, | |
181 | method: 'GET' | |
182 | } | |
183 | ||
184 | let requestResult | |
185 | try { | |
186 | requestResult = await doRequest(options) | |
187 | } catch (err) { | |
350e31d6 | 188 | logger.warn('Cannot fetch remote account count %s.', url, err) |
e4f97bab C |
189 | return undefined |
190 | } | |
191 | ||
192 | return requestResult.totalItems ? requestResult.totalItems : 0 | |
193 | } |