summaryrefslogtreecommitdiff
path: root/src/remote/activitypub
diff options
context:
space:
mode:
authorsyuilo <syuilotan@yahoo.co.jp>2018-04-04 23:12:35 +0900
committersyuilo <syuilotan@yahoo.co.jp>2018-04-04 23:12:35 +0900
commite8b42d7e1668679e6a6ee0a7aea1e2ff7f37005b (patch)
tree6973284192eb419bd7bfed2361a594e668b81f9a /src/remote/activitypub
parentMerge pull request #1393 from akihikodaki/duplicate (diff)
downloadsharkey-e8b42d7e1668679e6a6ee0a7aea1e2ff7f37005b.tar.gz
sharkey-e8b42d7e1668679e6a6ee0a7aea1e2ff7f37005b.tar.bz2
sharkey-e8b42d7e1668679e6a6ee0a7aea1e2ff7f37005b.zip
wip
Diffstat (limited to 'src/remote/activitypub')
-rw-r--r--src/remote/activitypub/act/create.ts90
-rw-r--r--src/remote/activitypub/act/index.ts44
-rw-r--r--src/remote/activitypub/create.ts158
-rw-r--r--src/remote/activitypub/resolver.ts75
4 files changed, 139 insertions, 228 deletions
diff --git a/src/remote/activitypub/act/create.ts b/src/remote/activitypub/act/create.ts
index fa681982cf..c1a30ce7d0 100644
--- a/src/remote/activitypub/act/create.ts
+++ b/src/remote/activitypub/act/create.ts
@@ -1,10 +1,92 @@
-import create from '../create';
+import { JSDOM } from 'jsdom';
+const createDOMPurify = require('dompurify');
+
import Resolver from '../resolver';
+import DriveFile from '../../../models/drive-file';
+import Post from '../../../models/post';
+import uploadFromUrl from '../../../drive/upload-from-url';
+import createPost from '../../../post/create';
-export default (resolver: Resolver, actor, activity, distribute) => {
+export default async (resolver: Resolver, actor, activity): Promise<void> => {
if ('actor' in activity && actor.account.uri !== activity.actor) {
- throw new Error();
+ throw new Error('invalid actor');
}
- return create(resolver, actor, activity.object, distribute);
+ const uri = activity.id || activity;
+
+ try {
+ await Promise.all([
+ DriveFile.findOne({ 'metadata.uri': uri }).then(file => {
+ if (file !== null) {
+ throw new Error();
+ }
+ }, () => {}),
+ Post.findOne({ uri }).then(post => {
+ if (post !== null) {
+ throw new Error();
+ }
+ }, () => {})
+ ]);
+ } catch (object) {
+ throw new Error(`already registered: ${uri}`);
+ }
+
+ const object = await resolver.resolve(activity);
+
+ switch (object.type) {
+ case 'Image':
+ createImage(resolver, object);
+ break;
+
+ case 'Note':
+ createNote(resolver, object);
+ break;
+ }
+
+ ///
+
+ async function createImage(resolver: Resolver, image) {
+ if ('attributedTo' in image && actor.account.uri !== image.attributedTo) {
+ throw new Error('invalid image');
+ }
+
+ return await uploadFromUrl(image.url, actor);
+ }
+
+ async function createNote(resolver: Resolver, note) {
+ if (
+ ('attributedTo' in note && actor.account.uri !== note.attributedTo) ||
+ typeof note.id !== 'string'
+ ) {
+ throw new Error('invalid note');
+ }
+
+ const mediaIds = [];
+
+ if ('attachment' in note) {
+ note.attachment.forEach(async media => {
+ const created = await createImage(resolver, media);
+ mediaIds.push(created._id);
+ });
+ }
+
+ const { window } = new JSDOM(note.content);
+
+ await createPost(actor, {
+ channelId: undefined,
+ index: undefined,
+ createdAt: new Date(note.published),
+ mediaIds,
+ replyId: undefined,
+ repostId: undefined,
+ poll: undefined,
+ text: window.document.body.textContent,
+ textHtml: note.content && createDOMPurify(window).sanitize(note.content),
+ userId: actor._id,
+ appId: null,
+ viaMobile: false,
+ geo: undefined,
+ uri: note.id
+ }, null, null, []);
+ }
};
diff --git a/src/remote/activitypub/act/index.ts b/src/remote/activitypub/act/index.ts
index d282e12885..d78335f16e 100644
--- a/src/remote/activitypub/act/index.ts
+++ b/src/remote/activitypub/act/index.ts
@@ -2,35 +2,29 @@ import create from './create';
import performDeleteActivity from './delete';
import follow from './follow';
import undo from './undo';
-import createObject from '../create';
import Resolver from '../resolver';
+import { IObject } from '../type';
-export default async (parentResolver: Resolver, actor, value, distribute?: boolean) => {
- const collection = await parentResolver.resolveCollection(value);
+export default async (parentResolver: Resolver, actor, activity: IObject): Promise<void> => {
+ switch (activity.type) {
+ case 'Create':
+ await create(parentResolver, actor, activity);
+ break;
- return collection.object.map(async element => {
- const { resolver, object } = await collection.resolver.resolveOne(element);
- const created = await (await createObject(resolver, actor, [object], distribute))[0];
+ case 'Delete':
+ await performDeleteActivity(parentResolver, actor, activity);
+ break;
- if (created !== null) {
- return created;
- }
+ case 'Follow':
+ await follow(parentResolver, actor, activity);
+ break;
- switch (object.type) {
- case 'Create':
- return create(resolver, actor, object, distribute);
+ case 'Undo':
+ await undo(parentResolver, actor, activity);
+ break;
- case 'Delete':
- return performDeleteActivity(resolver, actor, object);
-
- case 'Follow':
- return follow(resolver, actor, object, distribute);
-
- case 'Undo':
- return undo(resolver, actor, object);
-
- default:
- return null;
- }
- });
+ default:
+ console.warn(`unknown activity type: ${activity.type}`);
+ return null;
+ }
};
diff --git a/src/remote/activitypub/create.ts b/src/remote/activitypub/create.ts
deleted file mode 100644
index 97c72860fd..0000000000
--- a/src/remote/activitypub/create.ts
+++ /dev/null
@@ -1,158 +0,0 @@
-import { JSDOM } from 'jsdom';
-import { ObjectID } from 'mongodb';
-import config from '../../config';
-import DriveFile from '../../models/drive-file';
-import Post from '../../models/post';
-import { IRemoteUser } from '../../models/user';
-import uploadFromUrl from '../../drive/upload-from-url';
-import createPost from '../../post/create';
-import distributePost from '../../post/distribute';
-import Resolver from './resolver';
-const createDOMPurify = require('dompurify');
-
-type IResult = {
- resolver: Resolver;
- object: {
- $ref: string;
- $id: ObjectID;
- };
-};
-
-class Creator {
- private actor: IRemoteUser;
- private distribute: boolean;
-
- constructor(actor, distribute) {
- this.actor = actor;
- this.distribute = distribute;
- }
-
- private async createImage(resolver: Resolver, image) {
- if ('attributedTo' in image && this.actor.account.uri !== image.attributedTo) {
- throw new Error();
- }
-
- const { _id } = await uploadFromUrl(image.url, this.actor, image.id || null);
- return {
- resolver,
- object: { $ref: 'driveFiles.files', $id: _id }
- };
- }
-
- private async createNote(resolver: Resolver, note) {
- if (
- ('attributedTo' in note && this.actor.account.uri !== note.attributedTo) ||
- typeof note.id !== 'string'
- ) {
- throw new Error();
- }
-
- const mediaIds = 'attachment' in note &&
- (await Promise.all(await this.create(resolver, note.attachment)))
- .filter(media => media !== null && media.object.$ref === 'driveFiles.files')
- .map(({ object }) => object.$id);
-
- const { window } = new JSDOM(note.content);
-
- const inserted = await createPost({
- channelId: undefined,
- index: undefined,
- createdAt: new Date(note.published),
- mediaIds,
- replyId: undefined,
- repostId: undefined,
- poll: undefined,
- text: window.document.body.textContent,
- textHtml: note.content && createDOMPurify(window).sanitize(note.content),
- userId: this.actor._id,
- appId: null,
- viaMobile: false,
- geo: undefined,
- uri: note.id
- }, null, null, []);
-
- const promises = [];
-
- if (this.distribute) {
- promises.push(distributePost(this.actor, inserted.mentions, inserted));
- }
-
- // Register to search database
- if (note.content && config.elasticsearch.enable) {
- const es = require('../../db/elasticsearch');
-
- promises.push(new Promise((resolve, reject) => {
- es.index({
- index: 'misskey',
- type: 'post',
- id: inserted._id.toString(),
- body: {
- text: window.document.body.textContent
- }
- }, resolve);
- }));
- }
-
- await Promise.all(promises);
-
- return {
- resolver,
- object: { $ref: 'posts', id: inserted._id }
- };
- }
-
- public async create(parentResolver: Resolver, value): Promise<Array<Promise<IResult>>> {
- const collection = await parentResolver.resolveCollection(value);
-
- return collection.object.map(async element => {
- const uri = element.id || element;
-
- try {
- await Promise.all([
- DriveFile.findOne({ 'metadata.uri': uri }).then(file => {
- if (file === null) {
- return;
- }
-
- throw {
- $ref: 'driveFile.files',
- $id: file._id
- };
- }, () => {}),
- Post.findOne({ uri }).then(post => {
- if (post === null) {
- return;
- }
-
- throw {
- $ref: 'posts',
- $id: post._id
- };
- }, () => {})
- ]);
- } catch (object) {
- return {
- resolver: collection.resolver,
- object
- };
- }
-
- const { resolver, object } = await collection.resolver.resolveOne(element);
-
- switch (object.type) {
- case 'Image':
- return this.createImage(resolver, object);
-
- case 'Note':
- return this.createNote(resolver, object);
- }
-
- return null;
- });
- }
-}
-
-export default (resolver: Resolver, actor, value, distribute?: boolean) => {
- const creator = new Creator(actor, distribute);
- return creator.create(resolver, value);
-};
diff --git a/src/remote/activitypub/resolver.ts b/src/remote/activitypub/resolver.ts
index 371ccdcc30..de0bba2687 100644
--- a/src/remote/activitypub/resolver.ts
+++ b/src/remote/activitypub/resolver.ts
@@ -1,20 +1,45 @@
+import { IObject } from "./type";
+
const request = require('request-promise-native');
export default class Resolver {
- private requesting: Set<string>;
+ private history: Set<string>;
- constructor(iterable?: Iterable<string>) {
- this.requesting = new Set(iterable);
+ constructor() {
+ this.history = new Set();
}
- private async resolveUnrequestedOne(value) {
+ public async resolveCollection(value) {
+ const collection = typeof value === 'string'
+ ? await this.resolve(value)
+ : value;
+
+ switch (collection.type) {
+ case 'Collection':
+ collection.objects = collection.object.items;
+ break;
+
+ case 'OrderedCollection':
+ collection.objects = collection.object.orderedItems;
+ break;
+
+ default:
+ throw new Error(`unknown collection type: ${collection.type}`);
+ }
+
+ return collection;
+ }
+
+ public async resolve(value): Promise<IObject> {
if (typeof value !== 'string') {
- return { resolver: this, object: value };
+ return value;
}
- const resolver = new Resolver(this.requesting);
+ if (this.history.has(value)) {
+ throw new Error('cannot resolve already resolved one');
+ }
- resolver.requesting.add(value);
+ this.history.add(value);
const object = await request({
url: value,
@@ -29,41 +54,9 @@ export default class Resolver {
!object['@context'].includes('https://www.w3.org/ns/activitystreams') :
object['@context'] !== 'https://www.w3.org/ns/activitystreams'
)) {
- throw new Error();
- }
-
- return { resolver, object };
- }
-
- public async resolveCollection(value) {
- const resolved = typeof value === 'string' ?
- await this.resolveUnrequestedOne(value) :
- { resolver: this, object: value };
-
- switch (resolved.object.type) {
- case 'Collection':
- resolved.object = resolved.object.items;
- break;
-
- case 'OrderedCollection':
- resolved.object = resolved.object.orderedItems;
- break;
-
- default:
- if (!Array.isArray(value)) {
- resolved.object = [resolved.object];
- }
- break;
- }
-
- return resolved;
- }
-
- public resolveOne(value) {
- if (this.requesting.has(value)) {
- throw new Error();
+ throw new Error('invalid response');
}
- return this.resolveUnrequestedOne(value);
+ return object;
}
}