Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User blocking (Following part) #3035

Merged
merged 7 commits into from
Oct 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1203,6 +1203,9 @@ desktop/views/pages/user/user.profile.vue:
mute: "ミュートする"
muted: "ミュートしています"
unmute: "ミュート解除"
block: "ブロックする"
unblock: "ブロック解除"
block-confirm: "このユーザーをブロックしますか?"
push-to-a-list: "リストに追加"
list-pushed: "{user}を{list}に追加しました。"

Expand Down
25 changes: 25 additions & 0 deletions src/client/app/desktop/views/pages/user/user.profile.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
<span v-if="user.isMuted">%fa:eye% %i18n:@unmute%</span>
<span v-if="!user.isMuted">%fa:eye-slash% %i18n:@mute%</span>
</ui-button>
<ui-button @click="user.isBlocking ? unblock() : block()" v-if="$store.state.i.id != user.id">
<span v-if="user.isBlocking">%fa:user% %i18n:@unblock%</span>
<span v-if="!user.isBlocking">%fa:user-slash% %i18n:@block%</span>
</ui-button>
<ui-button @click="list">%fa:list% %i18n:@push-to-a-list%</ui-button>
</div>
</div>
Expand Down Expand Up @@ -66,6 +70,27 @@ export default Vue.extend({
});
},

block() {
if (!window.confirm('%i18n:@block-confirm%')) return;
(this as any).api('blocking/create', {
userId: this.user.id
}).then(() => {
this.user.isBlocking = true;
}, () => {
alert('error');
});
},

unblock() {
(this as any).api('blocking/delete', {
userId: this.user.id
}).then(() => {
this.user.isBlocking = false;
}, () => {
alert('error');
});
},

list() {
const w = (this as any).os.new(MkUserListsWindow);
w.$once('choosen', async list => {
Expand Down
41 changes: 41 additions & 0 deletions src/models/blocking.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import * as mongo from 'mongodb';
import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';

const Blocking = db.get<IBlocking>('blocking');
Blocking.createIndex(['blockerId', 'blockeeId'], { unique: true });
export default Blocking;

export type IBlocking = {
_id: mongo.ObjectID;
createdAt: Date;
blockeeId: mongo.ObjectID;
blockerId: mongo.ObjectID;
};

/**
* Blockingを物理削除します
*/
export async function deleteBlocking(blocking: string | mongo.ObjectID | IBlocking) {
let f: IBlocking;

// Populate
if (isObjectId(blocking)) {
f = await Blocking.findOne({
_id: blocking
});
} else if (typeof blocking === 'string') {
f = await Blocking.findOne({
_id: new mongo.ObjectID(blocking)
});
} else {
f = blocking as IBlocking;
}

if (f == null) return;

// このBlockingを削除
await Blocking.remove({
_id: f._id
});
}
27 changes: 26 additions & 1 deletion src/models/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
import Note, { packMany as packNoteMany, deleteNote } from './note';
import Following, { deleteFollowing } from './following';
import Blocking, { deleteBlocking } from './blocking';
import Mute, { deleteMute } from './mute';
import { getFriendIds } from '../server/api/common/get-friends';
import config from '../config';
Expand Down Expand Up @@ -275,6 +276,16 @@ export async function deleteUser(user: string | mongo.ObjectID | IUser) {
await FollowRequest.find({ followeeId: u._id })
).map(x => deleteFollowRequest(x)));

// このユーザーのBlockingをすべて削除
await Promise.all((
await Blocking.find({ blockerId: u._id })
).map(x => deleteBlocking(x)));

// このユーザーへのBlockingをすべて削除
await Promise.all((
await Blocking.find({ blockeeId: u._id })
).map(x => deleteBlocking(x)));

// このユーザーのSwSubscriptionをすべて削除
await Promise.all((
await SwSubscription.find({ userId: u._id })
Expand Down Expand Up @@ -427,7 +438,7 @@ export const pack = (
}

if (meId && !meId.equals(_user.id)) {
const [following1, following2, followReq1, followReq2, mute] = await Promise.all([
const [following1, following2, followReq1, followReq2, toBlocking, fromBlocked, mute] = await Promise.all([
Following.findOne({
followerId: meId,
followeeId: _user.id
Expand All @@ -444,6 +455,14 @@ export const pack = (
followerId: _user.id,
followeeId: meId
}),
Blocking.findOne({
blockerId: meId,
blockeeId: _user.id
}),
Blocking.findOne({
blockerId: _user.id,
blockeeId: meId
}),
Mute.findOne({
muterId: meId,
muteeId: _user.id
Expand All @@ -460,6 +479,12 @@ export const pack = (
// Whether the user is followed
_user.isFollowed = following2 !== null;

// Whether the user is blocking
_user.isBlocking = toBlocking !== null;

// Whether the user is blocked
_user.isBlocked = fromBlocked !== null;

// Whether the user is muted
_user.isMuted = mute !== null;
}
Expand Down
34 changes: 34 additions & 0 deletions src/remote/activitypub/kernel/block/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as mongo from 'mongodb';
import User, { IRemoteUser } from '../../../../models/user';
import config from '../../../../config';
import * as debug from 'debug';
import { IBlock } from '../../type';
import block from '../../../../services/blocking/create';

const log = debug('misskey:activitypub');

export default async (actor: IRemoteUser, activity: IBlock): Promise<void> => {
const id = typeof activity.object == 'string' ? activity.object : activity.object.id;

const uri = activity.id || activity;

log(`Block: ${uri}`);

if (!id.startsWith(config.url + '/')) {
return null;
}

const blockee = await User.findOne({
_id: new mongo.ObjectID(id.split('/').pop())
});

if (blockee === null) {
throw new Error('blockee not found');
}

if (blockee.host != null) {
throw new Error('ブロックしようとしているユーザーはローカルユーザーではありません');
}

block(actor, blockee);
};
5 changes: 5 additions & 0 deletions src/remote/activitypub/kernel/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import accept from './accept';
import reject from './reject';
import add from './add';
import remove from './remove';
import block from './block';

const self = async (actor: IRemoteUser, activity: Object): Promise<void> => {
switch (activity.type) {
Expand Down Expand Up @@ -53,6 +54,10 @@ const self = async (actor: IRemoteUser, activity: Object): Promise<void> => {
await undo(actor, activity);
break;

case 'Block':
await block(actor, activity);
break;

case 'Collection':
case 'OrderedCollection':
// TODO
Expand Down
34 changes: 34 additions & 0 deletions src/remote/activitypub/kernel/undo/block.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as mongo from 'mongodb';
import User, { IRemoteUser } from '../../../../models/user';
import config from '../../../../config';
import * as debug from 'debug';
import { IBlock } from '../../type';
import unblock from '../../../../services/blocking/delete';

const log = debug('misskey:activitypub');

export default async (actor: IRemoteUser, activity: IBlock): Promise<void> => {
const id = typeof activity.object == 'string' ? activity.object : activity.object.id;

const uri = activity.id || activity;

log(`UnBlock: ${uri}`);

if (!id.startsWith(config.url + '/')) {
return null;
}

const blockee = await User.findOne({
_id: new mongo.ObjectID(id.split('/').pop())
});

if (blockee === null) {
throw new Error('blockee not found');
}

if (blockee.host != null) {
throw new Error('ブロック解除しようとしているユーザーはローカルユーザーではありません');
}

unblock(actor, blockee);
};
6 changes: 5 additions & 1 deletion src/remote/activitypub/kernel/undo/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as debug from 'debug';

import { IRemoteUser } from '../../../../models/user';
import { IUndo, IFollow } from '../../type';
import { IUndo, IFollow, IBlock } from '../../type';
import unfollow from './follow';
import unblock from './block';
import Resolver from '../../resolver';

const log = debug('misskey:activitypub');
Expand Down Expand Up @@ -31,6 +32,9 @@ export default async (actor: IRemoteUser, activity: IUndo): Promise<void> => {
case 'Follow':
unfollow(actor, object as IFollow);
break;
case 'Block':
unblock(actor, object as IBlock);
break;
}

return null;
Expand Down
8 changes: 8 additions & 0 deletions src/remote/activitypub/renderer/block.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import config from '../../../config';
import { ILocalUser, IRemoteUser } from "../../../models/user";

export default (blocker?: ILocalUser, blockee?: IRemoteUser) => ({
type: 'Block',
actor: `${config.url}/users/${blocker._id}`,
object: blockee.uri
});
7 changes: 6 additions & 1 deletion src/remote/activitypub/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ export interface IAnnounce extends IActivity {
type: 'Announce';
}

export interface IBlock extends IActivity {
type: 'Block';
}

export type Object =
ICollection |
IOrderedCollection |
Expand All @@ -120,4 +124,5 @@ export type Object =
IAdd |
IRemove |
ILike |
IAnnounce;
IAnnounce |
IBlock;
75 changes: 75 additions & 0 deletions src/server/api/endpoints/blocking/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
const ms = require('ms');
import User, { pack, ILocalUser } from '../../../../models/user';
import Blocking from '../../../../models/blocking';
import create from '../../../../services/blocking/create';
import getParams from '../../get-params';

export const meta = {
stability: 'stable',

desc: {
'ja-JP': '指定したユーザーをブロックします。',
'en-US': 'Block a user.'
},

limit: {
duration: ms('1hour'),
max: 100
},

requireCredential: true,

kind: 'following-write',

params: {
userId: $.type(ID).note({
desc: {
'ja-JP': '対象のユーザーのID',
'en-US': 'Target user ID'
}
})
}
};

export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) return rej(psErr);

const blocker = user;

// 自分自身
if (user._id.equals(ps.userId)) {
return rej('blockee is yourself');
}

// Get blockee
const blockee = await User.findOne({
_id: ps.userId
}, {
fields: {
data: false,
profile: false
}
});

if (blockee === null) {
return rej('user not found');
}

// Check if already blocking
const exist = await Blocking.findOne({
blockerId: blocker._id,
blockeeId: blockee._id
});

if (exist !== null) {
return rej('already blocking');
}

// Create blocking
await create(blocker, blockee);

// Send response
res(await pack(blockee._id, user));
});