AP audience (visibility) パースの修正 (#5783)
* Refactor audience * audienceのないAP Object 対応 * fix * Update src/remote/activitypub/audience.ts Co-Authored-By: Acid Chicken (硫酸鶏) <root@acid-chicken.com> * Update src/remote/activitypub/audience.ts Co-Authored-By: Acid Chicken (硫酸鶏) <root@acid-chicken.com> Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com> Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
This commit is contained in:
parent
f6987c72ac
commit
711aa564c1
5 changed files with 123 additions and 68 deletions
93
src/remote/activitypub/audience.ts
Normal file
93
src/remote/activitypub/audience.ts
Normal file
|
@ -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<AudienceInfo> {
|
||||||
|
const toGroups = groupingAudience(getApIds(to), actor);
|
||||||
|
const ccGroups = groupingAudience(getApIds(cc), actor);
|
||||||
|
|
||||||
|
const others = unique(concat([toGroups.other, ccGroups.other]));
|
||||||
|
|
||||||
|
const limit = promiseLimit<User | null>(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);
|
||||||
|
}
|
|
@ -1,13 +1,13 @@
|
||||||
import Resolver from '../../resolver';
|
import Resolver from '../../resolver';
|
||||||
import post from '../../../../services/note/create';
|
import post from '../../../../services/note/create';
|
||||||
import { IRemoteUser, User } from '../../../../models/entities/user';
|
import { IRemoteUser } from '../../../../models/entities/user';
|
||||||
import { IAnnounce, getApId, getApIds } from '../../type';
|
import { IAnnounce, getApId } from '../../type';
|
||||||
import { fetchNote, resolveNote } from '../../models/note';
|
import { fetchNote, resolveNote } from '../../models/note';
|
||||||
import { resolvePerson } from '../../models/person';
|
|
||||||
import { apLogger } from '../../logger';
|
import { apLogger } from '../../logger';
|
||||||
import { extractDbHost } from '../../../../misc/convert-host';
|
import { extractDbHost } from '../../../../misc/convert-host';
|
||||||
import { fetchMeta } from '../../../../misc/fetch-meta';
|
import { fetchMeta } from '../../../../misc/fetch-meta';
|
||||||
import { getApLock } from '../../../../misc/app-lock';
|
import { getApLock } from '../../../../misc/app-lock';
|
||||||
|
import { parseAudience } from '../../audience';
|
||||||
|
|
||||||
const logger = apLogger;
|
const logger = apLogger;
|
||||||
|
|
||||||
|
@ -51,42 +51,16 @@ export default async function(resolver: Resolver, actor: IRemoteUser, activity:
|
||||||
|
|
||||||
logger.info(`Creating the (Re)Note: ${uri}`);
|
logger.info(`Creating the (Re)Note: ${uri}`);
|
||||||
|
|
||||||
//#region Visibility
|
const activityAudience = await parseAudience(actor, activity.to, activity.cc);
|
||||||
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
|
|
||||||
|
|
||||||
await post(actor, {
|
await post(actor, {
|
||||||
createdAt: activity.published ? new Date(activity.published) : null,
|
createdAt: activity.published ? new Date(activity.published) : null,
|
||||||
renote,
|
renote,
|
||||||
visibility,
|
visibility: activityAudience.visibility,
|
||||||
visibleUsers,
|
visibleUsers: activityAudience.visibleUsers,
|
||||||
uri
|
uri
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
unlock();
|
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';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ export default async (actor: IRemoteUser, activity: ICreate): Promise<void> => {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (validPost.includes(object.type)) {
|
if (validPost.includes(object.type)) {
|
||||||
createNote(resolver, actor, object);
|
createNote(resolver, actor, object, false, activity);
|
||||||
} else {
|
} else {
|
||||||
logger.warn(`Unknown type: ${object.type}`);
|
logger.warn(`Unknown type: ${object.type}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import Resolver from '../../resolver';
|
import Resolver from '../../resolver';
|
||||||
import { IRemoteUser } from '../../../../models/entities/user';
|
import { IRemoteUser } from '../../../../models/entities/user';
|
||||||
import { createNote, fetchNote } from '../../models/note';
|
import { createNote, fetchNote } from '../../models/note';
|
||||||
import { getApId, IObject } from '../../type';
|
import { getApId, IObject, ICreate } from '../../type';
|
||||||
import { getApLock } from '../../../../misc/app-lock';
|
import { getApLock } from '../../../../misc/app-lock';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 投稿作成アクティビティを捌きます
|
* 投稿作成アクティビティを捌きます
|
||||||
*/
|
*/
|
||||||
export default async function(resolver: Resolver, actor: IRemoteUser, note: IObject, silent = false): Promise<void> {
|
export default async function(resolver: Resolver, actor: IRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise<void> {
|
||||||
const uri = getApId(note);
|
const uri = getApId(note);
|
||||||
|
|
||||||
const unlock = await getApLock(uri);
|
const unlock = await getApLock(uri);
|
||||||
|
@ -15,7 +15,7 @@ export default async function(resolver: Resolver, actor: IRemoteUser, note: IObj
|
||||||
try {
|
try {
|
||||||
const exist = await fetchNote(note);
|
const exist = await fetchNote(note);
|
||||||
if (exist == null) {
|
if (exist == null) {
|
||||||
await createNote(note);
|
await createNote(note, resolver, silent, activity);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
unlock();
|
unlock();
|
||||||
|
|
|
@ -5,10 +5,10 @@ import Resolver from '../resolver';
|
||||||
import post from '../../../services/note/create';
|
import post from '../../../services/note/create';
|
||||||
import { resolvePerson, updatePerson } from './person';
|
import { resolvePerson, updatePerson } from './person';
|
||||||
import { resolveImage } from './image';
|
import { resolveImage } from './image';
|
||||||
import { IRemoteUser, User } from '../../../models/entities/user';
|
import { IRemoteUser } from '../../../models/entities/user';
|
||||||
import { fromHtml } from '../../../mfm/fromHtml';
|
import { fromHtml } from '../../../mfm/fromHtml';
|
||||||
import { ITag, extractHashtags } from './tag';
|
import { ITag, extractHashtags } from './tag';
|
||||||
import { unique, concat, difference } from '../../../prelude/array';
|
import { unique } from '../../../prelude/array';
|
||||||
import { extractPollFromQuestion } from './question';
|
import { extractPollFromQuestion } from './question';
|
||||||
import vote from '../../../services/note/polls/vote';
|
import vote from '../../../services/note/polls/vote';
|
||||||
import { apLogger } from '../logger';
|
import { apLogger } from '../logger';
|
||||||
|
@ -17,13 +17,14 @@ import { deliverQuestionUpdate } from '../../../services/note/polls/update';
|
||||||
import { extractDbHost, toPuny } from '../../../misc/convert-host';
|
import { extractDbHost, toPuny } from '../../../misc/convert-host';
|
||||||
import { Notes, Emojis, Polls } from '../../../models';
|
import { Notes, Emojis, Polls } from '../../../models';
|
||||||
import { Note } from '../../../models/entities/note';
|
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 { Emoji } from '../../../models/entities/emoji';
|
||||||
import { genId } from '../../../misc/gen-id';
|
import { genId } from '../../../misc/gen-id';
|
||||||
import { fetchMeta } from '../../../misc/fetch-meta';
|
import { fetchMeta } from '../../../misc/fetch-meta';
|
||||||
import { ensure } from '../../../prelude/ensure';
|
import { ensure } from '../../../prelude/ensure';
|
||||||
import { getApLock } from '../../../misc/app-lock';
|
import { getApLock } from '../../../misc/app-lock';
|
||||||
import { createMessage } from '../../../services/messages/create';
|
import { createMessage } from '../../../services/messages/create';
|
||||||
|
import { parseAudience } from '../audience';
|
||||||
|
|
||||||
const logger = apLogger;
|
const logger = apLogger;
|
||||||
|
|
||||||
|
@ -77,7 +78,7 @@ export async function fetchNote(value: string | IObject, resolver?: Resolver): P
|
||||||
/**
|
/**
|
||||||
* Noteを作成します。
|
* Noteを作成します。
|
||||||
*/
|
*/
|
||||||
export async function createNote(value: string | IObject, resolver?: Resolver, silent = false): Promise<Note | null> {
|
export async function createNote(value: string | IObject, resolver?: Resolver, silent = false, activity?: ICreate): Promise<Note | null> {
|
||||||
if (resolver == null) resolver = new Resolver();
|
if (resolver == null) resolver = new Resolver();
|
||||||
|
|
||||||
const object: any = await resolver.resolve(value);
|
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');
|
throw new Error('actor has been suspended');
|
||||||
}
|
}
|
||||||
|
|
||||||
//#region Visibility
|
const noteAudience = await parseAudience(actor, note.to, note.cc);
|
||||||
const to = getApIds(note.to);
|
let visibility = noteAudience.visibility;
|
||||||
const cc = getApIds(note.cc);
|
let visibleUsers = noteAudience.visibleUsers;
|
||||||
|
let apMentions = noteAudience.mentionedUsers;
|
||||||
|
|
||||||
let visibility = 'public';
|
// Audience (to, cc) が指定されてなかった場合
|
||||||
let visibleUsers: User[] = [];
|
if (visibility === 'specified' && visibleUsers.length === 0) {
|
||||||
if (!to.includes('https://www.w3.org/ns/activitystreams#Public')) {
|
if (activity && isCreate(activity)) {
|
||||||
if (cc.includes('https://www.w3.org/ns/activitystreams#Public')) {
|
// Create 起因ならば Activity を見る
|
||||||
visibility = 'home';
|
const activityAudience = await parseAudience(actor, activity.to, activity.cc);
|
||||||
} else if (to.includes(`${actor.uri}/followers`)) { // TODO: person.followerと照合するべき?
|
visibility = activityAudience.visibility;
|
||||||
visibility = 'followers';
|
visibleUsers = activityAudience.visibleUsers;
|
||||||
} else {
|
apMentions = activityAudience.mentionedUsers;
|
||||||
visibility = 'specified';
|
} else if (typeof value === 'string') { // 入力がstringならばresolverでGETが発生している
|
||||||
visibleUsers = await Promise.all(to.map(uri => resolvePerson(uri, resolver)));
|
// こちらから匿名GET出来たものならばpublic
|
||||||
|
visibility = 'public';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//#endergion
|
|
||||||
|
|
||||||
const apMentions = await extractMentionedUsers(actor, to, cc, resolver);
|
|
||||||
|
|
||||||
const apHashtags = await extractHashtags(note.tag);
|
const apHashtags = await extractHashtags(note.tag);
|
||||||
|
|
||||||
|
@ -363,15 +363,3 @@ export async function extractEmojis(tags: ITag[], host: string): Promise<Emoji[]
|
||||||
} as Partial<Emoji>);
|
} as Partial<Emoji>);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
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<User | null>(2);
|
|
||||||
const users = await Promise.all(
|
|
||||||
uris.map(uri => limit(() => resolvePerson(uri, resolver).catch(() => null)) as Promise<User | null>)
|
|
||||||
);
|
|
||||||
|
|
||||||
return users.filter(x => x != null) as User[];
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue