Implement announce

And bug fixes
This commit is contained in:
syuilo
2018-04-08 06:55:26 +09:00
parent 0004944708
commit 6e34e77372
17 changed files with 164 additions and 300 deletions

View File

@ -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<void> => {
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;
}
};

View File

@ -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<void> {
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
});
}

View File

@ -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<void> => {
switch (activity.type) {
@ -24,6 +25,10 @@ const self = async (actor: IRemoteUser, activity: Object): Promise<void> => {
// noop
break;
case 'Announce':
await announce(actor, activity);
break;
case 'Like':
await like(actor, activity);
break;

View File

@ -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();

View File

@ -0,0 +1,4 @@
export default object => ({
type: 'Announce',
object
});

View File

@ -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}`,

View File

@ -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 {

View File

@ -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<IUser> => {
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();

View File

@ -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');
}

View File

@ -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;