From b5ff2abdb9ee0c086c8970c738cce5d61761f8f5 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Wed, 5 Sep 2018 23:55:51 +0900
Subject: [PATCH] =?UTF-8?q?=E4=BA=92=E6=8F=9B=E6=80=A7=E3=81=AE=E3=81=9F?=
 =?UTF-8?q?=E3=82=81=E3=81=AE=E3=82=B3=E3=83=BC=E3=83=89=E3=82=92=E8=BF=BD?=
 =?UTF-8?q?=E5=8A=A0=20&=20#2623?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/server/api/endpoints/notes.ts             | 128 ++++++-----
 .../api/endpoints/notes/global-timeline.ts    |  83 ++++---
 .../api/endpoints/notes/hybrid-timeline.ts    |  12 +-
 .../api/endpoints/notes/local-timeline.ts     |  83 ++++---
 .../api/endpoints/notes/search_by_tag.ts      | 211 ++++++++++--------
 src/server/api/endpoints/notes/timeline.ts    |  12 +-
 .../api/endpoints/notes/user-list-timeline.ts |  12 +-
 src/server/api/endpoints/users/notes.ts       | 178 ++++++++++-----
 8 files changed, 435 insertions(+), 284 deletions(-)

diff --git a/src/server/api/endpoints/notes.ts b/src/server/api/endpoints/notes.ts
index 4d15e9483f..5fa58d19de 100644
--- a/src/server/api/endpoints/notes.ts
+++ b/src/server/api/endpoints/notes.ts
@@ -1,51 +1,65 @@
-/**
- * Module dependencies
- */
 import $ from 'cafy'; import ID from '../../../misc/cafy-id';
 import Note, { pack } from '../../../models/note';
+import getParams from '../get-params';
+
+export const meta = {
+	desc: {
+		'ja-JP': '投稿を取得します。'
+	},
+
+	params: {
+		local: $.bool.optional.note({
+			desc: {
+				'ja-JP': 'ローカルの投稿に限定するか否か'
+			}
+		}),
+
+		reply: $.bool.optional.note({
+			desc: {
+				'ja-JP': '返信に限定するか否か'
+			}
+		}),
+
+		renote: $.bool.optional.note({
+			desc: {
+				'ja-JP': 'Renoteに限定するか否か'
+			}
+		}),
+
+		withFiles: $.bool.optional.note({
+			desc: {
+				'ja-JP': 'ファイルが添付された投稿に限定するか否か'
+			}
+		}),
+
+		media: $.bool.optional.note({
+			desc: {
+				'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
+			}
+		}),
+
+		poll: $.bool.optional.note({
+			desc: {
+				'ja-JP': 'アンケートが添付された投稿に限定するか否か'
+			}
+		}),
+
+		limit: $.num.optional.range(1, 100).note({
+			default: 10
+		}),
+
+		sinceId: $.type(ID).optional.note({}),
+
+		untilId: $.type(ID).optional.note({}),
+	}
+};
 
-/**
- * Get all notes
- */
 export default (params: any) => new Promise(async (res, rej) => {
-	// Get 'local' parameter
-	const [local, localErr] = $.bool.optional.get(params.local);
-	if (localErr) return rej('invalid local param');
-
-	// Get 'reply' parameter
-	const [reply, replyErr] = $.bool.optional.get(params.reply);
-	if (replyErr) return rej('invalid reply param');
-
-	// Get 'renote' parameter
-	const [renote, renoteErr] = $.bool.optional.get(params.renote);
-	if (renoteErr) return rej('invalid renote param');
-
-	// Get 'files' parameter
-	const [files, filesErr] = $.bool.optional.get(params.files);
-	if (filesErr) return rej('invalid files param');
-
-	// Get 'poll' parameter
-	const [poll, pollErr] = $.bool.optional.get(params.poll);
-	if (pollErr) return rej('invalid poll param');
-
-	// Get 'bot' parameter
-	//const [bot, botErr] = $.bool.optional.get(params.bot);
-	//if (botErr) return rej('invalid bot param');
-
-	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
-	if (limitErr) return rej('invalid limit param');
-
-	// Get 'sinceId' parameter
-	const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId);
-	if (sinceIdErr) return rej('invalid sinceId param');
-
-	// Get 'untilId' parameter
-	const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId);
-	if (untilIdErr) return rej('invalid untilId param');
+	const [ps, psErr] = getParams(meta, params);
+	if (psErr) throw psErr;
 
 	// Check if both of sinceId and untilId is specified
-	if (sinceId && untilId) {
+	if (ps.sinceId && ps.untilId) {
 		return rej('cannot set sinceId and untilId');
 	}
 
@@ -56,35 +70,37 @@ export default (params: any) => new Promise(async (res, rej) => {
 	const query = {
 		visibility: 'public'
 	} as any;
-	if (sinceId) {
+	if (ps.sinceId) {
 		sort._id = 1;
 		query._id = {
-			$gt: sinceId
+			$gt: ps.sinceId
 		};
-	} else if (untilId) {
+	} else if (ps.untilId) {
 		query._id = {
-			$lt: untilId
+			$lt: ps.untilId
 		};
 	}
 
-	if (local) {
+	if (ps.local) {
 		query['_user.host'] = null;
 	}
 
-	if (reply != undefined) {
-		query.replyId = reply ? { $exists: true, $ne: null } : null;
+	if (ps.reply != undefined) {
+		query.replyId = ps.reply ? { $exists: true, $ne: null } : null;
 	}
 
-	if (renote != undefined) {
-		query.renoteId = renote ? { $exists: true, $ne: null } : null;
+	if (ps.renote != undefined) {
+		query.renoteId = ps.renote ? { $exists: true, $ne: null } : null;
 	}
 
-	if (files != undefined) {
-		query.fileIds = files ? { $exists: true, $ne: null } : [];
+	const withFiles = ps.withFiles != undefined ? ps.withFiles : ps.media;
+
+	if (withFiles) {
+		query.fileIds = withFiles ? { $exists: true, $ne: null } : [];
 	}
 
-	if (poll != undefined) {
-		query.poll = poll ? { $exists: true, $ne: null } : null;
+	if (ps.poll != undefined) {
+		query.poll = ps.poll ? { $exists: true, $ne: null } : null;
 	}
 
 	// TODO
@@ -95,7 +111,7 @@ export default (params: any) => new Promise(async (res, rej) => {
 	// Issue query
 	const notes = await Note
 		.find(query, {
-			limit: limit,
+			limit: ps.limit,
 			sort: sort
 		});
 
diff --git a/src/server/api/endpoints/notes/global-timeline.ts b/src/server/api/endpoints/notes/global-timeline.ts
index 554245a0f4..e70fc5d76f 100644
--- a/src/server/api/endpoints/notes/global-timeline.ts
+++ b/src/server/api/endpoints/notes/global-timeline.ts
@@ -3,40 +3,49 @@ import Note from '../../../../models/note';
 import Mute from '../../../../models/mute';
 import { pack } from '../../../../models/note';
 import { ILocalUser } from '../../../../models/user';
+import getParams from '../../get-params';
+
+export const meta = {
+	desc: {
+		'ja-JP': 'グローバルタイムラインを取得します。'
+	},
+
+	params: {
+		withFiles: $.bool.optional.note({
+			desc: {
+				'ja-JP': 'ファイルが添付された投稿に限定するか否か'
+			}
+		}),
+
+		mediaOnly: $.bool.optional.note({
+			desc: {
+				'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
+			}
+		}),
+
+		limit: $.num.optional.range(1, 100).note({
+			default: 10
+		}),
+
+		sinceId: $.type(ID).optional.note({}),
+
+		untilId: $.type(ID).optional.note({}),
+
+		sinceDate: $.num.optional.note({}),
+
+		untilDate: $.num.optional.note({}),
+	}
+};
 
-/**
- * Get timeline of global
- */
 export default async (params: any, user: ILocalUser) => {
-	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
-	if (limitErr) throw 'invalid limit param';
-
-	// Get 'sinceId' parameter
-	const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId);
-	if (sinceIdErr) throw 'invalid sinceId param';
-
-	// Get 'untilId' parameter
-	const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId);
-	if (untilIdErr) throw 'invalid untilId param';
-
-	// Get 'sinceDate' parameter
-	const [sinceDate, sinceDateErr] = $.num.optional.get(params.sinceDate);
-	if (sinceDateErr) throw 'invalid sinceDate param';
-
-	// Get 'untilDate' parameter
-	const [untilDate, untilDateErr] = $.num.optional.get(params.untilDate);
-	if (untilDateErr) throw 'invalid untilDate param';
+	const [ps, psErr] = getParams(meta, params);
+	if (psErr) throw psErr;
 
 	// Check if only one of sinceId, untilId, sinceDate, untilDate specified
-	if ([sinceId, untilId, sinceDate, untilDate].filter(x => x != null).length > 1) {
+	if ([ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate].filter(x => x != null).length > 1) {
 		throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified';
 	}
 
-	// Get 'withFiles' parameter
-	const [withFiles, withFilesErr] = $.bool.optional.get(params.withFiles);
-	if (withFilesErr) throw 'invalid withFiles param';
-
 	// ミュートしているユーザーを取得
 	const mutedUserIds = user ? (await Mute.find({
 		muterId: user._id
@@ -68,27 +77,29 @@ export default async (params: any, user: ILocalUser) => {
 		};
 	}
 
+	const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly;
+
 	if (withFiles) {
 		query.fileIds = { $exists: true, $ne: [] };
 	}
 
-	if (sinceId) {
+	if (ps.sinceId) {
 		sort._id = 1;
 		query._id = {
-			$gt: sinceId
+			$gt: ps.sinceId
 		};
-	} else if (untilId) {
+	} else if (ps.untilId) {
 		query._id = {
-			$lt: untilId
+			$lt: ps.untilId
 		};
-	} else if (sinceDate) {
+	} else if (ps.sinceDate) {
 		sort._id = 1;
 		query.createdAt = {
-			$gt: new Date(sinceDate)
+			$gt: new Date(ps.sinceDate)
 		};
-	} else if (untilDate) {
+	} else if (ps.untilDate) {
 		query.createdAt = {
-			$lt: new Date(untilDate)
+			$lt: new Date(ps.untilDate)
 		};
 	}
 	//#endregion
@@ -96,7 +107,7 @@ export default async (params: any, user: ILocalUser) => {
 	// Issue query
 	const timeline = await Note
 		.find(query, {
-			limit: limit,
+			limit: ps.limit,
 			sort: sort
 		});
 
diff --git a/src/server/api/endpoints/notes/hybrid-timeline.ts b/src/server/api/endpoints/notes/hybrid-timeline.ts
index 1060792683..16cec86797 100644
--- a/src/server/api/endpoints/notes/hybrid-timeline.ts
+++ b/src/server/api/endpoints/notes/hybrid-timeline.ts
@@ -7,8 +7,6 @@ import { ILocalUser } from '../../../../models/user';
 import getParams from '../../get-params';
 
 export const meta = {
-	name: 'notes/hybrid-timeline',
-
 	desc: {
 		'ja-JP': 'ハイブリッドタイムラインを取得します。'
 	},
@@ -68,7 +66,13 @@ export const meta = {
 
 		withFiles: $.bool.optional.note({
 			desc: {
-				'ja-JP': 'true にすると、メディアが添付された投稿だけ取得します'
+				'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します'
+			}
+		}),
+
+		mediaOnly: $.bool.optional.note({
+			desc: {
+				'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
 			}
 		}),
 	}
@@ -203,7 +207,7 @@ export default async (params: any, user: ILocalUser) => {
 		});
 	}
 
-	if (ps.withFiles) {
+	if (ps.withFiles || ps.mediaOnly) {
 		query.$and.push({
 			fileIds: { $exists: true, $ne: [] }
 		});
diff --git a/src/server/api/endpoints/notes/local-timeline.ts b/src/server/api/endpoints/notes/local-timeline.ts
index 018e636ab5..2458a70556 100644
--- a/src/server/api/endpoints/notes/local-timeline.ts
+++ b/src/server/api/endpoints/notes/local-timeline.ts
@@ -3,40 +3,49 @@ import Note from '../../../../models/note';
 import Mute from '../../../../models/mute';
 import { pack } from '../../../../models/note';
 import { ILocalUser } from '../../../../models/user';
+import getParams from '../../get-params';
+
+export const meta = {
+	desc: {
+		'ja-JP': 'ローカルタイムラインを取得します。'
+	},
+
+	params: {
+		withFiles: $.bool.optional.note({
+			desc: {
+				'ja-JP': 'ファイルが添付された投稿に限定するか否か'
+			}
+		}),
+
+		mediaOnly: $.bool.optional.note({
+			desc: {
+				'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
+			}
+		}),
+
+		limit: $.num.optional.range(1, 100).note({
+			default: 10
+		}),
+
+		sinceId: $.type(ID).optional.note({}),
+
+		untilId: $.type(ID).optional.note({}),
+
+		sinceDate: $.num.optional.note({}),
+
+		untilDate: $.num.optional.note({}),
+	}
+};
 
-/**
- * Get timeline of local
- */
 export default async (params: any, user: ILocalUser) => {
-	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
-	if (limitErr) throw 'invalid limit param';
-
-	// Get 'sinceId' parameter
-	const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId);
-	if (sinceIdErr) throw 'invalid sinceId param';
-
-	// Get 'untilId' parameter
-	const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId);
-	if (untilIdErr) throw 'invalid untilId param';
-
-	// Get 'sinceDate' parameter
-	const [sinceDate, sinceDateErr] = $.num.optional.get(params.sinceDate);
-	if (sinceDateErr) throw 'invalid sinceDate param';
-
-	// Get 'untilDate' parameter
-	const [untilDate, untilDateErr] = $.num.optional.get(params.untilDate);
-	if (untilDateErr) throw 'invalid untilDate param';
+	const [ps, psErr] = getParams(meta, params);
+	if (psErr) throw psErr;
 
 	// Check if only one of sinceId, untilId, sinceDate, untilDate specified
-	if ([sinceId, untilId, sinceDate, untilDate].filter(x => x != null).length > 1) {
+	if ([ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate].filter(x => x != null).length > 1) {
 		throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified';
 	}
 
-	// Get 'withFiles' parameter
-	const [withFiles, withFilesErr] = $.bool.optional.get(params.withFiles);
-	if (withFilesErr) throw 'invalid withFiles param';
-
 	// ミュートしているユーザーを取得
 	const mutedUserIds = user ? (await Mute.find({
 		muterId: user._id
@@ -69,27 +78,29 @@ export default async (params: any, user: ILocalUser) => {
 		};
 	}
 
+	const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly;
+
 	if (withFiles) {
 		query.fileIds = { $exists: true, $ne: [] };
 	}
 
-	if (sinceId) {
+	if (ps.sinceId) {
 		sort._id = 1;
 		query._id = {
-			$gt: sinceId
+			$gt: ps.sinceId
 		};
-	} else if (untilId) {
+	} else if (ps.untilId) {
 		query._id = {
-			$lt: untilId
+			$lt: ps.untilId
 		};
-	} else if (sinceDate) {
+	} else if (ps.sinceDate) {
 		sort._id = 1;
 		query.createdAt = {
-			$gt: new Date(sinceDate)
+			$gt: new Date(ps.sinceDate)
 		};
-	} else if (untilDate) {
+	} else if (ps.untilDate) {
 		query.createdAt = {
-			$lt: new Date(untilDate)
+			$lt: new Date(ps.untilDate)
 		};
 	}
 	//#endregion
@@ -97,7 +108,7 @@ export default async (params: any, user: ILocalUser) => {
 	// Issue query
 	const timeline = await Note
 		.find(query, {
-			limit: limit,
+			limit: ps.limit,
 			sort: sort
 		});
 
diff --git a/src/server/api/endpoints/notes/search_by_tag.ts b/src/server/api/endpoints/notes/search_by_tag.ts
index f982dc01e9..82f11a9775 100644
--- a/src/server/api/endpoints/notes/search_by_tag.ts
+++ b/src/server/api/endpoints/notes/search_by_tag.ts
@@ -4,119 +4,152 @@ import User, { ILocalUser } from '../../../../models/user';
 import Mute from '../../../../models/mute';
 import { getFriendIds } from '../../common/get-friends';
 import { pack } from '../../../../models/note';
+import getParams from '../../get-params';
+
+export const meta = {
+	desc: {
+		'ja-JP': '指定されたタグが付けられた投稿を取得します。'
+	},
+
+	params: {
+		tag: $.str.note({
+			desc: {
+				'ja-JP': 'タグ'
+			}
+		}),
+
+		includeUserIds: $.arr($.type(ID)).optional.note({
+			default: []
+		}),
+
+		excludeUserIds: $.arr($.type(ID)).optional.note({
+			default: []
+		}),
+
+		includeUserUsernames: $.arr($.str).optional.note({
+			default: []
+		}),
+
+		excludeUserUsernames: $.arr($.str).optional.note({
+			default: []
+		}),
+
+		following: $.bool.optional.nullable.note({
+			default: null
+		}),
+
+		mute: $.str.optional.note({
+			default: 'mute_all'
+		}),
+
+		reply: $.bool.optional.nullable.note({
+			default: null,
+
+			desc: {
+				'ja-JP': '返信に限定するか否か'
+			}
+		}),
+
+		renote: $.bool.optional.nullable.note({
+			default: null,
+
+			desc: {
+				'ja-JP': 'Renoteに限定するか否か'
+			}
+		}),
+
+		withFiles: $.bool.optional.nullable.note({
+			default: null,
+
+			desc: {
+				'ja-JP': 'ファイルが添付された投稿に限定するか否か'
+			}
+		}),
+
+		media: $.bool.optional.nullable.note({
+			default: null,
+
+			desc: {
+				'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
+			}
+		}),
+
+		poll: $.bool.optional.nullable.note({
+			default: null,
+
+			desc: {
+				'ja-JP': 'アンケートが添付された投稿に限定するか否か'
+			}
+		}),
+
+		sinceDate: $.num.optional.note({
+		}),
+
+		untilDate: $.num.optional.note({
+		}),
+
+		offset: $.num.optional.min(0).note({
+			default: 0
+		}),
+
+		limit: $.num.optional.range(1, 30).note({
+			default: 10
+		}),
+	}
+};
 
-/**
- * Search notes by tag
- */
 export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
-	// Get 'tag' parameter
-	const [tag, tagError] = $.str.get(params.tag);
-	if (tagError) return rej('invalid tag param');
+	const [ps, psErr] = getParams(meta, params);
+	if (psErr) throw psErr;
 
-	// Get 'includeUserIds' parameter
-	const [includeUserIds = [], includeUserIdsErr] = $.arr($.type(ID)).optional.get(params.includeUserIds);
-	if (includeUserIdsErr) return rej('invalid includeUserIds param');
-
-	// Get 'excludeUserIds' parameter
-	const [excludeUserIds = [], excludeUserIdsErr] = $.arr($.type(ID)).optional.get(params.excludeUserIds);
-	if (excludeUserIdsErr) return rej('invalid excludeUserIds param');
-
-	// Get 'includeUserUsernames' parameter
-	const [includeUserUsernames = [], includeUserUsernamesErr] = $.arr($.str).optional.get(params.includeUserUsernames);
-	if (includeUserUsernamesErr) return rej('invalid includeUserUsernames param');
-
-	// Get 'excludeUserUsernames' parameter
-	const [excludeUserUsernames = [], excludeUserUsernamesErr] = $.arr($.str).optional.get(params.excludeUserUsernames);
-	if (excludeUserUsernamesErr) return rej('invalid excludeUserUsernames param');
-
-	// Get 'following' parameter
-	const [following = null, followingErr] = $.bool.optional.nullable.get(params.following);
-	if (followingErr) return rej('invalid following param');
-
-	// Get 'mute' parameter
-	const [mute = 'mute_all', muteErr] = $.str.optional.get(params.mute);
-	if (muteErr) return rej('invalid mute param');
-
-	// Get 'reply' parameter
-	const [reply = null, replyErr] = $.bool.optional.nullable.get(params.reply);
-	if (replyErr) return rej('invalid reply param');
-
-	// Get 'renote' parameter
-	const [renote = null, renoteErr] = $.bool.optional.nullable.get(params.renote);
-	if (renoteErr) return rej('invalid renote param');
-
-	// Get 'withFiles' parameter
-	const [withFiles = null, withFilesErr] = $.bool.optional.nullable.get(params.withFiles);
-	if (withFilesErr) return rej('invalid withFiles param');
-
-	// Get 'poll' parameter
-	const [poll = null, pollErr] = $.bool.optional.nullable.get(params.poll);
-	if (pollErr) return rej('invalid poll param');
-
-	// Get 'sinceDate' parameter
-	const [sinceDate, sinceDateErr] = $.num.optional.get(params.sinceDate);
-	if (sinceDateErr) throw 'invalid sinceDate param';
-
-	// Get 'untilDate' parameter
-	const [untilDate, untilDateErr] = $.num.optional.get(params.untilDate);
-	if (untilDateErr) throw 'invalid untilDate param';
-
-	// Get 'offset' parameter
-	const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset);
-	if (offsetErr) return rej('invalid offset param');
-
-	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional.range(1, 30).get(params.limit);
-	if (limitErr) return rej('invalid limit param');
-
-	if (includeUserUsernames != null) {
-		const ids = (await Promise.all(includeUserUsernames.map(async (username) => {
+	if (ps.includeUserUsernames != null) {
+		const ids = (await Promise.all(ps.includeUserUsernames.map(async (username) => {
 			const _user = await User.findOne({
 				usernameLower: username.toLowerCase()
 			});
 			return _user ? _user._id : null;
 		}))).filter(id => id != null);
 
-		ids.forEach(id => includeUserIds.push(id));
+		ids.forEach(id => ps.includeUserIds.push(id));
 	}
 
-	if (excludeUserUsernames != null) {
-		const ids = (await Promise.all(excludeUserUsernames.map(async (username) => {
+	if (ps.excludeUserUsernames != null) {
+		const ids = (await Promise.all(ps.excludeUserUsernames.map(async (username) => {
 			const _user = await User.findOne({
 				usernameLower: username.toLowerCase()
 			});
 			return _user ? _user._id : null;
 		}))).filter(id => id != null);
 
-		ids.forEach(id => excludeUserIds.push(id));
+		ids.forEach(id => ps.excludeUserIds.push(id));
 	}
 
 	let q: any = {
 		$and: [{
-			tagsLower: tag.toLowerCase()
+			tagsLower: ps.tag.toLowerCase()
 		}]
 	};
 
 	const push = (x: any) => q.$and.push(x);
 
-	if (includeUserIds && includeUserIds.length != 0) {
+	if (ps.includeUserIds && ps.includeUserIds.length != 0) {
 		push({
 			userId: {
-				$in: includeUserIds
+				$in: ps.includeUserIds
 			}
 		});
-	} else if (excludeUserIds && excludeUserIds.length != 0) {
+	} else if (ps.excludeUserIds && ps.excludeUserIds.length != 0) {
 		push({
 			userId: {
-				$nin: excludeUserIds
+				$nin: ps.excludeUserIds
 			}
 		});
 	}
 
-	if (following != null && me != null) {
+	if (ps.following != null && me != null) {
 		const ids = await getFriendIds(me._id, false);
 		push({
-			userId: following ? {
+			userId: ps.following ? {
 				$in: ids
 			} : {
 				$nin: ids.concat(me._id)
@@ -131,7 +164,7 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
 		});
 		const mutedUserIds = mutes.map(m => m.muteeId);
 
-		switch (mute) {
+		switch (ps.mute) {
 			case 'mute_all':
 				push({
 					userId: {
@@ -202,8 +235,8 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
 		}
 	}
 
-	if (reply != null) {
-		if (reply) {
+	if (ps.reply != null) {
+		if (ps.reply) {
 			push({
 				replyId: {
 					$exists: true,
@@ -223,8 +256,8 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
 		}
 	}
 
-	if (renote != null) {
-		if (renote) {
+	if (ps.renote != null) {
+		if (ps.renote) {
 			push({
 				renoteId: {
 					$exists: true,
@@ -244,6 +277,8 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
 		}
 	}
 
+	const withFiles = ps.withFiles != null ? ps.withFiles : ps.media;
+
 	if (withFiles != null) {
 		if (withFiles) {
 			push({
@@ -265,8 +300,8 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
 		}
 	}
 
-	if (poll != null) {
-		if (poll) {
+	if (ps.poll != null) {
+		if (ps.poll) {
 			push({
 				poll: {
 					$exists: true,
@@ -286,18 +321,18 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
 		}
 	}
 
-	if (sinceDate) {
+	if (ps.sinceDate) {
 		push({
 			createdAt: {
-				$gt: new Date(sinceDate)
+				$gt: new Date(ps.sinceDate)
 			}
 		});
 	}
 
-	if (untilDate) {
+	if (ps.untilDate) {
 		push({
 			createdAt: {
-				$lt: new Date(untilDate)
+				$lt: new Date(ps.untilDate)
 			}
 		});
 	}
@@ -312,8 +347,8 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
 			sort: {
 				_id: -1
 			},
-			limit: limit,
-			skip: offset
+			limit: ps.limit,
+			skip: ps.offset
 		});
 
 	// Serialize
diff --git a/src/server/api/endpoints/notes/timeline.ts b/src/server/api/endpoints/notes/timeline.ts
index 145f648c56..089e7a182a 100644
--- a/src/server/api/endpoints/notes/timeline.ts
+++ b/src/server/api/endpoints/notes/timeline.ts
@@ -69,7 +69,13 @@ export const meta = {
 
 		withFiles: $.bool.optional.note({
 			desc: {
-				'ja-JP': 'true にすると、メディアが添付された投稿だけ取得します'
+				'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します'
+			}
+		}),
+
+		mediaOnly: $.bool.optional.note({
+			desc: {
+				'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
 			}
 		}),
 	}
@@ -193,7 +199,9 @@ export default async (params: any, user: ILocalUser) => {
 		});
 	}
 
-	if (ps.withFiles) {
+	const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly;
+
+	if (withFiles) {
 		query.$and.push({
 			fileIds: { $exists: true, $ne: [] }
 		});
diff --git a/src/server/api/endpoints/notes/user-list-timeline.ts b/src/server/api/endpoints/notes/user-list-timeline.ts
index e00a7de371..61192d7d3e 100644
--- a/src/server/api/endpoints/notes/user-list-timeline.ts
+++ b/src/server/api/endpoints/notes/user-list-timeline.ts
@@ -75,7 +75,13 @@ export const meta = {
 
 		withFiles: $.bool.optional.note({
 			desc: {
-				'ja-JP': 'true にすると、メディアが添付された投稿だけ取得します'
+				'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します'
+			}
+		}),
+
+		mediaOnly: $.bool.optional.note({
+			desc: {
+				'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
 			}
 		}),
 	}
@@ -199,7 +205,9 @@ export default async (params: any, user: ILocalUser) => {
 		});
 	}
 
-	if (ps.withFiles) {
+	const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly;
+
+	if (withFiles) {
 		query.$and.push({
 			fileIds: { $exists: true, $ne: [] }
 		});
diff --git a/src/server/api/endpoints/users/notes.ts b/src/server/api/endpoints/users/notes.ts
index d894e52dba..42c31189d6 100644
--- a/src/server/api/endpoints/users/notes.ts
+++ b/src/server/api/endpoints/users/notes.ts
@@ -2,63 +2,121 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
 import getHostLower from '../../common/get-host-lower';
 import Note, { pack } from '../../../../models/note';
 import User, { ILocalUser } from '../../../../models/user';
+import getParams from '../../get-params';
+
+export const meta = {
+	desc: {
+		'ja-JP': '指定したユーザーのタイムラインを取得します。'
+	},
+
+	params: {
+		userId: $.type(ID).optional.note({
+			desc: {
+				'ja-JP': 'ユーザーID'
+			}
+		}),
+
+		username: $.str.optional.note({
+			desc: {
+				'ja-JP': 'ユーザー名'
+			}
+		}),
+
+		host: $.str.optional.note({
+		}),
+
+		includeReplies: $.bool.optional.note({
+			default: true,
+
+			desc: {
+				'ja-JP': 'リプライを含めるか否か'
+			}
+		}),
+
+		limit: $.num.optional.range(1, 100).note({
+			default: 10,
+			desc: {
+				'ja-JP': '最大数'
+			}
+		}),
+
+		sinceId: $.type(ID).optional.note({
+			desc: {
+				'ja-JP': '指定すると、この投稿を基点としてより新しい投稿を取得します'
+			}
+		}),
+
+		untilId: $.type(ID).optional.note({
+			desc: {
+				'ja-JP': '指定すると、この投稿を基点としてより古い投稿を取得します'
+			}
+		}),
+
+		sinceDate: $.num.optional.note({
+			desc: {
+				'ja-JP': '指定した時間を基点としてより新しい投稿を取得します。数値は、1970年1月1日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。'
+			}
+		}),
+
+		untilDate: $.num.optional.note({
+			desc: {
+				'ja-JP': '指定した時間を基点としてより古い投稿を取得します。数値は、1970年1月1日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。'
+			}
+		}),
+
+		includeMyRenotes: $.bool.optional.note({
+			default: true,
+			desc: {
+				'ja-JP': '自分の行ったRenoteを含めるかどうか'
+			}
+		}),
+
+		includeRenotedMyNotes: $.bool.optional.note({
+			default: true,
+			desc: {
+				'ja-JP': 'Renoteされた自分の投稿を含めるかどうか'
+			}
+		}),
+
+		includeLocalRenotes: $.bool.optional.note({
+			default: true,
+			desc: {
+				'ja-JP': 'Renoteされたローカルの投稿を含めるかどうか'
+			}
+		}),
+
+		withFiles: $.bool.optional.note({
+			default: false,
+			desc: {
+				'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します'
+			}
+		}),
+
+		mediaOnly: $.bool.optional.note({
+			default: false,
+			desc: {
+				'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
+			}
+		}),
+	}
+};
 
-/**
- * Get notes of a user
- */
 export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
-	// Get 'userId' parameter
-	const [userId, userIdErr] = $.type(ID).optional.get(params.userId);
-	if (userIdErr) return rej('invalid userId param');
+	const [ps, psErr] = getParams(meta, params);
+	if (psErr) throw psErr;
 
-	// Get 'username' parameter
-	const [username, usernameErr] = $.str.optional.get(params.username);
-	if (usernameErr) return rej('invalid username param');
-
-	if (userId === undefined && username === undefined) {
+	if (ps.userId === undefined && ps.username === undefined) {
 		return rej('userId or username is required');
 	}
 
-	// Get 'host' parameter
-	const [host, hostErr] = $.str.optional.get(params.host);
-	if (hostErr) return rej('invalid host param');
-
-	// Get 'includeReplies' parameter
-	const [includeReplies = true, includeRepliesErr] = $.bool.optional.get(params.includeReplies);
-	if (includeRepliesErr) return rej('invalid includeReplies param');
-
-	// Get 'withFiles' parameter
-	const [withFiles = false, withFilesErr] = $.bool.optional.get(params.withFiles);
-	if (withFilesErr) return rej('invalid withFiles param');
-
-	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
-	if (limitErr) return rej('invalid limit param');
-
-	// Get 'sinceId' parameter
-	const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId);
-	if (sinceIdErr) return rej('invalid sinceId param');
-
-	// Get 'untilId' parameter
-	const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId);
-	if (untilIdErr) return rej('invalid untilId param');
-
-	// Get 'sinceDate' parameter
-	const [sinceDate, sinceDateErr] = $.num.optional.get(params.sinceDate);
-	if (sinceDateErr) throw 'invalid sinceDate param';
-
-	// Get 'untilDate' parameter
-	const [untilDate, untilDateErr] = $.num.optional.get(params.untilDate);
-	if (untilDateErr) throw 'invalid untilDate param';
-
 	// Check if only one of sinceId, untilId, sinceDate, untilDate specified
-	if ([sinceId, untilId, sinceDate, untilDate].filter(x => x != null).length > 1) {
+	if ([ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate].filter(x => x != null).length > 1) {
 		throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified';
 	}
 
-	const q = userId !== undefined
-		? { _id: userId }
-		: { usernameLower: username.toLowerCase(), host: getHostLower(host) } ;
+	const q = ps.userId !== undefined
+		? { _id: ps.userId }
+		: { usernameLower: ps.username.toLowerCase(), host: getHostLower(ps.host) } ;
 
 	// Lookup user
 	const user = await User.findOne(q, {
@@ -80,30 +138,32 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
 		userId: user._id
 	} as any;
 
-	if (sinceId) {
+	if (ps.sinceId) {
 		sort._id = 1;
 		query._id = {
-			$gt: sinceId
+			$gt: ps.sinceId
 		};
-	} else if (untilId) {
+	} else if (ps.untilId) {
 		query._id = {
-			$lt: untilId
+			$lt: ps.untilId
 		};
-	} else if (sinceDate) {
+	} else if (ps.sinceDate) {
 		sort._id = 1;
 		query.createdAt = {
-			$gt: new Date(sinceDate)
+			$gt: new Date(ps.sinceDate)
 		};
-	} else if (untilDate) {
+	} else if (ps.untilDate) {
 		query.createdAt = {
-			$lt: new Date(untilDate)
+			$lt: new Date(ps.untilDate)
 		};
 	}
 
-	if (!includeReplies) {
+	if (!ps.includeReplies) {
 		query.replyId = null;
 	}
 
+	const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly;
+
 	if (withFiles) {
 		query.fileIds = {
 			$exists: true,
@@ -115,12 +175,10 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
 	// Issue query
 	const notes = await Note
 		.find(query, {
-			limit: limit,
+			limit: ps.limit,
 			sort: sort
 		});
 
 	// Serialize
-	res(await Promise.all(notes.map(async (note) =>
-		await pack(note, me)
-	)));
+	res(await Promise.all(notes.map(note => pack(note, me))));
 });