From 6b66ec12316a82448371b5ff1b40f15a852023d9 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Tue, 3 Apr 2018 16:33:16 +0900 Subject: [PATCH] Implement unfollow by remote account --- src/processor/http/perform-activitypub.ts | 3 +- src/processor/http/process-inbox.ts | 3 +- src/remote/activitypub/act/follow.ts | 73 +++++++++++++-------- src/remote/activitypub/act/index.ts | 19 +++--- src/remote/activitypub/act/undo/index.ts | 23 +++++++ src/remote/activitypub/act/undo/unfollow.ts | 24 +++++++ 6 files changed, 108 insertions(+), 37 deletions(-) create mode 100644 src/remote/activitypub/act/undo/index.ts create mode 100644 src/remote/activitypub/act/undo/unfollow.ts diff --git a/src/processor/http/perform-activitypub.ts b/src/processor/http/perform-activitypub.ts index adf4e65a72..4fdbb901d7 100644 --- a/src/processor/http/perform-activitypub.ts +++ b/src/processor/http/perform-activitypub.ts @@ -1,5 +1,6 @@ import User from '../../models/user'; import act from '../../remote/activitypub/act'; +import Resolver from '../../remote/activitypub/resolver'; export default ({ data }) => User.findOne({ _id: data.actor }) - .then(actor => act(actor, data.outbox, false)); + .then(actor => act(new Resolver(), actor, data.outbox)); diff --git a/src/processor/http/process-inbox.ts b/src/processor/http/process-inbox.ts index 11801409cc..e75c0b5c54 100644 --- a/src/processor/http/process-inbox.ts +++ b/src/processor/http/process-inbox.ts @@ -3,6 +3,7 @@ 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 Resolver from '../../remote/activitypub/resolver'; export default async ({ data }) => { const keyIdLower = data.signature.keyId.toLowerCase(); @@ -34,5 +35,5 @@ export default async ({ data }) => { throw 'signature verification failed'; } - await act(user, data.inbox, true); + await act(new Resolver(), user, data.inbox, true); }; diff --git a/src/remote/activitypub/act/follow.ts b/src/remote/activitypub/act/follow.ts index d7ea15fa36..ec00b78bf9 100644 --- a/src/remote/activitypub/act/follow.ts +++ b/src/remote/activitypub/act/follow.ts @@ -1,6 +1,6 @@ import { MongoError } from 'mongodb'; import parseAcct from '../../../acct/parse'; -import Following from '../../../models/following'; +import Following, { IFollowing } from '../../../models/following'; import User from '../../../models/user'; import config from '../../../config'; import queue from '../../../queue'; @@ -8,7 +8,7 @@ import context from '../renderer/context'; import renderAccept from '../renderer/accept'; import request from '../../request'; -export default async (actor, activity) => { +export default async (resolver, actor, activity, distribute) => { const prefix = config.url + '/@'; const id = activity.object.id || activity.object; @@ -26,33 +26,52 @@ export default async (actor, activity) => { throw new Error(); } + if (!distribute) { + const { _id } = await Following.findOne({ + followerId: actor._id, + followeeId: followee._id + }) + + return { + resolver, + object: { $ref: 'following', $id: _id } + }; + } + + const promisedFollowing = Following.insert({ + createdAt: new Date(), + followerId: actor._id, + followeeId: followee._id + }).then(following => new Promise((resolve, reject) => { + queue.create('http', { + type: 'follow', + following: following._id + }).save(error => { + if (error) { + reject(error); + } else { + resolve(following); + } + }); + }) as Promise, async error => { + // duplicate key error + if (error instanceof MongoError && error.code === 11000) { + return Following.findOne({ + followerId: actor._id, + followeeId: followee._id + }); + } + + throw error; + }); + const accept = renderAccept(activity); accept['@context'] = context; - await Promise.all([ - request(followee, actor.account.inbox, accept), + await request(followee, actor.account.inbox, accept); - Following.insert({ - createdAt: new Date(), - followerId: actor._id, - followeeId: followee._id - }).then(following => new Promise((resolve, reject) => { - queue.create('http', { type: 'follow', following: following._id }).save(error => { - if (error) { - reject(error); - } else { - resolve(); - } - }); - }), error => { - // duplicate key error - if (error instanceof MongoError && error.code === 11000) { - return; - } - - throw error; - }) - ]); - - return null; + return promisedFollowing.then(({ _id }) => ({ + resolver, + object: { $ref: 'following', $id: _id } + })); }; diff --git a/src/remote/activitypub/act/index.ts b/src/remote/activitypub/act/index.ts index 24320dcb1d..3b7dd5b249 100644 --- a/src/remote/activitypub/act/index.ts +++ b/src/remote/activitypub/act/index.ts @@ -1,23 +1,26 @@ import create from './create'; import follow from './follow'; +import undo from './undo'; import createObject from '../create'; -import Resolver from '../resolver'; -export default (actor, value, distribute) => { - return new Resolver().resolve(value).then(resolved => Promise.all(resolved.map(async promisedResult => { - const { resolver, object } = await promisedResult; - const created = await (await createObject(resolver, actor, [object], distribute))[0]; +export default (resolver, actor, value, distribute?: boolean) => { + return resolver.resolve(value).then(resolved => Promise.all(resolved.map(async promisedResult => { + const result = await promisedResult; + const created = await (await createObject(result.resolver, actor, [result.object], distribute))[0]; if (created !== null) { return created; } - switch (object.type) { + switch (result.object.type) { case 'Create': - return create(resolver, actor, object, distribute); + return create(result.resolver, actor, result.object, distribute); case 'Follow': - return follow(actor, object); + return follow(result.resolver, actor, result.object, distribute); + + case 'Undo': + return undo(result.resolver, actor, result.object); default: return null; diff --git a/src/remote/activitypub/act/undo/index.ts b/src/remote/activitypub/act/undo/index.ts new file mode 100644 index 0000000000..b43ae86173 --- /dev/null +++ b/src/remote/activitypub/act/undo/index.ts @@ -0,0 +1,23 @@ +import act from '../../act'; +import unfollow from './unfollow'; + +export default async (resolver, actor, activity) => { + if ('actor' in activity && actor.account.uri !== activity.actor) { + throw new Error(); + } + + const results = await act(resolver, actor, activity.object); + + await Promise.all(results.map(async result => { + if (result === null) { + return; + } + + switch (result.object.$ref) { + case 'following': + await unfollow(result.resolver, result.object); + } + })); + + return null; +}; diff --git a/src/remote/activitypub/act/undo/unfollow.ts b/src/remote/activitypub/act/undo/unfollow.ts new file mode 100644 index 0000000000..0523699bfe --- /dev/null +++ b/src/remote/activitypub/act/undo/unfollow.ts @@ -0,0 +1,24 @@ +import FollowedLog from '../../../../models/followed-log'; +import Following from '../../../../models/following'; +import FollowingLog from '../../../../models/following-log'; +import User from '../../../../models/user'; + +export default async (resolver, { $id }) => { + const following = await Following.findOneAndDelete({ _id: $id }); + if (following === null) { + return; + } + + await Promise.all([ + User.update({ _id: following.followerId }, { $inc: { followingCount: -1 } }), + User.findOne({ _id: following.followerId }).then(({ followingCount }) => FollowingLog.insert({ + userId: following.followerId, + count: followingCount - 1 + })), + User.update({ _id: following.followeeId }, { $inc: { followersCount: -1 } }), + User.findOne({ _id: following.followeeId }).then(({ followersCount }) => FollowedLog.insert({ + userId: following.followeeId, + count: followersCount - 1 + })), + ]); +};