From 711aa564c1cf219f860e1e96ab4f7efb5e3d140e Mon Sep 17 00:00:00 2001 From: MeiMei <30769358+mei23@users.noreply.github.com> Date: Thu, 30 Jan 2020 18:58:13 +0900 Subject: [PATCH] =?UTF-8?q?AP=20audience=20(visibility)=20=E3=83=91?= =?UTF-8?q?=E3=83=BC=E3=82=B9=E3=81=AE=E4=BF=AE=E6=AD=A3=20(#5783)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Refactor audience * audienceのないAP Object 対応 * fix * Update src/remote/activitypub/audience.ts Co-Authored-By: Acid Chicken (硫酸鶏) * Update src/remote/activitypub/audience.ts Co-Authored-By: Acid Chicken (硫酸鶏) Co-authored-by: Acid Chicken (硫酸鶏) Co-authored-by: syuilo --- src/remote/activitypub/audience.ts | 93 +++++++++++++++++++ .../activitypub/kernel/announce/note.ts | 38 ++------ src/remote/activitypub/kernel/create/index.ts | 2 +- src/remote/activitypub/kernel/create/note.ts | 6 +- src/remote/activitypub/models/note.ts | 52 ++++------- 5 files changed, 123 insertions(+), 68 deletions(-) create mode 100644 src/remote/activitypub/audience.ts diff --git a/src/remote/activitypub/audience.ts b/src/remote/activitypub/audience.ts new file mode 100644 index 000000000..7cff678ae --- /dev/null +++ b/src/remote/activitypub/audience.ts @@ -0,0 +1,93 @@ +import { ApObject, getApIds } from './type'; +import Resolver from './resolver'; +import { resolvePerson } from './models/person'; +import { unique, concat } from '../../prelude/array'; +import * as promiseLimit from 'promise-limit'; +import { User, IRemoteUser } from '../../models/entities/user'; + +type Visibility = 'public' | 'home' | 'followers' | 'specified'; + +type AudienceInfo = { + visibility: Visibility, + mentionedUsers: User[], + visibleUsers: User[], +}; + +export async function parseAudience(actor: IRemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise { + const toGroups = groupingAudience(getApIds(to), actor); + const ccGroups = 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(() => 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 + }; +} + +function groupingAudience(ids: string[], actor: IRemoteUser) { + const groups = { + public: [] as string[], + followers: [] as string[], + other: [] as string[], + }; + + for (const id of ids) { + if (isPublic(id)) { + groups.public.push(id); + } else if (isFollowers(id, actor)) { + groups.followers.push(id); + } else { + groups.other.push(id); + } + } + + groups.other = unique(groups.other); + + return groups; +} + +function isPublic(id: string) { + return [ + 'https://www.w3.org/ns/activitystreams#Public', + 'as#Public', + 'Public', + ].includes(id); +} + +function isFollowers(id: string, actor: IRemoteUser) { + return [ + `${actor.uri}/followers`, + // actor.followerUri, // TODO + ].includes(id); +} diff --git a/src/remote/activitypub/kernel/announce/note.ts b/src/remote/activitypub/kernel/announce/note.ts index f22ed77d4..765180742 100644 --- a/src/remote/activitypub/kernel/announce/note.ts +++ b/src/remote/activitypub/kernel/announce/note.ts @@ -1,13 +1,13 @@ import Resolver from '../../resolver'; import post from '../../../../services/note/create'; -import { IRemoteUser, User } from '../../../../models/entities/user'; -import { IAnnounce, getApId, getApIds } from '../../type'; +import { IRemoteUser } from '../../../../models/entities/user'; +import { IAnnounce, getApId } from '../../type'; import { fetchNote, resolveNote } from '../../models/note'; -import { resolvePerson } from '../../models/person'; import { apLogger } from '../../logger'; import { extractDbHost } from '../../../../misc/convert-host'; import { fetchMeta } from '../../../../misc/fetch-meta'; import { getApLock } from '../../../../misc/app-lock'; +import { parseAudience } from '../../audience'; const logger = apLogger; @@ -51,42 +51,16 @@ export default async function(resolver: Resolver, actor: IRemoteUser, activity: logger.info(`Creating the (Re)Note: ${uri}`); - //#region Visibility - const to = getApIds(activity.to); - const cc = getApIds(activity.cc); - - const visibility = getVisibility(to, cc, actor); - - let visibleUsers: User[] = []; - if (visibility == 'specified') { - visibleUsers = await Promise.all(to.map(uri => resolvePerson(uri))); - } - //#endergion + const activityAudience = await parseAudience(actor, activity.to, activity.cc); await post(actor, { createdAt: activity.published ? new Date(activity.published) : null, renote, - visibility, - visibleUsers, + visibility: activityAudience.visibility, + visibleUsers: activityAudience.visibleUsers, uri }); } finally { unlock(); } } - -type visibility = 'public' | 'home' | 'followers' | 'specified'; - -function getVisibility(to: string[], cc: string[], actor: IRemoteUser): visibility { - const PUBLIC = 'https://www.w3.org/ns/activitystreams#Public'; - - if (to.includes(PUBLIC)) { - return 'public'; - } else if (cc.includes(PUBLIC)) { - return 'home'; - } else if (to.includes(`${actor.uri}/followers`)) { - return 'followers'; - } else { - return 'specified'; - } -} diff --git a/src/remote/activitypub/kernel/create/index.ts b/src/remote/activitypub/kernel/create/index.ts index 395139bb7..5210afe28 100644 --- a/src/remote/activitypub/kernel/create/index.ts +++ b/src/remote/activitypub/kernel/create/index.ts @@ -19,7 +19,7 @@ export default async (actor: IRemoteUser, activity: ICreate): Promise => { }); if (validPost.includes(object.type)) { - createNote(resolver, actor, object); + createNote(resolver, actor, object, false, activity); } else { logger.warn(`Unknown type: ${object.type}`); } diff --git a/src/remote/activitypub/kernel/create/note.ts b/src/remote/activitypub/kernel/create/note.ts index 6ccaa17ef..e39344016 100644 --- a/src/remote/activitypub/kernel/create/note.ts +++ b/src/remote/activitypub/kernel/create/note.ts @@ -1,13 +1,13 @@ import Resolver from '../../resolver'; import { IRemoteUser } from '../../../../models/entities/user'; import { createNote, fetchNote } from '../../models/note'; -import { getApId, IObject } from '../../type'; +import { getApId, IObject, ICreate } from '../../type'; import { getApLock } from '../../../../misc/app-lock'; /** * 投稿作成アクティビティを捌きます */ -export default async function(resolver: Resolver, actor: IRemoteUser, note: IObject, silent = false): Promise { +export default async function(resolver: Resolver, actor: IRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise { const uri = getApId(note); const unlock = await getApLock(uri); @@ -15,7 +15,7 @@ export default async function(resolver: Resolver, actor: IRemoteUser, note: IObj try { const exist = await fetchNote(note); if (exist == null) { - await createNote(note); + await createNote(note, resolver, silent, activity); } } finally { unlock(); diff --git a/src/remote/activitypub/models/note.ts b/src/remote/activitypub/models/note.ts index d5c83208b..6e1f468f6 100644 --- a/src/remote/activitypub/models/note.ts +++ b/src/remote/activitypub/models/note.ts @@ -5,10 +5,10 @@ import Resolver from '../resolver'; import post from '../../../services/note/create'; import { resolvePerson, updatePerson } from './person'; import { resolveImage } from './image'; -import { IRemoteUser, User } from '../../../models/entities/user'; +import { IRemoteUser } from '../../../models/entities/user'; import { fromHtml } from '../../../mfm/fromHtml'; import { ITag, extractHashtags } from './tag'; -import { unique, concat, difference } from '../../../prelude/array'; +import { unique } from '../../../prelude/array'; import { extractPollFromQuestion } from './question'; import vote from '../../../services/note/polls/vote'; import { apLogger } from '../logger'; @@ -17,13 +17,14 @@ import { deliverQuestionUpdate } from '../../../services/note/polls/update'; import { extractDbHost, toPuny } from '../../../misc/convert-host'; import { Notes, Emojis, Polls } from '../../../models'; import { Note } from '../../../models/entities/note'; -import { IObject, IPost, getApIds, getOneApId, getApId, validPost } from '../type'; +import { IObject, INote, getOneApId, getApId, validPost, ICreate, isCreate } from '../type'; import { Emoji } from '../../../models/entities/emoji'; import { genId } from '../../../misc/gen-id'; import { fetchMeta } from '../../../misc/fetch-meta'; import { ensure } from '../../../prelude/ensure'; import { getApLock } from '../../../misc/app-lock'; import { createMessage } from '../../../services/messages/create'; +import { parseAudience } from '../audience'; const logger = apLogger; @@ -77,7 +78,7 @@ export async function fetchNote(value: string | IObject, resolver?: Resolver): P /** * Noteを作成します。 */ -export async function createNote(value: string | IObject, resolver?: Resolver, silent = false): Promise { +export async function createNote(value: string | IObject, resolver?: Resolver, silent = false, activity?: ICreate): Promise { if (resolver == null) resolver = new Resolver(); const object: any = await resolver.resolve(value); @@ -109,25 +110,24 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s throw new Error('actor has been suspended'); } - //#region Visibility - const to = getApIds(note.to); - const cc = getApIds(note.cc); + const noteAudience = await parseAudience(actor, note.to, note.cc); + let visibility = noteAudience.visibility; + let visibleUsers = noteAudience.visibleUsers; + let apMentions = noteAudience.mentionedUsers; - let visibility = 'public'; - let visibleUsers: User[] = []; - if (!to.includes('https://www.w3.org/ns/activitystreams#Public')) { - if (cc.includes('https://www.w3.org/ns/activitystreams#Public')) { - visibility = 'home'; - } else if (to.includes(`${actor.uri}/followers`)) { // TODO: person.followerと照合するべき? - visibility = 'followers'; - } else { - visibility = 'specified'; - visibleUsers = await Promise.all(to.map(uri => resolvePerson(uri, resolver))); + // Audience (to, cc) が指定されてなかった場合 + if (visibility === 'specified' && visibleUsers.length === 0) { + if (activity && isCreate(activity)) { + // Create 起因ならば Activity を見る + const activityAudience = await parseAudience(actor, activity.to, activity.cc); + visibility = activityAudience.visibility; + visibleUsers = activityAudience.visibleUsers; + apMentions = activityAudience.mentionedUsers; + } else if (typeof value === 'string') { // 入力がstringならばresolverでGETが発生している + // こちらから匿名GET出来たものならばpublic + visibility = 'public'; } } - //#endergion - - const apMentions = await extractMentionedUsers(actor, to, cc, resolver); const apHashtags = await extractHashtags(note.tag); @@ -363,15 +363,3 @@ export async function extractEmojis(tags: ITag[], host: string): Promise); })); } - -async function extractMentionedUsers(actor: IRemoteUser, to: string[], cc: string[], resolver: Resolver) { - const ignoreUris = ['https://www.w3.org/ns/activitystreams#Public', `${actor.uri}/followers`]; - const uris = difference(unique(concat([to || [], cc || []])), ignoreUris); - - const limit = promiseLimit(2); - const users = await Promise.all( - uris.map(uri => limit(() => resolvePerson(uri, resolver).catch(() => null)) as Promise) - ); - - return users.filter(x => x != null) as User[]; -}