summaryrefslogtreecommitdiff
path: root/src/server/api/endpoints/notes/timeline.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/api/endpoints/notes/timeline.ts')
-rw-r--r--src/server/api/endpoints/notes/timeline.ts148
1 files changed, 109 insertions, 39 deletions
diff --git a/src/server/api/endpoints/notes/timeline.ts b/src/server/api/endpoints/notes/timeline.ts
index b5feaac817..78786d4a16 100644
--- a/src/server/api/endpoints/notes/timeline.ts
+++ b/src/server/api/endpoints/notes/timeline.ts
@@ -1,12 +1,11 @@
/**
* Module dependencies
*/
-import $ from 'cafy';
-import rap from '@prezzemolo/rap';
+import $ from 'cafy'; import ID from '../../../../cafy-id';
import Note from '../../../../models/note';
import Mute from '../../../../models/mute';
import ChannelWatching from '../../../../models/channel-watching';
-import getFriends from '../../common/get-friends';
+import { getFriends } from '../../common/get-friends';
import { pack } from '../../../../models/note';
/**
@@ -14,23 +13,23 @@ import { pack } from '../../../../models/note';
*/
module.exports = async (params, user, app) => {
// Get 'limit' parameter
- const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$;
+ const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
if (limitErr) throw 'invalid limit param';
// Get 'sinceId' parameter
- const [sinceId, sinceIdErr] = $(params.sinceId).optional.id().$;
+ const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId);
if (sinceIdErr) throw 'invalid sinceId param';
// Get 'untilId' parameter
- const [untilId, untilIdErr] = $(params.untilId).optional.id().$;
+ const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId);
if (untilIdErr) throw 'invalid untilId param';
// Get 'sinceDate' parameter
- const [sinceDate, sinceDateErr] = $(params.sinceDate).optional.number().$;
+ const [sinceDate, sinceDateErr] = $.num.optional().get(params.sinceDate);
if (sinceDateErr) throw 'invalid sinceDate param';
// Get 'untilDate' parameter
- const [untilDate, untilDateErr] = $(params.untilDate).optional.number().$;
+ const [untilDate, untilDateErr] = $.num.optional().get(params.untilDate);
if (untilDateErr) throw 'invalid untilDate param';
// Check if only one of sinceId, untilId, sinceDate, untilDate specified
@@ -38,59 +37,130 @@ module.exports = async (params, user, app) => {
throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified';
}
- const { followingIds, watchingChannelIds, mutedUserIds } = await rap({
- // ID list of the user itself and other users who the user follows
- followingIds: getFriends(user._id),
+ // Get 'includeMyRenotes' parameter
+ const [includeMyRenotes = true, includeMyRenotesErr] = $.bool.optional().get(params.includeMyRenotes);
+ if (includeMyRenotesErr) throw 'invalid includeMyRenotes param';
+
+ // Get 'includeRenotedMyNotes' parameter
+ const [includeRenotedMyNotes = true, includeRenotedMyNotesErr] = $.bool.optional().get(params.includeRenotedMyNotes);
+ if (includeRenotedMyNotesErr) throw 'invalid includeRenotedMyNotes param';
+
+ const [followings, watchingChannelIds, mutedUserIds] = await Promise.all([
+ // フォローを取得
+ // Fetch following
+ getFriends(user._id),
// Watchしているチャンネルを取得
- watchingChannelIds: ChannelWatching.find({
+ ChannelWatching.find({
userId: user._id,
// 削除されたドキュメントは除く
deletedAt: { $exists: false }
}).then(watches => watches.map(w => w.channelId)),
// ミュートしているユーザーを取得
- mutedUserIds: Mute.find({
+ Mute.find({
muterId: user._id
}).then(ms => ms.map(m => m.muteeId))
- });
+ ]);
//#region Construct query
const sort = {
_id: -1
};
- const query = {
+ const followQuery = followings.map(f => f.stalk ? {
+ userId: f.id
+ } : {
+ userId: f.id,
+
+ // ストーキングしてないならリプライは含めない(ただし投稿者自身の投稿へのリプライ、自分の投稿へのリプライ、自分のリプライは含める)
$or: [{
- // フォローしている人のタイムラインへの投稿
- userId: {
- $in: followingIds
- },
- // 「タイムラインへの」投稿に限定するためにチャンネルが指定されていないもののみに限る
+ // リプライでない
+ replyId: null
+ }, { // または
+ // リプライだが返信先が投稿者自身の投稿
+ $expr: {
+ $eq: ['$_reply.userId', '$userId']
+ }
+ }, { // または
+ // リプライだが返信先が自分(フォロワー)の投稿
+ '_reply.userId': user._id
+ }, { // または
+ // 自分(フォロワー)が送信したリプライ
+ userId: user._id
+ }]
+ });
+
+ const query = {
+ $and: [{
$or: [{
+ $and: [{
+ // フォローしている人のタイムラインへの投稿
+ $or: followQuery
+ }, {
+ // 「タイムラインへの」投稿に限定するためにチャンネルが指定されていないもののみに限る
+ $or: [{
+ channelId: {
+ $exists: false
+ }
+ }, {
+ channelId: null
+ }]
+ }]
+ }, {
+ // Watchしているチャンネルへの投稿
channelId: {
- $exists: false
+ $in: watchingChannelIds
}
+ }],
+ // mute
+ userId: {
+ $nin: mutedUserIds
+ },
+ '_reply.userId': {
+ $nin: mutedUserIds
+ },
+ '_renote.userId': {
+ $nin: mutedUserIds
+ },
+ }]
+ } as any;
+
+ // MongoDBではトップレベルで否定ができないため、De Morganの法則を利用してクエリします。
+ // つまり、「『自分の投稿かつRenote』ではない」を「『自分の投稿ではない』または『Renoteではない』」と表現します。
+ // for details: https://en.wikipedia.org/wiki/De_Morgan%27s_laws
+
+ if (includeMyRenotes === false) {
+ query.$and.push({
+ $or: [{
+ userId: { $ne: user._id }
+ }, {
+ renoteId: null
+ }, {
+ text: { $ne: null }
+ }, {
+ mediaIds: { $ne: [] }
}, {
- channelId: null
+ poll: { $ne: null }
}]
- }, {
- // Watchしているチャンネルへの投稿
- channelId: {
- $in: watchingChannelIds
- }
- }],
- // mute
- userId: {
- $nin: mutedUserIds
- },
- '_reply.userId': {
- $nin: mutedUserIds
- },
- '_renote.userId': {
- $nin: mutedUserIds
- },
- } as any;
+ });
+ }
+
+ if (includeRenotedMyNotes === false) {
+ query.$and.push({
+ $or: [{
+ '_renote.userId': { $ne: user._id }
+ }, {
+ renoteId: null
+ }, {
+ text: { $ne: null }
+ }, {
+ mediaIds: { $ne: [] }
+ }, {
+ poll: { $ne: null }
+ }]
+ });
+ }
if (sinceId) {
sort._id = 1;