diff --git a/src/models/following.ts b/src/models/following.ts index 3f8a9be50..fe9ce550d 100644 --- a/src/models/following.ts +++ b/src/models/following.ts @@ -2,6 +2,7 @@ import * as mongo from 'mongodb'; import db from '../db/mongodb'; const Following = db.get('following'); +Following.createIndex(['followerId', 'followeeId'], { unique: true }); export default Following; export type IFollowing = { diff --git a/src/processor/http/follow.ts b/src/processor/http/follow.ts index a7e4fa23d..7ec1ee675 100644 --- a/src/processor/http/follow.ts +++ b/src/processor/http/follow.ts @@ -1,7 +1,7 @@ import { request } from 'https'; import { sign } from 'http-signature'; import { URL } from 'url'; -import User, { isLocalUser, pack as packUser, ILocalUser } from '../../models/user'; +import User, { isLocalUser, pack as packUser } from '../../models/user'; import Following from '../../models/following'; import event from '../../publishers/stream'; import notify from '../../publishers/notify'; @@ -10,7 +10,7 @@ import render from '../../remote/activitypub/renderer/follow'; import config from '../../config'; export default ({ data }, done) => Following.findOne({ _id: data.following }).then(({ followerId, followeeId }) => { - const promisedFollower: Promise = User.findOne({ _id: followerId }); + const promisedFollower = User.findOne({ _id: followerId }); const promisedFollowee = User.findOne({ _id: followeeId }); return Promise.all([ @@ -34,14 +34,18 @@ export default ({ data }, done) => Following.findOne({ _id: data.following }).th // Publish follow event Promise.all([promisedFollower, promisedFollowee]).then(([follower, followee]) => { - const followerEvent = packUser(followee, follower) - .then(packed => event(follower._id, 'follow', packed)); + let followerEvent; let followeeEvent; + if (isLocalUser(follower)) { + followerEvent = packUser(followee, follower) + .then(packed => event(follower._id, 'follow', packed)); + } + if (isLocalUser(followee)) { followeeEvent = packUser(follower, followee) .then(packed => event(followee._id, 'followed', packed)); - } else { + } else if (isLocalUser(follower)) { followeeEvent = new Promise((resolve, reject) => { const { protocol, diff --git a/src/remote/activitypub/act/follow.ts b/src/remote/activitypub/act/follow.ts new file mode 100644 index 000000000..ec9e080df --- /dev/null +++ b/src/remote/activitypub/act/follow.ts @@ -0,0 +1,51 @@ +import { MongoError } from 'mongodb'; +import parseAcct from '../../../acct/parse'; +import Following from '../../../models/following'; +import User from '../../../models/user'; +import config from '../../../config'; +import queue from '../../../queue'; + +export default async (actor, activity) => { + const prefix = config.url + '/@'; + const id = activity.object.id || activity.object; + let following; + + if (!id.startsWith(prefix)) { + return null; + } + + const { username, host } = parseAcct(id.slice(prefix.length)); + if (host !== null) { + throw new Error(); + } + + const followee = await User.findOne({ username, host }); + if (followee === null) { + throw new Error(); + } + + try { + following = await Following.insert({ + createdAt: new Date(), + followerId: actor._id, + followeeId: followee._id + }); + } catch (exception) { + // duplicate key error + if (exception instanceof MongoError && exception.code === 11000) { + return null; + } + + throw exception; + } + + await new Promise((resolve, reject) => { + queue.create('http', { type: 'follow', following: following._id }).save(error => { + if (error) { + reject(error); + } else { + resolve(null); + } + }); + }); +}; diff --git a/src/remote/activitypub/act/index.ts b/src/remote/activitypub/act/index.ts index 06d662c19..24320dcb1 100644 --- a/src/remote/activitypub/act/index.ts +++ b/src/remote/activitypub/act/index.ts @@ -1,4 +1,5 @@ import create from './create'; +import follow from './follow'; import createObject from '../create'; import Resolver from '../resolver'; @@ -15,6 +16,9 @@ export default (actor, value, distribute) => { case 'Create': return create(resolver, actor, object, distribute); + case 'Follow': + return follow(actor, object); + default: return null; }