fix(backend): check visibility of following/followers of remote users / feat: moderators can see following/followers of all users (#14375)
* fix(backend): check visibility of following/followers of remote users Resolves https://github.com/misskey-dev/misskey/issues/13362. * test(backend): add tests for visibility of following/followers of remote users * docs(changelog): update CHANGELOG.md * feat: moderators can see following/followers of all users * docs(changelog): update CHANGELOG.md * refactor(backend): minor refactoring `createPerson`と`if`の条件を統一するとともに、異常系の 処理をearly returnに追い出すための変更。 * feat(backend): moderators can see following/followers count of all users As per https://github.com/misskey-dev/misskey/pull/14375#issuecomment-2275044908.
This commit is contained in:
@@ -48,7 +48,7 @@ import type { ApResolverService, Resolver } from '../ApResolverService.js';
|
||||
import type { ApLoggerService } from '../ApLoggerService.js';
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||
import type { ApImageService } from './ApImageService.js';
|
||||
import type { IActor, IObject } from '../type.js';
|
||||
import type { IActor, ICollection, IObject, IOrderedCollection } from '../type.js';
|
||||
|
||||
const nameLength = 128;
|
||||
const summaryLength = 2048;
|
||||
@@ -296,6 +296,21 @@ export class ApPersonService implements OnModuleInit {
|
||||
|
||||
const isBot = getApType(object) === 'Service' || getApType(object) === 'Application';
|
||||
|
||||
const [followingVisibility, followersVisibility] = await Promise.all(
|
||||
[
|
||||
this.isPublicCollection(person.following, resolver),
|
||||
this.isPublicCollection(person.followers, resolver),
|
||||
].map((p): Promise<'public' | 'private'> => p
|
||||
.then(isPublic => isPublic ? 'public' : 'private')
|
||||
.catch(err => {
|
||||
if (!(err instanceof StatusError) || err.isRetryable) {
|
||||
this.logger.error('error occurred while fetching following/followers collection', { stack: err });
|
||||
}
|
||||
return 'private';
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/);
|
||||
|
||||
const url = getOneApHrefNullable(person.url);
|
||||
@@ -357,6 +372,8 @@ export class ApPersonService implements OnModuleInit {
|
||||
description: _description,
|
||||
url,
|
||||
fields,
|
||||
followingVisibility,
|
||||
followersVisibility,
|
||||
birthday: bday?.[0] ?? null,
|
||||
location: person['vcard:Address'] ?? null,
|
||||
userHost: host,
|
||||
@@ -464,6 +481,23 @@ export class ApPersonService implements OnModuleInit {
|
||||
|
||||
const tags = extractApHashtags(person.tag).map(normalizeForSearch).splice(0, 32);
|
||||
|
||||
const [followingVisibility, followersVisibility] = await Promise.all(
|
||||
[
|
||||
this.isPublicCollection(person.following, resolver),
|
||||
this.isPublicCollection(person.followers, resolver),
|
||||
].map((p): Promise<'public' | 'private' | undefined> => p
|
||||
.then(isPublic => isPublic ? 'public' : 'private')
|
||||
.catch(err => {
|
||||
if (!(err instanceof StatusError) || err.isRetryable) {
|
||||
this.logger.error('error occurred while fetching following/followers collection', { stack: err });
|
||||
// Do not update the visibiility on transient errors.
|
||||
return undefined;
|
||||
}
|
||||
return 'private';
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/);
|
||||
|
||||
const url = getOneApHrefNullable(person.url);
|
||||
@@ -532,6 +566,8 @@ export class ApPersonService implements OnModuleInit {
|
||||
url,
|
||||
fields,
|
||||
description: _description,
|
||||
followingVisibility,
|
||||
followersVisibility,
|
||||
birthday: bday?.[0] ?? null,
|
||||
location: person['vcard:Address'] ?? null,
|
||||
});
|
||||
@@ -703,4 +739,16 @@ export class ApPersonService implements OnModuleInit {
|
||||
|
||||
return 'ok';
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async isPublicCollection(collection: string | ICollection | IOrderedCollection | undefined, resolver: Resolver): Promise<boolean> {
|
||||
if (collection) {
|
||||
const resolved = await resolver.resolveCollection(collection);
|
||||
if (resolved.first || (resolved as ICollection).items || (resolved as IOrderedCollection).orderedItems) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@@ -97,13 +97,15 @@ export interface IActivity extends IObject {
|
||||
export interface ICollection extends IObject {
|
||||
type: 'Collection';
|
||||
totalItems: number;
|
||||
items: ApObject;
|
||||
first?: IObject | string;
|
||||
items?: ApObject;
|
||||
}
|
||||
|
||||
export interface IOrderedCollection extends IObject {
|
||||
type: 'OrderedCollection';
|
||||
totalItems: number;
|
||||
orderedItems: ApObject;
|
||||
first?: IObject | string;
|
||||
orderedItems?: ApObject;
|
||||
}
|
||||
|
||||
export const validPost = ['Note', 'Question', 'Article', 'Audio', 'Document', 'Image', 'Page', 'Video', 'Event'];
|
||||
|
@@ -454,12 +454,12 @@ export class UserEntityService implements OnModuleInit {
|
||||
}
|
||||
|
||||
const followingCount = profile == null ? null :
|
||||
(profile.followingVisibility === 'public') || isMe ? user.followingCount :
|
||||
(profile.followingVisibility === 'public') || isMe || iAmModerator ? user.followingCount :
|
||||
(profile.followingVisibility === 'followers') && (relation && relation.isFollowing) ? user.followingCount :
|
||||
null;
|
||||
|
||||
const followersCount = profile == null ? null :
|
||||
(profile.followersVisibility === 'public') || isMe ? user.followersCount :
|
||||
(profile.followersVisibility === 'public') || isMe || iAmModerator ? user.followersCount :
|
||||
(profile.followersVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount :
|
||||
null;
|
||||
|
||||
|
Reference in New Issue
Block a user