From 6e34e77372bd74c85ebf5a6b4214c818231dbe8b Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 8 Apr 2018 06:55:26 +0900 Subject: Implement announce And bug fixes --- src/remote/activitypub/act/announce/index.ts | 39 +++++++++++++++++++++ src/remote/activitypub/act/announce/note.ts | 52 ++++++++++++++++++++++++++++ src/remote/activitypub/act/index.ts | 5 +++ src/remote/activitypub/act/like.ts | 2 +- src/remote/activitypub/renderer/announce.ts | 4 +++ src/remote/activitypub/renderer/like.ts | 3 +- src/remote/activitypub/renderer/note.ts | 8 +++-- src/remote/activitypub/resolve-person.ts | 10 +++--- src/remote/activitypub/resolver.ts | 7 ++++ src/remote/activitypub/type.ts | 15 +++++++- 10 files changed, 135 insertions(+), 10 deletions(-) create mode 100644 src/remote/activitypub/act/announce/index.ts create mode 100644 src/remote/activitypub/act/announce/note.ts create mode 100644 src/remote/activitypub/renderer/announce.ts (limited to 'src/remote/activitypub') diff --git a/src/remote/activitypub/act/announce/index.ts b/src/remote/activitypub/act/announce/index.ts new file mode 100644 index 0000000000..c3ac06607d --- /dev/null +++ b/src/remote/activitypub/act/announce/index.ts @@ -0,0 +1,39 @@ +import * as debug from 'debug'; + +import Resolver from '../../resolver'; +import { IRemoteUser } from '../../../../models/user'; +import announceNote from './note'; +import { IAnnounce } from '../../type'; + +const log = debug('misskey:activitypub'); + +export default async (actor: IRemoteUser, activity: IAnnounce): Promise => { + if ('actor' in activity && actor.uri !== activity.actor) { + throw new Error('invalid actor'); + } + + const uri = activity.id || activity; + + log(`Announce: ${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 'Note': + announceNote(resolver, actor, activity, object); + break; + + default: + console.warn(`Unknown announce type: ${object.type}`); + break; + } +}; diff --git a/src/remote/activitypub/act/announce/note.ts b/src/remote/activitypub/act/announce/note.ts new file mode 100644 index 0000000000..24d159f184 --- /dev/null +++ b/src/remote/activitypub/act/announce/note.ts @@ -0,0 +1,52 @@ +import * as debug from 'debug'; + +import Resolver from '../../resolver'; +import Note from '../../../../models/note'; +import post from '../../../../services/note/create'; +import { IRemoteUser, isRemoteUser } from '../../../../models/user'; +import { IAnnounce, INote } from '../../type'; +import createNote from '../create/note'; +import resolvePerson from '../../resolve-person'; + +const log = debug('misskey:activitypub'); + +/** + * アナウンスアクティビティを捌きます + */ +export default async function(resolver: Resolver, actor: IRemoteUser, activity: IAnnounce, note: INote): Promise { + const uri = activity.id || activity; + + if (typeof uri !== 'string') { + throw new Error('invalid announce'); + } + + // 既に同じURIを持つものが登録されていないかチェック + const exist = await Note.findOne({ uri }); + if (exist) { + return; + } + + // アナウンス元の投稿の投稿者をフェッチ + const announcee = await resolvePerson(note.attributedTo); + + const renote = isRemoteUser(announcee) + ? await createNote(resolver, announcee, note, true) + : await Note.findOne({ _id: note.id.split('/').pop() }); + + log(`Creating the (Re)Note: ${uri}`); + + //#region Visibility + let visibility = 'public'; + if (!activity.to.includes('https://www.w3.org/ns/activitystreams#Public')) visibility = 'unlisted'; + if (activity.cc.length == 0) visibility = 'private'; + // TODO + if (visibility != 'public') throw new Error('unspported visibility'); + //#endergion + + await post(actor, { + createdAt: new Date(activity.published), + renote, + visibility, + uri + }); +} diff --git a/src/remote/activitypub/act/index.ts b/src/remote/activitypub/act/index.ts index 45d7bd16a9..15ea9494ae 100644 --- a/src/remote/activitypub/act/index.ts +++ b/src/remote/activitypub/act/index.ts @@ -5,6 +5,7 @@ import performDeleteActivity from './delete'; import follow from './follow'; import undo from './undo'; import like from './like'; +import announce from './announce'; const self = async (actor: IRemoteUser, activity: Object): Promise => { switch (activity.type) { @@ -24,6 +25,10 @@ const self = async (actor: IRemoteUser, activity: Object): Promise => { // noop break; + case 'Announce': + await announce(actor, activity); + break; + case 'Like': await like(actor, activity); break; diff --git a/src/remote/activitypub/act/like.ts b/src/remote/activitypub/act/like.ts index a3243948ba..4941608588 100644 --- a/src/remote/activitypub/act/like.ts +++ b/src/remote/activitypub/act/like.ts @@ -7,7 +7,7 @@ export default async (actor: IRemoteUser, activity: ILike) => { const id = typeof activity.object == 'string' ? activity.object : activity.object.id; // Transform: - // https://misskey.ex/@syuilo/xxxx to + // https://misskey.ex/notes/xxxx to // xxxx const noteId = id.split('/').pop(); diff --git a/src/remote/activitypub/renderer/announce.ts b/src/remote/activitypub/renderer/announce.ts new file mode 100644 index 0000000000..8e4b3d26a6 --- /dev/null +++ b/src/remote/activitypub/renderer/announce.ts @@ -0,0 +1,4 @@ +export default object => ({ + type: 'Announce', + object +}); diff --git a/src/remote/activitypub/renderer/like.ts b/src/remote/activitypub/renderer/like.ts index fe36c7094d..744896cc41 100644 --- a/src/remote/activitypub/renderer/like.ts +++ b/src/remote/activitypub/renderer/like.ts @@ -1,6 +1,7 @@ import config from '../../../config'; +import { ILocalUser } from '../../../models/user'; -export default (user, note) => { +export default (user: ILocalUser, note) => { return { type: 'Like', actor: `${config.url}/@${user.username}`, diff --git a/src/remote/activitypub/renderer/note.ts b/src/remote/activitypub/renderer/note.ts index 244aecf6a3..48799af084 100644 --- a/src/remote/activitypub/renderer/note.ts +++ b/src/remote/activitypub/renderer/note.ts @@ -3,9 +3,9 @@ import renderHashtag from './hashtag'; import config from '../../../config'; import DriveFile from '../../../models/drive-file'; import Note, { INote } from '../../../models/note'; -import User, { IUser } from '../../../models/user'; +import User from '../../../models/user'; -export default async (user: IUser, note: INote) => { +export default async (note: INote) => { const promisedFiles = note.mediaIds ? DriveFile.find({ _id: { $in: note.mediaIds } }) : Promise.resolve([]); @@ -30,6 +30,10 @@ export default async (user: IUser, note: INote) => { inReplyTo = null; } + const user = await User.findOne({ + _id: note.userId + }); + const attributedTo = `${config.url}/@${user.username}`; return { diff --git a/src/remote/activitypub/resolve-person.ts b/src/remote/activitypub/resolve-person.ts index ddb8d68715..7d7989a01f 100644 --- a/src/remote/activitypub/resolve-person.ts +++ b/src/remote/activitypub/resolve-person.ts @@ -2,18 +2,18 @@ import { JSDOM } from 'jsdom'; import { toUnicode } from 'punycode'; import parseAcct from '../../acct/parse'; import config from '../../config'; -import User, { validateUsername, isValidName, isValidDescription } from '../../models/user'; +import User, { validateUsername, isValidName, isValidDescription, IUser } from '../../models/user'; import webFinger from '../webfinger'; import Resolver from './resolver'; import uploadFromUrl from '../../services/drive/upload-from-url'; -import { isCollectionOrOrderedCollection } from './type'; +import { isCollectionOrOrderedCollection, IObject } from './type'; -export default async (value, verifier?: string) => { - const id = value.id || value; +export default async (value: string | IObject, verifier?: string): Promise => { + const id = typeof value == 'string' ? value : value.id; const localPrefix = config.url + '/@'; if (id.startsWith(localPrefix)) { - return User.findOne(parseAcct(id.slice(localPrefix))); + return await User.findOne(parseAcct(id.substr(localPrefix.length))); } const resolver = new Resolver(); diff --git a/src/remote/activitypub/resolver.ts b/src/remote/activitypub/resolver.ts index 4a97e2ef66..1466139c48 100644 --- a/src/remote/activitypub/resolver.ts +++ b/src/remote/activitypub/resolver.ts @@ -1,6 +1,7 @@ import * as request from 'request-promise-native'; import * as debug from 'debug'; import { IObject } from './type'; +//import config from '../../config'; const log = debug('misskey:activitypub:resolver'); @@ -47,6 +48,11 @@ export default class Resolver { this.history.add(value); + //#region resolve local objects + // TODO + //if (value.startsWith(`${config.url}/@`)) { + //#endregion + const object = await request({ url: value, headers: { @@ -60,6 +66,7 @@ export default class Resolver { !object['@context'].includes('https://www.w3.org/ns/activitystreams') : object['@context'] !== 'https://www.w3.org/ns/activitystreams' )) { + log(`invalid response: ${JSON.stringify(object, null, 2)}`); throw new Error('invalid response'); } diff --git a/src/remote/activitypub/type.ts b/src/remote/activitypub/type.ts index 450d5906d8..2335517645 100644 --- a/src/remote/activitypub/type.ts +++ b/src/remote/activitypub/type.ts @@ -5,6 +5,10 @@ export interface IObject { type: string; id?: string; summary?: string; + published?: string; + cc?: string[]; + to?: string[]; + attributedTo: string; } export interface IActivity extends IObject { @@ -26,6 +30,10 @@ export interface IOrderedCollection extends IObject { orderedItems: IObject | string | IObject[] | string[]; } +export interface INote extends IObject { + type: 'Note'; +} + export const isCollection = (object: IObject): object is ICollection => object.type === 'Collection'; @@ -59,6 +67,10 @@ export interface ILike extends IActivity { type: 'Like'; } +export interface IAnnounce extends IActivity { + type: 'Announce'; +} + export type Object = ICollection | IOrderedCollection | @@ -67,4 +79,5 @@ export type Object = IUndo | IFollow | IAccept | - ILike; + ILike | + IAnnounce; -- cgit v1.2.3-freya