import { Injectable } from '@nestjs/common'; import promiseLimit from 'promise-limit'; import type { RemoteUser, User } from '@/models/entities/User.js'; import { concat, unique } from '@/misc/prelude/array.js'; import { bindThis } from '@/decorators.js'; import { getApIds } from './type.js'; import { ApPersonService } from './models/ApPersonService.js'; import type { ApObject } from './type.js'; import type { Resolver } from './ApResolverService.js'; type Visibility = 'public' | 'home' | 'followers' | 'specified'; type AudienceInfo = { visibility: Visibility, mentionedUsers: User[], visibleUsers: User[], }; @Injectable() export class ApAudienceService { constructor( private apPersonService: ApPersonService, ) { } @bindThis public async parseAudience(actor: RemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise { const toGroups = this.groupingAudience(getApIds(to), actor); const ccGroups = this.groupingAudience(getApIds(cc), actor); const others = unique(concat([toGroups.other, ccGroups.other])); const limit = promiseLimit(2); const mentionedUsers = (await Promise.all( others.map(id => limit(() => this.apPersonService.resolvePerson(id, resolver).catch(() => null))), )).filter((x): x is User => x != null); if (toGroups.public.length > 0) { return { visibility: 'public', mentionedUsers, visibleUsers: [], }; } if (ccGroups.public.length > 0) { return { visibility: 'home', mentionedUsers, visibleUsers: [], }; } if (toGroups.followers.length > 0) { return { visibility: 'followers', mentionedUsers, visibleUsers: [], }; } return { visibility: 'specified', mentionedUsers, visibleUsers: mentionedUsers, }; } @bindThis private groupingAudience(ids: string[], actor: RemoteUser) { const groups = { public: [] as string[], followers: [] as string[], other: [] as string[], }; for (const id of ids) { if (this.isPublic(id)) { groups.public.push(id); } else if (this.isFollowers(id, actor)) { groups.followers.push(id); } else { groups.other.push(id); } } groups.other = unique(groups.other); return groups; } @bindThis private isPublic(id: string) { return [ 'https://www.w3.org/ns/activitystreams#Public', 'as#Public', 'Public', ].includes(id); } @bindThis private isFollowers(id: string, actor: RemoteUser) { return ( id === (actor.followersUri ?? `${actor.uri}/followers`) ); } }