From 9599a3123987b94def83c5d70e7dd4ddcc6bf649 Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 31 May 2018 18:08:47 +0900 Subject: [PATCH 01/21] wip --- src/models/follow-requests.ts | 50 +++++++++++++++++++++++++++++++++++ src/models/user.ts | 21 +++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 src/models/follow-requests.ts diff --git a/src/models/follow-requests.ts b/src/models/follow-requests.ts new file mode 100644 index 000000000..0de4b8e3a --- /dev/null +++ b/src/models/follow-requests.ts @@ -0,0 +1,50 @@ +import * as mongo from 'mongodb'; +import db from '../db/mongodb'; + +const FollowRequest = db.get('followRequests'); +FollowRequest.createIndex(['followerId', 'followeeId'], { unique: true }); +export default FollowRequest; + +export type IFollowRequest = { + _id: mongo.ObjectID; + createdAt: Date; + followeeId: mongo.ObjectID; + followerId: mongo.ObjectID; + + // 非正規化 + _followee: { + host: string; + inbox?: string; + }, + _follower: { + host: string; + inbox?: string; + } +}; + +/** + * FollowRequestを物理削除します + */ +export async function deleteFollowRequest(followRequest: string | mongo.ObjectID | IFollowRequest) { + let f: IFollowRequest; + + // Populate + if (mongo.ObjectID.prototype.isPrototypeOf(followRequest)) { + f = await FollowRequest.findOne({ + _id: followRequest + }); + } else if (typeof followRequest === 'string') { + f = await FollowRequest.findOne({ + _id: new mongo.ObjectID(followRequest) + }); + } else { + f = followRequest as IFollowRequest; + } + + if (f == null) return; + + // このFollowingを削除 + await FollowRequest.remove({ + _id: f._id + }); +} diff --git a/src/models/user.ts b/src/models/user.ts index 11eafe05e..8dfd783a9 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -22,6 +22,7 @@ import FollowedLog, { deleteFollowedLog } from './followed-log'; import SwSubscription, { deleteSwSubscription } from './sw-subscription'; import Notification, { deleteNotification } from './notification'; import UserList, { deleteUserList } from './user-list'; +import FollowRequest, { deleteFollowRequest } from './follow-requests'; const User = db.get('users'); @@ -50,7 +51,17 @@ type IUserBase = { data: any; description: string; pinnedNoteId: mongo.ObjectID; + + /** + * 凍結されているか否か + */ isSuspended: boolean; + + /** + * 鍵アカウントか否か + */ + isLocked: boolean; + host: string; }; @@ -240,6 +251,16 @@ export async function deleteUser(user: string | mongo.ObjectID | IUser) { await Following.find({ followeeId: u._id }) ).map(x => deleteFollowing(x))); + // このユーザーのFollowRequestをすべて削除 + await Promise.all(( + await FollowRequest.find({ followerId: u._id }) + ).map(x => deleteFollowRequest(x))); + + // このユーザーへのFollowRequestをすべて削除 + await Promise.all(( + await FollowRequest.find({ followeeId: u._id }) + ).map(x => deleteFollowRequest(x))); + // このユーザーのFollowingLogをすべて削除 await Promise.all(( await FollowingLog.find({ userId: u._id }) From 51929fb60786dbb1af0471e40e834adfe1c37b21 Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 31 May 2018 18:11:28 +0900 Subject: [PATCH 02/21] typo --- src/models/{follow-requests.ts => follow-request.ts} | 0 src/models/user.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/models/{follow-requests.ts => follow-request.ts} (100%) diff --git a/src/models/follow-requests.ts b/src/models/follow-request.ts similarity index 100% rename from src/models/follow-requests.ts rename to src/models/follow-request.ts diff --git a/src/models/user.ts b/src/models/user.ts index 8dfd783a9..aa051517f 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -22,7 +22,7 @@ import FollowedLog, { deleteFollowedLog } from './followed-log'; import SwSubscription, { deleteSwSubscription } from './sw-subscription'; import Notification, { deleteNotification } from './notification'; import UserList, { deleteUserList } from './user-list'; -import FollowRequest, { deleteFollowRequest } from './follow-requests'; +import FollowRequest, { deleteFollowRequest } from './follow-request'; const User = db.get('users'); From bd758a156ef81b628900930731042ee5b0ffb44f Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 31 May 2018 18:34:15 +0900 Subject: [PATCH 03/21] wip --- src/remote/activitypub/models/person.ts | 1 + src/remote/activitypub/renderer/person.ts | 4 +++- src/remote/activitypub/type.ts | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/remote/activitypub/models/person.ts b/src/remote/activitypub/models/person.ts index 33280f3d8..b720c445c 100644 --- a/src/remote/activitypub/models/person.ts +++ b/src/remote/activitypub/models/person.ts @@ -93,6 +93,7 @@ export async function createPerson(value: any, resolver?: Resolver): Promise { +export default (user: ILocalUser) => { const id = `${config.url}/users/${user._id}`; return { @@ -17,6 +18,7 @@ export default user => { summary: user.description, icon: user.avatarId && renderImage({ _id: user.avatarId }), image: user.bannerId && renderImage({ _id: user.bannerId }), + manuallyApprovesFollowers: user.isLocked, publicKey: renderKey(user) }; }; diff --git a/src/remote/activitypub/type.ts b/src/remote/activitypub/type.ts index ca38ec222..77e6bc304 100644 --- a/src/remote/activitypub/type.ts +++ b/src/remote/activitypub/type.ts @@ -45,6 +45,7 @@ export interface IPerson extends IObject { type: 'Person'; name: string; preferredUsername: string; + manuallyApprovesFollowers: boolean; inbox: string; publicKey: any; followers: any; From 51255bb4467d7f797686639d785edf0eede846ec Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 31 May 2018 22:56:02 +0900 Subject: [PATCH 04/21] wip --- .../activitypub/kernel/accept/follow.ts | 27 ++++ src/remote/activitypub/kernel/accept/index.ts | 35 +++++ src/remote/activitypub/renderer/follow.ts | 8 +- src/server/api/endpoints/i/update.ts | 47 +++--- src/services/following/create.ts | 141 +++++++++++------- src/services/following/delete.ts | 2 +- .../user/accept-all-follow-requests.ts | 18 +++ src/services/user/accept-follow-request.ts | 64 ++++++++ 8 files changed, 259 insertions(+), 83 deletions(-) create mode 100644 src/remote/activitypub/kernel/accept/follow.ts create mode 100644 src/remote/activitypub/kernel/accept/index.ts create mode 100644 src/services/user/accept-all-follow-requests.ts create mode 100644 src/services/user/accept-follow-request.ts diff --git a/src/remote/activitypub/kernel/accept/follow.ts b/src/remote/activitypub/kernel/accept/follow.ts new file mode 100644 index 000000000..9d425419a --- /dev/null +++ b/src/remote/activitypub/kernel/accept/follow.ts @@ -0,0 +1,27 @@ +import * as mongo from 'mongodb'; +import User, { IRemoteUser } from '../../../../models/user'; +import config from '../../../../config'; +import accept from '../../../../services/user/accept-follow-request'; +import { IFollow } from '../../type'; + +export default async (actor: IRemoteUser, activity: IFollow): Promise => { + const id = typeof activity.object == 'string' ? activity.object : activity.object.id; + + if (!id.startsWith(config.url + '/')) { + return null; + } + + const follower = await User.findOne({ + _id: new mongo.ObjectID(id.split('/').pop()) + }); + + if (follower === null) { + throw new Error('follower not found'); + } + + if (follower.host != null) { + throw new Error('フォローリクエストしたユーザーはローカルユーザーではありません'); + } + + await accept(actor, follower); +}; diff --git a/src/remote/activitypub/kernel/accept/index.ts b/src/remote/activitypub/kernel/accept/index.ts new file mode 100644 index 000000000..b647df022 --- /dev/null +++ b/src/remote/activitypub/kernel/accept/index.ts @@ -0,0 +1,35 @@ +import * as debug from 'debug'; + +import Resolver from '../../resolver'; +import { IRemoteUser } from '../../../../models/user'; +import acceptFollow from './follow'; +import { IAccept } from '../../type'; + +const log = debug('misskey:activitypub'); + +export default async (actor: IRemoteUser, activity: IAccept): Promise => { + const uri = activity.id || activity; + + log(`Accept: ${uri}`); + + const resolver = new Resolver(); + + let object; + + try { + object = await resolver.resolve(activity.object); + } catch (e) { + log(`Resolution failed: ${e}`); + throw e; + } + + switch (object.type) { + case 'Follow': + acceptFollow(resolver, actor, activity, object); + break; + + default: + console.warn(`Unknown accept type: ${object.type}`); + break; + } +}; diff --git a/src/remote/activitypub/renderer/follow.ts b/src/remote/activitypub/renderer/follow.ts index bf8eeff06..522422bcf 100644 --- a/src/remote/activitypub/renderer/follow.ts +++ b/src/remote/activitypub/renderer/follow.ts @@ -1,8 +1,8 @@ import config from '../../../config'; -import { IRemoteUser, ILocalUser } from '../../../models/user'; +import { IUser, isLocalUser } from '../../../models/user'; -export default (follower: ILocalUser, followee: IRemoteUser) => ({ +export default (follower: IUser, followee: IUser) => ({ type: 'Follow', - actor: `${config.url}/users/${follower._id}`, - object: followee.uri + actor: isLocalUser(follower) ? `${config.url}/users/${follower._id}` : follower.uri, + object: isLocalUser(followee) ? `${config.url}/users/${followee._id}` : followee.uri }); diff --git a/src/server/api/endpoints/i/update.ts b/src/server/api/endpoints/i/update.ts index 6e0c5b851..5ca54d013 100644 --- a/src/server/api/endpoints/i/update.ts +++ b/src/server/api/endpoints/i/update.ts @@ -12,50 +12,57 @@ import DriveFile from '../../../../models/drive-file'; module.exports = async (params, user, app) => new Promise(async (res, rej) => { const isSecure = user != null && app == null; + const updates = {} as any; + // Get 'name' parameter const [name, nameErr] = $.str.optional().nullable().pipe(isValidName).get(params.name); if (nameErr) return rej('invalid name param'); - if (name) user.name = name; + if (name) updates.name = name; // Get 'description' parameter const [description, descriptionErr] = $.str.optional().nullable().pipe(isValidDescription).get(params.description); if (descriptionErr) return rej('invalid description param'); - if (description !== undefined) user.description = description; + if (description !== undefined) updates.description = description; // Get 'location' parameter const [location, locationErr] = $.str.optional().nullable().pipe(isValidLocation).get(params.location); if (locationErr) return rej('invalid location param'); - if (location !== undefined) user.profile.location = location; + if (location !== undefined) updates.profile.location = location; // Get 'birthday' parameter const [birthday, birthdayErr] = $.str.optional().nullable().pipe(isValidBirthday).get(params.birthday); if (birthdayErr) return rej('invalid birthday param'); - if (birthday !== undefined) user.profile.birthday = birthday; + if (birthday !== undefined) updates.profile.birthday = birthday; // Get 'avatarId' parameter const [avatarId, avatarIdErr] = $.type(ID).optional().get(params.avatarId); if (avatarIdErr) return rej('invalid avatarId param'); - if (avatarId) user.avatarId = avatarId; + if (avatarId) updates.avatarId = avatarId; // Get 'bannerId' parameter const [bannerId, bannerIdErr] = $.type(ID).optional().get(params.bannerId); if (bannerIdErr) return rej('invalid bannerId param'); - if (bannerId) user.bannerId = bannerId; + if (bannerId) updates.bannerId = bannerId; + + // Get 'isLocked' parameter + const [isLocked, isLockedErr] = $.bool.optional().get(params.isLocked); + if (isLockedErr) return rej('invalid isLocked param'); + if (isLocked != null) updates.isLocked = isLocked; // Get 'isBot' parameter const [isBot, isBotErr] = $.bool.optional().get(params.isBot); if (isBotErr) return rej('invalid isBot param'); - if (isBot != null) user.isBot = isBot; + if (isBot != null) updates.isBot = isBot; // Get 'isCat' parameter const [isCat, isCatErr] = $.bool.optional().get(params.isCat); if (isCatErr) return rej('invalid isCat param'); - if (isCat != null) user.isCat = isCat; + if (isCat != null) updates.isCat = isCat; // Get 'autoWatch' parameter const [autoWatch, autoWatchErr] = $.bool.optional().get(params.autoWatch); if (autoWatchErr) return rej('invalid autoWatch param'); - if (autoWatch != null) user.settings.autoWatch = autoWatch; + if (autoWatch != null) updates.settings.autoWatch = autoWatch; if (avatarId) { const avatar = await DriveFile.findOne({ @@ -63,7 +70,7 @@ module.exports = async (params, user, app) => new Promise(async (res, rej) => { }); if (avatar != null && avatar.metadata.properties.avgColor) { - user.avatarColor = avatar.metadata.properties.avgColor; + updates.avatarColor = avatar.metadata.properties.avgColor; } } @@ -73,23 +80,12 @@ module.exports = async (params, user, app) => new Promise(async (res, rej) => { }); if (banner != null && banner.metadata.properties.avgColor) { - user.bannerColor = banner.metadata.properties.avgColor; + updates.bannerColor = banner.metadata.properties.avgColor; } } await User.update(user._id, { - $set: { - name: user.name, - description: user.description, - avatarId: user.avatarId, - avatarColor: user.avatarColor, - bannerId: user.bannerId, - bannerColor: user.bannerColor, - profile: user.profile, - isBot: user.isBot, - isCat: user.isCat, - settings: user.settings - } + $set: updates }); // Serialize @@ -103,4 +99,9 @@ module.exports = async (params, user, app) => new Promise(async (res, rej) => { // Publish i updated event event(user._id, 'i_updated', iObj); + + // 鍵垢を解除したとき、溜まっていたフォローリクエストがあるならすべて承認 + if (user.isLocked && isLocked === false) { + acceptAllFollowRequests(user); + } }); diff --git a/src/services/following/create.ts b/src/services/following/create.ts index 3424c55da..03a8f399e 100644 --- a/src/services/following/create.ts +++ b/src/services/following/create.ts @@ -8,72 +8,103 @@ import pack from '../../remote/activitypub/renderer'; import renderFollow from '../../remote/activitypub/renderer/follow'; import renderAccept from '../../remote/activitypub/renderer/accept'; import { deliver } from '../../queue'; +import FollowRequest from '../../models/follow-request'; -export default async function(follower: IUser, followee: IUser, activity?) { - const following = await Following.insert({ - createdAt: new Date(), - followerId: follower._id, - followeeId: followee._id, - stalk: true, +export default async function(follower: IUser, followee: IUser) { + if (followee.isLocked) { + await FollowRequest.insert({ + createdAt: new Date(), + followerId: follower._id, + followeeId: followee._id, - // 非正規化 - _follower: { - host: follower.host, - inbox: isRemoteUser(follower) ? follower.inbox : undefined - }, - _followee: { - host: followee.host, - inbox: isRemoteUser(followee) ? followee.inbox : undefined + // 非正規化 + _follower: { + host: follower.host, + inbox: isRemoteUser(follower) ? follower.inbox : undefined + }, + _followee: { + host: followee.host, + inbox: isRemoteUser(followee) ? followee.inbox : undefined + } + }); + + // Publish reciveRequest event + if (isLocalUser(followee)) { + packUser(follower, followee).then(packed => event(followee._id, 'reciveRequest', packed)), + + // 通知を作成 + notify(followee._id, follower._id, 'reciveRequest'); } - }); - //#region Increment following count - User.update({ _id: follower._id }, { - $inc: { - followingCount: 1 + if (isLocalUser(follower) && isRemoteUser(followee)) { + const content = pack(renderFollow(follower, followee)); + deliver(follower, content, followee.inbox); } - }); + } else { + const following = await Following.insert({ + createdAt: new Date(), + followerId: follower._id, + followeeId: followee._id, - FollowingLog.insert({ - createdAt: following.createdAt, - userId: follower._id, - count: follower.followingCount + 1 - }); - //#endregion + // 非正規化 + _follower: { + host: follower.host, + inbox: isRemoteUser(follower) ? follower.inbox : undefined + }, + _followee: { + host: followee.host, + inbox: isRemoteUser(followee) ? followee.inbox : undefined + } + }); - //#region Increment followers count - User.update({ _id: followee._id }, { - $inc: { - followersCount: 1 + //#region Increment following count + User.update({ _id: follower._id }, { + $inc: { + followingCount: 1 + } + }); + + FollowingLog.insert({ + createdAt: following.createdAt, + userId: follower._id, + count: follower.followingCount + 1 + }); + //#endregion + + //#region Increment followers count + User.update({ _id: followee._id }, { + $inc: { + followersCount: 1 + } + }); + FollowedLog.insert({ + createdAt: following.createdAt, + userId: followee._id, + count: followee.followersCount + 1 + }); + //#endregion + + // Publish follow event + if (isLocalUser(follower)) { + packUser(followee, follower).then(packed => event(follower._id, 'follow', packed)); } - }); - FollowedLog.insert({ - createdAt: following.createdAt, - userId: followee._id, - count: followee.followersCount + 1 - }); - //#endregion - // Publish follow event - if (isLocalUser(follower)) { - packUser(followee, follower).then(packed => event(follower._id, 'follow', packed)); - } + // Publish followed event + if (isLocalUser(followee)) { + packUser(follower, followee).then(packed => event(followee._id, 'followed', packed)), - // Publish followed event - if (isLocalUser(followee)) { - packUser(follower, followee).then(packed => event(followee._id, 'followed', packed)), + // 通知を作成 + notify(followee._id, follower._id, 'follow'); + } - // 通知を作成 - notify(followee._id, follower._id, 'follow'); - } + if (isLocalUser(follower) && isRemoteUser(followee)) { + const content = pack(renderFollow(follower, followee)); + deliver(follower, content, followee.inbox); + } - if (isLocalUser(follower) && isRemoteUser(followee)) { - const content = pack(renderFollow(follower, followee)); - deliver(follower, content, followee.inbox); - } - - if (isRemoteUser(follower) && isLocalUser(followee)) { - const content = pack(renderAccept(activity)); - deliver(followee, content, follower.inbox); + if (isRemoteUser(follower) && isLocalUser(followee)) { + const content = pack(renderAccept(renderFollow(follower, followee))); + deliver(followee, content, follower.inbox); + } } } diff --git a/src/services/following/delete.ts b/src/services/following/delete.ts index c0c99fbed..4fc5d4247 100644 --- a/src/services/following/delete.ts +++ b/src/services/following/delete.ts @@ -8,7 +8,7 @@ import renderFollow from '../../remote/activitypub/renderer/follow'; import renderUndo from '../../remote/activitypub/renderer/undo'; import { deliver } from '../../queue'; -export default async function(follower: IUser, followee: IUser, activity?) { +export default async function(follower: IUser, followee: IUser) { const following = await Following.findOne({ followerId: follower._id, followeeId: followee._id diff --git a/src/services/user/accept-all-follow-requests.ts b/src/services/user/accept-all-follow-requests.ts new file mode 100644 index 000000000..fbb221e77 --- /dev/null +++ b/src/services/user/accept-all-follow-requests.ts @@ -0,0 +1,18 @@ +import User, { IUser } from "../../models/user"; +import FollowRequest from "../../models/follow-request"; +import accept from './accept-follow-request'; + +/** + * 指定したユーザー宛てのフォローリクエストをすべて承認 + * @param user ユーザー + */ +export default async function(user: IUser) { + const requests = await FollowRequest.find({ + followeeId: user._id + }); + + requests.forEach(async request => { + const follower = await User.findOne({ _id: request.followerId }); + accept(user, follower); + }); +} diff --git a/src/services/user/accept-follow-request.ts b/src/services/user/accept-follow-request.ts new file mode 100644 index 000000000..8b5c82c84 --- /dev/null +++ b/src/services/user/accept-follow-request.ts @@ -0,0 +1,64 @@ +import User, { IUser, isRemoteUser, ILocalUser } from "../../models/user"; +import FollowRequest from "../../models/follow-request"; +import pack from '../../remote/activitypub/renderer'; +import renderFollow from '../../remote/activitypub/renderer/follow'; +import renderAccept from '../../remote/activitypub/renderer/accept'; +import { deliver } from '../../queue'; +import Following from "../../models/following"; +import FollowingLog from "../../models/following-log"; +import FollowedLog from "../../models/followed-log"; + +export default async function(followee: IUser, follower: IUser) { + const following = await Following.insert({ + createdAt: new Date(), + followerId: follower._id, + followeeId: followee._id, + + // 非正規化 + _follower: { + host: follower.host, + inbox: isRemoteUser(follower) ? follower.inbox : undefined + }, + _followee: { + host: followee.host, + inbox: isRemoteUser(followee) ? followee.inbox : undefined + } + }); + + if (isRemoteUser(follower)) { + const content = pack(renderAccept(renderFollow(follower, followee))); + deliver(followee as ILocalUser, content, follower.inbox); + } + + FollowRequest.remove({ + followeeId: followee._id, + followerId: follower._id + }); + + //#region Increment following count + User.update({ _id: follower._id }, { + $inc: { + followingCount: 1 + } + }); + + FollowingLog.insert({ + createdAt: following.createdAt, + userId: follower._id, + count: follower.followingCount + 1 + }); + //#endregion + + //#region Increment followers count + User.update({ _id: followee._id }, { + $inc: { + followersCount: 1 + } + }); + FollowedLog.insert({ + createdAt: following.createdAt, + userId: followee._id, + count: followee.followersCount + 1 + }); + //#endregion +} From 35115607bc7eb1ff974c7f7c4d9f14bc8377ee6e Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 1 Jun 2018 00:42:37 +0900 Subject: [PATCH 05/21] wip --- src/models/user.ts | 13 ++++++- src/remote/activitypub/kernel/accept/index.ts | 2 +- src/remote/activitypub/kernel/index.ts | 8 ++++- .../activitypub/kernel/reject/follow.ts | 27 ++++++++++++++ src/remote/activitypub/kernel/reject/index.ts | 35 +++++++++++++++++++ src/remote/activitypub/renderer/reject.ts | 4 +++ src/remote/activitypub/type.ts | 5 +++ .../api/endpoints/users/recommendation.ts | 1 + src/services/user/reject-follow-request.ts | 18 ++++++++++ 9 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 src/remote/activitypub/kernel/reject/follow.ts create mode 100644 src/remote/activitypub/kernel/reject/index.ts create mode 100644 src/remote/activitypub/renderer/reject.ts create mode 100644 src/services/user/reject-follow-request.ts diff --git a/src/models/user.ts b/src/models/user.ts index aa051517f..4186241a5 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -416,7 +416,7 @@ export const pack = ( } if (meId && !meId.equals(_user.id)) { - const [following1, following2, mute] = await Promise.all([ + const [following1, following2, followReq1, followReq2, mute] = await Promise.all([ Following.findOne({ followerId: meId, followeeId: _user.id @@ -425,6 +425,14 @@ export const pack = ( followerId: _user.id, followeeId: meId }), + _user.isLocked ? FollowRequest.findOne({ + followerId: meId, + followeeId: _user.id + }) : Promise.resolve(null), + FollowRequest.findOne({ + followerId: _user.id, + followeeId: meId + }), Mute.findOne({ muterId: meId, muteeId: _user.id @@ -435,6 +443,9 @@ export const pack = ( _user.isFollowing = following1 !== null; _user.isStalking = following1 && following1.stalk; + _user.hasPendingFollowRequestFromYou = followReq1 !== null; + _user.hasPendingFollowRequestToYou = followReq2 !== null; + // Whether the user is followed _user.isFollowed = following2 !== null; diff --git a/src/remote/activitypub/kernel/accept/index.ts b/src/remote/activitypub/kernel/accept/index.ts index b647df022..2f9d646d1 100644 --- a/src/remote/activitypub/kernel/accept/index.ts +++ b/src/remote/activitypub/kernel/accept/index.ts @@ -25,7 +25,7 @@ export default async (actor: IRemoteUser, activity: IAccept): Promise => { switch (object.type) { case 'Follow': - acceptFollow(resolver, actor, activity, object); + acceptFollow(actor, object); break; default: diff --git a/src/remote/activitypub/kernel/index.ts b/src/remote/activitypub/kernel/index.ts index 15ea9494a..752a9bd2e 100644 --- a/src/remote/activitypub/kernel/index.ts +++ b/src/remote/activitypub/kernel/index.ts @@ -6,6 +6,8 @@ import follow from './follow'; import undo from './undo'; import like from './like'; import announce from './announce'; +import accept from './accept'; +import reject from './reject'; const self = async (actor: IRemoteUser, activity: Object): Promise => { switch (activity.type) { @@ -22,7 +24,11 @@ const self = async (actor: IRemoteUser, activity: Object): Promise => { break; case 'Accept': - // noop + await accept(actor, activity); + break; + + case 'Reject': + await reject(actor, activity); break; case 'Announce': diff --git a/src/remote/activitypub/kernel/reject/follow.ts b/src/remote/activitypub/kernel/reject/follow.ts new file mode 100644 index 000000000..862f3def3 --- /dev/null +++ b/src/remote/activitypub/kernel/reject/follow.ts @@ -0,0 +1,27 @@ +import * as mongo from 'mongodb'; +import User, { IRemoteUser } from '../../../../models/user'; +import config from '../../../../config'; +import reject from '../../../../services/user/reject-follow-request'; +import { IFollow } from '../../type'; + +export default async (actor: IRemoteUser, activity: IFollow): Promise => { + const id = typeof activity.object == 'string' ? activity.object : activity.object.id; + + if (!id.startsWith(config.url + '/')) { + return null; + } + + const follower = await User.findOne({ + _id: new mongo.ObjectID(id.split('/').pop()) + }); + + if (follower === null) { + throw new Error('follower not found'); + } + + if (follower.host != null) { + throw new Error('フォローリクエストしたユーザーはローカルユーザーではありません'); + } + + await reject(actor, follower); +}; diff --git a/src/remote/activitypub/kernel/reject/index.ts b/src/remote/activitypub/kernel/reject/index.ts new file mode 100644 index 000000000..a82c3fd61 --- /dev/null +++ b/src/remote/activitypub/kernel/reject/index.ts @@ -0,0 +1,35 @@ +import * as debug from 'debug'; + +import Resolver from '../../resolver'; +import { IRemoteUser } from '../../../../models/user'; +import rejectFollow from './follow'; +import { IReject } from '../../type'; + +const log = debug('misskey:activitypub'); + +export default async (actor: IRemoteUser, activity: IReject): Promise => { + const uri = activity.id || activity; + + log(`Reject: ${uri}`); + + const resolver = new Resolver(); + + let object; + + try { + object = await resolver.resolve(activity.object); + } catch (e) { + log(`Resolution failed: ${e}`); + throw e; + } + + switch (object.type) { + case 'Follow': + rejectFollow(actor, object); + break; + + default: + console.warn(`Unknown reject type: ${object.type}`); + break; + } +}; diff --git a/src/remote/activitypub/renderer/reject.ts b/src/remote/activitypub/renderer/reject.ts new file mode 100644 index 000000000..29c998a6b --- /dev/null +++ b/src/remote/activitypub/renderer/reject.ts @@ -0,0 +1,4 @@ +export default object => ({ + type: 'Reject', + object +}); diff --git a/src/remote/activitypub/type.ts b/src/remote/activitypub/type.ts index 77e6bc304..13e1969b4 100644 --- a/src/remote/activitypub/type.ts +++ b/src/remote/activitypub/type.ts @@ -83,6 +83,10 @@ export interface IAccept extends IActivity { type: 'Accept'; } +export interface IReject extends IActivity { + type: 'Reject'; +} + export interface ILike extends IActivity { type: 'Like'; _misskey_reaction: string; @@ -100,5 +104,6 @@ export type Object = IUndo | IFollow | IAccept | + IReject | ILike | IAnnounce; diff --git a/src/server/api/endpoints/users/recommendation.ts b/src/server/api/endpoints/users/recommendation.ts index 620ae17ca..23821a552 100644 --- a/src/server/api/endpoints/users/recommendation.ts +++ b/src/server/api/endpoints/users/recommendation.ts @@ -36,6 +36,7 @@ module.exports = (params, me) => new Promise(async (res, rej) => { _id: { $nin: followingIds.concat(mutedUserIds) }, + isLocked: false, $or: [{ lastUsedAt: { $gte: new Date(Date.now() - ms('7days')) diff --git a/src/services/user/reject-follow-request.ts b/src/services/user/reject-follow-request.ts new file mode 100644 index 000000000..32965a155 --- /dev/null +++ b/src/services/user/reject-follow-request.ts @@ -0,0 +1,18 @@ +import User, { IUser, isRemoteUser, ILocalUser } from "../../models/user"; +import FollowRequest from "../../models/follow-request"; +import pack from '../../remote/activitypub/renderer'; +import renderFollow from '../../remote/activitypub/renderer/follow'; +import renderReject from '../../remote/activitypub/renderer/reject'; +import { deliver } from '../../queue'; + +export default async function(followee: IUser, follower: IUser) { + if (isRemoteUser(follower)) { + const content = pack(renderReject(renderFollow(follower, followee))); + deliver(followee as ILocalUser, content, follower.inbox); + } + + FollowRequest.remove({ + followeeId: followee._id, + followerId: follower._id + }); +} From bae44b47086d8a0f0c8f96254b9453a29490a508 Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 1 Jun 2018 01:12:02 +0900 Subject: [PATCH 06/21] wip --- src/models/user.ts | 5 +++++ src/server/api/endpoints/i/update.ts | 1 + src/services/following/create.ts | 6 ++++++ src/services/user/accept-all-follow-requests.ts | 6 ++++++ src/services/user/reject-follow-request.ts | 8 +++++++- 5 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/models/user.ts b/src/models/user.ts index 4186241a5..0e06512da 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -62,6 +62,11 @@ type IUserBase = { */ isLocked: boolean; + /** + * このアカウントに届いているフォローリクエストの数 + */ + pendingReceivedFollowRequestsCount: number; + host: string; }; diff --git a/src/server/api/endpoints/i/update.ts b/src/server/api/endpoints/i/update.ts index 5ca54d013..103cd3ac3 100644 --- a/src/server/api/endpoints/i/update.ts +++ b/src/server/api/endpoints/i/update.ts @@ -5,6 +5,7 @@ import $ from 'cafy'; import ID from '../../../../cafy-id'; import User, { isValidName, isValidDescription, isValidLocation, isValidBirthday, pack } from '../../../../models/user'; import event from '../../../../publishers/stream'; import DriveFile from '../../../../models/drive-file'; +import acceptAllFollowRequests from '../../../../services/user/accept-all-follow-requests'; /** * Update myself diff --git a/src/services/following/create.ts b/src/services/following/create.ts index 03a8f399e..39cecfddf 100644 --- a/src/services/following/create.ts +++ b/src/services/following/create.ts @@ -28,6 +28,12 @@ export default async function(follower: IUser, followee: IUser) { } }); + User.update({ _id: followee._id }, { + $inc: { + pendingReceivedFollowRequestsCount: 1 + } + }); + // Publish reciveRequest event if (isLocalUser(followee)) { packUser(follower, followee).then(packed => event(followee._id, 'reciveRequest', packed)), diff --git a/src/services/user/accept-all-follow-requests.ts b/src/services/user/accept-all-follow-requests.ts index fbb221e77..397a0d7ec 100644 --- a/src/services/user/accept-all-follow-requests.ts +++ b/src/services/user/accept-all-follow-requests.ts @@ -15,4 +15,10 @@ export default async function(user: IUser) { const follower = await User.findOne({ _id: request.followerId }); accept(user, follower); }); + + User.update({ _id: user._id }, { + $set: { + pendingReceivedFollowRequestsCount: 0 + } + }); } diff --git a/src/services/user/reject-follow-request.ts b/src/services/user/reject-follow-request.ts index 32965a155..c6388f53e 100644 --- a/src/services/user/reject-follow-request.ts +++ b/src/services/user/reject-follow-request.ts @@ -11,8 +11,14 @@ export default async function(followee: IUser, follower: IUser) { deliver(followee as ILocalUser, content, follower.inbox); } - FollowRequest.remove({ + await FollowRequest.remove({ followeeId: followee._id, followerId: follower._id }); + + User.update({ _id: followee._id }, { + $inc: { + pendingReceivedFollowRequestsCount: -1 + } + }); } From 0be790fa31d28f7efc0d0b5b847c5c93a60f0dbd Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 1 Jun 2018 21:55:27 +0900 Subject: [PATCH 07/21] wip --- .../views/components/notifications.vue | 21 +++++++++++ .../views/components/notification-preview.vue | 35 ++++++++++++------- .../mobile/views/components/notification.vue | 15 ++++++++ src/server/api/endpoints.ts | 10 ++++++ .../api/endpoints/following/request/accept.ts | 26 ++++++++++++++ .../api/endpoints/following/request/reject.ts | 26 ++++++++++++++ 6 files changed, 120 insertions(+), 13 deletions(-) create mode 100644 src/server/api/endpoints/following/request/accept.ts create mode 100644 src/server/api/endpoints/following/request/reject.ts diff --git a/src/client/app/desktop/views/components/notifications.vue b/src/client/app/desktop/views/components/notifications.vue index 5564dad62..aff21b14d 100644 --- a/src/client/app/desktop/views/components/notifications.vue +++ b/src/client/app/desktop/views/components/notifications.vue @@ -5,6 +5,7 @@