wip
This commit is contained in:
		
							parent
							
								
									3ef82ccb62
								
							
						
					
					
						commit
						08beb45935
					
				|  | @ -4,8 +4,8 @@ import * as debug from 'debug'; | |||
| import { verifySignature } from 'http-signature'; | ||||
| import parseAcct from '../../../acct/parse'; | ||||
| import User, { IRemoteUser } from '../../../models/user'; | ||||
| import act from '../../../remote/activitypub/act'; | ||||
| import resolvePerson from '../../../remote/activitypub/resolve-person'; | ||||
| import perform from '../../../remote/activitypub/perform'; | ||||
| import { resolvePerson } from '../../../remote/activitypub/objects/person'; | ||||
| 
 | ||||
| const log = debug('misskey:queue:inbox'); | ||||
| 
 | ||||
|  | @ -58,7 +58,7 @@ export default async (job: kue.Job, done): Promise<void> => { | |||
| 
 | ||||
| 	// アクティビティを処理
 | ||||
| 	try { | ||||
| 		await act(user, activity); | ||||
| 		await perform(user, activity); | ||||
| 		done(); | ||||
| 	} catch (e) { | ||||
| 		done(e); | ||||
|  |  | |||
|  | @ -1,18 +0,0 @@ | |||
| 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.uri !== image.attributedTo) { | ||||
| 		log(`invalid image: ${JSON.stringify(image, null, 2)}`); | ||||
| 		throw new Error('invalid image'); | ||||
| 	} | ||||
| 
 | ||||
| 	log(`Creating the Image: ${image.url}`); | ||||
| 
 | ||||
| 	return await uploadFromUrl(image.url, actor); | ||||
| } | ||||
|  | @ -1,87 +0,0 @@ | |||
| import { JSDOM } from 'jsdom'; | ||||
| import * as debug from 'debug'; | ||||
| 
 | ||||
| import Resolver from '../../resolver'; | ||||
| import Note, { INote } from '../../../../models/note'; | ||||
| import post from '../../../../services/note/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<INote> { | ||||
| 	if (typeof note.id !== 'string') { | ||||
| 		log(`invalid note: ${JSON.stringify(note, null, 2)}`); | ||||
| 		throw new Error('invalid note'); | ||||
| 	} | ||||
| 
 | ||||
| 	// 既に同じURIを持つものが登録されていないかチェックし、登録されていたらそれを返す
 | ||||
| 	const exist = await Note.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 添付メディア
 | ||||
| 	let media = []; | ||||
| 	if ('attachment' in note && note.attachment != null) { | ||||
| 		// TODO: attachmentは必ずしもImageではない
 | ||||
| 		// TODO: attachmentは必ずしも配列ではない
 | ||||
| 		media = await Promise.all(note.attachment.map(x => { | ||||
| 			return createImage(actor, x); | ||||
| 		})); | ||||
| 	} | ||||
| 	//#endregion
 | ||||
| 
 | ||||
| 	//#region リプライ
 | ||||
| 	let reply = null; | ||||
| 	if ('inReplyTo' in note && note.inReplyTo != null) { | ||||
| 		// リプライ先の投稿がMisskeyに登録されているか調べる
 | ||||
| 		const uri: string = note.inReplyTo.id || note.inReplyTo; | ||||
| 		const inReplyToNote = uri.startsWith(config.url + '/') | ||||
| 			? await Note.findOne({ _id: uri.split('/').pop() }) | ||||
| 			: await Note.findOne({ uri }); | ||||
| 
 | ||||
| 		if (inReplyToNote) { | ||||
| 			reply = inReplyToNote; | ||||
| 		} 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 post(actor, { | ||||
| 		createdAt: new Date(note.published), | ||||
| 		media, | ||||
| 		reply, | ||||
| 		renote: undefined, | ||||
| 		text: window.document.body.textContent, | ||||
| 		viaMobile: false, | ||||
| 		geo: undefined, | ||||
| 		visibility, | ||||
| 		uri: note.id | ||||
| 	}); | ||||
| } | ||||
|  | @ -0,0 +1,29 @@ | |||
| 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'); | ||||
| 
 | ||||
| /** | ||||
|  * Imageを作成します。 | ||||
|  */ | ||||
| export async function createImage(actor: IRemoteUser, image): Promise<IDriveFile> { | ||||
| 	log(`Creating the Image: ${image.url}`); | ||||
| 
 | ||||
| 	return await uploadFromUrl(image.url, actor); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Imageを解決します。 | ||||
|  * | ||||
|  * Misskeyに対象のImageが登録されていればそれを返し、そうでなければ | ||||
|  * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 | ||||
|  */ | ||||
| export async function resolveImage(actor: IRemoteUser, value: any): Promise<IDriveFile> { | ||||
| 	// TODO
 | ||||
| 
 | ||||
| 	// リモートサーバーからフェッチしてきて登録
 | ||||
| 	return await createImage(actor, value); | ||||
| } | ||||
|  | @ -0,0 +1,110 @@ | |||
| import { JSDOM } from 'jsdom'; | ||||
| import * as debug from 'debug'; | ||||
| 
 | ||||
| import config from '../../../config'; | ||||
| import Resolver from '../resolver'; | ||||
| import Note, { INote } from '../../../models/note'; | ||||
| import post from '../../../services/note/create'; | ||||
| import { INote as INoteActivityStreamsObject, IObject } from '../type'; | ||||
| import { resolvePerson } from './person'; | ||||
| import { resolveImage } from './image'; | ||||
| import { IRemoteUser } from '../../../models/user'; | ||||
| 
 | ||||
| const log = debug('misskey:activitypub'); | ||||
| 
 | ||||
| /** | ||||
|  * Noteをフェッチします。 | ||||
|  * | ||||
|  * Misskeyに対象のNoteが登録されていればそれを返します。 | ||||
|  */ | ||||
| export async function fetchNote(value: string | IObject, resolver?: Resolver): Promise<INote> { | ||||
| 	const uri = typeof value == 'string' ? value : value.id; | ||||
| 
 | ||||
| 	// URIがこのサーバーを指しているならデータベースからフェッチ
 | ||||
| 	if (uri.startsWith(config.url + '/')) { | ||||
| 		return await Note.findOne({ _id: uri.split('/').pop() }); | ||||
| 	} | ||||
| 
 | ||||
| 	//#region このサーバーに既に登録されていたらそれを返す
 | ||||
| 	const exist = await Note.findOne({ uri }); | ||||
| 
 | ||||
| 	if (exist) { | ||||
| 		return exist; | ||||
| 	} | ||||
| 	//#endregion
 | ||||
| 
 | ||||
| 	return null; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Noteを作成します。 | ||||
|  */ | ||||
| export async function createNote(value: any, resolver?: Resolver, silent = false): Promise<INote> { | ||||
| 	if (resolver == null) resolver = new Resolver(); | ||||
| 
 | ||||
| 	const object = await resolver.resolve(value) as any; | ||||
| 
 | ||||
| 	if (object == null || object.type !== 'Note') { | ||||
| 		throw new Error('invalid note'); | ||||
| 	} | ||||
| 
 | ||||
| 	const note: INoteActivityStreamsObject = object; | ||||
| 
 | ||||
| 	log(`Creating the Note: ${note.id}`); | ||||
| 
 | ||||
| 	// 投稿者をフェッチ
 | ||||
| 	const actor = await resolvePerson(note.attributedTo) as 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'; | ||||
| 	// TODO
 | ||||
| 	if (visibility != 'public') throw new Error('unspported visibility'); | ||||
| 	//#endergion
 | ||||
| 
 | ||||
| 	// 添付メディア
 | ||||
| 	// TODO: attachmentは必ずしもImageではない
 | ||||
| 	// TODO: attachmentは必ずしも配列ではない
 | ||||
| 	const media = note.attachment | ||||
| 		? await Promise.all(note.attachment.map(x => resolveImage(actor, x))) | ||||
| 		: []; | ||||
| 
 | ||||
| 	// リプライ
 | ||||
| 	const reply = note.inReplyTo ? await resolveNote(note.inReplyTo, resolver) : null; | ||||
| 
 | ||||
| 	const { window } = new JSDOM(note.content); | ||||
| 
 | ||||
| 	return await post(actor, { | ||||
| 		createdAt: new Date(note.published), | ||||
| 		media, | ||||
| 		reply, | ||||
| 		renote: undefined, | ||||
| 		text: window.document.body.textContent, | ||||
| 		viaMobile: false, | ||||
| 		geo: undefined, | ||||
| 		visibility, | ||||
| 		uri: note.id | ||||
| 	}, silent); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Noteを解決します。 | ||||
|  * | ||||
|  * Misskeyに対象のNoteが登録されていればそれを返し、そうでなければ | ||||
|  * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 | ||||
|  */ | ||||
| export async function resolveNote(value: string | IObject, resolver?: Resolver): Promise<INote> { | ||||
| 	const uri = typeof value == 'string' ? value : value.id; | ||||
| 
 | ||||
| 	//#region このサーバーに既に登録されていたらそれを返す
 | ||||
| 	const exist = await fetchNote(uri); | ||||
| 
 | ||||
| 	if (exist) { | ||||
| 		return exist; | ||||
| 	} | ||||
| 	//#endregion
 | ||||
| 
 | ||||
| 	// リモートサーバーからフェッチしてきて登録
 | ||||
| 	return await createNote(value, resolver); | ||||
| } | ||||
|  | @ -0,0 +1,142 @@ | |||
| import { JSDOM } from 'jsdom'; | ||||
| import { toUnicode } from 'punycode'; | ||||
| import * as debug from 'debug'; | ||||
| 
 | ||||
| import config from '../../../config'; | ||||
| import User, { validateUsername, isValidName, isValidDescription, IUser, IRemoteUser } from '../../../models/user'; | ||||
| import webFinger from '../../webfinger'; | ||||
| import Resolver from '../resolver'; | ||||
| import { resolveImage } from './image'; | ||||
| import { isCollectionOrOrderedCollection, IObject, IPerson } from '../type'; | ||||
| 
 | ||||
| const log = debug('misskey:activitypub'); | ||||
| 
 | ||||
| /** | ||||
|  * Personをフェッチします。 | ||||
|  * | ||||
|  * Misskeyに対象のPersonが登録されていればそれを返します。 | ||||
|  */ | ||||
| export async function fetchPerson(value: string | IObject, resolver?: Resolver): Promise<IUser> { | ||||
| 	const uri = typeof value == 'string' ? value : value.id; | ||||
| 
 | ||||
| 	// URIがこのサーバーを指しているならデータベースからフェッチ
 | ||||
| 	if (uri.startsWith(config.url + '/')) { | ||||
| 		return await User.findOne({ _id: uri.split('/').pop() }); | ||||
| 	} | ||||
| 
 | ||||
| 	//#region このサーバーに既に登録されていたらそれを返す
 | ||||
| 	const exist = await User.findOne({ uri }); | ||||
| 
 | ||||
| 	if (exist) { | ||||
| 		return exist; | ||||
| 	} | ||||
| 	//#endregion
 | ||||
| 
 | ||||
| 	return null; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Personを作成します。 | ||||
|  */ | ||||
| export async function createPerson(value: any, resolver?: Resolver): Promise<IUser> { | ||||
| 	if (resolver == null) resolver = new Resolver(); | ||||
| 
 | ||||
| 	const object = await resolver.resolve(value) as any; | ||||
| 
 | ||||
| 	if ( | ||||
| 		object == null || | ||||
| 		object.type !== 'Person' || | ||||
| 		typeof object.preferredUsername !== 'string' || | ||||
| 		!validateUsername(object.preferredUsername) || | ||||
| 		!isValidName(object.name == '' ? null : object.name) || | ||||
| 		!isValidDescription(object.summary) | ||||
| 	) { | ||||
| 		throw new Error('invalid person'); | ||||
| 	} | ||||
| 
 | ||||
| 	const person: IPerson = object; | ||||
| 
 | ||||
| 	log(`Creating the Person: ${person.id}`); | ||||
| 
 | ||||
| 	const [followersCount = 0, followingCount = 0, notesCount = 0, finger] = await Promise.all([ | ||||
| 		resolver.resolve(person.followers).then( | ||||
| 			resolved => isCollectionOrOrderedCollection(resolved) ? resolved.totalItems : undefined, | ||||
| 			() => undefined | ||||
| 		), | ||||
| 		resolver.resolve(person.following).then( | ||||
| 			resolved => isCollectionOrOrderedCollection(resolved) ? resolved.totalItems : undefined, | ||||
| 			() => undefined | ||||
| 		), | ||||
| 		resolver.resolve(person.outbox).then( | ||||
| 			resolved => isCollectionOrOrderedCollection(resolved) ? resolved.totalItems : undefined, | ||||
| 			() => undefined | ||||
| 		), | ||||
| 		webFinger(person.id) | ||||
| 	]); | ||||
| 
 | ||||
| 	const host = toUnicode(finger.subject.replace(/^.*?@/, '')); | ||||
| 	const hostLower = host.replace(/[A-Z]+/, matched => matched.toLowerCase()); | ||||
| 	const summaryDOM = JSDOM.fragment(person.summary); | ||||
| 
 | ||||
| 	// Create user
 | ||||
| 	const user = await User.insert({ | ||||
| 		avatarId: null, | ||||
| 		bannerId: null, | ||||
| 		createdAt: Date.parse(person.published) || null, | ||||
| 		description: summaryDOM.textContent, | ||||
| 		followersCount, | ||||
| 		followingCount, | ||||
| 		notesCount, | ||||
| 		name: person.name, | ||||
| 		driveCapacity: 1024 * 1024 * 8, // 8MiB
 | ||||
| 		username: person.preferredUsername, | ||||
| 		usernameLower: person.preferredUsername.toLowerCase(), | ||||
| 		host, | ||||
| 		hostLower, | ||||
| 		publicKey: { | ||||
| 			id: person.publicKey.id, | ||||
| 			publicKeyPem: person.publicKey.publicKeyPem | ||||
| 		}, | ||||
| 		inbox: person.inbox, | ||||
| 		uri: person.id | ||||
| 	}) as IRemoteUser; | ||||
| 
 | ||||
| 	//#region アイコンとヘッダー画像をフェッチ
 | ||||
| 	const [avatarId, bannerId] = (await Promise.all([ | ||||
| 		person.icon, | ||||
| 		person.image | ||||
| 	].map(img => | ||||
| 		img == null | ||||
| 			? Promise.resolve(null) | ||||
| 			: resolveImage(user, img.url) | ||||
| 	))).map(file => file != null ? file._id : null); | ||||
| 
 | ||||
| 	User.update({ _id: user._id }, { $set: { avatarId, bannerId } }); | ||||
| 
 | ||||
| 	user.avatarId = avatarId; | ||||
| 	user.bannerId = bannerId; | ||||
| 	//#endregion
 | ||||
| 
 | ||||
| 	return user; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Personを解決します。 | ||||
|  * | ||||
|  * Misskeyに対象のPersonが登録されていればそれを返し、そうでなければ | ||||
|  * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 | ||||
|  */ | ||||
| export async function resolvePerson(value: string | IObject, verifier?: string): Promise<IUser> { | ||||
| 	const uri = typeof value == 'string' ? value : value.id; | ||||
| 
 | ||||
| 	//#region このサーバーに既に登録されていたらそれを返す
 | ||||
| 	const exist = await fetchPerson(uri); | ||||
| 
 | ||||
| 	if (exist) { | ||||
| 		return exist; | ||||
| 	} | ||||
| 	//#endregion
 | ||||
| 
 | ||||
| 	// リモートサーバーからフェッチしてきて登録
 | ||||
| 	return await createPerson(value); | ||||
| } | ||||
|  | @ -1,12 +1,10 @@ | |||
| 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 { IRemoteUser } from '../../../../models/user'; | ||||
| import { IAnnounce, INote } from '../../type'; | ||||
| import createNote from '../create/note'; | ||||
| import resolvePerson from '../../resolve-person'; | ||||
| import { fetchNote, resolveNote } from '../../objects/note'; | ||||
| 
 | ||||
| const log = debug('misskey:activitypub'); | ||||
| 
 | ||||
|  | @ -21,17 +19,12 @@ export default async function(resolver: Resolver, actor: IRemoteUser, activity: | |||
| 	} | ||||
| 
 | ||||
| 	// 既に同じURIを持つものが登録されていないかチェック
 | ||||
| 	const exist = await Note.findOne({ uri }); | ||||
| 	const exist = await fetchNote(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() }); | ||||
| 	const renote = await resolveNote(note); | ||||
| 
 | ||||
| 	log(`Creating the (Re)Note: ${uri}`); | ||||
| 
 | ||||
|  | @ -0,0 +1,6 @@ | |||
| import { IRemoteUser } from '../../../../models/user'; | ||||
| import { createImage } from '../../objects/image'; | ||||
| 
 | ||||
| export default async function(actor: IRemoteUser, image): Promise<void> { | ||||
| 	await createImage(image.url, actor); | ||||
| } | ||||
|  | @ -0,0 +1,13 @@ | |||
| import Resolver from '../../resolver'; | ||||
| import { IRemoteUser } from '../../../../models/user'; | ||||
| import { createNote, fetchNote } from '../../objects/note'; | ||||
| 
 | ||||
| /** | ||||
|  * 投稿作成アクティビティを捌きます | ||||
|  */ | ||||
| export default async function(resolver: Resolver, actor: IRemoteUser, note, silent = false): Promise<void> { | ||||
| 	const exist = await fetchNote(note); | ||||
| 	if (exist == null) { | ||||
| 		await createNote(note); | ||||
| 	} | ||||
| } | ||||
|  | @ -1,98 +0,0 @@ | |||
| import { JSDOM } from 'jsdom'; | ||||
| import { toUnicode } from 'punycode'; | ||||
| import config from '../../config'; | ||||
| 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, IObject } from './type'; | ||||
| 
 | ||||
| export default async (value: string | IObject, verifier?: string): Promise<IUser> => { | ||||
| 	const id = typeof value == 'string' ? value : value.id; | ||||
| 
 | ||||
| 	if (id.startsWith(config.url + '/')) { | ||||
| 		return await User.findOne({ _id: id.split('/').pop() }); | ||||
| 	} else { | ||||
| 		const exist = await User.findOne({ | ||||
| 			uri: id | ||||
| 		}); | ||||
| 
 | ||||
| 		if (exist) { | ||||
| 			return exist; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	const resolver = new Resolver(); | ||||
| 
 | ||||
| 	const object = await resolver.resolve(value) as any; | ||||
| 
 | ||||
| 	if ( | ||||
| 		object == null || | ||||
| 		object.type !== 'Person' || | ||||
| 		typeof object.preferredUsername !== 'string' || | ||||
| 		!validateUsername(object.preferredUsername) || | ||||
| 		!isValidName(object.name == '' ? null : object.name) || | ||||
| 		!isValidDescription(object.summary) | ||||
| 	) { | ||||
| 		throw new Error('invalid person'); | ||||
| 	} | ||||
| 
 | ||||
| 	const [followersCount = 0, followingCount = 0, notesCount = 0, finger] = await Promise.all([ | ||||
| 		resolver.resolve(object.followers).then( | ||||
| 			resolved => isCollectionOrOrderedCollection(resolved) ? resolved.totalItems : undefined, | ||||
| 			() => undefined | ||||
| 		), | ||||
| 		resolver.resolve(object.following).then( | ||||
| 			resolved => isCollectionOrOrderedCollection(resolved) ? resolved.totalItems : undefined, | ||||
| 			() => undefined | ||||
| 		), | ||||
| 		resolver.resolve(object.outbox).then( | ||||
| 			resolved => isCollectionOrOrderedCollection(resolved) ? resolved.totalItems : undefined, | ||||
| 			() => undefined | ||||
| 		), | ||||
| 		webFinger(id, verifier) | ||||
| 	]); | ||||
| 
 | ||||
| 	const host = toUnicode(finger.subject.replace(/^.*?@/, '')); | ||||
| 	const hostLower = host.replace(/[A-Z]+/, matched => matched.toLowerCase()); | ||||
| 	const summaryDOM = JSDOM.fragment(object.summary); | ||||
| 
 | ||||
| 	// Create user
 | ||||
| 	const user = await User.insert({ | ||||
| 		avatarId: null, | ||||
| 		bannerId: null, | ||||
| 		createdAt: Date.parse(object.published) || null, | ||||
| 		description: summaryDOM.textContent, | ||||
| 		followersCount, | ||||
| 		followingCount, | ||||
| 		notesCount, | ||||
| 		name: object.name, | ||||
| 		driveCapacity: 1024 * 1024 * 8, // 8MiB
 | ||||
| 		username: object.preferredUsername, | ||||
| 		usernameLower: object.preferredUsername.toLowerCase(), | ||||
| 		host, | ||||
| 		hostLower, | ||||
| 		publicKey: { | ||||
| 			id: object.publicKey.id, | ||||
| 			publicKeyPem: object.publicKey.publicKeyPem | ||||
| 		}, | ||||
| 		inbox: object.inbox, | ||||
| 		uri: id | ||||
| 	}); | ||||
| 
 | ||||
| 	const [avatarId, bannerId] = (await Promise.all([ | ||||
| 		object.icon, | ||||
| 		object.image | ||||
| 	].map(img => | ||||
| 		img == null | ||||
| 			? Promise.resolve(null) | ||||
| 			: uploadFromUrl(img.url, user) | ||||
| 	))).map(file => file != null ? file._id : null); | ||||
| 
 | ||||
| 	User.update({ _id: user._id }, { $set: { avatarId, bannerId } }); | ||||
| 
 | ||||
| 	user.avatarId = avatarId; | ||||
| 	user.bannerId = bannerId; | ||||
| 
 | ||||
| 	return user; | ||||
| }; | ||||
|  | @ -9,6 +9,11 @@ export interface IObject { | |||
| 	cc?: string[]; | ||||
| 	to?: string[]; | ||||
| 	attributedTo: string; | ||||
| 	attachment?: any[]; | ||||
| 	inReplyTo?: any; | ||||
| 	content: string; | ||||
| 	icon?: any; | ||||
| 	image?: any; | ||||
| } | ||||
| 
 | ||||
| export interface IActivity extends IObject { | ||||
|  | @ -34,6 +39,17 @@ export interface INote extends IObject { | |||
| 	type: 'Note'; | ||||
| } | ||||
| 
 | ||||
| export interface IPerson extends IObject { | ||||
| 	type: 'Person'; | ||||
| 	name: string; | ||||
| 	preferredUsername: string; | ||||
| 	inbox: string; | ||||
| 	publicKey: any; | ||||
| 	followers: any; | ||||
| 	following: any; | ||||
| 	outbox: any; | ||||
| } | ||||
| 
 | ||||
| export const isCollection = (object: IObject): object is ICollection => | ||||
| 	object.type === 'Collection'; | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,8 +1,8 @@ | |||
| import { toUnicode, toASCII } from 'punycode'; | ||||
| import User from '../models/user'; | ||||
| import resolvePerson from './activitypub/resolve-person'; | ||||
| import webFinger from './webfinger'; | ||||
| import config from '../config'; | ||||
| import { createPerson } from './activitypub/objects/person'; | ||||
| 
 | ||||
| export default async (username, host, option) => { | ||||
| 	const usernameLower = username.toLowerCase(); | ||||
|  | @ -18,13 +18,13 @@ export default async (username, host, option) => { | |||
| 	if (user === null) { | ||||
| 		const acctLower = `${usernameLower}@${hostLowerAscii}`; | ||||
| 
 | ||||
| 		const finger = await webFinger(acctLower, acctLower); | ||||
| 		const finger = await webFinger(acctLower); | ||||
| 		const self = finger.links.find(link => link.rel && link.rel.toLowerCase() === 'self'); | ||||
| 		if (!self) { | ||||
| 			throw new Error('self link not found'); | ||||
| 		} | ||||
| 
 | ||||
| 		user = await resolvePerson(self.href, acctLower); | ||||
| 		user = await createPerson(self.href); | ||||
| 	} | ||||
| 
 | ||||
| 	return user; | ||||
|  |  | |||
|  | @ -3,36 +3,21 @@ const WebFinger = require('webfinger.js'); | |||
| const webFinger = new WebFinger({ }); | ||||
| 
 | ||||
| type ILink = { | ||||
|   href: string; | ||||
|   rel: string; | ||||
| 	href: string; | ||||
| 	rel: string; | ||||
| }; | ||||
| 
 | ||||
| type IWebFinger = { | ||||
|   links: ILink[]; | ||||
|   subject: string; | ||||
| 	links: ILink[]; | ||||
| 	subject: string; | ||||
| }; | ||||
| 
 | ||||
| export default async function resolve(query, verifier?: string): Promise<IWebFinger> { | ||||
| 	const finger = await new Promise((res, rej) => webFinger.lookup(query, (error, result) => { | ||||
| export default async function resolve(query): Promise<IWebFinger> { | ||||
| 	return await new Promise((res, rej) => webFinger.lookup(query, (error, result) => { | ||||
| 		if (error) { | ||||
| 			return rej(error); | ||||
| 		} | ||||
| 
 | ||||
| 		res(result.object); | ||||
| 	})) as IWebFinger; | ||||
| 	const subject = finger.subject.toLowerCase().replace(/^acct:/, ''); | ||||
| 
 | ||||
| 	if (typeof verifier === 'string') { | ||||
| 		if (subject !== verifier) { | ||||
| 			throw new Error(); | ||||
| 		} | ||||
| 
 | ||||
| 		return finger; | ||||
| 	} | ||||
| 
 | ||||
| 	if (typeof subject === 'string') { | ||||
| 		return resolve(subject, subject); | ||||
| 	} | ||||
| 
 | ||||
| 	throw new Error(); | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue