summaryrefslogtreecommitdiff
path: root/src/remote/activitypub/act
diff options
context:
space:
mode:
Diffstat (limited to 'src/remote/activitypub/act')
-rw-r--r--src/remote/activitypub/act/create.ts10
-rw-r--r--src/remote/activitypub/act/create/image.ts18
-rw-r--r--src/remote/activitypub/act/create/index.ts44
-rw-r--r--src/remote/activitypub/act/create/note.ts89
-rw-r--r--src/remote/activitypub/act/delete.ts21
-rw-r--r--src/remote/activitypub/act/delete/index.ts36
-rw-r--r--src/remote/activitypub/act/delete/note.ts30
-rw-r--r--src/remote/activitypub/act/follow.ts64
-rw-r--r--src/remote/activitypub/act/index.ts53
-rw-r--r--src/remote/activitypub/act/undo/follow.ts26
-rw-r--r--src/remote/activitypub/act/undo/index.ts42
-rw-r--r--src/remote/activitypub/act/undo/unfollow.ts11
12 files changed, 304 insertions, 140 deletions
diff --git a/src/remote/activitypub/act/create.ts b/src/remote/activitypub/act/create.ts
deleted file mode 100644
index fa681982cf..0000000000
--- a/src/remote/activitypub/act/create.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import create from '../create';
-import Resolver from '../resolver';
-
-export default (resolver: Resolver, actor, activity, distribute) => {
- if ('actor' in activity && actor.account.uri !== activity.actor) {
- throw new Error();
- }
-
- return create(resolver, actor, activity.object, distribute);
-};
diff --git a/src/remote/activitypub/act/create/image.ts b/src/remote/activitypub/act/create/image.ts
new file mode 100644
index 0000000000..30a75e7377
--- /dev/null
+++ b/src/remote/activitypub/act/create/image.ts
@@ -0,0 +1,18 @@
+import * as debug from 'debug';
+
+import uploadFromUrl from '../../../../services/drive/upload-from-url';
+import { IRemoteUser } from '../../../../models/user';
+import { IDriveFile } from '../../../../models/drive-file';
+
+const log = debug('misskey:activitypub');
+
+export default async function(actor: IRemoteUser, image): Promise<IDriveFile> {
+ if ('attributedTo' in image && actor.account.uri !== image.attributedTo) {
+ log(`invalid image: ${JSON.stringify(image, null, 2)}`);
+ throw new Error('invalid image');
+ }
+
+ log(`Creating the Image: ${image.id}`);
+
+ return await uploadFromUrl(image.url, actor);
+}
diff --git a/src/remote/activitypub/act/create/index.ts b/src/remote/activitypub/act/create/index.ts
new file mode 100644
index 0000000000..dd0b112141
--- /dev/null
+++ b/src/remote/activitypub/act/create/index.ts
@@ -0,0 +1,44 @@
+import * as debug from 'debug';
+
+import Resolver from '../../resolver';
+import { IRemoteUser } from '../../../../models/user';
+import createNote from './note';
+import createImage from './image';
+import { ICreate } from '../../type';
+
+const log = debug('misskey:activitypub');
+
+export default async (actor: IRemoteUser, activity: ICreate): Promise<void> => {
+ if ('actor' in activity && actor.account.uri !== activity.actor) {
+ throw new Error('invalid actor');
+ }
+
+ const uri = activity.id || activity;
+
+ log(`Create: ${uri}`);
+
+ const resolver = new Resolver();
+
+ let object;
+
+ try {
+ object = await resolver.resolve(activity.object);
+ } catch (e) {
+ log(`Resolution failed: ${e}`);
+ throw e;
+ }
+
+ switch (object.type) {
+ case 'Image':
+ createImage(actor, object);
+ break;
+
+ case 'Note':
+ createNote(resolver, actor, object);
+ break;
+
+ default:
+ console.warn(`Unknown type: ${object.type}`);
+ break;
+ }
+};
diff --git a/src/remote/activitypub/act/create/note.ts b/src/remote/activitypub/act/create/note.ts
new file mode 100644
index 0000000000..82a6207038
--- /dev/null
+++ b/src/remote/activitypub/act/create/note.ts
@@ -0,0 +1,89 @@
+import { JSDOM } from 'jsdom';
+import * as debug from 'debug';
+
+import Resolver from '../../resolver';
+import Post, { IPost } from '../../../../models/post';
+import createPost from '../../../../services/post/create';
+import { IRemoteUser } from '../../../../models/user';
+import resolvePerson from '../../resolve-person';
+import createImage from './image';
+import config from '../../../../config';
+
+const log = debug('misskey:activitypub');
+
+/**
+ * 投稿作成アクティビティを捌きます
+ */
+export default async function createNote(resolver: Resolver, actor: IRemoteUser, note, silent = false): Promise<IPost> {
+ if (typeof note.id !== 'string') {
+ log(`invalid note: ${JSON.stringify(note, null, 2)}`);
+ throw new Error('invalid note');
+ }
+
+ // 既に同じURIを持つものが登録されていないかチェックし、登録されていたらそれを返す
+ const exist = await Post.findOne({ uri: note.id });
+ if (exist) {
+ return exist;
+ }
+
+ log(`Creating the Note: ${note.id}`);
+
+ //#region Visibility
+ let visibility = 'public';
+ if (!note.to.includes('https://www.w3.org/ns/activitystreams#Public')) visibility = 'unlisted';
+ if (note.cc.length == 0) visibility = 'private';
+ // TODO
+ if (visibility != 'public') throw new Error('unspported visibility');
+ //#endergion
+
+ //#region 添付メディア
+ const media = [];
+ if ('attachment' in note && note.attachment != null) {
+ // TODO: attachmentは必ずしもImageではない
+ // TODO: attachmentは必ずしも配列ではない
+ // TODO: ループの中でawaitはすべきでない
+ note.attachment.forEach(async media => {
+ const created = await createImage(note.actor, media);
+ media.push(created);
+ });
+ }
+ //#endregion
+
+ //#region リプライ
+ let reply = null;
+ if ('inReplyTo' in note && note.inReplyTo != null) {
+ // リプライ先の投稿がMisskeyに登録されているか調べる
+ const uri: string = note.inReplyTo.id || note.inReplyTo;
+ const inReplyToPost = uri.startsWith(config.url + '/')
+ ? await Post.findOne({ _id: uri.split('/').pop() })
+ : await Post.findOne({ uri });
+
+ if (inReplyToPost) {
+ reply = inReplyToPost;
+ } else {
+ // 無かったらフェッチ
+ const inReplyTo = await resolver.resolve(note.inReplyTo) as any;
+
+ // リプライ先の投稿の投稿者をフェッチ
+ const actor = await resolvePerson(inReplyTo.attributedTo) as IRemoteUser;
+
+ // TODO: silentを常にtrueにしてはならない
+ reply = await createNote(resolver, actor, inReplyTo);
+ }
+ }
+ //#endregion
+
+ const { window } = new JSDOM(note.content);
+
+ return await createPost(actor, {
+ createdAt: new Date(note.published),
+ media,
+ reply,
+ repost: undefined,
+ text: window.document.body.textContent,
+ viaMobile: false,
+ geo: undefined,
+ visibility,
+ uri: note.id
+ });
+}
diff --git a/src/remote/activitypub/act/delete.ts b/src/remote/activitypub/act/delete.ts
deleted file mode 100644
index f9eb4dd08d..0000000000
--- a/src/remote/activitypub/act/delete.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import create from '../create';
-import deleteObject from '../delete';
-
-export default async (resolver, actor, activity) => {
- if ('actor' in activity && actor.account.uri !== activity.actor) {
- throw new Error();
- }
-
- const results = await create(resolver, actor, activity.object);
-
- await Promise.all(results.map(async promisedResult => {
- const result = await promisedResult;
- if (result === null) {
- return;
- }
-
- await deleteObject(result);
- }));
-
- return null;
-};
diff --git a/src/remote/activitypub/act/delete/index.ts b/src/remote/activitypub/act/delete/index.ts
new file mode 100644
index 0000000000..e34577b310
--- /dev/null
+++ b/src/remote/activitypub/act/delete/index.ts
@@ -0,0 +1,36 @@
+import Resolver from '../../resolver';
+import deleteNote from './note';
+import Post from '../../../../models/post';
+import { IRemoteUser } from '../../../../models/user';
+
+/**
+ * 削除アクティビティを捌きます
+ */
+export default async (actor: IRemoteUser, activity): Promise<void> => {
+ if ('actor' in activity && actor.account.uri !== activity.actor) {
+ throw new Error('invalid actor');
+ }
+
+ const resolver = new Resolver();
+
+ const object = await resolver.resolve(activity.object);
+
+ const uri = (object as any).id;
+
+ switch (object.type) {
+ case 'Note':
+ deleteNote(actor, uri);
+ break;
+
+ case 'Tombstone':
+ const post = await Post.findOne({ uri });
+ if (post != null) {
+ deleteNote(actor, uri);
+ }
+ break;
+
+ default:
+ console.warn(`Unknown type: ${object.type}`);
+ break;
+ }
+};
diff --git a/src/remote/activitypub/act/delete/note.ts b/src/remote/activitypub/act/delete/note.ts
new file mode 100644
index 0000000000..8e9447b481
--- /dev/null
+++ b/src/remote/activitypub/act/delete/note.ts
@@ -0,0 +1,30 @@
+import * as debug from 'debug';
+
+import Post from '../../../../models/post';
+import { IRemoteUser } from '../../../../models/user';
+
+const log = debug('misskey:activitypub');
+
+export default async function(actor: IRemoteUser, uri: string): Promise<void> {
+ log(`Deleting the Note: ${uri}`);
+
+ const post = await Post.findOne({ uri });
+
+ if (post == null) {
+ throw new Error('post not found');
+ }
+
+ if (!post.userId.equals(actor._id)) {
+ throw new Error('投稿を削除しようとしているユーザーは投稿の作成者ではありません');
+ }
+
+ Post.update({ _id: post._id }, {
+ $set: {
+ deletedAt: new Date(),
+ text: null,
+ textHtml: null,
+ mediaIds: [],
+ poll: null
+ }
+ });
+}
diff --git a/src/remote/activitypub/act/follow.ts b/src/remote/activitypub/act/follow.ts
index 222a257e1a..3dd029af54 100644
--- a/src/remote/activitypub/act/follow.ts
+++ b/src/remote/activitypub/act/follow.ts
@@ -1,17 +1,12 @@
-import { MongoError } from 'mongodb';
import parseAcct from '../../../acct/parse';
-import Following, { IFollowing } from '../../../models/following';
-import User from '../../../models/user';
+import User, { IRemoteUser } from '../../../models/user';
import config from '../../../config';
-import { createHttp } from '../../../queue';
-import context from '../renderer/context';
-import renderAccept from '../renderer/accept';
-import request from '../../request';
-import Resolver from '../resolver';
+import follow from '../../../services/following/create';
+import { IFollow } from '../type';
-export default async (resolver: Resolver, actor, activity, distribute) => {
+export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
const prefix = config.url + '/@';
- const id = activity.object.id || activity.object;
+ const id = typeof activity == 'string' ? activity : activity.id;
if (!id.startsWith(prefix)) {
return null;
@@ -27,52 +22,5 @@ export default async (resolver: Resolver, actor, activity, distribute) => {
throw new Error();
}
- if (!distribute) {
- const { _id } = await Following.findOne({
- followerId: actor._id,
- followeeId: followee._id
- });
-
- return {
- resolver,
- object: { $ref: 'following', $id: _id }
- };
- }
-
- const promisedFollowing = Following.insert({
- createdAt: new Date(),
- followerId: actor._id,
- followeeId: followee._id
- }).then(following => new Promise((resolve, reject) => {
- createHttp({
- type: 'follow',
- following: following._id
- }).save(error => {
- if (error) {
- reject(error);
- } else {
- resolve(following);
- }
- });
- }) as Promise<IFollowing>, async error => {
- // duplicate key error
- if (error instanceof MongoError && error.code === 11000) {
- return Following.findOne({
- followerId: actor._id,
- followeeId: followee._id
- });
- }
-
- throw error;
- });
-
- const accept = renderAccept(activity);
- accept['@context'] = context;
-
- await request(followee, actor.account.inbox, accept);
-
- return promisedFollowing.then(({ _id }) => ({
- resolver,
- object: { $ref: 'following', $id: _id }
- }));
+ await follow(actor, followee, activity);
};
diff --git a/src/remote/activitypub/act/index.ts b/src/remote/activitypub/act/index.ts
index d282e12885..5be07c478e 100644
--- a/src/remote/activitypub/act/index.ts
+++ b/src/remote/activitypub/act/index.ts
@@ -2,35 +2,40 @@ 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';
+import { IRemoteUser } from '../../../models/user';
-export default async (parentResolver: Resolver, actor, value, distribute?: boolean) => {
- const collection = await parentResolver.resolveCollection(value);
+const self = async (actor: IRemoteUser, activity: IObject): Promise<void> => {
+ switch (activity.type) {
+ case 'Create':
+ await create(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(actor, activity);
+ break;
- if (created !== null) {
- return created;
- }
+ case 'Follow':
+ await follow(actor, activity);
+ break;
- switch (object.type) {
- case 'Create':
- return create(resolver, actor, object, distribute);
+ case 'Accept':
+ // noop
+ break;
- case 'Delete':
- return performDeleteActivity(resolver, actor, object);
+ case 'Undo':
+ await undo(actor, activity);
+ break;
- case 'Follow':
- return follow(resolver, actor, object, distribute);
+ case 'Collection':
+ case 'OrderedCollection':
+ // TODO
+ break;
- case 'Undo':
- return undo(resolver, actor, object);
-
- default:
- return null;
- }
- });
+ default:
+ console.warn(`unknown activity type: ${activity.type}`);
+ return null;
+ }
};
+
+export default self;
diff --git a/src/remote/activitypub/act/undo/follow.ts b/src/remote/activitypub/act/undo/follow.ts
new file mode 100644
index 0000000000..fcf27c9507
--- /dev/null
+++ b/src/remote/activitypub/act/undo/follow.ts
@@ -0,0 +1,26 @@
+import parseAcct from '../../../../acct/parse';
+import User, { IRemoteUser } from '../../../../models/user';
+import config from '../../../../config';
+import unfollow from '../../../../services/following/delete';
+import { IFollow } from '../../type';
+
+export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
+ const prefix = config.url + '/@';
+ const id = typeof activity == 'string' ? activity : activity.id;
+
+ if (!id.startsWith(prefix)) {
+ return null;
+ }
+
+ const { username, host } = parseAcct(id.slice(prefix.length));
+ if (host !== null) {
+ throw new Error();
+ }
+
+ const followee = await User.findOne({ username, host });
+ if (followee === null) {
+ throw new Error();
+ }
+
+ await unfollow(actor, followee, activity);
+};
diff --git a/src/remote/activitypub/act/undo/index.ts b/src/remote/activitypub/act/undo/index.ts
index aa60d3a4fa..3ede9fcfb8 100644
--- a/src/remote/activitypub/act/undo/index.ts
+++ b/src/remote/activitypub/act/undo/index.ts
@@ -1,27 +1,37 @@
-import act from '../../act';
-import deleteObject from '../../delete';
-import unfollow from './unfollow';
+import * as debug from 'debug';
+
+import { IRemoteUser } from '../../../../models/user';
+import { IUndo } from '../../type';
+import unfollow from './follow';
import Resolver from '../../resolver';
-export default async (resolver: Resolver, actor, activity): Promise<void> => {
+const log = debug('misskey:activitypub');
+
+export default async (actor: IRemoteUser, activity: IUndo): Promise<void> => {
if ('actor' in activity && actor.account.uri !== activity.actor) {
- throw new Error();
+ throw new Error('invalid actor');
}
- const results = await act(resolver, actor, activity.object);
+ const uri = activity.id || activity;
+
+ log(`Undo: ${uri}`);
- await Promise.all(results.map(async promisedResult => {
- const result = await promisedResult;
+ const resolver = new Resolver();
- if (result === null || await deleteObject(result) !== null) {
- return;
- }
+ let object;
- switch (result.object.$ref) {
- case 'following':
- await unfollow(result.object);
- }
- }));
+ try {
+ object = await resolver.resolve(activity.object);
+ } catch (e) {
+ log(`Resolution failed: ${e}`);
+ throw e;
+ }
+
+ switch (object.type) {
+ case 'Follow':
+ unfollow(actor, object);
+ break;
+ }
return null;
};
diff --git a/src/remote/activitypub/act/undo/unfollow.ts b/src/remote/activitypub/act/undo/unfollow.ts
deleted file mode 100644
index 4f15d9a3e4..0000000000
--- a/src/remote/activitypub/act/undo/unfollow.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { createHttp } from '../../../../queue';
-
-export default ({ $id }) => new Promise((resolve, reject) => {
- createHttp({ type: 'unfollow', id: $id }).save(error => {
- if (error) {
- reject(error);
- } else {
- resolve();
- }
- });
-});