From 4e9ae8e8d5c862ac7a72f56d7bc0b7dab9c81044 Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 6 Apr 2018 19:35:23 +0900 Subject: Split code --- src/remote/activitypub/act/create/note.ts | 60 +++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 src/remote/activitypub/act/create/note.ts (limited to 'src/remote/activitypub/act/create/note.ts') diff --git a/src/remote/activitypub/act/create/note.ts b/src/remote/activitypub/act/create/note.ts new file mode 100644 index 0000000000..2ccd503aeb --- /dev/null +++ b/src/remote/activitypub/act/create/note.ts @@ -0,0 +1,60 @@ +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, isRemoteUser } from '../../../../models/user'; +import resolvePerson from '../../resolve-person'; +import createImage from './image'; + +const log = debug('misskey:activitypub'); + +export default async function createNote(resolver: Resolver, actor: IRemoteUser, note): Promise { + if ( + ('attributedTo' in note && actor.account.uri !== note.attributedTo) || + typeof note.id !== 'string' + ) { + log(`invalid note: ${JSON.stringify(note, null, 2)}`); + throw new Error('invalid note'); + } + + log(`Creating the Note: ${note.id}`); + + const media = []; + if ('attachment' in note && note.attachment != null) { + // TODO: attachmentは必ずしもImageではない + // TODO: ループの中でawaitはすべきでない + note.attachment.forEach(async media => { + const created = await createImage(resolver, note.actor, media); + media.push(created); + }); + } + + let reply = null; + if ('inReplyTo' in note && note.inReplyTo != null) { + const inReplyToPost = await Post.findOne({ uri: note.inReplyTo.id || note.inReplyTo }); + if (inReplyToPost) { + reply = inReplyToPost; + } else { + const inReplyTo = await resolver.resolve(note.inReplyTo) as any; + const actor = await resolvePerson(inReplyTo.attributedTo); + if (isRemoteUser(actor)) { + reply = await createNote(resolver, actor, inReplyTo); + } + } + } + + 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, + uri: note.id + }); +} -- cgit v1.2.3-freya From a01251477ee5ab4766810453dd540170e65c02b2 Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 6 Apr 2018 20:45:33 +0900 Subject: Revert "Remove silent flag" This reverts commit 9c15c94f801de7019f014446aa3b8ea42980a1da. --- src/remote/activitypub/act/create/note.ts | 2 +- src/services/post/create.ts | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) (limited to 'src/remote/activitypub/act/create/note.ts') diff --git a/src/remote/activitypub/act/create/note.ts b/src/remote/activitypub/act/create/note.ts index 2ccd503aeb..d50042e163 100644 --- a/src/remote/activitypub/act/create/note.ts +++ b/src/remote/activitypub/act/create/note.ts @@ -10,7 +10,7 @@ import createImage from './image'; const log = debug('misskey:activitypub'); -export default async function createNote(resolver: Resolver, actor: IRemoteUser, note): Promise { +export default async function createNote(resolver: Resolver, actor: IRemoteUser, note, silent = false): Promise { if ( ('attributedTo' in note && actor.account.uri !== note.attributedTo) || typeof note.id !== 'string' diff --git a/src/services/post/create.ts b/src/services/post/create.ts index 0bede2772d..745683b518 100644 --- a/src/services/post/create.ts +++ b/src/services/post/create.ts @@ -31,7 +31,7 @@ export default async (user: IUser, data: { visibility?: string; uri?: string; app?: IApp; -}) => new Promise(async (res, rej) => { +}, silent = false) => new Promise(async (res, rej) => { if (data.createdAt == null) data.createdAt = new Date(); if (data.visibility == null) data.visibility = 'public'; @@ -127,10 +127,7 @@ export default async (user: IUser, data: { _id: false }); - // この投稿が3分以内に作成されたものであるならストリームに配信 - const shouldDistribute = new Date().getTime() - post.createdAt.getTime() < 1000 * 60 * 3; - - if (shouldDistribute) { + if (!silent) { const note = await renderNote(user, post); const content = renderCreate(note); content['@context'] = context; -- cgit v1.2.3-freya From bc06c66407b1720047826572b6ea955ff751e3de Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 6 Apr 2018 22:00:37 +0900 Subject: Add todo --- src/remote/activitypub/act/create/note.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'src/remote/activitypub/act/create/note.ts') diff --git a/src/remote/activitypub/act/create/note.ts b/src/remote/activitypub/act/create/note.ts index d50042e163..8b22f1b475 100644 --- a/src/remote/activitypub/act/create/note.ts +++ b/src/remote/activitypub/act/create/note.ts @@ -40,6 +40,7 @@ export default async function createNote(resolver: Resolver, actor: IRemoteUser, const inReplyTo = await resolver.resolve(note.inReplyTo) as any; const actor = await resolvePerson(inReplyTo.attributedTo); if (isRemoteUser(actor)) { + // TODO: silentを常にtrueにしてはならない reply = await createNote(resolver, actor, inReplyTo); } } -- cgit v1.2.3-freya From ba1a81dab13b7f4750945c8ce4a29e3076267c6d Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 7 Apr 2018 06:13:40 +0900 Subject: Bug fixes and some refactors --- src/remote/activitypub/act/create/index.ts | 2 -- src/remote/activitypub/act/create/note.ts | 40 ++++++++++++++++++++++-------- src/remote/activitypub/renderer/note.ts | 4 +-- 3 files changed, 31 insertions(+), 15 deletions(-) (limited to 'src/remote/activitypub/act/create/note.ts') diff --git a/src/remote/activitypub/act/create/index.ts b/src/remote/activitypub/act/create/index.ts index d210aa4c5c..7ab4c2abaf 100644 --- a/src/remote/activitypub/act/create/index.ts +++ b/src/remote/activitypub/act/create/index.ts @@ -16,8 +16,6 @@ export default async (actor: IRemoteUser, activity): Promise => { log(`Create: ${uri}`); - // TODO: 同じURIをもつものが既に登録されていないかチェック - const resolver = new Resolver(); let object; diff --git a/src/remote/activitypub/act/create/note.ts b/src/remote/activitypub/act/create/note.ts index 8b22f1b475..253478b6f9 100644 --- a/src/remote/activitypub/act/create/note.ts +++ b/src/remote/activitypub/act/create/note.ts @@ -4,23 +4,31 @@ import * as debug from 'debug'; import Resolver from '../../resolver'; import Post, { IPost } from '../../../../models/post'; import createPost from '../../../../services/post/create'; -import { IRemoteUser, isRemoteUser } from '../../../../models/user'; +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 { - if ( - ('attributedTo' in note && actor.account.uri !== note.attributedTo) || - typeof note.id !== 'string' - ) { + 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 添付メディア const media = []; if ('attachment' in note && note.attachment != null) { // TODO: attachmentは必ずしもImageではない @@ -30,21 +38,31 @@ export default async function createNote(resolver: Resolver, actor: IRemoteUser, media.push(created); }); } + //#endregion + //#region リプライ let reply = null; if ('inReplyTo' in note && note.inReplyTo != null) { - const inReplyToPost = await Post.findOne({ uri: note.inReplyTo.id || note.inReplyTo }); + // リプライ先の投稿が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); - if (isRemoteUser(actor)) { - // TODO: silentを常にtrueにしてはならない - reply = await createNote(resolver, actor, inReplyTo); - } + + // リプライ先の投稿の投稿者をフェッチ + const actor = await resolvePerson(inReplyTo.attributedTo) as IRemoteUser; + + // TODO: silentを常にtrueにしてはならない + reply = await createNote(resolver, actor, inReplyTo); } } + //#endregion const { window } = new JSDOM(note.content); diff --git a/src/remote/activitypub/renderer/note.ts b/src/remote/activitypub/renderer/note.ts index e45b10215a..b971a53951 100644 --- a/src/remote/activitypub/renderer/note.ts +++ b/src/remote/activitypub/renderer/note.ts @@ -19,11 +19,11 @@ export default async (user: IUser, post: IPost) => { if (inReplyToPost !== null) { const inReplyToUser = await User.findOne({ - _id: post.userId, + _id: inReplyToPost.userId, }); if (inReplyToUser !== null) { - inReplyTo = `${config.url}@${inReplyToUser.username}/${inReplyToPost._id}`; + inReplyTo = inReplyToPost.uri || `${config.url}/@${inReplyToUser.username}/${inReplyToPost._id}`; } } } else { -- cgit v1.2.3-freya From 779a37c4ae0f9442c46e1dd847153c4d46f36088 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 7 Apr 2018 07:27:18 +0900 Subject: Visibility support --- src/remote/activitypub/act/create/note.ts | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/remote/activitypub/act/create/note.ts') diff --git a/src/remote/activitypub/act/create/note.ts b/src/remote/activitypub/act/create/note.ts index 253478b6f9..88e3a875a6 100644 --- a/src/remote/activitypub/act/create/note.ts +++ b/src/remote/activitypub/act/create/note.ts @@ -28,6 +28,11 @@ export default async function createNote(resolver: Resolver, actor: IRemoteUser, log(`Creating the Note: ${note.id}`); + //#region Visibility + let visibility = 'public'; + if (note.cc.length == 0) visibility = 'private'; + //#endergion + //#region 添付メディア const media = []; if ('attachment' in note && note.attachment != null) { @@ -74,6 +79,7 @@ export default async function createNote(resolver: Resolver, actor: IRemoteUser, text: window.document.body.textContent, viaMobile: false, geo: undefined, + visibility, uri: note.id }); } -- cgit v1.2.3-freya From e68dd11eccb0adfe957e598c5e385035d4e493da Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 7 Apr 2018 12:02:25 +0900 Subject: Support unlisted visibility type --- src/remote/activitypub/act/create/note.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'src/remote/activitypub/act/create/note.ts') diff --git a/src/remote/activitypub/act/create/note.ts b/src/remote/activitypub/act/create/note.ts index 88e3a875a6..df9f1d69e4 100644 --- a/src/remote/activitypub/act/create/note.ts +++ b/src/remote/activitypub/act/create/note.ts @@ -30,6 +30,7 @@ export default async function createNote(resolver: Resolver, actor: IRemoteUser, //#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'; //#endergion -- cgit v1.2.3-freya From ac5076c6782db4249b31b64318beec5dbce2f37a Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 7 Apr 2018 12:05:15 +0900 Subject: Ignore post that not public --- src/remote/activitypub/act/create/note.ts | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/remote/activitypub/act/create/note.ts') diff --git a/src/remote/activitypub/act/create/note.ts b/src/remote/activitypub/act/create/note.ts index df9f1d69e4..c40facea43 100644 --- a/src/remote/activitypub/act/create/note.ts +++ b/src/remote/activitypub/act/create/note.ts @@ -32,6 +32,8 @@ export default async function createNote(resolver: Resolver, actor: IRemoteUser, 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 添付メディア -- cgit v1.2.3-freya From 81d19195cf2e913e9eaa2bef2ad4414e0c384be9 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 7 Apr 2018 15:52:42 +0900 Subject: Add todo --- src/remote/activitypub/act/create/note.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'src/remote/activitypub/act/create/note.ts') diff --git a/src/remote/activitypub/act/create/note.ts b/src/remote/activitypub/act/create/note.ts index c40facea43..364fddfe0b 100644 --- a/src/remote/activitypub/act/create/note.ts +++ b/src/remote/activitypub/act/create/note.ts @@ -40,6 +40,7 @@ export default async function createNote(resolver: Resolver, actor: IRemoteUser, 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(resolver, note.actor, media); -- cgit v1.2.3-freya From 93f631e3586e0674dc177789bebca8ddd946c153 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 7 Apr 2018 16:14:35 +0900 Subject: Refactor --- src/remote/activitypub/act/create/image.ts | 3 +-- src/remote/activitypub/act/create/index.ts | 5 +++-- src/remote/activitypub/act/create/note.ts | 2 +- src/remote/activitypub/act/follow.ts | 7 ++++--- src/remote/activitypub/act/undo/follow.ts | 7 ++++--- src/remote/activitypub/act/undo/index.ts | 28 +++++++++++++++++++++++++--- src/remote/activitypub/act/unfollow.ts | 25 ------------------------- src/remote/activitypub/type.ts | 22 +++++++++++++++++++--- 8 files changed, 57 insertions(+), 42 deletions(-) delete mode 100644 src/remote/activitypub/act/unfollow.ts (limited to 'src/remote/activitypub/act/create/note.ts') diff --git a/src/remote/activitypub/act/create/image.ts b/src/remote/activitypub/act/create/image.ts index cd9e7b4e0f..30a75e7377 100644 --- a/src/remote/activitypub/act/create/image.ts +++ b/src/remote/activitypub/act/create/image.ts @@ -1,13 +1,12 @@ import * as debug from 'debug'; -import Resolver from '../../resolver'; 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(resolver: Resolver, actor: IRemoteUser, image): Promise { +export default async function(actor: IRemoteUser, image): Promise { if ('attributedTo' in image && actor.account.uri !== image.attributedTo) { log(`invalid image: ${JSON.stringify(image, null, 2)}`); throw new Error('invalid image'); diff --git a/src/remote/activitypub/act/create/index.ts b/src/remote/activitypub/act/create/index.ts index 7ab4c2abaf..dd0b112141 100644 --- a/src/remote/activitypub/act/create/index.ts +++ b/src/remote/activitypub/act/create/index.ts @@ -4,10 +4,11 @@ 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): Promise => { +export default async (actor: IRemoteUser, activity: ICreate): Promise => { if ('actor' in activity && actor.account.uri !== activity.actor) { throw new Error('invalid actor'); } @@ -29,7 +30,7 @@ export default async (actor: IRemoteUser, activity): Promise => { switch (object.type) { case 'Image': - createImage(resolver, actor, object); + createImage(actor, object); break; case 'Note': diff --git a/src/remote/activitypub/act/create/note.ts b/src/remote/activitypub/act/create/note.ts index 364fddfe0b..82a6207038 100644 --- a/src/remote/activitypub/act/create/note.ts +++ b/src/remote/activitypub/act/create/note.ts @@ -43,7 +43,7 @@ export default async function createNote(resolver: Resolver, actor: IRemoteUser, // TODO: attachmentは必ずしも配列ではない // TODO: ループの中でawaitはすべきでない note.attachment.forEach(async media => { - const created = await createImage(resolver, note.actor, media); + const created = await createImage(note.actor, media); media.push(created); }); } diff --git a/src/remote/activitypub/act/follow.ts b/src/remote/activitypub/act/follow.ts index 4fc423d15c..3dd029af54 100644 --- a/src/remote/activitypub/act/follow.ts +++ b/src/remote/activitypub/act/follow.ts @@ -1,11 +1,12 @@ import parseAcct from '../../../acct/parse'; -import User from '../../../models/user'; +import User, { IRemoteUser } from '../../../models/user'; import config from '../../../config'; import follow from '../../../services/following/create'; +import { IFollow } from '../type'; -export default async (actor, activity): Promise => { +export default async (actor: IRemoteUser, activity: IFollow): Promise => { 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; diff --git a/src/remote/activitypub/act/undo/follow.ts b/src/remote/activitypub/act/undo/follow.ts index b1c462a3b0..fcf27c9507 100644 --- a/src/remote/activitypub/act/undo/follow.ts +++ b/src/remote/activitypub/act/undo/follow.ts @@ -1,11 +1,12 @@ import parseAcct from '../../../../acct/parse'; -import User from '../../../../models/user'; +import User, { IRemoteUser } from '../../../../models/user'; import config from '../../../../config'; import unfollow from '../../../../services/following/delete'; +import { IFollow } from '../../type'; -export default async (actor, activity): Promise => { +export default async (actor: IRemoteUser, activity: IFollow): Promise => { 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; diff --git a/src/remote/activitypub/act/undo/index.ts b/src/remote/activitypub/act/undo/index.ts index ecd9944b42..3ede9fcfb8 100644 --- a/src/remote/activitypub/act/undo/index.ts +++ b/src/remote/activitypub/act/undo/index.ts @@ -1,13 +1,35 @@ +import * as debug from 'debug'; + +import { IRemoteUser } from '../../../../models/user'; +import { IUndo } from '../../type'; import unfollow from './follow'; +import Resolver from '../../resolver'; + +const log = debug('misskey:activitypub'); -export default async (actor, activity): Promise => { +export default async (actor: IRemoteUser, activity: IUndo): Promise => { if ('actor' in activity && actor.account.uri !== activity.actor) { throw new Error('invalid actor'); } - switch (activity.object.type) { + const uri = activity.id || activity; + + log(`Undo: ${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 'Follow': - unfollow(actor, activity.object); + unfollow(actor, object); break; } diff --git a/src/remote/activitypub/act/unfollow.ts b/src/remote/activitypub/act/unfollow.ts deleted file mode 100644 index 66c15e9a91..0000000000 --- a/src/remote/activitypub/act/unfollow.ts +++ /dev/null @@ -1,25 +0,0 @@ -import parseAcct from '../../../acct/parse'; -import User from '../../../models/user'; -import config from '../../../config'; -import unfollow from '../../../services/following/delete'; - -export default async (actor, activity): Promise => { - const prefix = config.url + '/@'; - const id = activity.object.id || activity.object; - - 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/type.ts b/src/remote/activitypub/type.ts index cd7f40630a..9a4b3c75fc 100644 --- a/src/remote/activitypub/type.ts +++ b/src/remote/activitypub/type.ts @@ -1,8 +1,5 @@ export type Object = { [x: string]: any }; -export type ActivityType = - 'Create'; - export interface IObject { '@context': string | object | any[]; type: string; @@ -10,6 +7,13 @@ export interface IObject { summary?: string; } +export interface IActivity extends IObject { + //type: 'Activity'; + actor: IObject | string; + object: IObject | string; + target?: IObject | string; +} + export interface ICollection extends IObject { type: 'Collection'; totalItems: number; @@ -30,3 +34,15 @@ export const isOrderedCollection = (object: IObject): object is IOrderedCollecti export const isCollectionOrOrderedCollection = (object: IObject): object is ICollection | IOrderedCollection => isCollection(object) || isOrderedCollection(object); + +export interface ICreate extends IActivity { + type: 'Create'; +} + +export interface IUndo extends IActivity { + type: 'Undo'; +} + +export interface IFollow extends IActivity { + type: 'Follow'; +} -- cgit v1.2.3-freya