Implement remote follow

This commit is contained in:
Akihiko Odaki 2018-04-01 19:43:26 +09:00
parent cba73d6bc1
commit dc529711ce
11 changed files with 114 additions and 32 deletions

View file

@ -1,8 +1,8 @@
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
import Notification from '../../../models/notification'; import Notification from '../models/notification';
import Mute from '../../../models/mute'; import Mute from '../models/mute';
import event from '../../../common/event'; import event from './event';
import { pack } from '../../../models/notification'; import { pack } from '../models/notification';
export default ( export default (
notifiee: mongo.ObjectID, notifiee: mongo.ObjectID,

View file

@ -0,0 +1,8 @@
import config from '../../../../conf';
import { IRemoteAccount } from '../../../../models/user';
export default ({ username }, { account }) => ({
type: 'Follow',
actor: `${config.url}/@${username}`,
object: (account as IRemoteAccount).uri
});

View file

@ -66,6 +66,7 @@ export default async (value, usernameLower, hostLower, acctLower) => {
id: object.publicKey.id, id: object.publicKey.id,
publicKeyPem: object.publicKey.publicKeyPem publicKeyPem: object.publicKey.publicKeyPem
}, },
inbox: object.inbox,
uri: object.id, uri: object.id,
}, },
}); });

View file

@ -1,6 +1,6 @@
const WebFinger = require('webfinger.js'); const WebFinger = require('webfinger.js');
const webFinger = new WebFinger({}); const webFinger = new WebFinger({ tls_only: false });
type ILink = { type ILink = {
href: string; href: string;

View file

@ -70,6 +70,7 @@ export type ILocalAccount = {
}; };
export type IRemoteAccount = { export type IRemoteAccount = {
inbox: string;
uri: string; uri: string;
publicKey: { publicKey: {
id: string; id: string;

View file

@ -0,0 +1,89 @@
import { request } from 'https';
import { sign } from 'http-signature';
import { URL } from 'url';
import User, { ILocalAccount, IRemoteAccount, pack as packUser } from '../../models/user';
import Following from '../../models/following';
import event from '../../common/event';
import notify from '../../common/notify';
import context from '../../common/remote/activitypub/renderer/context';
import render from '../../common/remote/activitypub/renderer/follow';
import config from '../../conf';
export default ({ data }, done) => Following.findOne({ _id: data.following }).then(({ followerId, followeeId }) => {
const promisedFollower = User.findOne({ _id: followerId });
const promisedFollowee = User.findOne({ _id: followeeId });
return Promise.all([
// Increment following count
User.update(followerId, {
$inc: {
followingCount: 1
}
}),
// Increment followers count
User.update({ _id: followeeId }, {
$inc: {
followersCount: 1
}
}),
// Notify
promisedFollowee.then(followee => followee.host === null ?
notify(followeeId, followerId, 'follow') : null),
// Publish follow event
Promise.all([promisedFollower, promisedFollowee]).then(([follower, followee]) => {
const followerEvent = packUser(followee, follower)
.then(packed => event(follower._id, 'follow', packed));
let followeeEvent;
if (followee.host === null) {
followeeEvent = packUser(follower, followee)
.then(packed => event(followee._id, 'followed', packed));
} else {
followeeEvent = new Promise((resolve, reject) => {
const {
protocol,
hostname,
port,
pathname,
search
} = new URL(followee.account as IRemoteAccount).inbox);
const req = request({
protocol,
hostname,
port,
method: 'POST',
path: pathname + search,
}, res => {
res.on('close', () => {
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve();
} else {
reject(res);
}
});
res.on('data', () => {});
res.on('error', reject);
});
sign(req, {
authorizationHeaderName: 'Signature',
key: (follower.account as ILocalAccount).keypair,
keyId: `acct:${follower.username}@${config.host}`
});
const rendered = render(follower, followee);
rendered['@context'] = context;
req.end(JSON.stringify(rendered));
});
}
return Promise.all([followerEvent, followeeEvent]);
})
]);
}).then(done, done);

View file

@ -1,7 +1,9 @@
import follow from './follow';
import performActivityPub from './perform-activitypub'; import performActivityPub from './perform-activitypub';
import reportGitHubFailure from './report-github-failure'; import reportGitHubFailure from './report-github-failure';
const handlers = { const handlers = {
follow,
performActivityPub, performActivityPub,
reportGitHubFailure, reportGitHubFailure,
}; };

View file

@ -2,10 +2,9 @@
* Module dependencies * Module dependencies
*/ */
import $ from 'cafy'; import $ from 'cafy';
import User, { pack as packUser } from '../../../../models/user'; import User from '../../../../models/user';
import Following from '../../../../models/following'; import Following from '../../../../models/following';
import notify from '../../common/notify'; import queue from '../../../../queue';
import event from '../../../../common/event';
/** /**
* Follow a user * Follow a user
@ -52,33 +51,15 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
} }
// Create following // Create following
await Following.insert({ const { _id } = await Following.insert({
createdAt: new Date(), createdAt: new Date(),
followerId: follower._id, followerId: follower._id,
followeeId: followee._id followeeId: followee._id
}); });
queue.create('http', { type: 'follow', following: _id }).save();
// Send response // Send response
res(); res();
// Increment following count
User.update(follower._id, {
$inc: {
followingCount: 1
}
});
// Increment followers count
User.update({ _id: followee._id }, {
$inc: {
followersCount: 1
}
});
// Publish follow event
event(follower._id, 'follow', await packUser(followee, follower));
event(followee._id, 'followed', await packUser(follower, followee));
// Notify
notify(followee._id, follower._id, 'follow');
}); });

View file

@ -14,9 +14,9 @@ import DriveFile from '../../../../models/drive-file';
import Watching from '../../../../models/post-watching'; import Watching from '../../../../models/post-watching';
import ChannelWatching from '../../../../models/channel-watching'; import ChannelWatching from '../../../../models/channel-watching';
import { pack } from '../../../../models/post'; import { pack } from '../../../../models/post';
import notify from '../../common/notify';
import watch from '../../common/watch-post'; import watch from '../../common/watch-post';
import event, { pushSw, publishChannelStream } from '../../../../common/event'; import event, { pushSw, publishChannelStream } from '../../../../common/event';
import notify from '../../../../common/notify';
import getAcct from '../../../../common/user/get-acct'; import getAcct from '../../../../common/user/get-acct';
import parseAcct from '../../../../common/user/parse-acct'; import parseAcct from '../../../../common/user/parse-acct';
import config from '../../../../conf'; import config from '../../../../conf';

View file

@ -5,9 +5,9 @@ import $ from 'cafy';
import Vote from '../../../../../models/poll-vote'; import Vote from '../../../../../models/poll-vote';
import Post from '../../../../../models/post'; import Post from '../../../../../models/post';
import Watching from '../../../../../models/post-watching'; import Watching from '../../../../../models/post-watching';
import notify from '../../../common/notify';
import watch from '../../../common/watch-post'; import watch from '../../../common/watch-post';
import { publishPostStream } from '../../../../../common/event'; import { publishPostStream } from '../../../../../common/event';
import notify from '../../../../../common/notify';
/** /**
* Vote poll of a post * Vote poll of a post

View file

@ -6,9 +6,9 @@ import Reaction from '../../../../../models/post-reaction';
import Post, { pack as packPost } from '../../../../../models/post'; import Post, { pack as packPost } from '../../../../../models/post';
import { pack as packUser } from '../../../../../models/user'; import { pack as packUser } from '../../../../../models/user';
import Watching from '../../../../../models/post-watching'; import Watching from '../../../../../models/post-watching';
import notify from '../../../common/notify';
import watch from '../../../common/watch-post'; import watch from '../../../common/watch-post';
import { publishPostStream, pushSw } from '../../../../../common/event'; import { publishPostStream, pushSw } from '../../../../../common/event';
import notify from '../../../../../common/notify';
/** /**
* React to a post * React to a post