diff options
Diffstat (limited to 'server/lib/activitypub/actor.ts')
-rw-r--r-- | server/lib/activitypub/actor.ts | 106 |
1 files changed, 79 insertions, 27 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, |