summaryrefslogtreecommitdiff
path: root/src/api/endpoints/posts/search.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/api/endpoints/posts/search.ts')
-rw-r--r--src/api/endpoints/posts/search.ts394
1 files changed, 315 insertions, 79 deletions
diff --git a/src/api/endpoints/posts/search.ts b/src/api/endpoints/posts/search.ts
index b434f64342..6e26f55390 100644
--- a/src/api/endpoints/posts/search.ts
+++ b/src/api/endpoints/posts/search.ts
@@ -1,12 +1,13 @@
/**
* Module dependencies
*/
-import * as mongo from 'mongodb';
import $ from 'cafy';
const escapeRegexp = require('escape-regexp');
import Post from '../../models/post';
-import serialize from '../../serializers/post';
-import config from '../../../conf';
+import User from '../../models/user';
+import Mute from '../../models/mute';
+import getFriends from '../../common/get-friends';
+import { pack } from '../../models/post';
/**
* Search a post
@@ -16,104 +17,339 @@ import config from '../../../conf';
* @return {Promise<any>}
*/
module.exports = (params, me) => new Promise(async (res, rej) => {
- // Get 'query' parameter
- const [query, queryError] = $(params.query).string().pipe(x => x != '').$;
- if (queryError) return rej('invalid query param');
+ // Get 'text' parameter
+ const [text, textError] = $(params.text).optional.string().$;
+ if (textError) return rej('invalid text param');
+
+ // Get 'include_user_ids' parameter
+ const [includeUserIds = [], includeUserIdsErr] = $(params.include_user_ids).optional.array('id').$;
+ if (includeUserIdsErr) return rej('invalid include_user_ids param');
+
+ // Get 'exclude_user_ids' parameter
+ const [excludeUserIds = [], excludeUserIdsErr] = $(params.exclude_user_ids).optional.array('id').$;
+ if (excludeUserIdsErr) return rej('invalid exclude_user_ids param');
+
+ // Get 'include_user_usernames' parameter
+ const [includeUserUsernames = [], includeUserUsernamesErr] = $(params.include_user_usernames).optional.array('string').$;
+ if (includeUserUsernamesErr) return rej('invalid include_user_usernames param');
+
+ // Get 'exclude_user_usernames' parameter
+ const [excludeUserUsernames = [], excludeUserUsernamesErr] = $(params.exclude_user_usernames).optional.array('string').$;
+ if (excludeUserUsernamesErr) return rej('invalid exclude_user_usernames param');
+
+ // Get 'following' parameter
+ const [following = null, followingErr] = $(params.following).optional.nullable.boolean().$;
+ if (followingErr) return rej('invalid following param');
+
+ // Get 'mute' parameter
+ const [mute = 'mute_all', muteErr] = $(params.mute).optional.string().$;
+ if (muteErr) return rej('invalid mute param');
+
+ // Get 'reply' parameter
+ const [reply = null, replyErr] = $(params.reply).optional.nullable.boolean().$;
+ if (replyErr) return rej('invalid reply param');
+
+ // Get 'repost' parameter
+ const [repost = null, repostErr] = $(params.repost).optional.nullable.boolean().$;
+ if (repostErr) return rej('invalid repost param');
+
+ // Get 'media' parameter
+ const [media = null, mediaErr] = $(params.media).optional.nullable.boolean().$;
+ if (mediaErr) return rej('invalid media param');
+
+ // Get 'poll' parameter
+ const [poll = null, pollErr] = $(params.poll).optional.nullable.boolean().$;
+ if (pollErr) return rej('invalid poll param');
+
+ // Get 'since_date' parameter
+ const [sinceDate, sinceDateErr] = $(params.since_date).optional.number().$;
+ if (sinceDateErr) throw 'invalid since_date param';
+
+ // Get 'until_date' parameter
+ const [untilDate, untilDateErr] = $(params.until_date).optional.number().$;
+ if (untilDateErr) throw 'invalid until_date param';
// Get 'offset' parameter
const [offset = 0, offsetErr] = $(params.offset).optional.number().min(0).$;
if (offsetErr) return rej('invalid offset param');
- // Get 'max' parameter
- const [max = 10, maxErr] = $(params.max).optional.number().range(1, 30).$;
- if (maxErr) return rej('invalid max param');
+ // Get 'limit' parameter
+ const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 30).$;
+ if (limitErr) return rej('invalid limit param');
+
+ let includeUsers = includeUserIds;
+ if (includeUserUsernames != null) {
+ const ids = (await Promise.all(includeUserUsernames.map(async (username) => {
+ const _user = await User.findOne({
+ username_lower: username.toLowerCase()
+ });
+ return _user ? _user._id : null;
+ }))).filter(id => id != null);
+ includeUsers = includeUsers.concat(ids);
+ }
- // If Elasticsearch is available, search by $
- // If not, search by MongoDB
- (config.elasticsearch.enable ? byElasticsearch : byNative)
- (res, rej, me, query, offset, max);
+ let excludeUsers = excludeUserIds;
+ if (excludeUserUsernames != null) {
+ const ids = (await Promise.all(excludeUserUsernames.map(async (username) => {
+ const _user = await User.findOne({
+ username_lower: username.toLowerCase()
+ });
+ return _user ? _user._id : null;
+ }))).filter(id => id != null);
+ excludeUsers = excludeUsers.concat(ids);
+ }
+
+ search(res, rej, me, text, includeUsers, excludeUsers, following,
+ mute, reply, repost, media, poll, sinceDate, untilDate, offset, limit);
});
-// Search by MongoDB
-async function byNative(res, rej, me, query, offset, max) {
- const escapedQuery = escapeRegexp(query);
+async function search(
+ res, rej, me, text, includeUserIds, excludeUserIds, following,
+ mute, reply, repost, media, poll, sinceDate, untilDate, offset, max) {
- // Search posts
- const posts = await Post
- .find({
- text: new RegExp(escapedQuery)
- }, {
- sort: {
- _id: -1
- },
- limit: max,
- skip: offset
- });
+ let q: any = {
+ $and: []
+ };
- // Serialize
- res(await Promise.all(posts.map(async post =>
- await serialize(post, me))));
-}
+ const push = x => q.$and.push(x);
-// Search by Elasticsearch
-async function byElasticsearch(res, rej, me, query, offset, max) {
- const es = require('../../db/elasticsearch');
+ if (text) {
+ // 完全一致検索
+ if (/"""(.+?)"""/.test(text)) {
+ const x = text.match(/"""(.+?)"""/)[1];
+ push({
+ text: x
+ });
+ } else {
+ push({
+ $and: text.split(' ').map(x => ({
+ // キーワードが-で始まる場合そのキーワードを除外する
+ text: x[0] == '-' ? {
+ $not: new RegExp(escapeRegexp(x.substr(1)))
+ } : new RegExp(escapeRegexp(x))
+ }))
+ });
+ }
+ }
- es.search({
- index: 'misskey',
- type: 'post',
- body: {
- size: max,
- from: offset,
- query: {
- simple_query_string: {
- fields: ['text'],
- query: query,
- default_operator: 'and'
- }
- },
- sort: [
- { _doc: 'desc' }
- ],
- highlight: {
- pre_tags: ['<mark>'],
- post_tags: ['</mark>'],
- encoder: 'html',
- fields: {
- text: {}
- }
+ if (includeUserIds && includeUserIds.length != 0) {
+ push({
+ user_id: {
+ $in: includeUserIds
}
+ });
+ } else if (excludeUserIds && excludeUserIds.length != 0) {
+ push({
+ user_id: {
+ $nin: excludeUserIds
+ }
+ });
+ }
+
+ if (following != null && me != null) {
+ const ids = await getFriends(me._id, false);
+ push({
+ user_id: following ? {
+ $in: ids
+ } : {
+ $nin: ids.concat(me._id)
+ }
+ });
+ }
+
+ if (me != null) {
+ const mutes = await Mute.find({
+ muter_id: me._id,
+ deleted_at: { $exists: false }
+ });
+ const mutedUserIds = mutes.map(m => m.mutee_id);
+
+ switch (mute) {
+ case 'mute_all':
+ push({
+ user_id: {
+ $nin: mutedUserIds
+ },
+ '_reply.user_id': {
+ $nin: mutedUserIds
+ },
+ '_repost.user_id': {
+ $nin: mutedUserIds
+ }
+ });
+ break;
+ case 'mute_related':
+ push({
+ '_reply.user_id': {
+ $nin: mutedUserIds
+ },
+ '_repost.user_id': {
+ $nin: mutedUserIds
+ }
+ });
+ break;
+ case 'mute_direct':
+ push({
+ user_id: {
+ $nin: mutedUserIds
+ }
+ });
+ break;
+ case 'direct_only':
+ push({
+ user_id: {
+ $in: mutedUserIds
+ }
+ });
+ break;
+ case 'related_only':
+ push({
+ $or: [{
+ '_reply.user_id': {
+ $in: mutedUserIds
+ }
+ }, {
+ '_repost.user_id': {
+ $in: mutedUserIds
+ }
+ }]
+ });
+ break;
+ case 'all_only':
+ push({
+ $or: [{
+ user_id: {
+ $in: mutedUserIds
+ }
+ }, {
+ '_reply.user_id': {
+ $in: mutedUserIds
+ }
+ }, {
+ '_repost.user_id': {
+ $in: mutedUserIds
+ }
+ }]
+ });
+ break;
}
- }, async (error, response) => {
- if (error) {
- console.error(error);
- return res(500);
- }
+ }
- if (response.hits.total === 0) {
- return res([]);
+ if (reply != null) {
+ if (reply) {
+ push({
+ reply_id: {
+ $exists: true,
+ $ne: null
+ }
+ });
+ } else {
+ push({
+ $or: [{
+ reply_id: {
+ $exists: false
+ }
+ }, {
+ reply_id: null
+ }]
+ });
}
+ }
- const hits = response.hits.hits.map(hit => new mongo.ObjectID(hit._id));
+ if (repost != null) {
+ if (repost) {
+ push({
+ repost_id: {
+ $exists: true,
+ $ne: null
+ }
+ });
+ } else {
+ push({
+ $or: [{
+ repost_id: {
+ $exists: false
+ }
+ }, {
+ repost_id: null
+ }]
+ });
+ }
+ }
- // Fetch found posts
- const posts = await Post
- .find({
- _id: {
- $in: hits
+ if (media != null) {
+ if (media) {
+ push({
+ media_ids: {
+ $exists: true,
+ $ne: null
}
- }, {
- sort: {
- _id: -1
+ });
+ } else {
+ push({
+ $or: [{
+ media_ids: {
+ $exists: false
+ }
+ }, {
+ media_ids: null
+ }]
+ });
+ }
+ }
+
+ if (poll != null) {
+ if (poll) {
+ push({
+ poll: {
+ $exists: true,
+ $ne: null
}
});
+ } else {
+ push({
+ $or: [{
+ poll: {
+ $exists: false
+ }
+ }, {
+ poll: null
+ }]
+ });
+ }
+ }
+
+ if (sinceDate) {
+ push({
+ created_at: {
+ $gt: new Date(sinceDate)
+ }
+ });
+ }
+
+ if (untilDate) {
+ push({
+ created_at: {
+ $lt: new Date(untilDate)
+ }
+ });
+ }
+
+ if (q.$and.length == 0) {
+ q = {};
+ }
- posts.map(post => {
- post._highlight = response.hits.hits.filter(hit => post._id.equals(hit._id))[0].highlight.text[0];
+ // Search posts
+ const posts = await Post
+ .find(q, {
+ sort: {
+ _id: -1
+ },
+ limit: max,
+ skip: offset
});
- // Serialize
- res(await Promise.all(posts.map(async post =>
- await serialize(post, me))));
- });
+ // Serialize
+ res(await Promise.all(posts.map(async post =>
+ await pack(post, me))));
}