diff options
author | Chocobozzz <me@florianbigard.com> | 2021-04-06 17:01:35 +0200 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2021-04-08 10:07:53 +0200 |
commit | 2cb03dc1f4e01ba491c36caff30c33fe9c5bad89 (patch) | |
tree | 08a8706d105ea1e280339c02b9e2b1dc1edb0ff9 /server/lib/activitypub | |
parent | f479685678406a5df864d89615b33d29085ebfc6 (diff) | |
download | PeerTube-2cb03dc1f4e01ba491c36caff30c33fe9c5bad89.tar.gz PeerTube-2cb03dc1f4e01ba491c36caff30c33fe9c5bad89.tar.zst PeerTube-2cb03dc1f4e01ba491c36caff30c33fe9c5bad89.zip |
Add banners support
Diffstat (limited to 'server/lib/activitypub')
-rw-r--r-- | server/lib/activitypub/actor.ts | 106 | ||||
-rw-r--r-- | server/lib/activitypub/process/process-delete.ts | 6 | ||||
-rw-r--r-- | server/lib/activitypub/process/process-update.ts | 14 |
3 files changed, 90 insertions, 36 deletions
diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts index da831dcfd..fe4796a3d 100644 --- a/server/lib/activitypub/actor.ts +++ b/server/lib/activitypub/actor.ts | |||
@@ -4,6 +4,7 @@ import { Op, Transaction } from 'sequelize' | |||
4 | import { URL } from 'url' | 4 | import { URL } from 'url' |
5 | import { v4 as uuidv4 } from 'uuid' | 5 | import { v4 as uuidv4 } from 'uuid' |
6 | import { getServerActor } from '@server/models/application/application' | 6 | import { getServerActor } from '@server/models/application/application' |
7 | import { ActorImageType } from '@shared/models' | ||
7 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | 8 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' |
8 | import { ActivityPubActor, ActivityPubActorType, ActivityPubOrderedCollection } from '../../../shared/models/activitypub' | 9 | import { ActivityPubActor, ActivityPubActorType, ActivityPubOrderedCollection } from '../../../shared/models/activitypub' |
9 | import { ActivityPubAttributedTo } from '../../../shared/models/activitypub/objects' | 10 | import { ActivityPubAttributedTo } from '../../../shared/models/activitypub/objects' |
@@ -30,10 +31,10 @@ import { | |||
30 | MActorAccountChannelId, | 31 | MActorAccountChannelId, |
31 | MActorAccountChannelIdActor, | 32 | MActorAccountChannelIdActor, |
32 | MActorAccountId, | 33 | MActorAccountId, |
33 | MActorDefault, | ||
34 | MActorFull, | 34 | MActorFull, |
35 | MActorFullActor, | 35 | MActorFullActor, |
36 | MActorId, | 36 | MActorId, |
37 | MActorImages, | ||
37 | MChannel | 38 | MChannel |
38 | } from '../../types/models' | 39 | } from '../../types/models' |
39 | import { JobQueue } from '../job-queue' | 40 | import { JobQueue } from '../job-queue' |
@@ -168,43 +169,60 @@ async function updateActorInstance (actorInstance: ActorModel, attributes: Activ | |||
168 | } | 169 | } |
169 | } | 170 | } |
170 | 171 | ||
171 | type AvatarInfo = { name: string, onDisk: boolean, fileUrl: string } | 172 | type AvatarInfo = { name: string, onDisk: boolean, fileUrl: string, type: ActorImageType } |
172 | async function updateActorAvatarInstance (actor: MActorDefault, info: AvatarInfo, t: Transaction) { | 173 | async function updateActorImageInstance (actor: MActorImages, info: AvatarInfo, t: Transaction) { |
173 | if (!info.name) return actor | 174 | if (!info.name) return actor |
174 | 175 | ||
175 | if (actor.Avatar) { | 176 | const oldImageModel = info.type === ActorImageType.AVATAR |
177 | ? actor.Avatar | ||
178 | : actor.Banner | ||
179 | |||
180 | if (oldImageModel) { | ||
176 | // Don't update the avatar if the file URL did not change | 181 | // Don't update the avatar if the file URL did not change |
177 | if (info.fileUrl && actor.Avatar.fileUrl === info.fileUrl) return actor | 182 | if (info.fileUrl && oldImageModel.fileUrl === info.fileUrl) return actor |
178 | 183 | ||
179 | try { | 184 | try { |
180 | await actor.Avatar.destroy({ transaction: t }) | 185 | await oldImageModel.destroy({ transaction: t }) |
181 | } catch (err) { | 186 | } catch (err) { |
182 | logger.error('Cannot remove old avatar of actor %s.', actor.url, { err }) | 187 | logger.error('Cannot remove old actor image of actor %s.', actor.url, { err }) |
183 | } | 188 | } |
184 | } | 189 | } |
185 | 190 | ||
186 | const avatar = await ActorImageModel.create({ | 191 | const imageModel = await ActorImageModel.create({ |
187 | filename: info.name, | 192 | filename: info.name, |
188 | onDisk: info.onDisk, | 193 | onDisk: info.onDisk, |
189 | fileUrl: info.fileUrl | 194 | fileUrl: info.fileUrl, |
195 | type: info.type | ||
190 | }, { transaction: t }) | 196 | }, { transaction: t }) |
191 | 197 | ||
192 | actor.avatarId = avatar.id | 198 | if (info.type === ActorImageType.AVATAR) { |
193 | actor.Avatar = avatar | 199 | actor.avatarId = imageModel.id |
200 | actor.Avatar = imageModel | ||
201 | } else { | ||
202 | actor.bannerId = imageModel.id | ||
203 | actor.Banner = imageModel | ||
204 | } | ||
194 | 205 | ||
195 | return actor | 206 | return actor |
196 | } | 207 | } |
197 | 208 | ||
198 | async function deleteActorAvatarInstance (actor: MActorDefault, t: Transaction) { | 209 | async function deleteActorImageInstance (actor: MActorImages, type: ActorImageType, t: Transaction) { |
199 | try { | 210 | try { |
200 | await actor.Avatar.destroy({ transaction: t }) | 211 | if (type === ActorImageType.AVATAR) { |
212 | await actor.Avatar.destroy({ transaction: t }) | ||
213 | |||
214 | actor.avatarId = null | ||
215 | actor.Avatar = null | ||
216 | } else { | ||
217 | await actor.Banner.destroy({ transaction: t }) | ||
218 | |||
219 | actor.bannerId = null | ||
220 | actor.Banner = null | ||
221 | } | ||
201 | } catch (err) { | 222 | } catch (err) { |
202 | logger.error('Cannot remove old avatar of actor %s.', actor.url, { err }) | 223 | logger.error('Cannot remove old image of actor %s.', actor.url, { err }) |
203 | } | 224 | } |
204 | 225 | ||
205 | actor.avatarId = null | ||
206 | actor.Avatar = null | ||
207 | |||
208 | return actor | 226 | return actor |
209 | } | 227 | } |
210 | 228 | ||
@@ -219,9 +237,11 @@ async function fetchActorTotalItems (url: string) { | |||
219 | } | 237 | } |
220 | } | 238 | } |
221 | 239 | ||
222 | function getAvatarInfoIfExists (actorJSON: ActivityPubActor) { | 240 | function getImageInfoIfExists (actorJSON: ActivityPubActor, type: ActorImageType) { |
223 | const mimetypes = MIMETYPES.IMAGE | 241 | const mimetypes = MIMETYPES.IMAGE |
224 | const icon = actorJSON.icon | 242 | const icon = type === ActorImageType.AVATAR |
243 | ? actorJSON.icon | ||
244 | : actorJSON.image | ||
225 | 245 | ||
226 | if (!icon || icon.type !== 'Image' || !isActivityPubUrlValid(icon.url)) return undefined | 246 | if (!icon || icon.type !== 'Image' || !isActivityPubUrlValid(icon.url)) return undefined |
227 | 247 | ||
@@ -239,7 +259,8 @@ function getAvatarInfoIfExists (actorJSON: ActivityPubActor) { | |||
239 | 259 | ||
240 | return { | 260 | return { |
241 | name: uuidv4() + extension, | 261 | name: uuidv4() + extension, |
242 | fileUrl: icon.url | 262 | fileUrl: icon.url, |
263 | type | ||
243 | } | 264 | } |
244 | } | 265 | } |
245 | 266 | ||
@@ -293,10 +314,22 @@ async function refreshActorIfNeeded <T extends MActorFull | MActorAccountChannel | |||
293 | const avatarInfo = { | 314 | const avatarInfo = { |
294 | name: result.avatar.name, | 315 | name: result.avatar.name, |
295 | fileUrl: result.avatar.fileUrl, | 316 | fileUrl: result.avatar.fileUrl, |
296 | onDisk: false | 317 | onDisk: false, |
318 | type: ActorImageType.AVATAR | ||
319 | } | ||
320 | |||
321 | await updateActorImageInstance(actor, avatarInfo, t) | ||
322 | } | ||
323 | |||
324 | if (result.banner !== undefined) { | ||
325 | const bannerInfo = { | ||
326 | name: result.banner.name, | ||
327 | fileUrl: result.banner.fileUrl, | ||
328 | onDisk: false, | ||
329 | type: ActorImageType.BANNER | ||
297 | } | 330 | } |
298 | 331 | ||
299 | await updateActorAvatarInstance(actor, avatarInfo, t) | 332 | await updateActorImageInstance(actor, bannerInfo, t) |
300 | } | 333 | } |
301 | 334 | ||
302 | // Force update | 335 | // Force update |
@@ -338,11 +371,11 @@ export { | |||
338 | buildActorInstance, | 371 | buildActorInstance, |
339 | generateAndSaveActorKeys, | 372 | generateAndSaveActorKeys, |
340 | fetchActorTotalItems, | 373 | fetchActorTotalItems, |
341 | getAvatarInfoIfExists, | 374 | getImageInfoIfExists, |
342 | updateActorInstance, | 375 | updateActorInstance, |
343 | deleteActorAvatarInstance, | 376 | deleteActorImageInstance, |
344 | refreshActorIfNeeded, | 377 | refreshActorIfNeeded, |
345 | updateActorAvatarInstance, | 378 | updateActorImageInstance, |
346 | addFetchOutboxJob | 379 | addFetchOutboxJob |
347 | } | 380 | } |
348 | 381 | ||
@@ -381,12 +414,25 @@ function saveActorAndServerAndModelIfNotExist ( | |||
381 | const avatar = await ActorImageModel.create({ | 414 | const avatar = await ActorImageModel.create({ |
382 | filename: result.avatar.name, | 415 | filename: result.avatar.name, |
383 | fileUrl: result.avatar.fileUrl, | 416 | fileUrl: result.avatar.fileUrl, |
384 | onDisk: false | 417 | onDisk: false, |
418 | type: ActorImageType.AVATAR | ||
385 | }, { transaction: t }) | 419 | }, { transaction: t }) |
386 | 420 | ||
387 | actor.avatarId = avatar.id | 421 | actor.avatarId = avatar.id |
388 | } | 422 | } |
389 | 423 | ||
424 | // Banner? | ||
425 | if (result.banner) { | ||
426 | const banner = await ActorImageModel.create({ | ||
427 | filename: result.banner.name, | ||
428 | fileUrl: result.banner.fileUrl, | ||
429 | onDisk: false, | ||
430 | type: ActorImageType.BANNER | ||
431 | }, { transaction: t }) | ||
432 | |||
433 | actor.bannerId = banner.id | ||
434 | } | ||
435 | |||
390 | // Force the actor creation, sometimes Sequelize skips the save() when it thinks the instance already exists | 436 | // Force the actor creation, sometimes Sequelize skips the save() when it thinks the instance already exists |
391 | // (which could be false in a retried query) | 437 | // (which could be false in a retried query) |
392 | const [ actorCreated, created ] = await ActorModel.findOrCreate<MActorFullActor>({ | 438 | const [ actorCreated, created ] = await ActorModel.findOrCreate<MActorFullActor>({ |
@@ -440,6 +486,10 @@ type FetchRemoteActorResult = { | |||
440 | name: string | 486 | name: string |
441 | fileUrl: string | 487 | fileUrl: string |
442 | } | 488 | } |
489 | banner?: { | ||
490 | name: string | ||
491 | fileUrl: string | ||
492 | } | ||
443 | attributedTo: ActivityPubAttributedTo[] | 493 | attributedTo: ActivityPubAttributedTo[] |
444 | } | 494 | } |
445 | async function fetchRemoteActor (actorUrl: string): Promise<{ statusCode?: number, result: FetchRemoteActorResult }> { | 495 | async function fetchRemoteActor (actorUrl: string): Promise<{ statusCode?: number, result: FetchRemoteActorResult }> { |
@@ -479,7 +529,8 @@ async function fetchRemoteActor (actorUrl: string): Promise<{ statusCode?: numbe | |||
479 | : null | 529 | : null |
480 | }) | 530 | }) |
481 | 531 | ||
482 | const avatarInfo = await getAvatarInfoIfExists(actorJSON) | 532 | const avatarInfo = getImageInfoIfExists(actorJSON, ActorImageType.AVATAR) |
533 | const bannerInfo = getImageInfoIfExists(actorJSON, ActorImageType.BANNER) | ||
483 | 534 | ||
484 | const name = actorJSON.name || actorJSON.preferredUsername | 535 | const name = actorJSON.name || actorJSON.preferredUsername |
485 | return { | 536 | return { |
@@ -488,6 +539,7 @@ async function fetchRemoteActor (actorUrl: string): Promise<{ statusCode?: numbe | |||
488 | actor, | 539 | actor, |
489 | name, | 540 | name, |
490 | avatar: avatarInfo, | 541 | avatar: avatarInfo, |
542 | banner: bannerInfo, | ||
491 | summary: actorJSON.summary, | 543 | summary: actorJSON.summary, |
492 | support: actorJSON.support, | 544 | support: actorJSON.support, |
493 | playlists: actorJSON.playlists, | 545 | playlists: actorJSON.playlists, |
diff --git a/server/lib/activitypub/process/process-delete.ts b/server/lib/activitypub/process/process-delete.ts index a86def936..070ee0f1d 100644 --- a/server/lib/activitypub/process/process-delete.ts +++ b/server/lib/activitypub/process/process-delete.ts | |||
@@ -7,7 +7,7 @@ import { VideoModel } from '../../../models/video/video' | |||
7 | import { VideoCommentModel } from '../../../models/video/video-comment' | 7 | import { VideoCommentModel } from '../../../models/video/video-comment' |
8 | import { VideoPlaylistModel } from '../../../models/video/video-playlist' | 8 | import { VideoPlaylistModel } from '../../../models/video/video-playlist' |
9 | import { APProcessorOptions } from '../../../types/activitypub-processor.model' | 9 | import { APProcessorOptions } from '../../../types/activitypub-processor.model' |
10 | import { MAccountActor, MActor, MActorSignature, MChannelActor, MChannelActorAccountActor, MCommentOwnerVideo } from '../../../types/models' | 10 | import { MAccountActor, MActor, MActorSignature, MChannelActor, MCommentOwnerVideo } from '../../../types/models' |
11 | import { markCommentAsDeleted } from '../../video-comment' | 11 | import { markCommentAsDeleted } from '../../video-comment' |
12 | import { forwardVideoRelatedActivity } from '../send/utils' | 12 | import { forwardVideoRelatedActivity } from '../send/utils' |
13 | 13 | ||
@@ -30,9 +30,7 @@ async function processDeleteActivity (options: APProcessorOptions<ActivityDelete | |||
30 | } else if (byActorFull.type === 'Group') { | 30 | } else if (byActorFull.type === 'Group') { |
31 | if (!byActorFull.VideoChannel) throw new Error('Actor ' + byActorFull.url + ' is a group but we cannot find it in database.') | 31 | if (!byActorFull.VideoChannel) throw new Error('Actor ' + byActorFull.url + ' is a group but we cannot find it in database.') |
32 | 32 | ||
33 | const channelToDelete = byActorFull.VideoChannel as MChannelActorAccountActor | 33 | const channelToDelete = Object.assign({}, byActorFull.VideoChannel, { Actor: byActorFull }) |
34 | channelToDelete.Actor = byActorFull | ||
35 | |||
36 | return retryTransactionWrapper(processDeleteVideoChannel, channelToDelete) | 34 | return retryTransactionWrapper(processDeleteVideoChannel, channelToDelete) |
37 | } | 35 | } |
38 | } | 36 | } |
diff --git a/server/lib/activitypub/process/process-update.ts b/server/lib/activitypub/process/process-update.ts index 849f70b94..ad3bb392d 100644 --- a/server/lib/activitypub/process/process-update.ts +++ b/server/lib/activitypub/process/process-update.ts | |||
@@ -6,7 +6,7 @@ import { sequelizeTypescript } from '../../../initializers/database' | |||
6 | import { AccountModel } from '../../../models/account/account' | 6 | import { AccountModel } from '../../../models/account/account' |
7 | import { ActorModel } from '../../../models/activitypub/actor' | 7 | import { ActorModel } from '../../../models/activitypub/actor' |
8 | import { VideoChannelModel } from '../../../models/video/video-channel' | 8 | import { VideoChannelModel } from '../../../models/video/video-channel' |
9 | import { getAvatarInfoIfExists, updateActorAvatarInstance, updateActorInstance } from '../actor' | 9 | import { getImageInfoIfExists, updateActorImageInstance, updateActorInstance } from '../actor' |
10 | import { getOrCreateVideoAndAccountAndChannel, getOrCreateVideoChannelFromVideoObject, updateVideoFromAP } from '../videos' | 10 | import { getOrCreateVideoAndAccountAndChannel, getOrCreateVideoChannelFromVideoObject, updateVideoFromAP } from '../videos' |
11 | import { sanitizeAndCheckVideoTorrentObject } from '../../../helpers/custom-validators/activitypub/videos' | 11 | import { sanitizeAndCheckVideoTorrentObject } from '../../../helpers/custom-validators/activitypub/videos' |
12 | import { isCacheFileObjectValid } from '../../../helpers/custom-validators/activitypub/cache-file' | 12 | import { isCacheFileObjectValid } from '../../../helpers/custom-validators/activitypub/cache-file' |
@@ -17,6 +17,7 @@ import { createOrUpdateVideoPlaylist } from '../playlist' | |||
17 | import { APProcessorOptions } from '../../../types/activitypub-processor.model' | 17 | import { APProcessorOptions } from '../../../types/activitypub-processor.model' |
18 | import { MActorSignature, MAccountIdActor } from '../../../types/models' | 18 | import { MActorSignature, MAccountIdActor } from '../../../types/models' |
19 | import { isRedundancyAccepted } from '@server/lib/redundancy' | 19 | import { isRedundancyAccepted } from '@server/lib/redundancy' |
20 | import { ActorImageType } from '@shared/models' | ||
20 | 21 | ||
21 | async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate>) { | 22 | async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate>) { |
22 | const { activity, byActor } = options | 23 | const { activity, byActor } = options |
@@ -119,7 +120,8 @@ async function processUpdateActor (actor: ActorModel, activity: ActivityUpdate) | |||
119 | let accountOrChannelFieldsSave: object | 120 | let accountOrChannelFieldsSave: object |
120 | 121 | ||
121 | // Fetch icon? | 122 | // Fetch icon? |
122 | const avatarInfo = await getAvatarInfoIfExists(actorAttributesToUpdate) | 123 | const avatarInfo = getImageInfoIfExists(actorAttributesToUpdate, ActorImageType.AVATAR) |
124 | const bannerInfo = getImageInfoIfExists(actorAttributesToUpdate, ActorImageType.BANNER) | ||
123 | 125 | ||
124 | try { | 126 | try { |
125 | await sequelizeTypescript.transaction(async t => { | 127 | await sequelizeTypescript.transaction(async t => { |
@@ -132,10 +134,12 @@ async function processUpdateActor (actor: ActorModel, activity: ActivityUpdate) | |||
132 | 134 | ||
133 | await updateActorInstance(actor, actorAttributesToUpdate) | 135 | await updateActorInstance(actor, actorAttributesToUpdate) |
134 | 136 | ||
135 | if (avatarInfo !== undefined) { | 137 | for (const imageInfo of [ avatarInfo, bannerInfo ]) { |
136 | const avatarOptions = Object.assign({}, avatarInfo, { onDisk: false }) | 138 | if (!imageInfo) continue |
137 | 139 | ||
138 | await updateActorAvatarInstance(actor, avatarOptions, t) | 140 | const imageOptions = Object.assign({}, imageInfo, { onDisk: false }) |
141 | |||
142 | await updateActorImageInstance(actor, imageOptions, t) | ||
139 | } | 143 | } |
140 | 144 | ||
141 | await actor.save({ transaction: t }) | 145 | await actor.save({ transaction: t }) |