diff --git a/src/client/app/common/views/components/visibility-chooser.vue b/src/client/app/common/views/components/visibility-chooser.vue
index b6a8e9b66..dd36d32e7 100644
--- a/src/client/app/common/views/components/visibility-chooser.vue
+++ b/src/client/app/common/views/components/visibility-chooser.vue
@@ -22,11 +22,11 @@
 				<span>自分のフォロワーにのみ公開</span>
 			</div>
 		</div>
-		<div @click="choose('mentioned')" :class="{ active: v == 'mentioned' }">
+		<div @click="choose('specified')" :class="{ active: v == 'specified' }">
 			<div>%fa:envelope%</div>
 			<div>
-				<span>メンション</span>
-				<span>言及したユーザーにのみ公開</span>
+				<span>ダイレクト</span>
+				<span>指定したユーザーにのみ公開</span>
 			</div>
 		</div>
 		<div @click="choose('private')" :class="{ active: v == 'private' }">
diff --git a/src/models/note.ts b/src/models/note.ts
index 2f95cbfd6..5c4ac8635 100644
--- a/src/models/note.ts
+++ b/src/models/note.ts
@@ -12,6 +12,7 @@ import NoteWatching, { deleteNoteWatching } from './note-watching';
 import NoteReaction from './note-reaction';
 import Favorite, { deleteFavorite } from './favorite';
 import Notification, { deleteNotification } from './notification';
+import Following from './following';
 
 const Note = db.get<INote>('notes');
 
@@ -51,10 +52,12 @@ export type INote = {
 	 * public ... 公開
 	 * home ... ホームタイムライン(ユーザーページのタイムライン含む)のみに流す
 	 * followers ... フォロワーのみ
-	 * mentioned ... 言及したユーザーのみ
+	 * specified ... visibleUserIds で指定したユーザーのみ
 	 * private ... 自分のみ
 	 */
-	visibility: 'public' | 'home' | 'followers' | 'mentioned' | 'private';
+	visibility: 'public' | 'home' | 'followers' | 'specified' | 'private';
+
+	visibleUserIds: mongo.ObjectID[];
 
 	geo: {
 		coordinates: number[];
@@ -190,6 +193,52 @@ export const pack = async (
 
 	if (!_note) throw `invalid note arg ${note}`;
 
+	let hide = false;
+
+	// visibility が private かつ投稿者のIDが自分のIDではなかったら非表示
+	if (_note.visibility == 'private' && (meId == null || !meId.equals(_note.userId))) {
+		hide = true;
+	}
+
+	// visibility が specified かつ自分が指定されていなかったら非表示
+	if (_note.visibility == 'specified') {
+		if (meId == null) {
+			hide = true;
+		} else if (meId.equals(_note.userId)) {
+			hide = false;
+		} else {
+			// 指定されているかどうか
+			const specified = _note.visibleUserIds.test(id => id.equals(meId));
+
+			if (specified) {
+				hide = false;
+			} else {
+				hide = true;
+			}
+		}
+	}
+
+	// visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示
+	if (_note.visibility == 'followers') {
+		if (meId == null) {
+			hide = true;
+		} else if (meId.equals(_note.userId)) {
+			hide = false;
+		} else {
+			// フォロワーかどうか
+			const following = await Following.findOne({
+				followeeId: _note.userId,
+				followerId: meId
+			});
+
+			if (following == null) {
+				hide = true;
+			} else {
+				hide = false;
+			}
+		}
+	}
+
 	const id = _note._id;
 
 	// Rename _id to id
diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts
index af4f36522..52c6068fd 100644
--- a/src/server/api/endpoints/notes/create.ts
+++ b/src/server/api/endpoints/notes/create.ts
@@ -3,7 +3,7 @@
  */
 import $ from 'cafy'; import ID from '../../../../cafy-id';
 import Note, { INote, isValidText, isValidCw, pack } from '../../../../models/note';
-import { ILocalUser } from '../../../../models/user';
+import User, { ILocalUser } from '../../../../models/user';
 import Channel, { IChannel } from '../../../../models/channel';
 import DriveFile from '../../../../models/drive-file';
 import create from '../../../../services/note/create';
@@ -14,9 +14,20 @@ import { IApp } from '../../../../models/app';
  */
 module.exports = (params, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => {
 	// Get 'visibility' parameter
-	const [visibility = 'public', visibilityErr] = $(params.visibility).optional.string().or(['public', 'unlisted', 'private', 'direct']).get();
+	const [visibility = 'public', visibilityErr] = $(params.visibility).optional.string().or(['public', 'home', 'followers', 'specified', 'private']).get();
 	if (visibilityErr) return rej('invalid visibility');
 
+	// Get 'visibleUserIds' parameter
+	const [visibleUserIds, visibleUserIdsErr] = $(params.visibleUserIds).optional.array($().type(ID)).unique().min(1).get();
+	if (visibleUserIdsErr) return rej('invalid visibleUserIds');
+
+	let visibleUsers = [];
+	if (visibleUserIds !== undefined) {
+		visibleUsers = await Promise.all(visibleUserIds.map(id => User.findOne({
+			_id: id
+		})));
+	}
+
 	// Get 'text' parameter
 	const [text = null, textErr] = $(params.text).optional.nullable.string().pipe(isValidText).get();
 	if (textErr) return rej('invalid text');
@@ -191,6 +202,7 @@ module.exports = (params, user: ILocalUser, app: IApp) => new Promise(async (res
 		app,
 		viaMobile,
 		visibility,
+		visibleUsers,
 		geo
 	});
 
diff --git a/src/services/note/create.ts b/src/services/note/create.ts
index 4808edfda..e8070595c 100644
--- a/src/services/note/create.ts
+++ b/src/services/note/create.ts
@@ -30,6 +30,7 @@ export default async (user: IUser, data: {
 	tags?: string[];
 	cw?: string;
 	visibility?: string;
+	visibleUsers?: IUser[];
 	uri?: string;
 	app?: IApp;
 }, silent = false) => new Promise<INote>(async (res, rej) => {
@@ -57,6 +58,10 @@ export default async (user: IUser, data: {
 		});
 	}
 
+	if (data.visibleUsers) {
+		data.visibleUsers = data.visibleUsers.filter(x => x != null);
+	}
+
 	const insert: any = {
 		createdAt: data.createdAt,
 		mediaIds: data.media ? data.media.map(file => file._id) : [],
@@ -71,6 +76,7 @@ export default async (user: IUser, data: {
 		geo: data.geo || null,
 		appId: data.app ? data.app._id : null,
 		visibility: data.visibility,
+		visibleUserIds: data.visibleUsers ? data.visibleUsers.map(u => u._id) : [],
 
 		// 以下非正規化データ
 		_reply: data.reply ? { userId: data.reply.userId } : null,