aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/lib/activitypub
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2021-04-06 17:01:35 +0200
committerChocobozzz <chocobozzz@cpy.re>2021-04-08 10:07:53 +0200
commit2cb03dc1f4e01ba491c36caff30c33fe9c5bad89 (patch)
tree08a8706d105ea1e280339c02b9e2b1dc1edb0ff9 /server/lib/activitypub
parentf479685678406a5df864d89615b33d29085ebfc6 (diff)
downloadPeerTube-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.ts106
-rw-r--r--server/lib/activitypub/process/process-delete.ts6
-rw-r--r--server/lib/activitypub/process/process-update.ts14
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'
4import { URL } from 'url' 4import { URL } from 'url'
5import { v4 as uuidv4 } from 'uuid' 5import { v4 as uuidv4 } from 'uuid'
6import { getServerActor } from '@server/models/application/application' 6import { getServerActor } from '@server/models/application/application'
7import { ActorImageType } from '@shared/models'
7import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' 8import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
8import { ActivityPubActor, ActivityPubActorType, ActivityPubOrderedCollection } from '../../../shared/models/activitypub' 9import { ActivityPubActor, ActivityPubActorType, ActivityPubOrderedCollection } from '../../../shared/models/activitypub'
9import { ActivityPubAttributedTo } from '../../../shared/models/activitypub/objects' 10import { 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'
39import { JobQueue } from '../job-queue' 40import { JobQueue } from '../job-queue'
@@ -168,43 +169,60 @@ async function updateActorInstance (actorInstance: ActorModel, attributes: Activ
168 } 169 }
169} 170}
170 171
171type AvatarInfo = { name: string, onDisk: boolean, fileUrl: string } 172type AvatarInfo = { name: string, onDisk: boolean, fileUrl: string, type: ActorImageType }
172async function updateActorAvatarInstance (actor: MActorDefault, info: AvatarInfo, t: Transaction) { 173async 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
198async function deleteActorAvatarInstance (actor: MActorDefault, t: Transaction) { 209async 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
222function getAvatarInfoIfExists (actorJSON: ActivityPubActor) { 240function 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}
445async function fetchRemoteActor (actorUrl: string): Promise<{ statusCode?: number, result: FetchRemoteActorResult }> { 495async 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'
7import { VideoCommentModel } from '../../../models/video/video-comment' 7import { VideoCommentModel } from '../../../models/video/video-comment'
8import { VideoPlaylistModel } from '../../../models/video/video-playlist' 8import { VideoPlaylistModel } from '../../../models/video/video-playlist'
9import { APProcessorOptions } from '../../../types/activitypub-processor.model' 9import { APProcessorOptions } from '../../../types/activitypub-processor.model'
10import { MAccountActor, MActor, MActorSignature, MChannelActor, MChannelActorAccountActor, MCommentOwnerVideo } from '../../../types/models' 10import { MAccountActor, MActor, MActorSignature, MChannelActor, MCommentOwnerVideo } from '../../../types/models'
11import { markCommentAsDeleted } from '../../video-comment' 11import { markCommentAsDeleted } from '../../video-comment'
12import { forwardVideoRelatedActivity } from '../send/utils' 12import { 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'
6import { AccountModel } from '../../../models/account/account' 6import { AccountModel } from '../../../models/account/account'
7import { ActorModel } from '../../../models/activitypub/actor' 7import { ActorModel } from '../../../models/activitypub/actor'
8import { VideoChannelModel } from '../../../models/video/video-channel' 8import { VideoChannelModel } from '../../../models/video/video-channel'
9import { getAvatarInfoIfExists, updateActorAvatarInstance, updateActorInstance } from '../actor' 9import { getImageInfoIfExists, updateActorImageInstance, updateActorInstance } from '../actor'
10import { getOrCreateVideoAndAccountAndChannel, getOrCreateVideoChannelFromVideoObject, updateVideoFromAP } from '../videos' 10import { getOrCreateVideoAndAccountAndChannel, getOrCreateVideoChannelFromVideoObject, updateVideoFromAP } from '../videos'
11import { sanitizeAndCheckVideoTorrentObject } from '../../../helpers/custom-validators/activitypub/videos' 11import { sanitizeAndCheckVideoTorrentObject } from '../../../helpers/custom-validators/activitypub/videos'
12import { isCacheFileObjectValid } from '../../../helpers/custom-validators/activitypub/cache-file' 12import { isCacheFileObjectValid } from '../../../helpers/custom-validators/activitypub/cache-file'
@@ -17,6 +17,7 @@ import { createOrUpdateVideoPlaylist } from '../playlist'
17import { APProcessorOptions } from '../../../types/activitypub-processor.model' 17import { APProcessorOptions } from '../../../types/activitypub-processor.model'
18import { MActorSignature, MAccountIdActor } from '../../../types/models' 18import { MActorSignature, MAccountIdActor } from '../../../types/models'
19import { isRedundancyAccepted } from '@server/lib/redundancy' 19import { isRedundancyAccepted } from '@server/lib/redundancy'
20import { ActorImageType } from '@shared/models'
20 21
21async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate>) { 22async 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 })