diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts index 98e944f347..e0715093df 100644 --- a/packages/backend/src/core/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -9,7 +9,8 @@ import { In } from 'typeorm'; import * as mfm from 'mfm-js'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; -import type { MiPartialLocalUser, MiLocalUser, MiPartialRemoteUser, MiRemoteUser, MiUser } from '@/models/User.js'; +import type { MiPartialLocalUser, MiLocalUser, MiPartialRemoteUser, MiRemoteUser, MiUser, MiLocalUserForApPersonRender } from '@/models/User.js'; +import type { MiUserProfileForApPersonRender } from '@/models/UserProfile.js'; import type { IMentionedRemoteUsers, MiNote } from '@/models/Note.js'; import type { MiBlocking } from '@/models/Blocking.js'; import type { MiRelay } from '@/models/Relay.js'; @@ -251,7 +252,7 @@ export class ApRendererService { } @bindThis - public renderKey(user: MiLocalUser, key: MiUserKeypair, postfix?: string): IKey { + public renderKey(user: { id: MiUser['id'] }, key: MiUserKeypair, postfix?: string): IKey { return { id: `${this.config.url}/users/${user.id}${postfix ?? '/publickey'}`, type: 'Key', @@ -449,14 +450,14 @@ export class ApRendererService { } @bindThis - public async renderPerson(user: MiLocalUser) { + public async renderPerson(user: MiLocalUserForApPersonRender) { const id = this.userEntityService.genLocalUserUri(user.id); const isSystem = user.username.includes('.'); const [avatar, banner, profile] = await Promise.all([ user.avatarId ? this.driveFilesRepository.findOneBy({ id: user.avatarId }) : undefined, user.bannerId ? this.driveFilesRepository.findOneBy({ id: user.bannerId }) : undefined, - this.userProfilesRepository.findOneByOrFail({ userId: user.id }), + (this.userProfilesRepository.findOneByOrFail({ userId: user.id }) as Promise), ]); const attachment = profile.fields.map(field => ({ diff --git a/packages/backend/src/misc/prelude/object.ts b/packages/backend/src/misc/prelude/object.ts new file mode 100644 index 0000000000..daa18ce1da --- /dev/null +++ b/packages/backend/src/misc/prelude/object.ts @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export function keys(obj: T): (keyof T)[] { + return Object.keys(obj); +} diff --git a/packages/backend/src/models/User.ts b/packages/backend/src/models/User.ts index 9e2d7a3444..758a2e4b91 100644 --- a/packages/backend/src/models/User.ts +++ b/packages/backend/src/models/User.ts @@ -285,6 +285,24 @@ export type MiPartialRemoteUser = Partial & { uri: string; } +export const miLocalUserKeysUsedForApPersonRender = [ + 'id', + 'username', + 'avatarId', + 'bannerId', + 'emojis', + 'tags', + 'isBot', + 'isCat', + 'name', + 'isLocked', + 'isExplorable', + 'movedToUri', + 'alsoKnownAs', +] as const satisfies (keyof MiLocalUser)[]; + +export type MiLocalUserForApPersonRender = Pick; + export const localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toString().slice(1, -1) } as const; export const passwordSchema = { type: 'string', minLength: 1 } as const; export const nameSchema = { type: 'string', minLength: 1, maxLength: 50 } as const; diff --git a/packages/backend/src/models/UserProfile.ts b/packages/backend/src/models/UserProfile.ts index 7dbe0b3717..cf955f6e79 100644 --- a/packages/backend/src/models/UserProfile.ts +++ b/packages/backend/src/models/UserProfile.ts @@ -287,3 +287,12 @@ export class MiUserProfile { } } } + +export const miUserProfileKeysUsedForApPersonRender = [ + 'fields', + 'description', + 'birthday', + 'location', +] as const satisfies (keyof MiUserProfile)[]; + +export type MiUserProfileForApPersonRender = Pick; diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index a1e2fa5e4c..9111175e47 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -11,6 +11,7 @@ import { JSDOM } from 'jsdom'; import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js'; import { extractHashtags } from '@/misc/extract-hashtags.js'; import * as Acct from '@/misc/acct.js'; +import { keys } from '@/misc/prelude/object.js'; import type { UsersRepository, DriveFilesRepository, UserProfilesRepository, PagesRepository } from '@/models/_.js'; import type { MiLocalUser, MiUser } from '@/models/User.js'; import { birthdaySchema, descriptionSchema, locationSchema, nameSchema } from '@/models/User.js'; @@ -34,6 +35,8 @@ import type { Config } from '@/config.js'; import { safeForSql } from '@/misc/safe-for-sql.js'; import { AvatarDecorationService } from '@/core/AvatarDecorationService.js'; import { notificationRecieveConfig } from '@/models/json-schema/user.js'; +import { miLocalUserKeysUsedForApPersonRender } from '@/models/User.js'; +import { miUserProfileKeysUsedForApPersonRender } from '@/models/UserProfile.js'; import { ApiLoggerService } from '../../ApiLoggerService.js'; import { ApiError } from '../../error.js'; @@ -501,8 +504,13 @@ export default class extends Endpoint { // eslint- this.userFollowingService.acceptAllFollowRequests(user); } - // フォロワーにUpdateを配信 - this.accountUpdateService.publishToFollowers(user.id); + // 連合する必要があるプロパティが変更されている場合はフォロワーにUpdateを配信 + if ( + miLocalUserKeysUsedForApPersonRender.some(k => keys(updates).includes(k)) || + miUserProfileKeysUsedForApPersonRender.some(k => keys(profileUpdates).includes(k)) + ) { + this.accountUpdateService.publishToFollowers(user.id); + } const urls = updatedProfile.fields.filter(x => x.value.startsWith('https://')); for (const url of urls) {