AP featured collectionの修正 / Collection Activityの対応 / typeの修正など (#5460)

* resolver type / fix updateFeatured

* type ApObject

* fix strange type

* AP Activity

* Collection Activityが失敗したらとりあえず無視
This commit is contained in:
MeiMei 2019-09-27 04:58:28 +09:00 committed by syuilo
parent 3a093f8bd7
commit e14509574d
10 changed files with 85 additions and 106 deletions

View file

@ -120,3 +120,11 @@ export function cumulativeSum(xs: number[]): number[] {
export function fromEntries(xs: [string, any][]): { [x: string]: any; } { export function fromEntries(xs: [string, any][]): { [x: string]: any; } {
return xs.reduce((obj, [k, v]) => Object.assign(obj, { [k]: v }), {} as { [x: string]: any; }); return xs.reduce((obj, [k, v]) => Object.assign(obj, { [k]: v }), {} as { [x: string]: any; });
} }
export function toArray<T>(x: T | T[] | undefined): T[] {
return Array.isArray(x) ? x : x != null ? [x] : [];
}
export function toSingle<T>(x: T | T[] | undefined): T | undefined {
return Array.isArray(x) ? x[0] : x;
}

View file

@ -1,7 +1,7 @@
import Resolver from '../../resolver'; import Resolver from '../../resolver';
import { IRemoteUser } from '../../../../models/entities/user'; import { IRemoteUser } from '../../../../models/entities/user';
import announceNote from './note'; import announceNote from './note';
import { IAnnounce, INote, validPost, getApId } from '../../type'; import { IAnnounce, validPost, getApId } from '../../type';
import { apLogger } from '../../logger'; import { apLogger } from '../../logger';
const logger = apLogger; const logger = apLogger;
@ -23,7 +23,7 @@ export default async (actor: IRemoteUser, activity: IAnnounce): Promise<void> =>
} }
if (validPost.includes(object.type)) { if (validPost.includes(object.type)) {
announceNote(resolver, actor, activity, object as INote); announceNote(resolver, actor, activity, object);
} else { } else {
logger.warn(`Unknown announce type: ${object.type}`); logger.warn(`Unknown announce type: ${object.type}`);
} }

View file

@ -1,7 +1,7 @@
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, User } from '../../../../models/entities/user';
import { IAnnounce, INote, getApId, getApIds } from '../../type'; import { IAnnounce, IObject, getApId, getApIds } from '../../type';
import { fetchNote, resolveNote } from '../../models/note'; import { fetchNote, resolveNote } from '../../models/note';
import { resolvePerson } from '../../models/person'; import { resolvePerson } from '../../models/person';
import { apLogger } from '../../logger'; import { apLogger } from '../../logger';
@ -14,7 +14,7 @@ const logger = apLogger;
/** /**
* *
*/ */
export default async function(resolver: Resolver, actor: IRemoteUser, activity: IAnnounce, note: INote): Promise<void> { export default async function(resolver: Resolver, actor: IRemoteUser, activity: IAnnounce, note: IObject): Promise<void> {
const uri = getApId(activity); const uri = getApId(activity);
// アナウンサーが凍結されていたらスキップ // アナウンサーが凍結されていたらスキップ

View file

@ -1,5 +1,5 @@
import config from '../../../../config'; import config from '../../../../config';
import { IBlock } from '../../type'; import { IBlock, getApId } from '../../type';
import block from '../../../../services/blocking/create'; import block from '../../../../services/blocking/create';
import { apLogger } from '../../logger'; import { apLogger } from '../../logger';
import { Users } from '../../../../models'; import { Users } from '../../../../models';
@ -8,10 +8,9 @@ import { IRemoteUser } from '../../../../models/entities/user';
const logger = apLogger; const logger = apLogger;
export default async (actor: IRemoteUser, activity: IBlock): Promise<void> => { export default async (actor: IRemoteUser, activity: IBlock): Promise<void> => {
const id = typeof activity.object == 'string' ? activity.object : activity.object.id; const id = getApId(activity.object);
if (id == null) throw new Error('missing id');
const uri = activity.id || activity; const uri = getApId(activity);
logger.info(`Block: ${uri}`); logger.info(`Block: ${uri}`);

View file

@ -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 } from '../../type'; import { getApId, IObject } from '../../type';
import { getApLock } from '../../../../misc/app-lock'; import { getApLock } from '../../../../misc/app-lock';
/** /**
* 稿 * 稿
*/ */
export default async function(resolver: Resolver, actor: IRemoteUser, note: any, silent = false): Promise<void> { export default async function(resolver: Resolver, actor: IRemoteUser, note: IObject, silent = false): Promise<void> {
const uri = getApId(note); const uri = getApId(note);
const unlock = await getApLock(uri); const unlock = await getApLock(uri);

View file

@ -1,4 +1,4 @@
import { Object } from '../type'; import { IObject, isCreate, isDelete, isUpdate, isFollow, isAccept, isReject, isAdd, isRemove, isAnnounce, isLike, isUndo, isBlock, isCollectionOrOrderedCollection, isCollection } from '../type';
import { IRemoteUser } from '../../../models/entities/user'; import { IRemoteUser } from '../../../models/entities/user';
import create from './create'; import create from './create';
import performDeleteActivity from './delete'; import performDeleteActivity from './delete';
@ -13,68 +13,53 @@ import add from './add';
import remove from './remove'; import remove from './remove';
import block from './block'; import block from './block';
import { apLogger } from '../logger'; import { apLogger } from '../logger';
import Resolver from '../resolver';
import { toArray } from '../../../prelude/array';
const self = async (actor: IRemoteUser, activity: Object): Promise<void> => { export async function performActivity(actor: IRemoteUser, activity: IObject) {
if (isCollectionOrOrderedCollection(activity)) {
const resolver = new Resolver();
for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) {
const act = await resolver.resolve(item);
try {
await performOneActivity(actor, act);
} catch (e) {
apLogger.error(e);
}
}
} else {
await performOneActivity(actor, activity);
}
}
async function performOneActivity(actor: IRemoteUser, activity: IObject): Promise<void> {
if (actor.isSuspended) return; if (actor.isSuspended) return;
switch (activity.type) { if (isCreate(activity)) {
case 'Create':
await create(actor, activity); await create(actor, activity);
break; } else if (isDelete(activity)) {
case 'Delete':
await performDeleteActivity(actor, activity); await performDeleteActivity(actor, activity);
break; } else if (isUpdate(activity)) {
case 'Update':
await performUpdateActivity(actor, activity); await performUpdateActivity(actor, activity);
break; } else if (isFollow(activity)) {
case 'Follow':
await follow(actor, activity); await follow(actor, activity);
break; } else if (isAccept(activity)) {
case 'Accept':
await accept(actor, activity); await accept(actor, activity);
break; } else if (isReject(activity)) {
case 'Reject':
await reject(actor, activity); await reject(actor, activity);
break; } else if (isAdd(activity)) {
case 'Add':
await add(actor, activity).catch(err => apLogger.error(err)); await add(actor, activity).catch(err => apLogger.error(err));
break; } else if (isRemove(activity)) {
case 'Remove':
await remove(actor, activity).catch(err => apLogger.error(err)); await remove(actor, activity).catch(err => apLogger.error(err));
break; } else if (isAnnounce(activity)) {
case 'Announce':
await announce(actor, activity); await announce(actor, activity);
break; } else if (isLike(activity)) {
case 'Like':
await like(actor, activity); await like(actor, activity);
break; } else if (isUndo(activity)) {
case 'Undo':
await undo(actor, activity); await undo(actor, activity);
break; } else if (isBlock(activity)) {
case 'Block':
await block(actor, activity); await block(actor, activity);
break; } else {
case 'Collection':
case 'OrderedCollection':
// TODO
break;
default:
apLogger.warn(`unknown activity type: ${(activity as any).type}`); apLogger.warn(`unknown activity type: ${(activity as any).type}`);
return;
} }
}; }
export default self;

View file

@ -26,6 +26,8 @@ import { UserProfile } from '../../../models/entities/user-profile';
import { validActor } from '../../../remote/activitypub/type'; import { validActor } from '../../../remote/activitypub/type';
import { getConnection } from 'typeorm'; import { getConnection } from 'typeorm';
import { ensure } from '../../../prelude/ensure'; import { ensure } from '../../../prelude/ensure';
import { toArray } from '../../../prelude/array';
const logger = apLogger; const logger = apLogger;
/** /**
@ -463,8 +465,7 @@ export async function updateFeatured(userId: User['id']) {
// Resolve to Object(may be Note) arrays // Resolve to Object(may be Note) arrays
const unresolvedItems = isCollection(collection) ? collection.items : collection.orderedItems; const unresolvedItems = isCollection(collection) ? collection.items : collection.orderedItems;
const items = await resolver.resolve(unresolvedItems); const items = await Promise.all(toArray(unresolvedItems).map(x => resolver.resolve(x)));
if (!Array.isArray(items)) throw new Error(`Collection items is not an array`);
// Resolve and regist Notes // Resolve and regist Notes
const limit = promiseLimit<Note | null>(2); const limit = promiseLimit<Note | null>(2);

View file

@ -1,7 +1,7 @@
import { Object } from './type'; import { IObject } from './type';
import { IRemoteUser } from '../../models/entities/user'; import { IRemoteUser } from '../../models/entities/user';
import kernel from './kernel'; import { performActivity } from './kernel';
export default async (actor: IRemoteUser, activity: Object): Promise<void> => { export default async (actor: IRemoteUser, activity: IObject): Promise<void> => {
await kernel(actor, activity); await performActivity(actor, activity);
}; };

View file

@ -1,5 +1,5 @@
import * as request from 'request-promise-native'; import * as request from 'request-promise-native';
import { IObject } from './type'; import { IObject, isCollectionOrOrderedCollection, ICollection, IOrderedCollection } from './type';
import config from '../../config'; import config from '../../config';
export default class Resolver { export default class Resolver {
@ -14,31 +14,19 @@ export default class Resolver {
return Array.from(this.history); return Array.from(this.history);
} }
public async resolveCollection(value: any) { public async resolveCollection(value: string | IObject): Promise<ICollection | IOrderedCollection> {
const collection = typeof value === 'string' const collection = typeof value === 'string'
? await this.resolve(value) ? await this.resolve(value)
: value; : value;
switch (collection.type) { if (isCollectionOrOrderedCollection(collection)) {
case 'Collection': { return collection;
collection.objects = collection.items; } else {
break;
}
case 'OrderedCollection': {
collection.objects = collection.orderedItems;
break;
}
default: {
throw new Error(`unknown collection type: ${collection.type}`); throw new Error(`unknown collection type: ${collection.type}`);
} }
} }
return collection; public async resolve(value: string | IObject): Promise<IObject> {
}
public async resolve(value: any): Promise<IObject> {
if (value == null) { if (value == null) {
throw new Error('resolvee is null (or undefined)'); throw new Error('resolvee is null (or undefined)');
} }

View file

@ -1,4 +1,5 @@
export type obj = { [x: string]: any }; export type obj = { [x: string]: any };
export type ApObject = IObject | string | (IObject | string)[];
export interface IObject { export interface IObject {
'@context': string | obj | obj[]; '@context': string | obj | obj[];
@ -6,9 +7,9 @@ export interface IObject {
id?: string; id?: string;
summary?: string; summary?: string;
published?: string; published?: string;
cc?: IObject | string | (IObject | string)[]; cc?: ApObject;
to?: IObject | string | (IObject | string)[]; to?: ApObject;
attributedTo: IObject | string | (IObject | string)[]; attributedTo: ApObject;
attachment?: any[]; attachment?: any[];
inReplyTo?: any; inReplyTo?: any;
replies?: ICollection; replies?: ICollection;
@ -26,7 +27,7 @@ export interface IObject {
/** /**
* Get array of ActivityStreams Objects id * Get array of ActivityStreams Objects id
*/ */
export function getApIds(value: IObject | string | (IObject | string)[] | undefined): string[] { export function getApIds(value: ApObject | undefined): string[] {
if (value == null) return []; if (value == null) return [];
const array = Array.isArray(value) ? value : [value]; const array = Array.isArray(value) ? value : [value];
return array.map(x => getApId(x)); return array.map(x => getApId(x));
@ -35,7 +36,7 @@ export function getApIds(value: IObject | string | (IObject | string)[] | undefi
/** /**
* Get first ActivityStreams Object id * Get first ActivityStreams Object id
*/ */
export function getOneApId(value: IObject | string | (IObject | string)[]): string { export function getOneApId(value: ApObject): string {
const firstOne = Array.isArray(value) ? value[0] : value; const firstOne = Array.isArray(value) ? value[0] : value;
return getApId(firstOne); return getApId(firstOne);
} }
@ -59,13 +60,13 @@ export interface IActivity extends IObject {
export interface ICollection extends IObject { export interface ICollection extends IObject {
type: 'Collection'; type: 'Collection';
totalItems: number; totalItems: number;
items: IObject | string | IObject[] | string[]; items: ApObject;
} }
export interface IOrderedCollection extends IObject { export interface IOrderedCollection extends IObject {
type: 'OrderedCollection'; type: 'OrderedCollection';
totalItems: number; totalItems: number;
orderedItems: IObject | string | IObject[] | string[]; orderedItems: ApObject;
} }
export const validPost = ['Note', 'Question', 'Article', 'Audio', 'Document', 'Image', 'Page', 'Video']; export const validPost = ['Note', 'Question', 'Article', 'Audio', 'Document', 'Image', 'Page', 'Video'];
@ -170,18 +171,15 @@ export interface IBlock extends IActivity {
type: 'Block'; type: 'Block';
} }
export type Object = export const isCreate = (object: IObject): object is ICreate => object.type === 'Create';
ICollection | export const isDelete = (object: IObject): object is IDelete => object.type === 'Delete';
IOrderedCollection | export const isUpdate = (object: IObject): object is IUpdate => object.type === 'Update';
ICreate | export const isUndo = (object: IObject): object is IUndo => object.type === 'Undo';
IDelete | export const isFollow = (object: IObject): object is IFollow => object.type === 'Follow';
IUpdate | export const isAccept = (object: IObject): object is IAccept => object.type === 'Accept';
IUndo | export const isReject = (object: IObject): object is IReject => object.type === 'Reject';
IFollow | export const isAdd = (object: IObject): object is IAdd => object.type === 'Add';
IAccept | export const isRemove = (object: IObject): object is IRemove => object.type === 'Remove';
IReject | export const isLike = (object: IObject): object is ILike => object.type === 'Like';
IAdd | export const isAnnounce = (object: IObject): object is IAnnounce => object.type === 'Announce';
IRemove | export const isBlock = (object: IObject): object is IBlock => object.type === 'Block';
ILike |
IAnnounce |
IBlock;