Merge pull request #2570 from mei23/mei-0901-update2b

Implement ActivityPub Update(Person)
This commit is contained in:
syuilo 2018-09-01 21:38:49 +09:00 committed by GitHub
commit 4b1886990f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 89 additions and 13 deletions

View File

@ -5,7 +5,7 @@ const httpSignature = require('http-signature');
import parseAcct from '../../../misc/acct/parse'; import parseAcct from '../../../misc/acct/parse';
import User, { IRemoteUser } from '../../../models/user'; import User, { IRemoteUser } from '../../../models/user';
import perform from '../../../remote/activitypub/perform'; import perform from '../../../remote/activitypub/perform';
import { resolvePerson } from '../../../remote/activitypub/models/person'; import { resolvePerson, updatePerson } from '../../../remote/activitypub/models/person';
import { toUnicode } from 'punycode'; import { toUnicode } from 'punycode';
import { URL } from 'url'; import { URL } from 'url';
@ -44,11 +44,6 @@ export default async (job: bq.Job, done: any): Promise<void> => {
} }
user = await User.findOne({ usernameLower: username, host: host.toLowerCase() }) as IRemoteUser; user = await User.findOne({ usernameLower: username, host: host.toLowerCase() }) as IRemoteUser;
// アクティビティを送信してきたユーザーがまだMisskeyサーバーに登録されていなかったら登録する
if (user === null) {
user = await resolvePerson(activity.actor) as IRemoteUser;
}
} else { } else {
// アクティビティ内のホストの検証 // アクティビティ内のホストの検証
const host = toUnicode(new URL(signature.keyId).hostname.toLowerCase()); const host = toUnicode(new URL(signature.keyId).hostname.toLowerCase());
@ -64,11 +59,26 @@ export default async (job: bq.Job, done: any): Promise<void> => {
host: { $ne: null }, host: { $ne: null },
'publicKey.id': signature.keyId 'publicKey.id': signature.keyId
}) as IRemoteUser; }) as IRemoteUser;
}
// アクティビティを送信してきたユーザーがまだMisskeyサーバーに登録されていなかったら登録する // Update activityの場合は、ここで署名検証/更新処理まで実施して終了
if (user === null) { if (activity.type === 'Update') {
user = await resolvePerson(activity.actor) as IRemoteUser; if (activity.object && activity.object.type === 'Person') {
if (user == null) {
console.warn('Update activity received, but user not registed.');
} else if (!httpSignature.verifySignature(signature, user.publicKey.publicKeyPem)) {
console.warn('Update activity received, but signature verification failed.');
} else {
updatePerson(activity.actor, null, activity.object);
}
} }
done();
return;
}
// アクティビティを送信してきたユーザーがまだMisskeyサーバーに登録されていなかったら登録する
if (user === null) {
user = await resolvePerson(activity.actor) as IRemoteUser;
} }
if (user === null) { if (user === null) {

View File

@ -139,6 +139,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
avatarId: null, avatarId: null,
bannerId: null, bannerId: null,
createdAt: Date.parse(person.published) || null, createdAt: Date.parse(person.published) || null,
updatedAt: new Date(),
description: htmlToMFM(person.summary), description: htmlToMFM(person.summary),
followersCount, followersCount,
followingCount, followingCount,
@ -215,10 +216,12 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
/** /**
* Personの情報を更新します * Personの情報を更新します
*
* Misskeyに対象のPersonが登録されていなければ無視します * Misskeyに対象のPersonが登録されていなければ無視します
* @param uri URI of Person
* @param resolver Resolver
* @param hint Hint of Person object (Personの場合Remote resolveをせずに更新に利用します)
*/ */
export async function updatePerson(uri: string, resolver?: Resolver): Promise<void> { export async function updatePerson(uri: string, resolver?: Resolver, hint?: object): Promise<void> {
if (typeof uri !== 'string') throw 'uri is not string'; if (typeof uri !== 'string') throw 'uri is not string';
// URIがこのサーバーを指しているならスキップ // URIがこのサーバーを指しているならスキップ
@ -236,7 +239,7 @@ export async function updatePerson(uri: string, resolver?: Resolver): Promise<vo
if (resolver == null) resolver = new Resolver(); if (resolver == null) resolver = new Resolver();
const object = await resolver.resolve(uri) as any; const object = hint || await resolver.resolve(uri) as any;
const err = validatePerson(object, uri); const err = validatePerson(object, uri);
@ -290,7 +293,14 @@ export async function updatePerson(uri: string, resolver?: Resolver): Promise<vo
name: person.name, name: person.name,
url: person.url, url: person.url,
endpoints: person.endpoints, endpoints: person.endpoints,
isCat: (person as any).isCat === true ? true : false isBot: object.type == 'Service',
isCat: (person as any).isCat === true ? true : false,
isLocked: person.manuallyApprovesFollowers,
createdAt: Date.parse(person.published) || null,
publicKey: {
id: person.publicKey.id,
publicKeyPem: person.publicKey.publicKeyPem
},
} }
}); });
} }

View File

@ -0,0 +1,14 @@
import config from '../../../config';
import { ILocalUser } from '../../../models/user';
export default (object: any, user: ILocalUser) => {
const activity = {
id: `${config.url}/users/${user._id}#updates/${new Date().getTime()}`,
actor: `${config.url}/users/${user._id}`,
type: 'Update',
to: [ 'https://www.w3.org/ns/activitystreams#Public' ],
object
} as any;
return activity;
};

View File

@ -5,6 +5,7 @@ import DriveFile from '../../../../models/drive-file';
import acceptAllFollowRequests from '../../../../services/following/requests/accept-all'; import acceptAllFollowRequests from '../../../../services/following/requests/accept-all';
import { IApp } from '../../../../models/app'; import { IApp } from '../../../../models/app';
import config from '../../../../config'; import config from '../../../../config';
import { publishToFollowers } from '../../../../services/i/update';
export const meta = { export const meta = {
desc: { desc: {
@ -144,4 +145,7 @@ export default async (params: any, user: ILocalUser, app: IApp) => new Promise(a
if (user.isLocked && isLocked === false) { if (user.isLocked && isLocked === false) {
acceptAllFollowRequests(user); acceptAllFollowRequests(user);
} }
// フォロワーにUpdateを配信
publishToFollowers(user._id);
}); });

38
src/services/i/update.ts Normal file
View File

@ -0,0 +1,38 @@
import * as mongo from 'mongodb';
import User, { isLocalUser, isRemoteUser } from '../../models/user';
import Following from '../../models/following';
import renderPerson from '../../remote/activitypub/renderer/person';
import renderUpdate from '../../remote/activitypub/renderer/update';
import packAp from '../../remote/activitypub/renderer';
import { deliver } from '../../queue';
export async function publishToFollowers(userId: mongo.ObjectID) {
const user = await User.findOne({
_id: userId
});
const followers = await Following.find({
followeeId: user._id
});
const queue: string[] = [];
// フォロワーがリモートユーザーかつ投稿者がローカルユーザーならUpdateを配信
if (isLocalUser(user)) {
followers.map(following => {
const follower = following._follower;
if (isRemoteUser(follower)) {
const inbox = follower.sharedInbox || follower.inbox;
if (!queue.includes(inbox)) queue.push(inbox);
}
});
if (queue.length > 0) {
const content = packAp(renderUpdate(await renderPerson(user), user));
queue.forEach(inbox => {
deliver(user, content, inbox);
});
}
}
}